Refactor request handling with improved error management and header consolidation

This commit is contained in:
wood chen 2025-02-08 02:00:48 +08:00
parent 2d0565c72e
commit 2e1e7ee722

614
main.ts
View File

@ -51,7 +51,6 @@ interface Price {
} }
// 声明全局变量 // 声明全局变量
declare const kv: Deno.Kv;
declare const vendors: { [key: string]: Vendor }; declare const vendors: { [key: string]: Vendor };
// 缓存供应商数据 // 缓存供应商数据
@ -59,6 +58,16 @@ let vendorsCache: VendorResponse | null = null;
let vendorsCacheTime: number = 0; let vendorsCacheTime: number = 0;
const CACHE_DURATION = 1000 * 60 * 5; // 5分钟缓存 const CACHE_DURATION = 1000 * 60 * 5; // 5分钟缓存
// 初始化 KV 存储
let kv: Deno.Kv;
try {
kv = await Deno.openKv();
} catch (error) {
console.error('初始化 KV 存储失败:', error);
Deno.exit(1);
}
// 获取供应商数据 // 获取供应商数据
async function getVendors(): Promise<VendorResponse> { async function getVendors(): Promise<VendorResponse> {
const now = Date.now(); const now = Date.now();
@ -791,8 +800,6 @@ function validateData(data: any): string | null {
// 修改处理函数 // 修改处理函数
async function handler(req: Request): Promise<Response> { async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
const headers = { const headers = {
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
@ -800,348 +807,345 @@ async function handler(req: Request): Promise<Response> {
"Access-Control-Allow-Credentials": "true" "Access-Control-Allow-Credentials": "true"
}; };
if (req.method === "OPTIONS") { const jsonHeaders = {
return new Response(null, { headers }); ...headers,
} "Content-Type": "application/json"
};
// 登录处理 const htmlHeaders = {
if (url.pathname === "/api/auth/login") { ...headers,
const params = new URLSearchParams(url.search); "Content-Type": "text/html; charset=utf-8"
const returnUrl = params.get('return_url'); };
if (!returnUrl) {
return new Response(JSON.stringify({ error: "缺少 return_url 参数" }), { try {
status: 400, const url = new URL(req.url);
headers: {
"Content-Type": "application/json", if (req.method === "OPTIONS") {
...headers return new Response(null, { headers });
}
});
} }
const ssoUrl = await generateSSO(returnUrl); // 认证状态检查
return new Response(null, { if (url.pathname === "/api/auth/status") {
status: 302, try {
headers: { const username = await verifyDiscourseSSO(req);
...headers, return new Response(JSON.stringify({
"Location": ssoUrl authenticated: !!username,
user: username
}), { headers: jsonHeaders });
} catch (error) {
console.error('验证用户状态失败:', error);
return new Response(JSON.stringify({
error: "验证用户状态失败",
details: error.message
}), {
status: 500,
headers: jsonHeaders
});
} }
});
}
// SSO 回调处理
if (url.pathname === "/auth/callback") {
const params = new URLSearchParams(url.search);
const sso = params.get('sso');
const sig = params.get('sig');
if (!sso || !sig) {
return new Response("Invalid SSO parameters", {
status: 400,
headers: {
"Content-Type": "text/plain",
...headers
}
});
} }
try { // 登录处理
// 验证签名 if (url.pathname === "/api/auth/login") {
const key = await crypto.subtle.importKey( const params = new URLSearchParams(url.search);
"raw", const returnUrl = params.get('return_url');
new TextEncoder().encode(DISCOURSE_SSO_SECRET), if (!returnUrl) {
{ name: "HMAC", hash: "SHA-256" }, return new Response(JSON.stringify({ error: "缺少 return_url 参数" }), {
false, status: 400,
["sign"] headers: jsonHeaders
); });
const signature = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(sso)
);
const expectedSig = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
if (sig !== expectedSig) {
throw new Error('Invalid signature');
} }
// 解码 payload const ssoUrl = await generateSSO(returnUrl);
const payload = atob(sso);
const payloadParams = new URLSearchParams(payload);
// 验证 nonce
const nonce = payloadParams.get('nonce');
if (!nonce) {
throw new Error('Missing nonce');
}
const nonceData = await kv.get(['sso_nonce', nonce]);
if (!nonceData.value) {
throw new Error('Invalid or expired nonce');
}
// 删除已使用的 nonce
await kv.delete(['sso_nonce', nonce]);
const username = payloadParams.get('username');
if (!username) {
throw new Error('Missing username');
}
// 设置 session cookie
const sessionId = crypto.randomUUID();
await kv.set(['sessions', sessionId], {
username,
created_at: new Date().toISOString()
}, { expireIn: 24 * 60 * 60 * 1000 }); // 24小时过期
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
...headers, ...headers,
"Location": "/", "Location": ssoUrl
"Set-Cookie": `session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`
}
});
} catch (error) {
console.error('SSO 回调处理失败:', error);
return new Response("SSO verification failed: " + error.message, {
status: 400,
headers: {
"Content-Type": "text/plain",
...headers
}
});
}
}
// 登出处理
if (url.pathname === "/api/auth/logout" && req.method === "POST") {
const cookie = req.headers.get('cookie');
if (cookie) {
const sessionMatch = cookie.match(/session=([^;]+)/);
if (sessionMatch) {
const sessionId = sessionMatch[1];
await kv.delete(['sessions', sessionId]);
}
}
return new Response(JSON.stringify({ success: true }), {
headers: {
...headers,
"Content-Type": "application/json",
"Set-Control-Allow-Credentials": "true",
"Set-Cookie": "session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"
}
});
}
// 认证状态检查
if (url.pathname === "/api/auth/status") {
const username = await verifyDiscourseSSO(req);
return new Response(JSON.stringify({
authenticated: !!username,
user: username
}), {
headers: {
"Content-Type": "application/json",
...headers
}
});
}
// 价格审核
if (url.pathname.match(/^\/api\/prices\/\d+\/review$/)) {
const username = await verifyDiscourseSSO(req);
if (!username || username !== 'wood') {
return new Response(JSON.stringify({ error: "未授权" }), {
status: 403,
headers: {
"Content-Type": "application/json",
...headers
} }
}); });
} }
if (req.method === "POST") { // SSO 回调处理
if (url.pathname === "/auth/callback") {
const params = new URLSearchParams(url.search);
const sso = params.get('sso');
const sig = params.get('sig');
if (!sso || !sig) {
return new Response("Invalid SSO parameters", {
status: 400,
headers: {
"Content-Type": "text/plain",
...headers
}
});
}
try { try {
const id = url.pathname.split('/')[3]; // 验证签名
const { status } = await req.json(); const key = await crypto.subtle.importKey(
"raw",
if (status !== 'approved' && status !== 'rejected') { new TextEncoder().encode(DISCOURSE_SSO_SECRET),
throw new Error("无效的状态"); { name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(sso)
);
const expectedSig = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
if (sig !== expectedSig) {
throw new Error('Invalid signature');
} }
const prices = await readPrices(); // 解码 payload
const priceIndex = prices.findIndex(p => p.id === id); const payload = atob(sso);
const payloadParams = new URLSearchParams(payload);
if (priceIndex === -1) { // 验证 nonce
throw new Error("价格记录不存在"); const nonce = payloadParams.get('nonce');
if (!nonce) {
throw new Error('Missing nonce');
} }
prices[priceIndex].status = status; const nonceData = await kv.get(['sso_nonce', nonce]);
prices[priceIndex].reviewed_by = username; if (!nonceData.value) {
prices[priceIndex].reviewed_at = new Date().toISOString(); throw new Error('Invalid or expired nonce');
}
await writePrices(prices); // 删除已使用的 nonce
await kv.delete(['sso_nonce', nonce]);
return new Response(JSON.stringify({ success: true }), { const username = payloadParams.get('username');
headers: { if (!username) {
"Content-Type": "application/json", throw new Error('Missing username');
...headers }
// 设置 session cookie
const sessionId = crypto.randomUUID();
await kv.set(['sessions', sessionId], {
username,
created_at: new Date().toISOString()
}, { expireIn: 24 * 60 * 60 * 1000 }); // 24小时过期
return new Response(null, {
status: 302,
headers: {
...headers,
"Location": "/",
"Set-Cookie": `session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`
} }
}); });
} catch (error) { } catch (error) {
console.error('SSO 回调处理失败:', error);
return new Response("SSO verification failed: " + error.message, {
status: 400,
headers: {
"Content-Type": "text/plain",
...headers
}
});
}
}
// 登出处理
if (url.pathname === "/api/auth/logout" && req.method === "POST") {
const cookie = req.headers.get('cookie');
if (cookie) {
const sessionMatch = cookie.match(/session=([^;]+)/);
if (sessionMatch) {
const sessionId = sessionMatch[1];
await kv.delete(['sessions', sessionId]);
}
}
return new Response(JSON.stringify({ success: true }), {
headers: {
...jsonHeaders,
"Set-Cookie": "session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"
}
});
}
// 价格审核
if (url.pathname.match(/^\/api\/prices\/\d+\/review$/)) {
const username = await verifyDiscourseSSO(req);
if (!username || username !== 'wood') {
return new Response(JSON.stringify({ error: "未授权" }), {
status: 403,
headers: jsonHeaders
});
}
if (req.method === "POST") {
try {
const id = url.pathname.split('/')[3];
const { status } = await req.json();
if (status !== 'approved' && status !== 'rejected') {
throw new Error("无效的状态");
}
const prices = await readPrices();
const priceIndex = prices.findIndex(p => p.id === id);
if (priceIndex === -1) {
throw new Error("价格记录不存在");
}
prices[priceIndex].status = status;
prices[priceIndex].reviewed_by = username;
prices[priceIndex].reviewed_at = new Date().toISOString();
await writePrices(prices);
return new Response(JSON.stringify({ success: true }), {
headers: jsonHeaders
});
} catch (error) {
return new Response(JSON.stringify({
error: error.message || "审核失败"
}), {
status: 400,
headers: jsonHeaders
});
}
}
}
// 提交新价格
if (url.pathname === "/api/prices" && req.method === "POST") {
const username = await verifyDiscourseSSO(req);
if (!username) {
return new Response(JSON.stringify({ error: "请先登录" }), {
status: 401,
headers: jsonHeaders
});
}
try {
let rawData;
const contentType = req.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
rawData = await req.json();
} else if (contentType.includes("application/x-www-form-urlencoded")) {
const formData = await req.formData();
rawData = {};
for (const [key, value] of formData.entries()) {
rawData[key] = value;
}
} else {
throw new Error("不支持的内容类型");
}
console.log('接收到的数据:', rawData); // 添加日志
// 处理数据
const newPrice: Price = {
model: String(rawData.model).trim(),
billing_type: rawData.billing_type as 'tokens' | 'times',
channel_type: Number(rawData.channel_type),
currency: rawData.currency as 'CNY' | 'USD',
input_price: Number(rawData.input_price),
output_price: Number(rawData.output_price),
input_ratio: calculateRatio(Number(rawData.input_price), rawData.currency as 'CNY' | 'USD'),
output_ratio: calculateRatio(Number(rawData.output_price), rawData.currency as 'CNY' | 'USD'),
price_source: String(rawData.price_source),
status: 'pending',
created_by: username,
created_at: new Date().toISOString()
};
console.log('处理后的数据:', newPrice); // 添加日志
// 验证数据
const error = validatePrice(newPrice);
if (error) {
return new Response(JSON.stringify({ error }), {
status: 400,
headers: jsonHeaders
});
}
// 读取现有数据
const prices = await readPrices();
// 生成唯一ID
newPrice.id = Date.now().toString();
// 添加新数据
prices.push(newPrice);
// 保存数据
await writePrices(prices);
return new Response(JSON.stringify({ return new Response(JSON.stringify({
error: error.message || "审核失败" success: true,
data: newPrice
}), { }), {
status: 400, headers: jsonHeaders
headers: { });
"Content-Type": "application/json", } catch (error) {
...headers console.error("处理价格提交失败:", error);
} return new Response(JSON.stringify({
error: error.message,
details: "数据处理失败,请检查输入格式"
}), {
status: 500,
headers: jsonHeaders
}); });
} }
} }
}
// 提交新价格 // 获取价格列表
if (url.pathname === "/api/prices" && req.method === "POST") { if (url.pathname === "/api/prices" && req.method === "GET") {
const username = await verifyDiscourseSSO(req); try {
if (!username) { const prices = await readPrices();
return new Response(JSON.stringify({ error: "请先登录" }), { return new Response(JSON.stringify(prices), {
status: 401, headers: jsonHeaders
headers: { });
"Content-Type": "application/json", } catch (error) {
...headers console.error('获取价格列表失败:', error);
} return new Response(JSON.stringify({
}); error: "获取价格列表失败",
} details: error.message
}), {
try { status: 500,
let rawData; headers: jsonHeaders
const contentType = req.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
rawData = await req.json();
} else if (contentType.includes("application/x-www-form-urlencoded")) {
const formData = await req.formData();
rawData = {};
for (const [key, value] of formData.entries()) {
rawData[key] = value;
}
} else {
throw new Error("不支持的内容类型");
}
console.log('接收到的数据:', rawData); // 添加日志
// 处理数据
const newPrice: Price = {
model: String(rawData.model).trim(),
billing_type: rawData.billing_type as 'tokens' | 'times',
channel_type: Number(rawData.channel_type),
currency: rawData.currency as 'CNY' | 'USD',
input_price: Number(rawData.input_price),
output_price: Number(rawData.output_price),
input_ratio: calculateRatio(Number(rawData.input_price), rawData.currency as 'CNY' | 'USD'),
output_ratio: calculateRatio(Number(rawData.output_price), rawData.currency as 'CNY' | 'USD'),
price_source: String(rawData.price_source),
status: 'pending',
created_by: username,
created_at: new Date().toISOString()
};
console.log('处理后的数据:', newPrice); // 添加日志
// 验证数据
const error = validatePrice(newPrice);
if (error) {
return new Response(JSON.stringify({ error }), {
status: 400,
headers: {
"Content-Type": "application/json",
...headers
}
}); });
} }
}
// 读取现有数据
const prices = await readPrices(); // 提供静态页面
if (url.pathname === "/" || url.pathname === "/index.html") {
// 生成唯一ID return new Response(html, {
newPrice.id = Date.now().toString(); headers: htmlHeaders
// 添加新数据
prices.push(newPrice);
// 保存数据
await writePrices(prices);
return new Response(JSON.stringify({
success: true,
data: newPrice
}), {
headers: {
"Content-Type": "application/json",
...headers
}
});
} catch (error) {
console.error("处理价格提交失败:", error);
return new Response(JSON.stringify({
error: error.message,
details: "数据处理失败,请检查输入格式"
}), {
status: 500,
headers: {
"Content-Type": "application/json",
...headers
}
}); });
} }
}
return new Response("Not Found", {
// 获取价格列表 status: 404,
if (url.pathname === "/api/prices" && req.method === "GET") { headers: {
try { ...headers,
const prices = await readPrices(); "Content-Type": "text/plain"
return new Response(JSON.stringify(prices), {
headers: {
"Content-Type": "application/json",
...headers
}
});
} catch (error) {
console.error('获取价格列表失败:', error);
return new Response(JSON.stringify({
error: "获取价格列表失败",
details: error.message
}), {
status: 500,
headers: {
"Content-Type": "application/json",
...headers
}
});
}
}
// 提供静态页面
if (url.pathname === "/" || url.pathname === "/index.html") {
return new Response(html, {
headers: {
"Content-Type": "text/html; charset=utf-8",
...headers
} }
}); });
} catch (error) {
console.error('处理请求失败:', error);
return new Response(JSON.stringify({
error: "处理请求失败",
details: error.message
}), {
status: 500,
headers: jsonHeaders
});
} }
return new Response("Not Found", {
status: 404,
headers
});
} }
// 启动服务器 // 启动服务器