15. 通用API开发

15.1. 1.获取页面导航栏

接口路由地址为:http://localhost:3000/getNavMenu

展示型网站的首页一般都有导航标题栏,单击导航栏菜单,会跳转到一个新的页面(可能是分类或其他连接)。

导航栏一般存在多级菜单,本例为了方便,仅展示一级菜单。导航栏存放在数据库中的格式如下方的JSON字符串所示。

通过本接口获取的数据基本格式如下:

[{
    "name": "主页",
    "src": "http://loaclhost"
}, {
    "name": "文章",
    "src": "/article/list"
}]

注意:后端接口本身保存的就是JSON字符串,所以实现多级菜单是非常简单的,嵌套对象即可,前端同样也要实现多级菜单才可以正常显示。

通过redis-cli工具可实现数据操作,使用set命令手动添加数据,同时指定其键名为book:nav_menu。

导航栏路由建立在router文件夹的index.js文件中,为了避免大量的路由和逻辑导致程序编写混乱,应单独将逻辑处理部分提取出来,通过引入的方式进行应用。

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getNavMenu} = require('../controller/getData')
const util = require('../util/common')

// 获取footer显示内容
router.get('/getFooter', getFooter);
//获取菜单
router.get('/getNavMenu', getNavMenu);

访问导航栏路由后会调用getNavMenu()方法,接下来编写该方法。在项目文件中新建controller文件,在其中添加getData.js文件并在该文件中编写所有获取数据的代码逻辑,具体如下:

let redis = require("../util/redisDB")
const util = require('../util/common')
const crypto = require('crypto');

exports.getNavMenu = (req, res, next) => {
    let key = req.headers.fapp + ":nav_menu"
    //获取数据
    redis.get(key).then((data) => {
        console.log(data)
        res.json(util.getReturnData(0, '', data))
    })
}

Postman中进行测试.返回如下数据:

{
    "code": 0,
    "message": "",
    "data": [
        {
            "name": "主页",
            "src": "http://loaclhost"
        },
        {
            "name": "文章",
            "src": "/article/list"
        }
    ]
}

15.2. 2.获取底部详细内容

接口路由地址为:http://localhost:3000/getFooter

通过本接口获取的数据基本格式如下:

[
  {
    "name":  "版权所有",
    "src":  "http://loaclhost",
    "text":  "Stiller"

  },
   {
    "name":  "发送邮件",
    "src":  "mailto:1879324764@qq.com",
    "text":  "Gmail"
  }
]

首先在routers/index.js中编写相应的路由地址,修改后的index.js代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getFooter} = require('../controller/getData')
const util = require('../util/common')

// 获取footer显示内容
router.get('/getFooter', getFooter);

module.exports = router;

接着在controller/getData.js文件中添加getFooter对象,代码如下:

//获取Footer相关内容
exports.getFooter = (req, res, next) => {
    let key = req.headers.fapp + ":footer"
    //获取数据
    redis.get(key).then((data) => {
        console.log(data)
        res.json(util.getReturnData(0, '', data))
    })
}

最后通过Postman插件就可以获取相应的数据:

{
    "code": 0,
    "message": "",
    "data": [
        {
            "name": "版权所有",
            "src": "http://loaclhost",
            "text": "Stiller"
        },
        {
            "name": "发送邮件",
            "src": "mailto:1879324764@qq.com",
            "text": "Gmail"
        }
    ]
}

15.3. 3.获取友情链接

接口路由地址为: http://localhost:3000/getLinks

通过本接口获取的数据基本格式如下:

[{
    "name": "baidu",
    "src": "http://baidu.com"
}]

首先在routers/index.js中编写相应的路由地址,修改后的index.js代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getLinks} = require('../controller/getData')
const util = require('../util/common')


//获取友情链接
router.get('/getLinks', getLinks)
module.exports = router;

接着在controller/getData.js文件中添加getLinks对象,代码如下:

//获取友情链接
exports.getLinks = (req, res, next) => {
    let key = req.headers.fapp + ":links"
    //获取数据
    redis.get(key).then((data) => {
        console.log(data)
        res.json(util.getReturnData(0, '', data))
    })
}

最后通过Postman插件就可以获取相应的数据

