/** * WebDAV 代理控制器模块 * 提供 WebDAV 文件的 HTTP 直链访问功能 * @module webdav-proxy-controller */ import {ENV} from '../utils/env.js'; import {WebDAVClient} from '../utils/webdav.js'; import {readFileSync} from 'fs'; import path from 'path'; /** * WebDAV 代理控制器插件 * @param {Object} fastify - Fastify实例 * @param {Object} options - 插件选项 * @param {Function} done - 完成回调 */ export default (fastify, options, done) => { // WebDAV 客户端缓存 const webdavClients = new Map(); // 文件信息缓存 const fileCache = new Map(); // 缓存超时时间(5分钟) const cacheTimeout = 5 * 60 * 1000; /** * 获取或创建 WebDAV 客户端 */ function getWebDAVClient(config) { const key = `${config.baseURL}:${config.username}`; if (!webdavClients.has(key)) { const client = new WebDAVClient(config); webdavClients.set(key, client); } return webdavClients.get(key); } /** * 加载默认配置 */ function loadDefaultConfig() { try { const configPath = path.join(process.cwd(), 'json', 'webdav.json'); const configData = readFileSync(configPath, 'utf8'); const parsed = JSON.parse(configData); // 如果是数组,取第一个配置 if (Array.isArray(parsed)) { return parsed.length > 0 ? parsed[0] : null; } return parsed; } catch (error) { console.warn('Could not load default WebDAV config:', error.message); return null; } } /** * WebDAV 健康检查接口 * GET /webdav/health - 检查 WebDAV 代理服务状态 */ fastify.get('/webdav/health', async (request, reply) => { console.log(`[webdavController] Health check request`); return reply.send({ status: 'ok', service: 'WebDAV Proxy', timestamp: new Date().toISOString(), cache: { clients: webdavClients.size, files: fileCache.size } }); }); /** * WebDAV 文件直链访问接口 * GET /webdav/file - 获取文件直链 */ fastify.get('/webdav/file', async (request, reply) => { const {path: filePath, config: configParam} = request.query; console.log(`[webdavController] File request: ${filePath}`); // 验证必需参数 if (!filePath) { return reply.status(400).send({error: 'Missing required parameter: path'}); } try { // 获取 WebDAV 配置 let config; if (configParam) { try { config = JSON.parse(decodeURIComponent(configParam)); } catch (e) { return reply.status(400).send({error: 'Invalid config parameter'}); } } else { config = loadDefaultConfig(); } if (!config) { return reply.status(400).send({error: 'WebDAV config not provided'}); } const client = getWebDAVClient(config); // 检查缓存 const cacheKey = `${config.baseURL}:${filePath}`; const cached = fileCache.get(cacheKey); const now = Date.now(); let fileInfo; if (cached && (now - cached.timestamp) < cacheTimeout) { fileInfo = cached.info; } else { // 获取文件信息并缓存 fileInfo = await client.getInfo(filePath); fileCache.set(cacheKey, { info: fileInfo, timestamp: now }); } if (!fileInfo || fileInfo.isDirectory) { return reply.status(404).send({error: 'File not found or is a directory'}); } // 处理 Range 请求 const range = request.headers.range; let streamOptions = {}; if (range) { const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileInfo.size - 1; if (start >= fileInfo.size || end >= fileInfo.size) { reply.status(416); reply.header('Content-Range', `bytes */${fileInfo.size}`); return; } streamOptions.headers = { 'Range': `bytes=${start}-${end}` }; reply.status(206); reply.header('Content-Range', `bytes ${start}-${end}/${fileInfo.size}`); reply.header('Accept-Ranges', 'bytes'); reply.header('Content-Length', end - start + 1); } else { reply.header('Content-Length', fileInfo.size); reply.header('Accept-Ranges', 'bytes'); } // 设置响应头 reply.header('Content-Type', fileInfo.contentType || 'application/octet-stream'); if (fileInfo.lastModified) { reply.header('Last-Modified', new Date(fileInfo.lastModified).toUTCString()); } if (fileInfo.etag) { reply.header('ETag', fileInfo.etag); } reply.header('Cache-Control', 'public, max-age=3600'); reply.header('Access-Control-Allow-Origin', '*'); // 获取文件流 const {stream} = await client.getFileStream(filePath, streamOptions); // 返回流 return reply.send(stream); } catch (error) { console.error('[webdavController] File request error:', error); return reply.status(500).send({error: error.message}); } }); /** * WebDAV 文件信息获取接口 * GET /webdav/info - 获取文件或目录信息 */ fastify.get('/webdav/info', async (request, reply) => { const {path: filePath, config: configParam} = request.query; console.log(`[webdavController] Info request: ${filePath}`); // 验证必需参数 if (!filePath) { return reply.status(400).send({error: 'Missing required parameter: path'}); } try { let config; if (configParam) { config = JSON.parse(decodeURIComponent(configParam)); } else { config = loadDefaultConfig(); } if (!config) { return reply.status(400).send({error: 'WebDAV config not provided'}); } const client = getWebDAVClient(config); const fileInfo = await client.getInfo(filePath); return reply.send(fileInfo); } catch (error) { console.error('[webdavController] Info request error:', error); return reply.status(500).send({error: error.message}); } }); /** * WebDAV 目录列表接口 * GET /webdav/list - 获取目录内容列表 */ fastify.get('/webdav/list', async (request, reply) => { const {path: dirPath = '/', config: configParam} = request.query; console.log(`[webdavController] List request: ${dirPath}`); try { let config; if (configParam) { config = JSON.parse(decodeURIComponent(configParam)); } else { config = loadDefaultConfig(); } if (!config) { return reply.status(400).send({error: 'WebDAV config not provided'}); } const client = getWebDAVClient(config); const items = await client.listDirectory(dirPath); return reply.send(items); } catch (error) { console.error('[webdavController] List request error:', error); return reply.status(500).send({error: error.message}); } }); /** * WebDAV 配置测试接口 * POST /webdav/config - 测试和配置 WebDAV 连接 */ fastify.post('/webdav/config', async (request, reply) => { console.log(`[webdavController] Config test request`); try { const config = request.body; // 验证配置 if (!config.baseURL || !config.username || !config.password) { return reply.status(400).send({ error: 'Missing required config fields: baseURL, username, password' }); } // 测试连接 const client = getWebDAVClient(config); await client.testConnection(); return reply.send({ success: true, message: 'WebDAV connection configured and tested successfully', config: { baseURL: config.baseURL, username: config.username, // 不返回密码 } }); } catch (error) { console.error('[webdavController] Config test error:', error); return reply.status(500).send({error: error.message}); } }); /** * WebDAV 缓存管理接口 * DELETE /webdav/cache - 清理缓存 */ fastify.delete('/webdav/cache', async (request, reply) => { console.log(`[webdavController] Cache clear request`); try { // 非VERCEL环境可在设置中心控制此功能是否开启 if (!process.env.VERCEL) { if (ENV.get('allow_webdav_cache_clear') !== '1') { return reply.status(403).send({error: 'Cache clear is not allowed by owner'}); } } const clientCount = webdavClients.size; const fileCount = fileCache.size; // 清理缓存 fileCache.clear(); webdavClients.clear(); return reply.send({ success: true, message: 'Cache cleared successfully', cleared: { clients: clientCount, files: fileCount } }); } catch (error) { console.error('[webdavController] Cache clear error:', error); return reply.status(500).send({error: error.message}); } }); /** * WebDAV 代理状态接口 * GET /webdav/status - 获取代理服务状态 */ fastify.get('/webdav/status', async (request, reply) => { console.log(`[webdavController] Status request`); try { const config = loadDefaultConfig(); return reply.send({ service: 'WebDAV Proxy Controller', version: '1.0.0', status: 'running', cache: { clients: webdavClients.size, files: fileCache.size, timeout: cacheTimeout }, config: config ? { baseURL: config.baseURL, username: config.username, hasPassword: !!config.password } : null, endpoints: [ 'GET /webdav/health - Health check', 'GET /webdav/file?path= - Get file direct link', 'GET /webdav/info?path= - Get file information', 'GET /webdav/list?path= - List directory contents', 'POST /webdav/config - Test WebDAV configuration', 'DELETE /webdav/cache - Clear cache', 'GET /webdav/status - Get service status' ] }); } catch (error) { console.error('[webdavController] Status request error:', error); return reply.status(500).send({error: error.message}); } }); done(); };