我将逐句解释这段 Cloudflare Worker 代码。
这个 worker.js 文件是 Cloudflare Worker 的入口点,它负责处理所有传入的 HTTP 请求,并根据请求的路径和方法将其路由到不同的处理逻辑,包括:
-
Durable Object (DO) 管理:用于持久化聊天室状态。
-
CORS 处理:允许跨域请求。
-
文件上传:将文件存储到 Cloudflare R2。
-
AI 服务集成:调用 DeepSeek 和 Google Gemini API 进行文本解释和图片描述。
-
聊天室统计和历史消息:通过 Durable Object 获取数据。
-
静态文件服务:提供
index.html页面。 -
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 && ...): 检查请求头中是否包含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。
// --- 新增:模块化的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;: 返回提取到的解释文本。
-
/**
* 调用 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 存储桶,文件名为filename。httpMetadata选项将请求头作为对象的元数据存储。 -
const publicUrl ={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 解释函数 (getGeminiExplanation或getDeepSeekExplanation)。如果模型未知,则返回 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:通过
idFromName和get方法获取 Durable Object 实例的存根,并将其用于处理 WebSocket 连接、获取聊天室统计和历史消息。 -
集成外部服务:调用 DeepSeek 和 Google Gemini 等 AI 服务进行文本解释和图片描述。
-
处理文件存储:利用 Cloudflare R2 进行文件上传。
-
提供前端页面:在默认情况下返回
index.html供用户访问。 -
处理 CORS:确保跨域请求能够正常进行。
它将前端请求、后端逻辑(通过 Durable Objects)和第三方 AI/存储服务有效地连接起来,构建了一个完整的、可扩展的实时聊天和AI辅助应用。