利用Express框架建立部落格

2021-04-03 18:00:25

專案資料夾結構

建立專案所需資料夾

  • public 靜態資源
    樣式檔案、指令碼檔案、圖片檔案
  • model 資料庫操作
    建立集合的檔案和連線資料庫的檔案
  • route 路由
    路由檔案
  • views 模板
    模板檔案

route 資料夾:(模組化路由)

route/home 資料夾
route/admin 資料夾

route 資料夾下的檔案

route/home.js
描述:部落格展示頁面路由

//參照express框架
const express = require('express');
//建立部落格展示頁面路由(二級路由)
const home = express.Router();

將回撥函數放入route資料夾下的home資料夾中

//部落格前臺首頁的展示頁面+路由優化
home.get('/', require('./home/index'));
//將路由物件作為模組成員進行匯出
module.exports = home;

route/admin.js
描述:部落格管理頁面路由

//參照express框架
const express = require('express');
//建立部落格展示頁面路由
const admin = express.Router();
//將路由物件作為模組成員進行匯出
module.exports = admin;

回撥函數在route資料夾下的admin資料夾中

//渲染登入頁面
admin.get('/login', require('./admin/loginPage'));

:res.render()方法中第一個引數要傳遞模板路徑,預設情況下要傳遞絕對路徑。但考慮到渲染模板有很多所以需要在app.js中進行設定---(標註1)

//實現登入功能
admin.post('/login', require('./admin/login'));
//建立使用者列表路由
admin.get('/user', require('./admin/userPage'));
//實現退出功能
admin.get('/logout', require('./admin/logout'));
//建立使用者編輯頁面路由
admin.get('/user-edit', require('./admin/user-edit'));
//建立實現使用者新增功能路由
admin.post('/user-edit', require('./admin/user-edit-fn'));
//建立使用者資訊修改功能路由
admin.post('/user-modify', require('./admin/user-modify'));
//刪除使用者功能路由
admin.get('/delete', require('./admin/user-delete'));
//文章列表頁面路由
admin.get('/article', require('./admin/article'));
//文章編輯頁面路由
admin.get('/article-edit', require('./admin/article-edit'));
//實現文章新增功能的路由
admin.post('/article-add', require('./admin/article-add'));

views 資料夾:(模板檔案)

views/home 資料夾

views/home/common資料夾

home 資料夾下的檔案:

views/home/article.art
views/home/default.art

views/admin 資料夾

views/admin/common資料夾(模板的公共部分)
子模版的相對路徑是相對於當前檔案的(由模板引擎解析)

views/admin/common/aside.art(側邊欄)
views/admin/common/header.art(頭部)
views/admin/common/layout.art(HTML骨架)

views/admin 資料夾下的檔案:
模板內部外連資原始檔問題(外連資源是由瀏覽器解析的):
外連資源即模板內部外連的css\js\img檔案。需要注意的是模板檔案中的外連資源的相對路徑是相對與瀏覽器位址列中的請求路徑的,如:http://localhost/admin/login 地址,瀏覽器認為login為路徑下的檔案,/admin為請求路徑,所以模板中的相對路徑是相對與/admin的。
為保證外連資源能夠正常被存取,應該使用絕對路徑。由於在app.js中開放了靜態資源(public資料夾),所以使用絕對路徑存取的是public資料夾下的資源,
如:

<link rel="stylesheet" href="/admin/css/base.css">

"css/base.css"相對路徑前加上了/admin/

views/admin/login.art
為登入表單設定請求地址及請求方式

<form action="/admin/login" method="post" id="loginForm"></form>

為表單項設定name屬性如:

<input name="email" type="email" class="form-control" placeholder="請輸入郵件地址">

注:要阻止表單預設提交行為

<script src="/admin/js/common.js"></script>
<script>
    //為表單新增提交事件
    $('#loginForm').on('submit',function() {
        var result = serializeToJson($(this));
        //trim去除字串兩端空格
        if (result.email.trim().length == 0) {
            alert('請輸入郵件地址');
            //阻止程式向下執行
            return false;
        }
        if (result.password.trim().length == 0) {
            alert('請輸入密碼');
            //阻止程式向下執行
            //return false阻止表單預設提交行為
            return false;
        }
    })
</script>
// 在public/admin/js/common.js中
function serializeToJson(form) {
    var result = {};
    //.serializeArray()是JQury提供的方法 獲取到表單中使用者輸入的內容
    //[{name: "email", value: "uwhdb@gv"},{name: "password", value: "1223"}]返回內容是一個陣列
    var f = form.serializeArray();
    f.forEach(function(item) {
        result[item.name] = item.value;
    });
    return result;
}
views/admin/user.art
views/admin/user-edit.art
views/admin/error.art
views/admin/article.art
views/admin/article-edit.art

model 資料夾:

model/connect.js
連線資料庫

