koa+jwt+sequelize+nginx+vue+elementUI 擼一個屬於本身的博客

源碼地址html

博客地址前端

CSDNvue

運行項目前必讀

  1. 三個項目中各類各樣的受權參數 已所有修改爲本身的受權參數,忘悉知!!!!忘悉知!!!!忘悉知!!!!
  2. 本身建立一個數據庫名稱就能夠了,表是運行 node 時候自動建立好
    myblog3 爲數據庫名稱, admin 登陸頁面有個 一鍵生成地方生成帳號:admin 密碼:123, 只能生成一次,由於是程序寫死的帳號,省的您手動去數據庫中 User 表去添加一條帳戶信息。帳戶的體系的增刪改查的接口都已經實現,根據本身的需求去釋放出來 您能夠很愉快的登陸玩耍了

整套項目功能

  1. 前端 BLOG 功能介紹:
  • 首頁 (思惟導圖(樹狀圖))
  • 文章 (文章列表,我的分類,熱門文章,文章詳情,文章目錄結構)
  • 留言(登陸,留言, 發郵件)
  • 做品 (歷年來的項目地址)
  • 關於(關於項目介紹)
  • 登陸 (普通登陸,Github 登陸, QQ 登陸(審覈中...))
  1. Admin 後臺系統功能介紹
  • 首頁
  • 分類管理(CURD)
  • 留言管理(CURD,經過,拒絕,回覆)
  • 文章
    列表:CURD,上下架,上傳圖片,markdown 編輯器,評論列表按鈕 評論列表:CURD,上下架,評論, 發郵件
  1. KOA 實現後端接口
  • node 主要是後端接口,具體的接口的具體查看代碼。

vue-blog-web

  • 代理(5454 爲 node 的端口)
proxyTable: {
        "/mapi": {
            target: "http://localhost:5454",
            changeOrigin: true,
            pathRewrite: {
                "^/mapi": ""
            }
        },
    }
複製代碼
  • 接口層封裝(http/index.js)
import Axios from 'axios'
    import Router from '../router'

    export default {
        get(url, params) {
            return new Promise((resolve, reject) => {

                Axios({
                        method: "get",
                        url: url,
                        params: {
                            ...params,

                            author: 'admin'
                        },
                        validateStatus: function(status) {
                            // 截獲狀態碼範圍
                            return status >= 200 && status < 500
                        },
                    }).then(response => {
                        if (response.status == 200) {
                            resolve(response.data);
                        } else if (response.status == 401) {
                            // 無權限

                        } else if (response.status == 403) {
                            // session 過時
                            Router.push('/login')
                        } else {
                            reject(response.data)
                        }
                    })
                    .catch(error => {

                        reject(error);
                    })
            })
        },
        post(url, method = 'post', params) {
            return new Promise((resolve, reject) => {

                Axios({
                        method,
                        url: url,
                        data: {
                            ...params,

                            author: 'admin'
                        },
                        validateStatus: function(status) {
                            return status >= 200 && status < 500
                        },
                    }).then(response => {
                        if (response.status == 200) {
                            resolve(response.data);
                        } else if (response.status == 401) {
                            // Message.error('登陸信息過時,請從新登陸');
                            // Router.replace('/login')
                            resolve({});
                        } else if (response.status == 403) {
                            Router.push('/login')
                        } else {
                            reject(response.data)
                        }
                    })
                    .catch(error => {
                        reject(error);
                    })
            })
        },
        fetch(url, params, headers = {}) {
            return new Promise((resolve, reject) => {
                Axios({
                        method: "post",
                        url: url,
                        data: params,
                        headers: {
                            "Content-Type": "application/json",
                            ...headers
                        }
                    }).then(response => {
                        if (response.status == 200) {
                            resolve(response.data);
                        } else {
                            reject(response.data)
                        }
                    })
                    .catch(error => {
                        reject(error);
                    })
            })
        },
    }
複製代碼
  • markdown 的語法編譯成 html
