学习用Node.js写后端常见接口

3,313 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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

进到了你目录看到如下就算成功了

image.png

安装依赖

cd learn-koa && npm install

启动

npm run dev

然后打开本地3000端口, 看到如下就算是成功运行了 冬天第一个接口 image.png

常见后端接口(基于koa2)

阅读本节, 需要您了解koajs的基本使用, 请阅读上一节。

阅读本节, 默认您对koajskoa-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数据

例如下面固定格式 messagedatacode, 这个我们前端同学肯定经常见,但是有没有思考过这种统一格式的返回 你知道一般后端是怎么做的吗?

{
  "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设置。

链接 github.com/zhangbowy/n…

路由

/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后端接口实现,欢迎评论区留言!

image.png

node.js阅读推荐

juejin.cn/post/713904… Nodejs生成PDF

juejin.cn/post/714948… 基于nodejs的后端路由自动加载