//引入mongoose第三方模組
const mongoose = require('mongoose');
//連線資料庫
mongoose.connect('mongodb://localhost/blog', { useNewUrlParser: true, useUnifiedTopology: true }).then(() => console.log('資料庫連線成功')).catch(() => console.log('資料庫連線失敗'));

model/user.js

//引入mongoose第三方模組
const mongoose = require('mongoose');
//建立使用者集合規則
const userSchema = new mongoose.Schema({});
//建立使用者集合
const User = mongoose.model('User', userSchema);
//ES6中如果物件的鍵和值名稱一樣 可以省略值
module.exports = {User,validateUser};

model/article.js

//引入mongoose第三方模組
const mongoose = require('mongoose');
//建立文章集合規則
const articleSchema = new mongoose.Schema
//根據規則建立集合
const Article = mongoose.model('Article', articleSchema);
//將集合規則作為模組成員進行匯出
module.exports = {Article}

model/comment.js


根目錄下的檔案
package.json檔案
可以通過初始化專案描述檔案得到

//命令列中輸入
npm init -y

app.js檔案
描述:專案入口檔案
app.js檔案需要進行的操作:

//參照express框架
const express = require('express');
//建立網站伺服器
const app = express();
//監聽埠
app.listen(80);
console.log('網站伺服器啟動成功');

設定路由

//匯入路由模組
const home = require('./route/home');
const admin = require('./route/admin');
//為路由匹配請求路徑
app.use('/home', home);
app.use('/admin', admin);

設定靜態資源

//引入path系統模組
const path = require('path');
//開放靜態資原始檔
app.use(express.static(path.join(__dirname, 'public')));

模板設定--對本文中標註1進行解釋

//當渲染字尾為art的模板時 所使用的模板引擎是什麼 以便使用res.render()方法
app.engine('art', require('express-art-template'));
//告訴express框架模板所在位置
app.set('views', path.join(__dirname, 'views'));
//告訴express框架模板的預設字尾
app.set('view engine', 'art');
//資料庫連線
require('./model/connect');
//引入body-parser模組 用來處理post請求引數
const bodyParser = require('body-parser');
//處理post請求引數
//extended: false表示使用系統模組querystring解析 而不是其他第三方模組
app.use(bodyParser.urlencoded({ extended: false }));
//匯入express-session模組
const session = require('express-session');
//設定session secret的值可以自定義
//saveUninitialized false 沒有登入的情況下不儲存cookie
// cookie: {maxAge: 24 * 60 * 60 * 1000} 設定cookie的過期時間
app.use(session({secret: 'secret key',saveUninitialized: false,cookie: {maxAge: 24 * 60 * 60 * 1000}}));
//攔截請求 判斷使用者登入狀態
//use的第一個引數/admin是指以/admin開頭的路由
app.use('/admin', require('./middleware/loginGuard'));

專案包含的知識點

密碼加密 bcrypt

雜湊加密是單程加密方式:1234 => abcd
在加密的密碼中加入隨機字串可以增加密碼被破解的難度。

// 匯入bcrypt模組
const bcrypt = require('bcrypt');
// 生成隨機字串 gen => generate 生成 salt 鹽
let salt = await bcrypt.genSalt(10);
// 使用隨機字串對密碼進行加密
let pass = await bcrypt.hash('明文密碼', salt);
// 密碼比對
let isEqual = await bcrypt.compare('明文密碼', '加密密碼');

bcrypt依賴的其他環境

  1. python 2.x
    安裝好後新增環境變數 也就是安裝路徑
    image.png
  2. node-gyp
    npm install -g node-gyp
  3. windows-build-tools
    npm install --global --production windows-build-tools
  4. bcrypt
    npm install bcrypt

cookie與session

網站應用是基於http協定,即請求與相應模型的應用。特點是:在完成一次使用者端和伺服器端的請求與響應後,使用者端與伺服器端便沒有聯絡了(http協定的無狀態性)
cookie:瀏覽器在電腦硬碟中開闢的一塊空間,主要供伺服器端儲存資料。

  • cookie中的資料是以域名的形式進行區分的。
  • cookie中的資料是有過期時間的,超過時間資料會被瀏覽器自動刪除。如果沒有設定過期時間,瀏覽器在關閉的時候就會刪除。
  • cookie中的資料會隨著請求被自動傳送到伺服器端。

image.png

session:實際上就是一個物件,儲存在伺服器端的記憶體中,在session物件中也可以儲存多條資料,每一條資料都有一個sessionid做為唯一標識。

image.png

在node.js中需要藉助express-session實現session功能。

const session = require('express-session');
app.use(session({ secret: 'secret key' }));
//為請求物件下面新增session屬性
//secret key金鑰值可以自定義

Joi

JavaScript物件的規則描述語言和驗證器。

const Joi = require('joi');
const schema = {
username: Joi.string().alphanum().min(3).max(30).required().error(new Error(‘錯誤資訊’)),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
};
Joi.validate({ username: 'abc', birthyear: 1994 }, schema);

