惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
GbyAI
GbyAI
博客园 - 叶小钗
小众软件
小众软件
WordPress大学
WordPress大学
I
InfoQ
Last Week in AI
Last Week in AI
Vercel News
Vercel News
博客园 - Franky
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
腾讯CDC
D
DataBreaches.Net
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
G
Google Developers Blog
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 看到有公司考核 token 指标,很好奇大家上个月的 AI 账单是多少 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 现在还有什么渠道可以稳定安全地使用 Claude 吗? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用?
深入 alova3 服务端能力:分布式 BFF 层到 API 网关的最佳实践
ScottHU · 2025-12-04 · via V2EX

可能大家对 alova 还停留在轻量化的请求策略库的层面,这当然是 alova2 的核心特点,比如以下这段

const { loading, data, error } = useRequest(() => alovaInstance.Get('/xxx'))

这是一段 alova 在客户端使用的典型代码,不过现在 alova 已经更新到 3 了,当然这些 client strategies 依然是原汁原味的,不过它不仅局限于客户端,而是在服务端也可以游刃有余了。

在 alova3 中提供了服务端请求策略( server hooks )和 redis 、file 等服务端的存储适配器,可以让我们很方便地在服务端实现全链路的请求和转发。

我们先来看一个请求的全流程:

客户端(浏览器/App )
    → Node.js BFF 层(转换数据等)
    → API 网关(鉴权、速率限制、路由分发等)
    → 后端微服务

alova 提供的 server hook 和分布式的多级缓存,可以让我们很方便地实现以上的全部层级的请求处理。

在 BFF 层转发客户端请求

在 BFF 层中经常需要转发客户端请求到后端微服务,你可以使用配合async_hooks访问每个请求的上下文,并在 alova 的beforeRequest中添加到请求中,实现用户相关数据的转发。

import { createAlova } from 'alova';
import adapterFetch from '@alova/fetch';
import express from 'express';
import { AsyncLocalStorage } from 'node:async_hooks';

// 创建异步本地存储实例
const asyncLocalStorage = new AsyncLocalStorage();

const alovaInstance = createAlova({
  requestAdapter: adapterFetch(),
  beforeRequest(method) {
    // 从异步上下文中获取请求头并传递到下游
    const context = asyncLocalStorage.getStore();
    if (context && context.headers) {
      method.config.headers = {
        ...method.config.headers,
        ...context.headers
      };
    }
  },
  responded: {
    onSuccess(response) {
      // 数据转换处理
      return {
        data: response.data,
        timestamp: Date.now(),
        transformed: true
      };
    },
    onError(error) {
      console.error('Request failed:', error);
      throw error;
    }
  }
});

const app = express();

// 中间件里设置一次,全程自动传递
app.use((req, res, next) => {
  const context = {
    userId: req.headers['x-user-id'],
    token: req.headers['authorization']
  };
  asyncLocalStorage.run(context, next);
});

// 业务代码专注业务逻辑
app.get('/api/user-profile', async (req, res) => {
  // 不用手动传递上下文了!
  const [userInfo, orders] = await Promise.all([
    alovaInstance.Get('http://gateway.com/user/profile'),
    alovaInstance.Get('http://gateway.com/order/recent')
  ]);
  
  res.json({ user: userInfo.data, orders: orders.data });
});

API 网关中的使用场景

在网关中经常需要进行鉴权、请求速率限制以及请求分发等,alova3 的 redis 存储适配器和 rateLimiter 可以很好地实现分布式的鉴权服务和请求速率限制。

鉴权可以这么搞

如果鉴权 token 有一定的过期时间,可在网关中配置 redis 存储适配器,将 token 存储在 redis 中便于重复使用,对于单机的集群服务也可以使用@alova/storage-file文件存储适配器。

import { createAlova } from 'alova';
import RedisStorageAdapter from '@alova/storage-redis';
import adapterFetch from '@alova/fetch';
import express from 'express';

const redisAdapter = new RedisStorageAdapter({
  host: 'localhost',
  port: '6379',
  username: 'default',
  password: 'my-top-secret',
  db: 0
});

const gatewayAlova = createAlova({
  requestAdapter: adapterFetch(),
  async beforeRequest(method) {
    const newToken = await authRequest(method.config.headers['Authorization'], method.config.headers['UserId'])
    method.config.headers['Authorization'] = `Bearer ${newToken}`;
  }
  // 设置 2 级存储适配器
  l2Cache: redisAdapter,
  // ...
});

const authRequest = (token, userId) => gatewayAlova.Post('http://auth.com/auth/token', null, {
  // 设置 3 个小时的缓存,将保存在 redis 中,再次以相同参数请求会命中缓存
  cacheFor: {
    mode: 'restore',
    expire: 3 * 3600 * 1000
  },
  headers: {
    'x-user-id': userId,
    'Authorization': `Bearer ${token}`
  }
});

const app = express();

