兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
我将逐句解释这段 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。 --- ```javascript // src/worker.js ``` * **`// src/worker.js`**: 这是一个注释,表示当前文件的路径和名称。 ```javascript import { HibernatingChatRoom } from './chatroom_do.js'; ``` * **`import { HibernatingChatRoom } from './chatroom_do.js';`**: 从当前目录下的 `chatroom_do.js` 文件中导入 `HibernatingChatRoom` 类。`HibernatingChatRoom` 是一个 Cloudflare Durable Object 类,用于实现持久化的聊天室逻辑。 ```javascript import html from '../public/index.html'; ``` * **`import html from '../public/index.html';`**: 导入 `../public/index.html` 文件。在 Cloudflare Worker 中,可以通过这种方式将静态 HTML 文件作为字符串或模块导入,以便在 Worker 响应请求时直接返回其内容。 ```javascript // 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。 ```javascript // --- 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 小时内,浏览器不需要为相同的跨域请求再次发送预检请求。 ```javascript // 处理 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 && ...)`**: 检查请求头中是否包含 `Origin`、`Access-Control-Request-Method` 和 `Access-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`。 ```javascript // --- 新增:模块化的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-Type` 为 `application/json` 和 `Authorization`(携带 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;`**: 返回提取到的解释文本。 ```javascript /** * 调用 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` 来提取解释文本。 ```javascript // --- 新增: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 编码图片数据和内容类型的对象。 ```javascript 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;`**: 返回提取到的图片描述。 ```javascript // --- 主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']`)。 ```javascript // 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 存储桶,文件名为 `filename`。`httpMetadata` 选项将请求头作为对象的元数据存储。 * **`const publicUrl = `${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 错误响应。 ```javascript // --- 更新后的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 解释函数 (`getGeminiExplanation` 或 `getDeepSeekExplanation`)。如果模型未知,则返回 400 错误。 * **`return new Response(JSON.stringify({ explanation }), { ... });`**: 返回包含 AI 解释结果的 JSON 响应。 * **`catch (error) { ... }`**: 捕获处理过程中的错误,并返回 500 错误响应,包含具体的错误信息。 ```javascript // --- 新增: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 错误响应。 ```javascript // --- 新增:获取房间用户统计数据的请求处理逻辑 --- 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 错误响应。 ```javascript // --- 新增:获取历史消息的请求处理逻辑 --- 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 响应。 ```javascript // --- 剩余的路由逻辑保持不变 --- 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**:通过 `idFromName` 和 `get` 方法获取 Durable Object 实例的存根,并将其用于处理 WebSocket 连接、获取聊天室统计和历史消息。 * **集成外部服务**:调用 DeepSeek 和 Google Gemini 等 AI 服务进行文本解释和图片描述。 * **处理文件存储**:利用 Cloudflare R2 进行文件上传。 * **提供前端页面**:在默认情况下返回 `index.html` 供用户访问。 * **处理 CORS**:确保跨域请求能够正常进行。 它将前端请求、后端逻辑(通过 Durable Objects)和第三方 AI/存储服务有效地连接起来,构建了一个完整的、可扩展的实时聊天和AI辅助应用。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章