34 服务端功能扩展:如何对Vue.js全栈项目做服务端功能扩展?
你好,我是杨文坚。
上节课我们基于页面视角,设计了运营搭建平台项目的扩展规范,主要围绕着服务端路由和浏览器路由的线索,串联了业务和技术两大内容,先定制出扩展规范,再以“页面路由”为线索,根据扩展规范,设计和实现了技术底座。
但是,对一个完善的全栈项目来说,比如,要实现一个后台用户找回密码的功能,我们单纯扩展几个操作页面,只能提供扩展操作界面,无法扩展对业务逻辑、对底层数据的修改。再比如,随着业务的发展,需要新增搭建页面的下线功能,之后又希望扩展成页面定时自动下线的功能,也都无法通过扩展页面实现。
所以,搭建平台要扩展更多功能的时候,单纯扩展页面是远远不够的,我们还需要整个服务端链路提供扩展的规范和能力。那今天,我们就来学习如何围绕搭建平台的特点,进行服务端功能的扩展设计。
为什么需要学习服务端功能扩展的内容
不过讲到这里,作为前端开发工程师,你可能会存在疑问,我们用Node.js直接开发全栈Web项目,实现所需要的功能,就够用了;而且,我们课程里的Web服务,主要工作就是对数据进行增删改查,有必要了解那么多服务端功能扩展知识吗?
这个问题,站在职场视角上,没有绝对的技术立场和标准答案,你要根据实际工作场景的需要作选择。我个人认为有三个关键因素。
- Vue.js必须依赖Node.js。
- 搭建场景侧重前端主导。
- 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,怎么做功能扩展?
欢迎在留言区分享你的想法。
通过今天的学习,希望能加深你对技术项目功能扩展的认知,也希望你在以后的开发工作中,能举一反三,利用我们整理的“套路”,设计出更优雅的项目架构。我们下节课见。
完整的代码在这里
- ifelse 👍(0) 💬(0)
学习打卡
2024-10-07