在前后端分离的开发模式中,我们的项目都是分成了前后端两个部分:

  1. 前端项目:使用 webpack 构建的前端项目;
  2. 后端项目:基于 express 搭建的后端项目;

以我们的考试系统为例的话,就是 exam-system 前端项目和 exam-system-server 后端项目。
在实际项目开发中,前端和后端一定是由至少两个程序员分别负责开发,所以两个项目会同时进行,不存在先写前端还是先写后端的问题。但是因为现在前后端都只有我们自己一个人开发,所以我们在写代码的时候,就可以根据自己的习惯来选择先写前端还是先写后端。
这里我们以先写前端代码为例,来梳理一下前后端分离开发的代码编写流程。

一、前端发送请求

当我们在前端把页面和样式写好后,就可以开始根据业务需求来发送 AJAX 请求到后端。

1、添加事件

通常,前端要发送 AJAX 请求主要有两种情况:

  1. 页面刷新一进来时就需要发送 AJAX 请求;
    1. 在全局先封装好一个方法,直接调用即可。
  2. 用户在页面中触发了某些事件后,需要发送 AJAX 请求;
    1. 先给对应的元素节点身上添加事件,在事件中调用请求

      2、封装 AJAX

      找到前端项目 api 目录中对应的 .js 文件,如果没有就自己创建。 api 的 .js 文件有了之后就可以开始封装相关 AJAX 请求。

      3、配置请求代理

      在前后端分离的开发模式中,通常前端项目都有一个 webpack 的服务器,后端也有一个 express 的服务器。默认情况下,webpack 的服务器是无法发送请求到 express 的服务器中的。存在跨域问题。
      所以,我们需要在前端的 webpack 的 webpack.dev.config.js 配置文件对开发服务器进行处理:
      1. module.exports = merge(base, {
      2. mode: 'development',
      3. devtool: 'inline-source-map',
      4. // 开发服务器
      5. devServer: {
      6. port: '8080', // 端口号
      7. open: 'index.html', // 启动项目时默认打开的页面
      8. // 默认情况下,前端8080的服务器不能向后端3000的服务器发送请求
      9. proxy: {
      10. // 匹配所有包含 /api 的请求
      11. '/api': {
      12. target: 'http://localhost:3000', // 真正处理请求的目标后端服务器地址
      13. changeOrigin: true,
      14. // 前端所有以 /api 开头的请求,转发到后端服务器后,会自动去掉 /api
      15. pathRewrite: {
      16. '^/api': ''
      17. }
      18. }
      19. }
      20. }
      21. })
      配置完成后,项目中所有的 AJAX 请求的 URL,都应该在原本的 URL 的前面加上 /api 的前缀,例如:
      1. // 获取所有分类数据
      2. export function getTypesAsync() {
      3. return new Promise((resolve, reject) => {
      4. $.ajax({
      5. // url 通常由两部分组成:/数据名/描述操作
      6. url: '/api/types/getTypes',
      7. type: 'GET',
      8. success(res) {
      9. resolve(res);
      10. }
      11. })
      12. })
      13. }

      4、引入 API 方法

      AJAX 封装成功后,回到我们页面的 .js 文件中。想要调用哪一个功能的 AJAX,就引入封装好的 API 方法。
      1. import { 封装好的api方法名 } from '封装api方法路径'

      5、调用 API 方法

      引入 API 方法后,就可以在对应的位置调用方法发送请求了。
      注意:请求是异步执行,封装和调用API时都需要使用AsyncAwait

      6、测试 AJAX 请求

      前端 AJAX 的调用写好后,我们就可以在页面中进行刷新,或者点击相关按钮触发事件,来检测前端的 AJAX 请求是否发送成功。
      前后端流程梳理 - 图1
      说明:在还没有处理后端时,前端请求应该会有 404 的报错。

      7、检查前端参数

      如果当前的请求需要发送参数给后端,在 Network 中点击进入对应的请求,查看参数的传递情况:
      前后端流程梳理 - 图2
      请求和参数都没问题后,就可以开始编写后端的代码了。

      二、后端处理请求

      1、检查前端请求

      虽然我们还没有开始编写后端的代码,但是前端请求已经可以发送到后端。
      正常情况下,当前端发送了请求后,我们在后端项目的终端中可以看到一个 404 的请求日志:
      前后端流程梳理 - 图3
      这样才表示前端请求成功的发送到了后端。

      2、匹配一级路径

      前端请求发送到后端,首先会先进入 app.js 中匹配请求的一级路径。
      例如我们前端发送的 /types/deleteTypes 请求,会先在 app.js 中查找以下配置:
      1. // ...
      2. var typesRouter = require('./routes/types');
      3. // ...
      4. app.use('/types', typesRouter);
      5. // ...
      如果没有以上配置,我们就需要自己在 routes 目录中创建对一个的 .js 文件,然后在 app.js 中添加以上配置。
      一级路径匹配成功后,我们的请求就会根据一级路径的配置,进入到对应的 .js 文件:
      前后端流程梳理 - 图4

      3、匹配二级路径

      进入到 routes 中对应的 .js 文件后,开始匹配请求类型和二级路径:
      1. router.post('/deleteTypes', async function (req, res, next) {
      2. console.log('成功匹配到前端的 /types/deleteTypes 请求');
      3. });
      注意:前端发送的请求,除了一级路径外,还一定要和后端的“请求类型”和“二级路径”都匹配上,参考下图:
      前后端流程梳理 - 图5
      最终,当一级路径、二级路径和请求类型都匹配成功时,前端的请求才成功的进入到后端。

      4、获取前端发送的参数

      如果这次请求中,前端发送了参数给后端,我们还需要在后端获取到前端发送的参数(如果本次请求前端没有发送参数,可以忽略这一步骤)。
      1. router.post('/deleteTypes', async function (req, res, next) {
      2. // 获取前端通过 AJAX 的 data 发送给后端的数据
      3. const params = req.body;
      4. console.log('获取前端发送的参数', params);
      5. });
  • req.body:可以用来接收前端 POST 请求发送的参数
  • req.query:可以用来接收前端 GET 请求发送的参数

    5、后端配置数据模型

    后端接收到前端发送的请求后,下一步就需要开始通过数据模型来操作数据库。所以,需要先配置好数据模型。
    我们在项目的 dao/models 目录中创建一个 typesModel.js 文件,来作为考试分类集合的模型配置文件:
    1. // 考试分类集合
    2. const { Schema, model } = require('mongoose');
    3. // 定义该集合中每一条数据的格式(排除 _id)
    4. const typesSchema = new Schema({
    5. type: String,
    6. icon: String
    7. }, { versionKey: false })
    8. // 设置数据库的集合名称
    9. module.exports = model('typesModel', typesSchema, 'types');

    6、引入数据模型

    模型配置好后,回到 rouets 目录的文件中,引入数据模型:
    1. const typesModel = require('../dao/models/typesModel');
    前后端流程梳理 - 图6

    7、操作数据库

    获取到模型后,就可以通过模型去操作数据库了。

    8、返回结果给前端

    根据第 7 步操作数据库的结果,我们需要通过判断等操作,返回一个结果给前端。
    例如将获取到的所有分类数据返回给前端:
    1. router.get('/getTypes', async function (req, res, next) {
    2. const data = await typesModel.find();
    3. // 将处理结果返回给前端
    4. res.send({
    5. message: '考试分类数据获取成功',
    6. code: 1,
    7. data: data
    8. });
    9. });
    注意:不管做什么请求,后端最终都必须要通过 res.send() 返回一个结果给前端,这样,一个请求才能完成,才能继续执行后续的代码。

    三、前端处理请求结果

    后端 res.send() 返回给前端的结果,最终会发送到 AJAX 的 success(res){}的 res 中,然后通过我们的封装处理,将 success 的结果 resolve() 出来,最终在页面的 .js 文件中通过 await 来接收到后端的结果。