@arcjet/next

中文文档
1.15.0发布于 2026/10/29Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 30万

Arcjet runtime security SDK for Next.js — 机器人保护、限速、即时注射检测、PII阻塞和WAF

中文使用文档
文档目录22 节
  1. 为什么是Arcjet?
  2. 开始
  3. 快速设置 AI 代理
  4. 手动设置
  5. 特征
  6. 快开始
  7. 快速注射检测
  8. 瓶装保护
  9. 瓶类
  10. 已验证 vs spoofed bots
  11. 限制费率
  12. 敏感信息检测
  13. 盾牌 WAF
  14. 电子邮件验证
  15. 请求过滤器
  16. 按国家分列的区块
  17. 屏蔽 VPN 和代理流量
  18. 知识产权分析
  19. 自定义特性
  20. 服务器组件和行动
  21. 最佳做法
  22. 许可证
Arcjet Logo

@arcjet/next

npm badge

弧喷 是您AI代码中的运行时安全平台。 检测出快速注射,授权代理工具呼叫,编辑敏感数据,并阻断bots和滥用. 在行动发生前,您在应用程序内拨打实时安全构件.

这是 弧喷 SDK 为 下一个.js 请求保护 —— 说 使用它来保护HTTP路由处理器和API端点. 如果你需要保护 AI代理工具呼叫, MCP 服务器处理器, 或背景任务( 任何没有 hTTP 请求,参见 @arcjet/guard。 。 。 .

为什么是Arcjet?

您的应用程序的AI功能和代理采取真实的行动,呼叫工具,读取数据,打击API. Arcjet在代码内运行 让你实时执行每个动作的安全 然后审计发生了什么

开始

快速设置 AI 代理

  1. 登录 CLI :
    npx @arcjet/cli auth login
    
  2. 安装版本代理技能, 这样您的编码代理匹配 SDK :
    npx @tanstack/intent@latest install
    
  3. 告诉你的探员要保护什么 剩下的就交给他.

手动设置

  1. 登录 使用 CLI(或 app.arcjet.com:
    npx @arcjet/cli auth login
    
  2. npm install @arcjet/next
  3. 设定 ARCJET_KEY=ajkey_yourkey 内 .env.local
  4. 将 Arcjet 添加到您的路线中 — 见 快速开始 下级

npm 软件包 | GitHub 来源 | 完整文档 | 其他 SDKs 软件

特征

以下所有特征均可使用 @arcjet/next 请求保护. 警卫保护(工具呼叫、MCP服务器、队列)见 @arcjet/guard。 。 。 .

特性@arcjet/next@arcjet/guard
限制费率✅✅
快速注射检测✅✅
敏感信息检测✅✅
瓶装保护✅—— 说
盾牌 WAF✅—— 说
电子邮件验证✅—— 说
注册表保护✅—— 说
请求过滤器✅—— 说
IP 分析✅—— 说
自定义规则—— 说✅
  • 🔒 快速注射检测 - 探测和阻断 在到达你的LLM前立即注射.
  • 🤖 瓶装保护 · 停止刮刮机、证明材料和 AI爬行者滥用你的终点.
  • 🛑 限制费率 - 标志桶、固定窗口和滑动 窗口算法; 每个用户的模型 AI 符号预算.
  • 🕵️ 敏感信息检测 - 块 PII,信用卡和定制模式 从进入你的AI管道.
  • 🛡️ 盾牌 WAF • 防止SQL注射、XSS和其他 常见的网络攻击.
  • 📧 电子邮件验证 一次性、无效和 注册时无法提供地址 .
  • 📝 注册表保护 结合机器人保护, 电子邮件验证, 以及限制您注册表格的速率 .
  • 🎯 请求过滤器 ——IP,路径上以表达为主的规则, 标题和自定义字段.
  • 🌐 IP 分析 地理定位、ASN、VPN、代理、Tor和托管 检测包括每个请求.
  • 🚅 无锡 ——设置安全头等 Content-Security-Policy (中文(简体) ).

快开始

此示例保护 Next.js AI 聊天路由: 屏蔽自动客户端 增加费用、执行每个用户代号的预算、检测敏感信息 在信息中,并阻止快速注射攻击 在他们到达模型之前.

安装 维塞尔 AI SDK,一个AI提供者,和 兰巴 敏感信息后端 :

npm install ai @ai-sdk/openai @arcjet/sensitive-info-rampart

创建一个新的 API 路由 /app/api/chat/route.ts数字 :

import { openai } from "@ai-sdk/openai";
import arcjet, {
  detectBot,
  detectPromptInjection,
  sensitiveInfo,
  shield,
  tokenBucket,
} from "@arcjet/next";
import { rampart } from "@arcjet/sensitive-info-rampart";
import type { UIMessage } from "ai";
import { convertToModelMessages, isTextUIPart, streamText } from "ai";

const aj = arcjet({
  key: process.env.ARCJET_KEY!, // Get your key with: npx @arcjet/cli sites get-key
  // Track budgets per user — replace "userId" with any stable identifier
  characteristics: ["userId"],
  rules: [
    // Shield protects against common web attacks e.g. SQL injection
    shield({ mode: "LIVE" }),
    // Block all automated clients — bots inflate AI costs
    detectBot({
      mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
      allow: [], // Block all bots. See https://arcjet.com/bot-list
    }),
    // Enforce budgets to control AI costs. Adjust rates and limits as needed.
    tokenBucket({
      mode: "LIVE",
      refillRate: 2_000, // Refill 2,000 tokens per hour
      interval: "1h",
      capacity: 5_000, // Maximum 5,000 tokens in the bucket
    }),
    // Block messages containing sensitive information to prevent data leaks
    sensitiveInfo({
      mode: "LIVE",
      // Block PII types that should never appear in AI prompts.
      // Remove types your app legitimately handles (e.g. EMAIL for a support bot).
      deny: ["CREDIT_CARD_NUMBER", "EMAIL"],
      // Detect PII on-device with the Rampart model
      backend: rampart(),
    }),
    // Detect prompt injection attacks before they reach your AI model
    detectPromptInjection({
      mode: "LIVE",
    }),
  ],
});

export async function POST(req: Request) {
  const userId = "user-123"; // Replace with your session/auth lookup
  const { messages }: { messages: UIMessage[] } = await req.json();
  const modelMessages = await convertToModelMessages(messages);

  // Estimate token cost: ~1 token per 4 characters of text (rough heuristic).
  // For accurate counts use https://www.npmjs.com/package/tiktoken
  const totalChars = modelMessages.reduce((sum, m) => {
    const content =
      typeof m.content === "string" ? m.content : JSON.stringify(m.content);
    return sum + content.length;
  }, 0);
  const estimate = Math.ceil(totalChars / 4);

  // Extract the most recent user message to scan for injection and PII.
  // Pass all messages if you want to scan the full conversation.
  const lastMessage: string = (messages.at(-1)?.parts ?? [])
    .filter(isTextUIPart)
    .map((p) => p.text)
    .join(" ");

  const decision = await aj.protect(req, {
    userId,
    requested: estimate,
    sensitiveInfoValue: lastMessage,
    detectPromptInjectionMessage: lastMessage,
  });

  if (decision.isDenied()) {
    if (decision.reason.isBot()) {
      return new Response("Automated clients are not permitted", {
        status: 403,
      });
    } else if (decision.reason.isRateLimit()) {
      return new Response("AI usage limit exceeded", { status: 429 });
    } else if (decision.reason.isSensitiveInfo()) {
      return new Response("Sensitive information detected", { status: 400 });
    } else if (decision.reason.isPromptInjection()) {
      return new Response(
        "Prompt injection detected — please rephrase your message",
        { status: 400 },
      );
    } else {
      return new Response("Forbidden", { status: 403 });
    }
  }

  const result = await streamText({
    model: openai("gpt-4o"),
    messages: modelMessages,
  });

  return result.toUIMessageStreamResponse();
}

Rampart 装入本地的 ONNX 运行时间, 所以添加 @arcjet/sensitive-info-rampart, (中文(简体) ), @huggingface/transformers,以及 onnxruntime-node 改为 serverExternalPackages 内 next.config.js 在节点运行路线 运行时间( 默认) 。 见 Bundlers和框架。 。 。 .

全文见 Arcjet Next.js SDK 文档。 。 。 .

快速注射检测

检测和阻止快速注射攻击——试图推翻你的AI 模型的指示——在他们到达你的模型之前. 传递用户消息 通过 detectPromptInjectionMessage 每个 protect() 电话.

import arcjet, { detectPromptInjection } from "@arcjet/next";
import { NextResponse } from "next/server";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    detectPromptInjection({
      mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
    }),
  ],
});

