Recently, due to the business of the company, we need to do the file upload function. When talking about this, many people will think that it is not simple, a file input control will do it.

Things, of course, are not that simple, because in addition to basic file uploads, you need to support sliced uploads, breakpoint uploads, breakpoint uploads, cross-browser breakpoint uploads, and so on.

Because the technology stack of the project is VUE, I found a plug-in vue-simple-Uploader which realized file slice uploading and file queue management based on VUE.

The front-end function can be solved by the plug-in, and when writing the file upload function, because the back-end interface is not provided, so on a whim, I use nodeJS to load a upload service vue-uploader for debugging, here to share:

Upload process

  • The user selects the file to be uploaded
  • Send a request to verify the upload status of the file
  • Uploading file Slices
  • Merge file slices

The logic of user selection and upload interaction is handled on the front side, so here are a few aspects:

Verify file upload status

The main operation of this interface is to determine whether the file to be uploaded exists on the server. If so, an identifier is returned to inform the user that the upload operation can be skipped and the upload is successful. The main steps and some codes are as follows:

  1. Generate a file access path based on the unique file id and file name
const router = require('express').Router()
const sparkMD5 = require('spark-md5')
// Encapsulate the file operation code
const { genFilePath } = require('.. /utils/file')

// GET /upload interface internal code
router.get('/', (req, res) => {
    const { identifier, filename } = req.query
    const name = decodeURIComponent(filename)
    const fileHash = sparkMD5.hash(name + identifier)
    const filePath = genFilePath(fileHash)
    // ... other code
})
Copy the code
  1. Check whether the file already exists. If yes, return the available transmission and file download path
const router = require('express').Router()
const sparkMD5 = require('spark-md5')
// The product hash is used to generate file signatures
const proHash = sparkMD5.hash('This is your flag')
// File signatures generate merge symbols between fields
const hashSeparator = '!!!!!! '
// Encapsulates the method that generates the response object
const { success } = require('.. /utils/response')

// GET /upload interface internal code
router.get('/', (req, res) => {
    / /... Generate file access path code based on the unique file id and file name
    
    const result = {
        isRapidUpload: false.url: ' '.uploadedChunks: []}if(fs.existsSync(filePath)) {
        // File signature
        const sig = sparkMD5.hash([proHash, name, fileHash].join(hashSeparator))
        return res.json(success(Object.assign(result, {
            isRapidUpload: true.url: [
                'http://localhost:3000/upload'.'file', fileHash, filename, sig
            ].join('/')}),'Can transmit in seconds'))}// ... other code
})
Copy the code
  1. If the full file does not exist, the list of uploaded slices of the file is returned
const router = require('express').Router()
const sparkMD5 = require('spark-md5')
// Slice name prefix
const chunkNamePre = 'chunk'
// Encapsulate the file operation code
const { genChunkDir } = require('.. /utils/file')
// Encapsulates the method that generates the response object
const { success } = require('.. /utils/response')

// GET /upload interface internal code
router.get('/', (req, res) => {
    / /... Generate file access path code based on the unique file id and file name
    
    / /... Check whether the file exists. If yes, return the code of the file download path

    const chunkDir = genChunkDir(identifier)
    const existsChunks = fs.readdirSync(chunkDir)
    // There are no uploaded slices
    if(! existsChunks.length)return res.json(success(result, 'File does not exist'))

    // Upload some slices
    const uploadedChunks = existsChunks.map(chunk= > parseInt(chunk.slice(chunkNamePre.length)))
    res.json(success(Object.assign(result, { uploadedChunks }), 'Partially uploaded slices'))})Copy the code

The above is the logic for verifying file status before uploading. The front-end can decide whether to upload files and which slices need to be uploaded based on the status.

Uploading file Slices

If the file to be uploaded does not exist on the server, the front-end will invoke the slice upload interface, and its logic is as follows:

  1. Save the slices uploaded from the front end to the corresponding temporary directory
const router = require('express').Router()
const multer = require('multer')
const upload = multer({ dest: path.join(__dirname, '.. /tpl/')})// Slice name prefix
const chunkNamePre = 'chunk'
// Encapsulate the file operation code
const { genChunkDir } = require('.. /utils/file')

