本网站的架构(三)API接口

 0 0条评论

后端使用的node+express框架实现典型的RESTful架构的接口。

api文档

可以在线阅读,点击这里,api文档是用apidoc来写的,也非常方便,只需要在每个路由新建一个js文件,在ji文件中按照规范写即可,具体不再阐述,大家可以网上搜索,我这里就贴一小段文档备注。

 /**
 * @api {delete} /article/:id 删除
 * @apiVersion 1.0.0
 * @apiName delete
 * @apiGroup 文章
 * @apiUse TokenResponse
 * @apiParamExample {string} Request-Example
 *      /article/5ebe5d0bbfa2995450469615
 * @apiSuccess {Json} data 删除的对象
 * @apiSuccessExample {Json} Success-Response:
 *    {
 *    "data": [
 *       {         
 *           "modifyDatetime": "2020-06-18T09:43:39.000Z",
 *           "modifyUser": "",
 *           "enable": true,
 *           "_id": "5fa25a128b54d433f7e0635d",
 *           "title": "这里是标题",
 *           "summary": "这里是摘要",     
 *           "catalog": "5f9fa44a1e6c034ef51eb4e3",
 *            .................
 *       }
 *  }
 *  @apiUse ErrorResponse
 */

token验证

所有接口,都需要进行token验证,这里我使用的逻辑是,每个用户都必须使用用户ID,和apikey(由系统生成),来访问一个专门生成token的接口,token只保持2个小时,2个小时内必须重新刷新,否则即过期。另外,token会保存在服务器redis中,过期时间同样也是2个小时,避免同一个id生成多个token,交给不同用户访问,即同一用户ID,只能生成一个token。

核心代码如下:


权限控制

用户和角色多对多的关系,分别对路由,前台url路径,每个前台的button做了控制。如下图:



api路由这块,使用了一个中间件,对所有路由进行判断,是否符合请求权限,核心代码如下:

const notokenPath = ['/user/login', '/common/token'] //不需要验证token的路由
/**
 * 登录守卫,验证token信息是否合法,以及权限检查
 */
module.exports = async (req, res, next) => {
  let model = req.url.split('/')[1]
  let { method } = req
  let ip = //获取用户IP
  //console.log(ip)
  let list = await new BlackList().findAll()
  if (
    // 判断IP是否在黑名单中
    list.find((n) => {
      return n.IP == ip
    })
  ) {
    next(IPBlack)
  }
  if (checkUrl(routerlist, model, method)) {
    //判断路由表里是否由此路由
    if (!notokenPath.includes(URL.parse(req.url).pathname)) {
      //#region token验证
      let reqtoken = req.headers.authorization
      if (reqtoken) {
        reqtoken = reqtoken.trim()
        try {
          let result = await JWT.verifyToken(reqtoken)
          let payload = result.payload //获得加密的载荷
          let { id, name, router } = payload
          if (!checkUrl(router, model, method)) {
            //检查该用户是否有该权限
            let objID = ''
            if (
              method.toLowerCase() === 'get' ||
              method.toLowerCase === 'delete'
            ) {
              objID = req.params.ID
            } else {
              objID = req.body._id
            }
            if (!(await checkSelf(req.url, payload.name, method, objID))) {
              // 是否有仅自己执行
              next(AuthFail)
              return
            }
          }
          client = redis.createRedis()
          client.get(`user:${name}`, (error, value) => {
            if (error) next(RedisReadFail)
            else {
              client.quit()
              if (value === null) {
                next(RedisNoToken) 
                // 内存中不存在此token,通常是被管理员强制踢下线,或者是该token非服务端生成的
              } else if (reqtoken != value.trim()) {
                // redis里保存的该用户的token与传递来的token不一致,
                //说明在其他地方已经再次刷洗token了,或者用户非法串改token
                next(TokenRefush)
              }
              req.username = name 
              //传递token里保存的当前用户名
              req.id = id 
              //传递token里保存的当前用户id
              next()
            }
          })
        } catch (e) {
          if (e.message == 'jwt expired') {
            next(TokenExpire)
          } else if (e.message == 'invalid token') {
            next(TokenFail)
          } else {
            TokenUnkownFail.syserr = e
            next(TokenUnkownFail)
          }
        }
      } else {
        next(NoToken)
      }
      //#endregion
    } else {
      next()
    }
  } else next(URLFaiL)
}

//验证路由是否在路由表
function checkUrl(list, model, method) {
  let result = list.find((n) => {
    // model是否在路由表中
    return n.model == model
  })
  if (result) {
    //if (result.onlyHost) {
      // 如果需要验证是否是主机访问,已弃用2021.6.1
      //if (!hostIP.includes(ip)) return false
    //}
    return result.methods.includes(method.toLowerCase())
    //是否有该方法
  } else {
    return false
  }
}
async function checkSelf(url, username, method, objID) {
  let result = selfList.find((n) => {
    // model是否在路由表中
    return n.router.test(url)
  })
  if (result) {
    if (result.method === method.toLowerCase()) {
      let { model, key } = result
      let excute = require(`../model/${model}/excute`)
      let data = await new excute().findOneByID(objID).lean()
      return data[key] === username
    }
    return false
  } else {
    return false
  }
}

下一篇:本网站的架构(四)vue实现后台管理

本文作者:双黑

版权声明:本站文章欢迎链接分享,禁止全文转载!

游客