export async function POST(request: Request) {
  const { message } = await request.json();

  const decision = await aj.protect(request, {
    detectPromptInjectionMessage: message,
  });

  if (decision.isDenied() && decision.reason.isPromptInjection()) {
    return NextResponse.json(
      { error: "Prompt injection detected — please rephrase your message" },
      { status: 400 },
    );
  }

  // Forward to your AI model...
}

瓶装保护

Arcjet允许您配置一个 bots 列表以允许或拒绝 。 指定 allow 意味着所有其他的bots被否定。 一个空的允许列表块全部块 .

可用类别 : CATEGORY:ACADEMIC, (中文(简体) ), CATEGORY:ADVERTISING, (中文(简体) ), CATEGORY:AI, (中文(简体) ), CATEGORY:AMAZON, (中文(简体) ), CATEGORY:APPLE, (中文(简体) ), CATEGORY:ARCHIVE, (中文(简体) ), CATEGORY:BOTNET, (中文(简体) ), CATEGORY:FEEDFETCHER, (中文(简体) ), CATEGORY:GOOGLE, (中文(简体) ), CATEGORY:META, (中文(简体) ), CATEGORY:MICROSOFT, (中文(简体) ), CATEGORY:MONITOR, (中文(简体) ), CATEGORY:OPTIMIZER, (中文(简体) ), CATEGORY:PREVIEW, (中文(简体) ), CATEGORY:PROGRAMMATIC, (中文(简体) ), CATEGORY:SEARCH_ENGINE, (中文(简体) ), CATEGORY:SLACK, (中文(简体) ), CATEGORY:SOCIAL, (中文(简体) ), CATEGORY:TOOL, (中文(简体) ), CATEGORY:UNKNOWN, (中文(简体) ), CATEGORY:VERCEL, (中文(简体) ), CATEGORY:WEBHOOK, (中文(简体) ), CATEGORY:YAHOO。也可以允许或拒绝 按名称排列的特定块。 。 。 .