{
    "code": 0,
    "message": "",
    "data": [
        {
            "name": "baidu",
            "src": "http://www.baidu.com"
        }
    ]
}

15.4. 4.获取首页轮播图

接口路由地址为: http://localhost:3000/getIndexPic

通过本接口获取的数据基本格式如下:

[{
    "title": "baidu",
    "src": "http://www.baidu.com",
    "img": "https://www.bejson.com/static/bejson/img/logo.png"
}]

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getIndexPic} = require('../controller/getData')
const util = require('../util/common')


//获取首页轮播图片
router.get('/getIndexPic', getIndexPic)
module.exports = router;

接着在controller/getData.js文件中添加getIndexPic对象,代码如下:

//获取首页轮播图片相关内容
exports.getIndexPic = (req, res, next) => {
    let key = req.headers.fapp + ":indexPic"
    //获取数据
    redis.get(key).then((data) => {
        console.log(data)
        res.json(util.getReturnData(0, '', data))
    })
}

最后通过Postman就可以获取相应的数据

{
    "code": 0,
    "message": "",
    "data": [
        {
            "title": "baidu",
            "src": "http://www.baidu.com",
            "img": "https://www.bejson.com/static/bejson/img/logo.png"
        }
    ]
}

15.5. 5.获取热点文章列表内容

接口路由地址为: http://localhost:3000/getHotArticle

通过本接口获取的数据基本格式如下:

[{
    "title": "文章暂未上线",
    "data": "",
    "id": 0,
    "view": 0
}, {
    "title": "测试文章3",
    "data": "",
    "id": 3,
    "view": "3"
}]

获取热点文章时返回文章的发布时间、题目和ID,最多5条数据。如果文章已经发布但未上线或已下线,则返回文章暂未上线的提示。

注意:新建文章时,会在Redis中新建名为book:a_view的有序列表,该列表按照时间戳保存文章key。

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getHotArticle} = require('../controller/getData')
const util = require('../util/common')

//获得热点文章列表
router.get('/getHotArticle', getHotArticle)
module.exports = router;

接着在controller/getData.js文件中添加getHotArticle对象,代码如下:

//获取热点文章
exports.getHotArticle = (req, res, next) => {
    let key = req.headers.fapp + ":a_view"
    //获得集合,只取得0,1,2,3,4五个
    redis.zrevrange(key, 0, 4).then(async (data) => {
        console.log(data)
        let result = data.map((item) => {
            //获得每一篇文章的题目和id以及日期
            return redis.get(item.member).then((data1) => {
                console.log(data1)
                if (data1 && data1.show != 0) {
                    return {
                        'title': data1.title,
                        'date': util.getLocalDate(data1.time),
                        'id': data1.a_id,
                        'view': item.score
                    }
                } else {
                    return {'title': '文章暂未上线', 'date': '', 'id': 0, 'view': 0}
                }
            })
        })
        let t_data = await Promise.all(result)
        res.json(util.getReturnData(0, '', t_data))
    })
}

需要注意的是,使用map循环时不可避免地会碰到异步问题。

笔者采用了await/async方式,使用Promise.all等待多个异步执行完毕后再获取汇总结果。如果直接在map后输出相关的值,则无法获取实际的值。

获取热点文章列表,实际上和根据时间获取列表的逻辑一样,不同之处在于对取值的限制,热点文章还需要将获取的数值返回给前端。通过Postman插件可以获取相应的数据

{
    "code": 0,
    "message": "",
    "data": [
        {
            "title": "文章暂未上线",
            "date": "",
            "id": 0,
            "view": 0
        },
        {
            "title": "文章暂未上线",
            "date": "",
            "id": 0,
            "view": 0
        }
    ]
}

15.6. 6.获取文章列表

接口路由地址为: http://localhost:3000/getNewArticle

可以先编写文章添加或修改的API,以提供数据源,使数据库中有了真实的数据后,再返回来编写相应数据获取的逻辑代码。新建文章时会在Redis中创建名为book:a_time的有序列表,该列表按照时间戳保存文章key。

通过本接口获得的数据结构如下,返回的是文章的时间、题目和ID,暂时不提供分页功能,只是一次性返回所有的文章列表。如果文章已经发布但未上线或已下线,则返回文章暂未上线的提示。

