1. 程式人生 > >koa2/Express4+mongdb+webpack4優化配置實踐經驗

koa2/Express4+mongdb+webpack4優化配置實踐經驗

本文用於自我總結以及給予新手一些借鑑藉此共勉

demo地址 => 傳送門

ps:覺得有幫助的同學點個贊或者github star 在此謝過

使用版本:

  • koa: 2.6.1

  • express: 4.X

  • webpack: 4.19.1

  • mongoose: 5.3.9

實現的功能:

 批量上傳和下載

 mongoose資料庫操作

 增刪查

 webpack4部分優化配置

單檔案下載&批量下載:

  tips:批量下載需要使用引入archiver第三方包將多個檔案打包成一個壓縮檔案

  • Koa:
    //此處分為檔案和圖片兩個介面
    router.get('/downloadFile/:name',//檔案
        async ctx => {
            ctx.set('Content-disposition', `attachment;filename=${ctx.params.name}`);
            const paths = `src/assets/upload/files/${ctx.params.name}`
    
            const data = fs.createReadStream(paths) // 傳送路徑檔案內容的檔案
    
            ctx.body 
    = data } ).get('/downloadImg/:name',//圖片 async ctx => { ctx.set('Content-disposition', `attachment;filename=${ctx.params.name}`); const paths = `src/assets/upload/imgs/${ctx.params.name}`; ctx.attachment(paths); await send(ctx, paths); } ) //批量下載 router.get('/batchDownload', async ctx
    => { const list = [{ name: '1.jpg', path: 'src/assets/upload/imgs/1.jpg' }, { name: '1.csv', path: 'src/assets/upload/files/1.csv' }, { name: '2.png', path: 'src/assets/upload/imgs/2.png' }];//name為壓縮檔案內生成的檔名稱,path為要下載的檔案路徑 const zipName = 'download.zip';//自定義生成的壓縮檔名稱 const zipPath = `src/assets/download/${zipName}`;//自定義生成的壓縮檔案路徑 const zipStream = fs.createWriteStream(zipPath); const zip = archiver('zip'); zip.pipe(zipStream); for (let i = 0; i < list.length; i++) { // 新增單個檔案到壓縮包 zip.append(fs.createReadStream(list[i].path), { name: list[i].name }) } await zip.finalize(); ctx.attachment(zipPath); await send(ctx, zipPath); } )

     

  • Express:
    //此處同Koa
    app.get('/downloadFile/:name',
        (req, res) => {
            const filePath = `src/assets/upload/files/${req.params.name}`
            if (fs.existsSync(filePath)) {
                res.download(filePath)
            } else {
                res.json({ err: 'file is not exist', success: false })
            }
        }
    ).get('/downloadImg/:name',
        (req, res) => {
            const filePath = `src/assets/upload/imgs/${req.params.name}`
            if (fs.existsSync(filePath)) {
                res.download(filePath)
            } else {
                res.json({ err: 'file is not exist', success: false })
            }
        }
    ).get('/batchDownload',
        async (req, res) => {
            const list = [{ name:'1.jpg',path: 'src/assets/upload/imgs/1.jpg' }, { name:'1.csv',path: 'src/assets/upload/files/1.csv' }, { name:'2.png',path: 'src/assets/upload/imgs/2.png' }];
            const zipName = 'download.zip';
            const zipPath = `src/assets/download/${zipName}`;
            const zipStream = fs.createWriteStream(zipPath);
            const zip = archiver('zip');
            zip.pipe(zipStream);
            
            for (let i = 0; i < list.length; i++) {
                // 新增單個檔案到壓縮包
                zip.append(fs.createReadStream(list[i].path), { name: list[i].name })
            }
            await zip.finalize();
            setTimeout(() => {
                res.download(zipPath)
            }, 0);
            
        }
    )

     

上傳&批量上傳:

  • Koa:
    //接收post請求需要先處理options請求
    app.use(
        async (ctx, next) => {
            if (ctx.request.method === "OPTIONS") {
                ctx.response.status = 200
                ctx.set('Access-Control-Allow-Origin', ctx.request.headers.origin)
                ctx.set("Access-Control-Max-Age", 24 * 60 * 60 * 1000);
                ctx.set("Access-Control-Allow-Methods", 'GET,POST,OPTIONS,DELETE,PUT');
                ctx.set("Access-Control-Allow-Headers", 'Origin,Content-Type,Authorization,Accept,X-Custom-Header,anonymous,X-Requested-With')
                ctx.body = ''
            } else {
                await next()
            }
    
        }
    )
    //上傳
    router.post('/upload',
        ctx => {
            try {
                const files = ctx.request.files.file
                files.length ?
                    files.forEach(file =>
                        fs
                            .createReadStream(file.path)
                            .pipe(fs.createWriteStream(`${file.type.indexOf('image') >= 0 ? 'src/assets/upload/imgs' : 'src/assets/upload/files'}/${file.name}`))
                    ) :
                    fs
                        .createReadStream(files.path)
                        .pipe(fs.createWriteStream(`${files.type.indexOf('image') >= 0 ? 'src/assets/upload/imgs' : 'src/assets/upload/files'}/${files.name}`))
    
                ctx.body = '上傳成功'
            } catch (e) {
                ctx.body = '上傳失敗'
            }
        }
    )

     

  • Express(需要引入multer接受upload檔案):   const multer = require('multer')  const upload = multer({ dest: '/src/upload' })

    //
    處理options app.all('*', (req, res, next) => { if (req.method === "OPTIONS") { res.status(200) res.end() } else { next() } } ).post('/upload', upload.any(), (req, res, next) => { try { const files = req.files console.log(files) files.length ? files.forEach(file => fs .createReadStream(file.path) .pipe(fs.createWriteStream(`${file.mimetype.indexOf('image') >= 0 ? 'src/assets/upload/imgs' : 'src/assets/upload/files'}/${file.originalname}`)) ) : fs .createReadStream(files.path) .pipe(fs.createWriteStream(`${files.mimetype.indexOf('image') >= 0 ? 'src/assets/upload/imgs' : 'src/assets/upload/files'}/${files.originalname}`)) res.json({ err: 'upload success', success: false }) } catch (e) { res.json({ err: 'upload failed', success: false }) } } )

     

mongodb線上資料mLab庫:

  分為兩個檔案 connect.js連線資料庫,db.js匯出mongodb的自定義資料模型

  • connect.js
    const mongoose = require('mongoose')
    
    mongoose.Promise = global.Promise
    
    mongoose.connect('mongodb://使用者名稱:密碼@ds145093.mlab.com:45093/使用者名稱',
        { useNewUrlParser: true },
        (err, info) => {
            if (err) throw 'mongodb connect failed'
            console.log('mongodb connect success')
        }
    )

     

  • db.js
    require('./connect')
    const mongoose = require('mongoose');
    const Schema = mongoose.Schema;
    
    const todoSchema = new Schema({
        name: String,
        isDoing: Boolean,
        createDate: Date, 
    }, {versionKey: false})
    
    
    const Todo = mongoose.model('Todo', todoSchema)
    exports.Todo=Todo;

     

增刪查:

  • Koa:
    //需要引入koa-body
    const koaBody = require('koa-body')
    
    //查詢所有記錄
    app.get('/todoList',
        async ctx => {
            try {
                let data = await Todo.find({})
                ctx.body = { data, success: true }
            } catch (e) {
                ctx.body = { data: [], success: false }
            }
    
        },
    ).get('/todoList/:name',//模糊查詢單一記錄
        async ctx => {
            const params = { name: { $regex: new RegExp(`${ctx.params.name}`) } }
    
            try {
                let data = await Todo.find(params)
                ctx.body = { data, success: true }
            } catch (e) {
                ctx.body = { data: [], success: false }
            }
        }
    ).post('/todoList', koaBody(),//新增記錄
        async ctx => {
            const todo = new Todo({ ...ctx.request.body })
            try {
                let data = await todo.save()
                ctx.body = { data, success: true }
            } catch (err) {
                ctx.body = { data: { err }, success: false }
            }
    
        }
    ).del('/todoList/:name',//刪除記錄
        async ctx => {
            const params = { name: ctx.params.name }
            try {
                let data = await Todo.findOneAndDelete(params)
                ctx.body = { data, success: true }
            } catch (err) {
                ctx.body = { data: { err }, success: false }
            }
        }
    )

     

  • Express:
    //查詢所有記錄
    app.get('/todoList',
        async (req, res, next) => {
            try {
                let data = await Todo.find({})
                res.json({ data, success: true })
            } catch (e) {
                res.json({ data: [], success: false })
            }
    
        },
    ).get('/todoList/:name',//模糊查詢記錄
        async (req, res, next) => {
            const params = { name: { $regex: new RegExp(`${req.params.name}`) } }
    
            try {
                let data = await Todo.find(params)
                res.json({ data, success: true })
            } catch (e) {
                res.json({ data: [], success: false })
            }
        }
    ).post('/todoList',//新增記錄
        async (req, res, next) => {
            const todo = new Todo({ ...req.body })
            try {
                let data = await todo.save()
                res.json({ data, success: true })
            } catch (err) {
                res.json({ data: { err }, success: false })
            }
    
        }
    ).delete('/todoList/:name',//刪除記錄
        async (req, res, next) => {
            const params = { name: req.params.name }
            try {
                let data = await Todo.findOneAndDelete(params)
                res.json({ data, success: true })
            } catch (err) {
                res.json({ data: { err }, success: false })
            }
        }
    )

     

webpack優化配置:

  • 打包第三方庫預編譯:
    //dll.js
    const path = require('path')
    const webpack = require('webpack')
    
    module.exports = {
      entry: {
        vendor: [
            'react',
            'react-dom',
            'react-router-dom',
            'immutable', 
            'antd',
            'axios',      
        ],
      },
      output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../public'),
        library: '[name]_library', 
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify('production')
        }),
        new webpack.DllPlugin({
          path: path.resolve(__dirname, '../public','[name]-manifest.json'),
          name: '[name]_library',
          context: __dirname
        })
      ],
    }

     

  • 提取公共程式碼
    optimization: {
        splitChunks: {
          chunks: 'all',
          name: false,
          cacheGroups: {
            commons: {
              name: 'commons',
              priority: 10,
              chunks: 'initial'
            }
          },
        }
      }

     

  • happypack
    //引入happypack
    const HappyPack = require('happypack')
    
    new HappyPack({
          id: 'jsx',
          threads: 4,
          loaders: ['babel-loader?presets[]=react,presets[]=latest&compact=false'],
    })

     

  • 藉由路由分包
    //引入react-loadable
    import Loadable from 'react-loadable'
    
    const MyLoadingComponent = ({ isLoading, error }) => {
      return ''
    }
    const Nav = Loadable({
      loader: () => import(/* webpackChunkName: "Nav" */'../components/Nav'),//註釋是為了讓webpack打包出來的檔案以路由為名
      loading: MyLoadingComponent
    })

     

  • 以上的優化足夠一般的專案使用,還有其他一些零散的優化配置如減少編譯後文件大小:Tree-shaking等,快取loader: cache-loader,檔案hash快取:webpack-md5-plugin等 有興趣的可以自行Google或百度

以上就是所有內容 有不明白的可以評論 看到的會回覆