egg+vue+mongodb實踐開發在線文檔管理平臺——水墨文檔

前言

團隊中會遇到在線文檔管理的需求,包括技術文檔,接口文檔,excel文檔,和產品原型的託管等需求,一直沒有找到合適的開源項目來知足需求,因此動手實現了個文檔管理系統(實現起來並不複雜,該教程只是提供思路,並不是最佳實踐)javascript

Github: 傳送門<br/>
演示地址:傳送門css

功能列表

  • [x] 登陸註冊
  • [x] 工做臺|文檔列表
  • [x] 文檔編輯預覽(支持:md, excel,html產品原型託管)
  • [x] 協做編輯
  • [x] 訪問權限設置
  • [x] 團隊管理
  • [x] 點贊收藏
  • [x] 模板管理
  • [x] 瀏覽歷史
  • [x] 回收站
  • [ ] 文件夾形式閱讀(接口文檔)
  • [ ] 編輯歷史版本

系統界面預覽

閱讀前準備

一、瞭解vue技術棧開發<br/>
二、瞭解koa<br/>
三、瞭解egg<br/>
四、瞭解mongodb<br/>html

技術棧

前端:<br/>
vue: 模塊化開發少不了angular,react,vue三選一,這裏選擇了vue。<br/>
vuex: 狀態管理<br/>
sass: css預編譯器。<br/>
element-ui:不造輪子,有現成的優秀的vue組件庫固然要用起來。<br/>前端

服務端:<br/>
egg.js:企業級框架,按照一套統一的約定進行應用開發,開發十分高效。<br/>
mongodb:一個基於分佈式文件存儲的數據庫,比較靈活。<br/>
egg-alinode:阿里提供的免費nodejs服務器性能監控。<br/>vue

工程搭建

這裏咱們將先後端項目放在同一個目錄下管理,分別用egg腳手架和vue-cli3生成初始化項目,拷貝合併到同一個目錄下,記得合併下package.json內容。(腳手架生成項目就不介紹了,按照文檔來就是了),合併後將vue項目src目錄改成web,以下:java

···
·
|-- app                    // egg 初始化app目錄
|-- config                // egg 初始化app目錄
|-- public                // vue 靜態資源目錄
|-- web                    // 原 src 目錄,改爲 web 用做前端項目目錄
·
···

這樣的話 咱們須要再把咱們vue webpack打包配置稍做一下調整,首先是把原先的編譯指向src的目錄改爲 web,其次爲了 npm run build 能正常編譯 web 咱們也須要爲 babel-loader 再增長一個編譯目錄node

  • 根目錄新增vue.config.js,目的是爲了改造vue項目入口,改成:web/main.jsreact

    module.exports = {    
          pages: {        
            index: {            
              entry: "web/main.js"        
            }    
          }
        }
  • babel-loader能正常編譯 web目錄, 在vue.config.js新增以下配置webpack

    // 擴展 webpack 配置
    chainWebpack: config => {
        config.module
        .rule('js')
        .include.add(/web/).end()
        .use('babel')
        .loader('babel-loader')
        .tap(options => {
        // 修改它的選項...
        return options
        })
    }
  • package.json 新增前端項目打包命令
"dev-web": "vue-cli-service serve",
"build-web": "vue-cli-service build",

至此先後端項目初始化工做就完了,前端開發啓動npm run dev-web 後端開發啓動 npm run devios

工程目錄結構

|-- app                    --------服務器端項目代碼
    |--controller                --------用於解析用戶的輸入,處理後返回相應的結果
    |--extend                    --------框架的擴展
    |--middleware                --------編寫中間件
    |--model                    --------Schema數據模型
    |--public                    --------用於放置靜態資源
    |--service                    --------用於編寫業務邏輯層
    |--router.js                --------用於配置 URL 路由規則
|-- config                    --------egg 配置文件
    |--config.default.js            --------默認配置
    |--config.local.js                --------開發環境配置
    |--config.prod.js                --------生產環境配置
    |--plugin.js                    --------配置須要加載的插件
|-- web                        --------前端項目界面代碼
    |--common                    --------前端界面對應靜態資源
    |--components                --------組件
    |--config                    --------配置文件
    |--filter                    --------過濾器
    |--pages                    --------頁面
    |--router                    --------路由配置
    |--store                    --------vuex狀態管理
    |--service                    --------axios封裝
    |--App.vue                    --------App
    |--main.js                    --------入口文件
    |--permission.js            --------權限控制
|-- docs                    --------預留編寫項目文檔目錄
|-- vue.config.js            --------vue webpack配置文件
|-- package.json
...
...

完成項目目錄初始化後,接下來先把mongodb全局得一些中間件、擴展方法給配置上,爲接口開發作好準備工做

mongodb配置

一、安裝mongoose模塊

npm install egg-mongoose --save

二、配置config文件

