跳转至

34 服务端功能扩展:如何对Vue.js全栈项目做服务端功能扩展?

你好,我是杨文坚。

上节课我们基于页面视角,设计了运营搭建平台项目的扩展规范,主要围绕着服务端路由和浏览器路由的线索,串联了业务和技术两大内容,先定制出扩展规范,再以“页面路由”为线索,根据扩展规范,设计和实现了技术底座。

但是,对一个完善的全栈项目来说,比如,要实现一个后台用户找回密码的功能,我们单纯扩展几个操作页面,只能提供扩展操作界面,无法扩展对业务逻辑、对底层数据的修改。再比如,随着业务的发展,需要新增搭建页面的下线功能,之后又希望扩展成页面定时自动下线的功能,也都无法通过扩展页面实现。

所以,搭建平台要扩展更多功能的时候,单纯扩展页面是远远不够的,我们还需要整个服务端链路提供扩展的规范和能力。那今天,我们就来学习如何围绕搭建平台的特点,进行服务端功能的扩展设计。

为什么需要学习服务端功能扩展的内容

不过讲到这里,作为前端开发工程师,你可能会存在疑问,我们用Node.js直接开发全栈Web项目,实现所需要的功能,就够用了;而且,我们课程里的Web服务,主要工作就是对数据进行增删改查,有必要了解那么多服务端功能扩展知识吗?

这个问题,站在职场视角上,没有绝对的技术立场和标准答案,你要根据实际工作场景的需要作选择。我个人认为有三个关键因素。

  1. Vue.js必须依赖Node.js。
  2. 搭建场景侧重前端主导。
  3. Node.js开发岗位的局限性。

第一点是必要因素。Vue.js的运营搭建平台页面,离不开Node.js服务。

最核心的原因就是Vue.js的技术生态都是基于Node.js建设的。首先,我们会基于Vue.js这个前端技术,围绕其语法和生态,设计和构建搭建平台的物料,也就是Vue.js组件;然后,基于这些物料,我们会布局和编译生产最终搭建页面,也就是Vue.js页面。

所以,整个过程,都必须用Node.js的能力,在服务端环境进行Vue.js内容的操作处理。

第二点,搭建场景,也就是搭建平台场景,大量业务和开发的重心,都是前端相关的内容,侧重前端主导。

搭建平台属于“复杂交互”的功能项目,业务逻辑重心就是搭建页面操作逻辑,对于前端程序员来讲有天然的“主场优势”。所以,搭建操作需要的数据模型、HTTP接口和功能操作逻辑,应该更加侧重于满足“前端搭建”的诉求。

第三点,Node.js开发岗位的局限性,职场中对Node.js的职责归属比较尴尬。

这是因为Node.js的开发语言,和传统前端领域使用的JavaScript语言一致,而在大部分企业里,主流的服务端开发语言都是Java、Python等传统后端语言,所以,Node.js服务端开发工作,大部分都是由前端程序员主导的。但是,目前就业市场上,很少有企业开设纯Node.js服务端开发岗位,都是我们前端程序员兼顾Node.js服务端开发。

所以,为什么我们前端程序员要学习服务端功能扩展的知识点,总结起来就是“业务场景需要和技术场景局限”。

了解了目的,接下来我们正式进入今天的核心内容,搭建平台服务端扩展。经过上节课的学习,相信你也知道“功能扩展”的套路了,先根据业务和技术两方面的特点,设计出“扩展规范”,然后根据规范,实现对应的“技术底座”,总的来讲就是两个要素,“规范”和“底座”。

我们先做第一件事,设计扩展规范。

如何设计搭建平台服务端的扩展规范

如何对搭建平台服务端设计扩展规范呢?我们还是先梳理运营搭建平台在业务扩展、技术扩展两方面所需要的能力。

业务扩展能力

业务需要扩展的内容,主要有三点。

  • 业务数据扩展
  • 功能操作扩展
  • 逻辑状态扩展

第一点业务数据扩展,扩展范围主要分为“新增数据类型”和“调整数据内容”

“新增数据类型”,你可以理解就是扩展出新的数据内容。

比如,业务需要统计平台用户的登录数据,就需要扩展“用户登录的记录数据”,那么我们要做的第一件事就是从数据库层面,设计用户登录的数据表。之前数据库设计的学习(第 20 讲)中,我们讲过,只要数据库表设计好了,就等于业务逻辑设计好了,也就等于功能完成了一半。新增数据的扩展也一样,只要定义好新数据的数据库表,就等于实现了新功能的大部分设计工作。

“调整数据内容”,就是要对原有数据内容或者结构做扩展。

