Node学习 项目接口开发

项目接口架构

完整的项目接口包括:

  • 面向用户的业务接口;
  • 面向企业或者内部的后台管理接口;

项目搭建

  • 功能一:目录结构的划分:

    • 按照功能模块划分;
    • 按照业务模块划分;
  • 功能二:应用配置信息写到环境变量

    • 编写.env文件
    • 通过dotenv加载配置的变量
  • 功能三:创建和启动服务器

    • 基于koa创建app;
    • 启动服务器;

注册接口

配置postman

  • 建立coderhub文件目录
  • 配置环境变量 coder_dev coder_test 使用baseURL即可在不同环境下对接口进行调试

通过用户接口打通目录结构 划分为三个层级

  • 引入koa-router router目录下的user.router.js专门 是负责user这一路由
  • 在user.controller.js 专门负责处理router 的逻辑
  • user.service.js负责处理数据库插入查询等

数据写入数据库

  • 数据库创建user 表
  • 链接数据库
    • 使用mysql2 链接
    • 需要对用户输入的用户名密码进行判断 使用verifyUser中间件进行判断
  • 封装错误处理函数
    • app.on(‘error’,errorhandler)
    • 在errorhandler中通过判断常量设置返回状态码

加密密码

封装加密函数

1
2
3
4
5
6
7
8
const crypto = require('crypto')

const md5password = (password) => {
const md5 = crypto.createHash('md5')
const result = md5.update(password).digest('hex')
return result
}
module.exports = md5password

登录接口

中间件验证用户输入的用户名密码

动态路由

1
2
3
4
5
6
7
8
const useRoutes = function(){
fs.readdirSync(__dirname).forEach(file=>{
if(file === 'index.js') return
const router = require(`./${file}`)
this.use(router.routes())
this.use(router.allowedMethods())
})
}

登录凭证

为什么需要登录凭证

http是一个无状态的协议 http每一次请求对于服务器都是一次单独的请求 服务器无法知道我们上一步做了什么 所以需要一个凭证证明登陆过

登陆成功返回凭证:

  • cookie+session;
  • Token令牌

类型是一个小型文本文件

浏览器会在特定的情况下携带上cookie来发送请求,我们可以通过cookie来获取一些信息;

Cookie总是保存在客户端中,按在客户端中的存储位置,Cookie可以分为内存Cookie和硬盘Cookie。

  • 内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;

  • 硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;

判断cookie是内存还是硬盘cookie

  • 没有设置过期时间,默认情况下cookie是内存cookie,在关闭浏览器时会自动删除
  • 有设置过期时间,并且过期时间不为0或者负数的cookie,是硬盘cookie,需要手动或者到期时,才会删除;

cookie常见的属性

默认情况下的cookie是内存cookie,也称之为会话cookie,也就是在浏览器关闭时会自动被删除;

我们可以通过设置expires或者max-age来设置过期的时间;

  • expires:设置的是Date.toUTCString(),设置格式是;expires=date-in-GMTString-format;

  • max-age:设置过期的秒钟,;max-age=max-age-in-seconds (例如一年为60 * 60 * 24 * 365);

cookie的作用域

Domain:指定哪些主机可以接受cookie

  • 如果不指定,那么默认是 origin,不包括子域名。

  • 如果指定Domain,则包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。

Path:指定主机下哪些路径可以接受cookie

  • 例如,设置 Path=/docs,则以下地址都会匹配:
    • /docs
    • /docs/Web/
    • /docs/Web/HTTP

客户端设计cookie

  • js直接获取设置cookie
  • 这个cookie会在会话关闭时被删掉
    • document.cookie=’name=ch’
  • 设置cookie 同时设置时间 会在时间结束时被删掉
    • document.cookie = ‘age=18;max-age=5’

服务器端设置cookie

koa中支持直接操作cookie

1
2
3
4
5
6
7
8
9
10
11
12
testRouter.get('/test',(ctx,next)=>{
ctx.cookies.set("name","hyp",{
//maxage对应的毫秒
maxAge:5*1000
})
ctx.body="haoaho"
})

testRouter.get('/demo',(ctx,next)=>{
const result = ctx.cookies.get('name')
ctx.body = `你的cookie是${result}`
})
session

session 是基于cookie的

在koa中 可以借助于 koa-session 来实现session认证

1
2
3
4
5
6
7
8
9
const session = KoaSession({
key: 'sessionid',
maxAge: 5*1000,
signed: true, //使用加密签名认证,防止数据被篡改
httpOnly: true, //不允许通过js获取cookie
rolling: true, //每次响应时 刷新session的有效期
},app)
app.keys = ["aa"]
app.use(session)
1
2
3
4
5
6
7
8
9
10
11
testRouter.get('/test',(ctx,next)=>{
const id = 100
const name = "code"
ctx.session.user = {id,name}
ctx.body="haoahoo"
})

testRouter.get('/demo',(ctx,next)=>{
console.log(ctx.session.user);
ctx.body = `sess`
})

cookie和session的区别

  1. session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,客户端能够知道其中的信息。
  2. session中保存的是对象,cookie中保存的是字符串
  3. session需要借助cookie才能正常工作。如果客户端完全禁止cookie,session将失效。

session针对用户 cookie针对主机

Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。

session需要存储在服务端(占用资源),而token不需要,session把钥匙给客户端,token把钥匙和锁都给客户端,两者都存在生存时间。

token