// config/plugin.js
exports.mongoose = {
    enable: true,
    package: 'egg-mongoose',
};

// config/config.default.js
config.mongoose = {
    url: 'mongodb://127.0.0.1:27017/inkwash',
    options: {},
};

全局中間件和擴展配置

一、後端接口開發中咱們須要一個統一得返回格式,能夠在context對象下擴展個返回數據function用於統一處理接口response data

app下新建文件夾extend 新建context.js

// app/extend/context.js
module.exports = {
    /**
     * 返回客戶端的內容
     * @param status // 接口是否成功
     * @param body // 返回數據
     * @param msg // 返回信息提示
     * @param code // 返回狀態碼
     */
    returnBody (status = true, body = {}, msg = 'success', code = 200) {
        this.status = code;
        this.body = {
            status: status,
            body: body,
            msg,
            code: code
        }
    }
}

// 調用
const { ctx } = this;
ctx.returnBody(true, {}, "成功");

二、添加統一處理錯誤得中間件
app文件夾下新建middleware文件夾,新建error_handler.js, 並配置congfig全局中間件配置

// app/middleware/error_handler.js
module.exports = () => {

    return async function errorHandler(ctx, next) {
        try {
            await next();
        } catch (err) {
            // 全部的異常都會在app上出發一個error事件,框架會記錄一條錯誤日誌
            ctx.app.emit('error', err, ctx);

            const status = err.status || 500;

            // 若是時生產環境的時候 500錯誤的詳細錯誤內容不返回給客戶端
            const error = status === 500 && ctx.app.config.env === 'prod' ? '網絡錯誤' : err.message;

            ctx.body = {
                msg: error,
                status: false,
                body: {},
                code: status
            };
        }
    };
};

// app/middleware/error_handler.js
// config/config.default.js 配置全局中間件
config.middleware = [ 'errorHandler'];

jwt鑑權登陸認證

一、安裝egg-jwt token生成以及驗證包

npm install egg-jwt --save

二、安裝完成後在根目錄下的 config/plugin.js 配置一下,如:

'use strict';

/** @type Egg.EggPlugin */
module.exports = {
    jwt: {
        enable: true,
        package: "egg-jwt"
    },
  mongoose: {
    enable: true,
    package: 'egg-mongoose',
  }
};

三、接下來在 config/config.default.js 裏面繼續配置:

config.jwt = {
  secret: "123456"//自定義 token 的加密條件字符串
};

四、在context上擴展兩個function, getToken 和 checkToken用於生成token和驗證token

// app/extend/context.js
async getToken(data) {
    return await this.app.jwt.sign(data, this.app.config.jwt.secret, {expiresIn: 30* 24 * 60 * 60 + 's'});
},
async checkToken(token) {
    return await this.app.jwt.verify(token, this.app.config.jwt.secret)
}

五、編寫個中間件實現登陸驗證攔截
在app/middleware文件夾下新建auth.js

// app/middleware/auth.js
module.exports = () => {
    return async function(ctx, next) {
        let token = '';
        if (
            ctx.headers.authorization && ctx.headers.authorization.split(' ')[0] === 'Bearer'
        ) {
            token = ctx.headers.authorization.split(' ')[1];
        } else if (ctx.query.accesstoken) {
            token = ctx.query.accesstoken;
        } else if (ctx.request.body.accesstoken) {
            token = ctx.request.body.accesstoken;
        }
        let user;
        try{
            user = await ctx.checkToken(token);
        }catch (e) {
            ctx.returnBody(false,{}, 'Token 無效,請從新登陸', 401);
        }
        if (!user) {
            ctx.returnBody(false,{}, 'Token 無效,請從新登陸', 401);
            return;
        }
        ctx.request.user = user;
        await next();
    };
};

好了以上配置完成後就開始接下來的核心註冊功能相關操做了。

  • 首先我在根目錄下的 app/router.js 建立訪問路由:
import { Application } from 'egg';

export default (app: Application) => {
  const { controller, router, jwt } = app;
  //正常路由
  router.post('/auth/register', controller.auth.register);
  
  // 只有在須要驗證 token 的路由上添加jwt
  router.post('/user/infor',jwt, controller.user.infor); 
};
  • 接下來我去編寫個人控制器,在根目錄下的 app/controller/home.ts 編寫內容:
    這裏使用了兩個咱們在app/extend/context.js上擴展的兩個通用方法

    1. 經過ctx.getToken(用戶信息object對象)將用戶信息經過jwt生成token返回給前端
    2. 經過ctx.returnBody返回數據
// app/controller/auth.js
const Controller = require('egg').Controller
class AuthController extends Controller {
    async login() {
        //... 略
    }
    async register() {
        const { ctx, service } = this;
        const { username, password, email } = ctx.request.body
        let userData = await ctx.service.user.createUser(username, password, email);
        userData = userData.toObject();
        let userDataStr = JSON.parse(JSON.stringify(userData));
        // 生成token
        let token =await ctx.getToken(userDataStr);
        ctx.returnBody(true, {access_token: token, userInfo: userData}, "註冊成功!")
    }
}

