使用nodeJs開發本身的圖牀應用

前言

本文主要覆盤筆者的nodeJS,經過一個線上的實戰案例來總結node生態經常使用的技術點和最佳實踐。後面會花費大概一個月的時間輸出3篇以實戰爲主的nodeJs項目,本文是第一篇,主要介紹如何使用nodeJs開發一個圖牀應用。該項目對於測試和我的服務型網站很是實用,你們能夠基於此擴展出更強大的應用。本文的圖牀項目主要使用Koa進行開發,不熟悉的能夠先研究一下koa官網,或者看筆者以前寫的nodeJS的文章。javascript

你將收穫

  • Node應用基本架構方式以及開發NodeJS應用的流程
  • Koa + Koa-Router + glob + Node基本API使用
  • 跨域解決方案Koa Cors的使用介紹,以及如何和前協做跨域
  • 基於@koa/multer封裝文件上傳中間件
  • 使用React開發前端應用以及xui基本使用

正文

首先圖牀應用要保證不一樣域下均可以訪問咱們的圖片資源,不存在跨域問題,而且能夠支持在不一樣域下的應用均可以上傳圖片到圖牀上,以下圖所示: css

結合上圖咱們能夠先作應用的需求分析:
以上是一個很是簡單的圖牀應用的需求分析,咱們接下來將根據這個分析來搭建項目架構並開發咱們的應用程序。在開始以前咱們先看看簡單的實現效果:

  • 訪問並上傳圖片
  • 獲取圖片連接地址
  • 刪除圖片
    這個展現界面只是一個例子,咱們能夠經過前端的方式設計專屬於本身的圖牀管理界面。這裏提供的公共API在任何域名下都是能夠調用的,沒有跨域問題。

前臺地址:基於xui搭建的圖牀界面前臺前端

api開放地址:圖牀開放地址(免費勿黑)vue

1.Node應用基本架構方式以及開發NodeJS應用的流程

有關nodejs的項目架構以及如何組織nodejs目錄,我在30分鐘教你優雅的搭建nodejs開發環境及目錄設計這篇文章中有詳細的說明,你們在讀完本文以後能夠學習研究一下.java

開發任何一個應用以前首先要作的就是了解需求,需求理清楚以後就能夠作技術選型了,開發基於nodeJS的後端應用的技術方案不少,若是對nodejs很熟悉,徹底可使用原生nodejs來開發應用; 對於中小型應用咱們能夠直接採用Koa來開發,其中間件機制和插拔式的設計理念能夠很方便的讓咱們開發本身的中間件;若是是涉及到比較複雜的業務線咱們能夠採用egg.js或者nest.js來做爲nodeJS的框架選型,因爲本文的圖牀應用比較簡單,因此筆者這裏直接採用koa生態來作開發. 接下來先看看咱們圖牀應用的目錄結構: node

2.Koa + Koa-Router + glob + Node基本API使用

學習koa最快的方式就是直接看官方文檔, koa的官方文檔很是簡單也很是詳細,因此不懂的能夠先看看官網.

1.服務端路由(接口)設計

服務端路由咱們主要使用koa-router, 使用方式也很簡單, 代碼以下:react

const Koa = require('koa');
const Router = require('@koa/router');

const app = new Koa();
const router = new Router();
// 獲取列表的路由接口
router.get('/api/list', (ctx, next) => {
  // 獲取列表的邏輯
});
// 上傳圖片的路由接口
router.post('/api/upload', (ctx, next) => {
  // 上傳圖片的邏輯
});

app
  .use(router.routes())
  .use(router.allowedMethods());
複製代碼

由於圖牀的應用很是簡單,咱們這裏就直接使用傳統的方式實現, 有關nodeJS的MVC架構能夠參考我以前寫的node的文章.webpack

2.使用glob來批量獲取圖片路徑

這裏批量獲取圖片路徑咱們主要使用glob來經過遍歷目錄來獲取, 這種方式在圖片數據量小的時候可使用,可是一旦圖片量指數級增加,更建議用數據庫來存取,畢竟IO操做仍是比較費性能的.筆者這裏爲了方便採用glob來實現. glob是一個基於node的第三發庫,支持咱們使用模式匹配的方式遍歷文件目錄, 具體用法以下:ios

