TTOANN

490 lượt xem

Upload Multiple Files To Github

  • #Github
  • #Upload
8 phút đọc

Hôm nay chúng ta sẽ dùng Github để làm một kho lưu trữ file miễn phí (hình như mỗi tài khoản Github chỉ được 500MB dung lượng miễn phí, dùng hơn phải nâng cấp).

Ở bài viết này thì mình dùng để upload hình lên nhé, mà upload nhiều hình cùng một lúc.

# Bắt đầu.

Chúng ta cần gì ?

  1. Server (Chắc chắn rồi, chức năng là để nhận dữ liệu rồi gửi lên Github)
  2. Github Account (Không có thì sao làm được chức năng này)

# Github

Chúng ta cần phải làm một số việc với Github trước.
Truy cập Github rồi tiến hành đăng ký và đăng nhập vào.

github-step1

Chọn Settings

github-step2

Kéo xuống phía dưới chọn tiếp Developer settings

github-step3

Phần này mọi người chọn Personal access tokens chọn tiếp Tokens (classic) và chọn dòng chữ Generate a personal access token

github-step4

Đến trang này mọi người nhập thông tin vào.

  • note nhập token này dùng để làm gì.
  • Expiration chọn option No expiration.
  • Select scopes chỉ cần chọn option repo là đủ rồi.

Xong rồi thì kéo xuống dưới và chọn nút Generate Token màu xanh lá thôi.

github-step5

Xong việc mọi người sẽ nhận được token như trên hình, copy lại và để đâu đó xí chúng ta có việc cần dùng tới.

# Code.

Vào việc, mở Editor lên và code thôi.
Chúng ta sẽ cần tạo 1 project mới

yarn init

Enter n lần 🤣.

D:\*********\upload-multiple-files-to-github\code\api>yarn init
yarn init v1.22.19
question name (api):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
Done in 4.16s.

Giờ thì chúng ta cần install các package sau:

  1. Express (dùng để tạo http server)
  2. Multer (dùng để xử lý dữ liệu dạng multipart/form-data từ phía người dùng gửi lên)
  3. dotenv (Environment dùng để dấu các dữ liệu quan trọng, như là cái token khi nãy chúng ta tạo đó)
  4. Cors (Giúp không bị chặn Cors ở Server)
yarn add express multer dotenv cors

Chúng ta sẽ cấu trúc thư mục như này.

├── controller
│   └── uploadController.js
│
├── middleware
│   └── handleUpload.js
│
├── server.js
├── .env
├── node_modules
├── package.json
└── package-lock.json 

# Bắt đầu ở server.js

require('dotenv').config()
const express = require('express')

const app = express()
const port = 5000

// CORS
const cors = require('cors')
app.use(
    cors({
        origin: 'http://localhost:3000',
        credentials: true,
    })
)

app.post('/upload', (req, res) => { 
    res.status(200).json({ message: 'Ok' })
})

app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
})

Nôn na là tạo một http server với với chỉ một route /upload với method Post 😸.

Nhớ thêm vào package.json

"scripts": {
    "start": "node server.js"
}

Mọi người nhớ config env nữa nhé.

TOKEN_GITHUB_UPLOAD_IMAGES =  //copy cái token mọi người tạo ở trên vào đây

GITHUB_REPO = //tên tài khoản/repo mọi người muốn upload lên. Ex: toan/blog

Xong rồi giờ thì.

yarn start

yarn run v1.22.19
$ node server.js
Server đang chạy ở port:5000

Mở Postman lên và test liền thôi.

code-step1

Ok vậy là server đã hoạt động rồi.

# Middleware

handleUpload.js chúng ta sẽ có như sau:

// Multer
const multer = require('multer')
const handleUpload = (req, res, next) => {
    const maxSize = 1024 * 1024 * 3
    const upload = multer({
        limits: { fileSize: maxSize },
        fileFilter: (req, file, cb) => {
            if (file.mimetype !== 'image/png' && file.mimetype !== 'image/jpeg') {
                return cb(null, false)
            }
            cb(null, true)
        },
    }).array('images', 5)

    upload(req, res, function (err) {
        if (err) {
            return res.status(400).json({ message: 'Có lỗi xảy ra' + err.message })
        }
        next()
    })
}

