import { serve } from "std/http/server.ts"; import { crypto } from "std/crypto/mod.ts"; import { encode as base64Encode, decode as base64Decode } from "std/encoding/base64.ts"; import { createHmac } from "crypto"; // 类型定义 interface Vendor { id: number; name: string; icon: string; } interface VendorResponse { data: { [key: string]: Vendor; }; } interface Price { id?: string; model: string; billing_type: 'tokens' | 'times'; channel_type: number; currency: 'CNY' | 'USD'; input_price: number; output_price: number; input_ratio: number; output_ratio: number; price_source: string; status: 'pending' | 'approved' | 'rejected'; created_by: string; created_at: string; reviewed_by?: string; reviewed_at?: string; } // 缓存供应商数据 let vendorsCache: VendorResponse | null = null; let vendorsCacheTime: number = 0; const CACHE_DURATION = 1000 * 60 * 5; // 5分钟缓存 // 获取供应商数据 async function getVendors(): Promise { const now = Date.now(); if (vendorsCache && (now - vendorsCacheTime) < CACHE_DURATION) { return vendorsCache; } try { const response = await fetch('https://oapi.czl.net/api/ownedby'); const data = await response.json() as VendorResponse; vendorsCache = data; vendorsCacheTime = now; return data; } catch (error) { console.error('获取供应商数据失败:', error); throw new Error('获取供应商数据失败'); } } // 计算倍率 function calculateRatio(price: number, currency: 'CNY' | 'USD'): number { return currency === 'USD' ? price / 2 : price / 14; } // 验证价格数据 function validatePrice(data: any): string | null { if (!data.model || !data.billing_type || !data.channel_type || !data.currency || data.input_price === undefined || data.output_price === undefined || !data.price_source) { return "所有字段都是必需的"; } if (data.billing_type !== 'tokens' && data.billing_type !== 'times') { return "计费类型必须是 tokens 或 times"; } if (data.currency !== 'CNY' && data.currency !== 'USD') { return "币种必须是 CNY 或 USD"; } if (isNaN(data.input_price) || isNaN(data.output_price)) { return "价格必须是数字"; } if (data.input_price < 0 || data.output_price < 0) { return "价格不能为负数"; } return null; } // 添加 Discourse SSO 配置 const DISCOURSE_URL = Deno.env.get('DISCOURSE_URL') || 'https://discourse.czl.net'; const DISCOURSE_SSO_SECRET = Deno.env.get('DISCOURSE_SSO_SECRET'); // 验证必需的环境变量 if (!DISCOURSE_SSO_SECRET) { console.error('错误: 必须设置 DISCOURSE_SSO_SECRET 环境变量'); Deno.exit(1); } // 添加认证相关函数 async function verifyDiscourseSSO(request: Request): Promise { const cookie = request.headers.get('cookie'); if (!cookie) return null; const sessionMatch = cookie.match(/session=([^;]+)/); if (!sessionMatch) return null; const sessionId = sessionMatch[1]; const session = await kv.get(['sessions', sessionId]); if (!session.value) return null; return session.value.username; } // 修改 generateSSO 函数为异步函数 async function generateSSO(returnUrl: string): Promise { const payload = base64Encode(`return_sso_url=${encodeURIComponent(returnUrl)}`); const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(DISCOURSE_SSO_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(payload) ); const sig = Array.from(new Uint8Array(signature)) .map(b => b.toString(16).padStart(2, '0')) .join(''); return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(payload)}&sig=${sig}`; } // HTML 页面 const html = ` AI Models Price API
模型名称 计费类型 供应商 币种 输入价格(M) 输出价格(M) 输入倍率 输出倍率 价格依据 状态 操作
加载中...
`; // 使用 Deno KV 存储数据 const kv = await Deno.openKv(); // 读取价格数据 async function readPrices(): Promise { const prices = await kv.get(["prices"]); return prices.value || []; } // 写入价格数据 async function writePrices(prices: any[]): Promise { await kv.set(["prices"], prices); } // 修改验证函数 function validateData(data: any): string | null { if (!data.model || !data.type || data.channel_type === undefined || data.input === undefined || data.output === undefined) { return "所有字段都是必需的"; } // 确保数字字段是数字类型 const channel_type = Number(data.channel_type); const input = Number(data.input); const output = Number(data.output); if (isNaN(channel_type) || isNaN(input) || isNaN(output)) { return "数字字段格式无效"; } // 验证数字范围(允许等于0) if (channel_type < 0 || input < 0 || output < 0) { return "数字不能小于0"; } return null; } // 修改处理函数 async function handler(req: Request): Promise { const url = new URL(req.url); const headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Cookie", "Access-Control-Allow-Credentials": "true" }; if (req.method === "OPTIONS") { return new Response(null, { headers }); } // 登录处理 if (url.pathname === "/api/auth/login") { const params = new URLSearchParams(url.search); const returnUrl = params.get('return_url'); if (!returnUrl) { return new Response(JSON.stringify({ error: "缺少 return_url 参数" }), { status: 400, headers: { "Content-Type": "application/json", ...headers } }); } const ssoUrl = await generateSSO(returnUrl); return new Response(null, { status: 302, headers: { ...headers, "Location": ssoUrl } }); } // 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 { // 验证签名 const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(DISCOURSE_SSO_SECRET), { 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'); } // 解码 payload const payload = new TextDecoder().decode(base64Decode(sso)); const payloadParams = new URLSearchParams(payload); 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, { status: 302, headers: { ...headers, "Location": "/", "Set-Cookie": `session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` } }); } catch (error) { console.error('SSO 回调处理失败:', error); return new Response("SSO verification failed", { 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") { 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: { "Content-Type": "application/json", ...headers } }); } catch (error) { return new Response(JSON.stringify({ error: error.message || "审核失败" }), { status: 400, headers: { "Content-Type": "application/json", ...headers } }); } } } // 提交新价格 if (url.pathname === "/api/prices" && req.method === "POST") { const username = await verifyDiscourseSSO(req); if (!username) { return new Response(JSON.stringify({ error: "请先登录" }), { status: 401, headers: { "Content-Type": "application/json", ...headers } }); } 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("不支持的内容类型"); } // 处理数据 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() }; // 验证数据 const error = validatePrice(newPrice); if (error) { return new Response(JSON.stringify({ error }), { status: 400, headers: { "Content-Type": "application/json", ...headers } }); } // 读取现有数据 const prices = await readPrices(); // 生成唯一ID newPrice.id = Date.now().toString(); // 添加新数据 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("Processing error:", error); return new Response(JSON.stringify({ error: error.message, details: "数据处理失败,请检查输入格式" }), { status: 500, headers: { "Content-Type": "application/json", ...headers } }); } } // 获取价格列表 if (url.pathname === "/api/prices" && req.method === "GET") { const prices = await readPrices(); return new Response(JSON.stringify(prices), { 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 } }); } return new Response("Not Found", { status: 404, headers }); } // 启动服务器 serve(handler);