我自己对原生 node.js 做后端服务语言的理解

url.jpg

以下是 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。