系列文章github項目地址(最終版):https://github.com/zliuyang1287/MyBlog
前言
前面已經瞭解了express快速搭建web項目的應用,瞭解了各個文件目錄的作用,也編寫了一個簡單的頁面測試了一下,所有的這些工作只是瞭解了express的工作流程,從這一篇開始慢慢搭建後臺服務。在前面的文章裏提到過這麼一段代碼位於app.js裏
// 設置模板 其中__dirname爲全局變量,表示當前正在執行的腳本所在的目錄
// 設置模版文件所在路徑,也就是試圖文件,都放在views層
app.set('views', path.join(__dirname, 'views'));
// // 設置模版引擎,比如jade、ejs
app.set('view engine', 'ejs');
這段代碼已經被我註釋掉了,原因是這裏是設置要被渲染的頁面的,而且ejs跟express也是融合性極好的模版引擎。有了這個模版引擎我們可以直接前後端放在一塊(不好解釋,可以理解爲Java中的servlet+jsp,而且ejs也跟jsp語法類似),連接數據庫後可以直接將返回的查詢信息通過模版引擎渲染到頁面中直接返回一個HTML頁面給用戶,對於小的項目這是很好的選擇。本來想寫一個例子,但是代碼在我無數次的修改中已經面目全非,也沒時間了。因爲我主要想做的是前後臺分離式的demo,下面要寫的是搭建後臺服務。
正題
開始之前先把數據庫建好,建立一個簡單的USER_INFO數據庫表
看一下項目修改之後的目錄結構
相比之前目錄結構多了conf文件夾,用於配置訪問數據庫的連接信息。多了dao文件夾用於訪問數據庫,多了util文件夾用於封裝一些工具類。缺失了views文件夾,既然要前後端分離當然不需要這些頁面了。
再看一下我想得到的最終效果
標準的後端數據返回格式。下面是實現流程,依然從app.js出發
app.js
主要改動有兩個,一個是引入自己編寫的json格式化工具,另一個是把404與異常錯誤改爲json格式返回(原先是返回錯誤頁面,既然不需要在這裏展示頁面就改爲返回錯誤信息描述),其餘的不動。
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// 引入路由
var indexRouter = require('./routes/index');
// 引入json格式化工具
var JsonFormat = require('./util/util').JsonFormat;
// 創建一個express實例
var app = express();
// 日誌中間件
app.use(logger('dev'));
// json解析中間件
app.use(express.json());
// 解析urlencoded請求體的中間件
app.use(express.urlencoded({ extended: false }));
// cookie解析中間件
app.use(cookieParser());
// public設置爲靜態文件夾
app.use(express.static(path.join(__dirname, 'public')));
// 設置路由
app.use('/', indexRouter);
// 捕捉404錯誤信息並給出404提示,可以自定義提示信息
app.use(function(req, res, next) {
JsonFormat.rtnError(res,'404 NOT FOUNT');
});
// 異常或錯誤信息處理
app.use(function(err, req, res, next) {
JsonFormat.rtnError(res,err.message);
});
module.exports = app;
util.js
既然說到了json格式化工具,就看一下里面怎麼寫的。裏面的res就是response對象,res.json()表示傳送JSON響應,三個方法分別響應操作成功、操作失敗和帶查詢數據的邏輯處理。其中0表示成功,1表示失敗
// 封裝返回消息
var rtnSuccess = function(res){
var obj = {"code":0,"msg":"操作成功"}
res.json(obj);
};
var rtnError = function(res,message){
var obj = {"code":1,"msg":message}
res.json(obj);
};
var rtnObj = function(res,data){
var obj = {"code":1,"msg":"操作成功","data":data}
res.json(obj);
};
var JsonFormat = {rtnError,rtnObj,rtnSuccess};
module.exports = {
JsonFormat
};
conf.js
劃重點,最主要的部分,在這裏設置數據庫的連接信息,既然要連接MySQL數據庫,自然需要MySQL的依賴包
npm install mysql
// conf/db.js
// MySQL數據庫連接配置
var mysql = require('mysql');
var dbconf = {
// 主機名
host: '127.0.0.1',
// 數據庫用戶名
user: 'root',
// 數據庫密碼
password: '123456',
// 鏈接的數據庫
database: 'blog',
// 鏈接的端口
port: 3306
}
// 創建數據庫連接池
var pool = mysql.createPool(dbconf);
// 將連接池提供給它用
module.exports = pool;
user.js
學習的過程中,大多數教程直接把sql放在數據庫連接後的請求中,個人不是很適應,一個是不方便管理一個是顯得太亂。受Java的影響我更喜歡把數據庫訪問的東西拿出來,類似於當作一個實體類,這麼說也不嚴謹,反正就是爲了方便管理。
//建立操作數據庫的sql
var user = {
insert:'INSERT INTO USER_INFO(ACCOUNT,PASSWORD,NAME,CREATE_DATE) VALUES(?,?,?,?)',
update:'UPDATE USER_INFO SET ACCOUNT=?, NAME=? WHERE ID=?',
delete: 'DELETE FROM USER_INFO WHERE ID=?',
queryById: 'SELECT * FROM USER_INFO WHERE ID=?',
queryAll: 'SELECT * FROM USER_INFO',
updatePsd:'UPDATE USER_INFO SET PASSWORD=? WHERE ID=?'
};
//將sql提供給它用
module.exports = user;
userDao.js
這是訪問數據庫的主要實現部分,類比Java這裏就是service層了
一共提供了6個方法,分別是新建用戶、修改用戶、刪除用戶、查詢單個用戶、查詢多個用戶、修改密碼。後續有別的功能需求可以不斷的添加,代碼過多,看一個就可以。我已經註釋的非常詳細了
// 實現與MySQL交互
// 獲取連接池
var pool = require('../../conf/db');
// 獲取json格式化工具類
var json = require('../../util/util').JsonFormat;
// 獲取編寫好的sql語句
var sql = require('./user');
// 暴露方法給它用
module.exports = {
//增加
add: function (req, res, next) {
// 獲取前臺頁面傳過來的參數
// ps:res和rsp有哪些屬性可以去菜鳥教程的express部分查看,或者我在文末附上
var param = req.query || req.search;
// 獲取數據庫的連接
pool.getConnection(function (err, connection) {
// 捕捉鏈接錯誤信息並返回
if (err) {
json.rtnError(res,"數據庫連接失敗:"+err.message);
return;
}
// 執行sql語句,這裏是插入一條信息query一共有三個參數,第一個是sql語句。第二個是參數數組,一定要和數據庫的
// 數據類型相對應,也要跟sql語句的佔位符相對應。第三個是回調函數,err代表訪問是否出錯,result代表本次訪問的回執信息
connection.query(sql.insert, [param.account, param.password, param.name, new Date()], function (err, result) {
// 插入失敗,返回錯誤信息
if(err){
json.rtnError(res,err.message);
return;
}
// 插入成功返回操作成功,result可以自己打印看一下,當只做查詢時返回查詢到的數據,其他諸如增刪改操作返回的是一個
// 對象,其中affectedRows就是受影響的行數,其餘的屬性可以自行打印看看
if (result.affectedRows>0) {
json.rtnSuccess(res);
}
// 釋放連接,訪問數據庫完成一定要及時幹掉此次連接請求,不然一會你的數據庫就崩死了
connection.release();
});
});
},
delete: function (req, res, next) {
// delete by Id
pool.getConnection(function(err, connection) {
if (err) {
json.rtnError(res,"數據庫連接失敗:"+err.message);
return;
}
var id = parseInt(req.query.id);
connection.query(sql.delete, id, function(err, result) {
if(err){
json.rtnError(res,err.message);
return;
}
if(result.affectedRows > 0) {
json.rtnSuccess(res);
}
connection.release();
});
});
},
queryById: function (req, res, next) {
var id = req.query.id;
if (id==null) {
json.rtnError(res,"刪除失敗!未獲取到要刪除的信息");
return;
}
pool.getConnection(function(err, connection) {
if (err) {
json.rtnError(res,"數據庫連接失敗:"+err.message);
return;
}
connection.query(sql.queryById, id, function(err, result) {
if(err){
json.rtnError(res,err.message);
return;
}
if(result){
json.rtnObj(res,result);
}
connection.release();
});
});
}
};
index.js
routes下的index依舊是配置路由信息,類比Java這就是controller層。
var express = require('express');
var router = express.Router();
// 引入數據庫的訪問方法,類比Java這裏就是注入Service
var userDao = require('../dao/user/userDao');
//配置路由
// post就是post請求
router.post('/addUser', function(req, res, next) {
// 調用userDao中的add方法
userDao.add(req, res, next);
});
// get請求,習慣上數據添加修改都用post,查詢刪除用get
router.get('/deleteUser', function(req, res, next) {
userDao.delete(req, res, next);
});
router.post('/updateUser', function(req, res, next) {
userDao.update(req, res, next);
});
router.post('/updatePsd', function(req, res, next) {
userDao.updatePsd(req, res, next);
});
router.get('/queryUserById', function(req, res, next) {
userDao.queryById(req, res, next);
});
router.get('/queryAllUser', function(req, res, next) {
userDao.queryAll(req, res, next);
});
module.exports = router;
以上就是全部的文件信息,下面用postman測試一下效果
插入信息
查看一下數據庫,插入成功
刪除信息
刪掉id爲3的張三的信息
可見張三已被幹掉
查詢信息
把我剛剛插入的信息查出來
好了,經過測試沒有問題,基本的服務器搭建完畢。
看似沒問題了實則問題一大堆,於我而言,,最受不了的就是代碼臃腫、重複。userDao.js裏面的代碼明顯還可以深入修改,封裝優化。路由也需要重新整理,後面路由多了擠成塊既不美觀也不易於維護,要重新整理。當然,有時間再說。。。
後面如果前後端分離式,前端可能要用一下vue了,依然從0開始,後端出身的我也要與時俱進~
附:express框架的request和response對象
request:
- req.app:當callback爲外部文件時,用req.app訪問express的實例
- req.baseUrl:獲取路由當前安裝的URL路徑
- req.body / req.cookies:獲得「請求主體」/ Cookies
- req.fresh / req.stale:判斷請求是否還「新鮮」
- req.hostname / req.ip:獲取主機名和IP地址
- req.originalUrl:獲取原始請求URL
- req.params:獲取路由的parameters
- req.path:獲取請求路徑
- req.protocol:獲取協議類型
- req.query:獲取URL的查詢參數串
- req.route:獲取當前匹配的路由
- req.subdomains:獲取子域名
- req.accepts():檢查可接受的請求的文檔類型
- req.acceptsCharsets / req.acceptsEncodings /
- req.acceptsLanguages:返回指定字符集的第一個可接受字符編碼
- req.get():獲取指定的HTTP請求頭
- req.is():判斷請求頭Content-Type的MIME類型
response:
- res.app:同req.app一樣
- res.append():追加指定HTTP頭
- res.set()在res.append()後將重置之前設置的頭
- res.cookie(name,value [,option]):設置Cookie
- opition: domain / expires / httpOnly / maxAge / path / secure /
signed - res.clearCookie():清除Cookie
- res.download():傳送指定路徑的文件
- res.get():返回指定的HTTP頭
- res.json():傳送JSON響應
- res.jsonp():傳送JSONP響應
- res.location():只設置響應的Location HTTP頭,不設置狀態碼或者close response
- res.redirect():設置響應的Location HTTP頭,並且設置狀態碼302
- res.render(view,[locals],callback):渲染一個view,同時向callback傳遞渲染後的字符串,如果在渲染過程中有錯誤發生next(err)將會被自動調用。callback將會被傳入一個可能發生的錯誤以及渲染後的頁面,這樣就不會自動輸出了。
- res.send():傳送HTTP響應
- res.sendFile(path [,options] [,fn]):傳送指定路徑的文件-會自動根據文件extension設定Content-Type
- res.set():設置HTTP頭,傳入object可以一次設置多個頭
- res.status():設置HTTP狀態碼
- res.type():設置Content-Type的MIME類型