从客户端到服务器:Alova 3 的全栈请求策略实践
Source: Dev.to
在 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';
// Create async local storage instance
const asyncLocalStorage = new AsyncLocalStorage();
const alovaInstance = createAlova({
requestAdapter: adapterFetch(),
beforeRequest(method) {
// Get request headers from async context and pass to downstream
const context = asyncLocalStorage.getStore();
if (context && context.headers) {
method.config.headers = {
...method.config.headers,
...context.headers,
};
}
},
responded: {
onSuccess(response) {
// Data transformation processing
return {
data: response.data,
timestamp: Date.now(),
transformed: true,
};
},
onError(error) {
console.error('Request failed:', error);
throw error;
},
},
});
const app = express();
// Set once in middleware, automatically passed throughout
app.use((req, res, next) => {
const context = {
userId: req.headers['x-user-id'],
token: req.headers['authorization'],
};
asyncLocalStorage.run(context, next);
});
// Business code focuses on business logic
app.get('/api/user-profile', async (req, res) => {
// No need to manually pass context anymore!
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 网关中的使用场景
在网关中,通常需要实现身份验证、请求限流和请求分发等功能。Alova 3 的 Redis 存储适配器和 rateLimiter 能够有效实现分布式身份验证服务和请求限流。
认证可以这样实现
如果认证令牌有过期时间,可以在网关中配置 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}`;
},
// Set L2 storage adapter
l2Cache: redisAdapter,
// ...
});
const authRequest = (token, userId) =>
gatewayAlova.Post('http://auth.com/auth/token', null, {
// Cache for 3 hours; subsequent requests with the same parameters will hit Redis
cacheFor: {
mode: 'restore',
expire: 3 * 3600 * 1000,
},
headers: {
'x-user-id': userId,
Authorization: `Bearer ${token}`,
},
});
const app = express;
// Forward all incoming requests to alova
const methods = [
'get',
'post',
'put',
'delete',
'patch',
'options',
'head',
];
methods.forEach((m) => {
app[m]('*', async (req, res) => {
const { method, originalUrl, headers, body, query } = req;
const response = await gatewayAlova.Request({
method: method.toLowerCase(),
url: originalUrl,
params: query,
data: body,
headers,
});
// Forward response headers
for (const [key, value] of response.headers.entries()) {
res.setHeader(key, value);
}
// Send response data
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({
/**
* Time for points reset, in ms
* @default 4000
*/
duration: 60 * 1000,
/**
* Maximum consumable quantity within duration
* @default 4
*/
points: 4,
/**
* Namespace, prevents conflicts when multiple rateLimits use the same storage
*/
keyPrefix: 'user-rate-limit',
/**
* Lock duration in ms; when the limit is reached, the block lasts this long.
*/
blockDuration: 24 * 60 * 60 * 1000,
});
const methods = [
'get',
'post',
'put',
'delete',
'patch',
'options',
'head',
];
methods.forEach((m) => {
app[m]('*', async (req, res) => {
const { method, originalUrl, headers, body, query } = req;
const alovaRequest = gatewayAlova.Request({
method: method.toLowerCase(),
url: originalUrl,
params: query,
data: body,
headers,
});
const response = await rateLimit(alovaRequest, {
key: req.ip, // Use IP as tracking key to prevent frequent requests from the same IP
});
// Forward response (similar to the previous example)
for (const [key, value] of response.headers.entries()) {
res.setHeader(key, value);
}
res.status(response.status).send(await response.json());
});
});
第三方服务集成:自动令牌维护
与外部 API 集成时,通常需要管理 access_token 的生命周期。结合 Alova 3、Redis 存储适配器以及 atom 钩子,可以实现分布式的访问令牌自动维护。
import { createAlova, queryCache, atom } from 'alova';
import RedisStorageAdapter from '@alova/storage-redis';
import adapterFetch from '@alova/fetch';
// Configure Redis for token caching
const redisAdapter = new RedisStorageAdapter({
host: 'localhost',
port: 6379,
});
const alovaInstance = createAlova({
requestAdapter: adapterFetch(),
l2Cache: redisAdapter,
});
// Atomic operation to refresh token only once across distributed instances
const refreshTokenAtom = atom(async (currentToken) => {
if (currentToken && Date.now() {
const res = await requestWithToken({
method: 'GET',
url: 'https://thirdparty.com/api/resource',
});
console.log(res.data);
})();