import arcjet, { detectBot } from "@arcjet/next";
import { isSpoofedBot } from "@arcjet/inspect";
import { NextResponse } from "next/server";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    detectBot({
      mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
      allow: [
        "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc
        // Uncomment to allow these other common bot categories:
        // "CATEGORY:MONITOR",  // Uptime monitoring services
        // "CATEGORY:PREVIEW",  // Link previews e.g. Slack, Discord
        // See the full list at https://arcjet.com/bot-list
      ],
    }),
  ],
});

export async function GET(request: Request) {
  const decision = await aj.protect(request);

  if (decision.isDenied() && decision.reason.isBot()) {
    return NextResponse.json({ error: "No bots allowed" }, { status: 403 });
  }

  // Verifies the authenticity of common bots using IP data.
  // Verification isn't always possible, so check the results separately.
  // https://docs.arcjet.com/bot-protection/reference#bot-verification
  if (decision.results.some(isSpoofedBot)) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  return NextResponse.json({ message: "Hello world" });
}

瓶类

波茨可以被配置为 类别 和(或) 具体 机器人名称。例如,允许搜索引擎和OpenAI 爬行者,但否认 所有其他bots:

detectBot({
  mode: "LIVE",
  allow: ["CATEGORY:SEARCH_ENGINE", "OPENAI_CRAWLER_SEARCH"],
});

已验证 vs spoofed bots

自称是知名爬行者(如:Googlebot)的Bots通过验证 检查他们的IP地址 与已知的IP范围 对于那个机器人。 如果一个机器人 无法校验, 它被标记为 soof 。 使用 isSpoofedBot 从 @arcjet/inspect 检查 :

import { isSpoofedBot } from "@arcjet/inspect";

if (decision.results.some(isSpoofedBot)) {
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}

限制费率

Arcjet支持符号桶,固定窗口和滑动窗口算法. Token桶是控制AI符号预算的理想工具——设置 capacity 改为 用户可以花费的最大代币, refillRate 以还原多少个令牌 百分比 interval,并通过 requested 内 protect()。 。 。 . 这个 interval 接受字符串( r)"1s", (中文(简体) ), "1m", (中文(简体) ), "1h", (中文(简体) ), "1d")或秒为 一个数字。 使用 characteristics 以跟踪每个用户的限制,而不是每个IP.

import arcjet, { tokenBucket } from "@arcjet/next";
import { NextResponse } from "next/server";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  characteristics: ["userId"], // Track per user
  rules: [
    // Token bucket: ideal for controlling AI token costs
    tokenBucket({
      mode: "LIVE",
      refillRate: 2_000, // Refill 2,000 tokens per hour
      interval: "1h",
      capacity: 5_000, // Maximum 5,000 tokens in the bucket
    }),
  ],
});