資料分頁

當資料庫中的資料非常多時,資料需要分批次顯示,這時就需要用到資料分頁功能。
分頁功能核心要素:

  1. 當前頁,使用者通過點選上一頁或者下一頁或者頁碼產生,使用者端通過get引數方式傳遞到伺服器端
  2. 總頁數,根據總頁數判斷當前頁是否為最後一頁,根據判斷結果做響應操作
總頁數:Math.ceil(總資料條數 / 每頁顯示資料條數)
limit(2) // limit 限制查詢數量  傳入每頁顯示的資料數量
skip(1) // skip 跳過多少條資料  傳入顯示資料的開始位置
資料開始查詢位置=(當前頁-1)* 每頁顯示的資料條數
let page = req.query.page || 1;
//每一頁顯示的資料條數
let pagesize = 10;
//查詢使用者資料的總數 使用者集合User下有countDocuments()方法
let count = await User.countDocuments({});
//總頁數 向上取整
let total = Math.ceil(count / pagesize);
//頁碼對應的開始位置
let start = (page - 1) * pagesize;
// 將使用者資訊從資料庫中查詢出來
//渲染使用者列表模板 
res.render('admin/user', {
    users: users,
    page: page,
    total: total
});

formidable

作用:解析表單,支援get請求引數,post請求引數、檔案上傳。

 // 引入formidable模組
 const formidable = require('formidable');
 // 建立表單解析物件
 const form = new formidable.IncomingForm();
 // 設定檔案上傳路徑
 form.uploadDir = "/my/dir";
 // 是否保留表單上傳檔案的擴充套件名
 form.keepExtensions = ture;
 // 對錶單進行解析
 form.parse(req, (err, fields, files) => {
// fields 儲存普通請求引數
// files 儲存上傳的檔案資訊
 });

檔案讀取 FileReader

    var file = document.querySelector('#file');
    var preview = document.querySelector('#preview');
    //當使用者選擇完檔案以後
    file.onchange = function() {
        //建立檔案讀取物件
        var reader = new FileReader();
        //使用者選擇的檔案列表 this.files
        // this.files[0] 在一組使用者中選擇第一個檔案
        //讀取檔案  .readAsDataURL()是非同步方法
        reader.readAsDataURL(this.files[0]);
        //監聽onload
        reader.onload = function() {
            //將檔案讀取的結果顯示在頁面中
            preview.src = reader.result;
        }
    }

零碎知識點

//根據app.js檔案中const app = express();
//請求物件下面有一個app屬性 也就是建立網站伺服器的app
req.app
//把公共資料暴露到模板中
app.locals
//express框架下的頁面重定向方法
res.redirect('');
//.redirect除了重定外 還做了res.end這步操作 所以要return
//不然命令列會報錯
return res.redirect(`/admin/user-edit?message=郵箱地址已經被佔用`);
//模板語法@表示原文輸出
<td>{{@$value._id}}</td>
//express框架中的錯誤處理中介軟體
app.use((err, req, res, next) => {
    //將字串物件轉換為物件型別
    //JSON.parse()
    const result = JSON.parse(err);
    let params = [];
    for (let attr in result) {
        if (attr != 'path') {
            params.push(attr + '=' + result[attr]);
        }
    }
    // res.redirect(`${result.path}?message=${result.message}`)
    res.redirect(`${result.path}?${params.join('&')}`);
})
觸發錯誤處理中介軟體
try {
    //程式碼優化 請求驗證處理程式碼放在了user.js中
    await validateUser(req.body);
} catch (err) {
    //驗證沒有通過
    //next()裡只能傳遞一個字串引數
    //JSON.stringify()將物件資料型別轉換為字串資料型別
    //程式碼優化 錯誤處理優化
    return next(JSON.stringify({ path: '/admin/user-edit', message: err.message }))
}
//減號有隱式型別轉換 而加號為字串拼接
<a href="/admin/user?page=<%=page-1%>">
<a href="/admin/user?page=<%=page-0+1%>">

如果表單要進行檔案上傳,表單的資料必須以二進位制的資料上傳

<!--
form下的屬性enctype 指定表單的編碼型別
預設值為 application/x-www-form-urlencoded
如:name=zhangsan&age=10
multipart/form-data 將表單資料編碼成二進位制型別
-->

由於body-paser只能處理普通表單傳遞過來的引數,而不能處理二進位制資料
使用要使用formidable 第三方模組

字串下有split字串分割方法

//分隔符為public,返回值是一個陣列,取陣列的第二個值
files.cover.path.split('public')[1]

專案所需的第三方模組

//命令列中輸入
npm install express mongoose art-template express-art-template
在express中接收post請求引數需要用到第三方模組body-paeser
//命令列中輸入
npm install body-parser
npm install -g node-gyp
npm install --global --production windows-build-tools
npm install bcrypt
npm install express-session
npm install joi
npm install formidable