代码地址:https://github.com/changeclass/Node_Blog.git

# Server 端 Cookie 的操作

存储在浏览器的一段字符串(最大 5kb)

server 操作 cookie 只需要设置响应头即可。例如:

res.setHeader('Set-Cookie', `username=lisi;realname=lisi`)

获取 Cookie 也很简单。通过请求的 header 的 cookie 即可获取

req.headers.cookie

其返回内容是一个字符串。

在此项目中,前端传递的 Cookie 可能较多,为了便于后边的逻辑处理,将解析 Cookie 的逻辑放在处理路由之前,并且加入到 request 对象中。

// 解析 cookie
req.cookie = {}
const cookieStr = req.headers.cookie || ''
cookieStr.split(';').forEach(item=>{
    if(!item){
        return
    }
    const arr = item.split('=')
    const key = arr[0].trim()
    req.cookie[key]=arr[1].trim()
})
console.log('cookie: ',req.cookie)

加入 Cookie 时默认会在分割处加一个空格,但是

image-20201006165847667

接下来只需要在登陆成功时为其创建一个只有 server 端才可以修改的 cookie。

if (method === 'POST' && req.path === '/api/user/login') {
    const {username, password} = req.body
    // const {username, password} = req.query
    const result = login(username, password)
    return result.then(data => {
        if (data.username) {
            // 操作 Cookie
            res.setHeader('Set-Cookie', `username=${data.username};path=/;httpOnly;expires=${getCookieExpries()}`)
            return new SuccessModel("登陆成功")
        } else {
            return new ErrorModel("登陆失败")
        }
    })
}
// 获取 cookie 的过期时间
const getCookieExpries = () => {
    const d = new Date()
    d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
    console.log("d.toGMTString(): ",d.toGMTString())
    return d.toGMTString()
}
  • httpOnly

    表示只有服务器能修改 Cookie

  • path

    表示生效的路径, / 表示所有路径都生效

  • expires

    表示过期时间。如果不设置则永不过期

# Session

使用 Cookie 存储会暴露一些信息,因为 Cookie 是明文存储。

鉴于 Cookie 存在的问题,因此改用 Session 来解决。

在 app.js 中进行解析 Cookie,并在处理路由前进行判断是否需要添加 cookie。

// 获取 cookie 的过期时间
const getCookieExpries = () => {
    const d = new Date()
    d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
    console.log("d.toGMTString(): ", d.toGMTString())
    return d.toGMTString()
}
// 初始化 session 数据
const SESSION_DATA = {}
// 解析 sesson
let needSetCookie = false // 判断是否需要设置 Cookie 的标志
let userId = req.cookie.userid // Cookie 中存放的值
// 如果存在
if (userId) {
    // 如果 SESSION_DATA 中不存在这个值,那么需要建立 Session
    if (!SESSION_DATA[userId]) {
        SESSION_DATA[userId] = {}
    }
} else {
    // 如果不存在 Cookie
    needSetCookie = true
    userId = `${Date.now()}_${Math.random()}`
    SESSION_DATA[userId] = {}
}
req.session = SESSION_DATA[userId]

接下来在路由处理之前为其建立(如果需要)Cookie。

if (blogResult) {
    blogResult.then(blogData => {
        if (needSetCookie) {
            res.setHeader('Set-Cookie', `userid=${userId};path=/;httpOnly;expires=${getCookieExpries()}`)
        }
        res.end(JSON.stringify(blogData))
    })
    return
}
if (userResult) {
    userResult.then(userData => {
        if (needSetCookie) {
            res.setHeader('Set-Cookie', `userid=${userId};path=/;httpOnly;expires=${getCookieExpries()}`)
        }
        res.end(JSON.stringify(userData))
    })
    return
}

在 user.js 路由处理规则中,也不需要在建立 Cookie,而是将请求中的 session 字段设置值。

if (data.username) {
    // 操作 Session
    req.session.username = data.username
    req.session.realName = data.realName
    return new SuccessModel("登陆成功")
}

# Redis 存储 session 的小 Demo

const redis = require('redis')
// 创建客户端 第一个参数表示端口 第二个参数表示地址
const redisClient = redis.createClient(6379,'127.0.0.1')
// 如果发生错误
redisClient.on('err',err=>{
    console.error(err)
})
// 测试 建立一个键为 myname 值为 zhangsan 的记录 最后一个参数为回调函数,redis.print 表示打印执行结果
redisClient.set('myname','zhangsan',redis.print)
// 获取键为 myname 的值
redisClient.get('myname',(err,val)=>{
    if(err){
        console.error(err)
        return
    }
    console.log('val: ',val)
    // 退出链接
    redisClient.quit()
})

image-20201007163007116

# 封装成工具函数

  1. 修改配置文件的 db.js ,新增一个 redis 数据库的配置项。

    let REDIS_CONF
    if (env === 'dev') {
        // redis
        REDIS_CONF = {
            host:'127.0.0.1',
            port:6379
        }
    }
    if (env === 'production'){
        // redis
        REDIS_CONF = {
            host:'127.0.0.1',
            port:6379
        }
    }
    module.exports = {MYSQL_CONF,REDIS_CONF}
  2. 创建数据库操作的文件,在 db 目录下新建一个名为 redis.js 的文件用于操作 redis 相关的逻辑。

    const redis = require('redis')
    const {REDIS_CONF} = require('../conf/db')
    // 创建客户端 第一个参数表示端口 第二个参数表示地址
    const redisClient = redis.createClient(REDIS_CONF.port,REDIS_CONF.host)
    // 如果发生错误
    redisClient.on('err',err=>{
        console.error(err)
    })
    function set(key,val){
        if(typeof val === 'object'){
            val = JSON.stringify(val)
        }
        redisClient.set(key,val,redis.print)
    }
    function get(key){
        return new Promise(((resolve, reject) => {
            redisClient.get(key, (err, val) => {
                if (err) {
                    reject(err)
                    return
                }
                // 如果没有此值
                if(val===null){
                    resolve(null)
                    return
                }
                // 如果是 JSON 格式对象那么转为 JSON 在返回,否则直接返回
                try {
                    resolve(JSON.parse(val))
                }catch (ex){
                    resolve(val)
                }
            })
        }))
    }
    module.exports = {
        set,get
    }

# 完成 server 端的登陆代码

由于关于博客相关的操作都需要验证是否已经登陆。因此登陆验证的逻辑应设计为类似中间件的逻辑。但在本项目中,user 相关的操作只有一个登陆。因此将登陆验证放置在 blog 路由里即可。

在 blog.js 路由中接入如下代码。

// 统一登陆验证的函数
const loginCheck = (req) => {
    if (!req.session.username) {
        return Promise.resolve(new ErrorModel('未登陆'))
    }
}

并且修改下方的逻辑,将假数据改成 req.session.username ,并在需要验证登陆的路由中调用上面定义的函数。

const loginCheckResult = loginCheck(req)
if(loginCheckResult){
    // 未登录
    return loginCheck
}

# Nginx 反向代理

由于 Cookie 存在跨域问题,因此静态页面与服务器不在同一端口,因此需要使用 Nginx 进行统一端口。

image-20201007172057449

配置 Nginx 的配置文件。

server {
    listen        8080;
    server_name  location;
    location / {
        proxy_pass http://localhost:8001;
    }
    location /api/ {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
    }
}

接下来通过访问 http://127.0.0.1:8080/ 即可看到效果

image-20201007193741521

更新于

请我喝[茶]~( ̄▽ ̄)~*

Dreamy.TZK 微信支付

微信支付

Dreamy.TZK 支付宝

支付宝