import glob from 'glob'
// 讀取文件
router.get('/api/v0/files',
    ctx => {
        const files = glob.sync(`${staticPath}/uploads/*`)
        const result = files.map(item => {
            return `${config.staticPath}${item.split('public')[1]}`
        })
        
        ctx.body = {
            state: 200,
            result
        }
    }
);
複製代碼

這樣就實現了批量獲取圖片的api,是否是很簡單呢? 咱們只須要訪問這個接口,就能夠拿到圖牀的全部圖片列表了.當咱們訪問這個接口時,會返回以下數據: css3

3.跨域解決方案Koa Cors的使用介紹,以及如何和前協做跨域

因爲瀏覽器同源策略,凡是發送請求url的協議、域名、端口三者之間任意一個與當前頁面地址不一樣就被算做跨域。實現跨域的方式也不少,好比JSONP跨域,nginx反向代理,服務器端修改header,設置document.domain,使用postMessage技術等,可是目前主流的方式仍是基於cors來實現.

爲了讓圖牀提供的服務給不一樣的域使用, 咱們須要配置跨域,這裏咱們採用koa2-cors提供的應答式跨域解決方案,其實原理也很簡單,就是配置http的請求響應頭信息, 讓咱們的服務器支持不一樣的ip訪問.其基本用法以下:

import cors from 'koa2-cors'
// 設置跨域
app.use(cors({
    origin: function (ctx) {
        console.log(111, ctx.url)
        if (ctx.url.indexOf('/api/v0') > -1) {
            return "*"; // 容許來自全部域名請求
        }
        return 'http://qutanqianduan.com'; // 這樣就能只容許 http://qutanqianduan.com 這個域名的請求了
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],  // 獲取額外的header信息
    maxAge: 5,  // 該字段可選,用來指定本次預檢請求的有效期,單位爲秒
    credentials: true,
    allowMethods: ['GET', 'POST', 'DELETE'],  // 請求容許的方法
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-requested-with'] // 容許的header字段名
}))
複製代碼

經過以上的配置,咱們就能夠實現基本的跨域了.若是咱們想只讓某些特定的接口實現跨域,咱們能夠設置接口白名單, 也能夠經過設置域名白名單來達到只讓特定的域名訪問咱們的api接口.這種狀況更適用於公司內部多個子系統間互相協做通訊的情景.

4.基於@koa/multer封裝文件上傳中間件

服務器要想接受客戶端上傳的文件,咱們還須要提供文件上傳接口, 這裏筆者採用koa生態比較主流的實現方式@koa/multer. 具體使用介紹官網寫的也很詳細,你們能夠看官網學習@koa/multer.

1.實現文件上傳接口

接下來咱們基於它實現文件上傳中間件.具體實現以下:

import multer from '@koa/multer'
import { resolve } from 'path'
import fs from 'fs'

const rootImages = resolve(__dirname, '../../public/uploads')
//上傳文件存放路徑、及文件命名
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, rootImages)
    },
    filename: function (req, file, cb) {
        let [name, type] = file.originalname.split('.');
        cb(null, `${name}_${Date.now().toString(16)}.${type}`)
    }
})
//文件上傳限制
const limits = {
    fields: 10,//非文件字段的數量
    fileSize: 1024 * 1024 * 2,//文件大小 單位 b
    files: 1//文件數量
}

export const upload = multer({storage,limits})
複製代碼

由以上代碼可知咱們在destination目錄下設置了文件上傳的目標目錄, 經過filename接口來設置上傳以後的文件名. limits是對文件操做的限制,具體的能夠根據本身的需求來配置.

其次結合koa-router來實現文件上傳接口:

// lib/upload.js
// 爲了捕獲multer的錯誤
export const uploadSingleCatchError = async (ctx, next) => {
    let err = await upload.single('file')(ctx, next).then(res => res)
                .catch(err => err);
    if(err) {
        ctx.status = 500
        ctx.body = {
            state: 500,
            msg: err.message
        }
    }
}