module.exports = handleUpload

Lại nôn na tiếp là middlware dùng lọc các hình ảnh với kích thước không quá 3Mb và thuộc các định dạng image/pngimage/jpeg

# Controller.

uploadController.js chúng ta sẽ có như sau, mình chia từng phần ra nhé.

Khung thì sẽ như thế này.

async function uploadController(req, res) {
    try {
        //Chúng ta sẽ code ở đây....
    }
    catch (error) {
        return res.status(500).json({ message: 'Có lỗi xảy ra ' + error.message })
    }
}

module.exports = uploadController
# Check xem có file hay không
if (!req.files || Object.keys(req.files).length === 0)
    return res.status(400).json({ message: 'Vui lòng chọn hình ảnh' })

Rất đơn giản đúng không 😼😼😼.

# Nếu có file thì làm gì ???

Đầu tiên chúng ta sẽ tạo các blob cho các file

const files = req.files

const blobs = await Promise.all(
  files.map((file) => {
    const base64 = file.buffer.toString("base64")
    const data = {
      content: base64,
      encoding: "base64"
    }
    return fetch(`https://api.github.com/repos/${process.env.GITHUB_REPO}/git/blobs`, {
      method: "POST",
      headers: {
        Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
        "Content-type": "application/vnd.github+json"
      },
      body: JSON.stringify(data)
    })
  })
)
const blobsData = await Promise.all(blobs.map((blob) => blob.json()))
const blobsSHA = blobsData.map((blob) => blob.sha)

Xong rồi thì đến tạo tree từ blob đã tạo ở trên.

const treeSHA = await fetch(
  `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/trees/main`,
  {
    method: "GET",
    headers: {
      Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
      "Content-type": "application/vnd.github+json"
    }
  }
)

const treeSHAData = await treeSHA.json()
const treeSHADataSHA = treeSHAData.sha

const treeArray = blobsSHA.map((sha, index) => ({
  path: Date.now() + files[index].originalname,
  mode: "100644",
  type: "blob",
  sha
}))
//Đến đây thì mọi người có thể nhận được url và path file ở đây rồi, nhưng đừng hấp tấp nhé, tiếp tục thôi.

const treeData = await fetch(
  `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/trees`,
  {
    method: "POST",
    headers: {
      Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
      "Content-type": "application/vnd.github+json"
    },
    body: JSON.stringify({
      base_tree: treeSHADataSHA,
      tree: treeArray
    })
  }
)

const treeDataData = await treeData.json()

Đã có tree rồi thì giờ chúng ta tạo commit như kiểu chúng ta commit code ở Terminal thôi.

const treeDataDataSHA = treeDataData.sha
const commit = await fetch(
  `https://api.github.com/repos//${process.env.GITHUB_REPO}/git/commits`,
  {
    method: "POST",
    headers: {
      Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
      "Content-type": "application/vnd.github+json"
    },
    body: JSON.stringify({
      message: "upload image",  //hoặc mọi người có thể chỉnh commit ở đây.
      tree: treeDataDataSHA,
      parents: [treeSHADataSHA]
    })
  }
)

const commitData = await commit.json()

Cuối cùng thì chúng ta cần update reference nữa là xong.

await fetch(
  `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/refs/heads/main`,
  {
    method: "PATCH",
    headers: {
      Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
      "Content-type": "application/vnd.github+json"
    },
    body: JSON.stringify({
      sha: commitDataSHA,
      force: true
    })
  }
)

Khi đã update reference xong thì chúng ta có thể nhận thông tin của các files ở phần treeArray.

const imageUrls = treeArray.map((item) => {
  return {
    public_name: item.path,
    url: item.path
  }
})

return res.status(200).json({ message: "Upload ảnh thành công", imageUrls })

Cuối cùng thì chúng ta sẽ có uploadController.js như sau:

async function uploadController(req, res) {
  try {
    if (!req.files || Object.keys(req.files).length === 0)
      return res.status(400).json({ message: "Vui lòng chọn hình ảnh" })
    const files = req.files
    const blobs = await Promise.all(
      files.map((file) => {
        const base64 = file.buffer.toString("base64");
        const data = {
          content: base64,
          encoding: "base64"
        };
        return fetch(
          `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/blobs`,
          {
            method: "POST",
            headers: {
              Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
              "Content-type": "application/vnd.github+json"
            },
            body: JSON.stringify(data)
          }
        );
      })
    )

    const blobsData = await Promise.all(blobs.map((blob) => blob.json()))
    const blobsSHA = blobsData.map((blob) => blob.sha)

    const treeSHA = await fetch(
      `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/trees/main`,
      {
        method: "GET",
        headers: {
          Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
          "Content-type": "application/vnd.github+json"
        }
      }
    )

    const treeSHAData = await treeSHA.json()
    const treeSHADataSHA = treeSHAData.sha

    const treeArray = blobsSHA.map((sha, index) => ({
      path: Date.now() + files[index].originalname,
      mode: "100644",
      type: "blob",
      sha
    }))
    const treeData = await fetch(
      `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/trees`,
      {
        method: "POST",
        headers: {
          Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
          "Content-type": "application/vnd.github+json"
        },
        body: JSON.stringify({
          base_tree: treeSHADataSHA,
          tree: treeArray
        })
      }
    )

    const treeDataData = await treeData.json()

    const treeDataDataSHA = treeDataData.sha

    const commit = await fetch(
      `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/commits`,
      {
        method: "POST",
        headers: {
          Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
          "Content-type": "application/vnd.github+json"
        },
        body: JSON.stringify({
          message: "upload image", //hoặc mọi người có thể chỉnh commit ở đây.
          tree: treeDataDataSHA,
          parents: [treeSHADataSHA]
        })
      }
    )

    const commitData = await commit.json()

    const commitDataSHA = commitData.sha

    // update reference
    await fetch(
      `https://api.github.com/repos/${process.env.GITHUB_REPO}/git/refs/heads/main`,
      {
        method: "PATCH",
        headers: {
          Authorization: `Token ${process.env.TOKEN_GITHUB_UPLOAD_IMAGES}`,
          "Content-type": "application/vnd.github+json"
        },
        body: JSON.stringify({
          sha: commitDataSHA,
          force: true
        })
      }
    )

    const imageUrls = treeArray.map((item) => {
      return {
        public_name: item.path,
        url: `https://raw.githubusercontent.com/${process.env.GITHUB_REPO}/main/${item.path}`,
      }
    })

    return res
      .status(200)
      .json({ message: "Upload ảnh thành công", imageUrls })
  } catch (error) {
    return res.status(500).json({ message: "Có lỗi xảy ra " + error.message })
  }
}

module.exports = uploadController

# Quay trở lại server.js và sửa lại như sau:

+ const handleUpload = require('./middleware/handleUpload')
+ const uploadController = require('./controller/uploadController')
- app.post('/upload', (req, res) => {
-   res.status(200).json({ message: 'Ok' })
- })
+ app.post('/upload', handleUpload, uploadController)

Cuối cùng thì nó sẽ như này:

require("dotenv").config()
const express = require("express")

const app = express()
const port = 5000

// CORS
const cors = require("cors")

app.use(
  cors({
    origin: "http://localhost:3000",
    credentials: true
  })
)

const handleUpload = require("./middleware/handleUpload")
const uploadController = require("./controller/uploadController")
app.post("/upload", handleUpload, uploadController)

app.listen(port, () => {
  console.log(`Server đang chạy ở port:${port}`)
})

Giờ thì vào test thôi.

final

Ok hoạt động tốt rồi.

# Kết thúc.

Vậy là chúng ta đã hoàn thành xong chức năng upload hình ảnh lên Github rồi.
Cảm ơn mọi người đã dành thời gian đọc.
Mọi người có thể tham khảo code ở đây

Trở về trang chủ

👋 0 người đang online