<mavon-editor ref="editor" :value="content" :subfield="false"
      :defaultOpen="'preview'" :toolbarsFlag="false" :editable="false"
      :scrollStyle="true" :ishljs="true">
    </mavon-editor>
複製代碼
  • 生成 H2,H3 的文章目錄 具體查看(vue-blog-web/views/article/detail.vue)node

  • 其餘都是 vue 的基礎的知識,省略。。。mysql

vue-blog-admin

  • 代理(vue.config.js)
proxy: {
        "/mapi": {
            target: "http://localhost:5454",
            changeOrigin: true,
            pathRewrite: {
                "^/mapi": ""
            }
        },
    },
複製代碼
  • 全局變量
  1. .env.development
  2. .env.production
  • 多級路由實現(首頁 /文章管理 /文章評論)
    多級路的緣由,由於如今不少的增長和刪除都是彈窗的實現,可是一旦內容不少的時候的,新頁面的就是最佳的方法

實現的主要方法: meta 的 hidden 的屬性
具體查看(vue-blog-admin/src/router/routes.js)ios

vue-blog-koa

  • 使用 koa-generator 生成 koa2 項目nginx

    blog.csdn.net/Zhooson/art…git

  • 運行的項目必定先修改 config/db.js 的文件,否則會報錯的,項目運行自動生成數據庫,本身在數據庫 User 表建立的一個用戶,而後登陸 admin 的操做github

const Sequelize = require('sequelize');
    /**
    *
    * 配置數據庫
    *
    * 第一個參數 myblog3   數據庫名字 (本身修改)
    * 第二個參數 root      數據庫名字 (本身修改)
    * 第三個參數 password  數據庫密碼 (本身修改)
    */
    const sequelize = new Sequelize('myblog3', 'root', '你的數據庫密碼', {
        host: 'localhost',
        dialect: 'mysql',
        operatorsAliases: false,
        dialectOptions: {
            charset: "utf8mb4",
            collate: "utf8mb4_unicode_ci",
            supportBigNumbers: true,
            bigNumberStrings: true
        },

        pool: {
            max: 5,
            min: 0,
            acquire: 30000,
            idle: 10000
        },
        timezone: '+08:00' //東八時區
    });

    module.exports = {
        sequelize
    }
複製代碼
  • jwt 權限校驗
// 簽發token
    const token = jwt.sign(userToken, JWT_SECRET, { expiresIn: '10h' });
複製代碼
  • 接口白名單
  1. myblog3-app-5454.js
// jwt
app.use(
    koajwt({ secret: JWT_SECRET }).unless({
        path: [
            // 登陸
            /^\/api\/user\/login/,
            ...
        ],
    })
);
複製代碼
  1. middleware/JWTPath.js
module.exports = [
    // 登陸
    '/api/user/login',
    ...
];
複製代碼
const nodemailer = require("nodemailer");

    // code...

    // Use Smtp Protocol to send Email
    var transporter = nodemailer.createTransport({
        //node_modules/nodemailer/well-known/services.json 支持列表
        host: 'smtp.qq.com',
        port: 465, // SMTP 端口
        secure: true,
        auth: {
            user: NODEMAILER.email,
            //這裏密碼不是qq密碼,是你設置的smtp密碼(受權碼)
            pass: NODEMAILER.pass
        }
    });
複製代碼
  • node 調用第三方接口
const koaRequest = require('koa-http-request');

    app.use(koaRequest({
        json: true, //automatically parsing of JSON response
        timeout: 3000, //3s timeout
        // host: 'https://api.github.com'
    }));
複製代碼
  • github 受權 流程(controllers/GithubToken.js)web

    1. 註冊 OAuth APP 的應用
    2. 保存 client_id client_secret
    3. 訪問 GET: github.com/login/oauth…
    4. 跳轉 http://localhost:3000/auth?code=8b309cc03f95 保存 code 字段
    5. github.com/login/oauth… POST 請求 body:{client_id,client_secret,code} 獲取 token
    6. api.github.com/user POST 請求: body:{client_id,client_secret} header: {Authorization: token token}
  • 上傳圖片(controllers/UploadServer.js)

    koa-body 實現上傳圖片,遞歸建立目錄等

  • sequelize 數據庫實現 (controllers/schema.js) 以 user.js 爲例子

