cloudflare下排除无用nodejs包
This commit is contained in:
+28
-7
@@ -2,7 +2,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
|
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
|
||||||
const webpack = require('webpack');
|
const path = require('path');
|
||||||
|
|
||||||
// 检测是否为 Cloudflare Pages 构建
|
// 检测是否为 Cloudflare Pages 构建
|
||||||
const isCloudflare = process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
|
const isCloudflare = process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
|
||||||
@@ -91,14 +91,35 @@ const createNextConfig = (phase) => {
|
|||||||
|
|
||||||
// Cloudflare 使用 D1,不需要把 better-sqlite3 原生模块带入 Worker 产物。
|
// Cloudflare 使用 D1,不需要把 better-sqlite3 原生模块带入 Worker 产物。
|
||||||
if (isCloudflare) {
|
if (isCloudflare) {
|
||||||
config.plugins.push(
|
|
||||||
new webpack.IgnorePlugin({
|
|
||||||
resourceRegExp: /^better-sqlite3$/,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
config.resolve.alias = {
|
config.resolve.alias = {
|
||||||
...config.resolve.alias,
|
...config.resolve.alias,
|
||||||
'better-sqlite3': false,
|
...Object.fromEntries(
|
||||||
|
[
|
||||||
|
'better-sqlite3',
|
||||||
|
'sharp',
|
||||||
|
'nodemailer',
|
||||||
|
'socket.io',
|
||||||
|
'redis',
|
||||||
|
'@vercel/postgres',
|
||||||
|
'pg',
|
||||||
|
].map((pkg) => [
|
||||||
|
pkg,
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'src/lib/cloudflare-shims/node-unsupported.ts'
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
// Cloudflare Workers 有原生 fetch;代理 Agent 在 Workers 中不可用。
|
||||||
|
// 用轻量 shim 替换 node-fetch / https-proxy-agent,避免把 Node HTTP 栈打入 Worker。
|
||||||
|
'node-fetch': path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'src/lib/cloudflare-shims/node-fetch.ts'
|
||||||
|
),
|
||||||
|
'https-proxy-agent': path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'src/lib/cloudflare-shims/https-proxy-agent.ts'
|
||||||
|
),
|
||||||
};
|
};
|
||||||
config.externals = (config.externals || []).filter((external) => {
|
config.externals = (config.externals || []).filter((external) => {
|
||||||
return !(
|
return !(
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm gen:manifest && node server.js",
|
"dev": "pnpm gen:manifest && node server.js",
|
||||||
"build": "pnpm gen:manifest && next build",
|
"build": "pnpm gen:manifest && next build",
|
||||||
"build:cloudflare": "BUILD_TARGET=cloudflare pnpm gen:manifest && npx @opennextjs/cloudflare build",
|
"build:cloudflare": "BUILD_TARGET=cloudflare pnpm gen:manifest && BUILD_TARGET=cloudflare npx @opennextjs/cloudflare build",
|
||||||
"start": "NODE_ENV=production node server.js",
|
"start": "NODE_ENV=production node server.js",
|
||||||
"preview:cloudflare": "wrangler dev",
|
"preview:cloudflare": "wrangler dev",
|
||||||
"deploy:cloudflare": "wrangler deploy",
|
"deploy:cloudflare": "wrangler deploy",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/* Cloudflare build shim: outbound proxy agents are not supported by Workers. */
|
||||||
|
|
||||||
|
export class HttpsProxyAgent {
|
||||||
|
constructor(..._args: unknown[]) {
|
||||||
|
throw new Error('HttpsProxyAgent is not supported in Cloudflare Workers');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* Cloudflare build shim: Workers provide standards-based fetch globally. */
|
||||||
|
|
||||||
|
export default function nodeFetch(
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init?: RequestInit
|
||||||
|
): Promise<Response> {
|
||||||
|
return fetch(input, init);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Headers = globalThis.Headers;
|
||||||
|
export const Request = globalThis.Request;
|
||||||
|
export const Response = globalThis.Response;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/* Cloudflare build shim for Node-only packages.
|
||||||
|
*
|
||||||
|
* This keeps unsupported Node dependencies out of the Worker bundle while
|
||||||
|
* failing loudly if a Node-only code path is actually executed on Workers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function unsupported(name = 'This Node-only package'): never {
|
||||||
|
throw new Error(`${name} is not supported in Cloudflare Workers`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createClient(): never {
|
||||||
|
return unsupported('redis');
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
constructor() {
|
||||||
|
unsupported('socket.io');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Socket {
|
||||||
|
constructor() {
|
||||||
|
unsupported('socket.io');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Pool {
|
||||||
|
constructor() {
|
||||||
|
unsupported('pg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sql = new Proxy(() => undefined, {
|
||||||
|
apply: () => unsupported('@vercel/postgres'),
|
||||||
|
get: () => unsupported('@vercel/postgres'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const shim: any = new Proxy(
|
||||||
|
function nodeUnsupportedDefault() {
|
||||||
|
unsupported();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apply: () => unsupported(),
|
||||||
|
construct: () => unsupported(),
|
||||||
|
get: (_target, prop) => {
|
||||||
|
if (prop === 'createTransport') {
|
||||||
|
return () => unsupported('nodemailer');
|
||||||
|
}
|
||||||
|
if (prop === 'default') {
|
||||||
|
return shim;
|
||||||
|
}
|
||||||
|
return () => unsupported(String(prop));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default shim;
|
||||||
+15
-3
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { AdminConfig } from './admin.types';
|
import { AdminConfig } from './admin.types';
|
||||||
import { MusicPlayRecord } from './db.client';
|
import { MusicPlayRecord } from './db.client';
|
||||||
import { KvrocksStorage } from './kvrocks.db';
|
|
||||||
import { MangaReadRecord, MangaShelfItem } from './manga.types';
|
import { MangaReadRecord, MangaShelfItem } from './manga.types';
|
||||||
import { BookReadRecord, BookShelfItem } from './book.types';
|
import { BookReadRecord, BookShelfItem } from './book.types';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
MusicV2PlaylistItem,
|
MusicV2PlaylistItem,
|
||||||
MusicV2PlaylistRecord,
|
MusicV2PlaylistRecord,
|
||||||
} from './music-v2';
|
} from './music-v2';
|
||||||
import { RedisStorage } from './redis.db';
|
|
||||||
import {
|
import {
|
||||||
DanmakuFilterConfig,
|
DanmakuFilterConfig,
|
||||||
Favorite,
|
Favorite,
|
||||||
@@ -18,9 +16,10 @@ import {
|
|||||||
PlayRecord,
|
PlayRecord,
|
||||||
SkipConfig,
|
SkipConfig,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { UpstashRedisStorage } from './upstash.db';
|
|
||||||
|
|
||||||
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1' | 'postgres',默认 'localstorage'
|
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1' | 'postgres',默认 'localstorage'
|
||||||
|
const IS_CLOUDFLARE_BUILD =
|
||||||
|
process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
|
||||||
const STORAGE_TYPE =
|
const STORAGE_TYPE =
|
||||||
(process.env.NEXT_PUBLIC_STORAGE_TYPE as
|
(process.env.NEXT_PUBLIC_STORAGE_TYPE as
|
||||||
| 'localstorage'
|
| 'localstorage'
|
||||||
@@ -35,10 +34,23 @@ const STORAGE_TYPE =
|
|||||||
function createStorage(): IStorage {
|
function createStorage(): IStorage {
|
||||||
switch (STORAGE_TYPE) {
|
switch (STORAGE_TYPE) {
|
||||||
case 'redis':
|
case 'redis':
|
||||||
|
if (IS_CLOUDFLARE_BUILD) {
|
||||||
|
throw new Error(
|
||||||
|
'Node Redis storage is not supported in Cloudflare builds. Use D1 or Upstash instead.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { RedisStorage } = require('./redis.db');
|
||||||
return new RedisStorage();
|
return new RedisStorage();
|
||||||
case 'upstash':
|
case 'upstash':
|
||||||
|
const { UpstashRedisStorage } = require('./upstash.db');
|
||||||
return new UpstashRedisStorage();
|
return new UpstashRedisStorage();
|
||||||
case 'kvrocks':
|
case 'kvrocks':
|
||||||
|
if (IS_CLOUDFLARE_BUILD) {
|
||||||
|
throw new Error(
|
||||||
|
'Kvrocks storage is not supported in Cloudflare builds. Use D1 or Upstash instead.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { KvrocksStorage } = require('./kvrocks.db');
|
||||||
return new KvrocksStorage();
|
return new KvrocksStorage();
|
||||||
case 'd1':
|
case 'd1':
|
||||||
// D1Storage 只能在服务端使用,客户端会报错
|
// D1Storage 只能在服务端使用,客户端会报错
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import { StandardRedisAdapter } from './redis-adapter';
|
import { StandardRedisAdapter } from './redis-adapter';
|
||||||
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
|
import { BaseRedisStorage } from './redis-base.db';
|
||||||
|
import { createRedisClient, createRetryWrapper } from './redis-node-client';
|
||||||
|
|
||||||
export class KvrocksStorage extends BaseRedisStorage {
|
export class KvrocksStorage extends BaseRedisStorage {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import { Redis } from '@upstash/redis';
|
import { Redis } from '@upstash/redis';
|
||||||
import { RedisClientType } from 'redis';
|
import type { RedisClientType } from 'redis';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一的 Redis 适配器接口
|
* 统一的 Redis 适配器接口
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import { createClient, RedisClientType } from 'redis';
|
|
||||||
|
|
||||||
import { AdminConfig } from './admin.types';
|
import { AdminConfig } from './admin.types';
|
||||||
import { MangaReadRecord, MangaShelfItem } from './manga.types';
|
import { MangaReadRecord, MangaShelfItem } from './manga.types';
|
||||||
import { BookReadRecord, BookShelfItem } from './book.types';
|
import { BookReadRecord, BookShelfItem } from './book.types';
|
||||||
@@ -30,141 +28,6 @@ function ensureStringArray(value: any[]): string[] {
|
|||||||
// 内存锁:用于防止同一用户的并发播放记录操作(迁移、清理等)
|
// 内存锁:用于防止同一用户的并发播放记录操作(迁移、清理等)
|
||||||
const playRecordLocks = new Map<string, Promise<void>>();
|
const playRecordLocks = new Map<string, Promise<void>>();
|
||||||
|
|
||||||
// 连接配置接口
|
|
||||||
export interface RedisConnectionConfig {
|
|
||||||
url: string;
|
|
||||||
clientName: string; // 用于日志显示,如 "Redis" 或 "Pika"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加Redis操作重试包装器
|
|
||||||
export function createRetryWrapper(
|
|
||||||
clientName: string,
|
|
||||||
getClient: () => RedisClientType
|
|
||||||
) {
|
|
||||||
return async function withRetry<T>(
|
|
||||||
operation: () => Promise<T>,
|
|
||||||
maxRetries = 3
|
|
||||||
): Promise<T> {
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
|
||||||
try {
|
|
||||||
return await operation();
|
|
||||||
} catch (err: any) {
|
|
||||||
const isLastAttempt = i === maxRetries - 1;
|
|
||||||
const isConnectionError =
|
|
||||||
err.message?.includes('Connection') ||
|
|
||||||
err.message?.includes('ECONNREFUSED') ||
|
|
||||||
err.message?.includes('ENOTFOUND') ||
|
|
||||||
err.code === 'ECONNRESET' ||
|
|
||||||
err.code === 'EPIPE';
|
|
||||||
|
|
||||||
if (isConnectionError && !isLastAttempt) {
|
|
||||||
console.log(
|
|
||||||
`${clientName} operation failed, retrying... (${
|
|
||||||
i + 1
|
|
||||||
}/${maxRetries})`
|
|
||||||
);
|
|
||||||
console.error('Error:', err.message);
|
|
||||||
|
|
||||||
// 等待一段时间后重试
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
|
|
||||||
|
|
||||||
// 尝试重新连接
|
|
||||||
try {
|
|
||||||
const client = getClient();
|
|
||||||
if (!client.isOpen) {
|
|
||||||
await client.connect();
|
|
||||||
}
|
|
||||||
} catch (reconnectErr) {
|
|
||||||
console.error('Failed to reconnect:', reconnectErr);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Max retries exceeded');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建客户端的工厂函数
|
|
||||||
export function createRedisClient(
|
|
||||||
config: RedisConnectionConfig,
|
|
||||||
globalSymbol: symbol
|
|
||||||
): RedisClientType {
|
|
||||||
let client: RedisClientType | undefined = (global as any)[globalSymbol];
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
if (!config.url) {
|
|
||||||
throw new Error(`${config.clientName}_URL env variable not set`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建客户端配置
|
|
||||||
const clientConfig: any = {
|
|
||||||
url: config.url,
|
|
||||||
socket: {
|
|
||||||
// 重连策略:指数退避,最大30秒
|
|
||||||
reconnectStrategy: (retries: number) => {
|
|
||||||
console.log(
|
|
||||||
`${config.clientName} reconnection attempt ${retries + 1}`
|
|
||||||
);
|
|
||||||
if (retries > 10) {
|
|
||||||
console.error(
|
|
||||||
`${config.clientName} max reconnection attempts exceeded`
|
|
||||||
);
|
|
||||||
return false; // 停止重连
|
|
||||||
}
|
|
||||||
return Math.min(1000 * Math.pow(2, retries), 30000); // 指数退避,最大30秒
|
|
||||||
},
|
|
||||||
connectTimeout: 10000, // 10秒连接超时
|
|
||||||
// 设置no delay,减少延迟
|
|
||||||
noDelay: true,
|
|
||||||
},
|
|
||||||
// 添加其他配置
|
|
||||||
pingInterval: 30000, // 30秒ping一次,保持连接活跃
|
|
||||||
};
|
|
||||||
|
|
||||||
client = createClient(clientConfig);
|
|
||||||
|
|
||||||
// 添加错误事件监听
|
|
||||||
client.on('error', (err) => {
|
|
||||||
console.error(`${config.clientName} client error:`, err);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('connect', () => {
|
|
||||||
console.log(`${config.clientName} connected`);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('reconnecting', () => {
|
|
||||||
console.log(`${config.clientName} reconnecting...`);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('ready', () => {
|
|
||||||
console.log(`${config.clientName} ready`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始连接,带重试机制
|
|
||||||
const connectWithRetry = async () => {
|
|
||||||
try {
|
|
||||||
await client!.connect();
|
|
||||||
console.log(`${config.clientName} connected successfully`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`${config.clientName} initial connection failed:`, err);
|
|
||||||
console.log('Will retry in 5 seconds...');
|
|
||||||
setTimeout(connectWithRetry, 5000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
connectWithRetry();
|
|
||||||
|
|
||||||
(global as any)[globalSymbol] = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 抽象基类,包含所有通用的Redis操作逻辑
|
// 抽象基类,包含所有通用的Redis操作逻辑
|
||||||
export abstract class BaseRedisStorage implements IStorage {
|
export abstract class BaseRedisStorage implements IStorage {
|
||||||
protected adapter: RedisAdapter;
|
protected adapter: RedisAdapter;
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { createClient, RedisClientType } from 'redis';
|
||||||
|
|
||||||
|
// 连接配置接口
|
||||||
|
export interface RedisConnectionConfig {
|
||||||
|
url: string;
|
||||||
|
clientName: string; // 用于日志显示,如 "Redis" 或 "Pika"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 Redis 操作重试包装器。这个文件只给 Node Redis/Kvrocks 使用,
|
||||||
|
// 避免 Cloudflare/D1/Upstash 构建因为 BaseRedisStorage 引入 node-redis。
|
||||||
|
export function createRetryWrapper(
|
||||||
|
clientName: string,
|
||||||
|
getClient: () => RedisClientType
|
||||||
|
) {
|
||||||
|
return async function withRetry<T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
maxRetries = 3
|
||||||
|
): Promise<T> {
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} catch (err: any) {
|
||||||
|
const isLastAttempt = i === maxRetries - 1;
|
||||||
|
const isConnectionError =
|
||||||
|
err.message?.includes('Connection') ||
|
||||||
|
err.message?.includes('ECONNREFUSED') ||
|
||||||
|
err.message?.includes('ENOTFOUND') ||
|
||||||
|
err.code === 'ECONNRESET' ||
|
||||||
|
err.code === 'EPIPE';
|
||||||
|
|
||||||
|
if (isConnectionError && !isLastAttempt) {
|
||||||
|
console.log(
|
||||||
|
`${clientName} operation failed, retrying... (${
|
||||||
|
i + 1
|
||||||
|
}/${maxRetries})`
|
||||||
|
);
|
||||||
|
console.error('Error:', err.message);
|
||||||
|
|
||||||
|
// 等待一段时间后重试
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
|
||||||
|
|
||||||
|
// 尝试重新连接
|
||||||
|
try {
|
||||||
|
const client = getClient();
|
||||||
|
if (!client.isOpen) {
|
||||||
|
await client.connect();
|
||||||
|
}
|
||||||
|
} catch (reconnectErr) {
|
||||||
|
console.error('Failed to reconnect:', reconnectErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Max retries exceeded');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建客户端的工厂函数
|
||||||
|
export function createRedisClient(
|
||||||
|
config: RedisConnectionConfig,
|
||||||
|
globalSymbol: symbol
|
||||||
|
): RedisClientType {
|
||||||
|
let client: RedisClientType | undefined = (global as any)[globalSymbol];
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
if (!config.url) {
|
||||||
|
throw new Error(`${config.clientName}_URL env variable not set`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建客户端配置
|
||||||
|
const clientConfig: any = {
|
||||||
|
url: config.url,
|
||||||
|
socket: {
|
||||||
|
// 重连策略:指数退避,最大30秒
|
||||||
|
reconnectStrategy: (retries: number) => {
|
||||||
|
console.log(
|
||||||
|
`${config.clientName} reconnection attempt ${retries + 1}`
|
||||||
|
);
|
||||||
|
if (retries > 10) {
|
||||||
|
console.error(
|
||||||
|
`${config.clientName} max reconnection attempts exceeded`
|
||||||
|
);
|
||||||
|
return false; // 停止重连
|
||||||
|
}
|
||||||
|
return Math.min(1000 * Math.pow(2, retries), 30000); // 指数退避,最大30秒
|
||||||
|
},
|
||||||
|
connectTimeout: 10000, // 10秒连接超时
|
||||||
|
// 设置 no delay,减少延迟
|
||||||
|
noDelay: true,
|
||||||
|
},
|
||||||
|
// 添加其他配置
|
||||||
|
pingInterval: 30000, // 30秒 ping 一次,保持连接活跃
|
||||||
|
};
|
||||||
|
|
||||||
|
client = createClient(clientConfig);
|
||||||
|
|
||||||
|
// 添加错误事件监听
|
||||||
|
client.on('error', (err) => {
|
||||||
|
console.error(`${config.clientName} client error:`, err);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('connect', () => {
|
||||||
|
console.log(`${config.clientName} connected`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('reconnecting', () => {
|
||||||
|
console.log(`${config.clientName} reconnecting...`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('ready', () => {
|
||||||
|
console.log(`${config.clientName} ready`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始连接,带重试机制
|
||||||
|
const connectWithRetry = async () => {
|
||||||
|
try {
|
||||||
|
await client!.connect();
|
||||||
|
console.log(`${config.clientName} connected successfully`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`${config.clientName} initial connection failed:`, err);
|
||||||
|
console.log('Will retry in 5 seconds...');
|
||||||
|
setTimeout(connectWithRetry, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connectWithRetry();
|
||||||
|
|
||||||
|
(global as any)[globalSymbol] = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import { StandardRedisAdapter } from './redis-adapter';
|
import { StandardRedisAdapter } from './redis-adapter';
|
||||||
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
|
import { BaseRedisStorage } from './redis-base.db';
|
||||||
|
import { createRedisClient, createRetryWrapper } from './redis-node-client';
|
||||||
|
|
||||||
export class RedisStorage extends BaseRedisStorage {
|
export class RedisStorage extends BaseRedisStorage {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
Reference in New Issue
Block a user