// index.js
// 上傳文件
router.post('/api/v0/upload', uploadSingleCatchError,
    ctx => {
        let { filename, path, size } = ctx.file;
        let { source } = ctx.request.body || 'unknow';

        let url = `${config.staticPath}${path.split('/public')[1]}`
        
        ctx.body = {
            state: 200,
            filename,
            url,
            source,
            size
        }
    }
  );
複製代碼

這樣咱們就能經過任意一個客戶端上傳圖片到咱們的圖牀上了.

2. 刪除文件接口實現

咱們用原生nodejs實現刪除文件的功能, 這裏會用到fs模塊,具體實現以下:

// lib/upload.js
// 刪除文件
export const delFile = (path) => {
    return new Promise((resolve, reject) => {
        fs.unlink(path, (err) => {
            if(err) {
                reject(err)
            }else {
                resolve(null)
            }
        })
    }) 
}

// 刪除文件接口
  router.get('/api/v0/del',
    async ctx => {
        const { id } = ctx.query
        if(id) {
            const err = await delFile(`${staticPath}/uploads/${id}`)
            if(!err) {
                ctx.body = {
                    state: 200,
                    result: '刪除成功'
                }
            }else {
                ctx.code = 500
                ctx.body = {
                    state: 500,
                    result: '文件不存在,刪除失敗'
                }
            } 
        }else {
            ctx.code = 500
            ctx.body = {
                state: 500,
                result: 'id不能爲空'
            }
        }  
    }
  )
複製代碼

這樣,咱們在本身的客戶端應用中點擊刪除按鈕就能夠刪除圖牀上的文件了.固然本圖傳應用還有不少接口實現細節, 這裏就不一一介紹了,感興趣的朋友能夠研究一下.

5.使用React開發前端應用以及xui基本使用

接下來借來實現咱們的圖牀客戶端,客戶端的實現以及設計風格徹底能夠由本身來定,因此這裏只是介紹一下筆者實現的客戶端,筆者將採用react全家桶以及本身開發的第三方ui庫xui——基於react的輕量級UI組件庫來實現,關於如何開發一個專屬於本身的組件庫,能夠參考筆者以前的文章. 首先咱們簡單開發一個圖牀應用的界面:

咱們先引入組件庫:

import React, { Component } from 'react'
import {  
  Notification,
  message,
  Layout, 
  Icon
} from '@alex_xu/xui'
const { Header, Content, Footer } = Layout
複製代碼

接着搭建咱們的頁面:

class UploadPage extends Component {
    state = {
      fileList: []
    }

    componentDidMount() {
      fetch(apiUrl + '/files').then(res => res.json()).then(res => {
        this.setState({
          fileList: res.result
        })
      })
    }

    showAddress = (item) => {
      Notification.config({
        placement: 'topRight',
      })
      Notification.pop({
        type: 'success',
        message: '圖片地址',
        duration: 10,
        description: item
      })
    }

    render() {
      return (
        <div className="upload-wrap">
          <Layout>
            <Header fixed>
              <div className="logo"><Icon type="FaBattleNet" style={{fontSize: '30px', marginRight: '12px'}} />XOSS</div>
            </Header>
            <Content style={{marginTop: '48px', backgroundColor: '#f0f2f5'}}>
              {
                this.state.fileList.map((item, i) => {
                  return <div key={i} className="imgBox" onClick={this.showAddress.bind(this, item)}>
                    <img src={item} alt=""/>
                    <span className="del-btn" onClick={this.delFile.bind(this, item)}><Icon type="FaMinusCircle" style={{fontSize: '24px'}} /></span>
                  </div>
                })
              }
            </Content>
            <Footer style={{color: 'rgba(0,0,0, .5)'}}>趣談前端 -- 徐小夕</Footer>
          </Layout>
        </div>
      )
    }
}

export default UploadPage
複製代碼

關於http庫咱們可使用任何一種主流的庫好比axios, umi-request等. 本客戶端代碼已發佈到github,你們能夠clone本地運行一下:

基於react+redux+redux-thunk+xui開發的todoOA管理平臺

最後

圖牀完整代碼我會發布在趣談前端公衆號內, 若是想學習更多H5遊戲, webpacknodegulpcss3javascriptnodeJScanvas數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索