{
    "code": 0,
    "message": "",
    "data": [
        {
            "title": "测试文章3",
            "date": "2022-7-20 9:30:5",
            "id": 3,
            "show": 0
        },
        {
            "title": "测试文章2",
            "date": "2022-7-20 9:29:54",
            "id": 2,
            "show": 0
        },
        {
            "title": "测试文章1",
            "date": "2022-7-20 9:29:37",
            "id": 1,
            "show": 0
        }
    ]
}

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getNewArticle} = require('../controller/getData')
const util = require('../util/common')


//获取最新文章列表
router.get('/getNewArticle', getNewArticle)
module.exports = router;

接着在controller/getData.js文件中添加一个getNewArticle对象,代码如下:

//获取最新的文章列表
exports.getNewArticle = (req, res, next) => {
    let key = req.headers.fapp + ":a_time"
    let isAdmin = false
    //获取数据
    console.log(key)
    //获得集合
    //登录用户才判断
    if ('token' in req.headers) {
        //如果是管理员,则在加一次查找
        let pKey = req.headers.fapp + ":user:power:" + req.headers.token
        redis.get(pKey).then((power) => {
            //管理员权限
            if (power == 'admin') {
                redis.zrevrange(key, 0, -1).then(async (data) => {
                    let result = data.map((item) => {
                        //获得每一篇文章的题目和id以及日期
                        return redis.get(item.member).then((data1) => {
                            console.log(data1)
                            if (data1) {
                                return {'title': data1.title, 'date': util.getLocalDate(item.score), 'id': data1.a_id,'show':data1.show}
                            }
                        })
                    })
                    let t_data = await Promise.all(result)
                    console.log(t_data)
                    res.json(util.getReturnData(0, '', t_data))
                })
            }else{
                // res.json(util.getReturnData(1, '其他权限'))
                //    其他权限暂时依旧执行普通未登录效果
                redis.zrevrange(key, 0, -1).then(async (data) => {
                    console.log(data)
                    let result = data.map((item) => {
                        //获得每一篇文章的题目和id以及日期
                        return redis.get(item.member).then((data1) => {
                            if (data1 && data1.show != 0) {
                                return {'title': data1.title, 'date': util.getLocalDate(item.score), 'id': data1.a_id}
                            } else {
                                return {'title': '文章暂未上线', 'date': '', 'id': 0}
                            }
                        })
                    })
                    let t_data = await Promise.all(result)
                    res.json(util.getReturnData(0, '', t_data))
                })
            }
        })
    } else {
        redis.zrevrange(key, 0, -1).then(async (data) => {
            console.log(data)
            let result = data.map((item) => {
                //获得每一篇文章的题目和id以及日期
                return redis.get(item.member).then((data1) => {
                    if (data1 && data1.show != 0) {
                        return {'title': data1.title, 'date': util.getLocalDate(item.score), 'id': data1.a_id}
                    } else {
                        return {'title': '文章暂未上线', 'date': '', 'id': 0}
                    }
                })
            })
            let t_data = await Promise.all(result)
            res.json(util.getReturnData(0, '', t_data))
        })
    }
}

上述代码通过键值获取有序列表中的值,同时查找这些值的内容,找到相应的文章并且汇总到最终的返回结果中。通过Postman插件可以获取相应的数据

{
    "code": 0,
    "message": "",
    "data": [
        {
            "title": "测试文章3",
            "date": "2022-7-20 9:30:5",
            "id": 3,
            "show": 0
        },
        {
            "title": "测试文章2",
            "date": "2022-7-20 9:29:54",
            "id": 2,
            "show": 0
        },
        {
            "title": "测试文章1",
            "date": "2022-7-20 9:29:37",
            "id": 1,
            "show": 0
        }
    ]
}

15.7. 7.获取文章详情

实现的接口路由地址为:http://localhost:3000/getArticle/:id

可以先编写文章添加或修改的API,以提供数据源,使数据库中有了真实的数据后,再返回来编写相应数据获取的逻辑代码。

本接口返回的应当是文章详情,需要文章的show字段为1,提供一个a_id作为参数,其数据基本格式如下:

