說明:
node.js提供接口,vue展現頁面,前后端分離,出於編輯器功能和編輯習慣,vue用HbuilderX,node.js用VScode。(PS:僅作為學習筆記,如有不當之處歡迎指出,在此先謝為敬~~~)
環境:
首先需要有node.js環境,安裝教程 在這里,最好下載較新的版本,對es6、es7有更好的支持,再裝個 淘寶鏡像,完畢!
后台:
1、安裝mysql
1.1、mysql下載地址
解壓到安裝位置,修改環境變量,win10編輯環境變量很方便了,win7的話記得以 ; 分割開
1.2、添加配置文件
在mysql的bin目錄下,新建my.ini文件(如果沒有),打開my.ini文件,寫入以下配置內容
[mysqld] # 設置3306端口 port=3306 # 設置mysql的安裝目錄 basedir=D:\\myInstalls\\mysql-8.0.11 # 設置mysql數據庫的數據的存放目錄 datadir=D:\\myInstalls\\mysql-8.0.11\\Data # 允許最大連接數 max_connections=200 # 允許連接失敗的次數。這是為了防止有人從該主機試圖攻擊數據庫系統 max_connect_errors=10 # 服務端使用的字符集默認為UTF8 character-set-server=utf8 # 創建新表時將使用的默認存儲引擎 default-storage-engine=INNODB # 默認使用“mysql_native_password”插件認證 default_authentication_plugin=mysql_native_password [mysql] # 設置mysql客戶端默認字符集 default-character-set=utf8 [client] # 設置mysql客戶端連接服務端時默認使用的端口 port=3306 default-character-set=utf8
1.3、安裝
以管理員身份運行cmd,進入mysql的bin目錄下,不進入也行,因為我們已經配置了環境變量
初始化數據庫,運行 mysqld --initialize --console,記住紅色框內的初始密碼
安裝mysql服務,運行 mysqld --install [服務名] ,服務名可以不寫,安裝完畢 net start mysql 啟動mysql
啟動成果,mysql停止指令 net stop mysql
默認密碼太復雜,改個簡單的,首先運行 mysql -u root -p 進入mysql,密碼是剛才記住的初始密碼
修改密碼指令:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密碼';
OK,mysql我們已經有了,接下來搭建koa2!
2、搭建koa2項目
(你可以使用系統自帶的cmd窗口,也可以用編輯器自帶的。我這里用VScode的命令行終端,看起來特別虛浮~~~)
2.1、我們不一步步搭建,采用koa2框架,並使用koa-generator生成項目,類似vue-cli
安裝指令:cnpm install koa-generator -g
2.2、在你的項目目錄下,運行 koa2 項目名,生成項目,如:koa2 paopao(泡泡是我的貓的名字~~~)
成功,根據上面提示走~~~
cd paopao 進入項目目錄
cnpm install 安裝項目依賴
cnpm start paopao 運行項目(cnpm是淘寶鏡像)
有個報錯大概意思是這個包不再維護了,cnpm uninstall koa-onerror 卸載,重新裝最新的版本 cnpm install koa-onerror --save
在瀏覽器輸入:localhost:3000,瀏覽器運行結果(左),項目結構(右)
3、實現API
3.1、用sequelize來操作數據庫,同時安裝mysql、mysql2
cnpm install sequelize mysql mysql2 --save
所有安裝的依賴可以在package.json里查看:
注意:我在使用時發現koa-static(處理靜態文件的中間件),默認3.0.0版本會報錯,於是更新成了最新版本
使用cnpm install koa-static@5.0.0 --save更新,再查看package.json,版本變成了5.0.0即可
3.2、連接數據庫
在項目根目錄下建一個config文件夾,在該文件夾建一個js文件,取名db.js,用來配置數據庫連接
config-->db.js
var Sequelize = require("sequelize") var sequelize = new Sequelize('paopao','root','happy',{ 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 };
paopao是我的數據庫表名,root數據庫用戶名,happy數據庫用戶密碼
3.3、定義數據庫模型
在根目錄建一個module文件夾,在module文件下面建一個user.js,用來定義數據模型,告訴sequelize怎么跟數據庫的數據一一對應
module-->user.js
module.exports = function(sequelize,DataTypes){ return sequelize.define( 'user', { userId:{ type: DataTypes.INTEGER, primaryKey: true, allowNull: true, autoIncrement: true }, mobileNo:{ type: DataTypes.STRING, allowNull: false, field: 'mobileNo' }, password:{ type: DataTypes.STRING, allowNull: false, field: 'password' } }, { timestamps: false } ); }
3.4、數據庫操作和功能處理
controller-->user.js 添加以下代碼
//引入db配置 const db = require('../config/db') //引入sequelize對象 const Sequelize = db.sequelize //引入數據表模型 const user = Sequelize.import('../module/user') //自動創建表 user.sync({ force: false }); //數據庫操作類 class userModule { static async userRegist(data) { return await user.create({ password: data.password, mobileNo: data.mobileNo }) } static async getUserInfo(mobileNo) { return await user.findOne({ where: { mobileNo } }) } }
數據庫操作有了,接下來進行功能處理,還是在該文件添加
controller-->user.js 里添加該userController 類,並將之exports出去
//功能處理 class userController { } module.exports = userController;
用戶注冊:
在 userController 類里添加用戶注冊邏輯
//注冊用戶 static async create(ctx) { const req = ctx.request.body; if (req.mobileNo && req.password) { try { const query = await userModule.getUserInfo(req.mobileNo); if (query) { ctx.response.status = 200; ctx.body = { code: -1, desc: '用戶已存在' } } else { const param = { password: req.password, mobileNo: req.mobileNo, userName: req.mobileNo } const data = await userModule.userRegist(param); ctx.response.status = 200; ctx.body = { code: 0, desc: '用戶注冊成功', userInfo: { mobileNo: req.mobileNo } } } } catch (error) { ctx.response.status = 416; ctx.body = { code: -1, desc: '參數不齊全' } } } }
因為還要做登錄超時token驗證,用戶登錄成功還要返回token,為了生成token,我們需要安裝幾個中間件
cnpm install jsonwebtoken --save 導入jwt模塊
cnpm install koa-jwt --save koa提供的jwt中間件
在app.js里添加如下代碼:
unless()表示里面的regist、login不做token驗證
const koajwt = require('koa-jwt') // logger app.use(async (ctx, next) => { return next().catch((err) => { if(err.status === 401){ ctx.status = 401; ctx.body = { code: '-2000', desc: '登陸過期,請重新登陸' }; }else{ throw err; } }) }) app.use(koajwt({ secret: '123456' }).unless({ path: [/^\/user\/regist/,/^\/user\/login/] }))
為了解析token,在public目錄下新建tool.js,加入解析token的代碼
const getToken = require('jsonwebtoken') exports.verToken = function(token){ return new Promise((resolve,rejece) => { const info = getToken.verify(token.split(' ')[1],"123456"); resolve(info); }) }
返回controller-->user.js,添加
//引入jwt做token驗證 const jwt = require('jsonwebtoken') //解析token const tools = require('../public/tool') //統一設置token有效時間 為了方便觀察,設為10s const expireTime = '10s'
用戶登錄:
之后就可以寫用戶登錄邏輯了
controller-->user.js-->userController 類里添加
通過 jwt.asign() 方法生成token,這里的123456跟app.js里的123456相同,就理解為一個秘鑰吧~~
//密碼登陸 static async login(ctx) { const req = ctx.request.body; if (!req.mobileNo || !req.password) { return ctx.body = { code: '-1', msg: '用戶名或密碼不能為空' } } else { const data = await userModule.getUserInfo(req.mobileNo); if (data) { if (data.password === req.passWord) { //生成token,驗證登錄有效期 const token = jwt.sign({ user: req.mobileNo, passWord: req.password }, '123456', { expiresIn: expireTime }); const info = { createdAt: data.createdAt, updatedAt: data.updatedAt, mobileNo: data.mobileNo, userId: data.userId } return ctx.body = { code: '0', token: token, userInfo: JSON.stringify(info), desc: '登陸成功' } } else { return ctx.body = { code: '-1', desc: '用戶密碼錯誤' } } } else { return ctx.body = { code: '-1', desc: '該用戶尚未注冊' } } }; }
為了驗證token是否過期,我們再定義一個獲取用戶信息的邏輯,登陸10s后獲取用戶信息,驗證token是否過期
獲取用戶信息:
controller-->user.js-->userController 類里添加
//獲取用戶信息(除密碼外) static async getUserInfo(ctx){ const req = ctx.request.body; const token = ctx.headers.authorization; if(token){ try { const result = await tools.verToken(token); if (!req.mobileNo) { return ctx.body = { code: '-1', desc: '參數錯誤' } } else { let data = await userModule.getUserInfo(req.mobileNo); if (req.mobileNo == data.mobileNo) { const info = { createdAt: data.createdAt, updatedAt: data.updatedAt, mobileNo: data.mobileNo, userId: data.userId }; return ctx.body = { code: '0', userInfo: JSON.stringify(info), desc: '獲取用戶信息成功' } } } } catch (error) { ctx.status = 401; return ctx.body = { code: '-1', desc: '登陸過期,請重新登陸' } } }else{ ctx.status = 401; return ctx.body = { code: '-1', desc: '登陸過期,請重新登陸' } } }
3.5、路由,即處理請求的url,使用koa-router
不用重新導入,koa-generator已經幫我們導入了,直接使用
在routes目錄下新建文件 user.js
寫入以下代碼:
routes-->user.js
const Router = require('koa-router'); const userController = require('../controller/user') const router = new Router({ prefix: '/user' }); //用戶注冊 router.post('/regist',userController.create) //密碼登陸 router.post('/login',userController.login) //獲取用戶信息 router.post('/getUserInfo',userController.getUserInfo) module.exports = router;
然后在入口文件app.js引入
使用
完成這些以后,cnpm run dev 啟動項目(依賴nodemon,package.json里面有,這樣每次更改代碼以后不用手動重新啟動)
啟動正常如下:
如果有報錯,提示缺少這包那包的,不用着急!
把根目錄下的node_modules目錄刪除
檢查一遍package.json
確認無誤后重新cnpm install
再次啟動 cnpm run dev ~~~
補充一點,如果想在其他端口啟動,在app.js里添加 app.listen(3333),修改為3333端口,自動熱刷新~~~蛋是此時接口仍然不可調試,因為存在跨域問題
3.6、解決跨域,koa-cors
koa同樣提供了解決跨域的依賴包
cnpm install koa-cors --save
在app.js添加:
現在可以測試接口了,隨便寫個ajax或者使用postman,postman測試結果:
注冊:
登錄:
查看數據庫結果(使用的是破解版Navicat圖形化數據庫管理工具):
到此為止,API就完成了,最后一步,驗證token過期有沒有效果
4、結合VUE驗證token
寫到太晚了,想起來今天還沒給泡泡鏟屎,VUE就不寫那么詳細了,有空再補上 ~.~
我就貼一下代碼和驗證結果
vue項目里,在接口文件里:
import axios from 'axios'; import qs from 'qs'; import route from '../router'; import { message } from 'ant-design-vue' axios.interceptors.request.use(function(config) { // 處理請求參數 config.data = qs.stringify(config.data) //將token寫入請求頭 if (window.localStorage.getItem('token')) { config.headers.Authorization = `Bearer ${window.localStorage.getItem('token')}`; } return config; }, function(error) { // 對請求錯誤做些什么 return Promise.reject(error); }); axios.interceptors.response.use( response => { return response }, error => { if (error.response) { switch (error.response.status) { case 401: message.error("登錄過期,請重新登錄!", ()=>{ window.localStorage.removeItem("token"); //可能是token過期,清除它 route.replace({ //跳轉到登錄頁面 path: '/login', query: { // 將跳轉的路由path作為參數,登錄成功后跳轉到該路由 redirect: route.currentRoute.fullPath } }); }) } } return Promise.reject(error) // 返回接口返回的錯誤信息 } ); //注冊 export const regist = params => { return axios.post('http://localhost:3333/user/regist', params, {}).then(res => res.data) } //登錄 export const login = params => { return axios.post('http://localhost:3333/user/login', params, {}).then(res => res.data) } //獲取用戶信息 export const getUserInfo = params => { return axios.post('http://localhost:3333/user/getUserInfo', params, {}).then(res => res.data) }
axios.interceptors.request.use攔截請求,給請求頭加上token
axios.interceptors.response.use攔截響應,如果返回401,token過期,跳回login路由
登錄后10s再請求用戶數據,返回登錄過期:
總結:完結撒花,如有不當指出,歡迎各位大神指出,我該鏟屎去了 ~.~