const decision = await aj.protect(request, {
  userId: "user-123",
  requested: estimate, // Number of tokens to deduct
});

if (decision.isDenied() && decision.reason.isRateLimit()) {
  return NextResponse.json(
    { error: "AI usage limit exceeded" },
    { status: 429 },
  );
}

敏感信息检测

检测和封存请求中的 PII 内容 @arcjet/sensitive-info-rampart, (中文(简体) ), 在本地运行着Rampart命名实体识别模型,所以没有数据 离开你的环境。 通过扫描传送内容 sensitiveInfoValue 打开 每个 protect() 电话 Rampart侦测到姓名、地址、政府和 财务识别符、电子邮件地址、电话号码、IP地址和卡片 数字。 添加识别器以检测自定义模式 .

npm install @arcjet/sensitive-info-rampart
import arcjet, { sensitiveInfo } from "@arcjet/next";
import { rampart } from "@arcjet/sensitive-info-rampart";
import { NextResponse } from "next/server";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    sensitiveInfo({
      mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
      deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER"],
      backend: rampart(),
    }),
  ],
});

export async function POST(request: Request) {
  const { message } = await request.json();

  const decision = await aj.protect(request, {
    sensitiveInfoValue: message,
  });

  if (decision.isDenied() && decision.reason.isSensitiveInfo()) {
    return NextResponse.json(
      { error: "Sensitive information detected" },
      { status: 400 },
    );
  }
}

Rampart 装入本地的 ONNX 运行时间, 所以添加 @arcjet/sensitive-info-rampart, (中文(简体) ), @huggingface/transformers,以及 onnxruntime-node 改为 serverExternalPackages 内 next.config.js 在节点运行路线 运行时间( 默认) 。 见 Bundlers和框架。 。 。 .

盾牌 WAF

保护您的应用程序免受常见的网络攻击, 包括 OWASP 排行前10名.

import arcjet, { shield } from "@arcjet/next";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    shield({
      mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
    }),
  ],
});

电子邮件验证

验证和验证电子邮件地址。 拒绝类型 : DISPOSABLE, (中文(简体) ), FREE, (中文(简体) ), NO_MX_RECORDS, (中文(简体) ), NO_GRAVATAR, (中文(简体) ), INVALID。 。 。 .

import arcjet, { validateEmail } from "@arcjet/next";
import { NextResponse } from "next/server";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    validateEmail({
      mode: "LIVE",
      deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],
    }),
  ],
});

export async function POST(request: Request) {
  const { email } = await request.json();

  const decision = await aj.protect(request, { email });

  if (decision.isDenied() && decision.reason.isEmail()) {
    return NextResponse.json(
      { error: "Invalid email address" },
      { status: 400 },
    );
  }
}

请求过滤器

使用基于表达式的规则对请求属性过滤请求(IP, 页: 1.

import arcjet, { filter } from "@arcjet/next";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    filter({
      mode: "LIVE",
      deny: ['ip.src == "1.2.3.4"', 'http.request.uri.path contains "/admin"'],
    }),
  ],
});

按国家分列的区块

限制进入特定国家——有助于发放许可证、遵守或 区域推广。 这个 allow 拒绝所有未列入名单的国家:

filter({
  mode: "LIVE",
  // Allow only US traffic — all other countries are denied
  allow: ['ip.src.country == "US"'],
});

屏蔽 VPN 和代理流量

防止匿名交通进入敏感终点——对 预防欺诈、实施地理限制并减少滥用:

filter({
  mode: "LIVE",
  deny: [
    "ip.src.vpn", // VPN services
    "ip.src.proxy", // Open proxies
    "ip.src.tor", // Tor exit nodes
  ],
});

对于更细致的处理,使用 decision.ip 呼叫后帮助 protect()数字 :

const decision = await aj.protect(request);

if (decision.ip.isVpn() || decision.ip.isTor()) {
  return NextResponse.json(
    { error: "VPN traffic not allowed" },
    { status: 403 },
  );
}

见 请求过滤文件, (中文(简体) ), IP 地理定位蓝图,以及 VPN/Proxy检测蓝图 更多细节.

知识产权分析

Arcjet用IP元数据丰富了每个请求. 使用这些助手来制作 基于网络信号的政策决定:

const decision = await aj.protect(request);

if (decision.ip.isHosting()) {
  // Requests from cloud/hosting providers are often automated.
  // https://docs.arcjet.com/blueprints/vpn-proxy-detection
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}

if (decision.ip.isVpn() || decision.ip.isProxy() || decision.ip.isTor()) {
  // Handle VPN/proxy traffic according to your policy
}