const moment = require('moment');
  module.exports = function(sequelize, DataTypes) {
    return sequelize.define('user', {
        id: {
            type: DataTypes.INTEGER.UNSIGNED,
            allowNull: false,
            primaryKey: true,
            autoIncrement: true
        },
        // 用戶名字
        username: {
            type: DataTypes.STRING(100),
            field: 'username',
            allowNull: false
        },
        // 用戶密碼
        password: {
            type: DataTypes.STRING(255),
            field: 'password',
            allowNull: false
        },
        // 用戶郵箱
        email: {
            type: DataTypes.STRING(100),
            field: 'email',
            allowNull: false
        },
        createdAt: {
            type: DataTypes.DATE,
            field: 'created_at',
            get() {
                return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss');
            }
        },
        updatedAt: {
            field: 'updated_at',
            type: DataTypes.DATE,
            get() {
                return moment(this.getDataValue('updatedAt')).format('YYYY-MM-DD HH:mm:ss');
            }
        }
    }, {
        // 若是爲 true 則表的名稱和 model 相同,即 user
        // 爲 false MySQL建立的表名稱會是複數 users
        // 若是指定的表名稱本就是複數形式則不變
        freezeTableName: true
    })
}
複製代碼
  • sequelize 的使用方法:

    以 Article 爲模型例子

  1. 建立
Article.create(params)
複製代碼
  1. 編輯
Article.update({ browser }, {
        where: {
            id
        },
        fields: ['browser']
    })
複製代碼
  1. 查看詳情
Article.findOne({
        where: id,
    });
複製代碼
  1. 刪除
Article.destroy({
      where: {
          id,
      }
  })
複製代碼
  1. 分頁列表
Article.findAndCountAll({
      row: true,
      limit: +pageSize,
      offset: (pageIndex - 1) * (+pageSize),
      order: [
          ['id', 'DESC']
      ],
  });
複製代碼
  1. 模糊搜索
const Op = Sequelize.Op;

  Article.findAndCountAll({
      row: true,
      limit: +pageSize,
      offset: (pageIndex - 1) * (+pageSize),
      where:{
        title: {
            // 模糊查詢
            [Op.like]: '%' + keyword + '%',
        },
      }
      order: [
          ['id', 'DESC']
      ],
  });

複製代碼

補充 Op 的知識