比如搭建后台用户要进行权限分级,就需要对原有用户数据库表进行字段新增扩展。这类扩展操作,就可以利用我们之前保留的用户数据库表的JSON类型的扩展字段,基于JSON数据,扩展出用户权限的二级数据内容。当然,如果原数据库表没有保留可扩展的字段,就必须修改数据库的表结构,新增数据字段。

第二点功能操作扩展,对原有的操作逻辑进行调整或者新增操作

比如扩展用户变更密码和找回密码操作,我们就需要基于存量的用户数据,扩展出修改密码的修改数据操作,基本需要扩展服务端分层中数据层、业务层或控制层的代码。

最后一点,逻辑状态扩展,就是扩展相关内容的状态逻辑。比如扩展用户登录态,能在浏览器保持登录状态24小时;再比如扩展用户单点登录的状态控制,不能同时在多个地方登录。

技术扩展能力

常见的服务端业务功能扩展,我们就分析到这里,接下来看技术层面的扩展。还是老规矩分层讲解,具体可以分成四种层级的扩展内容。

  • 路由层扩展
  • 控制层扩展
  • 业务层扩展
  • 数据层扩展

服务端的技术扩展,我们用技术的结构图来分析。

对于业务功能来说,每个分层,都是以独立的原子模块存在的。一个层级的某个功能,是由下一个层级中的一个或多个原子模块组合而成,或者同层级的一个或多个功能模块组合而成。

以注册操作功能为例,路由层引用了控制的注册子模块,控制层引用业务层的注册操作业务模块,业务层引用数据层的多个模块,包括查询账户名模块和插入用户数模块。

如何扩展

了解了运营搭建平台在业务扩展、技术扩展两方面的特点和所需要的能力,我们看看如何设计扩展规范。

如果是业务扩展,一般是基于业务的纵向扩展;如果单纯是技术能力的扩展,为了保持各个分层独立,一般都是横向每个分层独立扩展。所以,现在我们有两个扩展方向,基于业务的纵向扩展、基于技术的横向扩展

参考上节课的页面扩展思路,以“路由”作为突破口,串联前端和服务端页面的扩展,这节课的服务端扩展,也需要找一个“线索”作为突破口,我们可以选择平台“横向和纵向”作为扩展线索,也就是说,根据服务端扩展规范,扩展的内容就是“横向切面”和“纵向切面”

不知道你有没有发现,我们这里讲到的横向和纵向切面结构,其实跟项目代码案例的文件目录结构是一一对应的。

图片

所以,我们接下来要实现功能扩展的“技术底座”,其实已经是现成的了。不过,我们还是按照两种切面扩展来详细分析一下。

首先基于业务视角,也就是纵向切面,如何实现服务端的纵向切面扩展呢?

如何实现纵向切面的功能扩展

服务端纵向切面的功能扩展,就是贯穿服务端分层,实现每层的功能模块,我们要梳理原有分层中的模块内容,以及新增模块如何存放。

先梳理原来模块内容。

扩展的功能,按照需要,我们要选择合适的纵向位置,新增子模块

比如,现在要实现一个用户登录记录功能,我们需要从纵向切面,在需要的分层实现对应的模块。

看示意图,需要实现的扩展模块和扩展理解有三部分。

  • 数据层,新增“用户登录行为数据表”和数据库操作,实现“插入登录数据”的操作模块。
  • 业务层,使用数据层的数据库操作方法,实现登录用户的“记录登录行为”的操作模块。
  • 控制层,判断用户是否在登录状态下,再使用业务新增的记录模块,主要实现“逻辑操作扩展”。

接下来看横向切面的扩展。

如何实现横向切面的功能扩展

服务端横向向切面的功能扩展就是在同一个分层中,新增需要功能的子模块,一般侧重于技术层面的需要。

比如,要新增一个路径前缀,读取其它目录的静态资源内容。那么扩展功能就要在“路由层”中开展工作。

看示意图,原有的静态资源在路由层中,判断前缀是“/public”,就读取原有静态目录“/public”,现在,我们新增了一个新静态资源路径前缀“/static”,读取新的一个静态资源目录。

如果基于Koa.js代码来扩展,就需要这么来扩展路径处理。

// packages/work-server/src/index.ts
import path from 'node:path';
import Koa from 'koa';
import koaStatic from 'koa-static';
import koaMount from 'koa-mount';
// ...

const app = new Koa();
// ...

const publicDirPath = path.join(getServerDir(), 'public');
app.use(koaMount('/public', koaStatic(publicDirPath)));

// 静态资源路径前缀“/static”,读取新的一个静态资源目录
const staticDirPath = path.join(getServerDir(), 'static');
app.use(koaMount('/static', koaStatic(staticDirPath)));

// ...

基于“横向和纵向切面”,进行服务端功能的扩展,我们就学完了。不过,以“横纵切面”形式做扩展有点繁琐,有没有更加优雅的服务端扩展方式呢?