// Access geolocation and network details
console.log(decision.ip.country, decision.ip.city, decision.ip.asn);

自定义特性

跟踪和限制任何稳定的标识符的请求——用户ID,API密钥,会话, ——而不是仅IP地址.

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  characteristics: ["userId"], // Declare at the SDK level
  rules: [
    tokenBucket({
      mode: "LIVE",
      refillRate: 2_000,
      interval: "1h",
      capacity: 5_000,
    }),
  ],
});

// Pass the characteristic value at request time
const decision = await aj.protect(request, {
  userId: "user-123", // Replace with your actual user ID
  requested: estimate,
});

服务器组件和行动

@arcjet/next 出口a request() 在你没有的地方工作的助手 直接访问来源 Request 对象,例如服务器动作.

import arcjet, { shield } from "@arcjet/next";
import { request } from "@arcjet/next";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [shield({ mode: "LIVE" })],
});

export async function myServerAction() {
  "use server";
  const req = await request();
  const decision = await aj.protect(req);

  if (decision.isDenied()) {
    throw new Error("Forbidden");
  }

  // ...
}

最佳做法

见 Arcjet 最佳做法 详细指导。 密钥 建议:

创建单一客户端实例 并重新使用它与 withRule() 页:1 特定路线规则。 SDK 缓存决定和配置,因此创建 每一个请求的新实例 有效的废物.

// lib/arcjet.ts — create once, import everywhere
import arcjet, { shield } from "@arcjet/next";

export default arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    shield({ mode: "LIVE" }), // base rules applied to every request
  ],
});
// app/api/chat/route.ts — extend per-route with withRule()
import aj from "@/lib/arcjet";
import { detectBot, tokenBucket } from "@arcjet/next";

const routeAj = aj.withRule(detectBot({ mode: "LIVE", allow: [] })).withRule(
  tokenBucket({
    mode: "LIVE",
    refillRate: 2_000,
    interval: "1h",
    capacity: 5_000,
  }),
);

export async function POST(req: Request) {
  const decision = await routeAj.protect(req, { requested: 500 });
  // ...
}

其他建议:

  • 打电话 protect() 在路由处理器,而不是中间软件. 中间软件缺少路由 上下文,使其难以应用特定路由规则或自定义响应.
  • 打电话 protect() 一次请求. 在中间软件和一个 处理器将工作增加一倍,并可以产生出乎意料的结果.
  • 开始规则 DRY_RUN 模式 在切换到前观察行为 LIVE。这使得您可以调取阈值而不影响真实流量.
  • 配置代理 如果您的应用程序运行在负载平衡器或倒置代理服务器后 因此Arcjet解决了真正的客户端IP:
    arcjet({
      key: process.env.ARCJET_KEY!,
      rules: [],
      proxies: ["100.100.100.100"],
    });
    
  • 明确处理错误 . protect() 永不扔出 — 在错误时返回 a 一个 ERROR 结果。 无法通过日志和允许请求打开 :
    if (decision.isErrored()) {
      console.error("Arcjet error", decision.reason.message);
      // allow the request to proceed
    }
    

许可证

Apache 许可证,2.0版本 © Arcjet实验室股份有限公司.

版本与发布记录

@arcjet/next 共发布 79 个版本,首次发布于 2023/12/08;过去 12 个月发布了 30 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.15.0latest2026/10/298 天前
1.14.12026/10/0730 天前
1.14.02026/10/021 个月前
1.13.02026/09/171 个月前
1.12.02026/09/091 个月前
1.11.02026/08/272 个月前

其他发布通道:rc → 1.10.0-rc.1。

关于 @arcjet/next 的常见问题

@arcjet/next 是什么?

Arcjet runtime security SDK for Next.js — 机器人保护、限速、即时注射检测、PII阻塞和WAF

如何安装 @arcjet/next?

在项目目录运行 npm install @arcjet/next,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:next(>=13)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@arcjet/next 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arcjet/next 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@arcjet/next 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.21.0 <23 || >=24.5.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@arcjet/next 可以商用吗?

@arcjet/next 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@arcjet/next 的下载量有多少?

根据 npm 官方下载统计,@arcjet/next 在 2026/10/07 – 2026/11/05 这 30 天内被下载 300,335 次,日均约 10,011 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arcjet/next 还在维护吗?

最新版本 1.15.0 发布于 2026/10/29(8 天前);至今共发布 79 个版本,其中过去 12 个月发布了 30 个;首次发布于 2023/12/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。