worker.js路由文件解读

我将逐句解释这段 Cloudflare Worker 代码。

这个 worker.js 文件是 Cloudflare Worker 的入口点,它负责处理所有传入的 HTTP 请求,并根据请求的路径和方法将其路由到不同的处理逻辑,包括:

  1. Durable Object (DO) 管理:用于持久化聊天室状态。

  2. CORS 处理:允许跨域请求。

  3. 文件上传:将文件存储到 Cloudflare R2。

  4. AI 服务集成:调用 DeepSeek 和 Google Gemini API 进行文本解释和图片描述。

  5. 聊天室统计和历史消息:通过 Durable Object 获取数据。

  6. 静态文件服务:提供 index.html 页面。

  7. WebSocket 连接:将 WebSocket 请求转发给 Durable Object。


  
// src/worker.js
  
  • // src/worker.js: 这是一个注释,表示当前文件的路径和名称。
  
import { HibernatingChatRoom } from './chatroom_do.js';
  
  • import { HibernatingChatRoom } from './chatroom_do.js';: 从当前目录下的 chatroom_do.js 文件中导入 HibernatingChatRoom 类。HibernatingChatRoom 是一个 Cloudflare Durable Object 类,用于实现持久化的聊天室逻辑。
  
import html from '../public/index.html';
  
  • import html from '../public/index.html';: 导入 ../public/index.html 文件。在 Cloudflare Worker 中,可以通过这种方式将静态 HTML 文件作为字符串或模块导入,以便在 Worker 响应请求时直接返回其内容。
  
// Export Durable Object class for Cloudflare platform instantiation
  
export { HibernatingChatRoom };
  
  • // Export Durable Object class for Cloudflare platform instantiation: 注释,说明下面的导出是为了让 Cloudflare 平台能够实例化这个 Durable Object。

  • export { HibernatingChatRoom };: 导出 HibernatingChatRoom 类。这是 Cloudflare Durable Object 正常工作所必需的,它告诉 Cloudflare 运行时这个类是一个可以被绑定的 Durable Object。

  
// --- CORS Headers ---
  
const corsHeaders = {
  
    'Access-Control-Allow-Origin': '*', // 允许所有来源,或者更严格地设置为 'https://chats.want.biz'
  
    'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
  
    'Access-Control-Max-Age': '86400',
  
};
  
  • // --- CORS Headers ---: 注释,标记 CORS(跨域资源共享)头部设置的开始。

  • const corsHeaders = { ... };: 定义一个常量对象 corsHeaders,其中包含了常用的 CORS 响应头。

    • 'Access-Control-Allow-Origin': '*': 允许任何来源(*)的请求访问资源。在生产环境中,通常会将其设置为特定的域名(例如 'https://chats.want.biz')以提高安全性。

    • 'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS': 允许客户端使用 GET, HEAD, POST, OPTIONS 方法进行跨域请求。

    • 'Access-Control-Max-Age': '86400': 预检请求(OPTIONS 请求)的结果可以被缓存的时间(秒)。86400 秒等于 24 小时,这意味着在 24 小时内,浏览器不需要为相同的跨域请求再次发送预检请求。

  
// 处理 OPTIONS 请求 (CORS 预检请求)
  
function handleOptions(request) {
  
    if (request.headers.get('Origin') !== null &&
  
        request.headers.get('Access-Control-Request-Method') !== null &&
  
        request.headers.get('Access-Control-Request-Headers') !== null) {
  
        // Handle CORS preflight request.
  
        return new Response(null, {
  
            headers: {
  
                ...corsHeaders,
  
                'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'),
  
            },
  
        });
  
    } else {
  
        // Handle standard OPTIONS request.
  
        return new Response(null, { headers: corsHeaders });
  
    }
  
}
  
  • // 处理 OPTIONS 请求 (CORS 预检请求): 注释,说明此函数用于处理 CORS 预检请求。

  • function handleOptions(request) { ... }: 定义一个名为 handleOptions 的函数,接收一个 Request 对象作为参数。

  • if (request.headers.get('Origin') !== null && ...): 检查请求头中是否包含 OriginAccess-Control-Request-MethodAccess-Control-Request-Headers。这些头部是浏览器发送 CORS 预检请求的标志。

    • return new Response(null, { ... });: 如果是预检请求,返回一个空的 Response(状态码默认为 200 OK)。

    • headers: { ...corsHeaders, 'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'), }: 响应头中包含之前定义的 corsHeaders,并且特别添加了 Access-Control-Allow-Headers,其值取自客户端请求中指定的 Access-Control-Request-Headers,告诉浏览器服务器允许哪些自定义请求头。

  • else { ... }: 如果不是 CORS 预检请求(例如,只是一个普通的 OPTIONS 请求),则:

    • return new Response(null, { headers: corsHeaders });: 返回一个空的 Response,只包含基本的 corsHeaders
  