router.post('/', upload.single(fileParamName), async (req, res) => {
    const { identifier, chunkNumber, totalSize, chunkSize } = req.body

    // Generate a temporary storage directory for slices
    const chunksDir = genChunkDir(identifier)
    // Generate a temporary file name for each stored block based on the block index and unique file identifier
    const chunkPath = chunksDir + '/' + chunkNamePre + chunkNumber

    // Rename the uploaded block file to the file name generated above
    fs.renameSync(req.file.path, chunkPath)

    // Polling verifies that each block exists. If each block exists, the response is uploaded
    let currChunkNumber = 1
    const totalChunks = Math.max(Math.floor(totalSize / chunkSize), 1)
    const chunkPathList = []
    while(currChunkNumber <= totalChunks) {
        const currChunkPath = chunksDir + '/' + chunkNamePre + currChunkNumber
        chunkPathList.push(currChunkPath)
        currChunkNumber++
    }
    Promise.all(chunkPathList.map(chunkPath= > testChunkExist(chunkPath)))
        .then(resultList= > {
            res.json(success({
                isComplete: resultList.every(result= > result)
            }))
        })
})
Copy the code

Multer is a nodejs plugin that handles file uploads

  1. Verify whether file slices are uploaded completely
const router = require('express').Router()
const multer = require('multer')
const upload = multer({ dest: path.join(__dirname, '.. /tpl/')})// Slice name prefix
const chunkNamePre = 'chunk'
// Encapsulate the file operation code
const { testChunkExist } = require('.. /utils/file')

router.post('/', upload.single(fileParamName), async (req, res) => {
    const { identifier, chunkNumber, totalSize, chunkSize } = req.body

    // Check whether each slice exists. If each block exists, the response upload is complete
    let currChunkNumber = 1
    const totalChunks = Math.max(Math.floor(totalSize / chunkSize), 1)
    const chunkPathList = []
    while(currChunkNumber <= totalChunks) {
        const currChunkPath = chunksDir + '/' + chunkNamePre + currChunkNumber
        chunkPathList.push(currChunkPath)
        currChunkNumber++
    }
    // Asynchronous batch check whether file slices exist
    Promise.all(chunkPathList.map(chunkPath= > testChunkExist(chunkPath)))
        .then(resultList= > {
            res.json(success({
                isComplete: resultList.every(result= > result)
            }))
        })
})
Copy the code

If isComplete is true, the file slice is uploaded completely and can be merged

Merge file slices

If the file slices are uploaded completely, this interface merges all the file slices and returns the file download path

const router = require('express').Router()
const sparkMD5 = require('spark-md5')
const childProcess = require('child_process')
// The product hash is used to generate file signatures
const proHash = sparkMD5.hash('This is your flag')
// File signatures generate merge symbols between fields
const hashSeparator = '!!!!!! '
// Encapsulate the file operation code
const { testChunkExist, genFilePath, writeChunks } = require('.. /utils/file')
const { success, fail } = require('.. /utils/response')

router.post('/merge', (req, res) => {
    const { identifier, fileName } = req.body
    const chunkDir = genChunkDir(identifier)
    // File name + suffix
    const name = decodeURIComponent(fileName)
    // Generate the actual storage name of the file
    const fileHash = sparkMD5.hash(name + identifier)
    // Actual file storage path
    const filePath = genFilePath(fileHash)
    // File signature
    const sig = sparkMD5.hash([proHash, name, fileHash].join(hashSeparator))
    // Get all the slice paths in the slice directory and sort
    try {
        // Write each slice to the final path
        const writeSuccess = writeChunks(chunkDir, filePath, chunkNamePre)
        if(! writeSuccess)return res.json(fail(101.void 0.'This file fragment does not exist'))

        // Delete the cache slice directory
        childProcess.exec(`rm -rf ${chunkDir}`)

        // Returns the file access URL
        res.json(success({
            url: [
                'http://localhost:3000/upload'.'file', fileHash, fileName, sig
            ].join('/')},'Section merge successful'))}catch(err) {
        childProcess.exec(`rm -rf ${chunkDir}`)
        res.json(fail(102.void 0.'Slice merge failed'))}})Copy the code

Delete the temporary slice directory after the file slice merge is completed. Since this operation is not necessarily related to the interface response, the sub-process is enabled to perform the operation to avoid affecting the response time

conclusion

The above is a simple interface to support file slicing upload. Because it is a demo used by myself, it is relatively simple and only used to provide ideas for communication. Have any good ideas and opinions, look forward to your exchange.

The above is all the content of this article, if you have any questions, please correct; If need to reprint, hope to indicate the source