LoopBack 4 不再规划的三项 LoopBack 3 功能:存储组件、推送通知与离线同步的迁移替代方案

原创2026-10-13 19:14:081,993 阅读
文章标签:后端微服务

LoopBack 4 不再规划的三项 LoopBack 3 功能:存储组件、推送通知与离线同步的迁移替代方案

本文围绕 LoopBack 迁移指南中的 not-planned.md 文档展开,系统梳理 LoopBack 团队在从 LoopBack 3 迁移到 LoopBack 4 时明确"不再规划"(not planned)的三类功能——存储组件(Storage component)、推送通知(Push notifications)以及离线数据访问与同步(Synchronization),并说明官方推荐的在 LoopBack 4 中重建等价能力的技术路线。读完本文,你将掌握如何用 x-parser: stream 扩展 + multer + Service/Controller 组合替代旧的文件上传下载组件、如何用 Model/Repository/Service 结构重建推送能力,以及离线同步场景下应研究哪些专门解决方案。

背景:为什么这些 LoopBack 3 功能被"停用"

在 LoopBack 早期(2013—2015 年前后),团队曾广泛探索各种功能领域,试图让框架对更广泛的受众具有吸引力。其中一些功能流行起来,另一些则反响平平;与此同时,技术世界不断演进,当年的许多范式已被更优的思路取代。

更重要的是,LoopBack 维护者社区的增长速度不足以支撑 LoopBack 所提供的那张宽广的功能谱系。结果是:大部分扩展之间缺乏良好集成,且许多已经过时。为了保证项目可持续、并给框架用户提供良好的开发者体验,团队决定停用(sunset)若干 LoopBack 3 扩展与功能,并鼓励社区接手这些扩展的后续开发。

具体到迁移文档体系,overview.md 中明确提到:借着这次大版本升级的机会,LoopBack 团队放弃了一批没有精力继续维护和演进的功能与组件,相关说明详见 not-planned.md。被列入"不再规划"清单的共三类:

功能 对应 LoopBack 3 组件 LoopBack 4 中的态度
文件存储(上传/下载) loopback-component-storage 不提供一等公民支持,用 x-parser: stream + multer + Service/Controller 自行实现
移动推送通知 loopback-component-push 不内置,用 Model/Repository/Service + 第三方推送 SDK(apn、node-gcm)重建
离线数据访问与同步 Synchronization 组件 不迁移(与内置持久层深度耦合),推荐研究数据库配套的专门同步方案

Storage component:文件上传下载的 LoopBack 4 替代方案

LoopBack 3 中的组件构成

loopback-component-storage 提供了一套统一的 REST API,用于存储和获取任意文件,底层可对接第三方存储服务(如 Amazon S3)。从架构上看,该组件由以下四部分组成:

  1. 处理文件上传的自定义层;
  2. 处理文件下载的自定义层;
  3. 对接不同存储服务的连接器(这部分由 pkgcloud 包承担);
  4. 提供 File 与 Container 抽象并暴露 REST API 的集成层。

在 LoopBack 4 中逐层重建

