后端使用的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
}
}