// --- 新增:模块化的AI服务调用函数 ---
  

  
/**
  
 * 调用 DeepSeek API 获取解释
  
 * @param {string} text - 需要解释的文本
  
 * @param {object} env - Cloudflare环境变量
  
 * @returns {Promise<string>} - AI返回的解释文本
  
 */
  
async function getDeepSeekExplanation(text, env) {
  
    const DEEPSEEK_API_KEY = env.DEEPSEEK_API_KEY;
  
    if (!DEEPSEEK_API_KEY) {
  
        throw new Error('Server configuration error: DEEPSEEK_API_KEY is not set.');
  
    }
  

  
    const response = await fetch("https://api.deepseek.com/chat/completions", {
  
        method: "POST",
  
        headers: 
  
            "Content-Type": "application/json",
  
            "Authorization": `Bearer ${DEEPSEEK_API_KEY}`
  
        },
  
        body: JSON.stringify({
  
            model: "deepseek-chat",
  
            messages: [
  
                { role: "system", content: "你是一个有用的,善于用简洁的markdown语言来解释下面的文本." },
  
                { role: "user", content: `你是一位非常耐心的小学老师,专门给小学生讲解新知识。  我是一名小学三年级学生,我特别渴望弄明白事物的含义。  请你用精准、详细的语言解释(Markdown 格式):1. 用通俗易懂的语言解释下面这段文字。2. 给出关键概念的定义。3. 用生活中的比喻或小故事帮助理解。4. 举一个具体例子,并示范“举一反三”的思考方法。5. 最后用一至两个问题来引导我延伸思考。:\n\n${text}` }
  
            ]
  
        })
  
    });
  

  
    if (!response.ok) {
  
        const errorText = await response.text();
  
        console.error(`DeepSeek API error: ${response.status} - ${errorText}`);
  
        throw new Error(`DeepSeek API error: ${errorText}`);
  
    }
  

  
    const data = await response.json();
  
    const explanation = data?.choices?.[0]?.message?.content;
  

  
    if (!explanation) {
  
        console.error('Unexpected DeepSeek API response structure:', JSON.stringify(data));
  
        throw new Error('Unexpected AI response format from DeepSeek.');
  
    }
  

  
    return explanation;
  
}
  
  • // --- 新增:模块化的AI服务调用函数 ---: 注释,标记 AI 服务调用函数的开始。

  • /** ... */: JSDoc 风格的注释,描述了 getDeepSeekExplanation 函数的功能、参数和返回值。

  • async function getDeepSeekExplanation(text, env) { ... }: 定义异步函数,用于调用 DeepSeek API。

    • const DEEPSEEK_API_KEY = env.DEEPSEEK_API_KEY;: 从 Cloudflare Worker 的环境变量 env 中获取 DeepSeek API 密钥。

    • if (!DEEPSEEK_API_KEY) { ... }: 检查 API 密钥是否存在,如果不存在则抛出配置错误。

    • const response = await fetch("https://api.deepseek.com/chat/completions", { ... });: 向 DeepSeek 的聊天完成 API 发送 POST 请求。

      • method: "POST": 指定请求方法为 POST。

      • headers: { ... }: 设置请求头,包括 Content-Typeapplication/jsonAuthorization(携带 API 密钥)。

      • body: JSON.stringify({ ... }): 请求体,包含 AI 模型名称 (deepseek-chat) 和消息数组。

        • { role: "system", content: "..." }: 系统消息,设定 AI 的角色和行为(例如,善于用 Markdown 解释)。

        • { role: "user", content: "..." }: 用户消息,包含实际需要解释的文本 (${text}) 和详细的解释要求(例如,作为小学老师,用通俗易懂的语言,提供定义、比喻、例子、延伸思考等)。

    • if (!response.ok) { ... }: 检查 API 响应是否成功(HTTP 状态码 2xx)。如果失败,则读取错误信息并抛出错误。

    • const data = await response.json();: 解析 JSON 响应体。

    • const explanation = data?.choices?.[0]?.message?.content;: 使用可选链操作符 (?.) 安全地从响应数据中提取 AI 生成的解释文本。

    • if (!explanation) { ... }: 检查是否成功提取到解释文本,如果结构不符合预期则抛出错误。

    • return explanation;: 返回提取到的解释文本。

  