服务端扩展还有哪些更优雅的方式

答案是有的,我们要利用所使用Web服务框架特性。比如课程里选择的Koa.js服务框架,具有洋葱模型的中间件体系,天然就是一种服务端优雅的扩展方式。

简单复习一下Koa.js洋葱中间件模型的代码。

const context = {};

async function middleware1(ctx: any, next: any) {
  console.log('打印 001');
  await next();
  console.log('打印 004');
}

async function middleware2(ctx: any, next: any) {
  console.log('处理HTTP响应之前');
  await next();
  console.log('处理HTTP响应之前');
}

async function middleware3(ctx: any, next: any) {
  console.log('打印 002');
  await next();
  console.log('打印 003');
}

Promise.resolve(
  middleware1(context, async () => {
    return Promise.resolve(
      middleware2(context, async () => {
        return Promise.resolve(
          middleware3(context, async () => {
            return Promise.resolve();
          })
        );
      })
    );
  })
).then(() => {
  console.log('执行结束');
});

代码可以用这张中间件模型的示意图来描述。

图片

可以看出,在一个HTTP请求过程中,可以对每个中间件执行两次操作,而且,越往头部的中间件,两次执行操作,越靠近HTTP请求和响应的两端。我们可以利用这个机制,以HTTP切面为线索,开发中间件来扩展功能。

比如扩展HTTP请求日志记录功能,看具体扩展设计图。

基于这张的HTTP请求记录的设计图,可以这么来实现代码。

import type { Context, Next } from 'koa';

export async function record(ctx: Context, next: Next) {
  const info = `[${ctx.method}] ${ctx.url}`;
  // 进入内部中间件前的时间戳
  const start = Date.now();
  // 进入内部中间件前
  await next();
  // 跳出内部中间件后
  console.log(`${info} 执行内部所有中间件耗时 ${Date.now() - start}ms`);
  // 监听相应结束
  ctx.res.on('finish', () => {
    console.log(`${info} 请求完整耗时 ${Date.now() - start}ms`);
  });
}

在项目最开始使用中间件前,使用这个日志记录中间件。

// packages/work-server/src/index.ts
import path from 'node:path';
import Koa from 'koa';
import koaStatic from 'koa-static';
import koaMount from 'koa-mount';
import koaBodyParser from 'koa-bodyparser';
import routers from './router';
import { getServerDir } from './util/file';
import { syncFileFromCDN } from './middleware/sync-cdn';
import { record } from './middleware/record';

const app = new Koa();

// 使用扩展的 HTTP 日志打印中间件
app.use(record);

const publicDirPath = path.join(getServerDir(), 'public');
app.use(koaBodyParser());
app.use(koaMount('/public', koaStatic(publicDirPath)));
app.use(syncFileFromCDN);
app.use(routers);

const port = 8001;
app.listen(port, () => {
  console.log('服务启动: http://127.0.0.1:' + port);
});

使用中间件后,控制台可以看到所有HTTP请求的耗时日志打印情况。

图片

通过这个中间件扩展案例也可以看出,服务端的功能扩展有很多方式,除了常规的业务和技术规范分析得到的扩展线索,我们也能利用所使用的Web框架特性,基于纯框架自带的特性,更优雅地扩展。

总结

作为前端程序员,我们也要掌握服务端功能扩展的知识,不能只局限于页面开发的领域,也不能局限于只会用Node.js增删改查服务端数据。要学会思考如何设计或解构一个全栈服务,并且化整为零地分析处理,逐渐沉淀自己对全栈项目的功能组合的认知,构建属于自己的全栈化知识体系。

在围绕运营搭建平台,扩展服务端功能的过程中,我们也加固了对项目功能扩展套路的掌握程度。无论前端功能扩展,还是服务端的功能扩展,比较常见的套路是先找个切入点,作为“线索”来分析,然后把线索的分析结果作为扩展规范的基础。当扩展规范定好了,就可以实现技术底座。这样,我们能以较低的成本,实现扩展功能的规范化。

实际扩展服务端功能的时候,注意不能只墨守成规按扩展套路或者技术规范进行,要因地制宜地使用技术,借助框架的优势,更优雅地扩展。

思考题

如果Node.js服务功能需要扩展功能,使用其他服务端语言实现的服务功能,比如Java服务提供的API,怎么做功能扩展?

欢迎在留言区分享你的想法。

通过今天的学习,希望能加深你对技术项目功能扩展的认知,也希望你在以后的开发工作中,能举一反三,利用我们整理的“套路”,设计出更优雅的项目架构。我们下节课见。

完整的代码在这里

精选留言(1)
  • ifelse 👍(0) 💬(0)

    学习打卡

    2024-10-07