最近寫了一個node+mongo+vue的全棧項目,寫一個博客網站,該代碼node/vue都很注重模塊化處理, node中間件的使用,該代碼主要注重全棧前后端接口的調用及處理,前端的一些細節如驗證等并未寫,以下是整理出來遇到的問題及解決方案,歡迎多交流共同學習~
項目代碼:https://github.com/Little-God/node-vue-blog
1.mongodb模塊
把mongodb的增刪改查操作都封裝成模塊方法
const assert = require('assert');
const ObjectID = require('mongodb').ObjectID;
const insertDocuments = function(db, documents, query, callback) {
// Get the documents collection
const collection = db.collection(documents);
// Insert some documents
collection.insertMany([query], function(err, result) {
assert.equal(err, null);
callback(result);
});
}
const findDocuments = function(db, documents, query, callback) {
// Get the documents collection
const collection = db.collection(documents);
// Find some documents
if(query._id) query._id = ObjectID(query._id)
collection.find(query).toArray(function(err, docs) {
assert.equal(err, null);
callback(docs);
});
}
const updateDocument = function(db, documents, query, setQuery, callback) {
// Get the documents collection
const collection = db.collection(documents);
// Update document where a is 2, set b equal to 1
collection.updateOne(query, setQuery, function(err, result) {
assert.equal(err, null);
callback(result);
});
}
const removeDocument = function(db, documents, query, callback) {
// Get the documents collection
const collection = db.collection(documents);
// Delete document where a is 3
if(query._id) query._id = ObjectID(query._id)
collection.deleteOne(query, function(err, result) {
assert.equal(err, null);
callback(result);
});
}
const indexCollection = function(db, documents, query, callback) {
db.collection(documents).createIndex(
query,
null,
function(err, results) {
console.log(results);
callback();
}
);
};
module.exports = {
insertDocuments,
findDocuments,
updateDocument,
removeDocument,
indexCollection
}
2.mongodb根據id查詢/刪除
要使用objectID,封裝到findDocument,removeDocument等
詳情見1中代碼
3.static訪問靜態文件
app.use(express.static(path.join(__dirname, 'static')))
4.mode解決跨域問題
使用cors模塊,要先npm i cors -S
const cors = require('cors')
app.use(cors())
5.用戶登錄驗證token
用戶輸入用戶名和密碼登錄時后端返回一個token,前端存在localStorage, 請求接口的時候把localStorage中的token在axios設置header,后段驗證,如果過期,則返回401,重新登錄
jsonwebtokens模塊用來生成token,生成出來的token會包含過期時間
寫一個checkLogin中間件,來驗證token是否過期
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const config = require('../config/default')
const mongo = require('../lib/mongo')
const jwt = require('jsonwebtoken');
module.exports = {
// 驗證token是否過期
checkLogin: function checkLogin(req, res, next) {
if (req.headers.token) {
const token = req.headers.token
console.log(token)
MongoClient.connect(config.mongodb, function (err, client) {
assert.equal(null, err);
const db = client.db(config.dbName);
mongo.findDocuments(db, 'token', {token:token}, function (data) {
if(data.length>0){
// invalid token
jwt.verify(token, 'ken', function(err, decoded) {
if(err) {
return res.json({
code:401,
msg:'token過期,請重新登錄~'
})
}
req.username = decoded.name
client.close();
next()
});
} else {
client.close();
return res.json({
code:401,
msg:'寧還未登錄,請登錄~'
})
}
});
});
} else {
return res.json({
code:401,
esg:'token過期,請重新登錄~'
})
}
},
checkNotLogin: function checkNotLogin(req, res, next) {
if (req.session.user) {
req.flash('error', '已登錄')
return res.redirect('back')// 返回之前的頁面
}
next()
}
}
前端發送請求的時候設置token請求頭,并且攔截響應,看token是否有過期。
import http from 'axios'
import qs from 'querystring';
import router from '@/router'
import Vue from 'vue'
const axios = http.create({
baseURL: 'http://127.0.0.1:3000',
timeout: 1000,
headers: {'Content-Type': 'application/json'}
});
axios.interceptors.response.use((res) => {
if (res.data.code && res.data.code === 401) { // 401, token失效
Vue.prototype.$message.error(res.data.msg);
setTimeout(()=> {
router.push('/signin')
}, 500)
}
return res
}, (err) => {
return Promise.reject(err)
})
axios.interceptors.request.use((config) => {
config.headers['token'] = window.localStorage.getItem('token') || ''
return config
}, error => {
return Promise.reject(error)
})
6.js文件中使用element組件
401過期,全局調用element組件,彈出彈框
引入vue,然后調用 Vue.prototype.$message.error(res.data.msg);
詳情看上面5中前端部分的代碼
7.axios攔截401,在js文件中進行路由router跳轉
router引入
new router()
Main.js中,也是把new router掛載載vue實例中,也就是this.$router
在其他文件中引入也是一樣,直接引入這個實例就可以調用router的方法,比如push
詳情看上面5的前端代碼
8.checkLogin中間間驗證token用戶是否登錄或者過期
驗證同時把username寫在req對象上,后面遇到發布文章或者之類的可以直接從req中拿到username
9.Node獲取不到req.body
通過Postman improve功能,復制瀏覽器的curl請求與postman對比,發現瀏覽器的請求頭為content-type:application-json;utf-8; application-json,
實際上要content-type:application-json,不能有多余的字符才可請求成功
解決:
修改axios源碼
-axios
--lib
---defaults.js
defaults.js文件里transformRequest方法中,為post請求時,把默認加上的content-type的值去掉,自己在引入axios全局設置post的請求頭
- formidable模塊,node上傳圖片等文件接口要使用到的
注意:上傳后文件名會改,并且會去掉后綴名,所以在存儲的時候要用返回域名+儲存后的地址的文件名
const express = require('express')
const router = express.Router()
const formidable = require('formidable');
const path = require('path');
// POST /upload 上傳圖片
router.post('/', function (req, res, next) {
let form = new formidable.IncomingForm();
form.encoding = 'utf-8'; // 編碼
// 保留擴展名
form.keepExtensions = true;
//文件存儲路徑 最后要注意加 '/' 否則會被存在public下
form.uploadDir = path.join(__dirname, '../static/');
// 解析 formData 數據
form.parse(req, (err, fields ,files) => {
if(err) return next(err)
let imgPath = files.file.path;
let imgName = files.file.name;
console.log(imgName, imgPath);
// 返回路徑和文件名
res.json({code: 1, data: { name: imgName, path: 'http://127.0.0.1:3000/'+imgPath.split('/')[imgPath.split('/').length-1] }});
})
})
module.exports = router
11.注意查表的時候如果值為空對象{}那么查出來的是全部文檔
12.restfull設計接口
通過url設計請求方法定義資源的操作
如:同一個接口/posts
post請求是創建文章
delete請求是刪除文章
get請求是獲取文章列表
put請求是更新文章