持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
前言
大家好,我是 Taylor,一个有趣且乐于分享的人,目前专注前端、Node.js技术栈分享,如果你对 前端、Node.js 学习感兴趣的话(后续有计划也可以),可以关注我掘金.
本文适宜人群
node.js新手
想学习node.js成为全栈的前端同学
难易程度
简单
本文将带你
- 了解koajs
- 然后初始化一个koa项目,
写冬天的第一个接口 - 学习基于koa的后端常见接口实现
完整代码已经开源,github链接 github.com/zhangbowy/n…
背景&为什么学它??
1、可以让后端不再忽悠我们。
了解和学习了一般后端接口的实现逻辑, 遇到问题, 后端如果可以改,这时候昵你又不懂, 他忽悠你说他改不了...,让你处理,看完这篇, 直接给我怼回去, 让他改。
3、做其他前端做不了的事情 👍🏻🧑🏿💻🌈🌈🌈
写脚本、写工具,写BFF层,写接口, 成为全栈。
带着问题来看(我列了几个问题)
1、作为前端的你, 统一格式的JSON你知道后端同学是怎么做的吗?
2、一直听有人说node是写中间件的,那你知道中间件在node中是怎样的存在吗?
前端同学, 是不是有点兴趣了??, 看完这些问题都会得到解答。
koajs
阅读本节默认您对node.js有基本的了解。
node入门教程参考 dev.nodejs.cn/learn
简介
koa是node一个http框架, 源码十分简单,只有四个文件, 内核小巧, 是我们入门学习node的不二之选。
今天我们后端常见接口就是基于koa写的, 关于koa大家可以看我之前写的这篇 《koa源码浅析》www.yuque.com/zhangheihei…
洋葱圈模型
这张图就是koa的核心
初始化您的koa项目
阅读完上小节内容,相信您对koajs有了基本的了解,接下来我们使用工具来快速初始化我们的项目。
安装命令行工具koa-generator
npm install -g koa-generator
生成一个koa项目
koa learn-koa
进到了你目录看到如下就算成功了
安装依赖
cd learn-koa && npm install
启动
npm run dev
然后打开本地3000端口, 看到如下就算是成功运行了 冬天第一个接口
常见后端接口(基于koa2)
阅读本节, 需要您了解koajs的基本使用, 请阅读上一节。
阅读本节, 默认您对koajs和 koa-router已经有了一定的基础。
1、服务端模板渲染
模板渲染的原理就是服务端把html DOM内容拼接好,直接给响应体返回返回。
这里模板引擎用的是PUG
/**
* 模板渲染
*/
router.get('/', async (ctx, next) => {
await ctx.render('index', {
title: 'Hello Koa 2!',
name: '张博'
})
})
2、返回字符串
直接给ctx.body赋值字符串就可以
/**
* 字符串
*/
router.get('/string', async (ctx, next) => {
ctx.body = 'koa2 string'
})
3、返回JSON
直接给ctx.body赋值JSON就可以
/**
* json返回
*/
router.get('/json', async (ctx, next) => {
ctx.body = {
title: 'koa2 json'
}
})
4、返回格式统一的JSON数据
例如下面固定格式 message、data、code, 这个我们前端同学肯定经常见,但是有没有思考过这种统一格式的返回 你知道一般后端是怎么做的吗?
{
"message": "登录成功",
"data": {
"id": 1,
"username": "朱鑫涛",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjYwNjU4NzIyLCJleHAiOjE2NjMyNTA3MjJ9.DGBIMMZ-Cg984d2uLBFN8ceh3QMDS6C0OxJiudgfYZQ"
},
"code": 1
}
1、封装统一的方法
就是成功的一个方法
失败的逻辑一个方法
例如下面这样
/**
* 成功的返回
* @param {*} ctx
* @param {*} data 数据
*/
function success(ctx, data = {}) {
ctx.body = {
message: '',
data,
code: 1
}
}
/**
* 失败的返回
* @param {*} ctx
* @param {*} code 错误码
* @param {*} message 错误提示
*/
function fail(ctx, code = 0, message = '') {
ctx.body = {
message,
data: null,
code
}
}
2、代码里调用
router.get('/test',, async function (ctx, next) {
const data = {
"id": 1,
"username": "朱鑫涛",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjYwNjU4NzIyLCJleHAiOjE2NjMyNTA3MjJ9.DGBIMMZ-Cg984d2uLBFN8ceh3QMDS6C0OxJiudgfYZQ"
},
success(ctx, data)
})
// /test 返回值必然是
{
message: '',
data: {
"id": 1,
"username": "朱鑫涛",
"token": "..."
},
code: 1
}
这样就实现了统一格式的返回
5、登陆
从这一个开始就有了和数据库的交互, 为了不增加大家的学习成本.
我把数据库连接和orm配置没有列出来, 有兴趣的可以去github的找相关数据库配置和orm设置。
路由
/user/login
思路
登录接口整体思路
- 登录就需要用到数据库了, 首先建一张用户表
- 登录接口拿着传过来的用户名、密码去查用户表是否存在、和密码是否正确
- 用户存在且密码正确,返回登录成功状态码和token
- 用户不存在或者密码错误 提示用户名或密码错误
建表(这里建立了一个表做测试)
用户表
CREATE TABLE `user_copy` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL COMMENT '用户邮箱',
`pwd` varchar(255) DEFAULT NULL COMMENT '登录密码',
`tel` varchar(255) DEFAULT NULL COMMENT '用户手机号',
`open_id` varchar(255) DEFAULT NULL COMMENT '微信用户id',
`status` smallint(6) DEFAULT '1' COMMENT '用户状态',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5411 DEFAULT CHARSET=utf8;
代码示例
逻辑
1、用户带着用户名密码请求进来
2、校验用户名密码
3、生成token返回
/**
* 登陆
*/
router.post('/login', async function (ctx, next) {
console.log(ctx.request.body);
const body = ctx.request.body
const param = {
userName: body.userName,
pwd: body.pwd
}
// 这一步是查数据库比对, 为了不增加大家学习成本, 可以用假数据模拟
let res = await User.findOne({
where: param,
});
// 成功则返回用户信息和token
if (res) {
const data = {
id: res.id,
userName: res.userName,
token: createToken({id: res.id})
}
success(ctx, data)
} else {
fail(ctx, '用户名或密码错误')
}
})
6、用户状态(token校验)
我登录的token是用jwt生成的 www.ruanyifeng.com/blog/2018/0…
鉴权中间件
所以我们这里写一个中间价函数function auth()来解析token(鉴权),
/**
* created by zhangbo on 2022/08/16
* 鉴权中间件函数
* @returns
*/
function auth() {
return (ctx, next) => {
const token = ctx.request.header.token
console.log(token);
const isAuth = verifyToken(token);
if (isAuth) {
return next()
}
return fail(ctx, '未登录')
}
}
如何使用
我们在注册路由的时候, 业务中间价前面加上即可。
例如下面auth的位置
/**
* 测试
*/
router.get('/getList', auth, async function (ctx, next) {
....
})
7、列表页(查)
描述
列表页的话直接查数据库返回即可, 如果没有其他逻辑的话,一般是这么干的,偷偷告诉你们 这是后端最简单的接口了
这就是常说的增删改查的查, crud里的r
路由
/user/getList
代码
/**
* 列表
*/
router.get('/getList', async function (ctx, next) {
let res = await User.findAndCountAll({
limit: 10,
offset: 0,
});
if (res) {
success(ctx, res)
// ctx.body = res;
}
else {
ctx.body = "没有数据";
}
})
8、id获取详情(查)
描述
这个也是CRUD里的R, 平常的你就传个id, 后端拿着你传过来的ID, 去数据库查这条记录,查到直接返回, 如无其他逻辑 这是后端最简单的接口之一了
/user/getUser?id=5410
代码示例
/**
* 用户详情
*/
router.get('/getUser', async function (ctx, next) {
console.log(ctx.request.query);
let id = ctx.request.query.id;
const res1 = await User.findOne({
where: {
id: id
}
});
success(ctx, res1)
})
9、导出excel
/user/export
步骤
1、 查出数据
2、设置自定义对字段的自定义表头
3、使用 xlsx json对象转sheet
4、使用xlsx 新建工作簿
5、向工作簿添加一个工作表(这一步也可以有多个工作表)
6、xlsx 生成文件Buffer
7、设置文件下载的响应头
ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent('测试')}.xlsx`);
8、把文件Buffer赋给响应体ctx.body。
结束
代码示例
/**
* 导出excel
*/
router.get('/export', async function (ctx, next) {
// 从数据库查出数据
const data = await User.findAll({
attributes: [
'id',
'userName',
'openId',
'email',
'description',
'status'
],
limit: 10,
offset: 0,
raw: true
});
// 表头中文
const headerDis = {id: '用户id', userName: '用户名', openId: 'openId', email: '邮箱', description: '描述',status: '状态'}
// 表头字段
const header = [
'id',
'userName',
'openId',
'email',
'description',
'status',
]
/**
* json对象转sheet
*/
const sheet = XLSX.utils.json_to_sheet([headerDis, ...data], {
header,
skipHeader: true,
});
/**
* 表格样式设置
*/
sheet['!cols'] = Array.from({length: 17}, (v, i) => ({wch: 18, size: 14}));
/**
* 新建工作簿
*/
const wb = XLSX.utils.book_new();
/**
* 向工作簿增加一个工作表(sheet)
* @param {wb} 工作簿对象
* @param {sheet} 工作表对象
* @param {name} 当前工作表名称
*/
XLSX.utils.book_append_sheet(wb, sheet, `测试sheet`);
/**
* 工作簿生成文件
*/
const excelBuffer = XLSX.write(wb, {
type: 'buffer',
});
/**
* 设置下载头和文件名
*/
ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent('测试')}.xlsx`);
ctx.body = excelBuffer
})
github代码仓库
完整代码示例 github.com/zhangbowy/n…
总结
回答前面的问题
node中的中间件是怎样存在?
- 首先它的编程思想是面向切面(AOP)的.
- 抽离出来做一件事情的方法, 比如鉴权、跨域、路由解析中间件等
- 用来处理请求流程
最后
到这里呢,本文就告一段落了, 希望大家node.js的学习之路顺利。
如果大家对我有什么建议或者想看其他node后端接口实现,欢迎评论区留言!
node.js阅读推荐
juejin.cn/post/713904… Nodejs生成PDF
juejin.cn/post/714948… 基于nodejs的后端路由自动加载