// 实现 app 接收所有请求,并转发到 alova
// 注册所有 HTTP 方法的路由
const methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'];
methods.forEach(method => {
  app[method]('*', async (req, res) => {
    const { method, originalUrl, headers, body, query } = req;

    // 使用 alova 发送请求
    const response = await gatewayAlova.Request({
      method: method.toLowerCase(),
      url: originalUrl,
      params: query,
      data: body,
      headers
    });
    
    // 转发响应头部
    for (const [key, value] of response.headers.entries()) {
      res.setHeader(key, value);
    }
    
    // 发送响应数据
    res.status(response.status).send(await response.json());
  });
});

app.listen(3000, () => {
  console.log('Gateway server started on port 3000');
});

当然,如果需要每次请求都重新鉴权,也可以在authRequest中去掉cacheFor关闭缓存。

限流策略

alova 的 rateLimiter 可以实现分布式的限流策略,内部使用node-rate-limiter-flexible实现,我们改造一下实现。

import { createRateLimiter } from 'alova/server';

const rateLimit = createRateLimiter({
  /**
   * 点数重置的时间,单位 ms
   * @default 4000
   */
  duration: 60 * 1000,
  /**
   * duration 内可消耗的最大数量
   * @default 4
   */
  points: 4,
  /**
   * 命名空间,多个 rateLimit 使用相同存储器时可防止冲突
   */
  keyPrefix: 'user-rate-limit',
  /**
   * 锁定时长,单位 ms ,表示当到达速率限制后,将延长[blockDuration]ms ,例如 1 小时内密码错误 5 次,则锁定 24 小时,这个 24 小时就是此参数
   */
  blockDuration: 24 * 60 * 60 * 1000
});

const methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'];
methods.forEach(method => {
  app[method]('*', async (req, res) => {
    const { method, originalUrl, headers, body, query } = req;

    // 在此使用 rateLimit 包裹调用即可,它将默认使用 l2Cache 存储适配器作为控制参数的存储,这边的例子会用 redis 存储适配器。
    const method = gatewayAlova.Request({
      method: method.toLowerCase(),
      url: originalUrl,
      params: query,
      data: body,
      headers
    });
    const response = await rateLimit(method, {
      key: req.ip // 使用 ip 作为追踪 key ,防止同一 ip 频繁请求
    });
    
    // ...
  });
});

第三方服务集成:令牌自动维护

和外部 API 打交道需要 access_token 管理,并且很多第三方 access_token 具有调用限制,在这里我们可以使用 alova3+redis 存储适配器来实现分布式的 access_token 生命周期自动维护,其中 redis 用于 access_token 缓存,atom hook 用于分布式更新 token 的原子性操作。

import { createAlova, queryCache } from 'alova';
import RedisStorageAdapter from '@alova/storage-redis';
import adapterFetch from '@alova/fetch';
import { atomize } from 'alova/server';

const redisAdapter = new RedisStorageAdapter({
  host: 'localhost',
  port: '6379',
  username: 'default',
  password: 'my-top-secret',
  db: 0
});
const thirdPartyAlova = createAlova({
  requestAdapter: adapterFetch(),
  async beforeRequest(method) {
    // 判断是否为第三方 API ,如果是的话则获取令牌
    if (method.meta?.isThirdPartyApi) {
      // 以原子性的方式获取令牌,防止多进程同时获取 token
      const accessTokenGetMethod = getAccessToken();
      let accessToken = await queryCache(accessTokenGetMethod);
      if (!accessToken) {
        // 获取成功后将会缓存
        accessToken = await atomize(accessTokenGetMethod);
      }
      method.config.params.access_token = accessToken;
    }
  },
  l2Cache: redisAdapter,
});

const getAccessToken = () => thirdPartyAlova.Get('http://third-party.com/token', {
  params: {
    grant_type: 'client_credentials',
    client_id: process.env.THIRD_PARTY_CLIENT_ID,
    client_secret: process.env.THIRD_PARTY_CLIENT_SECRET
  },
  cacheFor: {
    mode: 'restore',
    expire: 1 * 3600 * 1000 // 两小时缓存时间
  }
});

const getThirdPartyUserInfo = userId => thirdPartyAlova.Get('http://third-party.com/user/info', {
  params: {
    userId
  },
  meta: {
    isThirdPartyApi: true
  }
});

写在最后

除此以外,alova 还提供了分布式的验证码发送和验证、请求重试等 server hooks ,想了解更多的同学可以参考服务端请求策略

如果觉得 alova 还不错,真诚希望你可以尝试体验一下,也可以给我们来一个免费的github stars

访问 alovajs 的官网查看更多详细信息:alovajs 官网

有兴趣可以加入我们的交流社区,在第一时间获取到最新进展,也能直接和开发团队交流,提出你的想法和建议。

有任何问题,你可以加入以下群聊咨询,也可以在github 仓库中发布 Discussions,如果遇到问题,也请在github 的 issues中提交,我们会在最快的时间解决。