[Op.and]: {a: 5} // 且 (a = 5)
    [Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6)
    [Op.gt]: 6, // id > 6
    [Op.gte]: 6, // id >= 6
    [Op.lt]: 10, // id < 10
    [Op.lte]: 10, // id <= 10
    [Op.ne]: 20, // id != 20
    [Op.eq]: 3, // = 3
    [Op.not]: true, // 不是 TRUE
    [Op.between]: [6, 10], // 在 6 和 10 之間
    [Op.notBetween]: [11, 15], // 不在 11 和 15 之間
    [Op.in]: [1, 2], // 在 [1, 2] 之中
    [Op.notIn]: [1, 2], // 不在 [1, 2] 之中
    [Op.like]: '%hat', // 包含 '%hat'
    [Op.notLike]: '%hat' // 不包含 '%hat'
    [Op.iLike]: '%hat' // 包含 '%hat' (不區分大小寫) (僅限 PG)
    [Op.notILike]: '%hat' // 不包含 '%hat' (僅限 PG)
    [Op.regexp]: '^[h|a|t]' // 匹配正則表達式/~ '^[h|a|t]' (僅限 MySQL/PG)
    [Op.notRegexp]: '^[h|a|t]' // 不匹配正則表達式/!~ '^[h|a|t]' (僅限 MySQL/PG)
    [Op.iRegexp]: '^[h|a|t]' // ~_ '^[h|a|t]' (僅限 PG)
    [Op.notIRegexp]: '^[h|a|t]' // !~_ '^[h|a|t]' (僅限 PG)
    [Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何數組['cat', 'hat'] - 一樣適用於 iLike 和 notLike
    [Op.overlap]: [1, 2] // && [1, 2](PG數組重疊運算符)
    [Op.contains]: [1, 2] // @> [1, 2](PG數組包含運算符)
    [Op.contained]: [1, 2] // <@ [1, 2](PG數組包含於運算符)
    [Op.any]: [2,3] // 任何數組[2, 3]::INTEGER (僅限 PG)
    [Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用數據庫語言特定的列標識符, 本例使用 PG
複製代碼
  1. 多對多的關係
Category.belongsToMany(Article, {
      through: {
          model: ArticleToCategory,
      },
      foreignKey: 'categoryId',
  })
  Article.belongsToMany(Category, {
      through: {
          model: ArticleToCategory,
      },
      foreignKey: 'articleId',
  })
複製代碼

數據庫表介紹

  • article -------------- 文章
  • articleComment ------- 文章評論
  • articleTocategory ---- 文章標籤(多對多的關聯關係)
  • category ------------- 標籤
  • comment -------------- 留言
  • user ----------------- 用戶

發佈

  1. 個人服務器環境 是 CentOS 7.5 64 位
  2. web 和 admin 打包發佈 dist 的文件, koa 所有文件發佈,別發佈 nodel_modules 啊, 本身的服務器安裝 node,nginx,pm2 , Mysql 等操做
  3. 針對的 koa 項目發佈須要注意點,由於線上每次上傳圖片寫在 public/images 這個文件可千萬不能覆蓋,爲了不這個問題,本身搭建一個文件服務項目,專門管理文件資源
  • myblog3-web-2222.js / myblog3-admin-1111.js (2 個文件就是端口號不同)
    執行 pm2 start myblog3-web-2222.js 把 web 的項目啓動
    執行 pm2 start myblog3-admin-1111.js 把 admin 的項目啓動
    執行 pm2 start myblog3-app-5454.js 把 node 的項目啓動

  • 文件夾結構; 以 web 爲例子(須要先下載 express)

|--web
|---- dist (vue 打包的文件)
|---- myblog3-web-2222.js ( 文件以下代碼所示)
|---- package.json (npm i express)
|---- node_moudles

//引入express中間件
    var express = require('express');
    var app = express();

    //指定啓動服務器到哪一個文件夾,我這邊指的是dist文件夾
    app.use(express.static('./dist'));

    // 監聽端口爲1111
    var server = app.listen(2222, function() {
        console.info('複製打開瀏覽器');
    });
複製代碼

nginx 配置

  • 登陸服務器(2 種登陸方式)

    1. Filezilla 軟件登陸
    2. ssh 登陸
    ssh -p 22 root@你的服務器ip地址
      回車
      輸入密碼
    複製代碼
  • 已 ssh 登陸爲例子: 查找 nginx.conf 的位置

whereis nginx.conf
複製代碼

查找結果:

/usr/local/nginx/conf

複製代碼

熟悉 vim 的操做的,直接操做,不熟悉的使用的 FileZilla 的軟件去修改

  • nginx.conf(以 web 爲例子) 具體詳見 ngixn.conf
#博客 - 後臺管理
    upstream myblog3Admin {
    	server 127.0.0.1:1111;
    }

    server {
        listen       80;
        server_name  www.zhooson.cn;
        #charset koi8-r;

        #access_log logs/host.access.lsog main;

        location / {

          #設置主機頭和客戶端真實地址,以便服務器獲取客戶端真實IP
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          #禁用緩存
          proxy_buffering off;

          #反向代理的地址
          proxy_pass http://myblog3Web;

          root /webproject/myblog3/web;
          index index.html index.htm;
          try_files $uri $uri/ /index.html;

        }

        location /api {
          proxy_pass http://myblog3Koa2;
        }


        #error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

複製代碼
  • 在 web 和 admin 項目的時候訪問 node 接口如何代理,關鍵代碼以下
location /api {
          proxy_pass http://myblog3Koa2;
     }
複製代碼
  • 在 web 和 admin 用 mode:history 的配置
root /webproject/myblog3/web;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
複製代碼

pm2

PM2 是 node 進程管理工具,能夠利用它來簡化不少 node 應用管理的繁瑣任務,如性能監控、自動重啓、負載均衡等,並且使用很是簡單。

安裝方法

npm i pm2 -g
複製代碼

經常使用命令記錄

  • pm2 start app.js # 啓動 app.js 應用程序

  • pm2 start app.js -i 4 # cluster mode 模式啓動 4 個 app.js 的應用實例

  • pm2 start app.js --name="api" # 啓動應用程序並命名爲 "api"

  • pm2 start app.js --watch # 當文件變化時自動重啓應用

  • pm2 start script.sh # 啓動 bash 腳本

  • pm2 list # 列表 PM2 啓動的全部的應用程序

  • pm2 monit # 顯示每一個應用程序的 CPU 和內存佔用狀況

  • pm2 show [app-name] # 顯示應用程序的全部信息

  • pm2 logs # 顯示全部應用程序的日誌

  • pm2 logs [app-name] # 顯示指定應用程序的日誌

  • pm2 flush # 清空全部日誌文件

  • pm2 stop all # 中止全部的應用程序

  • pm2 stop 0 # 中止 id 爲 0 的指定應用程序

  • pm2 restart all # 重啓全部應用

  • pm2 reload all # 重啓 cluster mode 下的全部應用

  • pm2 gracefulReload all # Graceful reload all apps in cluster mode

  • pm2 delete all # 關閉並刪除全部應用

  • pm2 delete 0 # 刪除指定應用 id 0

  • pm2 scale api 10 # 把名字叫 api 的應用擴展到 10 個實例

  • pm2 reset [app-name] # 重置重啓數量

  • pm2 startup # 建立開機自啓動命令

  • pm2 save # 保存當前應用列表

  • pm2 resurrect # 從新加載保存的應用列表

  • pm2 update # Save processes, kill PM2 and restore processes

  • pm2 generate # Generate a sample json configuration file

pm2 文檔地址:pm2.keymetrics.io/docs/usage/…

關於 koa 中環境變量

在 blog-koa/package.json 中

"scripts": {
    "dev": "cross-env NODE_ENV=development nodemon myblog3-app-5454.js",
    "prod": "cross-env NODE_ENV=production nodemon myblog3-app-5454.js"
  },
複製代碼

咱們在本地調試中可使用 npm run dev 去拿到 process.env.NODE_ENV的環境變量,咱們在 pm2 的環境下如何拿到呢??

具體實現的方法:(本文此處的才標註此代碼,具體文件中不作代碼添加

步驟 1: 和 myblog3-app-5454.js 同級別建立文件: ecosystem.config.js

// 這裏能夠寫 生產環境,測試環境,預發佈環境等
    module.exports = {
        apps: [{
            // 生產環境
            name: "myblog3-app-5454-prod",
            // 項目啓動入口文件
            script: "./myblog3-app-5454.js",
            // 項目環境變量
            env: {
                "NODE_ENV": "production",
                "PORT": 5454
            }
        },
        {
            ....
        }]
    }
複製代碼

步驟 2: 修改的package.json文件,添加一行代碼:

"start": "pm2 start ecosystem.config.js --only myblog3-app-5454-prod --watch"
複製代碼

步驟 3: 如何運行項目

本文初說明能夠用的 pm2 start myblog3-app-5454.js 的方式運行,如今使用 npm start啓動, myblog3-app-5454-prod 表明這個進程的 name ,其實就是--name=myblog3-app-5454-prod 的寫法

步驟 4: 直接文件中就能夠 process.env.NODE_ENV

PS: 不須要的 process.env.NODE_ENV 此功能的徹底前一種方式就能夠了,不過項目畢竟都是區分環境,最好的使用下哈

轉載請附上原文出處連接及本聲明。 原文連接:www.zhooson.cn/article/det… github.com/liuxingzhij…

相關文章
相關標籤/搜索