快速建立一個伺服器安裝koanpm install koa -S基本配置const Koa = require('koa');let { Port } = require('./config');let app = new Koa();// responseapp.use(ctx => { ctx.body = 'Hello Koa';});// 監聽伺服器啟動埠app.listen(Port, () => { console.log(`伺服器啟動在${ Port }埠`);});測試
就這樣一個node.js伺服器就啟動起來了,
使用postman測試一下
路由中介軟體
思路:
使用koa-router中介軟體處理路由;如果把所有的路由寫在一起,將會非常擁擠,不利於後期維護,所以為每個業務模組配置模組子路由;然後把所有的模組子路由彙總到./src/roters/index.js;再在入口檔案require('./routers')。路由中介軟體目錄└── src # 原始碼目錄 └── routers # 路由目錄 └── router # 子路由目錄 ├── usersRouter.js # 使用者模組子路由 ├── ... # 更多的模組子路由 ├── index.js # 路由入口檔案
安裝koa-router
npm install koa-router -S
模組子路由設計
const Router = require('koa-router');// 匯入控制層const usersController = require('../../controllers/usersController');let usersRouter = new Router();usersRouter .post('/users/login', usersController.Login)module.exports = usersRouter;
模組子路由彙總const Router = require('koa-router');let Routers = new Router();const usersRouter = require('./router/usersRouter');Routers.use(usersRouter.routes());module.exports = Routers;
使用路由中介軟體
// 使用路由中介軟體const Routers = require('./routers');app.use(Routers.routes()).use(Routers.allowedMethods());
介面測試
使用postman測試介面localhost:5000/users/login
資料庫連線封裝思路:
後端與資料庫的互動是非常頻繁的,如果是一個接一個地建立和管理連線,將會非常麻煩;所以使用連線池的方式,封裝一個連線池模組;對連線進行集中的管理(取出連線,釋放連線);執行查詢使用的是connection.query(),對connection.query()進行二次封裝,統一處理異常;向外匯出一個db.query()物件,使用的時候,只需要傳入sql語句、查詢引數即可,例如:db.query('select * from users where userName = ? and password = ?', ['userName', 'password'])
安裝mysql依賴包npm install mysql -S
配置連線選項
在config.js新增如下程式碼,然後在db.js引入
// 資料庫連線設定dbConfig: { connectionLimit: 10, host: 'localhost', user: 'root', password: '', database: 'storeDB'}
連線池封裝
建立"./src/models/db.js"
var mysql = require('mysql');const { dbConfig } = require('../config.js');var pool = mysql.createPool(dbConfig);var db = {};db.query = function (sql, params) { return new Promise((resolve, reject) => { // 取出連線 pool.getConnection(function (err, connection) { if (err) { reject(err); return; } connection.query(sql, params, function (error, results, fields) { console.log(`${ sql }=>${ params }`); // 釋放連線 connection.release(); if (error) { reject(error); return; } resolve(results); }); }); });}// 匯出物件module.exports = db;
更多的資訊請參考mysql文件。
請求體資料處理思路:
使用koa-body中介軟體,可以很方便的處理請求體的資料,例如let { userName, password } = ctx.request.body;
安裝koa-body中介軟體
npm install koa-body -S
使用koa-body中介軟體
在config.js配置上傳檔案路徑
uploadDir: path.join(__dirname, path.resolve('../public/')), // 上傳檔案路徑
在app.js使用koa-body中介軟體
const KoaBody = require('koa-body');let { uploadDir } = require('./config');
// 處理請求體資料app.use(KoaBody({ multipart: true, // parsedMethods預設是['POST', 'PUT', 'PATCH'] parsedMethods: ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE'], formidable: { uploadDir: uploadDir, // 設定檔案上傳目錄 keepExtensions: true, // 保持檔案的字尾 maxFieldsSize: 2 * 1024 * 1024, // 檔案上傳大小限制 onFileBegin: (name, file) => { // 檔案上傳前的設定 // console.log(`name: ${name}`); // console.log(file); } }}));
異常處理思路:
程式在執行的過程中難免會出現異常;如果因為一個異常伺服器就掛掉,那會大大增加伺服器的維護成本,而且體驗極差;所以在中介軟體的執行前進行一次異常處理。在app.js新增如下程式碼
// 異常處理中介軟體app.use(async (ctx, next) => { try { await next(); } catch (error) { console.log(error); ctx.body = { code: '500', msg: '伺服器未知錯誤' } }});
靜態資源伺服器
思路:
前端需要大量的靜態資源,後端不可能為每條靜態資源的請求都寫一份程式碼;koa-static可以非常方便的實現一個靜態資源伺服器;只需要建立一個資料夾統一放靜態資源,例如./public;那麼就可以通過http://localhost:5000/public/資料夾/檔名直接訪問。安裝koa-static中介軟體npm install koa-static -S
使用koa-static中介軟體
在config.js配置靜態資源路徑
staticDir: path.resolve('../public'), // 靜態資源路徑
在app.js使用koa-static中介軟體
const KoaStatic = require('koa-static');let { staticDir } = require('./config');
// 為靜態資源請求重寫urlapp.use(async (ctx, next) => { if (ctx.url.startsWith('/public')) { ctx.url = ctx.url.replace('/public', ''); } await next();});// 使用koa-static處理靜態資源app.use(KoaStatic(staticDir));
介面測試
使用瀏覽器測試介面http://localhost:5000/public/imgs/a.png
session實現思路:
使用koa-session中介軟體實現session的操作;用於登入狀態的管理;本例子使用記憶體儲存的方案,適用於session資料量小的場景;如果session資料量大,建議使用外部儲存介質存放session資料 。安裝koa-session中介軟體npm install koa-session -S使用koa-session中介軟體建立"./src/middleware/session.js"
let store = { storage: {}, set (key, session) { this.storage[key] = session; }, get (key) { return this.storage[key]; }, destroy (key) { delete this.storage[key]; }}let CONFIG = { key: 'koa:session', maxAge: 86400000, autoCommit: true, // 自動提交標頭(預設為true) overwrite: true, // 是否可以覆蓋(預設為true httpOnly: true, // httpOnly與否(預設為true) signed: true, // 是否簽名(預設為true) rolling: false, // 強制在每個響應上設定會話識別符號cookie。到期重置為原始的maxAge,重置到期倒數 renew: false, // 在會話即將到期時更新會話,因此我們始終可以使使用者保持登入狀態。(預設為false) sameSite: null, // 會話cookie sameSite選項 store // session池}module.exports = CONFIG;在app.js使用koa-session中介軟體
const Session = require('koa-session');// sessionconst CONFIG = require('./middleware/session');app.keys = ['session app keys'];app.use(Session(CONFIG, app));登入攔截器思路:
系統會有一些模組需要使用者登入後才能使用的;介面設計是,需要登入的模組api均以/user/開頭;那麼只需要在全域性路由執行前判斷api是否以/user/;如果是,則判斷是否登入,登入了就放行,否則攔截,直接返回錯誤資訊;如果不是,直接放行。在"./src/middleware/isLogin.js",建立一個驗證是否登入的函式
module.exports = async (ctx, next) => { if (ctx.url.startsWith('/user/')) { if (!ctx.session.user) { ctx.body = { code: '401', msg: '使用者沒有登入,請登入後再操作' } return; } } await next();};在app.js使用登入攔截器
// 判斷是否登入const isLogin = require('./middleware/isLogin');app.use(isLogin);分層設計思路:
路由負責流量分發;控制層負責業務邏輯處理,及返回介面json資料;資料持久層負責資料庫操作;下面以使用者模組的登入、註冊、使用者名稱查詢介面的實現為例說明。目錄結構└── src # 原始碼目錄 └── routers # 路由目錄 └── router # 子路由目錄 ├── usersRouter.js # 使用者模組子路由 ├── ... # 更多的模組子路由 ├── index.js # 路由入口檔案 └── controllers # 控制層目錄 ├── usersController.js # 使用者模組控制層 ├── ... # 更多的模組控制層 └── models # 資料持久層目錄 └── dao # 模組資料持久層目錄 ├── usersDao.js # 使用者模組資料持久層 ├── ... # 更多的模組資料持久層 ├── db.js # 資料庫連線函式 ├── app.js # 入口檔案使用者模組介面實現介面文件
資料庫設計create database storeDB;use storeDB;create table users( user_id int primary key auto_increment, userName char (20) not null unique, password char (20) not null, userPhoneNumber char(11) null);路由設計const Router = require('koa-router');// 匯入控制層const usersController = require('../../controllers/usersController');let usersRouter = new Router();usersRouter .post('/users/login', usersController.Login) .post('/users/findUserName', usersController.FindUserName) .post('/users/register', usersController.Register)module.exports = usersRouter;控制層設計const userDao = require('../models/dao/usersDao');const { checkUserInfo, checkUserName } = require('../middleware/checkUserInfo');module.exports = { /** * 使用者登入 * @param {Object} ctx */ Login: async ctx => { let { userName, password } = ctx.request.body; // 校驗使用者資訊是否符合規則 if (!checkUserInfo(ctx, userName, password)) { return; } // 連線資料庫根據使用者名稱和密碼查詢使用者資訊 let user = await userDao.Login(userName, password); // 結果集長度為0則代表沒有該使用者 if (user.length === 0) { ctx.body = { code: '004', msg: '使用者名稱或密碼錯誤' } return; } // 資料庫設定使用者名稱唯一 // 結果集長度為1則代表存在該使用者 if (user.length === 1) { const loginUser = { user_id: user[0].user_id, userName: user[0].userName }; // 儲存使用者資訊到session ctx.session.user = loginUser; ctx.body = { code: '001', user: loginUser, msg: '登入成功' } return; } //資料庫設定使用者名稱唯一 //若存在user.length != 1 || user.length!=0 //返回未知錯誤 //正常不會出現 ctx.body = { code: '500', msg: '未知錯誤' } }, /** * 查詢是否存在某個使用者名稱,用於註冊時前端校驗 * @param {Object} ctx */ FindUserName: async ctx => { let { userName } = ctx.request.body; // 校驗使用者名稱是否符合規則 if (!checkUserName(ctx, userName)) { return; } // 連線資料庫根據使用者名稱查詢使用者資訊 let user = await userDao.FindUserName(userName); // 結果集長度為0則代表不存在該使用者,可以註冊 if (user.length === 0) { ctx.body = { code: '001', msg: '使用者名稱不存在,可以註冊' } return; } //資料庫設定使用者名稱唯一 //結果集長度為1則代表存在該使用者,不可以註冊 if (user.length === 1) { ctx.body = { code: '004', msg: '使用者名稱已經存在,不能註冊' } return; } //資料庫設定使用者名稱唯一, //若存在user.length != 1 || user.length!=0 //返回未知錯誤 //正常不會出現 ctx.body = { code: '500', msg: '未知錯誤' } }, Register: async ctx => { let { userName, password } = ctx.request.body; // 校驗使用者資訊是否符合規則 if (!checkUserInfo(ctx, userName, password)) { return; } // 連線資料庫根據使用者名稱查詢使用者資訊 // 先判斷該使用者是否存在 let user = await userDao.FindUserName(userName); if (user.length !== 0) { ctx.body = { code: '004', msg: '使用者名稱已經存在,不能註冊' } return; } try { // 連線資料庫插入使用者資訊 let registerResult = await userDao.Register(userName, password); // 操作所影響的記錄行數為1,則代表註冊成功 if (registerResult.affectedRows === 1) { ctx.body = { code: '001', msg: '註冊成功' } return; } // 否則失敗 ctx.body = { code: '500', msg: '未知錯誤,註冊失敗' } } catch (error) { reject(error); } }};資料持久層設計const db = require('../db.js');module.exports = { // 連線資料庫根據使用者名稱和密碼查詢使用者資訊 Login: async (userName, password) => { const sql = 'select * from users where userName = ? and password = ?'; return await db.query(sql, [userName, password]); }, // 連線資料庫根據使用者名稱查詢使用者資訊 FindUserName: async (userName) => { const sql = 'select * from users where userName = ?'; return await db.query(sql, [userName]); }, // 連線資料庫插入使用者資訊 Register: async (userName, password) => { const sql = 'insert into users values(null,?,?,null)'; return await db.query(sql, [userName, password]); }}校驗使用者資訊規則函式module.exports = { /** * 校驗使用者資訊是否符合規則 * @param {Object} ctx * @param {string} userName * @param {string} password * @return: */ checkUserInfo: (ctx, userName = '', password = '') => { // userName = userName ? userName : ''; // password = password ? password : ''; // 判斷是否為空 if (userName.length === 0 || password.length === 0) { ctx.body = { code: '002', msg: '使用者名稱或密碼不能為空' } return false; } // 使用者名稱校驗規則 const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; if (!userNameRule.test(userName)) { ctx.body = { code: '003', msg: '使用者名稱不合法(以字母開頭,允許5-16位元組,允許字母數字下劃線)' } return false; } // 密碼校驗規則 const passwordRule = /^[a-zA-Z]\\w{5,17}$/; if (!passwordRule.test(password)) { ctx.body = { code: '003', msg: '密碼不合法(以字母開頭,長度在6~18之間,只能包含字母、數字和下劃線)' } return false; } return true; }, /** * 校驗使用者名稱是否符合規則 * @param {type} * @return: */ checkUserName: (ctx, userName = '') => { // 判斷是否為空 if (userName.length === 0) { ctx.body = { code: '002', msg: '使用者名稱不能為空' } return false; } // 使用者名稱校驗規則 const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; if (!userNameRule.test(userName)) { ctx.body = { code: '003', msg: '使用者名稱不合法(以字母開頭,允許5-16位元組,允許字母數字下劃線)' } return false; } return true; }}測試登入測試
註冊測試
查詢使用者名稱測試
結語一個node.js(Koa)後端伺服器快速啟動模板到這裡已經搭建好了;需要使用的時候只需要分模組的新增一些介面並實現,就可以快速的構建起來一個後端伺服器;後面還打算加一個檔案上傳(續傳)模組;專案原始碼倉庫:koa2-start-basic,如果你覺得還不錯,可以到Github點Star支援一下哦;筆者還在不斷的學習中,如果有表述錯誤或設計錯誤,歡迎提意見。感謝你的閱讀!作者:hai_27連結:https://juejin.im/post/5e6de50f6fb9a07cae137bf5