使用Koa2實現了一個node.js後端服務器快速啓動模板(即具有後端服務器的基本功能),使用了路由、數據庫鏈接、請求體處理、異常處理、靜態資源請求處理、session、登陸攔截器等中間件,基本實現了一個node.js後端服務器的基本功能。並設計實現了用戶模塊的登陸、註冊、查找用戶名接口。javascript
以前發了篇專欄 基於Vuex實現小米商城購物車,有同窗好奇問我接口數據怎麼來?昨晚我忽然想到,能夠從那個後端服務器把關鍵部分抽離出來實現一個後端服務器快速啓動模板,須要使用的時候只須要分模塊的添加一些接口並實現,就能夠快速的構建起來一個後端服務器。前端
npm install koa -S
複製代碼
const Koa = require('koa');
let { Port } = require('./config');
let app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
// 監聽服務器啓動端口
app.listen(Port, () => {
console.log(`服務器啓動在${ Port }端口`);
});
複製代碼
就這樣一個node.js服務器就啓動起來了,java
使用postman測試一下node
思路:mysql
./src/roters/index.js
;require('./routers')
。└── src # 源代碼目錄
└── routers # 路由目錄
└── router # 子路由目錄
├── usersRouter.js # 用戶模塊子路由
├── ... # 更多的模塊子路由
├── index.js # 路由入口文件
複製代碼
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
git
思路:github
db.query('select * from users where userName = ? and password = ?', ['userName', 'password'])
複製代碼
npm install mysql -S
複製代碼
在config.js添加以下代碼,而後在db.js引入sql
// 數據庫鏈接設置
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文檔。npm
思路:
let { userName, password } = ctx.request.body;
複製代碼
npm install koa-body -S
複製代碼
在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: '服務器未知錯誤'
}
}
});
複製代碼
思路:
./public
;http://localhost:5000/public/文件夾/文件名
直接訪問。npm install koa-static -S
複製代碼
在config.js配置靜態資源路徑
staticDir: path.resolve('../public'), // 靜態資源路徑
複製代碼
在app.js使用koa-static中間件
const KoaStatic = require('koa-static');
let { staticDir } = require('./config');
複製代碼
// 爲靜態資源請求重寫url
app.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
思路:
npm install koa-session -S
複製代碼
建立"./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');
// session
const CONFIG = require('./middleware/session');
app.keys = ['session app keys'];
app.use(Session(CONFIG, app));
複製代碼
思路:
/user/
開頭;/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);
複製代碼
思路:
└── 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;
}
}
複製代碼
登陸測試
註冊測試
查找用戶名測試
筆者:hai-27
2020年3月15日