/**
  
 * 调用 Google Gemini API 获取解释
  
 * @param {string} text - 需要解释的文本
  
 * @param {object} env - Cloudflare环境变量
  
 * @returns {Promise<string>} - AI返回的解释文本
  
 */
  
async function getGeminiExplanation(text, env) {
  
    const GEMINI_API_KEY = env.GEMINI_API_KEY;
  
    if (!GEMINI_API_KEY) {
  
        throw new Error('Server configuration error: GEMINI_API_KEY is not set.');
  
    }
  
    
  
    // Google Gemini API v1beta endpoint for text generation
  
    const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${GEMINI_API_KEY}`;
  
    
  
    const response = await fetch(GEMINI_API_URL, {
  
        method: "POST",
  
        headers: {
  
            "Content-Type": "application/json",
  
        },
  
        body: JSON.stringify({
  
            contents: [{
  
                parts: [{
  
                    text: `你是一位非常耐心的小学老师,专门给小学生讲解新知识。  我是一名小学三年级学生,我特别渴望弄明白事物的含义。  请你用精准、详细的语言解释(Markdown 格式):1. 用通俗易懂的语言解释下面这段文字。2. 给出关键概念的定义。3. 用生活中的比喻或小故事帮助理解。4. 举一个具体例子,并示范“举一反三”的思考方法。5. 最后用一至两个问题来引导我延伸思考。::\n\n${text}`
  
                }]
  
            }]
  
        })
  
    });
  

  
    if (!response.ok) {
  
        const errorText = await response.text();
  
        console.error(`Gemini API error: ${response.status} - ${errorText}`);
  
        throw new Error(`Gemini API error: ${errorText}`);
  
    }
  

  
    const data = await response.json();
  
    // Gemini的响应结构不同,需要这样提取
  
    const explanation = data?.candidates?.[0]?.content?.parts?.[0]?.text;
  
    
  
    if (!explanation) {
  
        console.error('Unexpected Gemini API response structure:', JSON.stringify(data));
  
        throw new Error('Unexpected AI response format from Gemini.');
  
    }
  

  
    return explanation;
  
}
  
  • async function getGeminiExplanation(text, env) { ... }: 定义异步函数,用于调用 Google Gemini API。

    • 逻辑与 getDeepSeekExplanation 类似,但针对 Gemini API 的特点进行了调整。

    • const GEMINI_API_KEY = env.GEMINI_API_KEY;: 从环境变量获取 Gemini API 密钥。

    • const GEMINI_API_URL = https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${GEMINI_API_KEY}`;`: 构建 Gemini API 的 URL,将 API 密钥作为查询参数。

    • body: JSON.stringify({ contents: [{ parts: [{ text: ... }] }] }): Gemini API 的请求体结构不同,它使用 contents 数组,每个元素包含 parts 数组,其中包含 text 字段。提示词内容与 DeepSeek 类似。

    • const explanation = data?.candidates?.[0]?.content?.parts?.[0]?.text;: Gemini 的响应结构也不同,需要通过 candidates[0].content.parts[0].text 来提取解释文本。

  
// --- 新增:AI图片描述服务 ---
  

  
async function fetchImageAsBase64(imageUrl) {
  
    const response = await fetch(imageUrl);
  
    if (!response.ok) {
  
        throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
  
    }
  
    const contentType = response.headers.get('content-type') || 'image/jpeg';
  
    const buffer = await response.arrayBuffer();
  
    
  
    let binary = '';
  
    const bytes = new Uint8Array(buffer);
  
    for (let i = 0; i < bytes.byteLength; i++) {
  
        binary += String.fromCharCode(bytes[i]);
  
    }
  
    const base64 = btoa(binary);
  
    
  
    return { base64, contentType };
  
}
  
  • // --- 新增:AI图片描述服务 ---: 注释,标记 AI 图片描述服务的开始。

  • async function fetchImageAsBase64(imageUrl) { ... }: 定义异步函数,用于从给定的 URL 获取图片并将其转换为 Base64 编码。

    • const response = await fetch(imageUrl);: 发送 HTTP 请求获取图片。

    • if (!response.ok) { ... }: 检查图片是否成功获取。

    • const contentType = response.headers.get('content-type') || 'image/jpeg';: 获取图片的 Content-Type,如果不存在则默认为 image/jpeg

    • const buffer = await response.arrayBuffer();: 将图片响应体读取为 ArrayBuffer

    • let binary = ''; const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); }: 将 ArrayBuffer 转换为二进制字符串。

    • const base64 = btoa(binary);: 使用 btoa() 函数将二进制字符串编码为 Base64 字符串。

    • return { base64, contentType };: 返回包含 Base64 编码图片数据和内容类型的对象。

  
async function getGeminiImageDescription(imageUrl, env) {
  
    const GEMINI_API_KEY = env.GEMINI_API_KEY;
  
    if (!GEMINI_API_KEY) {
  
        throw new Error('Server configuration error: GEMINI_API_KEY is not set.');
  
    }
  

  
    const { base64, contentType } = await fetchImageAsBase64(imageUrl);
  

  
    const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${GEMINI_API_KEY}`;
  
    
  
    const prompt = "请仔细描述图片的内容,如果图片中识别出有文字,则在回复的内容中返回这些文字,并且这些文字支持复制,之后是对文字的仔细描述,格式为:图片中包含文字:{文字内容};图片的描述:{图片描述}";
  

  
    const response = await fetch(GEMINI_API_URL, {
  
        method: "POST",
  
        headers: {
  
            "Content-Type": "application/json",
  
        },
  
        body: JSON.stringify({
  
            contents: [{
  
                parts: [
  
                    { text: prompt },
  
                    {
  
                        inline_data: {
  
                            mime_type: contentType,
  
                            data: base64
  
                        }
  
                    }
  
                ]
  
            }]
  
        })
  
    });
  

  
    if (!response.ok) {
  
        const errorText = await response.text();
  
        console.error(`Gemini Vision API error: ${response.status} - ${errorText}`);
  
        throw new Error(`Gemini Vision API error: ${errorText}`);
  
    }
  

  
    const data = await response.json();
  
    const description = data?.candidates?.[0]?.content?.parts?.[0]?.text;
  
    
  
    if (!description) {
  
        console.error('Unexpected Gemini Vision API response structure:', JSON.stringify(data));
  
        throw new Error('Unexpected AI response format from Gemini Vision.');
  
    }
  

  
    return description;
  
}
  
  • async function getGeminiImageDescription(imageUrl, env) { ... }: 定义异步函数,用于调用 Google Gemini Vision API 来描述图片。

    • const GEMINI_API_KEY = env.GEMINI_API_KEY;: 获取 Gemini API 密钥。

    • const { base64, contentType } = await fetchImageAsBase64(imageUrl);: 调用 fetchImageAsBase64 函数获取图片的 Base64 数据和内容类型。

    • const GEMINI_API_URL = ...: 构建 Gemini API 的 URL。

    • const prompt = "请仔细描述图片的内容,如果图片中识别出有文字,则在回复的内容中返回这些文字,并且这些文字支持复制,之后是对文字的仔细描述,格式为:图片中包含文字:{文字内容};图片的描述:{图片描述}";: 定义发送给 AI 的提示词,要求其描述图片内容,并提取图片中的文字。

    • body: JSON.stringify({ contents: [{ parts: [{ text: prompt }, { inline_data: { mime_type: contentType, data: base64 } }] }] }): 构建 Gemini Vision API 的请求体。与文本生成不同,这里 parts 数组中包含两部分:一部分是文本提示词,另一部分是 inline_data 对象,用于嵌入 Base64 编码的图片数据。

    • if (!response.ok) { ... }: 检查 API 响应是否成功。

    • const description = data?.candidates?.[0]?.content?.parts?.[0]?.text;: 从 Gemini Vision API 的响应中提取图片描述文本。

    • if (!description) { ... }: 检查是否成功提取到描述文本。

    • return description;: 返回提取到的图片描述。

  
