Skip to content

Commit baa4077

Browse files
committed
update:设置中心完善扫码功能
1 parent b66bbb8 commit baa4077

22 files changed

+1369
-623
lines changed

Diff for: README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# drpyS(drpy-node)
22

3-
nodejs作为服务端的drpy实现。全面升级异步写法
3+
nodejs作为服务端的drpy实现。全面升级异步写法
4+
积极开发中,每日一更,当前进度 `20%`
45

56
* [本地配置接口-动态本地](/config)
67
* [本地配置接口-动态外网/局域网](/config/1)
@@ -13,12 +14,13 @@ nodejs作为服务端的drpy实现。全面升级异步写法
1314

1415
## 更新记录
1516

16-
### 20241228
17+
### 20241229
1718

18-
更新至V1.0.24
19+
更新至V1.0.25
1920

20-
1. 本地代理支持多线程流代理
21+
1. 本地多线程流代理优化
2122
2. 设置中心功能优化
23+
3. 推送功能优化
2224

2325
[点此查看完整更新记录](docs/updateRecord.md)
2426

Diff for: controllers/api.js

+5-264
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import path from 'path';
22
import {existsSync} from 'fs';
33
import {base64Decode} from '../libs_drpy/crypto-util.js';
4-
import '../utils/random-http-ua.js'
54
import * as drpy from '../libs/drpyS.js';
6-
// 创建 Agent 实例以复用 TCP 连接
7-
import http from 'http';
8-
import https from 'https';
9-
import axios from 'axios';
105
import {ENV} from "../utils/env.js";
116