LoopBack 4 中,官方给出的替代路线与上述四个部分一一对应:

  1. 上传:使用 x-parser: stream 扩展让请求体以流(stream)形式接收,再用 multer 包处理 multipart/form-data 文件上传。
  2. 下载:LoopBack 4 尚未对文件下载提供一等公民支持(相关讨论见 loopback-next#2230)。作为变通方案,可以在 Controller 中注入完整的 Express response 对象,直接使用 Express API 将响应体以流式输出。
  3. 云存储对接:可继续使用 pkgcloud,或直接使用云服务商提供的 Node.js 客户端库;社区另有 multer-storage-pkgcloud 可将 multer 与 pkgcloud 打通。
  4. REST API 封装:用 TypeScript 创建提供 File/Container API 的 Service,再以一个或多个 Controller 实现 REST 接口。

源码级实例:examples/file-transfer

仓库中的 file-transfer 示例 是上述方案的一个完整可运行实现,它暴露了三个端点:POST /files(接受 multipart/form-data 上传)、GET /files(列出已上传文件)、GET /files/{filename}(下载单个文件)。下面沿着源码走一遍关键链路。

第一步:配置 multer 并绑定服务。 在 application.ts 中,应用通过 configureFileUpload() 方法把 multer 的 diskStorage 配置绑定到 FILE_UPLOAD_SERVICE 键上:

// examples/file-transfer/src/application.ts(节选)
protected configureFileUpload(destination?: string) {
  // 默认上传到 dist/.sandbox
  destination = destination ?? path.join(__dirname, '../.sandbox');
  this.bind(STORAGE_DIRECTORY).to(destination);
  const multerOptions: multer.Options = {
    storage: multer.diskStorage({
      destination,
      // 保留原始文件名
      filename: (req, file, cb) => {
        cb(null, file.originalname);
      },
    }),
  };
  // 配置 file upload service
  this.configure(FILE_UPLOAD_SERVICE).to(multerOptions);
}

其中 FILE_UPLOAD_SERVICE 与 STORAGE_DIRECTORY 两个绑定键定义在 keys.ts:

export const FILE_UPLOAD_SERVICE = BindingKey.create<FileUploadHandler>(
  'services.FileUpload',
);
export const STORAGE_DIRECTORY = BindingKey.create<string>('storage.directory');

第二步:用 Provider 暴露 multer 请求处理器。 上传服务本身是一个 @injectable 的 Provider,它接收 @config() 注入的 multer 配置,返回 multer(options).any() 生成的 Express 请求处理器,见 file-upload.service.ts:

@injectable({
  scope: BindingScope.TRANSIENT,
  tags: {[ContextTags.KEY]: FILE_UPLOAD_SERVICE},
})
export class FileUploadProvider implements Provider<FileUploadHandler> {
  constructor(@config() private options: multer.Options = {}) {
    if (!this.options.storage) {
      // 默认使用内存存储
      this.options.storage = multer.memoryStorage();
    }
  }
  value(): FileUploadHandler {
    return multer(this.options).any();
  }
}

这种"Provider + Binding"的方式正是 LoopBack 4 依赖注入的典型用法:FileUploadHandler 类型即 express-serve-static-core 的 RequestHandler(见 types.ts),意味着 multer 中间件可以像任何 Express 中间件一样在 Sequence 中执行。

第三步:Controller 接收流式请求体。 上传控制器在 file-upload.controller.ts 中,借助 @requestBody.file() 快捷装饰器(其底层正是 x-parser: 'stream' 扩展)把整个请求以流形式交给 multer handler:

@post('/files', {responses: {...}})
async fileUpload(
  @requestBody.file()
  request: Request,
  @inject(RestBindings.Http.RESPONSE) response: Response,
): Promise<object> {
  return new Promise<object>((resolve, reject) => {
    this.handler(request, response, (err: unknown) => {
      if (err) reject(err);
      else resolve(FileUploadController.getFilesAndFields(request));
    });
  });
}

从底层看,x-parser: 'stream' 由 body-parser.stream.ts 中的 StreamBodyParser 实现。该解析器在 supports(mediaType) 中恒返回 false,注释明确说明"它只能通过请求体规范中的 {x-parser: 'stream'} 扩展被触发",parse() 则直接把原始 Request 对象作为值返回——即不做任何解析、保留请求流原样。正是这一机制让 multer 可以接管后续的 multipart 解析。

第四步:文件下载——注入 Express response。 下载控制器在 file-download.controller.ts 中演示了"注入完整 Express response 对象并调用 Express API"的变通方案:

@get('/files/{filename}')
@oas.response.file()
downloadFile(
  @param.path.string('filename') fileName: string,
  @inject(RestBindings.Http.RESPONSE) response: Response,
) {
  const file = this.validateFileName(fileName);
  response.download(file, fileName);
  return response;
}

@oas.response.file() 装饰器(来自 @loopback/openapi-v3,源码见 response.decorator.ts)用于在 OpenAPI 规范中声明响应为二进制文件类型。注意 validateFileName() 对文件名做了路径沙箱校验:用 path.resolve 解析后必须仍落在存储目录内,否则抛出 HttpErrors.BadRequest——这正是文件下载场景必须防住的路径穿越(path traversal)攻击。

第五步:Sequence。 整个示例的 Sequence 只是简单继承 MiddlewareSequence(见 sequence.ts),说明这套方案不要求自定义请求处理流程,标准中间件序列即可承载 multer 处理器。

测试验证

仓库为 file-transfer 示例提供了两组验收测试,可作为实现正确性的参照:

  • file-upload.acceptance.ts:通过 client.post('/files').field(...).attach(...) 模拟 multipart 上传,断言响应体中的 files 数组包含 fieldname、originalname、mimetype 等元数据;
  • file-download.acceptance.ts:覆盖了列文件、下载文件、文件不存在返回 404,以及使用 %2Fbad-file.txt、%2E%2E%2Fbad-file.txt(即 / 与 ../ 变体)绕过沙箱时返回 400 的路径穿越防护场景。

实际运行效果可参考示例自带的截图 upload-download.png。

file-transfer 示例的上传下载界面

启动示例的方式为在 examples/file-transfer 目录执行 npm start,应用默认监听 3000 端口,上传文件默认存放在应用根目录下的 .sandbox 目录,可通过应用配置中的 fileStorageDirectory 自定义存储位置。

把成果打包成 LoopBack Component

如果你对上述实现满意,官方鼓励将代码打包为 LoopBack Component 并分享给整个社区。组件通过 app.component() 方法挂载到应用(详见 Component.md 中的 "Using components" 一节),例如:

const app = new RestApplication();
app.component(YourFileStorageComponent);

若希望官方在文档中收录你的组件,只需提交一个 pull request,把组件加入 "Using components" 列表即可。

Push notifications:推送通知的 LoopBack 4 重建路线

loopback-component-push 让服务端应用即使在移动 App 未被使用时也能向 App 发送信息。在 LoopBack 3 中,该组件由以下部分组成:

  1. Device 模型及管理"设备—应用—用户"关系的 API;
  2. Application 模型,为 iOS、Android 等设备类型提供推送设置;
  3. Notification 模型,捕获通知消息并持久化定时通知;
  4. 可选的后台任务,消费定时通知请求;
  5. Push 连接器,与设备注册记录及推送服务商交互——iOS 走 APNS、Android 走 GCM;
  6. Push 模型,提供与设备无关的高层推送 API。

LoopBack 4 中官方不打算内置该组件,但给出了清晰的"重建配方":

  • 实现 Device 与 Application 模型:按 LoopBack 4 的标准做法创建 Model、Repository 和 Controller,用 REST API 暴露设备注册与管理能力;
  • 实现推送 Service:创建一个 Service 提供 TypeScript 级别的"发送通知"与"持久化待发送定时通知"API。与推送服务商的交互可直接使用 apn(对接 Apple 的 APNS)和 node-gcm(对接 Google 的推送服务)等包。

值得说明的是,LoopBack 4 的 Service 是从依赖注入容器(Context 中的 Binding)解析出来的对象(详见 Service.md 的 Overview),支持 proxy(远程 REST/SOAP/gRPC 代理)、class、provider 三种形态;推送 Service 通常实现为 class 或 provider 并注册到应用上下文,然后被 Controller 注入使用。这样既隔离了第三方 SDK 的细节,也让定时发送逻辑可以挂在 Life-cycle 或后台任务上。

Offline data access and synchronization:离线同步的替代方案

LoopBack 3 的 Synchronization 组件允许浏览器客户端在离线状态下访问和修改数据,并在客户端重新上线后,把客户端与服务器两侧的变更进行同步(复制)。LoopBack 团队明确表示:该功能在 LoopBack 3 中的实现复杂且与内置持久化层深度耦合,几乎无法迁移到 LoopBack 4 的编程模型,因此不在规划内。

官方给出的建议是:如果确实需要离线数据访问能力,应当去研究针对你所使用的数据库专门设计的同步方案。文档提供了三个出发点:

  • MongoDB Realm:为 MongoDB Atlas 提供双向、离线优先的同步能力;
  • PouchDB:让应用离线时在本地存储数据,恢复在线后与 CouchDB 及兼容服务器同步;
  • GUN:一个轻量、易用且快速的实时数据同步与存储系统,提供离线优先(offline-first)能力。

这些方案的共同点是"离线优先"(offline-first):客户端先保证本地可用性,再以增量同步的方式与服务器收敛数据,这与 LoopBack 3 中"框架内建复制引擎"的思路完全不同,也是当下更符合生态现实的选择。

总结与迁移建议

"不再规划"不等于"功能缺失":它只是说明这些能力不再由 LoopBack 框架内建维护,而是通过组合生态中的现成库 + LoopBack 4 的 Service/Controller 编程模型来重建。对照表如下:

旧能力 LoopBack 4 重建路径 关键依赖/机制
文件存储组件 x-parser: stream + multer + Service/Controller @requestBody.file()、@oas.response.file()、RestBindings.Http.RESPONSE、pkgcloud
推送通知 Device/Application Model + Repository + Controller + 推送 Service apn、node-gcm
离线同步 不迁移,改用数据库配套同步方案 MongoDB Realm、PouchDB、GUN

在动手前,建议先通读 Understanding-the-differences.md 建立对两代 LoopBack 概念映射的整体认知,再结合 migration/overview.md 的增量迁移节奏,逐个评估这三类功能在你现有应用中的真实占比。对于文件上传下载这类高频需求,直接基于 file-transfer 示例 改造即可获得生产可用的最小实现;对于推送与离线同步,则应把精力放在第三方生态选型与 Service 抽象设计上。

登录后查看全文
loopback-next