// --- 主Worker逻辑 ---
  

  
export default {
  
    async fetch(request, env, ctx) {
  
        // Handle CORS preflight requests
  
        if (request.method === 'OPTIONS') {
  
            return handleOptions(request);
  
        }
  

  
        console.log('Incoming request URL:', request.url); // Add this line
  
        const url = new URL(request.url);
  
        
  
        let pathname = url.pathname;
  
        console.log('Received request for path:', pathname); // Add this line for debugging
  
        if (pathname.endsWith('/') && pathname.length > 1) {
  
            pathname = pathname.slice(0, -1);
  
        }
  
        const pathParts = pathname.split('/').filter(part => part);
  
  • // --- 主Worker逻辑 ---: 注释,标记主 Worker 逻辑的开始。

  • export default { async fetch(request, env, ctx) { ... } };: 这是 Cloudflare Worker 的标准入口点。当 Worker 接收到 HTTP 请求时,fetch 方法会被调用。

    • request: 传入的 Request 对象,包含请求的所有信息(URL、方法、头部、请求体等)。

    • env: 环境变量对象,包含了在 Cloudflare 控制台或 wrangler.toml 中配置的绑定(如 Durable Object 绑定、R2 绑定、环境变量等)。

    • ctx: 上下文对象,提供了 Worker 运行时的一些实用方法(如 waitUntil 用于延长 Worker 的生命周期)。

  • if (request.method === 'OPTIONS') { return handleOptions(request); }: 如果请求方法是 OPTIONS,则调用之前定义的 handleOptions 函数来处理 CORS 预检请求。

  • console.log('Incoming request URL:', request.url);: 打印传入请求的完整 URL,用于调试。

  • const url = new URL(request.url);: 使用 URL 对象解析请求的 URL,方便获取路径、查询参数等。

  • let pathname = url.pathname;: 获取 URL 的路径部分。

  • console.log('Received request for path:', pathname);: 打印解析后的路径,用于调试。

  • if (pathname.endsWith('/') && pathname.length > 1) { pathname = pathname.slice(0, -1); }: 对路径进行规范化处理,如果路径以 / 结尾且不是根路径 /,则移除末尾的 /(例如 /room/ 变为 /room)。

  • const pathParts = pathname.split('/').filter(part => part);: 将路径按 / 分割成数组,并过滤掉空字符串(例如 /a/b 会变成 ['a', 'b'])。

  
        // Handle image upload (no changes here)
  
        if (pathname === '/upload' && request.method === 'POST') {
  
            // ... 你的上传逻辑保持不变 ...
  
            try {
  
                if (!env.R2_BUCKET) {
  
                    return new Response('Server configuration error: R2_BUCKET not bound.', { status: 500 });
  
                }
  
                const filename = request.headers.get('X-Filename') || `upload-${Date.now()}`;
  
                const object = await env.R2_BUCKET.put(filename, request.body, { httpMetadata: request.headers });
  
                const publicUrl = `${new URL(request.url).origin}/${object.key}`;
  
                return new Response(JSON.stringify({ url: publicUrl }), { headers: { 'Content-Type': 'application/json', ...corsHeaders } });
  
            } catch (error) {
  
                console.error('Upload error:', error.stack);
  
                return new Response('Error uploading file.', { status: 500 });
  
            }
  
        }
  
  • if (pathname === '/upload' && request.method === 'POST') { ... }: 处理文件上传请求。

    • if (!env.R2_BUCKET) { ... }: 检查 env 中是否绑定了 R2 存储桶。R2_BUCKET 是在 Cloudflare 配置中绑定的 R2 存储桶的名称。

    • const filename = request.headers.get('X-Filename') || upload-${Date.now()};: 从请求头 X-Filename 获取文件名,如果没有则生成一个基于时间戳的默认文件名。

    • const object = await env.R2_BUCKET.put(filename, request.body, { httpMetadata: request.headers });: 将请求体 (request.body) 作为文件内容上传到 R2 存储桶,文件名为 filenamehttpMetadata 选项将请求头作为对象的元数据存储。

    • const publicUrl = newURL(request.url).origin/{new URL(request.url).origin}/{object.key};: 构建上传文件的公共 URL。

    • return new Response(JSON.stringify({ url: publicUrl }), { headers: { 'Content-Type': 'application/json', ...corsHeaders } });: 返回包含文件公共 URL 的 JSON 响应。

    • catch (error) { ... }: 捕获上传过程中的错误并返回 500 错误响应。

  
        // --- 更新后的AI解释请求处理逻辑 ---
  
        if (pathname === '/ai-explain' && request.method === 'POST') {
  
            try {
  
                const requestBody = await request.json();
  
                const text = requestBody.text;
  
                // 从请求中获取模型名称,如果前端没传,则默认为 'gemini'
  
                const model = requestBody.model || 'gemini'; 
  

  
                if (!text) {
  
                    return new Response('Missing text in request body.', { status: 400 });
  
                }
  

  
                let explanation = "";
  
                
  
                // 根据模型名称,调用相应的函数
  
                console.log(`Routing AI request to model: ${model}`);
  
                if (model === 'gemini') {
  
                    explanation = await getGeminiExplanation(text, env);
  
                } else if (model === 'deepseek') {
  
                    explanation = await getDeepSeekExplanation(text, env);
  
                } else {
  
                    return new Response(`Unknown AI model: ${model}`, { status: 400 });
  
                }
  

  
                return new Response(JSON.stringify({ explanation }), {
  
                    headers: { 'Content-Type': 'application/json', ...corsHeaders },
  
                });
  

  
            } catch (error) {
  
                console.error('AI explanation request error:', error.message);
  
                // 将具体的错误信息返回给前端,方便调试
  
                return new Response(`Error processing AI request: ${error.message}`, { status: 500 });
  
            }
  
        }
  
  • if (pathname === '/ai-explain' && request.method === 'POST') { ... }: 处理 AI 文本解释请求。

    • const requestBody = await request.json();: 解析请求体为 JSON 对象。

    • const text = requestBody.text;: 从请求体中获取需要解释的文本。

    • const model = requestBody.model || 'gemini';: 从请求体中获取要使用的 AI 模型名称,如果未提供则默认为 gemini

    • if (!text) { ... }: 检查 text 是否存在。

    • if (model === 'gemini') { ... } else if (model === 'deepseek') { ... } else { ... }: 根据 model 的值,调用相应的 AI 解释函数 (getGeminiExplanationgetDeepSeekExplanation)。如果模型未知,则返回 400 错误。

    • return new Response(JSON.stringify({ explanation }), { ... });: 返回包含 AI 解释结果的 JSON 响应。

    • catch (error) { ... }: 捕获处理过程中的错误,并返回 500 错误响应,包含具体的错误信息。

  
        // --- 新增:AI图片描述请求处理逻辑 ---
  
        if (pathname === '/ai-describe-image' && request.method === 'POST') {
  
            try {
  
                const requestBody = await request.json();
  
                const imageUrl = requestBody.imageUrl;
  

  
                if (!imageUrl) {
  
                    return new Response('Missing imageUrl in request body.', { status: 400 });
  
                }
  

  
                const description = await getGeminiImageDescription(imageUrl, env);
  

  
                return new Response(JSON.stringify({ description }), {
  
                    headers: { 'Content-Type': 'application/json', ...corsHeaders },
  
                });
  

  
            } catch (error) {
  
                console.error('AI image description request error:', error.message);
  
                return new Response(`Error processing AI image description request: ${error.message}`, { status: 500 });
  
            }
  
        }
  
  • if (pathname === '/ai-describe-image' && request.method === 'POST') { ... }: 处理 AI 图片描述请求。

    • const imageUrl = requestBody.imageUrl;: 从请求体中获取图片 URL。

    • if (!imageUrl) { ... }: 检查 imageUrl 是否存在。

    • const description = await getGeminiImageDescription(imageUrl, env);: 调用 getGeminiImageDescription 函数获取图片描述。

    • return new Response(JSON.stringify({ description }), { ... });: 返回包含图片描述的 JSON 响应。

    • catch (error) { ... }: 捕获错误并返回 500 错误响应。

  
        // --- 新增:获取房间用户统计数据的请求处理逻辑 ---
  
        if (pathname === '/room-user-stats' && request.method === 'GET') {
  
            try {
  
                const roomName = url.searchParams.get('roomName');
  
                if (!roomName) {
  
                    return new Response('Missing roomName in query parameters.', { status: 400 });
  
                }
  

  
                if (!env.CHAT_ROOM_DO) {
  
                    return new Response('Server configuration error: CHAT_ROOM_DO not bound.', { status: 500 });
  
                }
  
                const doId = env.CHAT_ROOM_DO.idFromName(roomName);
  
                const stub = env.CHAT_ROOM_DO.get(doId);
  

  
                // 向 Durable Object 发送内部请求获取统计数据
  
                const doResponse = await stub.fetch(new Request(`${url.origin}/user-stats`, { method: 'GET' }));
  
                
  
                if (!doResponse.ok) {
  
                    const errorText = await doResponse.text();
  
                    console.error(`Failed to fetch user stats from DO: ${doResponse.status} - ${errorText}`);
  
                    return new Response(`Error fetching user stats: ${errorText}`, { status: 500 });
  
                }
  

  
                const stats = await doResponse.json();
  
                return new Response(JSON.stringify(stats), {
  
                    headers: { 'Content-Type': 'application/json', ...corsHeaders },
  
                });
  

  
            } catch (error) {
  
                console.error('User stats request error:', error.message);
  
                return new Response(`Error processing user stats request: ${error.message}`, { status: 500 });
  
            }
  
        }
  
  • if (pathname === '/room-user-stats' && request.method === 'GET') { ... }: 处理获取聊天室用户统计数据的请求。

    • const roomName = url.searchParams.get('roomName');: 从 URL 查询参数中获取 roomName

    • if (!roomName) { ... }: 检查 roomName 是否存在。

    • if (!env.CHAT_ROOM_DO) { ... }: 检查 env 中是否绑定了 Durable Object。CHAT_ROOM_DO 是在 Cloudflare 配置中绑定的 Durable Object 的名称。

    • const doId = env.CHAT_ROOM_DO.idFromName(roomName);: 根据房间名获取 Durable Object 的 ID。对于每个唯一的房间名,都会对应一个唯一的 Durable Object 实例。

    • const stub = env.CHAT_ROOM_DO.get(doId);: 获取 Durable Object 的存根(stub)。存根是一个代理对象,允许 Worker 与 Durable Object 实例进行通信。

    • const doResponse = await stub.fetch(new Request(${url.origin}/user-stats, { method: 'GET' }));: 关键点:向 Durable Object 实例发送一个内部请求。这里 stub.fetch() 并不是向外部网络发送请求,而是调用 Durable Object 内部的 fetch 方法。请求的 URL /user-stats 是 Durable Object 内部定义的路由。

    • if (!doResponse.ok) { ... }: 检查 Durable Object 的响应是否成功。

    • const stats = await doResponse.json();: 解析 Durable Object 返回的 JSON 数据(用户统计信息)。

    • return new Response(JSON.stringify(stats), { ... });: 返回包含用户统计信息的 JSON 响应。

    • catch (error) { ... }: 捕获错误并返回 500 错误响应。

  
        // --- 新增:获取历史消息的请求处理逻辑 ---
  
        if (pathname === '/api/messages/history' && request.method === 'GET') {
  
            try {
  
                const roomName = url.searchParams.get('roomName');
  
                if (!roomName) {
  
                    return new Response('Missing roomName in query parameters.', { status: 400 });
  
                }
  

  
                if (!env.CHAT_ROOM_DO) {
  
                    return new Response('Server configuration error: CHAT_ROOM_DO not bound.', { status: 500 });
  
                }
  
                const doId = env.CHAT_ROOM_DO.idFromName(roomName);
  
                const stub = env.CHAT_ROOM_DO.get(doId);
  

  
                const doResponse = await stub.fetch(new Request(`${url.origin}/history-messages`, { method: 'GET' }));
  
                
  
                if (!doResponse.ok) {
  
                    const errorText = await doResponse.text();
  
                    console.error(`Failed to fetch history messages from DO: ${doResponse.status} - ${errorText}`);
  
                    return new Response(`Error fetching history messages: ${errorText}`, { status: 500 });
  
                }
  

  
                const messages = await doResponse.json();
  
                return new Response(JSON.stringify(messages), {
  
                    headers: { 'Content-Type': 'application/json', ...corsHeaders },
  
                });
  

  
            } catch (error) {
  
                console.error('History messages request error:', error.message);
  
                return new Response(`Error processing history messages request: ${error.message}`, { status: 500 });
  
            }
  
        }
  
  • if (pathname === '/api/messages/history' && request.method === 'GET') { ... }: 处理获取聊天室历史消息的请求。

    • 逻辑与 /room-user-stats 类似,也是通过 Durable Object 存根向对应的 Durable Object 实例发送内部请求 (/history-messages) 来获取数据。

    • const messages = await doResponse.json();: 解析 Durable Object 返回的 JSON 数据(历史消息列表)。

    • return new Response(JSON.stringify(messages), { ... });: 返回包含历史消息的 JSON 响应。

  
        // --- 剩余的路由逻辑保持不变 ---
  
        
  
        if (pathParts.length === 0) {
  
            return new Response('Welcome! Please access /<room-name> to join a chat room.', { status: 200 });
  
        }
  

  
        const roomName = pathParts[0];
  

  
        const upgradeHeader = request.headers.get("Upgrade");
  
        if (upgradeHeader === "websocket") {
  
            if (!env.CHAT_ROOM_DO) {
  
                return new Response('Server configuration error: CHAT_ROOM_DO not bound.', { status: 500 });
  
            }
  
            const doId = env.CHAT_ROOM_DO.idFromName(roomName);
  
            const stub = env.CHAT_ROOM_DO.get(doId);
  
            return stub.fetch(request);
  
        }
  

  
        return new Response(html, {
  
            headers: { 'Content-Type': 'text/html;charset=UTF-8' },
  
        });
  
    },
  
};
  
  • if (pathParts.length === 0) { ... }: 如果路径为空(即访问根路径 /),则返回一个欢迎消息。

  • const roomName = pathParts[0];: 获取路径的第一个部分作为房间名(例如 /myroom 中的 myroom)。

  • const upgradeHeader = request.headers.get("Upgrade");: 获取请求头中的 Upgrade 字段。

  • if (upgradeHeader === "websocket") { ... }: 如果 Upgrade 头部的值是 websocket,表示这是一个 WebSocket 升级请求。

    • if (!env.CHAT_ROOM_DO) { ... }: 检查 Durable Object 绑定。

    • const doId = env.CHAT_ROOM_DO.idFromName(roomName);: 根据房间名获取 Durable Object ID。

    • const stub = env.CHAT_ROOM_DO.get(doId);: 获取 Durable Object 存根。

    • return stub.fetch(request);: 关键点:将整个 WebSocket 升级请求直接转发给对应的 Durable Object 实例。Durable Object 会在其内部处理 WebSocket 连接的建立和管理。这是 Cloudflare Durable Object 处理 WebSocket 的标准方式。

  • return new Response(html, { headers: { 'Content-Type': 'text/html;charset=UTF-8' }, });: 如果以上所有条件都不满足(既不是特殊 API 路径,也不是 WebSocket 升级请求),则默认返回之前导入的 index.html 文件的内容,并设置正确的 Content-Type。这意味着访问 /some-room-name 这样的路径时,会返回前端 HTML 页面,然后前端 JavaScript 会尝试建立 WebSocket 连接。


总结:

这个 worker.js 文件是一个功能丰富的 Cloudflare Worker,它充当了整个应用的网关和协调器。它:

  • 路由请求:根据 URL 路径和 HTTP 方法将请求分发到不同的处理逻辑。

  • 管理 Durable Objects:通过 idFromNameget 方法获取 Durable Object 实例的存根,并将其用于处理 WebSocket 连接、获取聊天室统计和历史消息。

  • 集成外部服务:调用 DeepSeek 和 Google Gemini 等 AI 服务进行文本解释和图片描述。

  • 处理文件存储:利用 Cloudflare R2 进行文件上传。

  • 提供前端页面:在默认情况下返回 index.html 供用户访问。

  • 处理 CORS:确保跨域请求能够正常进行。

它将前端请求、后端逻辑(通过 Durable Objects)和第三方 AI/存储服务有效地连接起来,构建了一个完整的、可扩展的实时聊天和AI辅助应用。