文章目錄
一、express 安裝
1、使用腳手架 express-generator
npm install express-generator -g #全局安裝
express blog-express #創建一個名爲blog-express的項目
2、使用webstorm
3、添加依賴
yarn add nodemon cross-env -D
4、修改package.json
"dev": "cross-env NODE_ENV=development nodemon ./bin/www",
"prod": "cross-env NODE_ENV=production nodemon ./bin/www"
二、app.js
var createError = require('http-errors');// 404 頁面
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');//解析cookie req.cookies
var logger = require('morgan'); // 記錄日誌
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev')); //日誌
app.use(express.json()); //處理 postData req.body
app.use(express.urlencoded({ extended: false })); //解析application/x-www-form-urlencoded 表單參數 req.body
app.use(cookieParser()); // 解析cookie req.cookies
app.use(express.static(path.join(__dirname, 'public')));
//處理路由
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
三、路由
1、加路由
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/list', function (req, res, next) {
res.json({errno: 0, data: [1, 2, 3]})
});
module.exports = router;
app.js
var blogRouter = require('./routes/my/blog');
app.use('/api/blog', blogRouter);
2、完整示例:
var express = require('express');
const {SuccessModel, ErrorModel} = require('../../model/resModel');
var router = express.Router();
const loginCheck = require('../../middleware/loginCheck')
/* GET users listing. */
router.get('/list', function (req, res, next) {
let author = req.query.author || '';
let keyword = req.query.keyword || '';
getList(author, keyword).then(list => {
res.json(new SuccessModel(list))
})
});
router.get('/detail', loginCheck, function (req, res, next) {
let id = req.query.id;
getDetail(id).then(data => {
res.json(new SuccessModel(data))
})
});
router.get('/new', loginCheck, function (req, res, next) {
newBlog(body).then(data => {
res.json(new SuccessModel(data))
})
});
router.get('/update', loginCheck, function (req, res, next) {
let id = req.query.id
updateBlog(id, body).then(result => {
if (result) {
res.json(new SuccessModel())
} else {
res.json(new ErrorModel('更新博客失敗'))
}
})
});
router.get('/del', loginCheck, function (req, res, next) {
let id = req.query.id;
let author = req.query.author || '';
delBlog(id, author).then(result => {
if (result) {
res.json(new SuccessModel())
} else {
res.json(new ErrorModel('刪除博客失敗'))
}
})
});
module.exports = router;
四、express中間件
本質上就是請求的攔截器
const express = require('express')
// 本次 http 請求的實例
const app = express()
app.use((req, res, next) => {
console.log('請求開始...', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 假設在處理 cookie
req.cookie = {
userId: 'abc123'
}
next()
})
app.use((req, res, next) => {
// 假設處理 post data
// 異步
setTimeout(() => {
req.body = {
a: 100,
b: 200
}
next()
})
})
app.use('/api', (req, res, next) => {
console.log('處理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
app.post('/api', (req, res, next) => {
console.log('post /api 路由')
next()
})
// 模擬登錄驗證
function loginCheck(req, res, next) {
setTimeout(() => {
console.log('模擬登陸失敗')
res.json({
errno: -1,
msg: '登錄失敗'
})
// console.log('模擬登陸成功')
// next()
})
}
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.post('/api/get-post-data', loginCheck, (req, res, next) => {
console.log('post /api/get-post-data')
res.json({
errno: 0,
data: req.body
})
})
app.use((req, res, next) => {
console.log('處理 404')
res.json({
errno: -1,
msg: '404 not fount'
})
})
app.listen(3000, () => {
console.log('server is running on port 3000')
})
五、express登錄
1、使用 express-session 和 connect-redis
yarn add express-session
app.js
let session = require('express-session'); // session
app.use(session({
secret: 'asdXXnkj_asdnsk_90789_sad',
cookie: {
path: '/',//默認
httpPnly: true,//默認
maxAge: 24 * 60 * 60 * 1000
}
})); // 處理session
測試:
router.get('/session-test', function (req, res, next) {
let session = req.session;
if (session.vievCount == null) {
session.vievCount = 0;
}
session.vievCount++;
res.json(session.vievCount)
});
2、req.session 保存登錄信息到redis
yarn add redis connect-redis
redis.js
const redis = require('redis')
const {REDIS_CONF} = require('../conf/db')
//創建客戶端
let redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);
redisClient.on('error', err => {
console.log(err)
});
module.exports = redisClient
app.js
let RedisStore = require('connect-redis')(session);
let redisClient = require('./db/redis');
let sessionStore = new RedisStore({
client: redisClient
});
app.use(session({
secret: 'asdXXnkj_asdnsk_90789_sad',
cookie: {
path: '/',//默認
httpPnly: true,//默認
maxAge: 24 * 60 * 60 * 1000
},
store: sessionStore
})); // 處理session
3、登錄校驗做成express中間件
loginCheck.js
const {SuccessModel, ErrorModel} = require('../model/resModel');
module.exports = (re, res, next) => {
if (require.session.username) {
next()
} else {
res.json(new ErrorModel("未登錄"))
}
}
路由裏面:
blog.js
const loginCheck = require('../middleware/loginCheck')
router.post('/new', loginCheck, (req, res, next) => {
req.body.author = req.session.username
const result = newBlog(req.body)
return result.then(data => {
res.json(
new SuccessModel(data)
)
})
})
五、日誌配置
1、access log 記錄 直接使用腳手架推薦的morgan
app.use(logger('dev')); //直接打印控制檯日誌
- 默認是輸出到控制檯,即:
app.use(logger('dev',{
stream: process.stdout
}));
-
dev:表示日誌的格式,可以改成combined
-
配置詳情:https://github.com/expressjs/morgan
-
修改日誌輸出文件:
最終配置:
let env = process.env.NODE_ENV;
if (env === 'developement') {
//開發環境或測試環境
app.use(logger('dev')); //日誌
} else {
//線上環境
let accessPath = path.join(__dirname, 'logs', 'access.log');
let writeStream = fs.createWriteStream(accessPath, {
flags: 'a'
})
app.use(logger('combined', {
stream: writeStream
})); //日誌
}
六、中間件原理
- app.use 用來註冊中間件,先收集起來
- 遇到http請求,根據path和method判斷觸發哪些
- 實現next機制,即上一個通過next觸發下一個
類似於express的代碼:
const http = require('http');
const slice = Array.prototype.slice;
class LikeExpress {
constructor() {
//存放中間件的列表
this.routes = {
all: [],
get: [],
post: []
}
}
register(path) {
let info = {};
if (typeof path === 'string') {
info.path = path;
//從第二個參數開始,轉換爲數組,存入stack
info.stack = slice.call(arguments, 1)//數組
} else {
info.path = '/'
//從第1個參數開始,轉換爲數組,存入stack
info.stack = slice.call(arguments, 0)//數組
}
return info;
}
use() {
let info = this.register.apply(this, arguments);
this.routes.all.push(info);
}
get() {
let info = this.register.apply(this, arguments);
this.routes.get.push(info);
}
post() {
let info = this.register.apply(this, arguments);
this.routes.post.push(info);
}
match(method, url) {
let stack = [];
if (url === '/favicon.ico') {
return stack;
}
//獲取routers
let currentRoutes = [];
currentRoutes = currentRoutes.concat(this.routes.all);
currentRoutes = currentRoutes.concat(this.routes[method]);
currentRoutes.forEach(routerInfo => {
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
if (url.indexOf(routerInfo.path) === 0) {
stack = stack.concat(routerInfo.stack);
}
})
return stack;
}
//核心的next機制
handle(req, res, stack) {
let next = () => {
//拿到第一個匹配的中間件
let middleWare = stack.shift();
if (middleWare) {
middleWare(req, res, next)
}
};
next();
}
callBack() {
return (req, res) => {
res.json = (data) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
};
let url = req.url;
let method = req.method.toLowerCase();
let resultList = this.match(method, url);
this.handle(req, res, resultList);
}
}
listen(...args) {
let server = http.createServer(this.callBack());
server.listen(...args)
}
}
//工廠函數
module.exports = () => {
return new LikeExpress();
}
測試:
let express = require('./simple')
//本次http請求的實例
let app = express();
app.use((req, res, next) => {
console.log('請求開始...', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 假設在處理 cookie
console.log('處理 cookie ...')
req.cookie = {
userId: 'abc123'
}
next()
})
app.use('/api', (req, res, next) => {
console.log('處理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
// 模擬登錄驗證
function loginCheck(req, res, next) {
setTimeout(() => {
console.log('模擬登陸成功')
next()
})
}
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.listen(8000, () => {
console.log('server is running on port 8000')
})