12-
// const AgentOption = { keepAlive: true, maxSockets: 100,timeout: 60000 }; // 最大连接数100,60秒定期清理空闲连接
13-
const AgentOption = {keepAlive: true};
14-
const httpAgent = new http.Agent(AgentOption);
15-
const httpsAgent = new https.Agent(AgentOption);
16-
17-
187
export default (fastify, options, done) => {
198
// 动态加载模块并根据 query 执行不同逻辑
209
fastify.route({
@@ -30,8 +19,9 @@ export default (fastify, options, done) => {
3019
reply.status(404).send({error: `Module ${moduleName} not found`});
3120
return;
3221
}
22+
const method = request.method.toUpperCase();
3323
// 根据请求方法选择参数来源
34-
const query = request.method === 'GET' ? request.query : request.body;
24+
const query = method === 'GET' ? request.query : request.body;
3525
const protocol = request.protocol;
3626
const hostname = request.hostname;
3727
const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace('/api/', '/proxy/') + '/?do=js';
@@ -70,6 +60,9 @@ export default (fastify, options, done) => {
7060
}
7161

7262
if ('ac' in query && 'ids' in query) {
63+
if (method === 'POST') {
64+
fastify.log.info(`[${moduleName}] 二级已接收post数据: ${query.ids}`);
65+
}
7366
// 详情逻辑
7467
const result = await drpy.detail(modulePath, env, query.ids.split(','));
7568
return reply.send(result);
@@ -259,257 +252,5 @@ export default (fastify, options, done) => {
259252
}
260253
});
261254

262-
263-
// 用法同 https://github.com/Zhu-zi-a/mediaProxy
264-
fastify.all('/mediaProxy', async (request, reply) => {
265-
const {thread = 1, form = 'urlcode', url, header, size = '128K', randUa = 0} = request.query;
266-
267-
// Check if the URL parameter is missing
268-
if (!url) {
269-
return reply.code(400).send({error: 'Missing required parameter: url'});
270-
}
271-
272-
try {
273-
// Decode URL and headers based on the form type
274-
const decodedUrl = form === 'base64' ? base64Decode(url) : decodeURIComponent(url);
275-
const decodedHeader = header
276-
? JSON.parse(form === 'base64' ? base64Decode(header) : decodeURIComponent(header))
277-
: {};
278-
279-
// Call the proxy function, passing the decoded URL and headers
280-
return await proxyStreamMediaMulti(decodedUrl, decodedHeader, request, reply, thread, size, randUa);
281-
} catch (error) {
282-
fastify.log.error(error);
283-
reply.code(500).send({error: error.message});
284-
}
285-
});
286-
287255
done();
288256
};
289-
290-
// 媒体文件 流式代理
291-
function proxyStreamMedia(videoUrl, headers, reply) {
292-
console.log(`进入了流式代理: ${videoUrl} | headers: ${JSON.stringify(headers)}`);
293-
294-
const protocol = videoUrl.startsWith('https') ? https : http;
295-
const agent = videoUrl.startsWith('https') ? httpsAgent : httpAgent;
296-
297-
// 发起请求
298-
const proxyRequest = protocol.request(videoUrl, {headers, agent}, (videoResponse) => {
299-
console.log('videoResponse.statusCode:', videoResponse.statusCode);
300-
console.log('videoResponse.headers:', videoResponse.headers);
301-
302-
if (videoResponse.statusCode === 200 || videoResponse.statusCode === 206) {
303-
const resp_headers = {
304-
'Content-Type': videoResponse.headers['content-type'] || 'application/octet-stream',
305-
'Content-Length': videoResponse.headers['content-length'],
306-
...(videoResponse.headers['content-range'] ? {'Content-Range': videoResponse.headers['content-range']} : {}),
307-
};
308-
console.log('Response headers:', resp_headers);
309-
reply.headers(resp_headers).status(videoResponse.statusCode);
310-
311-
// 将响应流直接管道传输给客户端
312-
videoResponse.pipe(reply.raw);
313-
314-
videoResponse.on('data', (chunk) => {
315-
console.log('Data chunk received, size:', chunk.length);
316-
});
317-
318-
videoResponse.on('end', () => {
319-
console.log('Video data transmission complete.');
320-
});
321-
322-
videoResponse.on('error', (err) => {
323-
console.error('Error during video response:', err.message);
324-
reply.code(500).send({error: 'Error streaming video', details: err.message});
325-
});
326-
327-
reply.raw.on('finish', () => {
328-
console.log('Data fully sent to client');
329-
});
330-
331-
// 监听关闭事件,销毁视频响应流
332-
reply.raw.on('close', () => {
333-
console.log('Response stream closed.');
334-
videoResponse.destroy();
335-
});
336-
} else {
337-
console.error(`Unexpected status code: ${videoResponse.statusCode}`);
338-
reply.code(videoResponse.statusCode).send({error: 'Failed to fetch video'});
339-
}
340-
});
341-
342-
// 监听错误事件
343-
proxyRequest.on('error', (err) => {
344-
console.error('Proxy request error:', err.message);
345-
reply.code(500).send({error: 'Error fetching video', details: err.message});
346-
});
347-
348-
// 必须调用 .end() 才能发送请求
349-
proxyRequest.end();
350-
}
351-
352-
353-
// Helper function for range-based chunk downloading
354-
async function fetchStream(url, headers, start, end, randUa) {
355-
try {
356-
const response = await axios.get(url, {
357-
headers: {
358-
...headers,
359-
...randUa ? {'User-Agent': randomUa.generateUa(1, {device: ['pc']})} : {},
360-
Range: `bytes=${start}-${end}`,
361-
},
362-
responseType: 'stream',
363-
});
364-
return {stream: response.data, headers: response.headers};
365-
} catch (error) {
366-
throw new Error(`Failed to fetch range ${start}-${end}: ${error.message}`);
367-
}
368-
}
369-
370-
async function proxyStreamMediaMulti(mediaUrl, reqHeaders, request, reply, thread, size, randUa = 0) {
371-
try {
372-
// console.log('mediaUrl:', mediaUrl);
373-
// console.log('reqHeaders:', reqHeaders);
374-
// console.log('randUa:', randUa);
375-
let initialHeaders;
376-
let contentLength;
377-
378-
// First attempt with HEAD request to get the full content length
379-
/*
380-
try {
381-
const response = await axios.head(mediaUrl, {
382-
headers: Object.assign({}, reqHeaders, {'User-Agent': randomUa.generateUa()})
383-
});
384-
initialHeaders = response.headers;
385-
contentLength = parseInt(initialHeaders['content-length'], 10);
386-
387-
} catch (error) {
388-
// If HEAD fails, fallback to GET request without Range to get the full content length
389-
const response = await axios.get(mediaUrl, {
390-
headers: Object.assign({}, reqHeaders, {'User-Agent': randomUa.generateUa()}),
391-
responseType: 'stream'
392-
});
393-
initialHeaders = response.headers;
394-
contentLength = parseInt(initialHeaders['content-length'], 10);
395-
// 立即销毁流,防止下载文件内容
396-
response.data.destroy();
397-
}
398-
*/
399-
const randHeaders = randUa ? Object.assign({}, reqHeaders, {'User-Agent': randomUa.generateUa(1, {device: ['pc']})}) : reqHeaders;
400-
// console.log('randHeaders:', randHeaders);
401-
const response = await axios.get(mediaUrl, {
402-
headers: randHeaders,
403-
// headers: reqHeaders,
404-
responseType: 'stream'
405-
});
406-
initialHeaders = response.headers;
407-
contentLength = parseInt(initialHeaders['content-length'], 10);
408-
// 立即销毁流,防止下载文件内容
409-
response.data.destroy();
410-
// console.log('contentLength:', contentLength);
411-
412-
// Ensure that we got a valid content length
413-
if (!contentLength) {
414-
throw new Error('Failed to get the total content length.');
415-
}
416-
417-
// Set response headers based on the target URL headers, excluding certain ones
418-
Object.entries(initialHeaders).forEach(([key, value]) => {
419-
if (!['transfer-encoding', 'content-length'].includes(key.toLowerCase())) {
420-
reply.raw.setHeader(key, value);
421-
}
422-
});
423-
424-
reply.raw.setHeader('Accept-Ranges', 'bytes');
425-
426-
// Parse the range from the request or default to 'bytes=0-'
427-
const range = request.headers.range || 'bytes=0-';
428-
const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
429-
let start = parseInt(startStr, 10);
430-
let end = endStr ? parseInt(endStr, 10) : contentLength - 1;
431-
432-
// Ensure that the range is within the file's length
433-
if (start < 0) start = 0;
434-
if (end >= contentLength) end = contentLength - 1;
435-
436-
if (start >= end) {
437-
reply.code(416).header('Content-Range', `bytes */${contentLength}`).send();
438-
return;
439-
}
440-
441-
// Set Content-Range and Content-Length headers before any data is sent
442-
reply.raw.setHeader('Content-Range', `bytes ${start}-${end}/${contentLength}`);
443-
reply.raw.setHeader('Content-Length', end - start + 1);
444-
reply.raw.writeHead(206); // Ensure headers are sent before streaming
445-
446-
// Calculate the chunk size based on the provided size parameter (e.g., '128K', '1M')
447-
const chunkSize = sizeToBytes(size);
448-
449-
// Calculate the total number of chunks
450-
const totalChunks = Math.ceil((end - start + 1) / chunkSize);
451-
452-
// Limit the number of concurrent threads to the provided 'thread' value
453-
const threadCount = Math.min(thread, totalChunks);
454-
455-
// Split the range into multiple sub-ranges based on the number of threads
456-
const ranges = Array.from({length: threadCount}, (_, i) => {
457-
const subStart = start + i * (end - start + 1) / threadCount;
458-
const subEnd = Math.min(subStart + (end - start + 1) / threadCount - 1, end);
459-
return {start: Math.floor(subStart), end: Math.floor(subEnd)};
460-
});
461-
462-
// Fetch the streams concurrently for the calculated ranges
463-
const fetchChunks = ranges.map(range => fetchStream(mediaUrl, randHeaders, range.start, range.end, randUa));
464-
const streams = await Promise.all(fetchChunks);
465-
466-
// Send the chunks to the client in order
467-
let cnt = 0;
468-
for (const {stream} of streams) {
469-
cnt += 1;
470-
// Handle streaming and stop when client disconnects
471-
const onAbort = () => {
472-
console.log('Client aborted the connection');
473-
stream.destroy(); // Destroy the stream if client disconnects
474-
};
475-
476-
// Listen to the 'aborted' event on the request object
477-
request.raw.on('aborted', onAbort);
478-
479-
try {
480-
// console.log(`第${cnt}段流代理开始运行...`);
481-
for await (const chunk of stream) {
482-
if (request.raw.aborted) {
483-
// console.log(`第${cnt}段流代理结束运行`);
484-
break; // Stop streaming if the client aborted
485-
}
486-
reply.raw.write(chunk);
487-
}
488-
} catch (error) {
489-
console.error('[proxyStreamMediaMulti] error during streaming:', error.message);
490-
} finally {
491-
request.raw.removeListener('aborted', onAbort); // Clean up event listener
492-
}
493-
}
494-
495-
reply.raw.end(); // End the response once the streaming is done
496-
497-
} catch (error) {
498-
console.error('[proxyStreamMediaMulti] error:', error.message);
499-
if (!reply.sent) {
500-
reply.code(500).send({error: error.message});
501-
}
502-
}
503-
}
504-
505-
// Helper function to convert size string (e.g., '128K', '1M') to bytes
506-
function sizeToBytes(size) {
507-
const sizeMap = {
508-
K: 1024,
509-
M: 1024 * 1024,
510-
G: 1024 * 1024 * 1024
511-
};
512-
const unit = size[size.length - 1].toUpperCase();
513-
const number = parseInt(size, 10);
514-
return number * (sizeMap[unit] || 1);
515-
}

Diff for: controllers/http.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
import axios from 'axios';
2+
import http from 'http';
3+
import https from 'https';
4+
5+
const AgentOption = {keepAlive: true, maxSockets: 64, timeout: 30000}; // 最大连接数64,30秒定期清理空闲连接
6+
// const AgentOption = {keepAlive: true};
7+
const httpAgent = new http.Agent(AgentOption);
8+
const httpsAgent = new https.Agent({rejectUnauthorized: false, ...AgentOption});
9+
10+
// 配置 axios 使用代理
11+
const _axios = axios.create({
12+
httpAgent, // 用于 HTTP 请求的代理
13+
httpsAgent, // 用于 HTTPS 请求的代理
14+
});
215

316
export default (fastify, options, done) => {
417
// 读取 views 目录下的 encoder.html 文件并返回
518
fastify.post('/http', async (req, reply) => {
619
const {url, headers = {}, params = {}, method = 'GET', data = {}} = req.body;
7-
20+
// console.log('headers:', headers);
821
if (!url) {
922
return reply.status(400).send({error: 'Missing required field: url'});
1023
}
1124

1225
try {
13-
const response = await axios({
26+
const response = await _axios({
1427
url,
1528
method,
1629
headers,
@@ -24,6 +37,7 @@ export default (fastify, options, done) => {
2437
data: response.data,
2538
});
2639
} catch (error) {
40+
// console.error(error);
2741
if (error.response) {
2842
// 服务器返回了非 2xx 状态码
2943
reply.status(error.response.status).send({

Diff for: controllers/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import docsController from './docs.js';
22
import configController from './config.js';
33
import apiController from './api.js';
4+
import mediaProxyController from './mediaProxy.js';
45
import rootController from './root.js';
56
import encoderController from './encoder.js';
67
import decoderController from './decoder.js';
@@ -12,6 +13,7 @@ export const registerRoutes = (fastify, options) => {
1213
fastify.register(docsController, options);
1314
fastify.register(configController, options);
1415
fastify.register(apiController, options);
16+
fastify.register(mediaProxyController, options);
1517
fastify.register(rootController, options);
1618
fastify.register(encoderController, options);
1719
fastify.register(decoderController, options);

0 commit comments

Comments
 (0)