module.exports = AuthController;
  • 前端請求的時候須要在 headers 裏面上默認的驗證字斷 Authorization 就能夠了,如:
axios({
 method: 'get',
  url: 'http://127.0.0.1:7001/user/info',
  headers:{
      // 切記 token 不要直接發送,要在前面加上 Bearer 字符串和一個空格
    'Authorization':`Bearer ${token}`
  }
})
  • 接口從toen獲取加密信息
  1. 在app/extend/context.js再擴展個getUser方法獲取token加密信息
// app/extend/context.js
// 獲取用戶信息
    async getUserData() {
        var token = this.headers.authorization ? this.headers.authorization : '';
        token = token.substring(7) //把Bearer 截取掉,解析的時候不須要加上Bearer
        let user = {}
        try {
            user = this.app.jwt.verify(token, this.app.config.jwt.secret);
        } catch (err) {
            user = {}
        }
        return user;
    }
  1. 實現獲取我的信息接口
// app/controller/user.js
'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
    async info() {
        let {ctx} = this;
        let user = await this.ctx.getUserData()
        ctx.returnBody(true, user)
    }
}

module.exports = UserController;

至此咱們就實現了jwt生成token, 而後經過前端傳過來的token獲取當前登陸用戶的信息, jwt登陸受權這塊應該是講完了,其餘的業務接口應該實現起來難度不大

md文檔編輯

文檔編輯器使用Vditor, 一款瀏覽器端的 Markdown 編輯器,支持所見即所得(富文本)、即時渲染(相似 Typora)和分屏預覽模式
安裝Vditor

npm install vditor --save

在代碼中引入並初始化對象

<template>
  <div class="editor-component editor-md" ref="editor-component">
    <div id="editor-md-dom"></div>
  </div>
</template>

<script>
    import Vditor from 'vditor'
    import "vditor/src/assets/scss/index.scss"

    let timer = null;
    export default {
         data(){
             return {
                contentEditor: '',
            }
          },
          mounted () {
            this.contentEditor = new Vditor('vditor', {
              height: 360,
              toolbarConfig: {
                pin: true,
              },
              cache: {
                enable: false,
              },
              after: () => {
                this.contentEditor.setValue('hello, Vditor + Vue!')
              },
            })
         },
    }
</script>

excel表格編輯

安裝x-data-spreadsheet

npm install x-data-spreadsheet
<div id="x-spreadsheet-demo"></div>
import Spreadsheet from "x-data-spreadsheet";
// If you need to override the default options, you can set the override
// const options = {};
// new Spreadsheet('#x-spreadsheet-demo', options);
const s = new Spreadsheet("#x-spreadsheet-demo")
  .loadData({}) // load data
  .change(data => {
    // save data to db
  });
 
// data validation
s.validate()

axure原型託管

原型axure頁面託管,參考WuliHub讓用戶上傳生成的html壓縮包,而後解壓到靜態資源目錄,返回訪問地址就ok, 前端拿到原型地址用內嵌iframe渲染出來就ok

打包編譯&&靜態資源設置

一、配置前端vue頁面打包命令

// kage.json script新增打包命令
"build-web": "vue-cli-service build",

二、運行npm run build-web 根目錄會生成dist前端代碼靜態文件,由於egg支持設置多個靜態資源目錄,這裏就直接配置根目錄下的dist文件夾爲靜態目錄, 配置config

// config/config.default.js
config.static = {
        prefix: '/',// 將靜態資源前綴改成'/'(默認是 '/public')
        dir: [
            path.join(__dirname, '../app/public'), 
            path.join(__dirname, '../dist')
        ]
    }

打包完成後啓動egg,就能夠經過http://localhost:7001/index.html 訪問到前端頁面了


由於直接訪問http://localhost:7001 會404
能夠再配置個路由重定向,將跟路由'/' 重定向到 '/index.html'

// app/router.js 
// 重定向到index頁面
app.router.redirect('/', '/index.html', 302);

部署

服務端部署運行start命令

npm run start

性能監控

node服務性能監控這塊能夠使用阿里免費開源的alinode
一、安裝egg-alinode

npm i egg-alinode

二、插件配置

// config/plugin.js
exports.alinode = {
  enable: true,
  package: 'egg-alinode',
};

三、配置config

// config/config.default.js
exports.alinode = {
  enable: true,
  appid: 'my app id',
  secret: 'my app secret',
};

這樣就能夠了,監控數據能夠在阿里Node.js 性能平臺控制檯看到監控面板

結束

實現起來並不複雜,該教程只是提供思路,並不是最佳實踐

相關文章
相關標籤/搜索