以下是 app.js 文件
const querystring = require('querystring') //解析req.url;参数用的
const {set, get} = require('./src/db/redis.js')//redis存取
const {access} = require('./src/utils/log.js')//日志操作
//路由部分:
const handleBlogRouter = require('./src/router/blog.js')//路由:博客
const handleUserRouter = require('./src/router/user.js')//路由:用户
//Session 数据库
// const SESSION_DATABASE = {}; //如果只一个node进程且不用考虑内存最大限制, SESSION_DATABASE可以取代redis
//工具方法: 获取cookie过期时间
const getCookieExpires = (hours = 24) => {
const d = new Date();
d.setTime(d.getTime() + ((hours + 8) * 60 * 60 * 1000)) //中国时间 = 英国格林威治时间 + 8小时
return d.toGMTString();
}
//工具方法: 获取post data并返回promise
const getPostData = (req) => {
return new Promise((resolve, reject) => {
if (req.method !== 'POST') {
resolve({})
return
}
if (req.headers['content-type'] !== 'application/json') {
resolve({})
return
}
let postData = ''
req.on('end', () => {
if (!postData) {
resolve({})
return
}
resolve(JSON.parse(postData))
})
req.on('data', chunk => {
postData += chunk.toString()
});
})
}
/**
* @author wgm
* @date 2019/12/20 17:48
* @param {object} req request
* @param {object} res response
* @description 每次api请求过来:
* 1. 存日志 要知道什么是流
* 2. 设置response头信息: res.setHeader('Content-type', 'application/json')
* 3. 解析处理传过来的request信息
* 3-1. 解析处理query: req.url 存入 req.path req.query
* 3-2. 解析处理cookie: req.headers.cookie<String> 存入 req.cookie<{}>
* 3-3. 解析处理session: req.cookie.userid<String> = req.sessionId<String>
* 如果没有 req.cookie.userid 则生成一个新的userid并存入redis.set(userid,{}),并设置response
* 通过 sessionId<String> 从redis里拿到 sessionData<{}> 并保存在 req.session<{}> 里. 之后判断登录权限的地方用req.session.username<String>来判断
* 下面注释中有详细的如何掌握session的总结心得!!!
* 3-4. 解析处理POST请求 存入 req.body<{}>
* 4. app->router: 将app实参 req, res 先传给blog router(没命中了则继续向下执行代码user router).
* 5. router->controller: 如果命中了路由(有些api请求需要进一步判断是否有权限,如:已登录用户.未登录直接打回给客户端,这是一次没有顶到子宫的插入便return了出来),传给controller层.
* 6. controller->sql: controller层与数据库打交道执行sql语句(执行前要参数校验封装,防止sql注入 xss攻击).
* 7. 将在controller中执行的sql结果返回给router, router返回给app. app通过res.end()将结果返回给客户端.即sqlResult->controller->router->app
* 8. 如果api没有命中任何路由,则通过res返回404结果
* 9. nodejs: www -> app -> router -> controller -> database 用pm2做nodejs进程管理 用nginx做负载均衡、静态服务、反向代理(client -> nginx -> html or nodejs)
*/
const serverHandle = (req, res) => {
//记录access.log
access(`${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}`)
//开始部分
res.setHeader('Content-type', 'application/json')
//解析处理query
const url = req.url;
req.path = url.split('?')[0]
req.query = querystring.parse(url.split('?')[1])
//解析处理cookie
req.cookie = {};
const cookieStr = req.headers.cookie || '';
cookieStr.split(';').forEach(item => {
if (!item) return;
const arr = item.split('=');
const key = arr[0].trim();
const val = arr[1].trim();
req.cookie[key] = val;
})
// 这里是req.cookie的格式, 里面有很多键值对儿, 其中有一个就是userid
// req.cookie = {
// "key1" : "val1",
// "key2" : "val2",
// "key3" : "val3",
// "userid": "1561657468669_0.8963485294707485"
// }
console.log('req.cookie print ', req.cookie);
//解析处理session;原理目的-> req.session=SESSION_DATABASE[userid]={username:'wgm', realname:'王鹳厶'} || {}
// let userId = req.cookie.userid || '';
// if (!userId) {
// userId = `${Date.now()}_${Math.random()}`;
// res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
// }
// if (!SESSION_DATABASE[userId]) SESSION_DATABASE[userId] = {};
// req.session = SESSION_DATABASE[userId];
//解析处理session(使用redis)
let userId = req.cookie.userid || '';
if (!userId) {
userId = `${Date.now()}_${Math.random()}`; //生成userid
set(userId, {}) //存入redis
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`) //设置response
}
//用userId获取session
req.sessionId = userId;//将userId挂到req对象上, 实现共享, 到时候别的地方好用
get(userId)
.then(sessionData => {
if (sessionData === null) {
set(userId, {})
req.session = {}
}
else {
req.session = sessionData; //{"username": "wgm", "realname": "王鹳厶"}
}
/*
总结一下: 除app.js也要参看一下router\user.js,那里是给空session赋值的地址,即{} -> {"username": "wgm", "realname": "王鹳厶"}
前端cookie里只能看到"userid=1561657468669_0.8963485294707485"
以下是req对象中存储的有关登录的信息
req.headers.cookie = "userid=1561657468669_0.8963485294707485; BD_UPN=12314753"
req.cookie = {"userid": "1561657468669_0.8963485294707485", ... }
req.sessionId = const userId = req.cookie.userid = "1561657468669_0.8963485294707485"
req.session = {"username": "wgm", "realname": "王鹳厶"} //登录成功后会存到req.session
这里的redisDatabase就是进程Redis数据库, 这样的写法有助于解理Redis原理
const redisDatabase = {
"1561657468669_0.8963485294707485": {"username": "wgm", "realname": "王鹳厶"},
"1561657668669_0.6666666666666666": {"username": "wdy", "realname": "王鹳人"},
"1563263932485_0.7777777788888888": {},
...
req.cookie.userid: req.session
}
即: const redisDatabase = {req.cookie.userid: req.session}
判断的时候, 就看有没有username键, 如果没有说明没有登录
实践时,在cookie里传递的只是userid,这样安全,
后端拿到userid再去redis里找userid对应的session,
而且userid是经过加密的,
通过设置httpOnly为true, userid也不可以在客户被修改
userid是在服务端生成的,每一次api请求进来,nodejs都会判断cookie中是否有userid,以及通过userid查看redis是否有对应的session
所做的一切都是为了安全,性能,稳定...
*/
//处理post data 返回Promise
return getPostData(req)
})
//处理post data
.then(postData => {
req.body = postData;
//引入--博客
/*const blogData = handleBlogRouter(req, res)
if (blogData) {
res.end(
JSON.stringify(blogData)
)
return
}*/
const blogResult = handleBlogRouter(req, res);
if (blogResult) {
blogResult.then(blogData => {
res.end(
JSON.stringify(blogData)
);
});
return
}
//引入--用户
/*const userData = handleUserRouter(req, res);
if (userData) {
res.end(
JSON.stringify(userData)
)
return
}*/
const userResult = handleUserRouter(req, res);
if (userResult) {
userResult.then(userData => {
res.end(
JSON.stringify(userData)
)
})
return
}
//后续部分
res.writeHead(404, {"Content-type": "text/plain"})
res.write('404 Not Found!! \n')
res.end()
})
}
module.exports = serverHandle
//process.env.NODE_ENV