{
    "code": 0,
    "message": "success",
    "data": {
        "title": "测试文章1",
        "writer": "admin",
        "text": "测试文章1",
        "type": 1,
        "tag": [
            "js",
            "node"
        ],
        "show": 1,
        "time": 1658280576995,
        "a_id": 1,
        "view": "0",
        "like": "0"
    }
}

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getArticle} = require('../controller/getData')
const util = require('../util/common')


//获取文章的详情
router.get('/getArticle/:id', getArticle)
module.exports = router;

接着编写逻辑处理的controller/getData.js文件,在其中编写一个getArticle对象,除了根据ID获取文章以外,还涉及文章的分类(文章的分类仅是一个唯一的ID),以及有序队列中的点赞数、阅读量的获取,代码如下:

//根据id获取到文章的基本内容
exports.getArticle = (req, res, next) => {
    //获取参数
    let key = req.headers.fapp + ":article:" + req.params.id
    redis.get(key).then((data) => {
        // console.log(data)
        //判断是否显示文章内容
        if (data) {
            if (data.show == 1) {
                //获取文章分类详情
                redis.get(req.headers.fapp + ":a_type").then((type) => {
                    type.map((item) => {
                        if (item.uid == data.type) {
                            data.typename = item.name
                        }
                    })
                    //获取文章的阅读量
                    redis.zscore(req.headers.fapp + ":a_view", key).then((view) => {
                        console.log(view)
                        data.view = view
                        //获取文章的点赞量
                        redis.zscore(req.headers.fapp + ":a_like", key).then((like) => {
                            data.like = like
                            res.json(util.getReturnData(0, 'success', data))
                        })
                    })
                })

            } else {
                res.json(util.getReturnData(403, '该文章已经被删除或者是不存在'))
            }
        } else {
            res.json(util.getReturnData(404, '该文章已经被删除或者是不存在'))
        }
    })
}

注意,在功能设计中一些没有上线或不存在的文章是无法被访问的,需要提示相应的返回内容。通过Postman插件可以获取相应的数据。

获取文章详情

{
    "code": 0,
    "message": "success",
    "data": {
        "title": "测试文章1",
        "writer": "admin",
        "text": "测试文章1",
        "type": 1,
        "tag": [
            "js",
            "node"
        ],
        "show": 1,
        "time": 1658280576995,
        "a_id": 1,
        "view": "0",
        "like": "0"
    }
}

15.8. 8.获取文章评论

接口路由地址是: http://localhost:3000/getArticleTalk/:id

可以先编写文章添加或修改的API,以提供数据源,使数据库中有了真实的数据后,再返回来编写相应数据获取的逻辑代码。采用GET方式获取文章评论的接口,需要传递文章的唯一ID。

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {getArticleTalk} = require('../controller/getData')
const util = require('../util/common')

//获取文章评论
router.get('/getArticleTalk/:id',getArticleTalk)
module.exports = router;

接着编写处理逻辑的controller/getData.js文件,此处需要接收一个id参数,并通过ID查找评论数据,最终将该数据返回给前端。完整的代码如下:

//获得文章评论
exports.getArticleTalk = (req, res, next) => {
    let key = req.headers.fapp + ":article:" + req.params.id + ":talk"
    redis.get(key).then((data) => {
        res.json(util.getReturnData(0, 'success', data))
    })
}

传递一篇文章的ID后,返回内容如下

获取文章评论

{
    "code": 0,
    "message": "success",
    "data": [
        {
            "talk": "这是第一次评论",
            "time": 1658280939748,
            "username": "admin"
        }
    ]
}

15.9. 9.获取分类内容

接口路由地址是: http://localhost:3000/getArticles

可以先编写文章添加或修改的API,以提供数据源,使数据库中有了真实的数据后,再返回来编写相应数据获取的逻辑代码。

为了方便数据的传输,这里定义该接口为POST请求方式,传递的数据参数如下,传递tag时为小标签,传递type时为分类。

注意:对于不更改数据和仅用于查询的接口,建议使用GET方式定义接口。

本项目的所有接口发送数据时均采用JSON方式,在Postman中模拟时,选择Raw选项卡可以编辑JSON。

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var { getArticles} = require('../controller/getData')
const util = require('../util/common')

