@bililive-tools/douyin-recorder 1.5.3 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/lib/douyin_api.d.ts +10 -1
- package/lib/douyin_api.js +346 -28
- package/lib/index.js +46 -14
- package/lib/loadBalancer/example.d.ts +2 -0
- package/lib/loadBalancer/example.js +130 -0
- package/lib/loadBalancer/loadBalancer.d.ts +71 -0
- package/lib/loadBalancer/loadBalancer.js +196 -0
- package/lib/loadBalancer/loadBalancerManager.d.ts +75 -0
- package/lib/loadBalancer/loadBalancerManager.js +127 -0
- package/lib/stream.d.ts +12 -1
- package/lib/stream.js +22 -3
- package/lib/types.d.ts +33 -0
- package/lib/types.js +1 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +67 -0
- package/package.json +3 -3
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 抖音录播器负载均衡使用示例
|
|
3
|
+
*/
|
|
4
|
+
import { getInfo } from "../stream.js";
|
|
5
|
+
import { loadBalancer } from "./loadBalancerManager.js";
|
|
6
|
+
async function basicExample() {
|
|
7
|
+
console.log("=== 基本使用示例 ===");
|
|
8
|
+
try {
|
|
9
|
+
// 使用负载均衡模式获取房间信息
|
|
10
|
+
const roomInfo = await getInfo("测试房间ID", {
|
|
11
|
+
api: "balance", // 关键:使用balance模式启用负载均衡
|
|
12
|
+
cookie: "your-cookie-here",
|
|
13
|
+
});
|
|
14
|
+
console.log("获取到房间信息:", {
|
|
15
|
+
title: roomInfo.title,
|
|
16
|
+
owner: roomInfo.owner,
|
|
17
|
+
living: roomInfo.living,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error("获取房间信息失败:", error.message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function managementExample() {
|
|
25
|
+
console.log("\n=== 管理功能示例 ===");
|
|
26
|
+
// 1. 查看当前状态
|
|
27
|
+
console.log("1. 当前负载均衡器状态:");
|
|
28
|
+
loadBalancer.printStatus();
|
|
29
|
+
// 2. 检查API健康状态
|
|
30
|
+
console.log("\n2. API健康状态检查:");
|
|
31
|
+
const apis = ["web", "webHTML", "mobile", "userHTML"];
|
|
32
|
+
apis.forEach((api) => {
|
|
33
|
+
const isHealthy = loadBalancer.isAPIHealthy(api);
|
|
34
|
+
console.log(`${api}: ${isHealthy ? "✅ 健康" : "❌ 不健康"}`);
|
|
35
|
+
});
|
|
36
|
+
// 3. 获取推荐API
|
|
37
|
+
const recommendedAPI = loadBalancer.getRecommendedAPI();
|
|
38
|
+
console.log(`\n3. 推荐使用的API: ${recommendedAPI}`);
|
|
39
|
+
// 4. 配置调整示例
|
|
40
|
+
console.log("\n4. 配置调整示例:");
|
|
41
|
+
console.log("原始配置:", loadBalancer.getConfig());
|
|
42
|
+
// 调整配置:更保守的失败策略
|
|
43
|
+
loadBalancer.updateConfig({
|
|
44
|
+
maxFailures: 2, // 减少到2次失败就禁用
|
|
45
|
+
blockDuration: 120000, // 禁用2分钟
|
|
46
|
+
});
|
|
47
|
+
console.log("调整后配置:", loadBalancer.getConfig());
|
|
48
|
+
// 5. API权重调整示例
|
|
49
|
+
console.log("\n5. API权重调整示例:");
|
|
50
|
+
console.log("调整前状态:");
|
|
51
|
+
console.table(loadBalancer.getStatus());
|
|
52
|
+
// 提高mobile接口的优先级和权重
|
|
53
|
+
loadBalancer.updateAPIConfig("mobile", {
|
|
54
|
+
priority: 1, // 最高优先级
|
|
55
|
+
weight: 5, // 最高权重
|
|
56
|
+
});
|
|
57
|
+
console.log("调整mobile接口权重后:");
|
|
58
|
+
console.table(loadBalancer.getStatus());
|
|
59
|
+
}
|
|
60
|
+
async function errorHandlingExample() {
|
|
61
|
+
console.log("\n=== 错误处理示例 ===");
|
|
62
|
+
// 模拟检查被禁用的API
|
|
63
|
+
const blockedAPIs = loadBalancer.getBlockedAPIs();
|
|
64
|
+
if (blockedAPIs.length > 0) {
|
|
65
|
+
console.log(`发现 ${blockedAPIs.length} 个被禁用的API:`, blockedAPIs);
|
|
66
|
+
// 可以选择重置这些API
|
|
67
|
+
console.log("重置被禁用的API...");
|
|
68
|
+
blockedAPIs.forEach((api) => {
|
|
69
|
+
loadBalancer.resetAPI(api);
|
|
70
|
+
});
|
|
71
|
+
console.log("重置完成,当前健康的API:", loadBalancer.getHealthyAPIs());
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log("所有API都处于健康状态 ✅");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function monitoringExample() {
|
|
78
|
+
console.log("\n=== 监控示例 ===");
|
|
79
|
+
// 模拟监控函数
|
|
80
|
+
function checkSystemHealth() {
|
|
81
|
+
const status = loadBalancer.getStatus();
|
|
82
|
+
const healthyCount = status.filter((s) => !s.isBlocked).length;
|
|
83
|
+
const totalCount = status.length;
|
|
84
|
+
console.log(`系统健康状态: ${healthyCount}/${totalCount} 个API可用`);
|
|
85
|
+
if (healthyCount < totalCount / 2) {
|
|
86
|
+
console.warn("⚠️ 警告:超过一半的API不可用!");
|
|
87
|
+
loadBalancer.printStatus();
|
|
88
|
+
// 可以触发告警或自动恢复逻辑
|
|
89
|
+
console.log("执行自动恢复...");
|
|
90
|
+
loadBalancer.resetAll();
|
|
91
|
+
}
|
|
92
|
+
// 显示详细状态
|
|
93
|
+
status.forEach((s) => {
|
|
94
|
+
if (s.isBlocked) {
|
|
95
|
+
const retryTime = new Date(s.nextRetryTime).toLocaleString();
|
|
96
|
+
console.log(`❌ ${s.api}: 被禁用,下次重试时间: ${retryTime}`);
|
|
97
|
+
}
|
|
98
|
+
else if (s.failureCount > 0) {
|
|
99
|
+
console.log(`⚠️ ${s.api}: 失败 ${s.failureCount} 次,仍可用`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(`✅ ${s.api}: 健康`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// 执行健康检查
|
|
107
|
+
checkSystemHealth();
|
|
108
|
+
// 在实际应用中,可以设置定时器
|
|
109
|
+
// setInterval(checkSystemHealth, 60000); // 每分钟检查一次
|
|
110
|
+
}
|
|
111
|
+
// 主函数
|
|
112
|
+
async function runExamples() {
|
|
113
|
+
try {
|
|
114
|
+
await basicExample();
|
|
115
|
+
await managementExample();
|
|
116
|
+
await errorHandlingExample();
|
|
117
|
+
await monitoringExample();
|
|
118
|
+
console.log("\n=== 示例执行完成 ===");
|
|
119
|
+
console.log("\n最终状态:");
|
|
120
|
+
loadBalancer.printStatus();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error("示例执行失败:", error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// 如果直接运行此文件
|
|
127
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
128
|
+
runExamples();
|
|
129
|
+
}
|
|
130
|
+
export { runExamples };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { APIType, APIEndpoint, APIEndpointStatus, LoadBalancerConfig } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* API 负载均衡器类
|
|
4
|
+
* 实现多个 API 接口的负载均衡调用,具备失败重试和禁用机制
|
|
5
|
+
*/
|
|
6
|
+
export declare class APILoadBalancer {
|
|
7
|
+
private endpoints;
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config?: Partial<LoadBalancerConfig>);
|
|
10
|
+
/**
|
|
11
|
+
* 初始化 API 端点配置
|
|
12
|
+
*/
|
|
13
|
+
private initializeEndpoints;
|
|
14
|
+
/**
|
|
15
|
+
* 获取下一个可用的 API 端点
|
|
16
|
+
* 使用加权轮询算法,优先选择权重高且未被禁用的端点
|
|
17
|
+
*/
|
|
18
|
+
private getNextEndpoint;
|
|
19
|
+
/**
|
|
20
|
+
* 记录 API 调用失败
|
|
21
|
+
*/
|
|
22
|
+
private recordFailure;
|
|
23
|
+
/**
|
|
24
|
+
* 记录 API 调用成功
|
|
25
|
+
*/
|
|
26
|
+
private recordSuccess;
|
|
27
|
+
/**
|
|
28
|
+
* 使用负载均衡策略调用 getRoomInfo
|
|
29
|
+
*/
|
|
30
|
+
callWithLoadBalance(webRoomId: string, opts?: {
|
|
31
|
+
auth?: string;
|
|
32
|
+
doubleScreen?: boolean;
|
|
33
|
+
uid?: string | number;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
living: boolean;
|
|
36
|
+
roomId: string;
|
|
37
|
+
owner: string;
|
|
38
|
+
title: string;
|
|
39
|
+
streams: any[];
|
|
40
|
+
sources: any[];
|
|
41
|
+
avatar: string;
|
|
42
|
+
cover: string;
|
|
43
|
+
liveId: string;
|
|
44
|
+
uid: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* 获取当前端点状态(用于调试和监控)
|
|
48
|
+
*/
|
|
49
|
+
getEndpointStatus(): APIEndpointStatus[];
|
|
50
|
+
/**
|
|
51
|
+
* 手动重置某个端点的状态
|
|
52
|
+
*/
|
|
53
|
+
resetEndpoint(apiType: APIType): void;
|
|
54
|
+
/**
|
|
55
|
+
* 重置所有端点状态
|
|
56
|
+
*/
|
|
57
|
+
resetAllEndpoints(): void;
|
|
58
|
+
/**
|
|
59
|
+
* 更新端点配置
|
|
60
|
+
*/
|
|
61
|
+
updateEndpointConfig(apiType: APIType, updates: Partial<APIEndpoint>): void;
|
|
62
|
+
/**
|
|
63
|
+
* 获取负载均衡器配置
|
|
64
|
+
*/
|
|
65
|
+
getConfig(): LoadBalancerConfig;
|
|
66
|
+
/**
|
|
67
|
+
* 更新负载均衡器配置
|
|
68
|
+
*/
|
|
69
|
+
updateConfig(updates: Partial<LoadBalancerConfig>): void;
|
|
70
|
+
}
|
|
71
|
+
export declare const globalLoadBalancer: APILoadBalancer;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { getRoomInfo } from "../douyin_api.js";
|
|
2
|
+
/**
|
|
3
|
+
* API 负载均衡器类
|
|
4
|
+
* 实现多个 API 接口的负载均衡调用,具备失败重试和禁用机制
|
|
5
|
+
*/
|
|
6
|
+
export class APILoadBalancer {
|
|
7
|
+
endpoints = [];
|
|
8
|
+
config;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = {
|
|
11
|
+
maxFailures: 3, // 连续失败3次后禁用
|
|
12
|
+
blockDuration: 3 * 60 * 1000, // 禁用3分钟
|
|
13
|
+
retryMultiplier: 1.5, // 重试时间倍增
|
|
14
|
+
healthCheckInterval: 30 * 1000, // 30秒健康检查
|
|
15
|
+
...config,
|
|
16
|
+
};
|
|
17
|
+
// 初始化可用的 API 端点
|
|
18
|
+
this.initializeEndpoints();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 初始化 API 端点配置
|
|
22
|
+
*/
|
|
23
|
+
initializeEndpoints() {
|
|
24
|
+
const defaultEndpoints = [
|
|
25
|
+
{ name: "web", priority: 2, weight: 1 },
|
|
26
|
+
{ name: "webHTML", priority: 1, weight: 1 },
|
|
27
|
+
{ name: "mobile", priority: 6, weight: 1 },
|
|
28
|
+
{ name: "userHTML", priority: 4, weight: 1 },
|
|
29
|
+
];
|
|
30
|
+
this.endpoints = defaultEndpoints.map((endpoint) => ({
|
|
31
|
+
endpoint,
|
|
32
|
+
failureCount: 0,
|
|
33
|
+
lastFailureTime: 0,
|
|
34
|
+
isBlocked: false,
|
|
35
|
+
nextRetryTime: 0,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 获取下一个可用的 API 端点
|
|
40
|
+
* 使用加权轮询算法,优先选择权重高且未被禁用的端点
|
|
41
|
+
*/
|
|
42
|
+
getNextEndpoint() {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
// 清理过期的禁用状态
|
|
45
|
+
this.endpoints.forEach((status) => {
|
|
46
|
+
if (status.isBlocked && now >= status.nextRetryTime) {
|
|
47
|
+
status.isBlocked = false;
|
|
48
|
+
status.failureCount = Math.max(0, status.failureCount - 1); // 部分恢复
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// 获取可用的端点
|
|
52
|
+
const availableEndpoints = this.endpoints.filter((status) => !status.isBlocked);
|
|
53
|
+
if (availableEndpoints.length === 0) {
|
|
54
|
+
return null; // 所有端点都被禁用
|
|
55
|
+
}
|
|
56
|
+
// 按优先级和权重排序
|
|
57
|
+
availableEndpoints.sort((a, b) => {
|
|
58
|
+
if (a.endpoint.priority !== b.endpoint.priority) {
|
|
59
|
+
return a.endpoint.priority - b.endpoint.priority; // 优先级越小越好
|
|
60
|
+
}
|
|
61
|
+
return b.endpoint.weight - a.endpoint.weight; // 权重越大越好
|
|
62
|
+
});
|
|
63
|
+
// 使用加权随机选择
|
|
64
|
+
const totalWeight = availableEndpoints.reduce((sum, status) => sum + status.endpoint.weight, 0);
|
|
65
|
+
const random = Math.random() * totalWeight;
|
|
66
|
+
let currentWeight = 0;
|
|
67
|
+
for (const status of availableEndpoints) {
|
|
68
|
+
currentWeight += status.endpoint.weight;
|
|
69
|
+
if (random <= currentWeight) {
|
|
70
|
+
return status;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 如果加权选择失败,返回第一个可用的
|
|
74
|
+
return availableEndpoints[0];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 记录 API 调用失败
|
|
78
|
+
*/
|
|
79
|
+
recordFailure(apiType, error) {
|
|
80
|
+
const status = this.endpoints.find((s) => s.endpoint.name === apiType);
|
|
81
|
+
if (!status)
|
|
82
|
+
return;
|
|
83
|
+
status.failureCount++;
|
|
84
|
+
status.lastFailureTime = Date.now();
|
|
85
|
+
// 如果失败次数超过阈值,禁用该端点
|
|
86
|
+
if (status.failureCount >= this.config.maxFailures) {
|
|
87
|
+
status.isBlocked = true;
|
|
88
|
+
const blockDuration = this.config.blockDuration *
|
|
89
|
+
Math.pow(this.config.retryMultiplier, status.failureCount - this.config.maxFailures);
|
|
90
|
+
status.nextRetryTime = Date.now() + blockDuration;
|
|
91
|
+
console.warn(`API ${apiType} has been blocked due to ${status.failureCount} failures. Next retry at: ${(new Date(status.nextRetryTime).toISOString(), error.message)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 记录 API 调用成功
|
|
96
|
+
*/
|
|
97
|
+
recordSuccess(apiType) {
|
|
98
|
+
const status = this.endpoints.find((s) => s.endpoint.name === apiType);
|
|
99
|
+
if (!status)
|
|
100
|
+
return;
|
|
101
|
+
// 成功调用后,减少失败计数
|
|
102
|
+
if (status.failureCount > 0) {
|
|
103
|
+
status.failureCount = Math.max(0, status.failureCount - 1);
|
|
104
|
+
}
|
|
105
|
+
// 如果之前被禁用,现在可以恢复
|
|
106
|
+
if (status.isBlocked && status.failureCount === 0) {
|
|
107
|
+
status.isBlocked = false;
|
|
108
|
+
status.nextRetryTime = 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 使用负载均衡策略调用 getRoomInfo
|
|
113
|
+
*/
|
|
114
|
+
async callWithLoadBalance(webRoomId, opts = {}) {
|
|
115
|
+
const maxAttempts = this.endpoints.length;
|
|
116
|
+
let lastError = null;
|
|
117
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
118
|
+
const endpointStatus = this.getNextEndpoint();
|
|
119
|
+
if (!endpointStatus) {
|
|
120
|
+
throw new Error("所有 API 端点都不可用,请稍后重试");
|
|
121
|
+
}
|
|
122
|
+
const apiType = endpointStatus.endpoint.name;
|
|
123
|
+
try {
|
|
124
|
+
const result = await getRoomInfo(webRoomId, {
|
|
125
|
+
...opts,
|
|
126
|
+
api: apiType,
|
|
127
|
+
});
|
|
128
|
+
// 调用成功,记录成功状态
|
|
129
|
+
this.recordSuccess(apiType);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
lastError = error;
|
|
134
|
+
this.recordFailure(apiType, lastError);
|
|
135
|
+
console.warn(`API ${apiType} failed (attempt ${attempt + 1}/${maxAttempts}):`, lastError.message);
|
|
136
|
+
// 如果这是最后一次尝试,或者没有更多可用端点,则抛出错误
|
|
137
|
+
if (attempt === maxAttempts - 1) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`所有 API 调用都失败了。最后一个错误: ${lastError?.message || "未知错误"}`);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 获取当前端点状态(用于调试和监控)
|
|
146
|
+
*/
|
|
147
|
+
getEndpointStatus() {
|
|
148
|
+
return this.endpoints.map((status) => ({ ...status }));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 手动重置某个端点的状态
|
|
152
|
+
*/
|
|
153
|
+
resetEndpoint(apiType) {
|
|
154
|
+
const status = this.endpoints.find((s) => s.endpoint.name === apiType);
|
|
155
|
+
if (status) {
|
|
156
|
+
status.failureCount = 0;
|
|
157
|
+
status.lastFailureTime = 0;
|
|
158
|
+
status.isBlocked = false;
|
|
159
|
+
status.nextRetryTime = 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 重置所有端点状态
|
|
164
|
+
*/
|
|
165
|
+
resetAllEndpoints() {
|
|
166
|
+
this.endpoints.forEach((status) => {
|
|
167
|
+
status.failureCount = 0;
|
|
168
|
+
status.lastFailureTime = 0;
|
|
169
|
+
status.isBlocked = false;
|
|
170
|
+
status.nextRetryTime = 0;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 更新端点配置
|
|
175
|
+
*/
|
|
176
|
+
updateEndpointConfig(apiType, updates) {
|
|
177
|
+
const status = this.endpoints.find((s) => s.endpoint.name === apiType);
|
|
178
|
+
if (status) {
|
|
179
|
+
Object.assign(status.endpoint, updates);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 获取负载均衡器配置
|
|
184
|
+
*/
|
|
185
|
+
getConfig() {
|
|
186
|
+
return { ...this.config };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 更新负载均衡器配置
|
|
190
|
+
*/
|
|
191
|
+
updateConfig(updates) {
|
|
192
|
+
Object.assign(this.config, updates);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// 创建全局单例实例
|
|
196
|
+
export const globalLoadBalancer = new APILoadBalancer();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { APIType, LoadBalancerConfig } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 负载均衡器管理工具类
|
|
4
|
+
* 提供简化的接口来管理和配置负载均衡器
|
|
5
|
+
*/
|
|
6
|
+
export declare class LoadBalancerManager {
|
|
7
|
+
/**
|
|
8
|
+
* 获取所有端点的当前状态
|
|
9
|
+
*/
|
|
10
|
+
static getStatus(): {
|
|
11
|
+
api: APIType;
|
|
12
|
+
priority: number;
|
|
13
|
+
weight: number;
|
|
14
|
+
failureCount: number;
|
|
15
|
+
isBlocked: boolean;
|
|
16
|
+
lastFailureTime: string;
|
|
17
|
+
nextRetryTime: string;
|
|
18
|
+
}[];
|
|
19
|
+
/**
|
|
20
|
+
* 重置指定 API 的状态
|
|
21
|
+
*/
|
|
22
|
+
static resetAPI(apiType: APIType): void;
|
|
23
|
+
/**
|
|
24
|
+
* 重置所有 API 的状态
|
|
25
|
+
*/
|
|
26
|
+
static resetAll(): void;
|
|
27
|
+
/**
|
|
28
|
+
* 更新 API 端点的配置
|
|
29
|
+
*/
|
|
30
|
+
static updateAPIConfig(apiType: APIType, config: {
|
|
31
|
+
priority?: number;
|
|
32
|
+
weight?: number;
|
|
33
|
+
}): void;
|
|
34
|
+
/**
|
|
35
|
+
* 获取负载均衡器配置
|
|
36
|
+
*/
|
|
37
|
+
static getConfig(): LoadBalancerConfig;
|
|
38
|
+
/**
|
|
39
|
+
* 更新负载均衡器配置
|
|
40
|
+
*/
|
|
41
|
+
static updateConfig(config: Partial<LoadBalancerConfig>): void;
|
|
42
|
+
/**
|
|
43
|
+
* 获取健康的(未被禁用的)API 列表
|
|
44
|
+
*/
|
|
45
|
+
static getHealthyAPIs(): APIType[];
|
|
46
|
+
/**
|
|
47
|
+
* 获取被禁用的 API 列表
|
|
48
|
+
*/
|
|
49
|
+
static getBlockedAPIs(): APIType[];
|
|
50
|
+
/**
|
|
51
|
+
* 检查特定 API 是否可用
|
|
52
|
+
*/
|
|
53
|
+
static isAPIHealthy(apiType: APIType): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 获取推荐使用的 API(基于当前状态和权重)
|
|
56
|
+
*/
|
|
57
|
+
static getRecommendedAPI(): APIType | null;
|
|
58
|
+
/**
|
|
59
|
+
* 打印当前负载均衡器状态(用于调试)
|
|
60
|
+
*/
|
|
61
|
+
static printStatus(): void;
|
|
62
|
+
}
|
|
63
|
+
export declare const loadBalancer: {
|
|
64
|
+
getStatus: typeof LoadBalancerManager.getStatus;
|
|
65
|
+
resetAPI: typeof LoadBalancerManager.resetAPI;
|
|
66
|
+
resetAll: typeof LoadBalancerManager.resetAll;
|
|
67
|
+
updateAPIConfig: typeof LoadBalancerManager.updateAPIConfig;
|
|
68
|
+
getConfig: typeof LoadBalancerManager.getConfig;
|
|
69
|
+
updateConfig: typeof LoadBalancerManager.updateConfig;
|
|
70
|
+
getHealthyAPIs: typeof LoadBalancerManager.getHealthyAPIs;
|
|
71
|
+
getBlockedAPIs: typeof LoadBalancerManager.getBlockedAPIs;
|
|
72
|
+
isAPIHealthy: typeof LoadBalancerManager.isAPIHealthy;
|
|
73
|
+
getRecommendedAPI: typeof LoadBalancerManager.getRecommendedAPI;
|
|
74
|
+
printStatus: typeof LoadBalancerManager.printStatus;
|
|
75
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { globalLoadBalancer } from "./loadBalancer.js";
|
|
2
|
+
/**
|
|
3
|
+
* 负载均衡器管理工具类
|
|
4
|
+
* 提供简化的接口来管理和配置负载均衡器
|
|
5
|
+
*/
|
|
6
|
+
export class LoadBalancerManager {
|
|
7
|
+
/**
|
|
8
|
+
* 获取所有端点的当前状态
|
|
9
|
+
*/
|
|
10
|
+
static getStatus() {
|
|
11
|
+
return globalLoadBalancer.getEndpointStatus().map((status) => ({
|
|
12
|
+
api: status.endpoint.name,
|
|
13
|
+
priority: status.endpoint.priority,
|
|
14
|
+
weight: status.endpoint.weight,
|
|
15
|
+
failureCount: status.failureCount,
|
|
16
|
+
isBlocked: status.isBlocked,
|
|
17
|
+
lastFailureTime: status.lastFailureTime
|
|
18
|
+
? new Date(status.lastFailureTime).toISOString()
|
|
19
|
+
: null,
|
|
20
|
+
nextRetryTime: status.nextRetryTime ? new Date(status.nextRetryTime).toISOString() : null,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 重置指定 API 的状态
|
|
25
|
+
*/
|
|
26
|
+
static resetAPI(apiType) {
|
|
27
|
+
if (apiType === "balance") {
|
|
28
|
+
throw new Error("Cannot reset 'balance' type. Use resetAll() instead.");
|
|
29
|
+
}
|
|
30
|
+
globalLoadBalancer.resetEndpoint(apiType);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 重置所有 API 的状态
|
|
34
|
+
*/
|
|
35
|
+
static resetAll() {
|
|
36
|
+
globalLoadBalancer.resetAllEndpoints();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 更新 API 端点的配置
|
|
40
|
+
*/
|
|
41
|
+
static updateAPIConfig(apiType, config) {
|
|
42
|
+
if (apiType === "balance") {
|
|
43
|
+
throw new Error("Cannot update 'balance' type configuration.");
|
|
44
|
+
}
|
|
45
|
+
globalLoadBalancer.updateEndpointConfig(apiType, config);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 获取负载均衡器配置
|
|
49
|
+
*/
|
|
50
|
+
static getConfig() {
|
|
51
|
+
return globalLoadBalancer.getConfig();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 更新负载均衡器配置
|
|
55
|
+
*/
|
|
56
|
+
static updateConfig(config) {
|
|
57
|
+
globalLoadBalancer.updateConfig(config);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 获取健康的(未被禁用的)API 列表
|
|
61
|
+
*/
|
|
62
|
+
static getHealthyAPIs() {
|
|
63
|
+
return globalLoadBalancer
|
|
64
|
+
.getEndpointStatus()
|
|
65
|
+
.filter((status) => !status.isBlocked)
|
|
66
|
+
.map((status) => status.endpoint.name);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 获取被禁用的 API 列表
|
|
70
|
+
*/
|
|
71
|
+
static getBlockedAPIs() {
|
|
72
|
+
return globalLoadBalancer
|
|
73
|
+
.getEndpointStatus()
|
|
74
|
+
.filter((status) => status.isBlocked)
|
|
75
|
+
.map((status) => status.endpoint.name);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 检查特定 API 是否可用
|
|
79
|
+
*/
|
|
80
|
+
static isAPIHealthy(apiType) {
|
|
81
|
+
if (apiType === "balance")
|
|
82
|
+
return true; // balance 类型总是可用的
|
|
83
|
+
const status = globalLoadBalancer.getEndpointStatus().find((s) => s.endpoint.name === apiType);
|
|
84
|
+
return status ? !status.isBlocked : false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 获取推荐使用的 API(基于当前状态和权重)
|
|
88
|
+
*/
|
|
89
|
+
static getRecommendedAPI() {
|
|
90
|
+
const healthyEndpoints = globalLoadBalancer
|
|
91
|
+
.getEndpointStatus()
|
|
92
|
+
.filter((status) => !status.isBlocked)
|
|
93
|
+
.sort((a, b) => {
|
|
94
|
+
if (a.endpoint.priority !== b.endpoint.priority) {
|
|
95
|
+
return a.endpoint.priority - b.endpoint.priority;
|
|
96
|
+
}
|
|
97
|
+
return b.endpoint.weight - a.endpoint.weight;
|
|
98
|
+
});
|
|
99
|
+
return healthyEndpoints.length > 0 ? healthyEndpoints[0].endpoint.name : null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 打印当前负载均衡器状态(用于调试)
|
|
103
|
+
*/
|
|
104
|
+
static printStatus() {
|
|
105
|
+
console.log("=== 负载均衡器状态 ===");
|
|
106
|
+
console.log("配置:", LoadBalancerManager.getConfig());
|
|
107
|
+
console.log("端点状态:");
|
|
108
|
+
console.table(LoadBalancerManager.getStatus());
|
|
109
|
+
console.log("健康的 APIs:", LoadBalancerManager.getHealthyAPIs());
|
|
110
|
+
console.log("被禁用的 APIs:", LoadBalancerManager.getBlockedAPIs());
|
|
111
|
+
console.log("推荐 API:", LoadBalancerManager.getRecommendedAPI());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 暴露简化的管理函数
|
|
115
|
+
export const loadBalancer = {
|
|
116
|
+
getStatus: LoadBalancerManager.getStatus,
|
|
117
|
+
resetAPI: LoadBalancerManager.resetAPI,
|
|
118
|
+
resetAll: LoadBalancerManager.resetAll,
|
|
119
|
+
updateAPIConfig: LoadBalancerManager.updateAPIConfig,
|
|
120
|
+
getConfig: LoadBalancerManager.getConfig,
|
|
121
|
+
updateConfig: LoadBalancerManager.updateConfig,
|
|
122
|
+
getHealthyAPIs: LoadBalancerManager.getHealthyAPIs,
|
|
123
|
+
getBlockedAPIs: LoadBalancerManager.getBlockedAPIs,
|
|
124
|
+
isAPIHealthy: LoadBalancerManager.isAPIHealthy,
|
|
125
|
+
getRecommendedAPI: LoadBalancerManager.getRecommendedAPI,
|
|
126
|
+
printStatus: LoadBalancerManager.printStatus,
|
|
127
|
+
};
|
package/lib/stream.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Recorder } from "@bililive-tools/manager";
|
|
2
|
-
|
|
2
|
+
import type { APIType, RealAPIType } from "./types.js";
|
|
3
|
+
export declare function getInfo(channelId: string, opts?: {
|
|
4
|
+
cookie?: string;
|
|
5
|
+
api?: APIType;
|
|
6
|
+
uid?: string | number;
|
|
7
|
+
}): Promise<{
|
|
3
8
|
living: boolean;
|
|
4
9
|
owner: string;
|
|
5
10
|
title: string;
|
|
@@ -8,6 +13,8 @@ export declare function getInfo(channelId: string): Promise<{
|
|
|
8
13
|
cover: string;
|
|
9
14
|
startTime: Date;
|
|
10
15
|
liveId: string;
|
|
16
|
+
uid: string;
|
|
17
|
+
api: RealAPIType;
|
|
11
18
|
}>;
|
|
12
19
|
export declare function getStream(opts: Pick<Recorder, "channelId" | "quality" | "streamPriorities" | "sourcePriorities"> & {
|
|
13
20
|
rejectCache?: boolean;
|
|
@@ -15,6 +22,8 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality" |
|
|
|
15
22
|
cookie?: string;
|
|
16
23
|
formatPriorities?: Array<"flv" | "hls">;
|
|
17
24
|
doubleScreen?: boolean;
|
|
25
|
+
api?: APIType;
|
|
26
|
+
uid?: string | number;
|
|
18
27
|
}): Promise<{
|
|
19
28
|
currentStream: {
|
|
20
29
|
name: string;
|
|
@@ -30,4 +39,6 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality" |
|
|
|
30
39
|
avatar: string;
|
|
31
40
|
cover: string;
|
|
32
41
|
liveId: string;
|
|
42
|
+
uid: string;
|
|
43
|
+
api: RealAPIType;
|
|
33
44
|
}>;
|
package/lib/stream.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { getRoomInfo } from "./douyin_api.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { globalLoadBalancer } from "./loadBalancer/loadBalancer.js";
|
|
3
|
+
export async function getInfo(channelId, opts) {
|
|
4
|
+
let info;
|
|
5
|
+
// 如果使用 balance 模式,使用负载均衡器
|
|
6
|
+
if (opts?.api === "balance") {
|
|
7
|
+
info = await globalLoadBalancer.callWithLoadBalance(channelId, {
|
|
8
|
+
auth: opts.cookie,
|
|
9
|
+
uid: opts.uid,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
info = await getRoomInfo(channelId, opts ?? {});
|
|
14
|
+
}
|
|
4
15
|
return {
|
|
5
16
|
living: info.living,
|
|
6
17
|
owner: info.owner,
|
|
@@ -10,13 +21,21 @@ export async function getInfo(channelId) {
|
|
|
10
21
|
cover: info.cover,
|
|
11
22
|
startTime: new Date(),
|
|
12
23
|
liveId: info.liveId,
|
|
24
|
+
uid: info.uid,
|
|
25
|
+
api: info.api,
|
|
13
26
|
};
|
|
14
27
|
}
|
|
15
28
|
export async function getStream(opts) {
|
|
29
|
+
let api = opts.api ?? "web";
|
|
30
|
+
if (api === "userHTML") {
|
|
31
|
+
// userHTML 接口只能用于状态检测
|
|
32
|
+
api = "web";
|
|
33
|
+
}
|
|
16
34
|
const info = await getRoomInfo(opts.channelId, {
|
|
17
|
-
retryOnSpecialCode: true,
|
|
18
35
|
doubleScreen: opts.doubleScreen ?? true,
|
|
19
36
|
auth: opts.cookie,
|
|
37
|
+
api: api,
|
|
38
|
+
uid: opts.uid,
|
|
20
39
|
});
|
|
21
40
|
if (!info.living) {
|
|
22
41
|
throw new Error("It must be called getStream when living");
|