每日签到奶昔超市点数市场奶昔访达
返回列表 发布新帖
查看: 383|回复: 0

一个有点简陋的2api

发表于 2025-8-15 16:19:27 | 查看全部 |阅读模式

欢迎注册论坛,享受更多奶昔会员权益!

您需要 登录 才可以下载或查看,没有账号?注册

×
全部通过AI生成,运行在deno平台,功能有点简陋,有能力的大佬可以完善一下

  1. import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

  2. // ===== 配置 =====
  3. const TARGET_API = Deno.env.get("TARGET_API");
  4. const TARGET_MODEL = Deno.env.get("TARGET_MODEL");
  5. const MODEL_ALIAS = Deno.env.get("MODEL_ALIAS");
  6. const API_KEY = Deno.env.get("API_KEY");

  7. if (!API_KEY) {
  8.   console.error("❌ 缺少 API_KEY 环境变量");
  9.   Deno.exit(1);
  10. }

  11. //密钥列表)
  12. const authorizedKeys: Record<string, string> = {
  13.   "用户认证密钥": "备注",
  14.   // 你可以添加更多用户密钥
  15. };

  16. // ===== 工具函数 =====
  17. function checkAuth(req: Request): string | null {
  18.   const authHeader = req.headers.get("Authorization");
  19.   if (!authHeader || !authHeader.startsWith("Bearer ")) {
  20.     return null;
  21.   }
  22.   const token = authHeader.slice(7).trim();
  23.   if (token in authorizedKeys) {
  24.     return token; // 返回通过验证的密钥(也可以返回用户名等)
  25.   }
  26.   return null;
  27. }

  28. function jsonError(message: string, status = 401) {
  29.   return new Response(
  30.     JSON.stringify({
  31.       error: {
  32.         message,
  33.         type: "authentication_error",
  34.         status,
  35.       },
  36.     }),
  37.     {
  38.       status,
  39.       headers: { "Content-Type": "application/json; charset=utf-8" },
  40.     }
  41.   );
  42. }

  43. // ===== 主服务 =====
  44. serve(async (req) => {
  45.   const url = new URL(req.url);
  46.   const pathname = url.pathname;

  47.   // 需要验证的路径
  48.   const protectedPaths = ["/v1/models", "/v1/chat/completions"];
  49.   if (protectedPaths.includes(pathname)) {
  50.     const userKey = checkAuth(req);
  51.     if (!userKey) {
  52.       return jsonError("Invalid or missing access token", 401);
  53.     }
  54.     // 这里可以用 userKey 做日志、限流、配额等后续处理
  55.     console.log(`用户密钥验证通过: ${userKey}`);
  56.   }

  57.   if (pathname === "/v1/models") {
  58.     return new Response(
  59.       JSON.stringify({
  60.         object: "list",
  61.         data: [
  62.           {
  63.             id: MODEL_ALIAS,
  64.             object: "model",
  65.             created: Date.now(),
  66.             owned_by: "gmi-cloud",
  67.           },
  68.         ],
  69.       }),
  70.       { headers: { "Content-Type": "application/json; charset=utf-8" } },
  71.     );
  72.   }

  73.   if (pathname === "/v1/chat/completions") {
  74.     if (req.method !== "POST") {
  75.       return new Response("Method Not Allowed", { status: 405 });
  76.     }

  77.     const body = await req.json();
  78.     const isStream = body.stream === true;

  79.     if (body.model === MODEL_ALIAS) {
  80.       body.model = TARGET_MODEL;
  81.     } else if (body.model !== TARGET_MODEL) {
  82.       return new Response(
  83.         JSON.stringify({
  84.           error: {
  85.             message: `Only model "${MODEL_ALIAS}" is supported.`,
  86.             type: "invalid_request_error",
  87.           },
  88.         }),
  89.         {
  90.           status: 400,
  91.           headers: { "Content-Type": "application/json; charset=utf-8" },
  92.         },
  93.       );
  94.     }

  95.     const res = await fetch(TARGET_API, {
  96.       method: "POST",
  97.       headers: {
  98.         "Authorization": `Bearer ${API_KEY}`,
  99.         "Content-Type": "application/json",
  100.         "Accept": "application/json",
  101.       },
  102.       body: JSON.stringify(body),
  103.     });

  104.     if (!isStream) {
  105.       const text = await res.text();
  106.       return new Response(text, {
  107.         status: res.status,
  108.         headers: { "Content-Type": "application/json; charset=utf-8" },
  109.       });
  110.     }

  111.     const readableStream = new ReadableStream({
  112.       async start(controller) {
  113.         const reader = res.body?.getReader();
  114.         const decoder = new TextDecoder();
  115.         const encoder = new TextEncoder();

  116.         if (!reader) {
  117.           controller.enqueue(encoder.encode("data: [DONE]\n\n"));
  118.           controller.close();
  119.           return;
  120.         }

  121.         try {
  122.           while (true) {
  123.             const { done, value } = await reader.read();
  124.             if (done) break;
  125.             controller.enqueue(encoder.encode(decoder.decode(value, { stream: true })));
  126.           }
  127.         } finally {
  128.           controller.close();
  129.         }
  130.       },
  131.     });

  132.     return new Response(readableStream, {
  133.       status: 200,
  134.       headers: {
  135.         "Content-Type": "text/event-stream; charset=utf-8",
  136.         "Cache-Control": "no-cache",
  137.         "Connection": "keep-alive",
  138.         "Access-Control-Allow-Origin": "*",
  139.       },
  140.     });
  141.   }

  142.   if (pathname === "/v1/test") {
  143.     const testBody = {
  144.       model: TARGET_MODEL,
  145.       messages: [{ role: "user", content: "你好,请返回你好。" }],
  146.       temperature: 0.3,
  147.       stream: false,
  148.     };

  149.     try {
  150.       const [res, ipRes] = await Promise.all([
  151.         fetch(TARGET_API, {
  152.           method: "POST",
  153.           headers: {
  154.             "Authorization": `Bearer ${API_KEY}`,
  155.             "Content-Type": "application/json",
  156.             "Accept": "application/json",
  157.           },
  158.           body: JSON.stringify(testBody),
  159.         }),
  160.         fetch("https://api.ipify.org?format=json"),
  161.       ]);

  162.       const [json, ipJson] = await Promise.all([res.json(), ipRes.json()]);

  163.       const now = new Intl.DateTimeFormat("zh-CN", {
  164.         timeZone: "Asia/Shanghai",
  165.         dateStyle: "full",
  166.         timeStyle: "long",
  167.       }).format(new Date());

  168.       if (res.ok) {
  169.         return new Response(
  170.           JSON.stringify({
  171.             success: true,
  172.             message: "API 可用",
  173.             ip: ipJson.ip,
  174.             now,
  175.           }),
  176.           { headers: { "Content-Type": "application/json; charset=utf-8" } },
  177.         );
  178.       } else {
  179.         return new Response(
  180.           JSON.stringify({
  181.             success: false,
  182.             message: "API 响应异常",
  183.             ip: ipJson.ip,
  184.             now,
  185.             error: json,
  186.           }),
  187.           {
  188.             status: res.status,
  189.             headers: { "Content-Type": "application/json; charset=utf-8" },
  190.           },
  191.         );
  192.       }
  193.     } catch (err) {
  194.       return new Response(
  195.         JSON.stringify({
  196.           success: false,
  197.           message: "请求失败",
  198.           error: err.message,
  199.         }),
  200.         { status: 500, headers: { "Content-Type": "application/json; charset=utf-8" } },
  201.       );
  202.     }
  203.   }

  204.     return new Response("Not Found", { status: 404 });
  205. });
复制代码
爱生活,爱奶昔~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

© 2025 Nyarime 沪ICP备13020230号-1|沪公网安备 31010702007642号手机版小黑屋RSS
返回顶部 关灯 在本版发帖
快速回复 返回顶部 返回列表