cookie和session的方式有很多的缺点:

  • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量(事实上某些请求是不需要的);
  • Cookie是明文传递的,所以存在安全性的问题;
  • Cookie的大小限制是4KB,对于复杂的需求来说是不够的;
  • 对于浏览器外的其他客户端(比如iOS、Android),必须手动的设置cookie和session;
  • 对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session
    • 服务器集群:高并发请求对于一个服务器是承受不了的 所以需要多个服务器形成一个服务器集群,用户通过如Nginx反向代理 Nginx判断使用哪一个服务器 来减轻服务器压力 所以需要其他系统也能正确解析session

在目前的前后端开发过程中 token来进行身份验证是最多的的情况

  • token 令牌
  • 验证用户账号和密码正确后 给用户颁发令牌
  • 这个令牌可以作为后续用户访问一些接口或者资源的凭证
  • 可以根据这个凭证判断用户是否有相关权限

token 的使用步骤分为两个:

  • 生成token 登录的时候颁发token
  • 验证token 访问某些资源或者接口 验证token

JWT实现Token机制 [json web token]

JWT生成token由三部分组成

header

  • alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行加密和解密;
  • typ:JWT,固定值,通常都写成JWT即可;
  • 会通过base64Url算法进行编码;

payload

  • 携带的数据,比如我们可以将用户的id和name放到payload中;
  • 默认也会携带iat(issued at),令牌的签发时间;
  • 我们也可以设置过期时间:exp(expiration time);
  • 会通过base64Url算法进行编码

signature

  • 设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法
  • HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
  • 但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token,也可以解密token;

最后生成的token 为 header.payload.signature

在真实开发中使用token:

___借助于jsonwebtoken库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//登录
testRouter.post('/test',(ctx,next)=>{
const user = {id:123,name:"hyp"}
const token = jwt.sign(user,SECRTE_KEY,{
expiresIn:100
})
ctx.body = token
})
//验证
testRouter.get('/demo',(ctx,next)=>{
try{
const authorization = ctx.headers.authorization
const token = authorization.replace("Bearer ","")
const result = jwt.verify(token,SECRTE_KEY)
ctx.body = result
}
catch (error){
console.log(error);
ctx.body = error.message
}
})

非对称加密

HS256加密算法一单密钥暴露就是非常危险的事情:

  • 比如在分布式系统中,每一个子系统都需要获取到密钥;
  • 那么拿到这个密钥后这个子系统既可以发布令牌,也可以验证令牌;
  • 但是对于一些资源服务器来说,它们只需要有验证令牌的能力就可以了;

所以这种情况下我们就可以使用非对称加密 RS256

  • 私钥(private key):用来发布令牌
  • 公钥(public key):用来验证令牌

可以使用 openssl 生成公钥私钥 (mac自带)

  • 指令:
    • openssl
    • genrsa -out private.key 1024
    • rsa -in private.key -pubout -out public.key
  • 使用:
    • const PRIVATE_KEY = fs.readFileSync('./keys/private.key')
    • const PUBLIC_KEY = fs.readFileSync('./keys/public.key')
    • 需要指定算法 如:algorithm:"RS256"

在项目中的任何一个地方的相对路径,都是相对于process.cwd()

process.cwd()对应的是启动位置的文件夹

在项目中使用token

  • login中
1
2
3
4
5
6
7
8
9
10
11
12
13
async login(ctx,next){
const {id,name} = ctx.user
const token = jwt.sign({id,name},PRIVATE_KEY,{
expiresIn:60*60*24,
algorithm:'RS256'
})
ctx.body = {
id,
name,
token
}
await next()
}
  • 测试 test中的verifyAuth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const verifyAuth = async(ctx,next)=>{
console.log("验证授权的middleware");
//获取token
const authorization = ctx.headers.authorization
const token = authorization.replace('Bearer ','')
//验证token
try {
const result = jwt.verify(token,PUBLIC_KEY,{
algorithms:['RS256']
})
ctx.user = result
await next()
} catch (err) {
const error = new Error(errorTypes.UNAUTHORIZATION)
ctx.app.emit('error',error,ctx)
}
}

发布动态接口

  • 权限验证
  • 数据库建表
  • 将数据插入到数据库

获取动态接口

  • 定义查询单个内容的接口

    • 根据momentId查询接口内容;
    • 定义查询多条内容的接口
  • 查询所有moment接口内容(根据offset和limit决定查询数量)

修改动态接口

  • 修改的条件
    • 用户必须要登录(授权)
    • 验证登录的用户是否有具备权限修改内容
      • 用户1登陆成功不可以修改用户2的内容
  • 逻辑
    • 首先定义接口
    • verifyPermission 判断是否具有修改权限
    • update

删除动态接口

根据权限判断,执行sql语句即可

发表和修改评论内容接口

  • 创建表comment

  • 定义评论发布内容的接口

    • 定义路由接口
    • 验证用户登录
    • Controller和Service中处理内容
  • 回复评论接口

    • 符合restful风格 /comment/:commentId/reply
  • 修改评论

    • 封装验证权限方法
      • 闭包 和 restful风格两种思路
  • 删除评论

在获取动态时加入评论个数字段

  • 修改sql语句
    • (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id ) commentCount

评论的展示:

  • 在展示一条动态时,也将评论的列表进行展示
    • 获取评论列表(两种思路)
      • 动态的接口和评论的接口是分开的
      • 直接请求动态的接口的时候 就会一起携带评论的列表