我自己对原生 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350