//获取小标签或者是文章分类的内容
router.post('/getArticles', getArticles)

module.exports = router;

接着在controller/getData.js文件中添加一个getArticles对象,该对象接收两个相关的参数:如果存在type,则以type为准;如果存在tag,则对tag字符串执行MD5算法。

使用如下代码引入crypto包,其中包含了MD5算法,该模块是Node.js自带的,无须安装就可以使用其加密算法。

const crypto = require('crypto');

获取分类的数据列表之后,还需要获取完整的数据,再次使用数组的map循环完成这项工作。完整的代码如下:

//getArticles
//根据小标签或者是分类获得所有的文章
exports.getArticles = (req, res, next) => {
    let key = req.headers.fapp
    // 筛选,如果是tag则使用md5
    if ('tag' in req.body) {
        let tKeyMd5 = crypto.createHash('md5').update(req.body.tag).digest("hex")
        key = key + ':tag:' + tKeyMd5
        console.log(key)
    } else if ('type' in req.body) {
        //如是type则直接使用id
        key = key + ':a_type:' + req.body.type
        console.log(key)
    } else {
        res.json(util.getReturnData(1, '数据参数错误'))
        return
    }
    redis.get(key).then(async (data) => {
        console.log(data)
        //    获得所有的数据
        let result = data.map((item) => {
            // 获得每一篇文章的题目和id以及日期
            return redis.get(item).then((data1) => {
                console.log(data1)
                if (data1 && data1.show != 0) {
                    return {'title': data1.title, 'date': util.getLocalDate(data1.time), 'id': data1.a_id}
                } else {
                    return {'title': '文章暂未上线', 'date': '', 'id': 0}
                }
            })
        })
        let t_data = await Promise.all(result)
        res.json(util.getReturnData(0, '', t_data))
    })
}

指定不同的参数就可以获取相关的文章和内容。如果输入不正确的参数,则返回错误信息

{
    "code": 0,
    "message": "",
    "data": [
        {
            "title": "测试文章1",
            "date": "2022-7-20 9:29:36",
            "id": 1
        },
        {
            "title": "测试文章2",
            "date": "2022-7-20 9:29:54",
            "id": 2
        },
        {
            "title": "文章暂未上线",
            "date": "",
            "id": 0
        }
    ]
}

15.10. 10.记录文章浏览量

接口路由地址是: http://localhost:3000/viewArticle/:id

可以先编写文章添加或修改的API,以提供数据源,使数据库中有了真实的数据后,再返回来编写相应数据获取的逻辑代码。

本小节的查看文章数据其实可以不使用接口,在每次获取文章详情时直接对文章的浏览量执行+1操作即可。笔者之所以提供独立的接口,是为了后续更多功能的实现。

首先在routers/index.js文件中编写相应的路由地址,修改后的index.js文件代码如下:

var express = require('express');
var router = express.Router();

//// 引入了逻辑处理的JavaScript文件(需要注意是否有其他路由使用到其他文件,均需要引入,本处已省略)
var {viewArticle} = require('../controller/getData')
const util = require('../util/common')

//文章被查看数自动+1API
router.get('/viewArticle/:id', viewArticle)
module.exports = router;

接着在controller/getData.js文件中添加viewArticle对象,这里使用Redis提供的zincrby()方法,自动对该值执行+1操作,代码如下:

//浏览量自动加一
exports.viewArticle = (req, res, next) => {
    let key = req.headers.fapp + ":article:" + req.params.id
    redis.zincrby(req.headers.fapp + ':a_view', key)
    res.json(util.getReturnData(0, 'success',))
}

这样,当一次请求成功后,再次获取该文章的详情,会看到文章的浏览量发生了变化。每次请求可以增加一次浏览量

"view": "3",

{
    "code": 0,
    "message": "success",
    "data": {
        "title": "测试文章1",
        "writer": "admin",
        "text": "测试文章1",
        "type": 1,
        "tag": [
            "js",
            "node"
        ],
        "show": 1,
        "time": 1658280576995,
        "a_id": 1,
        "view": "3",
        "like": "0"
    }
}