<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Miya Liu - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Miya Liu - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 06 Oct 2026 06:06:34 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/miyaliu/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 Landlock 对 Linux 进程进行沙箱隔离，无需 root 权限 ]]>
                </title>
                <description>
                    <![CDATA[ 下面这个程序先限制自身，然后尝试读取两个文件： without landlock:   read /etc/hostname            ok   read /tmp/secret.txt          ok with landlock, /etc allowed:   read /etc/hostname          ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-sandbox-a-linux-process-with-landlock-no-root-required/</link>
                <guid isPermaLink="false">6aae5336d6857d046b7c6a1f</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 17 Sep 2026 09:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/09/a2ed03fe-31d5-4b0b-af8b-88e6a7b54cac.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-sandbox-a-linux-process-with-landlock-no-root-required/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Sandbox a Linux Process with Landlock, No Root Required</a>
      </p><!--kg-card-begin: markdown--><p>下面这个程序先限制自身，然后尝试读取两个文件：</p>
<pre><code class="language-text">without landlock:
  read /etc/hostname            ok
  read /tmp/secret.txt          ok
with landlock, /etc allowed:
  read /etc/hostname            ok
  read /tmp/secret.txt          FAILED (Permission denied)
</code></pre>
<p>这里没有 root 权限，没有容器，没有配置文件，也没有守护进程。这个程序请求内核收回它自身对文件系统大部分区域的访问权限，而内核照做了。</p>
<p>这就是 Landlock。它自 2021 年起便已存在于内核中，却没有引起大多数人的注意。本文将从零开始构建这个程序并运行它，然后介绍四个初次使用 Landlock 时常让人意外的地方。</p>
<h2 id="">目录</h2>
<ul>
<li>所需条件</li>
<li>Landlock 所处的位置</li>
<li>三个系统调用，不需要库</li>
<li>一个限制自身的程序</li>
<li>为什么必须使用 <code>no_new_privs</code></li>
<li>规则集取交集，绝不会扩大权限</li>
<li>子进程会继承什么</li>
<li><code>exec</code> 陷阱</li>
<li>封装一个并非由你编写的程序</li>
<li>找出程序需要访问的路径</li>
<li>确定你拥有的 ABI 版本</li>
<li>结论</li>
<li>后记</li>
</ul>
<h2 id="">所需条件</h2>
<p>要跟着本文操作，你需要 5.13 或更高版本的内核、标准头文件以及一个 C 编译器。除此之外不需要任何东西，尤其不需要 root 权限。</p>
<pre><code class="language-bash">grep landlock /sys/kernel/security/lsm
ls /usr/include/linux/landlock.h
</code></pre>
<p>第一条命令很重要。Landlock 即使被编译进内核，也仍有可能处于未启用状态，因为 Linux 安全模块必须在启动时启用。在这台机器上，该文件的内容是 <code>lockdown,capability,landlock,yama,apparmor</code>。</p>
<p>如果你的输出中没有 <code>landlock</code>，请在内核命令行的现有列表前面添加 <code>lsm=landlock,</code>，然后重启。Ubuntu 从 22.04 版本开始便默认启用了它，当前的 Fedora 和 Arch 内核也包含它，但只有上面的 <code>grep</code> 命令才能给出适用于你这台机器的确切答案。</p>
<p>下文中的所有操作均在 Ubuntu 22.04.5、5.15.0-190-generic 内核上运行，使用 gcc 11.4 编译，整个过程都以普通用户身份完成，没有在任何地方使用 sudo。</p>
<h2 id="landlock">Landlock 所处的位置</h2>
<p>Linux 安全模块（Linux Security Modules，LSM）是一个框架，而不是一项策略。在打开文件、创建进程或映射可执行内存之前，内核会在这些决策点调用 LSM 钩子，而所有已加载的模块都可以决定允许还是拒绝相应操作。</p>
<p>SELinux 和 AppArmor 是大多数人听说过的两个安全模块，它们都是供管理员使用的工具：拥有 root 权限的人编写策略，系统加载该策略，而你的程序只能在策略规定的范围内运行。</p>
<p>Landlock 把这种方式反了过来。它是第一个允许进程在运行时无需特权便可对自身应用限制的 LSM。你不需要说服管理员为你的程序制定一项策略。程序请求减少自己当前拥有的权限，内核则相应收窄其权限。</p>
<p>这种“请求减少权限”的机制正是整个设计的核心。Landlock 只能移除访问权限。它没有任何调用能够授予你原本不具备的权限，而这恰恰是可以安全地让非特权进程使用它的原因。</p>
<h2 id="">三个系统调用，不需要库</h2>
<p>Landlock 由三个系统调用组成，glibc 没有为其中任何一个提供封装函数，因此你需要通过 <code>syscall()</code> 直接调用它们：</p>
<pre><code class="language-c">static int create_ruleset(const struct landlock_ruleset_attr *attr)
{ return syscall(__NR_landlock_create_ruleset, attr, sizeof(*attr), 0); }

static int add_rule(int fd, const struct landlock_path_beneath_attr *pb)
{ return syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_PATH_BENEATH, pb, 0); }

static int restrict_self(int fd)
{ return syscall(__NR_landlock_restrict_self, fd, 0); }
</code></pre>
<p><code>landlock_create_ruleset</code> 声明你打算管控哪些访问类型，并返回一个代表该规则集的文件描述符。<code>landlock_add_rule</code> 以你希望保留访问权限的目录为形式添加一项例外。最后，<code>landlock_restrict_self</code> 将整个规则集永久应用于调用进程。</p>
<p>规则集属性中的 <code>handled_access_fs</code> 字段是人们经常理解反的部分。它列出的不是你要允许的操作，而是这个规则集负责处理的访问类型。凡是列入其中的访问类型，除了你明确添加的路径外，其他地方一律拒绝。只要把读取访问纳入处理范围，你就会失去对整个文件系统的读取权限，直到重新通过规则添加允许访问的路径。</p>
<h2 id="">一个限制自身的程序</h2>
<p>下面是完整程序：</p>
<pre><code class="language-c">#define _GNU_SOURCE
#include &lt;linux/landlock.h&gt;
#include &lt;sys/prctl.h&gt;
#include &lt;sys/syscall.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;errno.h&gt;

#define READ_RIGHTS (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)

static int create_ruleset(const struct landlock_ruleset_attr *attr)
{ return syscall(__NR_landlock_create_ruleset, attr, sizeof(*attr), 0); }
static int add_rule(int fd, const struct landlock_path_beneath_attr *pb)
{ return syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_PATH_BENEATH, pb, 0); }

static int restrict_self(int fd)
{ return syscall(__NR_landlock_restrict_self, fd, 0); }

static int allow_read(int ruleset_fd, const char *path)
{
    struct landlock_path_beneath_attr pb = { .allowed_access = READ_RIGHTS };
    int rc;

    pb.parent_fd = open(path, O_PATH | O_CLOEXEC);
    if (pb.parent_fd &lt; 0) { perror(path); return -1; }
    rc = add_rule(ruleset_fd, &amp;pb);
    close(pb.parent_fd);
    return rc;
}

static void try_read(const char *path)
{
    int fd = open(path, O_RDONLY);

    if (fd &lt; 0)
        printf("  read %-24s FAILED (%s)\n", path, strerror(errno));
    else
        { printf("  read %-24s ok\n", path); close(fd); }
}

int main(void)
{
    struct landlock_ruleset_attr attr = { .handled_access_fs = READ_RIGHTS };
    int ruleset_fd = create_ruleset(&amp;attr);

    if (ruleset_fd &lt; 0) { perror("landlock_create_ruleset"); return 1; }
    if (allow_read(ruleset_fd, "/etc") &lt; 0) return 1;

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("prctl"); return 1; }
    if (restrict_self(ruleset_fd)) { perror("landlock_restrict_self"); return 1; }
    close(ruleset_fd);
    printf("with landlock, /etc allowed:\n");
    try_read("/etc/hostname");
    try_read("/tmp/secret.txt");
    return 0;
}
</code></pre>
<p>编译并运行它：</p>
<pre><code class="language-bash">gcc -Wall -o sandbox sandbox.c
echo "hunter2" &gt; /tmp/secret.txt
./sandbox
</code></pre>
<pre><code class="language-text">with landlock, /etc allowed:
  read /etc/hostname            ok
  read /tmp/secret.txt          FAILED (Permission denied)
</code></pre>
<p>这里有两个细节很重要。规则通过一个已打开的文件描述符而非路径字符串来引用目录。该描述符使用 <code>O_PATH</code> 打开，因此即使没有对目录本身的读取权限，也能获得一个句柄。此外，<code>restrict_self</code> 会立即对调用进程生效，而且无法撤销。</p>
<h2 id="no_new_privs">为什么必须使用 <code>no_new_privs</code></h2>
<p>移除 <code>prctl</code> 调用后，程序便无法正常工作：</p>
<pre><code class="language-text">landlock_restrict_self -&gt; Operation not permitted
</code></pre>
<p>这就是 <code>EPERM</code>，而且是有意如此设计的。<code>PR_SET_NO_NEW_PRIVS</code> 告诉内核，这个进程及其后代永远不能通过 <code>execve</code> 获得新的特权。正是这一点阻止沙箱中的进程通过运行 setuid 二进制文件逃逸。</p>
<p>如果没有这项保证，受限进程便可以执行 <code>sudo</code> 或任何 setuid 程序，并越过刚刚应用的限制。Landlock 宁愿完全拒绝应用自身，也不会提供一个带有这种漏洞的沙箱。每次都要先设置 <code>no_new_privs</code>。</p>
<h2 id="">规则集取交集，绝不会扩大权限</h2>
<p>这个特性必须理解正确。先应用一个允许访问 <code>/etc</code> 的规则集，再应用第二个允许访问 <code>/tmp</code> 的规则集，然后看看你能访问哪些位置：</p>
<pre><code class="language-text">after first ruleset:  /etc=ok               /tmp=Permission denied
after second ruleset: /etc=Permission denied  /tmp=Permission denied
</code></pre>
<p>第二个规则集并没有添加 <code>/tmp</code>。它移除了对 <code>/etc</code> 的访问权限，让你最终什么也无法访问。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/3091ccb0-3d7e-4b52-8744-83a7b32848b9.png" alt="Landlock 沙箱分三个阶段逐步收紧：没有规则集时，五个目录全部可读；应用仅允许访问 /etc 的规则集后，只有 /etc 可读；再应用允许访问 /tmp 的第二个规则集后，由于两个规则集取交集且交集为空，所有目录都不可读" width="600" height="400" loading="lazy"></p>
<p>每次调用 <code>restrict_self</code>，都会与此前已应用的所有规则取交集。第一个规则集允许访问 <code>/etc</code> 并拒绝其余路径；第二个规则集允许访问 <code>/tmp</code> 并拒绝其余路径。最终保留下来的是两者重叠的部分，而这个交集为空。</p>
<p>因此，Landlock 沙箱就像一个棘轮。每次应用都只能进一步收紧限制，绝不能放宽，而且整个 API 中不存在任何能够扩大受限进程访问权限的操作。如果你需要一个进程访问两个目录，就必须在应用规则集之前，把两条规则都放入同一个规则集。</p>
<p>这也意味着你不能反悔。一个长时间运行的进程如果很早就限制了自身，那么此后无论是它自己还是其他任何主体，都无法再向它授予更多权限，除非启动一个新进程。</p>
<h2 id="">子进程会继承什么</h2>
<p>限制会自动随 <code>fork</code> 继承：</p>
<pre><code class="language-text">parent:       /etc=ok   /tmp/secret.txt=Permission denied
forked child: /etc=ok   /tmp/secret.txt=Permission denied
</code></pre>
<p>子进程会完整继承父进程的 Landlock 域，而且没有任何标志可以选择退出。跨越 <code>execve</code> 时也是如此，而这正是 <code>no_new_privs</code> 的意义所在：新程序一开始就已经处于旧程序建立的沙箱中。</p>
<p>这使得 Landlock 可用于封装并非由你编写的程序。先限制自身，再执行你想要约束的程序，它便会在你的限制范围内运行，根本不需要知道这些限制的存在。</p>
<h2 id="exec"><code>exec</code> 陷阱</h2>
<p>这也设置了一个陷阱。沿用上面的程序，只允许访问 <code>/etc</code>，然后尝试执行任何程序：</p>
<pre><code class="language-text">allowed: /etc
execl(/usr/bin/cat) failed: Permission denied
</code></pre>
<p>执行二进制文件需要读取它。这个规则集处理 <code>LANDLOCK_ACCESS_FS_READ_FILE</code>，因此内核会检查是否允许读取 <code>/usr/bin/cat</code>。由于找不到涵盖 <code>/usr</code> 的规则，内核会在程序真正启动之前拒绝执行。</p>
<p>把 <code>/usr</code> 添加到同一个规则集后，它就能正常工作：</p>
<pre><code class="language-text">allowed: /etc and /usr
devils-dell
</code></pre>
<p>一般原则是，Landlock 沙箱必须包含进程接触的所有内容，而这个集合比你想象的更大。其中包括你的二进制文件、它的解释器、它加载的每一个共享库，以及它在启动时读取的所有配置。对二进制文件运行 <code>ldd</code>，是着手整理这份列表的一个好方法。</p>
<h2 id="">封装一个并非由你编写的程序</h2>
<p>跨越 <code>exec</code> 的继承机制，使 Landlock 不仅能用于你自己的代码。先限制自身，再执行任何你想要约束的程序，它便会在沙箱中运行，无需配合，甚至根本不知道沙箱的存在。</p>
<p>要把上面的程序变成一个可用的封装器，需要增加一项内容。除了读取权限外，还要处理 <code>LANDLOCK_ACCESS_FS_EXECUTE</code>；允许访问任何二进制程序都需要的系统目录；然后允许访问用户指定的工作目录：</p>
<pre><code class="language-c">#define RIGHTS (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | \
                LANDLOCK_ACCESS_FS_EXECUTE)

static int add_path(int ruleset_fd, const char *path)
{
    struct landlock_path_beneath_attr pb = { .allowed_access = RIGHTS };
    int rc;

    pb.parent_fd = open(path, O_PATH | O_CLOEXEC);
    if (pb.parent_fd &lt; 0)
        return -1;
    rc = syscall(__NR_landlock_add_rule, ruleset_fd,
                 LANDLOCK_RULE_PATH_BENEATH, &amp;pb, 0);
    close(pb.parent_fd);
    return rc;
}

int main(int argc, char **argv)
{
    struct landlock_ruleset_attr attr = { .handled_access_fs = RIGHTS };
    const char *base[] = { "/usr", "/lib", "/lib64", "/bin", "/etc" };
    int fd, i;

    if (argc &lt; 3) { fprintf(stderr, "usage: %s DIR CMD...\n", argv[0]); return 2; }

    fd = syscall(__NR_landlock_create_ruleset, &amp;attr, sizeof(attr), 0);
    if (fd &lt; 0) { perror("create_ruleset"); return 1; }

    for (i = 0; i &lt; (int)(sizeof(base) / sizeof(*base)); i++)
        add_path(fd, base[i]);
    if (add_path(fd, argv[1]) &lt; 0) { perror(argv[1]); return 1; }
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("prctl"); return 1; }
    if (syscall(__NR_landlock_restrict_self, fd, 0)) { perror("restrict_self"); return 1; }
    close(fd);

    execvp(argv[2], &amp;argv[2]);
    fprintf(stderr, "%s: %s\n", argv[2], strerror(errno));
    return 1;
}
</code></pre>
<p>循环忽略了 <code>add_path</code> 的返回值，因此同一个二进制程序可以在不存在 <code>/lib64</code> 或 <code>/bin</code>，或者这些路径是指向其他位置的符号链接的系统上运行。用户指定的目录则会接受检查，因为其中若有拼写错误，就应该立即报错，而不是稍后出现令人困惑的拒绝访问错误。</p>
<p>现在，<code>cat</code> 和 <code>ls</code> 只能看到工作目录，无法看到其他任何位置：</p>
<pre><code class="language-bash">mkdir -p /tmp/work &amp;&amp; echo "project data" &gt; /tmp/work/notes.txt

./llrun /tmp/work cat /tmp/work/notes.txt
./llrun /tmp/work cat /tmp/secret.txt
./llrun /tmp/work ls /home
</code></pre>
<pre><code class="language-text">project data
cat: /tmp/secret.txt: Permission denied
ls: cannot open directory '/home': Permission denied
</code></pre>
<p>这两个程序都没有经过修改、重新编译，也没有被要求表示同意。<code>bwrap</code> 和类似工具通过在程序周围构建挂载命名空间和用户命名空间来达到这种效果。而这里通过请求一个 LSM 减少权限便实现了同样的结果，代码大约只有 60 行，而且无需安装任何东西。</p>
<h2 id="">找出程序需要访问的路径</h2>
<p>任何沙箱最困难的部分都不是 API，而是那份路径列表。程序打开的内容远比你预想的多，漏掉的某个路径会在运行过程的深处以失败的形式暴露出来。</p>
<p>有两个工具可以帮你建立这份列表。<code>ldd</code> 会给出共享库，而程序必须能够读取它们，否则根本无法启动：</p>
<pre><code class="language-bash">ldd /usr/bin/cat
</code></pre>
<pre><code class="language-text">linux-vdso.so.1 (0x00007fff85f39000)
libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4f61bb5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4f61e0a000)
</code></pre>
<p><code>strace</code> 会给出其余所有内容。先在不受限制的情况下运行程序，并收集它打开的路径：</p>
<pre><code class="language-bash">strace -e trace=openat cat /etc/hostname 2&gt;&amp;1 | grep -oE '"/[^"]+"' | sort -u
</code></pre>
<pre><code class="language-text">"/etc/hostname"
"/etc/ld.so.cache"
"/lib/x86_64-linux-gnu/libc.so.6"
"/usr/lib/locale/locale-archive"
</code></pre>
<p>一个只打印单个文件的程序却访问了四条路径，而且其中只有一条是你要求它读取的文件。链接器缓存、C 库和区域设置归档全都必不可少，因此封装器会先允许访问 <code>/usr</code>、<code>/lib</code> 和 <code>/etc</code>，然后才允许访问你选择的任何路径。</p>
<p>应用限制后，<code>strace</code> 也能准确告诉你哪项操作遭到了拒绝：</p>
<pre><code class="language-bash">strace -f -e trace=openat ./llrun /tmp/work cat /tmp/secret.txt 2&gt;&amp;1 | grep EACCES
</code></pre>
<pre><code class="language-text">openat(AT_FDCWD, "/tmp/secret.txt", O_RDONLY) = -1 EACCES (Permission denied)
</code></pre>
<p>这就是整个迭代过程：运行程序，查看包含 <code>EACCES</code> 的行，判断相应路径究竟应该加入规则集，还是程序本就不该尝试访问它，然后重复操作。在这台机器所用的内核上，Landlock 自身不记录任何日志，因此 <code>strace</code> 就是调试器。从 6.15 开始的内核会通过审计子系统报告拒绝事件，所以在寻找一个并不存在的日志之前，请先检查你的内核版本。</p>
<h2 id="abi">确定你拥有的 ABI 版本</h2>
<p>Landlock 自 5.13 版本以来一直在发展，而你读到的功能可能并不存在于自己的内核中。直接询问内核即可：</p>
<pre><code class="language-c">int v = syscall(__NR_landlock_create_ruleset, NULL, 0,
                LANDLOCK_CREATE_RULESET_VERSION);
</code></pre>
<p>这台机器返回 <code>1</code>，也就是 5.13 中最初提供的版本，其中包含 13 种文件系统访问权限：</p>
<pre><code class="language-bash">grep -oE "LANDLOCK_ACCESS_FS_[A-Z_]+" /usr/include/linux/landlock.h | sort -u
</code></pre>
<pre><code class="language-text">LANDLOCK_ACCESS_FS_EXECUTE      LANDLOCK_ACCESS_FS_MAKE_BLOCK
LANDLOCK_ACCESS_FS_MAKE_CHAR    LANDLOCK_ACCESS_FS_MAKE_DIR
LANDLOCK_ACCESS_FS_MAKE_FIFO    LANDLOCK_ACCESS_FS_MAKE_REG
LANDLOCK_ACCESS_FS_MAKE_SOCK    LANDLOCK_ACCESS_FS_MAKE_SYM
LANDLOCK_ACCESS_FS_READ_DIR     LANDLOCK_ACCESS_FS_READ_FILE
LANDLOCK_ACCESS_FS_REMOVE_DIR   LANDLOCK_ACCESS_FS_REMOVE_FILE
LANDLOCK_ACCESS_FS_WRITE_FILE
</code></pre>
<p>后续版本增加了文件重新归属、截断操作、涵盖 TCP 绑定和连接的网络规则，以及对设备 <code>ioctl</code> 的控制。每项功能都伴随一次独立的 ABI 版本升级，因此，需要使用较新权限的程序应该查询 ABI 版本并进行降级处理，而不能想当然。传入运行中内核无法识别的权限，会使 <code>landlock_create_ruleset</code> 以 <code>EINVAL</code> 失败。如果你没有先检查版本，这会成为一个令人困惑、难以调试的错误。</p>
<h2 id="">结论</h2>
<p>现在，你已经可以从进程内部对其自身实施沙箱隔离，无需特权，也无需配置，并且了解了四个出人意料的地方。必须先设置 <code>no_new_privs</code>，否则不会应用任何限制。规则集会取交集，而不是不断累加权限，因此应当构建一个包含全部所需内容的规则集。子进程会继承限制，这是一个特性。最后，读取限制会破坏 <code>exec</code>，除非二进制文件所在的路径也被允许访问。</p>
<p>接下来可以从几个方向继续探索。把 <code>LANDLOCK_ACCESS_FS_WRITE_FILE</code> 加入 <code>handled_access_fs</code>，构建一个可以广泛读取、却只能写入一个指定目录的程序。先限制自身，再调用 <code>execve</code>，从而封装一个并非由你编写的程序。或者观察 <code>strace</code> 如何报告拒绝事件，这是建立真实程序实际所需路径列表的最快方法。</p>
<h2 id="">后记</h2>
<p>Landlock 最值得探讨的问题并不是它能做什么，而是它无法表达什么。规则集指定的是路径，因此权限的基本单位是文件系统中的位置，而不是交给你的某个特定文件。你可以规定这个进程可以读取 <code>/etc</code> 下的内容，却无法规定这个进程只能读取用户刚刚选中的那一个文件，其他内容一概不能读取。</p>
<p>过去一段时间里，我一直在尝试弥补这一缺口，构建一个以 capability 机制为基础的桌面操作系统。在这个系统中，权限通过指向具体对象的句柄来授予，而不是通过针对某个路径或位置制定规则，同时底层仍然保持 Debian 生态系统正常运行。Landlock 承担了其中很大一部分工作，而它能力所及的边界之外，恰恰是系统设计开始变得有意思的地方。</p>
<p>我在 <a href="https://thechris.in/">thechris.in</a> 撰写有关系统及其奥秘的文章。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何对 Go 应用进行 Docker 容器化：完整分步指南 ]]>
                </title>
                <description>
                    <![CDATA[ 假设你想把源代码分享给一个电脑上没有安装 Go 的人。遗憾的是，这个人将无法运行你的应用。即使对方安装了 Go，由于你们的本地开发环境不同，应用的行为也可能有所差异。 那么，怎样才能把应用及其所需内容打包起来，让它在任何本地环境中都以相同的方式运行呢？这正是 Docker 的用武之地。 对于初学者来说，Docker 并不总是一个容易理解的概念。但一旦理解了它，我敢保证，你会觉得它非常有趣，甚至想把能接触到的每个应用都进行 Docker 容器化。 本文将以一个 Go 应用为例。这里讲解的容器化基本概念可以迁移到其他场景，因此不必过于担心使用其他语言开发的应用该如何进行 Docker 容器化。 我们将介绍如何仅使用 Docker、镜像和容器对 Go 应用进行容器化，如何使用 Docker Compose 在一个应用中设置多个容器，以及 Docker Compose 文件由哪些部分组成。 读完本文后，你将对 Docker、镜像和容器有基本的了解，并知道如何使用 Docker Compose 编排多个相互依赖的容器。 本文内容  1.  前置条件  2.  什么是 Docker   ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-dockerize-a-go-application-full-step-by-step-walkthrough/</link>
                <guid isPermaLink="false">6aadef8ed6857d046b7c6a0c</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 16 Sep 2026 02:17:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/09/e668b887-5ebf-4bd5-9eb6-1187fd650eb5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-dockerize-a-go-application-full-step-by-step-walkthrough/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Dockerize a Go Application – Full Step-by-Step Walkthrough</a>
      </p><!--kg-card-begin: markdown--><p>假设你想把源代码分享给一个电脑上没有安装 Go 的人。遗憾的是，这个人将无法运行你的应用。即使对方安装了 Go，由于你们的本地开发环境不同，应用的行为也可能有所差异。</p>
<p>那么，怎样才能把应用及其所需内容打包起来，让它在任何本地环境中都以相同的方式运行呢？这正是 Docker 的用武之地。</p>
<p>对于初学者来说，Docker 并不总是一个容易理解的概念。但一旦理解了它，我敢保证，你会觉得它非常有趣，甚至想把能接触到的每个应用都进行 Docker 容器化。</p>
<p>本文将以一个 Go 应用为例。这里讲解的容器化基本概念可以迁移到其他场景，因此不必过于担心使用其他语言开发的应用该如何进行 Docker 容器化。</p>
<p>我们将介绍如何仅使用 Docker、镜像和容器对 Go 应用进行容器化，如何使用 Docker Compose 在一个应用中设置多个容器，以及 Docker Compose 文件由哪些部分组成。</p>
<p>读完本文后，你将对 Docker、镜像和容器有基本的了解，并知道如何使用 Docker Compose 编排多个相互依赖的容器。</p>
<h2 id="">本文内容</h2>
<ol>
<li>前置条件</li>
<li>什么是 Docker</li>
<li>如何安装 Docker</li>
<li>什么是 Dockerfile</li>
<li>什么是 Docker Compose</li>
<li><code>app</code> 容器</li>
<li><code>database</code> 容器</li>
<li><code>phpMyAdmin</code> 容器</li>
<li>一起运行所有服务</li>
<li>总结</li>
</ol>
<h2 id="">前置条件</h2>
<p>阅读本教程不需要具备任何 Docker 基础知识。本文从初学者的角度编写，所以即使你从未接触过这个概念也没有关系。</p>
<p>如果你具备一些 Go 基础知识，就能更好地理解并跟随本文中的 Go 代码示例。如果你已经知道如何在本地电脑上搭建 Go 应用环境，就可以继续阅读。如果还不知道，可以查看这篇关于<a href="https://www.freecodecamp.org/news/how-to-get-started-coding-in-golang/">如何开始使用 Go 编程</a>的文章。</p>
<h2 id="docker">什么是 Docker</h2>
<p>想象一下，你有一个盒子。你把代码以及代码运行所需的一切都放进这个盒子，包括代码使用的编程语言，以及需要安装的所有其他外部软件包。</p>
<p>如果有人需要你的应用，你只需把这个盒子交给对方。你也可以把它交给任意数量的人。他们不需要在自己的电脑上安装相应的编程语言或其他任何东西，因为所需的一切都已经装在盒子里。因此，他们运行应用时，实际上是在运行这个盒子的一个实例。</p>
<p>应用在盒子这个标准化环境中运行。这意味着，每个拿到并“打开”盒子的人，都会以完全相同的方式运行该应用。</p>
<p>借助 Docker，应用可以在不同系统上的一致环境中运行，从而避免“在我的电脑上明明能运行”的问题。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/3b2b169d-d882-48a8-88bf-233e4acec611.png" alt="一个装有依赖项、运行时和源代码的盒子，通过箭头连接到多位开发者" width="800" height="332" loading="lazy"></p>
<p>用 Docker 的专业术语来说，这个盒子叫作<strong>镜像</strong>，镜像的运行实例叫作<strong>容器</strong>。</p>
<p>镜像是一种轻量级、独立且可执行的软件包，其中包含运行某个软件所需的一切，包括代码、运行时、库、系统工具，甚至操作系统环境。</p>
<p>容器就是镜像的一个可运行实例。它代表某个特定应用的执行环境。</p>
<p>如果这些内容听起来仍然很抽象，也不用担心。我们很快就会开始实际操作。</p>
<h2 id="docker">如何安装 Docker</h2>
<p>为了安装 Docker，我们将安装自带 Docker Engine 的 Docker Desktop。Docker Desktop 是一个用于管理容器的图形用户界面，你会在后面的章节中看到它有多实用。</p>
<p>在撰写本文时，我使用的是 WSL（Windows Subsystem for Linux，适用于 Linux 的 Windows 子系统）。如果你也使用 WSL，安装前需要考虑这一点，因为不同操作系统安装 Docker 的前置条件和步骤并不相同。</p>
<p>要在 WSL 环境中安装 Docker Desktop，请执行以下步骤：</p>
<ol>
<li>下载并安装 Windows 版 <a href="https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe"><code>.exe</code> 文件</a>。</li>
<li>从“开始”菜单启动 Docker Desktop，然后进入设置。</li>
<li>在 <strong>General</strong> 选项卡中选择 <strong>Use WSL 2 based engine</strong>。</li>
<li>单击 <strong>Apply</strong>。</li>
</ol>
<p>这样就完成了 WSL 环境中的安装。如果你使用其他操作系统，可以在<a href="https://docs.docker.com/get-started/introduction/get-docker-desktop/">官方文档</a>中查看相应的安装方式。</p>
<h2 id="dockerfile">什么是 Dockerfile</h2>
<p>为了创建前面提到的盒子，Docker 首先需要按照一系列明确的步骤操作。它需要知道应用的依赖项和运行时，还需要获得源代码。我们会把所有这些步骤都写在 Dockerfile 中。</p>
<p>在正式开始之前，先创建一个工作目录并进入该目录：</p>
<pre><code class="language-bash">mkdir go_book_api &amp;&amp; cd go_book_api
</code></pre>
<p>运行以下命令，在应用中初始化 Go 模块：</p>
<pre><code class="language-bash">go mod init go_book_api
</code></pre>
<p>执行这条命令需要本地已经安装 Go；但在项目文件准备完成后，其他人只需安装 Docker 就能构建和运行该项目。这条命令会创建一个 <code>go.mod</code> 文件，用来记录项目依赖项。在项目根目录下创建一个 <code>cmd</code> 目录，并在其中创建 <code>main.go</code> 文件。这个文件将作为应用的入口点。你可以在 <code>main.go</code> 文件中加入一条简单的输出语句：</p>
<pre><code class="language-go">// cmd/main.go
package main

import "fmt"

func main() {
	fmt.Println("Look at me gooo!")
}
</code></pre>
<p>接下来，在项目根目录下创建一个名为 <code>Dockerfile</code> 的文件。这个文件没有扩展名；默认情况下，<code>docker build</code> 会在构建上下文的根目录中查找这个名称的构建文件。</p>
<p>把以下内容粘贴到该文件中，然后我们逐项进行说明：</p>
<pre><code class="language-dockerfile"># base image
FROM golang:1.24

# define the working directory
WORKDIR /app

# copy go.mod so that the required modules are known in the container
# ./ here is the WORKDIR, /app
COPY go.mod ./

# command to download modules
RUN go mod download

# copy source code into working dir
COPY . .

# build
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping ./cmd/main.go

# run the compiled binary when the container starts
CMD ["/docker-gs-ping"]
</code></pre>
<p>大多数 Dockerfile 都以基础镜像开头，并使用 <code>FROM</code> 关键字指定基础镜像。基础镜像是一个底层模板，它提供在容器中构建和运行应用所需的精简操作系统环境、库或依赖项。</p>
<p>在本例中，基础镜像是 <code>golang:1.24</code>。基础镜像也可以提供 Linux 之类的操作系统环境。在这种情况下，即使你把代码交给没有使用 Linux 的人，对方也不必担心，因为应用将在已经包含精简 Linux 系统的容器环境中运行。同理，即使对方的本地电脑没有安装 Go，也能运行你的应用。</p>
<p>在编写 Dockerfile 时，如果不确定该使用哪种基础镜像，可以随时在 Docker Hub 中浏览已经发布的镜像。本例使用的是 <a href="https://hub.docker.com/_/golang">Docker 官方 Go 镜像</a>。</p>
<p>下一步是定义工作目录。容器内有一个类似 Linux 的文件系统，其中包含 <code>/app</code>、<code>/bin</code>、<code>/usr</code> 和 <code>/var</code> 等目录。本例通过 <code>WORKDIR</code> 指令把工作目录定义为 <code>/app</code>。</p>
<p>设置工作目录后，需要把 <code>go.mod</code> 文件复制到其中，以便 Docker 知道应用需要哪些模块。如果项目中已有 <code>go.sum</code>，通常也应将它一并复制，例如将相应指令写成 <code>COPY go.mod go.sum ./</code>。</p>
<p>Docker 的 <code>COPY</code> 指令至少接收两个参数：一个或多个源路径，以及一个目标路径。本例中的 <code>COPY go.mod ./</code> 会把 <code>go.mod</code> 复制到容器的工作目录 <code>/app</code> 中。</p>
<p>接下来，在容器环境中运行一条命令，把 <code>go.mod</code> 中定义的模块下载到模块缓存。要在构建镜像时运行命令，可以使用 <code>RUN</code>，后面接要执行的命令。本例执行的是 <code>go mod download</code>。</p>
<p>下一步是把所有源代码复制到工作目录中。</p>
<p>现在，依赖项和源代码都已经准备好了。最后，需要把 Go 应用构建为一个可在该环境，也就是容器内部运行的可执行文件。</p>
<p>容器中会生成一个位于 <code>/docker-gs-ping</code> 的已编译二进制文件，它由 <code>main.go</code> 中的代码编译而来。最后的 <code>CMD</code> 指令会告诉 Docker 在容器启动时运行这个可执行二进制文件。换句话说，就是“容器开始运行后，执行这个二进制文件”。</p>
<p>完成这些步骤后，Docker 会构建一个可以运行的镜像，也就是我们在类比中所说的盒子。要构建镜像，可以在终端中运行以下命令：</p>
<pre><code class="language-bash">docker build -t go_book_api .
</code></pre>
<p><code>docker build</code> 命令让 Docker 按照 Dockerfile 中的步骤构建镜像。<code>-t</code> 用于设置镜像标识，其格式可以包含镜像名称和标签。</p>
<p>本例将镜像命名为 <code>go_book_api</code>；由于没有显式指定标签，Docker 会使用默认标签 <code>latest</code>。命令末尾的 <code>.</code> 表示当前目录是构建上下文，Docker 会在其中查找 Dockerfile 以及需要复制到镜像中的文件。</p>
<p>下面是我在集成开发环境中构建镜像时的画面：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/361a805e-153d-4034-9d9a-d34c9015738a.png" alt="集成开发环境的终端正在构建 Docker 镜像" width="1910" height="992" loading="lazy"></p>
<p>如果查看 Docker Desktop 的 <strong>Images</strong> 选项卡，就会看到镜像已经构建完成：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/b569277e-295b-4a3d-8e51-fb91dd7e3d91.png" alt="Docker Desktop 中已经构建好的容器镜像" width="1881" height="363" loading="lazy"></p>
<p>你可以把这个镜像托管在 <a href="https://www.docker.com/products/docker-hub/">Docker Hub</a> 之类的公共镜像仓库平台上，并与朋友分享。他们可以拉取镜像，完成设置并运行你的应用，即使本地没有安装 Go 也没关系。他们只需要把容器运行起来。</p>
<p>单击最右侧的小型播放按钮，就可以从该镜像启动一个实例，也就是一个容器。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/09726294-be22-458d-b660-5f6d32102205.png" alt="用于运行新容器的 Docker Desktop 对话框" width="825" height="758" loading="lazy"></p>
<p>你可以为容器指定一个描述性名称。如果不指定，Docker 会随机生成一个名称。然后单击 <strong>Run</strong> 按钮。容器开始运行后，页面会跳转到它的日志页面。</p>
<p>现在容器已经启动并执行了应用。由于当前程序只输出一行文字后就会退出，容器也会很快停止；你仍然可以在日志中看到程序的输出。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/3133c16c-0950-4f03-9502-ae6495535c13.png" alt="Docker Desktop 中正在运行的 Docker 容器" width="1918" height="663" loading="lazy"></p>
<h2 id="dockercompose">什么是 Docker Compose</h2>
<p>如果你构建的是一个不需要任何外部依赖项的简单 Go 应用，那么上面的设置已经完全足够。</p>
<p>但在本例中，这个应用是一个图书 API，因此通常还需要数据库之类的服务，以及 phpMyAdmin 这样的数据库管理客户端，以便查看数据库表。</p>
<p>如果只使用一个 Dockerfile，把这些服务全部放进同一个容器会比较复杂。Dockerfile 虽然可以通过多阶段构建使用多个 <code>FROM</code> 指令，但 Go 应用、数据库和数据库管理工具是不同的长期运行服务，通常不应打包到同一个容器中。</p>
<p>你可以使用一个精简操作系统的基础镜像，再通过命令手动安装其他服务，但这会把多个组件紧密耦合在一起，使应用更难维护和扩展。某个组件出现故障时，也可能影响整个应用。</p>
<p>为了解决这个问题，Docker Compose 允许你为一个应用定义多个相互连接的服务。它可以协调容器的启动顺序和网络连接，并通过卷持久化或共享数据。</p>
<p>仍然沿用前面的盒子类比，只不过使用 Docker Compose 时，我们不再只有一个盒子：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/2c890de4-8d5d-4457-a27a-fc441f58d794.png" alt="一个装有多个容器的盒子，通过箭头连接到不同的开发者" width="823" height="609" loading="lazy"></p>
<p>Docker Compose 的作用是帮助你编排运行应用所需的多项服务及其容器。可以把它理解为把多个盒子连接在一起。</p>
<p>按照前面的说明，Go 应用将在 <code>app</code> 服务的 <code>go_book_api</code> 容器中运行。把它扩展为实际的图书 API 后，可以由 <code>database</code> 服务提供 MySQL，并把图书数据保存在 <code>mysql-go</code> 命名卷中；你还可以通过 <code>phpmyadmin</code> 服务查看数据库。本文中的最小 Go 示例尚未实现数据库连接。</p>
<p>为了具体了解这种配置，请在项目根目录下创建一个 <code>docker-compose.yml</code> 文件。Docker Compose 默认查找 <code>compose.yaml</code>（首选）、<code>compose.yml</code>、<code>docker-compose.yaml</code> 或 <code>docker-compose.yml</code>。扩展名表明其中的配置使用 YAML 编写。YAML 是一种主要用于配置文件的数据序列化语言。</p>
<pre><code class="language-yaml">services:
  app:
    depends_on:
      - database
    build:
      context: .
    container_name: go_book_api
    networks:
      - go_book_api_net
    ports:
      - 8080:8080

  database:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USER}
    volumes:
      - mysql-go:/var/lib/mysql
    ports:
      - 3356:3306
    networks:
      - go_book_api_net

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 9000:80
    environment:
      PMA_HOST: database
      PMA_ARBITRARY: 1
    depends_on:
      - database
    networks:
      - go_book_api_net

volumes:
  mysql-go:

networks:
  go_book_api_net:
    driver: bridge
</code></pre>
<p>Docker Compose 文件的顶层有一个 <code>services</code> 字段，其中定义了应用运行所需的所有服务。服务是抽象定义，每项服务可以由一个或多个容器实例实现。</p>
<h3 id="app"><code>app</code> 容器</h3>
<pre><code class="language-yaml">app:
  depends_on:
    - database
  build:
    context: .
  container_name: go_book_api
  networks:
    - go_book_api_net
  ports:
    - 8080:8080
</code></pre>
<p>第一项服务是 <code>app</code>，也就是你的 Go 应用。你还需要在 <code>app</code> 服务下定义它运行所需的一些参数。</p>
<p><code>depends_on</code> 属性控制服务的启动和停止顺序。这样一来，如果服务 A 依赖服务 B，系统就会先启动服务 B，再启动服务 A。在本例中，必须先启动 <code>database</code> 服务，再启动 <code>app</code> 服务。请注意，这并不意味着 <code>app</code> 一定会等到 <code>database</code> 已经可以接受请求后才启动。</p>
<p>下一个属性 <code>build</code> 告诉 Docker Compose 使用本地项目构建 Docker 镜像。由于应用的 Dockerfile 位于项目根目录，因此使用 <code>context</code> 属性并将根路径指定为 <code>.</code>。</p>
<p>要为容器指定名称，可以使用 <code>container_name</code>。在 Compose 网络中，服务之间通常使用服务名或网络别名进行通信；本例中，其他服务可以使用 <code>app</code> 引用这项服务。</p>
<p>Docker Compose 的目的在于让多个容器协同工作，而容器借助网络进行通信。如果没有显式声明网络，Compose 会为项目创建默认网络。本例显式定义了一个名为 <code>go_book_api_net</code> 的网络，并让需要相互通信的服务都加入这个网络。</p>
<p>下一个属性是 <code>ports</code>。后面我们会让这个 Go 应用启动一个 HTTP 服务器。要从本地访问它，需要把主机端口映射到容器端口。这里把电脑上的 <code>8080</code> 端口映射到容器中的 <code>8080</code> 端口。</p>
<p>Docker Compose 会读取项目目录中的 <code>.env</code> 文件，用其中的值替换 Compose 文件里的 <code>${DB_NAME}</code> 等表达式。</p>
<p>为了让本例中的数据库服务能够启动，<code>.env</code> 至少需要提供以下变量。下面的值仅用于演示，请换成适合本地开发环境的值：</p>
<pre><code class="language-dotenv">DB_ROOT_PASSWORD=change-this-root-password
DB_NAME=go_book_api
DB_USER=go_book_user
DB_PASSWORD=change-this-user-password
</code></pre>
<p>由于 Dockerfile 使用了 <code>COPY . .</code>，还应在项目根目录下创建 <code>.dockerignore</code>，防止 <code>.env</code> 被复制进镜像：</p>
<pre><code class="language-gitignore">.env
.git
</code></pre>
<p>同时应把 <code>.env</code> 加入 <code>.gitignore</code>，不要把真实凭据提交到版本库。</p>
<h3 id="database"><code>database</code> 容器</h3>
<pre><code class="language-yaml">database:
  image: mysql:8.0
  environment:
    MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    MYSQL_DATABASE: ${DB_NAME}
    MYSQL_PASSWORD: ${DB_PASSWORD}
    MYSQL_USER: ${DB_USER}
  volumes:
    - mysql-go:/var/lib/mysql
  ports:
    - 3356:3306
  networks:
    - go_book_api_net
</code></pre>
<p>第二项服务是 <code>database</code>。你可以自行命名列出的服务，不过通常应使用能够说明用途的服务名。</p>
<p>为了演示多容器配置，本例添加了 MySQL 数据库服务。当前的最小 Go 代码还没有连接数据库；实际扩展图书 API 时，可以通过这项服务读写数据。</p>
<p>请记住，创建容器需要镜像。在本例中，<code>image</code> 属性指定了现成的官方镜像 <code>mysql:8.0</code>。启动这项服务时，Docker Compose 会使用该镜像创建数据库容器。</p>
<p>如果你曾在本地配置过数据库，就会知道配置是一个无法省略的步骤。创建数据库需要设置用户、密码和数据库名称。可以在 <code>environment</code> 属性中引用这些变量，并在项目的 <code>.env</code> 文件或 shell 环境中提供实际值。不要把真实凭据直接写入 Compose 文件，也不要把包含真实凭据的 <code>.env</code> 文件提交到版本库。</p>
<p>无论数据库运行在本地还是远程，数据库服务器通常都会监听特定端口，以接收传入的连接。和 <code>app</code> 容器一样，可以为数据库设置一个本地端口，并把它映射到容器中的对应端口。如果要从本地访问数据库，应使用 <code>3356</code> 端口，连接流量会被转发到数据库容器中的 <code>3306</code> 端口；同一 Compose 网络中的其他容器则应通过 <code>database:3306</code> 连接数据库。</p>
<p>容器正常工作、应用开始在数据库中创建和存储数据后，还需要考虑数据持久化。如果数据只保存在容器的可写层中，那么容器被删除或重新创建时，这些数据就会丢失；单纯停止并重新启动同一个容器并不会删除数据。</p>
<p>为了避免数据随容器被删除，需要把数据存储在生命周期独立于容器的卷中。</p>
<p>卷就是为此而设计的。本例中的 <code>mysql-go:/var/lib/mysql</code> 表示把名为 <code>mysql-go</code> 的命名卷挂载到数据库容器内的 <code>/var/lib/mysql</code> 路径。</p>
<p>与前面在 <code>app</code> 容器中把网络设为 <code>go_book_api_net</code> 一样，这个数据库容器也要指定同一个网络。由于需要让容器相互通信，把它们放在同一个网络中是合理的。</p>
<h3 id="phpmyadmin"><code>phpMyAdmin</code> 容器</h3>
<p>本例还可以选择配置 <code>phpmyadmin</code> 服务。我发现使用数据库客户端会更加方便，因为它能让我轻松查看数据库的结构和内容。</p>
<pre><code class="language-yaml">phpmyadmin:
  image: phpmyadmin
  restart: always
  ports:
    - 9000:80
  environment:
    PMA_HOST: database
    PMA_ARBITRARY: 1
  depends_on:
    - database
  networks:
    - go_book_api_net
</code></pre>
<p>这个过程与配置前面几个容器时几乎相同。Docker 会拉取现成的官方 <code>phpmyadmin</code> 镜像，并使用它创建容器。</p>
<p>这里把 <code>restart</code> 设置为 <code>always</code>，表示容器退出后，Docker 通常会自动重启该容器；如果手动停止容器，则不会立即自动重启。</p>
<p>在主机，也就是本地环境上，可以通过 <code>9000</code> 端口访问这项服务。该端口会映射到容器内的 <code>80</code> 端口。</p>
<p>在 <code>environment</code> 配置中，<code>PMA_HOST</code> 告诉 phpMyAdmin 连接到名为 <code>database</code> 的主机，也就是数据库服务。之所以可以这样做，是因为两个服务位于同一个网络中，这一点可以从 <code>networks</code> 属性看出。<code>PMA_ARBITRARY: 1</code> 允许用户在 phpMyAdmin 登录页面手动填写数据库服务器的主机名。例如，如果以后配置了另一个数据库，但仍想通过 phpMyAdmin 连接它，就可以在登录页面中指定该主机。</p>
<p>数据库客户端依赖 <code>database</code> 服务，因此还需要在 <code>depends_on</code> 中指定这一点。</p>
<pre><code class="language-yaml">volumes:
  mysql-go:

networks:
  go_book_api_net:
    driver: bridge
</code></pre>
<p>Docker Compose 文件的最后一部分定义了前面配置服务时所用的命名卷和自定义网络。</p>
<p>在顶层 <code>volumes</code> 下定义名为 <code>mysql-go</code> 的命名卷，然后在需要使用该卷的服务中指定容器内的挂载路径。可以在数据库服务中看到它的用法：</p>
<pre><code class="language-yaml">volumes:
  - mysql-go:/var/lib/mysql
</code></pre>
<p>网络也采用相同的思路。这里定义了一个名为 <code>go_book_api_net</code> 的网络，加入该网络的容器可以相互通信。<code>driver</code> 选项用于指定网络驱动；<code>bridge</code> 会创建用户定义的桥接网络，为不同网络中的容器提供隔离，但默认仍允许容器访问外部网络。</p>
<h3 id="">一起运行所有服务</h3>
<p>在使用 Docker Compose 之前，你只有一个 Dockerfile，用来为 Go 应用构建镜像。使用 Docker Compose 后，你可以定义应用、数据库和 phpMyAdmin 三项服务，并由 Compose 创建相应的容器，让它们作为一个完整应用协同工作。</p>
<p>你可以把整套项目配置推送到 GitHub 等平台。其他人可以克隆、启动并运行该应用，而不必在本地电脑上安装 Go、MySQL 或 phpMyAdmin。不过，他们仍然需要安装 Docker。提交代码前，请确保 <code>.env</code> 等包含真实凭据的文件已经被排除。</p>
<p>要构建 Compose 项目中需要从本地源代码构建的服务镜像，可以使用 <code>docker compose build</code> 命令。本例只有 <code>app</code> 服务包含 <code>build:</code>，因此该命令只会构建应用镜像：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/0040fbdc-c541-494f-af9b-664d6a00bc17.png" alt="集成开发环境的终端正在构建镜像" width="1871" height="959" loading="lazy"></p>
<p>再次查看 Docker Desktop 界面，可以看到已经构建了一个与 <code>app</code> 服务对应的新镜像。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/736be9be-feb1-4888-8d15-c818e4683f4b.png" alt="Docker Desktop 中已经构建好的镜像" width="1913" height="363" loading="lazy"></p>
<p>要启动这些容器，可以使用 <code>docker compose up</code> 命令：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/8ba14bb9-77d5-48a1-b574-54a848f54b1e.png" alt="集成开发环境的终端中正在运行的容器" width="1912" height="993" loading="lazy"></p>
<p>进入 Docker Desktop 的 <strong>Containers</strong> 选项卡，可以查看各个容器的状态：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/82e3d54d-bfec-4cea-806a-c52846a3e077.png" alt="Docker Desktop 中正在运行的容器" width="1910" height="202" loading="lazy"></p>
<p><code>app</code> 服务的 <code>go_book_api</code> 容器并未保持运行，因为容器启动后，当前二进制文件只输出一行文字，执行完毕后几乎会立即退出。</p>
<p>修改 <code>main.go</code> 中的代码，创建一个监听 <code>8080</code> 端口的最小 HTTP 服务器：</p>
<pre><code class="language-go">// cmd/main.go
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})

	log.Println("listening on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}
</code></pre>
<p>如果你刚开始学习 Go，也不必太担心上面的代码。它为 <code>/health</code> 端点注册了一个处理函数，并让 HTTP 服务器监听 <code>8080</code> 端口；访问该端点时，服务器会返回“ok”。</p>
<p>确认 <code>Dockerfile</code> 中包含以下指令，让容器启动时执行已编译的二进制文件：</p>
<pre><code class="language-dockerfile"># run the compiled binary when the container starts
CMD ["/docker-gs-ping"]
</code></pre>
<p>修改 <code>main.go</code> 后，需要重新构建并再次启动这些容器。现在可以看到，所有容器都正在运行：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/3ddf3e15-87b8-4978-851f-d6179e323166.png" alt="Docker Desktop 中正在运行的所有容器" width="1555" height="202" loading="lazy"></p>
<p>单击 <code>go_book_api</code> 容器，可以看到服务器正按配置在 <code>8080</code> 端口上运行：</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/ddd07614-eb53-4bfc-b088-e824f651ef6c.png" alt="Docker Desktop 中正在运行的容器" width="1919" height="522" loading="lazy"></p>
<p>由于应用运行在 <code>8080</code> 端口，并且已经设置了 <code>/health</code> 端点，因此可以在浏览器中访问该端点并查看输出“ok”。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/39a1ea3e-7cbf-4d46-9bbe-bf8053d48586.png" alt="浏览器中的健康检查端点显示 ok 响应" width="1086" height="163" loading="lazy"></p>
<p>此外，单击已映射的 <code>phpmyadmin</code> 端口，就可以通过本地的 <code>9000</code> 端口访问数据库客户端，并使用 <code>.env</code> 文件中设置的数据库凭据登录。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/8d7de244-7268-4d17-a779-785feae389c4.png" alt="浏览器中的 phpMyAdmin 登录表单" width="1915" height="962" loading="lazy"></p>
<p>Docker Desktop 中另一个值得查看的功能是卷。你可以在 <strong>Volumes</strong> 选项卡中看到已经配置好的 <code>mysql-go</code> 卷。</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/61d7e29f8d56921d07b9014e/66d1dde3-2fc1-48aa-b701-7504dba2007f.png" alt="Docker Desktop 中的 Volumes 选项卡" width="1910" height="254" loading="lazy"></p>
<p>你可以随时在 Docker 图形界面中打开这些卷或容器，查看其中的文件和日志，也可以尝试停止某个容器，观察其他容器会如何响应，等等。</p>
<p>完成这一整套设置后，你发现了什么？项目文件准备完成后，运行项目的人不需要在本地安装 Go、MySQL 或 phpMyAdmin，只需要安装 Docker，就能使用官方发布的镜像编排出一个完整的应用。这就是 Docker 的魅力。</p>
<h2 id="">总结</h2>
<p>Docker 一开始可能显得非常抽象，但只要理解它的根本用途，一切就会清晰得多。</p>
<p>本文介绍了 Docker 是什么、如何对一个基本的 Go 应用进行容器化，以及如何使用 Docker Compose 管理多个容器。</p>
<p>如果你很难理解 Dockerfile 为什么这样设置，或者为什么要按这样的顺序编写，不必一开始就执着于独自弄清每个细节。刚开始学习 Docker 时，我发现把它想象成编写一份食谱会更容易理解。如果构建镜像失败，就说明你可能遗漏了其中某个步骤。</p>
<p>如果你希望在本教程之外进一步了解 Docker，可以查看 <a href="https://docs.docker.com/">Docker 官方文档</a>。其中有许多非常出色的资源。我也建议你继续深入学习，因为本文只是初步介绍了容器化可以实现的众多功能。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Python 高效数据处理：批处理与流式处理管道 ]]>
                </title>
                <description>
                    <![CDATA[ 每条数据管道在编写任何代码之前，都要做出一个根本选择：是按照计划分批处理数据，还是在数据到达时持续处理？ 这个选择——批处理还是流式处理——会决定下游所有环节的架构。你使用的工具、能对数据时效性作出的保证、错误处理的复杂程度，以及运行整套系统所需的基础设施，都直接取决于这个决定。 选错的代价很高。本来使用批处理就足够，却搭建了流式处理管道的团队，最终不得不为一个原本不需要复杂方案的问题维护复杂的基础设施。 而在实际用例需要实时处理时仍然搭建批处理管道的团队，往往会在最糟糕的时刻才发现两者之间的差距——例如利益相关者问起，为什么数据看板已经落后了六个小时。 本文将介绍批处理管道和流式处理管道究竟是什么、两者在架构和权衡方面有何不同，以及如何使用 Python 实现这两种模式。读完本文后，你将掌握一套清晰的判断框架，可以为遇到的任何数据工程问题选择合适的方案。 前置知识 为了顺利理解本文内容，请确保你具备以下基础：  * 有编写 Python 函数和使用模块的实践经验  * 熟悉 pandas DataFrame 和基本的数据操作  * 大致了解 ETL 管道的作用——抽取（e ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/efficient-data-processing-in-python-batch-vs-streaming-pipelines/</link>
                <guid isPermaLink="false">6aad341cd6857d046b7c69f5</guid>
                
                    <category>
                        <![CDATA[ 数据工程 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 15 Sep 2026 12:57:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/09/a8d3fc81-792f-4c6b-b93a-b7ccf0244eb7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/efficient-data-processing-in-python-batch-vs-streaming-pipelines/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Efficient Data Processing in Python: Batch vs Streaming Pipelines Explained</a>
      </p><!--kg-card-begin: markdown--><p>每条数据管道在编写任何代码之前，都要做出一个根本选择：是按照计划分批处理数据，还是在数据到达时持续处理？</p>
<p>这个选择——批处理还是流式处理——会决定下游所有环节的架构。你使用的工具、能对数据时效性作出的保证、错误处理的复杂程度，以及运行整套系统所需的基础设施，都直接取决于这个决定。</p>
<p>选错的代价很高。本来使用批处理就足够，却搭建了流式处理管道的团队，最终不得不为一个原本不需要复杂方案的问题维护复杂的基础设施。</p>
<p>而在实际用例需要实时处理时仍然搭建批处理管道的团队，往往会在最糟糕的时刻才发现两者之间的差距——例如利益相关者问起，为什么数据看板已经落后了六个小时。</p>
<p>本文将介绍批处理管道和流式处理管道究竟是什么、两者在架构和权衡方面有何不同，以及如何使用 Python 实现这两种模式。读完本文后，你将掌握一套清晰的判断框架，可以为遇到的任何数据工程问题选择合适的方案。</p>
<h2 id="">前置知识</h2>
<p>为了顺利理解本文内容，请确保你具备以下基础：</p>
<ul>
<li>有编写 Python 函数和使用模块的实践经验</li>
<li>熟悉 pandas DataFrame 和基本的数据操作</li>
<li>大致了解 ETL 管道的作用——抽取（extract）、转换（transform）和加载（load）</li>
</ul>
<h2 id="">目录</h2>
<ul>
<li>前置知识</li>
<li>什么是批处理管道
<ul>
<li>使用 Python 实现批处理管道</li>
<li>批处理适合哪些场景</li>
</ul>
</li>
<li>什么是流式处理管道
<ul>
<li>使用 Python 实现流式处理管道</li>
<li>流式处理适合哪些场景</li>
</ul>
</li>
<li>关键区别一览</li>
<li>如何在批处理与流式处理之间做出选择</li>
<li>混合模式：Lambda 架构与 Kappa 架构</li>
</ul>
<h2 id="">什么是批处理管道</h2>
<p>批处理管道会将一组有明确边界且数量有限的记录作为一个整体进行处理，例如一个文件、一个数据库快照，或者一天内产生的交易记录。它会按照计划定期运行，例如每小时、每晚或每周一次，读取相应时间段内的全部数据，对数据进行转换，再将结果写入某个位置。之后，管道会停止运行，等待下一次启动。</p>
<p>这种模式的思路很简单：<strong>先收集，再处理</strong>。两次运行之间不会发生任何处理。</p>
<p>以零售行业的 ETL 为例，一条典型的批处理管道可能采用以下流程：</p>
<ol>
<li>在午夜从事务数据库中提取过去 24 小时内产生的所有订单</li>
<li>将订单数据与产品目录表和客户维度表连接</li>
<li>按地区和产品类别计算每日营收汇总数据</li>
<li>将结果加载到数据仓库中，供报表使用</li>
</ol>
<p>管道启动、完成处理，并生成一份完整、一致的昨日业务快照。分析师早晨开始工作时，数据仓库已经完成更新。</p>
<h3 id="python">使用 Python 实现批处理管道</h3>
<p>最简单的批处理管道可以是一个 Python 脚本，其中包含三个明确分离的阶段：抽取、转换和加载。</p>
<pre><code class="language-python">import pandas as pd
from datetime import datetime, timedelta

def extract(filepath: str) -&gt; pd.DataFrame:
    """从每日导出的文件中加载原始订单数据。"""
    df = pd.read_csv(filepath, parse_dates=["order_timestamp"])
    return df

def transform(df: pd.DataFrame) -&gt; pd.DataFrame:
    """清洗订单数据，并按地区汇总每日营收。"""
    # 只保留已完成的订单
    df = df[df["status"] == "completed"].copy()

    # 从时间戳中提取日期，以便进行分组
    df["order_date"] = df["order_timestamp"].dt.date

    # 聚合：计算每个地区每天的总营收、订单数量和平均订单金额
    summary = (
        df.groupby(["order_date", "region"])
        .agg(
            total_revenue=("order_value_gbp", "sum"),
            order_count=("order_id", "count"),
            avg_order_value=("order_value_gbp", "mean"),
        )
        .reset_index()
    )
    return summary

def load(df: pd.DataFrame, output_path: str) -&gt; None:
    """将汇总结果写入数据仓库（此处写入 CSV 文件）。"""
    df.to_csv(output_path, index=False)
    print(f"已将 {len(df)} 行数据写入 {output_path}")

# 运行管道
raw = extract("orders_2024_06_01.csv")
aggregated = transform(raw)
load(aggregated, "warehouse/daily_revenue_2024_06_01.csv")
</code></pre>
<p>下面逐步看看这段代码做了什么：</p>
<ul>
<li><code>extract</code> 读取一个表示每日订单导出数据的 CSV 文件。<code>parse_dates</code> 参数会让 pandas 将 <code>order_timestamp</code> 列解析为日期时间对象，而不是普通字符串——这一点对于转换阶段提取日期非常重要。</li>
<li><code>transform</code> 完成两项工作：首先过滤掉所有未完成的订单，例如退货和取消的订单；然后按照日期和地区对剩余订单进行分组，生成营收汇总数据。<code>.agg()</code> 调用会在一次处理中为每个分组计算三项指标。</li>
<li><code>load</code> 将结果写入目标位置。在生产环境中，这一步可能是向数据库插入数据或将数据上传到云存储，但无论具体目标是什么，所采用的模式都相同。</li>
</ul>
<p>这三个函数被有意分开。通过将抽取、转换和加载相互分离，每个阶段都可以独立测试、替换和调试。如果转换逻辑发生变化，你不需要修改抽取或加载代码。</p>
<h3 id="">批处理适合哪些场景</h3>
<p>在以下情况下，批处理管道通常是合适的选择：</p>
<ul>
<li><strong>数据时效性要求以小时而不是秒来衡量。</strong> 每日销售报告不需要每分钟更新一次，每周运行的营销归因模型更不需要。</li>
<li><strong>你需要处理大规模历史数据集。</strong> 将两年的交易历史数据回填到新的数据仓库，本质上就是一项批处理作业——数据已经存在、边界明确，而你希望在一次运行中尽可能高效地完成处理。</li>
<li><strong>一致性比低延迟更重要。</strong> 批处理管道会生成完整的时间点快照，输出中的每一行都基于相同的输入状态计算得出。这种一致性对于财务报告、法规合规，以及任何需要稳定、可复现数据集的下游流程都很有价值。</li>
</ul>
<h2 id="">什么是流式处理管道</h2>
<p>流式处理管道会在数据到达时持续处理数据，可以逐条处理记录，也可以采用较小的微批次。数据集不存在所谓的“终点”——管道会持续运行，不断从消息队列、Kafka 主题或 webhook 等来源消费事件，并在每个事件到达时立即进行处理。</p>
<p>这种模式的思路是：<strong>边收集，边处理</strong>。管道始终处于运行状态。</p>
<p>仍以零售行业的 ETL 为例，流式处理管道可以在订单创建时处理相应事件：</p>
<ol>
<li>用户在网站上下单后，系统将一个事件发布到消息队列</li>
<li>流式处理管道在几毫秒内消费该事件</li>
<li>管道对事件进行验证、扩充，并将其路由到下游系统</li>
<li>欺诈检测服务、库存系统和实时数据看板都会立即收到更新后的信息</li>
</ol>
<p>这与批处理存在根本区别：数据并不是静静地存放在文件中等待处理，而是在持续流动，管道必须跟上数据流动的速度。</p>
<h3 id="python">使用 Python 实现流式处理管道</h3>
<p>Python 的生成器函数天然适合用作流式处理管道的构建模块。生成器每次只产生一个值，并在两次 <code>yield</code> 之间暂停执行——这正好对应了数据到达时逐条处理记录、而不将全部数据加载到内存中的思路。</p>
<pre><code class="language-python">import json
import time
from typing import Generator, Dict

def event_source(filepath: str) -&gt; Generator[Dict, None, None]:
    """
    从文件中模拟订单事件流。
    在生产环境中，这里会从 Kafka 或消息队列消费事件。
    """
    with open(filepath, "r") as f:
        for line in f:
            event = json.loads(line.strip())
            yield event
            time.sleep(0.01)  # 模拟事件到达之间的延迟

def validate(event: Dict) -&gt; bool:
    """检查事件是否包含必需字段，以及字段值是否有效。"""
    required_fields = ["order_id", "customer_id", "order_value_gbp", "region"]
    if not all(field in event for field in required_fields):
        return False
    if event["order_value_gbp"] &lt;= 0:
        return False
    return True

def enrich(event: Dict) -&gt; Dict:
    """在将事件路由到下游之前，为其添加派生字段。"""
    event["processed_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
    event["value_tier"] = (
        "high"   if event["order_value_gbp"] &gt;= 500
        else "mid"    if event["order_value_gbp"] &gt;= 100
        else "low"
    )
    return event

def run_streaming_pipeline(source_file: str) -&gt; None:
    """在每个事件从数据源到达时对其进行处理。"""
    processed = 0
    skipped = 0

    for raw_event in event_source(source_file):
        if not validate(raw_event):
            skipped += 1
            continue

        enriched_event = enrich(raw_event)

        # 在生产环境中：发布到下游主题，或写入数据接收端
        print(f"[{enriched_event['processed_at']}] "
              f"订单 {enriched_event['order_id']} | "
              f"£{enriched_event['order_value_gbp']:.2f} | "
              f"等级={enriched_event['value_tier']}")
        processed += 1

    print(f"\n完成。已处理：{processed} | 已跳过：{skipped}")

run_streaming_pipeline("order_events.jsonl")
</code></pre>
<p>下面看看代码的具体工作方式：</p>
<ul>
<li><code>event_source</code> 是一个生成器函数——注意它使用的是 <code>yield</code> 关键字，而不是 <code>return</code>。每次执行到 <code>yield event</code> 时，函数都会暂停，并将一个事件交给调用方。管道处理完这个事件后，生成器才会继续运行并获取下一个事件。这意味着，无论数据流有多大，内存中一次都只会保留一个事件。<code>time.sleep(0.01)</code> 用于模拟现实中事件从消息队列陆续到达时的间隔。</li>
<li><code>validate</code> 会先检查每个事件是否包含必需字段，以及字段值是否有效，然后才进行其他操作。在流式处理场景中，异常事件非常常见——网络问题、上游系统缺陷和数据模式变更都可能产生格式错误的记录。尽早验证并跳过无效事件，要比让它们继续传播到下游系统安全得多。</li>
<li><code>enrich</code> 为事件添加派生字段。这里添加的是处理时间戳和订单金额等级。在生产环境中，这一步还可能与查询表连接、调用外部 API，或者应用模型预测结果。</li>
<li><code>run_streaming_pipeline</code> 将各个部分组合起来。针对 <code>event_source</code> 的 <code>for</code> 循环会逐个消费事件，让每个事件依次经过 <code>validate → enrich → route</code>（验证 → 扩充 → 路由）阶段，并持续记录已处理和已跳过的事件数量。</li>
</ul>
<h3 id="">流式处理适合哪些场景</h3>
<p>在以下情况下，流式处理管道通常是合适的选择：</p>
<ul>
<li><strong>数据时效性以秒或毫秒来衡量。</strong> 欺诈检测、实时库存更新、实时数据看板和告警系统都要求数据立即得到处理——如果批处理作业每小时才运行一次，这些系统就会失去作用。</li>
<li><strong>数据量大到不适合先积累再处理。</strong> 高频物联网传感器数据、点击流事件和金融行情数据每小时可能产生数百万条记录。先积累所有数据再处理通常并不现实——这需要巨大的存储空间，而且处理作业耗时过长，等结果出来时可能已经失去价值。</li>
<li><strong>你需要作出反应，而不只是生成报告。</strong> 流式处理管道可以针对单个事件触发下游操作，例如发送通知、阻止交易或更新推荐结果。批处理管道只能报告已经发生的事情。</li>
</ul>
<h2 id="">关键区别一览</h2>
<p>下面汇总了前文讨论的批处理与流式处理之间的区别：</p>
<table>
<thead>
<tr>
<th style="text-align:left"><strong>维度</strong></th>
<th style="text-align:left"><strong>批处理</strong></th>
<th style="text-align:left"><strong>流式处理</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><strong>数据模型</strong></td>
<td style="text-align:left">有边界、有限的数据集</td>
<td style="text-align:left">无边界、持续的数据流</td>
</tr>
<tr>
<td style="text-align:left"><strong>处理触发方式</strong></td>
<td style="text-align:left">计划任务（时间或事件）</td>
<td style="text-align:left">每条记录到达时触发</td>
</tr>
<tr>
<td style="text-align:left"><strong>延迟</strong></td>
<td style="text-align:left">几分钟到几小时</td>
<td style="text-align:left">几毫秒到几秒</td>
</tr>
<tr>
<td style="text-align:left"><strong>吞吐量</strong></td>
<td style="text-align:left">高（针对批量处理进行了优化）</td>
<td style="text-align:left">单条记录的开销较低</td>
</tr>
<tr>
<td style="text-align:left"><strong>复杂度</strong></td>
<td style="text-align:left">较低</td>
<td style="text-align:left">较高</td>
</tr>
<tr>
<td style="text-align:left"><strong>状态管理</strong></td>
<td style="text-align:left">每次运行通常无状态</td>
<td style="text-align:left">通常需要跨事件维护状态</td>
</tr>
<tr>
<td style="text-align:left"><strong>错误处理</strong></td>
<td style="text-align:left">重试整个作业</td>
<td style="text-align:left">针对单个事件使用死信队列</td>
</tr>
<tr>
<td style="text-align:left"><strong>一致性</strong></td>
<td style="text-align:left">强一致性（时间点快照）</td>
<td style="text-align:left">最终一致性</td>
</tr>
<tr>
<td style="text-align:left"><strong>最适合的场景</strong></td>
<td style="text-align:left">报表、机器学习训练、数据回填</td>
<td style="text-align:left">告警、实时功能、事件路由</td>
</tr>
</tbody>
</table>
<h2 id="">如何在批处理与流式处理之间做出选择</h2>
<p>这些信息固然很有用，但究竟应该如何在批处理和流式处理之间做出选择？这个决定主要取决于三个问题：</p>
<p><strong>数据需要保持多高的时效性？</strong> 如果利益相关者可以接受几小时前的结果，批处理更简单，也更具成本效益。如果他们需要在几秒内获得结果，那么流式处理就不可避免。</p>
<p><strong>处理逻辑有多复杂？</strong> 批处理作业可以连接大型数据集、执行计算成本较高的聚合，并应用复杂的业务逻辑，而不必担心延迟。流式处理管道必须快速处理每个事件，因此会限制单条记录能够承载的处理工作量。</p>
<p><strong>团队具备多强的运维能力？</strong> 流式处理基础设施——Kafka 集群、Flink 或 Spark Streaming 作业、死信队列，以及恰好一次（exactly-once）交付保证——其运维复杂度远高于按计划运行的 Python 脚本。如果团队规模较小，或者用例并不要求实时结果，那么这种复杂性只会增加成本，而不会带来实际收益。</p>
<p>从批处理开始。它更容易构建、测试、调试和维护。只有在某项具体、明确的需求——而不是对未来的假设——使批处理无法满足要求时，再转向流式处理。大多数数据问题都属于批处理问题，而真正需要流式处理的问题，通常会在你遇到时表现得非常明显。</p>
<p>正如你可能已经猜到的那样，有些数据处理系统需要将两种方式结合起来，这正是混合方案存在的原因。</p>
<h2 id="lambdakappa">混合模式：Lambda 架构与 Kappa 架构</h2>
<p>在实践中，许多生产环境中的数据系统会同时使用这两种模式。最常见的两种混合架构是 Lambda 架构和 Kappa 架构。</p>
<p><a href="https://www.databricks.com/glossary/lambda-architecture"><strong>Lambda 架构</strong></a>会并行运行一个批处理层和一个流式处理层。批处理层负责处理完整的历史数据，并在一定延迟后生成准确、一致的结果。流式处理层负责处理实时数据，并立即生成近似结果。下游消费者会合并两层的输出——利用流式处理结果保证时效性，利用批处理结果保证正确性。</p>
<p>这种架构的代价是运维复杂度：你需要维护两套独立的处理代码，并确保它们产生在语义上等价的结果。</p>
<p><a href="https://hazelcast.com/glossary/kappa-architecture/"><strong>Kappa 架构</strong></a>通过只使用流式处理层来简化系统，但当你需要进行类似批处理的重新处理时，它允许通过同一条管道重放历史数据。当 <a href="https://kafka.apache.org/documentation/">Apache Kafka</a> 和 <a href="https://flink.apache.org/">Apache Flink</a> 等流式处理框架支持日志保留和重放时，这种方案会很有效。你只需要维护一套代码和一套处理逻辑，并且可以在管道发生变化后重新处理历史数据。</p>
<p>这两种架构都不是在所有场景下更优。Lambda 架构更常见于那些先采用批处理、后来逐步加入流式处理的组织。Kappa 架构则更常见于从一开始就将流式处理作为主要模式来设计的系统。</p>
<h2 id="">总结</h2>
<p>批处理和流式处理是具有不同权衡的工具，各自适合不同类型的问题。批处理管道在一致性、简单性和批量吞吐方面表现出色；流式处理管道则擅长实现低延迟、事件响应和持续处理。</p>
<p>在选择 Apache Spark、Kafka 或 Flink 等具体框架之前，先从架构层面理解这两种模式，可以帮助你判断哪一种更适合当前问题，并清楚地解释这一选择。框架负责实现这些模式，而哪种模式适合你的问题，则需要由你先作出判断。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 使用 Python 和多进程构建简历筛选系统 ]]>
                </title>
                <description>
                    <![CDATA[ 招聘合适的人选，往往从一项耗时的工作开始：筛选简历。如果你发布过职位招聘，一定体会过收件箱里有数百份申请，而你得花数小时手动审阅每份简历的烦恼。 本文将带你用纯 Python 搭建一个简历筛选系统，重点讲解核心编程概念和多进程的运用。你会创建一个自定义系统，通过将非结构化的简历文档转化为排名榜单，实现评估流程的自动化。 阅读完本指南后，你将能够：  * 解析文档：使用 Python 从 PDF 和 DOCX 简历中提取文本  * 提取信息：从简历内容中识别技能与关键词  * 设计评分算法：用加权逻辑客观地为候选人排序  * 构建 Web 界面：使用 Streamlit 完成  * 部署应用：在 Streamlit Cloud 上部署应用，供公众访问 按照本教程操作，你将构建一个能在几秒内处理数百份简历的工具。 源代码见 GitHub 仓库 [https://github.com/abdultalha0862/Resume_Parser_Project] 目录  * 前置要求  * 项目概览  * 系统如何工作  * ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/python-resume-screening-system/</link>
                <guid isPermaLink="false">69904195be64e2045fddd24a</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Sat, 14 Feb 2026 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/02/----_20260214181319_13_235.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/python-resume-screening-system/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build a Résumé Screening System Using Python and Multiprocessing</a>
      </p><!--kg-card-begin: markdown--><p>招聘合适的人选，往往从一项耗时的工作开始：筛选简历。如果你发布过职位招聘，一定体会过收件箱里有数百份申请，而你得花数小时手动审阅每份简历的烦恼。</p>
<p>本文将带你用纯 Python 搭建一个简历筛选系统，重点讲解核心编程概念和多进程的运用。你会创建一个自定义系统，通过将非结构化的简历文档转化为排名榜单，实现评估流程的自动化。</p>
<p>阅读完本指南后，你将能够：</p>
<ul>
<li>解析文档：使用 Python 从 PDF 和 DOCX 简历中提取文本</li>
<li>提取信息：从简历内容中识别技能与关键词</li>
<li>设计评分算法：用加权逻辑客观地为候选人排序</li>
<li>构建 Web 界面：使用 Streamlit 完成</li>
<li>部署应用：在 Streamlit Cloud 上部署应用，供公众访问</li>
</ul>
<p>按照本教程操作，你将构建一个能在几秒内处理数百份简历的工具。</p>
<p>源代码见 <a href="https://github.com/abdultalha0862/Resume_Parser_Project">GitHub 仓库</a></p>
<h2 id="">目录</h2>
<ul>
<li>前置要求</li>
<li>项目概览</li>
<li>系统如何工作</li>
<li>系统架构</li>
<li>项目结构</li>
<li>步骤 1：搭建项目</li>
<li>步骤 2：构建简历解析器</li>
<li>步骤 3：构建关键词提取器</li>
<li>步骤 4：实现评分引擎</li>
<li>步骤 5：构建 Web 界面</li>
<li>步骤 6：测试系统</li>
<li>步骤 7：部署应用</li>
<li>总结</li>
</ul>
<h2 id="">前置要求</h2>
<p>要学习本教程，你需要具备：</p>
<ul>
<li>Python 基础知识（函数、循环、字典）</li>
<li>已安装 Python 3.8 或更高版本</li>
<li>会用 <code>pip</code> 安装包</li>
<li>代码编辑器（如 VS Code、PyCharm 或任意你喜欢的编辑器）</li>
</ul>
<h2 id="">项目概览</h2>
<p>本指南将开发一个系统：输入一个简历文件夹和一份职位描述（JD），系统会处理每份简历、提取相关信息，并根据候选人与职位要求的匹配程度计算得分。</p>
<h2 id="">系统如何工作</h2>
<p>项目包含四个核心组件：</p>
<ul>
<li><strong>简历解析器</strong>：读取 PDF 和 DOCX 文件并提取文本</li>
<li><strong>职位描述解析器</strong>：分析职位描述以识别所需技能</li>
<li><strong>关键词提取器</strong>：将简历内容与技能分类体系进行匹配</li>
<li><strong>评分引擎</strong>：使用加权算法对候选人排序</li>
</ul>
<h3 id="">评分公式</h3>
<p>使用的评分公式如下：</p>
<pre><code>总分 =
（必备技能 × 50%）+
（优先技能 × 25%）+
（经验 × 15%）+
（关键词 × 10%）
</code></pre>
<p>这样设计可以确保核心技能比次要关键词权重更高。</p>
<h3 id="">这种方法如何有助于减少偏见</h3>
<p>本系统基于预定义标准评估简历，而不是主观判断。每份简历都根据同一套必备技能、优先技能、经验指标和关键词进行打分。</p>
<p>由于所有候选人都使用同一套加权公式评估，写作风格、排版或个人偏好等主观因素不会影响排名。评分逻辑只关注简历与职位要求的匹配程度。</p>
<p>通过将评估过程标准化，系统促进了更一致、更客观的筛选，有助于在简历初筛阶段减少偏见。</p>
<h2 id="">系统架构</h2>
<pre><code>输入                    处理                       输出
─────                   ──────────                  ─────

简历 ──► 简历解析器 ──► 关键词提取器 ──┐
(PDF/DOCX)                             │
                                        ├──► 评分引擎 ──► 排名结果
职位描述 ──► 职位描述解析器 ────────────┘
(TXT/PDF)
</code></pre>
<p>系统遵循简单的“输入—处理—输出”流程。</p>
<p>简历和职位描述作为输入。简历解析器从每份简历中提取文本，职位描述解析器从职位描述中识别必备技能和优先技能。</p>
<p>提取出的简历文本随后传给关键词提取器，该模块根据预定义的技能分类体系匹配技能和关键词。</p>
<p>最后，评分引擎应用加权公式为每位候选人计算得分，并输出一份按分数排序的简历列表。</p>
<h2 id="">项目结构</h2>
<pre><code>resume_screening_system/
├── app.py                    # Streamlit Web 界面
├── main.py                   # 命令行界面
├── parsers/
│   ├── resume_parser.py      # PDF/DOCX 文本提取
│   └── jd_parser.py          # 职位描述解析
├── extractors/
│   └── keyword_extractor.py  # 技能与经验提取
├── matcher/
│   └── scorer.py             # 评分算法
├── data/
│   ├── config.json           # 评分权重配置
│   └── skills_taxonomy.json  # 技能数据库
└── requirements.txt          # 依赖
</code></pre>
<p>项目按清晰的模块化目录组织。解析逻辑、关键词提取和评分分别放在各自文件夹中，配置文件与数据单独存放，便于浏览、维护和扩展。</p>
<h2 id="1">步骤 1：搭建项目</h2>
<p>创建目录结构并设置虚拟环境：</p>
<pre><code class="language-bash">mkdir resume_screening_system
cd resume_screening_system
mkdir parsers extractors matcher data input output
python -m venv venv
</code></pre>
<p>然后激活虚拟环境：</p>
<pre><code class="language-bash"># Windows
source venv/Scripts/activate

# macOS / Linux
source venv/bin/activate
</code></pre>
<p>安装所需依赖：</p>
<pre><code class="language-bash">pip install PyPDF2 python-docx streamlit pandas
</code></pre>
<h2 id="2">步骤 2：构建简历解析器</h2>
<p>简历解析器针对不同文件格式使用不同的提取方法。</p>
<p>对于 PDF，解析器逐页打开文档，用 PDF 阅读器从每页提取文本，再合并成单个字符串供后续处理。</p>
<p>对于 DOCX，解析器读取文档中的每个段落，将段落文本拼接成一块。这样无论简历格式如何，都能得到一致的文本输出。</p>
<p>将简历统一转为纯文本后，关键词提取和评分等组件就能高效工作。</p>
<p><strong>文件：</strong> <code>parsers/resume_parser.py</code></p>
<pre><code class="language-python">def _extract_pdf(self, file_path: Path) -&gt; str:
    text = ""
    with open(file_path, "rb") as file:
        pdf_reader = PyPDF2.PdfReader(file)
        for page in pdf_reader.pages:
            page_text = page.extract_text()
            if page_text:
                text += page_text + "\n"
    return text.strip()

def _extract_docx(self, file_path: Path) -&gt; str:
    from docx import Document
    doc = Document(file_path)
    return "\n".join(
        para.text for para in doc.paragraphs
    ).strip()
</code></pre>
<h2 id="3">步骤 3：构建关键词提取器</h2>
<p>本项目使用 <a href="https://www.kaggle.com/datasets/snehaanbhawal/resume-dataset">Kaggle</a> 上的简历数据集，确保逻辑在真实职业数据上可用。关键词提取器通过扫描简历文本来识别技能。</p>
<p>简历文本先转为小写，以便不区分大小写地匹配。预定义的技能分类体系存储每个技能及其可能变体，提取器用这些变体与简历文本比对。</p>
<p>匹配时使用词边界，避免部分匹配（例如在 “JavaScript” 里匹配到 “Java”）。匹配到的技能存入集合以避免重复。</p>
<p>这种方式能在所有简历上实现一致、可控的技能识别。</p>
<p><strong>文件：</strong> <code>extractors/keyword_extractor.py</code></p>
<pre><code class="language-python">def extract_skills(self, text: str) -&gt; Set[str]:
    text_lower = text.lower()
    found_skills = set()

    for category, skills_dict in self.skills_taxonomy.items():
        for skill_name, variations in skills_dict.items():
            for variation in variations:
                # 防止 "Java" 匹配到 "JavaScript"
                pattern = r"\b" + re.escape(variation) + r"\b"
                if re.search(pattern, text_lower):
                    found_skills.add(skill_name)
                    break

    return found_skills
</code></pre>
<h2 id="4">步骤 4：实现评分引擎</h2>
<p>为得到客观排名，系统使用加权评分公式。</p>
<table>
<thead>
<tr>
<th>组件</th>
<th>权重</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>必备技能</td>
<td>50%</td>
<td>核心技术需求</td>
</tr>
<tr>
<td>优先技能</td>
<td>25%</td>
<td>差异化竞争力</td>
</tr>
<tr>
<td>经验</td>
<td>15%</td>
<td>专业深度</td>
</tr>
<tr>
<td>关键词</td>
<td>10%</td>
<td>领域熟悉度</td>
</tr>
</tbody>
</table>
<pre><code>总分 =
(S_req × 0.50) +
(S_pref × 0.25) +
(E_exp × 0.15) +
(K_key × 0.10)
</code></pre>
<p>评分引擎用上述权重为每份简历计算最终得分。</p>
<p>它统计简历中出现的必备技能、优先技能、经验指标和关键词数量，每个数量乘以其对应权重（必备技能权重最高），加权值相加得到单一分数，再按该分数对简历排序，生成候选人排名列表。</p>
<h2 id="5web">步骤 5：构建 Web 界面</h2>
<p>Streamlit 为简历筛选系统提供了简单的 Web 界面。</p>
<p>文本区域用于输入职位描述，文件上传器用于上传多份简历。点击按钮后，Streamlit 触发后端逻辑解析简历、提取数据并计算得分，结果在浏览器中展示，用户无需使用命令行即可完成筛选。</p>
<p><strong>文件：</strong> <code>app.py</code></p>
<pre><code class="language-python">import streamlit as st

jd_text = st.text_area(
    "在此粘贴职位描述：",
    height=300
)

uploaded_files = st.file_uploader(
    "上传简历文件：",
    type=["pdf", "docx", "txt"],
    accept_multiple_files=True
)

if st.button("筛选简历", type="primary"):
    st.success("正在处理简历...")
</code></pre>
<p>运行应用：</p>
<pre><code class="language-bash">streamlit run app.py
</code></pre>
<p>应用将运行在 <a href="http://localhost:8500">http://localhost:8500</a>。</p>
<h2 id="6">步骤 6：测试系统</h2>
<h3 id="">示例职位描述输入</h3>
<p>下面是一份可用于测试的示例职位描述：</p>
<pre><code>我们正在寻找一名具有扎实后端开发经验的高级 Python 开发工程师。

必备技能：
- Python
- Django
- REST API
- SQL

优先技能：
- PostgreSQL
- Docker
- AWS

经验要求：
- 3 年以上 Python 专业开发经验
- 有构建 Web 应用的经验
</code></pre>
<p>该输入帮助系统识别必备技能、优先技能和经验关键词，供评分引擎对简历排序。</p>
<pre><code class="language-bash">python main.py
</code></pre>
<h3 id="">示例输出</h3>
<pre><code>============================================================
筛选结果
============================================================
第 1 名：Alice Johnson | 得分：85.42/100 | 匹配：python、django、postgresql
第 2 名：Carol Davis   | 得分：72.50/100 | 匹配：python、django
</code></pre>
<h2 id="7">步骤 7：部署应用</h2>
<p>若要让系统对外可访问：</p>
<ol>
<li>将代码推送到 GitHub</li>
<li>打开 share.streamlit.io</li>
<li>选择你的 <code>app.py</code> 文件</li>
<li>部署应用</li>
</ol>
<p>应用将发布在：</p>
<pre><code>https://your-app-name.streamlit.app
</code></pre>
<h2 id="">总结</h2>
<p>在本教程中，你使用 Python 从零搭建了一个完整的简历筛选系统。通过结合文本处理、结构化评分和自动化，该项目展示了如何将手动简历筛选转变为高效、客观的流程。</p>
<p>该系统有助于减少偏见、节省时间，并更一致地评估候选人。Happy coding!</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何成为 AI 辅助编程专家 - 开发者手册 ]]>
                </title>
                <description>
                    <![CDATA[ 在过去的七年里，我一直负责 freeCodeCamp 的基础设施。现在我确信，经验丰富的开发者可以在保持代码质量的同时提升 3-4 倍的编码速度。这就是 AI 辅助开发能够提供的优势。简单来说，使用像 GitHub Copilot 这样的 AI 工具作为你的编码伙伴可以提高效率。它们可以建议代码、帮助你调试以及加快处理重复性任务。 为什么这很重要 传统编码时，你需要自己编写每一行代码、查找文档、弄清语法。有了 AI，你可以：  * 专注于解决问题，而不是记住语法          * 通过实时看到优秀的代码示例更快地学习          * 快速构建项目而不牺牲质量         对于有经验的开发者来说，在 AI ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-become-an-expert-in-ai-assisted-coding-a-handbook-for-developers/</link>
                <guid isPermaLink="false">6953dfe138384c0457be01cb</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Fri, 09 Jan 2026 11:16:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/01/----_20260110202949_190_113.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-become-an-expert-in-ai-assisted-coding-a-handbook-for-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Become an Expert in AI-Assisted Coding – A Handbook for Developers</a>
      </p><!--kg-card-begin: markdown--><p>在过去的七年里，我一直负责 freeCodeCamp 的基础设施。现在我确信，经验丰富的开发者可以在保持代码质量的同时提升 3-4 倍的编码速度。这就是 AI 辅助开发能够提供的优势。简单来说，使用像 GitHub Copilot 这样的 AI 工具作为你的编码伙伴可以提高效率。它们可以建议代码、帮助你调试以及加快处理重复性任务。</p>
<h3 id="">为什么这很重要</h3>
<p>传统编码时，你需要自己编写每一行代码、查找文档、弄清语法。有了 AI，你可以：</p>
<ul>
<li>
<p>专注于解决问题，而不是记住语法</p>
</li>
<li>
<p>通过实时看到优秀的代码示例更快地学习</p>
</li>
<li>
<p>快速构建项目而不牺牲质量</p>
</li>
</ul>
<p>对于有经验的开发者来说，在 AI 帮助下可以更快地完成任务。但关键是：<strong>你需要了解如何有效使用这些工具</strong>。你也需要有编程背景来做到这一点。</p>
<p>感兴趣吗？让我们深入了解这些已经风靡全球的 AI 编码工具。</p>
<h2 id="">目录</h2>
<ul>
<li>
<p><a href="#essential-ai-terminology">基础 AI 术语</a></p>
</li>
<li>
<p><a href="#when-to-use-ai-vs-when-to-code-yourself">何时使用 AI 以及何时自己编码</a></p>
</li>
<li>
<p><a href="#prerequisites">前提条件</a></p>
</li>
<li>
<p><a href="#your-complete-learning-journey">你的完整学习旅程</a></p>
</li>
<li>
<p><a href="#how-to-generate-your-first-ai-assisted-code">如何生成你的第一个 AI 辅助代码（快速入门）</a></p>
</li>
<li>
<p><a href="#stage-1">阶段 1：基础 – 开始 AI 编码</a></p>
</li>
<li>
<p><a href="#stage-2">阶段 2：高级 GitHub Copilot 功能</a></p>
</li>
<li>
<p><a href="#stage-3">阶段 3：基于 CLI 的 AI 代理（Claude Code &amp; Gemini）</a></p>
</li>
<li>
<p><a href="#stage-4">阶段 4：掌握 – 结合工具和高级工作流程</a></p>
</li>
<li>
<p><a href="#common-ai-issues">常见 AI 问题</a></p>
</li>
<li>
<p><a href="#whats-next-after-completing-all-stages">完成所有阶段后接下来做什么？</a></p>
</li>
<li>
<p><a href="#conclusion">结语</a></p>
</li>
</ul>
<h2 id="essential-ai-terminology">基础 AI 术语</h2>
<p>在开始之前，确保你了解以下关键术语：</p>
<ul>
<li>
<p><strong>Tokens（令牌）：</strong> 把令牌想象成“词片”——AI 阅读你的代码和文本的方式。每个字符、单词或符号都会使用令牌。免费版本会限制你可以使用的令牌数量。</p>
</li>
<li>
<p><strong>Context Window（上下文窗口）：</strong> AI 能一次性“记住”多少代码/对话。就像短期记忆，更大的窗口意味着对项目的更好理解。</p>
</li>
<li>
<p><strong>Hallucinations（幻觉）：</strong> AI 自信地建议错误信息——例如，捏造不存在的函数。务必验证 AI 的建议！</p>
</li>
<li>
<p><strong>Prompt（提示）：</strong> 你给 AI 的指令——评论、问题或请求，用以指导其生成的代码。</p>
</li>
</ul>
<h2 id="when-to-use-ai-vs-when-to-code-yourself">何时使用 AI 以及何时自己编码</h2>
<p><strong>使用 AI 的场合：</strong></p>
<ul>
<li>
<p>编写模板代码（getters、setters、基本的 CRUD）</p>
</li>
<li>
<p>学习新的框架或语法</p>
</li>
<li>
<p>编写测试和文档</p>
</li>
<li>
<p>重构重复模式</p>
</li>
<li>
<p>在语法错误上解开困境</p>
</li>
</ul>
<p><strong>自己编码的场合：</strong></p>
<ul>
<li>
<p>设计系统架构</p>
</li>
<li>
<p>做出关键的安全决策</p>
</li>
<li>
<p>编写复杂的业务逻辑</p>
</li>
<li>
<p>学习新概念（首次）</p>
</li>
<li>
<p>进行关键的性能优化</p>
</li>
</ul>
<p><strong>黄金法则：</strong> 使用 AI 加速实施，但自己做架构决策。AI 擅长“如何”，但你决定“什么”和“为什么”。</p>
<h2 id="prerequisites">前提条件</h2>
<p>在开始本教程之前，你应该具备：</p>
<ul>
<li>
<p><strong>基本编程经验</strong> – 你可以用某种语言编写简单程序</p>
</li>
<li>
<p><strong>安装有代码编辑器</strong> – 推荐使用 VS Code（从 <a href="http://code.visualstudio.com/">code.visualstudio.com</a> 免费获取）</p>
</li>
<li>
<p><strong>基本 Git 知识</strong> – 你知道如何提交和推送代码</p>
</li>
<li>
<p><strong>免费起步</strong> – 许多工具现在都有慷慨的免费版本，付费计划最低大约每月 10-20 美元</p>
</li>
</ul>
<h2 id="your-complete-learning-journey">你的完整学习旅程</h2>
<p>这篇全面的教程提供了一个循序渐进的计划，旨在助你成为 AI 辅助开发的专家：</p>
<p>注意：为了保持教程的易用性，我们将只关注一些核心工具。但你应该研究和探索更多适合你特定需求的工具，超出我们在此使用的工具。</p>
<h3 id="">学习路径</h3>
<p>你将经过 4 个阶段：掌握 GitHub Copilot 基础、解锁高级功能如聊天模式和代理、探索 CLI 工具（Claude Code 和 Gemini），最终战略性地结合多个工具实现完整的项目工作流程。</p>
<p>首先，让我们快速看看如何生成你的第一个 AI 代码片段。</p>
<h2 id="how-to-generate-your-first-ai-assisted-code">如何生成你的第一个 AI 辅助代码（快速入门）</h2>
<p>让我们从最基础的开始。别担心选择“完美”的工具——你总可以后续切换。以下是开始的方法：</p>
<h3 id="githubcopilot">GitHub Copilot（推荐初学者使用）</h3>
<p>你可以通过以下步骤安装 GitHub Copilot：</p>
<ol>
<li>
<p>打开 VS Code</p>
</li>
<li>
<p>点击扩展图标（或按 Ctrl+Shift+X）</p>
</li>
<li>
<p>搜索“GitHub Copilot”</p>
</li>
<li>
<p>点击“安装”</p>
</li>
<li>
<p>使用你的 GitHub 账户登录</p>
</li>
</ol>
<p><strong>提示：</strong> 学生、教师和开源软件维护者<a href="https://docs.github.com/en/copilot/how-tos/manage-your-account/getting-free-access-to-copilot-pro-as-a-student-teacher-or-maintainer">可以免费获得 Pro 计划</a>，这可以提供无限制的使用，而不是免费的使用限制。</p>
<h3 id="ai">你的第一个 AI 建议</h3>
<p>安装后，创建一个名为 <code>test.js</code> 的新文件，并输入：</p>
<pre><code>// function to calculate the area of a circle
</code></pre>
<p>按下 Enter 键等待。你会看到灰色文本出现——这是您的 AI 建议！按 Tab 键接受它。</p>
<p>就这样！您刚刚获得了第一个 AI 建议！是不是很酷？</p>
<h2 id="stage-1">阶段 1：基础 – 开始使用 AI 编码</h2>
<h3 id="1">第 1 步：了解您的选项</h3>
<p>将 AI 编码助手想象成不同类型的有帮助的朋友和同事。让我们来浏览几个：</p>
<p><strong>基于 IDE 的：</strong> 一些工具设计为可以与熟悉的代码编辑器一起工作，或作为编辑器如 VS Code 的独立分支。例如：</p>
<ul>
<li>
<p><strong>GitHub Copilot (VS Code 扩展)</strong> – 来自 GitHub 的 AI 编码助手，直接在 VS Code 中工作，提供 Tab 补全和聊天功能</p>
</li>
<li>
<p><strong>Cursor (独立)</strong> – VS Code 分支，具有增强的代理模式、更快的自主编码和更好的大代码库重构处理</p>
</li>
<li>
<p><strong>Windsurf (独立或 VS Code 扩展)</strong> – 专注于实时建议和团队功能的协作 AI 开发</p>
</li>
<li>
<p><strong>Zed</strong> – 高性能编辑器，内置 AI 辅助功能和快速渲染</p>
</li>
</ul>
<p><strong>基于 CLI 的：</strong> 一些工具是基于 CLI 的，您可以在终端应用中启动：</p>
<ul>
<li>
<p><strong>Claude Code</strong> – Anthropic 的终端 AI，用于自主开发会话和复杂推理</p>
</li>
<li>
<p><strong>Gemini</strong> – Google 的 CLI 工具，具有大上下文窗口和多模态功能（图像、文档）</p>
</li>
<li>
<p><strong>OpenCode</strong> – 开源替代方案，具有可自定义模型和本地处理选项</p>
</li>
<li>
<p><strong>Cursor CLI</strong> – Cursor 的终端版本，用于命令行 AI 辅助</p>
</li>
</ul>
<p><strong>基于 UI 和后台代理的工具：</strong> 除此之外，还有一些后台代理和工具可以完全在后台运行，例如执行拉取请求审查等。</p>
<p>例如，如果您设置了它们，ChatGPT 和 Claude 的桌面应用程序都可以编辑本地文件系统上的文件。同样，一些基于云的代理可以“在后台运行”以完成您的指令。我们将在本指南范围内排除这些。</p>
<h3 id="2tab">第 2 步：做出选择并学习自动建议（Tab 补全）</h3>
<p>对于您的第一阶段，我建议从 GitHub Copilot 开始。您可以在学习完基础知识后随时切换到适合您需求的工具。</p>
<h3 id="3">第 3 步：逐步设置</h3>
<h4 id="githubcopilot">如何设置 GitHub Copilot（如果您之前已经遵循过快速入门，可以跳过此步骤）</h4>
<ol>
<li>
<p><strong>打开 VS Code。</strong> 如果您没有，请从 <a href="https://code.visualstudio.com/">code.visualstudio.com</a> 下载。</p>
</li>
<li>
<p><strong>安装扩展</strong></p>
<ul>
<li>
<p>按 <code>Ctrl+Shift+X</code>（Windows/Linux）或 <code>Cmd+Shift+X</code>（Mac）</p>
</li>
<li>
<p>在搜索框中输入“GitHub Copilot”</p>
</li>
<li>
<p>点击蓝色的“安装”按钮</p>
</li>
<li>
<p>您会看到一个弹出窗口要求您登录</p>
</li>
</ul>
</li>
<li>
<p><strong>登录</strong></p>
<ul>
<li>
<p>点击“登录 GitHub”</p>
</li>
<li>
<p>您的浏览器会打开</p>
</li>
<li>
<p>使用您的 GitHub 帐户登录（如果需要，可以在 <a href="http://github.com/">github.com</a> 免费创建一个）</p>
</li>
<li>
<p>点击“授权 GitHub Copilot”</p>
</li>
</ul>
</li>
<li>
<p><strong>开始使用 Copilot</strong></p>
<ul>
<li>返回 VS Code，您会看到“GitHub Copilot 已准备好使用”</li>
</ul>
</li>
</ol>
<h3 id="4tab">第 4 步：掌握 Tab 补全</h3>
<p>让我们确保它正常工作。创建一个新文件：<code>hello.py</code>。输入此注释并按 Enter：</p>
<pre><code># function to greet a user by name
</code></pre>
<p>等待 1-2 秒。您应该会看到灰色文本出现。只需按 <code>Tab</code> 接受建议。</p>
<p><strong>您应该看到的内容：</strong></p>
<pre><code># function to greet a user by name
def greet_user(name):
    return f"Hello, {name}!"
</code></pre>
<p>如果您看到了这个，恭喜您！您现在正在使用 AI 帮助您编写代码。</p>
<p>如果您遇到设置问题，可以查看 <a href="http://localhost:3333/#troubleshooting-quick-reference">疑难解答快速参考</a> 寻求解决方案。</p>
<h3 id="5">第 5 步：基本键盘快捷键和第一次练习</h3>
<p>以下是您第一周需要的唯一快捷键：</p>
<p><strong>基础：</strong></p>
<ul>
<li>
<p><code>Tab</code> – 接受 AI 建议（这个用得最多！）</p>
</li>
<li>
<p><code>Esc</code> – 拒绝建议（当您不需要时）</p>
</li>
</ul>
<p>当您准备好了解更多时，尝试这些：</p>
<p><strong>Windows/Linux：</strong></p>
<ul>
<li>
<p><code>Alt+]</code> – 查看下一个建议</p>
</li>
<li>
<p><code>Alt+[</code> – 查看上一个建议</p>
</li>
<li>
<p><code>Ctrl+Enter</code> – 在面板中查看所有建议</p>
</li>
</ul>
<p><strong>macOS：</strong></p>
<ul>
<li>
<p><code>Option+]</code> （或 <code>Alt+]</code>） – 查看下一个建议</p>
</li>
<li>
<p><code>Option+[</code> （或 <code>Alt+[</code>) – 查看上一个建议</p>
</li>
<li>
<p><code>Ctrl+Enter</code> – 在面板中查看所有建议</p>
</li>
</ul>
<h3 id="1">阶段 1 实践练习</h3>
<h4 id="">练习：构建一个简单的待办事项应用</h4>
<ol>
<li>
<p>创建一个名为 <code>todo.js</code> 的新文件</p>
</li>
<li>
<p>以此注释开始：<code>// TODO app with add, remove, and list functions</code></p>
</li>
<li>
<p>添加此注释并等待 AI 建议：<code>// function to add a new todo item</code></p>
</li>
<li>
<p>如果建议看起来不错，按 Tab 键接受</p>
</li>
<li>
<p>继续为移除和列出函数添加注释</p>
</li>
<li>
<p>测试您的函数以确保它们工作正常</p>
</li>
</ol>
<p><strong>目标：</strong> 学习通过清晰的注释与 AI “对话”，并建立接受/拒绝建议的信心。</p>
<h3 id="">准备好进入下一阶段了吗？在继续之前，请确保您能够：</h3>
<pre><code>- [ ] 通过输入注释获取 AI 建议
- [ ] 使用 Tab 接受建议，使用 Esc 拒绝建议
- [ ] 使用 Alt+] 和 Alt+[ 查看不同的建议
- [ ] 在 AI 帮助下编写基本函数
</code></pre>
<p>如果您对这些基本内容感到满意，就可以学习更强大的 Copilot 功能了。</p>
<h2 id="stage-2">阶段 2：高级 GitHub Copilot 功能</h2>
<h3 id="6ai">第 6 步：获取更好的 AI 建议</h3>
<p>现在您已经了解了基础知识，让我们学习如何从您的 AI 那里得到_更好_的建议。诀窍在于了解您的 AI 能看到什么。</p>
<h4 id="ai">您的 AI 助手能看到什么</h4>
<p>想象您的 AI 助手就像一个站在你肩头、随时提供帮助的朋友。它能看到：</p>
<ol>
<li>
<p><strong>您当前正在输入的内容</strong>——您的当前文件</p>
</li>
<li>
<p><strong>其他打开的标签页</strong>——您打开的文件（这很重要！）</p>
</li>
<li>
<p><strong>您的项目结构</strong>——文件夹和文件名</p>
</li>
<li>
<p><strong>您的注释</strong>——这是您与 AI “对话” 的方式</p>
</li>
</ol>
<h4 id="">“相邻标签页”技巧</h4>
<p>这是一个能为您节省数小时的专业提示：<strong>在标签页中打开相关文件</strong>。</p>
<p><strong>示例：</strong> 如果您在编写一个 React 组件：</p>
<ul>
<li>
<p>打开您的组件文件 (<code>Button.jsx</code>)</p>
</li>
<li>
<p>还要打开 CSS 文件 (<code>Button.css</code>)</p>
</li>
<li>
<p>同时也让测试文件可见 (<code>Button.test.js</code>)</p>
</li>
</ul>
<p>然后，您可以通过几种方式与 AI 共享这些额外文件的上下文：</p>
<ul>
<li>
<p><strong>@提及文件：</strong> 在聊天中键入 <code>@filename.js</code> 以引用特定文件</p>
</li>
<li>
<p><strong>使用 @workspace：</strong> 这个聊天参与者可以看到您项目中的所有文件</p>
</li>
<li>
<p><strong>拖放：</strong> 只需将文件从资源管理器窗口拖动到聊天窗口</p>
</li>
<li>
<p><strong>选择代码：</strong> 高亮代码并右键单击 “Ask Copilot” 以将其包括在上下文中</p>
</li>
</ul>
<p>AI 使用这些打开的文件来理解您的项目结构，并建议更相关的代码以匹配您现有的模式。</p>
<h3 id="7">第 7 步：质量控制与最佳实践</h3>
<h4 id="ai">了解 AI 的局限性</h4>
<p>AI 功能强大，但并不完美。以下是需要注意的关键事项。</p>
<p><strong>常见的 AI 错误：</strong></p>
<ol>
<li>
<p>虚构的函数：例如，<code>const result = array.superSort();</code> 并不存在！</p>
</li>
<li>
<p>错误的参数：例如，当函数期望 <code>greetUser(name)</code> 时，<code>greetUser("John", "Doe");</code></p>
</li>
<li>
<p>过于复杂的解决方案：例如，<code>const isEven = (num) =&gt; num.toString(2).slice(-1) === "0";</code> - 只需使用 <code>num % 2 === 0</code></p>
</li>
</ol>
<p>快速质量检查清单：</p>
<pre><code>- [ ] 测试代码 - 它真的有效吗？
- [ ] 阅读 - 逻辑上是否合理？
- [ ] 检查基础 - 所有函数/变量是否已定义？
- [ ] 信任直觉 - 如果感觉不对，进行调查
</code></pre>
<h4 id="">安全要点</h4>
<p>在接受 AI 建议之前，请确保检查这些安全问题：</p>
<pre><code>- [ ] 没有硬编码的密码或 API 密钥
- [ ] 用户输入已验证
- [ ] 没有对用户数据使用 eval()
- [ ] 错误消息不暴露敏感信息
</code></pre>
<h4 id="">更好的提示写作</h4>
<p>这里有一个编写可靠提示的公式：是什么 + 如何 + 返回类型。</p>
<pre><code>// ❌ 含糊： "make function"
// ✅ 清晰： "function to validate email format using regex, returns boolean"
</code></pre>
<h4 id="copilot">使用 Copilot 指令进行仓库级自定义</h4>
<p>GitHub Copilot 现在支持通过 <code>.github/copilot-instructions.md</code> 文件进行仓库级别的自定义。此功能帮助 Copilot 理解您项目的特定模式和约定。</p>
<p>以下是设置 Copilot 指令的方法：</p>
<pre><code># 如果不存在，创建 GitHub 目录
mkdir -p .github
touch .github/copilot-instructions.md
</code></pre>
<p>示例 <a href="http://copilot-instructions.md/">copilot-instructions.md</a> 文件：</p>
<pre><code># Copilot 指令

## 代码风格

- 使用具有 hooks 的 React 函数组件
- 为新文件更偏好使用 TypeScript 而不是 JavaScript
- 使用 Tailwind CSS 进行样式设计
- 遵循 `/src/components` 中的现有文件结构

## 测试

- 使用 React Testing Library 编写测试
- 将测试文件放在 `__tests__` 目录中
- 使用描述性测试名称来解释行为

## API 模式

- 使用自定义钩子进行 API 调用
- 一致地处理加载和错误状态
- 使用 React Query 进行数据提取

## 命名约定

- 组件：PascalCase（例如，`UserProfile.tsx`）
- 钩子：以“use”开头的 camelCase（例如，`useUserData.ts`）
- 工具：camelCase（例如，`formatDate.ts`）
</code></pre>
<p><strong>这项功能实现了：</strong></p>
<ul>
<li>
<p>Copilot 建议符合您项目模式的代码</p>
</li>
<li>
<p>自动遵循您的命名约定</p>
</li>
<li>
<p>建议合适的测试方法</p>
</li>
<li>
<p>理解您偏好的库和框架</p>
</li>
</ul>
<p><strong>最佳实践：</strong></p>
<ul>
<li>
<p>保持指令清晰和具体</p>
</li>
<li>
<p>随着项目标准的发展及时更新</p>
</li>
<li>
<p>包括首选模式的示例</p>
</li>
<li>
<p>提到您使用的库和框架</p>
</li>
</ul>
<h3 id="8copilot">第 8 步：解锁高级 Copilot 功能</h3>
<h4 id="">了解您的选择</h4>
<p>GitHub Copilot 提供多种方式来获得 AI 帮助：</p>
<ol>
<li>
<p><strong>Tab 补全</strong>（您一直在使用的）——在输入时提供建议</p>
</li>
<li>
<p><strong>聊天模式</strong>——与 AI 就您的代码进行对话</p>
</li>
<li>
<p><strong>编辑模式</strong>——请求 AI 对现有代码进行修改</p>
</li>
<li>
<p><strong>代理模式</strong>——让 AI 自主完成大型任务</p>
</li>
</ol>
<h4 id="">模型选择</h4>
<p>Copilot 现在提供不同的 AI 模型以满足不同需求：</p>
<p>订阅免费：</p>
<ul>
<li>
<p><strong>GPT-4.1</strong> – 默认模型，综合性能强大</p>
</li>
<li>
<p><strong>GPT-4</strong> – 对大多数编码任务来说可靠</p>
</li>
</ul>
<p>高级模型（有限的每月使用量）：</p>
<ul>
<li>
<p><strong>Claude 3.5 Sonnet</strong> – 适合复杂逻辑</p>
</li>
<li>
<p><strong>GPT-5</strong> – 最新且最强大</p>
</li>
<li>
<p><strong>Gemini 2.0 Flash</strong> – 响应非常快速</p>
</li>
</ul>
<p><strong>如何切换模型：</strong> 点击聊天视图中的模型下拉菜单</p>
<p><strong>提示：</strong> 学习时从免费模型（GPT-4.1）开始，对于复杂问题保留高级模型使用。</p>
<h4 id="githubcopilot">GitHub Copilot 的局限性</h4>
<p>以下是在使用 AI 来帮助您进行编码时需要考虑的一些重要事项：</p>
<ul>
<li>
<p><strong>互联网依赖性</strong> – 需要稳定的连接以获得建议</p>
</li>
<li>
<p><strong>上下文限制</strong> – 只能看到打开的文件，而不是整个项目结构</p>
</li>
<li>
<p><strong>免费账号限制</strong> – 每月 2,000 次补全和 50 次聊天请求</p>
</li>
<li>
<p><strong>代码质量参差不齐</strong> – 始终审查建议，特别是涉及安全性敏感的代码时</p>
</li>
<li>
<p><strong>学习曲线</strong> – 编写有效的提示需要时间，特别是对于复杂任务</p>
</li>
<li>
<p><strong>隐私考虑</strong> – 您的代码会被发送到 GitHub 的服务器（请检查您的组织的政策）</p>
</li>
</ul>
<h4 id="">基础聊天与建议</h4>
<p>您可能想知道 - 什么时候应该使用 Tab 补全，什么时候应该使用聊天？最好是用 Tab 补全来编写新函数、快速的语法帮助和模式补全。可以使用聊天来解释现有代码、获取错误帮助和规划解决问题的方法。</p>
<p><strong>尝试一下：</strong> 打开聊天（Ctrl+Shift+I）并询问：“这个函数有什么作用？”同时选择代码。</p>
<h3 id="9">第9步：掌握聊天和代理模式</h3>
<h4 id="">三种聊天模式</h4>
<ol>
<li><strong>询问模式（默认）</strong> – 用于问题和解释：</li>
</ol>
<pre><code>“这个函数有什么作用？”
“如何优化这段代码？”
“解释此错误消息”
</code></pre>
<ol start="2">
<li><strong>编辑模式</strong> – 用于对现有代码进行更改：</li>
</ol>
<pre><code>“重构这段代码以使用 async/await”
“为所有 API 调用添加错误处理”
“将其转换为 TypeScript”
</code></pre>
<ul>
<li>
<p>显示内联差异后应用更改</p>
</li>
<li>
<p>可跨多个文件工作</p>
</li>
<li>
<p>适合系统化重构</p>
</li>
</ul>
<ol start="3">
<li><strong>代理模式</strong> – 用于自主开发：</li>
</ol>
<pre><code>“创建一个带身份验证的 REST API”
“使用React和测试构建一个待办应用”
“将此代码库从Vue 2迁移到Vue 3”
</code></pre>
<ul>
<li>
<p>按 <code>Cmd+Shift+I</code> （Mac）或 <code>Ctrl+Shift+Alt+I</code>（Linux）或 <code>Ctrl+Shift+I</code>（Windows）</p>
</li>
<li>
<p>可独立工作数小时</p>
</li>
<li>
<p>自动安装包、创建文件、运行测试</p>
</li>
</ul>
<h4 id="">何时使用各模式</h4>
<p>每种模式都有特定的使用场景。当您学习新概念时，希望理解现有代码、获取解释和去计划方法时，使用询问模式。</p>
<p>当您正在重构现有代码、应用一致的更改、为现有功能添加特性或进行样式/模式更新时，使用编辑模式。</p>
<p>代理模式在构建完整特性（30分钟以上的工作）、设置新项目、大规模重构时有用，并且在您希望在 AI 编码时处理其他事情时使用。</p>
<h4 id="">代理模式示例</h4>
<p>小型代理任务（15分钟）：</p>
<pre><code>“为我的 Express 应用添加用户身份验证”
</code></pre>
<p>代理生成的内容：</p>
<pre><code>// middleware/auth.js
const jwt = require('jsonwebtoken');

const authenticateToken = (req, res, next) =&gt; {
  const authHeader = req.headers['authorization'];
  const token = authHeader &amp;&amp; authHeader.split(' ')[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) =&gt; {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
};

// routes/auth.js
router.post('/login', async (req, res) =&gt; {
  // 使用 bcrypt 进行身份验证的逻辑
  const accessToken = jwt.sign({username: user.username}, process.env.ACCESS_TOKEN_SECRET);
  res.json({accessToken: accessToken});
});
</code></pre>
<p><strong>我发现的关键问题：</strong> 代理最初忘记对密码进行哈希处理，并且没有包括刷新令牌。这需要一次迭代来修复安全漏洞并添加适当的错误处理。</p>
<p>大型代理任务（4小时以上）：</p>
<pre><code>“将此基于类的 React 应用更新为使用 TypeScript 的 hooks”
</code></pre>
<p>代理生成的内容：</p>
<pre><code>// 之前（类组件）
class UserProfile extends React.Component {
  constructor(props) {
    this.state = { user: null, loading: true };
  }
  // ... 生命周期方法
}

// 之后（Hooks + TypeScript）
interface User {
  id: number;
  name: string;
  email: string;
}

const UserProfile: React.FC = () =&gt; {
  const [user, setUser] = useState&lt;User | null&gt;(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&gt; {
    fetchUser().then(setUser).finally(() =&gt; setLoading(false));
  }, []);

  return &lt;div&gt;{loading ? 'Loading...' : user?.name}&lt;/div&gt;;
};
</code></pre>
<p>**我发现的关键问题：**代理成功更新了47个文件，但最初在事件处理程序中存在类型问题，并且需要改进泛型类型。自动化测试还需要人工检查以确保适当的 TypeScript 覆盖。</p>
<p>Chat 参与者是专门的 AI 助手，可以访问您的开发环境中的特定部分。可以将它们视为不同领域的专家，能够帮助完成有针对性的任务。</p>
<p>它们基本上是以 <code>@</code> 作为前缀的 AI 助手，具有特殊的知识和能力：</p>
<ul>
<li>
<p><strong>@workspace</strong> 可以访问您整个项目的结构，可以搜索文件并理解组件之间的关系。当您需要项目范围的分析时，请使用 <code>@workspace</code>："找出此项目中的所有 API 端点" 或 "显示用户身份验证是在哪里实现的。"</p>
</li>
<li>
<p><strong>@terminal</strong> 了解命令行操作，可以建议 shell 命令并解释终端输出。对于命令行帮助，请使用 <code>@terminal</code>："哪个命令可以运行测试？" 或 "如何为生产构建此项目？"</p>
</li>
<li>
<p><strong>@vscode</strong> 是 VS Code 功能方面的专家，可以帮助进行设置、调试和编辑器配置。对于编辑器帮助，请使用 <code>@vscode</code>："为 Node.js 设置调试" 或 "为此项目配置自动格式化。"</p>
</li>
</ul>
<p><strong>示例用法:</strong></p>
<pre><code>@workspace 你能找到这个项目中的所有数据库模型吗？
@terminal 安装依赖项并启动开发服务器的命令是什么？
@vscode 如何设置断点以调试这个 Express 应用？
</code></pre>
<h3 id="10">步骤 10：高级用户功能和高级工作流程</h3>
<p>除了您已经了解的 Copilot 核心功能外，还有一些专门的工具和命令可以极大地提高您的生产力。这些功能超越基本的聊天模式和模型选择，专注于复杂的多文件操作和高级自动化。</p>
<h4 id="">高级斜线命令</h4>
<pre><code>/doc - 生成文档
/explain - 详细的代码说明
/fix - 修复选定代码中的错误
/tests - 生成单元测试
/new - 创建新的项目结构
</code></pre>
<h4 id="">多文件操作</h4>
<p><strong>使用 # 引用：</strong></p>
<p><code>#</code> 符号创建特定的引用，告诉 Copilot 精确关注哪里。这些引用就像是指向项目不同部分的精准指针：</p>
<ul>
<li>
<p><strong>#file:filename</strong>: 引用特定文件：<code>#file:UserModel.js</code></p>
</li>
<li>
<p><strong>#codebase</strong>: 引用整个项目代码库以进行搜索</p>
</li>
<li>
<p><strong>#selection</strong>: 引用当前选定的代码</p>
</li>
<li>
<p><strong>#editor</strong>: 引用当前活动文件</p>
</li>
</ul>
<pre><code>"更新 #file:UserModel.js 以包含时间戳"
"在 #codebase 中搜索所有数据库查询"  
"将 #selection 重构为现代 JavaScript 语法"
"为所有 API 调用添加错误处理到 #editor"
</code></pre>
<p>这些引用帮助 Copilot 准确理解要查看和更改的位置，使多文件操作更加精确。</p>
<p><strong>拖放操作：</strong></p>
<p>拖放是提供给 Copilot 的最直观的上下文方式之一。您可以将文件从 VS Code 浏览器中直接拖入聊天窗口，Copilot 会立即了解其内容和结构。</p>
<p>当您处理相关组件并需要 AI 理解不同文件如何相互连接时，此功能特别有用。Copilot 会在整个对话中记住这些文件关系，因此在继续相同的讨论时无需再次上传文件。</p>
<p>这种上下文保留在多个聊天会话中有效，使您可以轻松在复杂的多文件项目中继续之前的工作。</p>
<h3 id="2">阶段 2 实践练习</h3>
<h4 id="1">练习 1：聊天模式实践</h4>
<ol>
<li>
<p>使用提问模式理解一个复杂的函数</p>
</li>
<li>
<p>切换到编辑模式进行重构</p>
</li>
<li>
<p>比较不同的方法</p>
</li>
</ol>
<h4 id="2">练习 2：代理模式项目</h4>
<ol>
<li>
<p>启动代理模式 (<code>Shift+Cmd+I</code>)</p>
</li>
<li>
<p>请求："创建一个带测试的简单待办应用"</p>
</li>
<li>
<p>观察自主开发过程</p>
</li>
<li>
<p>查看生成的代码</p>
</li>
</ol>
<h4 id="3">练习 3：高级功能</h4>
<ol>
<li>
<p>使用 @ 参与者进行项目问题</p>
</li>
<li>
<p>试验斜线命令</p>
</li>
<li>
<p>练习多文件操作</p>
</li>
</ol>
<h3 id="cli">准备好使用 CLI 工具了吗？</h3>
<p>您现在已经学习了在 VS Code 中使用 GitHub Copilot 的基础知识！像 Claude Code 和 Gemini 这样的 CLI 工具为基于终端的开发提供了更强大的功能。</p>
<p>如果你对终端 AI 感兴趣，可以继续学习下方的第三阶段。如果你更愿意继续使用 VS Code，请跳到第四阶段以获取高级工作流程。</p>
<h2 id="stage-3">阶段 3：基于 CLI 的 AI 代理（Claude Code &amp; Gemini）</h2>
<h3 id="11claudecodeai">步骤 11：认识 Claude Code——您的终端 AI 助手</h3>
<h4 id="claudecode">什么是 Claude Code？</h4>
<p>还记得 GitHub Copilot 如何在 VS Code 中帮助你吗？Claude Code 在终端中为你提供相同的帮助。</p>
<p>不用在 VS Code 中键入和获取建议，你可以在终端中键入并与 AI 对话。这就像在命令行上有一个编程伙伴。</p>
<h4 id="">简单例子：</h4>
<p>在 VS Code 中使用 Copilot：</p>
<pre><code>// 创建一个函数来验证电子邮件
[AI 提供建议代码]
</code></pre>
<p>在终端中使用 Claude Code：</p>
<pre><code>claude
&gt; 创造一个函数来验证电子邮件地址
[AI 为你编写代码]
</code></pre>
<p>那么，什么时候应该使用 VS Code/Copilot，什么时候应该使用 Claude Code 呢？</p>
<p><strong>如果你：</strong></p>
<ul>
<li>
<p>喜欢在终端工作</p>
</li>
<li>
<p>想要与 AI 进行关于代码的对话</p>
</li>
<li>
<p>需要有关命令行任务的帮助</p>
</li>
<li>
<p>想要更多控制 AI 互动</p>
</li>
</ul>
<p>Claude Code 非常适合你。</p>
<ul>
<li>
<p>偏好可视化编辑器</p>
</li>
<li>
<p>对当前的工作流程感到满意</p>
</li>
<li>
<p>不花太多时间在终端</p>
</li>
</ul>
<h4 id="">定价</h4>
<p>Claude Code 需要 Claude Pro（每月 20 美元）或 ClaudeMax（每月 100 美元）订阅，或使用 API 积分按使用量付费。</p>
<h4 id="claudecode">Claude Code 限制</h4>
<p>如果您计划使用 Claude Code，请考虑以下重要事项：</p>
<ul>
<li>
<p><strong>仅付费</strong> – 没有免费版本，需要 Claude Pro 订阅或 API 积分</p>
</li>
<li>
<p><strong>基于终端</strong> – 不如 IDE 集成工具可视化</p>
</li>
<li>
<p><strong>学习曲线</strong> – 需要熟悉命令行界面</p>
</li>
<li>
<p><strong>上下文管理</strong> – 需要手动管理对话上下文</p>
</li>
<li>
<p><strong>依赖互联网</strong> – 所有操作都需要稳定的连接</p>
</li>
<li>
<p><strong>会话限制</strong> – 长时间的自主会话会消耗大量 API 积分</p>
</li>
</ul>
<h4 id="">安装</h4>
<p>推荐（所有平台）：</p>
<pre><code>npm install -g @anthropic-ai/claude-code
</code></pre>
<p>其他安装方式：</p>
<ul>
<li>
<p><strong>macOS/Linux</strong>: <code>curl -fsSL https://claude.ai/install.sh | bash</code></p>
</li>
<li>
<p><strong>Windows</strong>: <code>irm https://claude.ai/install.ps1 | iex</code></p>
</li>
</ul>
<h4 id="">基本用法</h4>
<p><strong>交互模式（推荐）：</strong></p>
<p>交互模式是 Claude Code 的主要界面，您可以与 AI 实时对话。不同于一次性命令，交互模式创建了一个持久的会话，您可以提出后续问题、迭代解决方案，并随着时间的推移构建复杂的项目。</p>
<p>推荐使用交互模式，因为：</p>
<ul>
<li>
<p><strong>上下文持久性</strong>：Claude 会记住整个对话和项目上下文</p>
</li>
<li>
<p><strong>迭代开发</strong>：您可以优化请求，并基于先前的回应进行改进</p>
</li>
<li>
<p><strong>实时协作</strong>：在工作时提问、获取解释并修改方法</p>
</li>
<li>
<p><strong>会话恢复</strong>：使用 <code>claude --resume</code> 继续之前的对话</p>
</li>
</ul>
<p><strong>其他可用模式：</strong></p>
<ul>
<li>
<p><strong>一次性模式</strong>：单个命令执行（下文说明）</p>
</li>
<li>
<p><strong>代理模式</strong>：可独立工作数小时的自主开发会话</p>
</li>
</ul>
<ol>
<li>导航到您的项目：</li>
</ol>
<pre><code>cd your-project
claude
</code></pre>
<ol start="2">
<li>自然地开始对话：</li>
</ol>
<pre><code>Claude Code &gt; 分析这个代码库并提出改进建议

Claude Code &gt; 现在帮我重构用户认证

Claude Code &gt; 为支付模块添加单元测试
</code></pre>
<ol start="3">
<li>继续之前的会话：</li>
</ol>
<pre><code>claude --resume
</code></pre>
<p><strong>一次性命令（用于快速任务）：</strong></p>
<p>一次性命令是执行特定任务后即退出的单次执行命令。与交互模式不同，这些命令不保留对话上下文 —— 非常适合快速、独立的任务。</p>
<p><strong>什么是一次性命令？</strong></p>
<p>这些是在您的终端中直接运行带有特定指令的命令，而不进入交互会话。Claude 将执行请求并立即提供结果。</p>
<p><strong>何时使用一次性命令：</strong></p>
<ul>
<li>
<p>快速分析或代码审查</p>
</li>
<li>
<p>简单文件修改</p>
</li>
<li>
<p>自动化脚本和 CI/CD 集成</p>
</li>
<li>
<p>需要单一特定答案时</p>
</li>
</ul>
<p><strong>示例：</strong></p>
<pre><code>claude "分析这个代码库并提出改进建议"
claude "修复 src/ 中的所有 TypeScript 错误"
claude "为 utils.js 生成单元测试"
claude "解释这个函数的作用" --file src/auth.js
</code></pre>
<p>关键区别在于一次性命令在运行之间不记住上下文，而交互模式保持完整的对话历史记录和项目理解。</p>
<p><strong>交互式 vs 自主会话：</strong></p>
<p>在交互模式内，您可以选择协作和自主的方法：</p>
<p><strong>交互式会话（协作）：</strong></p>
<pre><code>Claude Code &gt; 我正在构建用户认证。我们应该采用什么方法？

你：使用 JWT 令牌和刷新令牌轮换

Claude Code &gt; 实现带刷新令牌的 JWT 认证
[逐步向您展示实现方案]

Claude Code &gt; 我还需要添加密码重置功能吗？

你：是的，使用基于邮件的重置
</code></pre>
<p><strong>自主会话（免动手开发）：</strong></p>
<pre><code>Claude Code &gt; 构建一个完整的用户管理系统，包括认证、个人资料、偏好设置和管理功能。使用安全和测试的最佳实践。

[Claude 自主工作数小时，定期提供更新]
[最终结果：完整的用户管理系统，准备投入生产]
</code></pre>
<p><strong>何时使用每种模式：</strong> 在学习时或希望对决策进行控制时使用交互式会话。对于定义明确的任务，在您信任 Claude 能够独立做出良好选择时使用自主会话。</p>
<h4 id="">关键特性</h4>
<p><strong>思维模式（在交互式会话中使用）：</strong></p>
<p>思维模式是特定的命令，可以告诉 Claude 在响应前要进行多深的分析。您可以根据问题的复杂程度手动选择这些模式。</p>
<p><strong>何时使用每种模式：</strong></p>
<ul>
<li>
<p><code>think</code> – 对简单任务进行快速分析："think: review this function for bugs"</p>
</li>
<li>
<p><code>think hard</code> – 对复杂逻辑进行深入推理："think hard: optimize this algorithm"</p>
</li>
<li>
<p><code>think harder</code> – 复杂问题解决，考虑多重因素："think harder: design a scalable database schema"</p>
</li>
<li>
<p><code>ultrathink</code> – 对架构决策进行最大深度分析："ultrathink: evaluate microservices vs monolith for this project"</p>
</li>
</ul>
<p>Claude展示其通过较长的思考模式进行推理的过程。您将在得出最终答案之前看到逐步分析。更高的思考模式花费更多时间，但会提供更全面的解决方案。</p>
<p><strong>选择正确的模式：</strong></p>
<p>使用<code>think</code>进行快速代码审查，使用<code>think hard</code>调试复杂问题，使用<code>think harder</code>处理系统设计问题，使用<code>ultrathink</code>进行影响整个项目的重大架构决策。</p>
<h4 id="claudemd">使用Claude.md进行项目级定制</h4>
<p>Claude Code最强大的功能之一是使用<code>.claude/CLAUDE.md</code>文件进行项目级定制。这让你可以为Claude提供有关特定项目、编码标准和偏好的上下文。</p>
<p>像这样设置CLAUDE.md：</p>
<pre><code># 创建项目级配置
mkdir -p .claude
touch .claude/CLAUDE.md
</code></pre>
<p>以下是一个CLAUDE.md文件的示例：</p>
<pre><code># 项目上下文

这是一个使用Express和PostgreSQL的Node.js REST API。

## 编码标准

- 使用async/await，绝不使用回调
- 所有数据库查询使用Prisma ORM
- 为所有新功能使用Jest编写测试
- 遵循RESTful约定

## 项目结构

- `/src/routes` - API端点
- `/src/models` - 数据库模型
- `/src/middleware` - Express中间件
- `/tests` - 单元和集成测试

## 偏好

- 为所有新文件使用TypeScript
- 更喜欢函数式编程模式
- 为所有函数包含JSDoc注释
</code></pre>
<p>这有助于Claude自动理解您的项目结构。还帮助Claude遵循您的特定编码标准，为您的技术栈建议合适的模式，并在多次会话中记住您的偏好。</p>
<p><strong>专业提示：</strong> 随着项目的发展更新你的CLAUDE.md，以保持Claude的建议相关和准确。</p>
<h4 id="claudecode">Claude Code自主开发会话</h4>
<p>这是一个多小时的自主开发会话示例：</p>
<pre><code>cd your-project
claude
</code></pre>
<p><strong>你的提示：</strong>“构建一个可扩展的具有实时订阅、身份验证、速率限制和全面测试的GraphQL API”</p>
<p>Claude的6小时自主过程：</p>
<pre><code>Claude Code &gt; ultrathink: 分析需求并设计架构

[创建详细的技术规格和开发计划]

Claude Code &gt; 实现核心GraphQL服务器与Apollo
[运行：npm install apollo-server-express graphql]
[创建：server.js, schema/typeDefs.js, resolvers/index.js]

Claude Code &gt; 添加身份验证和授权中间件
[创建：middleware/auth.js, utils/jwt.js, models/User.js]
[实现具有适当错误处理的JWT策略]

Claude Code &gt; 设置具有Redis的实时订阅
[运行：npm install graphql-redis-subscriptions]
[配置WebSocket连接，创建订阅解析器]

Claude Code &gt; 实施速率限制和安全措施
[添加helmet, express-rate-limit, 查询复杂度分析]

Claude Code &gt; 编写全面的测试套件
[创建单元测试、集成测试、负载测试]
[运行：npm test - 看到8个失败的测试]

Claude Code &gt; 自动修复测试失败
[识别异步时间问题，修复解析器逻辑]
[反复重新运行测试直到94/94个测试通过]

Claude Code &gt; 性能优化和缓存
[实现Redis缓存，优化数据库查询]
[添加性能监控和日志记录]

Claude Code &gt; 生成全面的文档
[创建带示例的API文档，README，部署指南]

Claude Code &gt; 最终验证和清理
[运行完整的测试套件，检查代码覆盖率：96%]
[创建生产构建和部署脚本]
</code></pre>
<p>这代表了6小时的自主工作（在此期间你可以处理其他项目）。结果是一个生产就绪的GraphQL API，具有身份验证、实时功能和全面的测试。</p>
<p>为什么这有效：</p>
<ul>
<li>
<p><strong>自主反馈循环：</strong> Claude运行测试，看到失败，自动修复</p>
</li>
<li>
<p><strong>上下文感知：</strong> 保持对整个项目结构的理解</p>
</li>
<li>
<p><strong>自我纠正：</strong> 反复迭代解决方案直至正确工作</p>
</li>
<li>
<p><strong>工具集成：</strong> 无缝使用git, npm, 测试框架</p>
</li>
</ul>
<p><strong>网络搜索集成：</strong></p>
<p>Claude Code可以搜索网络以获取当前信息，这对于AI训练数据有截断日期特别有用。该功能帮助您保持与最新文档、最佳实践和解决方案的同步。</p>
<pre><code>Claude Code &gt; 搜索最新的React 19功能并更新我的组件

[Claude搜索网络，然后以发现继续对话]

Claude Code &gt; 现在将这些新功能应用于UserProfile组件
</code></pre>
<p><strong>网络搜索何时有帮助：</strong></p>
<ul>
<li>
<p>获取新库版本的当前文档</p>
</li>
<li>
<p>找到最近错误消息或漏洞的解决方案</p>
</li>
<li>
<p>研究最新的最佳实践和模式</p>
</li>
<li>
<p>比较当前问题的解决方案</p>
</li>
</ul>
<p>当Claude检测到需要当前信息时，网络搜索会自动发生，或者你可以通过在提示中提到“搜索”或“最新”来显式请求。</p>
<h4 id="claudecode">Claude Code键盘快捷键</h4>
<p>你可以使用这些键盘快捷键提高工作效率：</p>
<ul>
<li>
<p><code>Ctrl+C</code> – 取消当前输入或生成</p>
</li>
<li>
<p><code>Ctrl+D</code> – 退出 Claude Code 会话</p>
</li>
<li>
<p><code>Ctrl+L</code> – 清除终端屏幕</p>
</li>
<li>
<p><code>Up/Down arrows</code> – 导航命令历史</p>
</li>
<li>
<p><code>Esc</code> + <code>Esc</code> – 编辑上一条消息</p>
</li>
</ul>
<p><strong>多行输入：</strong></p>
<ul>
<li>
<p><code>\</code> + <code>Enter</code> – 快速转义以创建新行（适用于所有终端）</p>
</li>
<li>
<p><code>Option+Enter</code> (Mac) / <code>Shift+Enter</code> (已配置) – 插入新行</p>
</li>
</ul>
<h3 id="12googlegemini">步骤12：Google Gemini 命令行界面</h3>
<h4 id="geminiclaudecode">何时使用 Gemini 与 Claude Code：</h4>
<p>Gemini 是另一种基于命令行界面的 AI 工具，它与 Claude Code 相辅相成，而不是竞争。尽管 Claude Code 擅长深度推理和复杂的开发任务，但 Gemini 提供了独特的优势：大规模上下文窗口（超过100万个标记）、慷慨的免费限制以及强大的多模态能力。</p>
<p><strong>使用 Gemini，当您：</strong></p>
<ul>
<li>
<p>需要一次分析整个大型代码库</p>
</li>
<li>
<p>希望处理图像、图表或草图</p>
</li>
<li>
<p>在预算限制内工作（慷慨的免费套餐）</p>
</li>
<li>
<p>需要极大的上下文窗口来处理复杂项目</p>
</li>
</ul>
<p><strong>使用 Claude Code，当您：</strong></p>
<ul>
<li>
<p>需要复杂的推理和问题解决</p>
</li>
<li>
<p>希望进行自主开发会话</p>
</li>
<li>
<p>偏爱用于复杂分析的高级思维模式</p>
</li>
<li>
<p>正在构建需要详细规划的生产系统</p>
</li>
</ul>
<p><strong>最佳方法：</strong> 许多开发人员策略性地使用这两种工具——Gemini用于分析和视觉输入，Claude Code用于复杂的开发任务。</p>
<p>Gemini 将 Google 的 AI 带到您的终端，并提供慷慨的免费限制。</p>
<h4 id="">安装</h4>
<p>使用 npx (推荐试用)：</p>
<pre><code>npx @google/gemini-cli
</code></pre>
<p>全局安装：</p>
<pre><code>npm install -g @google/gemini-cli
gemini  # 开始交互式会话
</code></pre>
<h4 id="">认证</h4>
<ol>
<li>使用 Google 登录：</li>
</ol>
<pre><code>gemini auth login
</code></pre>
<ol start="2">
<li>检查状态：</li>
</ol>
<pre><code>gemini auth status
</code></pre>
<p>免费限制：</p>
<ul>
<li>
<p>60 次请求/分钟</p>
</li>
<li>
<p>使用 Google 帐户每日 1,000 次请求</p>
</li>
</ul>
<p>内置工具：</p>
<ul>
<li>
<p><code>/memory</code> – 管理对话记忆</p>
</li>
<li>
<p><code>/stats</code> – 查看使用统计</p>
</li>
<li>
<p><code>/tools</code> – 列出可用工具</p>
</li>
<li>
<p><code>/mcp</code> – 配置模型上下文协议服务器</p>
</li>
</ul>
<h4 id="geminicli">Gemini CLI 的限制</h4>
<p>如果您计划使用 Gemini，请考虑以下重要事项：</p>
<ul>
<li>
<p><strong>速率限制</strong> – 免费套餐 60 次请求/分钟，1,000次/天</p>
</li>
<li>
<p><strong>Google 依赖性</strong> – 需要 Google 帐户和互联网连接</p>
</li>
<li>
<p><strong>较新的工具</strong> – 相较于 GitHub Copilot，社区较小，资源较少</p>
</li>
<li>
<p><strong>面向终端</strong> – 与流行的 IDE 整合较少</p>
</li>
<li>
<p><strong>多模态处理</strong> – 图像上传有大小限制 (20MB)</p>
</li>
<li>
<p><strong>测试版功能</strong> – 某些高级功能可能不稳定</p>
</li>
</ul>
<h4 id="gemini">独特的 Gemini 特性</h4>
<p><strong>庞大的上下文窗口：</strong><br>
Gemini 可以在单个会话中处理超过 100 万个标记，这意味着它可以同时分析整个大型代码库。这对于理解复杂系统架构和多个文件之间的关系特别有用。</p>
<p><strong>多模态能力：</strong><br>
Gemini 能够处理和理解各种类型的视觉内容以及代码，使其在设计到代码的工作流程和视觉调试中具有独特的优势。</p>
<h4 id="">将您的草图转化为代码</h4>
<p>这真的很酷：您可以在纸上绘制一些东西，然后 Gemini 将其转化为可工作的代码！</p>
<p>操作方法如下：</p>
<ol>
<li>
<p><strong>创建您的草图：</strong> 在纸上、白板或数位板上绘制您的想法</p>
</li>
<li>
<p><strong>拍照或截图：</strong> 使用手机拍摄或截图，将草图数字化</p>
</li>
<li>
<p><strong>保存图像：</strong> 保存为 JPG、PNG 或 WebP 格式（小于 20MB）</p>
</li>
<li>
<p><strong>通过命令行将其展示给 Gemini：</strong></p>
</li>
</ol>
<pre><code>gemini -p "将此草图转化为具有良好样式的 React 组件" sketch.jpg
</code></pre>
<p><strong>替代方法：</strong></p>
<pre><code># 如果您处于交互会话中，您可以引用文件：
gemini
&gt; 分析此UI草图并创建HTML/CSS：@sketch.jpg

# 或在支持的终端中拖放
gemini
&gt; 将此设计实现为 Vue 组件
[将 sketch.jpg 拖入终端]
</code></pre>
<p>然后，Gemini 会查看您的图纸并生成：</p>
<ul>
<li>
<p>与您的草图匹配的工作 React 组件</p>
</li>
<li>
<p>使其看起来不错的精美 CSS 样式</p>
</li>
<li>
<p>如果您绘制了表单则提供表单验证</p>
</li>
<li>
<p>使其工作的所有代码</p>
</li>
</ul>
<p>这就像拥有一个能读懂您心思的设计师和开发人员！</p>
<h4 id="gemini">通过展示图像来修复 Gemini 中的错误</h4>
<p>UI 出现了 Bug？您可以向 Gemini 展示视觉信息以帮助调试：</p>
<pre><code>gemini -p "该 UI 看起来有问题。有什么问题，我该如何解决？" image.png
</code></pre>
<p>Gemini 可以分析视觉信息并告诉您：</p>
<ul>
<li>
<p>问题的原因是什么</p>
</li>
<li>
<p>需要修改哪些代码</p>
</li>
<li>
<p>有时还有更好的解决方法</p>
</li>
</ul>
<h4 id="">将架构图转化为代码</h4>
<p>画出系统架构图，Gemini 可以构建它：</p>
<pre><code>gemini -p "使用 Docker 和数据库构建此系统架构" diagram.jpg
</code></pre>
<p>Gemini 将：</p>
<ul>
<li>
<p>理解您的图示</p>
</li>
<li>
<p>创建您所需的所有 Docker 文件</p>
</li>
<li>
<p>设置数据库和连接</p>
</li>
<li>
<p>根据您的设计提供一个可工作的系统</p>
</li>
</ul>
<p>将设计翻译成代码不再需要耗费数小时，您可以：</p>
<ol>
<li>
<p>向 Gemini 展示您的草图或设计</p>
</li>
<li>
<p>让 Gemini 构建它</p>
</li>
<li>
<p>在几分钟内获得工作代码，而不是几个小时，只需根据需要进行细化</p>
</li>
</ol>
<p>大多数情况下，Gemini 初次尝试就能很接近您的期望。即使它并不完美，它也为您提供了一个很好的起点，节省了大量时间。</p>
<h3 id="13cli">第13步：比较 CLI 工具</h3>
<p>这里有一个简易表格来帮助您比较 Claude Code 和 Gemini CLI 的功能：</p>
<table>
<thead>
<tr>
<th><strong>功能</strong></th>
<th><strong>Claude Code</strong></th>
<th><strong>Gemini CLI</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>上下文窗口</strong></td>
<td>大</td>
<td>1M+ 令牌</td>
</tr>
<tr>
<td><strong>网络搜索</strong></td>
<td>内置</td>
<td>谷歌搜索集成</td>
</tr>
<tr>
<td><strong>文件编辑</strong></td>
<td>直接编辑</td>
<td>基于差异</td>
</tr>
<tr>
<td><strong>思维模式</strong></td>
<td>4 个等级</td>
<td>ReAct 循环</td>
</tr>
<tr>
<td><strong>IDE 集成</strong></td>
<td>VS Code 快捷键</td>
<td>终端优先</td>
</tr>
<tr>
<td><strong>免费层</strong></td>
<td>有限</td>
<td>慷慨 (1000/天)</td>
</tr>
<tr>
<td><strong>开源</strong></td>
<td>否</td>
<td>是</td>
</tr>
<tr>
<td><strong>多模态</strong></td>
<td>否</td>
<td>是 (图像, PDF)</td>
</tr>
</tbody>
</table>
<h3 id="14cli">第14步：高级 CLI 工作流程</h3>
<h4 id="1claudecode">工作流程1：Claude Code 的互动代码审查</h4>
<pre><code>Claude Code &gt; review my recent git changes

[Claude 分析差异]

Claude Code &gt; fix the security issue you found in the login function

Claude Code &gt; now create a pull request with a good description
</code></pre>
<h4 id="2gemini">工作流程2：Gemini 的对话式架构分析</h4>
<pre><code>Gemini &gt; analyze this codebase architecture and identify technical debt

[Gemini 提供全面的分析]

Gemini &gt; create a migration plan for the database issues you found

Gemini &gt; generate API documentation for the endpoints
</code></pre>
<h4 id="3">工作流程3：互动测试驱动开发</h4>
<pre><code>Claude Code &gt; I need to add payment processing. Start by writing comprehensive tests

[Claude 创建测试套件]

Claude Code &gt; now implement the payment service to pass these tests

Claude Code &gt; add error handling and edge cases
</code></pre>
<h3 id="vscodecli">结合 VS Code 和 CLI 工具</h3>
<h4 id="">混合工作流程的力量：</h4>
<p>最具生产力的开发者通常不会只选择一个 AI 工具——他们策略性地将 VS Code 扩展与 CLI 工具结合起来，以最大化效率。每个工具都有独特的优势，结合它们会创造出一个大于其各部分总和的工作流程。</p>
<p><strong>结合工具的好处：</strong></p>
<ul>
<li>
<p><strong>无缝的上下文切换：</strong> 从 Copilot 开始快速开发，然后无缝移动到 Claude Code 进行复杂的分析，而不失去动力</p>
</li>
<li>
<p><strong>互补的优势：</strong> 利用每个工具的最佳功能，如 Copilot 的实时建议 + Claude 的深入推理 + Gemini 的视觉处理</p>
</li>
<li>
<p><strong>持续的工作流：</strong> 无需在工具之间复制/粘贴代码—在项目中直接工作，并根据需要获得不同的 AI 支持</p>
</li>
<li>
<p><strong>减少心理负担：</strong> 工具处理不同的认知任务，让您专注于创造性的问题解决</p>
</li>
</ul>
<h4 id="">如何实际组合工具：</h4>
<p>示例工作流程——构建用户仪表板：</p>
<ol>
<li>
<p><strong>在 VS Code 中使用 Copilot 开始：</strong> 使用制表符补全快速构建基本组件结构</p>
</li>
<li>
<p><strong>保持 VS Code 打开，启动 Claude Code：</strong> 获取架构建议和重构建议，同时保持编辑器上下文</p>
</li>
<li>
<p><strong>切换到 Gemini 以获取视觉元素：</strong> 上传 UI 模型以生成匹配的样式</p>
</li>
<li>
<p><strong>返回到 VS Code：</strong> 应用所有建议，同时有 Copilot 协助实现细节</p>
</li>
</ol>
<p><strong>关键集成点：</strong></p>
<ul>
<li>
<p><strong>共享项目上下文：</strong> 所有工具在同一个目录中工作，了解您的项目结构</p>
</li>
<li>
<p><strong>文件系统协调：</strong> CLI 工具进行的更改会立即在 VS Code 中可见</p>
</li>
<li>
<p><strong>版本控制集成：</strong> 使用 CLI 工具进行 git 操作，而 VS Code 显示视觉差异</p>
</li>
</ul>
<h3 id="">快速切换设置</h3>
<h4 id="">什么是快速切换？</h4>
<p>快速切换设置指的是配置你的开发环境，让你可以快速在不同的 AI 工具之间切换，而不产生摩擦。通过创建快捷方式，而不是输入长命令或通过多个设置步骤进行导航，您可以立即访问当前任务所需的 AI 工具。</p>
<p>在您的 shell 配置文件中添加（<code>.zshrc</code> 或 <code>.bashrc</code>）：</p>
<pre><code># 快速 AI 命令用于交互模式
alias cc="claude"
alias gc="gemini"

# 在需要时进行快速单次命令
alias think="claude 'think hard:'"
alias analyze="gemini -p 'analyze:'"
</code></pre>
<h3 id="3">阶段3实践练习</h3>
<h4 id="1claudecode">练习1：互动 Claude Code 项目设置</h4>
<ol>
<li>
<p>创建一个新项目目录</p>
</li>
<li>
<p>启动：<code>claude</code></p>
</li>
<li>
<p>开始对话："set up a Node.js Express API with PostgreSQL"</p>
</li>
<li>
<p>继续聊天："add authentication middleware"</p>
</li>
<li>
<p>继续："now add comprehensive error handling"</p>
</li>
<li>
<p>查看生成的代码并提问</p>
</li>
</ol>
<h4 id="2gemini">练习2：互动 Gemini 代码分析</h4>
<ol>
<li>
<p>导航到现有项目</p>
</li>
<li>
<p>启动：<code>gemini</code></p>
</li>
<li>
<p>从以下内容开始："analyze this codebase and identify potential security vulnerabilities"</p>
</li>
<li>
<p>后续："explain the most critical issue in detail"</p>
</li>
<li>
<p>继续："create a fix for the authentication vulnerability"</p>
</li>
<li>
<p>询问："what other improvements should I prioritize?"</p>
</li>
<li>
<p>在 VS Code 中使用 Copilot 开始初始开发</p>
</li>
<li>
<p>切换到交互式 Claude Code 会话进行复杂的代码重构</p>
</li>
<li>
<p>使用交互式 Gemini 会话进行代码库分析和文档撰写</p>
</li>
<li>
<p>熟练地在工具之间无缝切换</p>
</li>
</ol>
<p>需要 CLI 工具的帮助吗？请参阅<a href="http://localhost:3333/#troubleshooting-quick-reference">故障排除快速参考</a>以了解设置和常见问题。</p>
<h2 id="stage-4">阶段 4：掌握 - 工具与高级工作流程的结合</h2>
<h3 id="15">第 15 步：工具选择策略</h3>
<h4 id="">何时使用每种工具</h4>
<p>那么，在你的工作流程中，什么时候应该使用每个工具呢？</p>
<p>当速度至关重要时，你可以将 GitHub Copilot 作为一名在线的结对程序员使用。它可以帮助你快速编写新函数、在输入时获得实时建议，并即时掌握不熟悉的 API 或框架。它也方便于在不中断你的工作流程的情况下快速查找文档。</p>
<p>然后，你可以转而使用 Claude Code 来处理更大、更复杂的任务：复杂的多文件重构、起草全面的测试以及“大声思考”关于架构和权衡的问题。在这里，它还可以帮助完成 Git 任务，比如指导你进行操作和组装拉取请求。</p>
<p>最后，当需要端到端分析大型代码库或将视觉输入（如截图/图表）整合到工作流程中时，可以从终端调用 Gemini CLI。由于可以免费使用，所以适合大量运行的场景，并且它适用于需要可定制、脚本友好的设置的场合。</p>
<h3 id="16mcpai">第 16 步：理解 MCP —— 让 AI 工具协同工作</h3>
<h4 id="mcp">什么是 MCP？</h4>
<p>MCP（模型上下文协议）是一种让你的 AI 工具增强能力的简单方法。可以把它想象成给你的手机增添应用程序——每个 MCP 服务器都会为你的 AI 增加新的功能。</p>
<h4 id="mcp">为什么初学者需要关注 MCP？</h4>
<p>没有 MCP 会遇到的问题是：你的 AI 只能处理它所知道的和你告诉它的内容。它无法：</p>
<ul>
<li>
<p>搜索网络上的当前信息</p>
</li>
<li>
<p>自动测试你的网站</p>
</li>
<li>
<p>在会话之间记住你的项目细节</p>
</li>
<li>
<p>连接到你的数据库或 API</p>
</li>
</ul>
<p>但有了 MCP 服务器，你的 AI 就能突然：</p>
<ul>
<li>
<p><strong>获取当前信息</strong> —— 从 Google 搜索最新的文档和解决方案</p>
</li>
<li>
<p><strong>测试你的代码</strong> —— 自动检查你的网站是否正常工作</p>
</li>
<li>
<p><strong>记住你的项目</strong> —— 跟踪你的架构和决策</p>
</li>
<li>
<p><strong>连接到工具</strong> —— 使用 GitHub、数据库等</p>
</li>
</ul>
<p>所以，相比于手动重复劳动，你的 AI 可以自动处理这些任务。这意味着你将花更少的时间去谷歌搜索错误消息、手动测试代码，以及在每个会话中向 AI 解释你的项目，而是花更多时间实际构建东西。</p>
<h4 id="mcp">初学者简单的 MCP 示例</h4>
<p>以下是 MCP 能为你做的一些初学者友好的示例：</p>
<p><strong>示例 1：无需谷歌搜索的帮助</strong></p>
<pre><code>你：“这个 CSS 不工作。找出原因并修复它”

没有 MCP：你需要谷歌搜索错误，阅读文档，尝试解决方案
有了 MCP：AI 搜索当前的 CSS 文档，找出问题，并自动修复
</code></pre>
<p><strong>示例 2：自动测试你的网站</strong></p>
<pre><code>你：“检查我的联系表单是否正常工作”

没有 MCP：你需要手动填写表单，检查邮件，测试边缘案例
有了 MCP：AI 填写表单，确认邮件已发送，测试不同的输入
</code></pre>
<p><strong>示例 3：AI 记住你的项目</strong></p>
<pre><code>你：“为我的待办事项应用程序添加一项新功能”

没有 MCP：你需要解释数据库结构、API 路线、前端框架
有了 MCP：AI 已记住一切并直接构建功能
</code></pre>
<h4 id="mcp">准备尝试 MCP 吗？</h4>
<p>如果这看起来很复杂，不用担心！你可以从一个简单的 MCP 服务开始，在适应后再添加更多。</p>
<h4 id="mcp">初学者简单的 MCP 设置</h4>
<p>我们将从 VS Code 开始（因为这是最简单的选项）：</p>
<ol>
<li>
<p>打开 VS Code</p>
</li>
<li>
<p>转到扩展（Ctrl+Shift+X）</p>
</li>
<li>
<p>搜索“GitHub Copilot MCP”或类似的 MCP 扩展</p>
</li>
<li>
<p>点击“安装”</p>
</li>
</ol>
<p>这样就完成了！扩展会自动处理一切。</p>
<p>通过这样做，你可以为你的 AI 获取网络搜索功能、基本项目记忆力和简单的自动化功能。</p>
<p>要测试它，试着让你的 AI：“搜索最新的 React 最佳实践并给我一个示例”。 如果它能搜索并返回当前的信息，MCP 就正常工作了！</p>
<h4 id="mcp">想要更多的 MCP 功能吗？</h4>
<p>一旦你适应了基本的 MCP，可以探索下面更高级的设置：</p>
<ul>
<li>
<p>自定义 MCP 服务器安装</p>
</li>
<li>
<p>高级配置选项</p>
</li>
<li>
<p>构建自己的 MCP 集成</p>
</li>
</ul>
<p>目前，上述的 VS Code 扩展方法将为你提供充足的 AI 超能力以便入门！</p>
<p>**这就是 MCP 的精要！**从上面的简单 VS Code 扩展方法开始，你将迅速看到你的 AI 变得多么强大。</p>
<h4 id="">下一步</h4>
<ul>
<li>
<p>尝试基础的 VS Code MCP 扩展</p>
</li>
<li>
<p>使用简单的请求进行测试，比如“搜索 X 并实现它”</p>
</li>
<li>
<p>熟悉后，探索更多的第 4 阶段 MCP 服务器</p>
</li>
</ul>
<p>MCP 将你的 AI 从代码建议工具转变为真正的开发伙伴。最好的部分是？一旦你用一个工具设置好，它就会与所有工具协同工作！</p>
<p>如果 AI 提示无法搜索网络，可以尝试以下几种方法。</p>
<p>首先，检查 MCP 扩展是否确实安装在 VS Code 中。然后尝试重启 VS Code。最后，要确保你的提问方式是 AI 能理解的，比如：“搜索 X 并展示 Y”。</p>
<p>如果 VS Code 扩展无法安装，尝试检查你的网络连接或将 VS Code 更新到最新版本。你也可以尝试使用不同名称搜索“MCP”或“Model Context Protocol”扩展。</p>
<p>如果仍然遇到问题，我们将在下文中介绍高级故障排除方法。或者你也可以询问你的 AI：“帮助我排查 MCP 设置故障”。</p>
<h3 id="mcp">高级 MCP 设置与集成</h3>
<h4 id="mcp">手动 MCP 服务器安装</h4>
<p>对于希望完全控制其 MCP 设置的高级用户：</p>
<p><strong>步骤 1：安装 MCP 服务器</strong></p>
<p>大多数 MCP 服务器可以通过 npm 安装：</p>
<pre><code># 用于网络自动化和测试
npm install -g @modelcontextprotocol/server-puppeteer

# 用于无需 API 密钥的网络搜索
npm install -g @mcp-servers/duckduckgo

# 用于数据库访问
npm install -g @modelcontextprotocol/server-postgres
</code></pre>
<p>某些服务器（如 GitHub）则使用 Docker：</p>
<pre><code>docker pull ghcr.io/github/github-mcp-server
</code></pre>
<p><strong>步骤 2：配置你的工具</strong></p>
<p><strong>理解层次化配置：</strong></p>
<p>每个 AI 工具在多个位置检查 MCP 配置，更具体的设置优先级高于一般设置。这意味着你可以有全局默认值，但可以针对特定项目进行覆盖。这就像 CSS——更具体的规则覆盖一般规则。</p>
<p><strong>Claude Code 具有最灵活的设置：</strong></p>
<p>Claude Code 配置层次结构（按顺序检查）：</p>
<ol>
<li>
<p><strong>项目级别</strong>：<code>.claude/mcp.json</code>（最高优先级）</p>
</li>
<li>
<p><strong>本地设置</strong>：<code>.claude/settings.local.json</code></p>
</li>
<li>
<p><strong>全局配置</strong>：<code>~/.claude/mcp.json</code>（后备）</p>
</li>
</ol>
<p>其他工具：</p>
<ul>
<li>
<p><strong>VS Code</strong>：<code>.vscode/mcp.json</code>（仅项目级别）</p>
</li>
<li>
<p><strong>Cursor</strong>：<code>.cursor/mcp.json</code>（仅项目级别）</p>
</li>
<li>
<p><strong>Windsurf</strong>：使用 VS Code 的配置格式</p>
</li>
</ul>
<p>以下是一个示例配置（适用于任何工具，只需调整文件位置）：</p>
<pre><code>{
  "mcpServers": {
    "puppeteer": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-puppeteer"]
    },
    "duckduckgo": {
      "command": "npx",
      "args": ["@mcp-servers/duckduckgo"]
    },
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
</code></pre>
<h4 id="mcp">生产环境 MCP 服务器</h4>
<p><strong>1. 颠覆性认知工具：</strong></p>
<p><strong>序列思维服务器：</strong><br>
该服务器通过将复杂问题分解为逻辑步骤来改变 AI 处理复杂问题的方式。当你要求实现一个大型功能时，AI 不会直接跳到代码实施，而是首先创建一个详细的计划，其中包含阶段、依赖关系和决策点。</p>
<p>这对于重构遗留系统或构建新功能来说是无价的，尤其是在操作顺序很重要的情况下。服务器在整个开发会话期间保持这种规划上下文，确保决策的一致性。</p>
<p><strong>记忆库服务器：</strong><br>
消除了每次会话都需要重新解释项目结构的烦恼。该服务器会创建有关你的架构选择、编码标准、团队偏好和项目目标的持久性记忆。当你几天后返回工作时，AI 可以立即知道你的数据库架构、API 模式，甚至为什么做出某些决策。它就像一个随着你的开发工作完美同步的项目文档系统。</p>
<p><strong>知识图谱服务器：</strong><br>
创建代码库关系的动态地图——不仅仅是文件依赖，还有功能、共享工具和架构模式之间的概念连接。当你修改一个组件时，AI 可以立即识别出可能需要更新的所有相关区域。这可以防止由于遗漏相关更改而导致的错误，并有助于重构期间的影响分析。</p>
<p><strong>2. 网页自动化与测试服务器：</strong></p>
<p><strong>Puppeteer Server：</strong><br>
提供无头浏览器的控制，用于综合测试工作流。AI 可以自动浏览你的 Web 应用程序，填写表单、点击按钮，并验证预期行为。</p>
<p>这在回归测试中特别强大——AI 可以重播用户工作流并在部署前捕获更改。此外，它还启用基于截图的测试和性能监控自动化。</p>
<p><strong>Playwright Server：</strong><br>
同时扩展 Chrome、Firefox 和 Safari 浏览器的自动化功能。该服务器对于跨浏览器兼容性测试至关重要，并允许 AI 在开发初期捕获特定浏览器的问题。</p>
<p>与手动测试不同，AI 可以在所有浏览器上并行运行相同的测试场景，生成关于功能和性能差异的比较报告。</p>
<p><strong>3. 开发集成服务器：</strong></p>
<p><strong>GitHub Server：</strong><br>
将终端转变为具有 AI 智能的完整 GitHub 界面。AI 可以自动创建分支、管理拉取请求、分析代码审查评论，甚至根据代码更改生成 PR 描述。它还可以根据内容分析分配标签，并通过理解问题与实际代码更改之间的关系来维护项目板。</p>
<p><strong>PostgreSQL 服务器：</strong><br>
支持直接的数据库分析和优化。人工智能可以检查查询性能、建议索引优化、分析数据模式，甚至生成迁移脚本。此服务器在调试生产问题时特别有价值，因为人工智能需要理解实际的数据分布和查询执行模式，而不仅仅是理论上的数据库设计。</p>
<p><strong>4. 辅助工具：</strong></p>
<p><strong>MCP Compass</strong><br>
帮助您为任何任务找到合适的 MCP 服务器。</p>
<p>这些服务器将您的人工智能从代码建议者转变为能够测试、搜索、记忆和自动化的真正开发合作伙伴！</p>
<h3 id="17">步骤 17：高级提示工程</h3>
<h4 id="">上下文提示</h4>
<p>提供示例：</p>
<pre><code>// 替代用语："创建一个验证函数"
// 使用："创建一个类似这样的验证函数但用于电子邮件：
// function validatePhone(phone) { return /^\d{10}$/.test(phone); }"
</code></pre>
<p>指定约束条件：</p>
<pre><code>claude "将此代码重构为使用函数式编程，不使用循环，使用 map/filter/reduce"
</code></pre>
<p>包括边缘案例：</p>
<pre><code>gemini -p "实现用户认证，处理以下情况：过期令牌、并发登录、速率限制"
</code></pre>
<h3 id="18ai">步骤 18：构建 AI 辅助开发流水线</h3>
<h4 id="">自动化代码审查流水线</h4>
<ol>
<li>使用 Copilot 进行预提交检查：</li>
</ol>
<pre><code>// .copilot-instructions
"审核所有更改以发现：安全问题、性能问题、代码风格"
</code></pre>
<ol start="2">
<li>使用 Claude 进行 PR 审查：</li>
</ol>
<pre><code>claude "审查此 PR：git diff main..feature-branch"
</code></pre>
<ol start="3">
<li>使用 Gemini 完成文档：</li>
</ol>
<pre><code>gemini -p "为这些更改生成更新日志并更新 README"
</code></pre>
<h4 id="ai">测试驱动的 AI 开发</h4>
<ol>
<li>编写测试规范：</li>
</ol>
<pre><code>claude "为支付处理系统编写全面的测试规范"
</code></pre>
<ol start="2">
<li>生成测试代码：</li>
</ol>
<pre><code>gemini -p "使用 Jest 实现这些测试规范"
</code></pre>
<ol start="3">
<li>
<p>使用 Copilot 实现：</p>
<ul>
<li>
<p>使用代理模式实现功能</p>
</li>
<li>
<p>测试指导实现过程</p>
</li>
</ul>
</li>
</ol>
<h3 id="19ai">步骤 19：创建您的个人 AI 工作流</h3>
<h4 id="">设置您的环境</h4>
<p>1. VS Code 设置（<code>settings.json</code>）：</p>
<pre><code>{
  "github.copilot.enable": {
    "*": true
  },
  "github.copilot.advanced": {
    "inlineCompletions.enable": true,
    "chat.enabled": true
  }
}
</code></pre>
<p>2. Claude 代码配置（<code>~/.claude/settings.json</code>）：</p>
<pre><code>{
  "cleanupPeriodDays": 7,
  "permissions": {
    "allow": [
      "Bash(fd:*)",
      "Bash(rg:*)",
      "Bash(ls:*)",
      "WebFetch(domain:github.com)",
      "WebFetch(domain:stackoverflow.com)"
    ],
    "deny": ["WebFetch(domain:medium.com)"]
  }
}
</code></pre>
<p>3. Gemini 设置（<code>~/.gemini/config.json</code>）：</p>
<pre><code>{
  "defaultModel": "gemini-2.5-pro",
  "contextWindow": "large",
  "safetyMode": "interactive"
}
</code></pre>
<h4 id="">自定义命令和别名</h4>
<p>为常见任务设置 Shell 别名：</p>
<pre><code># 启动互动会话
alias cc='claude'
alias gc='gemini'

# 快速一次性命令（在需要时）
alias aicommit='claude "创建一个带有描述性信息的 git 提交"'
alias aireview='claude "审查我的未提交更改"'
alias complexity='gemini -p "分析代码复杂性并建议简化"'
alias security='claude "更深入思考：检查安全漏洞"'
alias aidocs='gemini -p "生成全面的文档"'
</code></pre>
<h3 id="ai">最终项目：构建一个完整的应用程序与 AI</h3>
<h4 id="">项目要求</h4>
<p>构建一个任务管理 API，具备：</p>
<ul>
<li>
<p>用户认证</p>
</li>
<li>
<p>CRUD 操作</p>
</li>
<li>
<p>实时更新</p>
</li>
<li>
<p>测试套件</p>
</li>
<li>
<p>文档</p>
</li>
</ul>
<h4 id="">建议工作流程</h4>
<p>阶段 1：互动规划</p>
<pre><code># 启动 Claude 代码会话
claude

Claude Code &gt; 超级思考：设计一个可扩展的任务管理 API 架构

[Claude 提供详细分析]

Claude Code &gt; 现在将其分解为实施阶段

# 切换到 Gemini 进行规格说明
gemini

Gemini &gt; 为此任务管理 API 创建详细的技术规格

Gemini &gt; 包括数据库架构和 API 端点规格
</code></pre>
<p>阶段 2：互动实现</p>
<ol>
<li>
<p>使用 Copilot 代理模式进行初始设置</p>
</li>
<li>
<p>使用内联 Copilot 实现功能</p>
</li>
<li>
<p>切换到互动 Claude 代码会话处理复杂逻辑：</p>
</li>
</ol>
<pre><code>Claude Code &gt; 实施我们计划的用户认证系统

Claude Code &gt; 现在添加任务 CRUD 操作

Claude Code &gt; 用 WebSockets 集成实时更新
</code></pre>
<p>阶段 3：互动测试与文档</p>
<pre><code># Claude 代码会话用于测试
claude

Claude Code &gt; 为所有 API 端点编写全面测试

Claude Code &gt; 为身份验证流程添加集成测试

Claude Code &gt; 为高负载场景创建性能测试

# Gemini 生成文档
gemini

Gemini &gt; 生成包含示例的全面 API 文档

Gemini &gt; 创建开发者入职指南
</code></pre>
<p>阶段 4：互动优化</p>
<pre><code># Claude 代码进行性能优化
claude

Claude Code &gt; 分析并优化我们的数据库查询

Claude Code &gt; 为经常访问的数据实现缓存

Claude Code &gt; 添加监控和日志

# Gemini 进行最终审查
gemini

Gemini &gt; 审查整个代码库以进行改进
</code></pre>
<h3 id="">衡量你的进展</h3>
<h4 id="1">阶段 1 里程碑</h4>
<ul>
<li>
<p>熟悉标签补全</p>
</li>
<li>
<p>可以编写有效的提示语</p>
</li>
<li>
<p>理解 AI 的局限性</p>
</li>
</ul>
<h4 id="2">阶段 2 里程碑</h4>
<ul>
<li>
<p>有效使用多个模型</p>
</li>
<li>
<p>掌握聊天模式和代理</p>
</li>
<li>
<p>使用高级聊天功能</p>
</li>
</ul>
<h4 id="3">阶段 3 里程碑</h4>
<ul>
<li>
<p>熟练使用 CLI 工具</p>
</li>
<li>
<p>可以将 VS Code 和终端工作流结合起来</p>
</li>
<li>
<p>了解工具的优势</p>
</li>
</ul>
<h4 id="4">阶段 4 里程碑</h4>
<ul>
<li>
<p>创建自定义 AI 工作流</p>
</li>
<li>
<p>使用 AI 构建完整应用程序</p>
</li>
<li>
<p>可以教别人 AI 辅助开发</p>
</li>
</ul>
<h3 id="4">阶段 4 练习</h3>
<h4 id="1">练习 1：掌握工具选择</h4>
<ol>
<li>
<p>选择一个中等复杂的编码任务（例如，“构建一个 URL 缩短服务 API”）</p>
</li>
<li>
<p>计划每个阶段使用的工具（设计、编码、测试、部署）</p>
</li>
<li>
<p>按照你选择的工作流程执行</p>
</li>
<li>
<p>记录哪些工作顺利，哪些需要改进</p>
</li>
</ol>
<h4 id="2">练习 2：创建自定义工作流</h4>
<ol>
<li>
<p>识别工作中的重复性开发任务</p>
</li>
<li>
<p>设计一个使用多个工具的 AI 辅助工作流</p>
</li>
<li>
<p>测试并改进该工作流</p>
</li>
<li>
<p>为团队同事创建相关文档</p>
</li>
</ol>
<h4 id="3">练习 3：完整项目构建</h4>
<ol>
<li>
<p>使用 AI 辅助构建一个小型但完整的应用程序</p>
</li>
<li>
<p>战略性地使用至少两种不同 AI 工具</p>
</li>
<li>
<p>包括测试、文档和部署</p>
</li>
<li>
<p>反思比起传统开发的效率提升</p>
</li>
</ol>
<h3 id="">继续你的旅程</h3>
<h4 id="">持续更新</h4>
<ul>
<li>
<p>关注工具的更新说明</p>
</li>
<li>
<p>加入 AI 编码社区</p>
</li>
<li>
<p>试验新功能</p>
</li>
</ul>
<h4 id="">探索高级主题</h4>
<ul>
<li>
<p>自定义 MCP 服务器开发</p>
</li>
<li>
<p>AI 模型微调</p>
</li>
<li>
<p>企业部署策略</p>
</li>
<li>
<p>团队协作模式</p>
</li>
</ul>
<h4 id="">持续学习资源</h4>
<ul>
<li>
<p>每个工具的官方文档</p>
</li>
<li>
<p>社区论坛和 Discord 服务器</p>
</li>
<li>
<p>开源 AI 编码项目</p>
</li>
<li>
<p>会议演讲和教程</p>
</li>
</ul>
<h2 id="common-ai-issues">常见 AI 问题</h2>
<p>即便使用最好的 AI 工具，你仍会遇到挑战。一旦你了解模式，这些问题是正常且可管理的。以下是开发者面临的最常见问题以及实际可行的解决方案。</p>
<h3 id="ai">“我的 AI 建议很糟！”</h3>
<p><strong>问题：</strong> AI 给出的建议无关或错误</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>写更清晰的注释</p>
</li>
<li>
<p>打开相关文件以提供上下文</p>
</li>
<li>
<p>从简单任务开始</p>
</li>
<li>
<p>确保你在正确的文件类型中</p>
</li>
</ul>
<p><strong>示例修正：</strong></p>
<pre><code>// 原先: "make function"
// 尝试: "create function to validate US phone number format (xxx) xxx-xxxx"
</code></pre>
<h3 id="ai">“AI 太慢”</h3>
<p><strong>问题：</strong> 等待建议时间过长</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>检查你的网络连接</p>
</li>
<li>
<p>关闭不必要的程序</p>
</li>
<li>
<p>尝试较轻量级的 AI 工具</p>
</li>
<li>
<p>要有耐心 —— 复杂建议需要时间</p>
</li>
</ul>
<h3 id="ai">“我害怕过于依赖 AI”</h3>
<p><strong>问题：</strong> 担心丧失编码能力</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>将 AI 作为学习工具，而非依赖的工具</p>
</li>
<li>
<p>在接受之前务必理解代码</p>
</li>
<li>
<p>定期练习不使用 AI 编码</p>
</li>
<li>
<p>专注于解决问题，而非语法</p>
</li>
</ul>
<h3 id="">“它建议使用过时代码”</h3>
<p><strong>问题：</strong> AI 建议的模式老旧或使用了废弃的方法</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>在注释中指定版本</p>
</li>
<li>
<p>保持工具的更新</p>
</li>
<li>
<p>学会识别过时的模式</p>
</li>
</ul>
<p><strong>示例：</strong></p>
<pre><code>// create React functional component using hooks (not class component)
</code></pre>
<h3 id="">故障排除快速参考</h3>
<h4 id="">常见问题（所有工具）</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>快捷修复</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>没有 AI 建议</td>
<td>检查网络连接，重启编辑器，验证登录</td>
</tr>
<tr>
<td>“需要付款”消息</td>
<td>检查免费层限制，验证账户状态</td>
</tr>
<tr>
<td>建议效果差</td>
<td>使用更清晰的注释，打开相关文件提供上下文</td>
</tr>
<tr>
<td>工具无法安装</td>
<td>更新编辑器，检查网络，尝试不同安装方法</td>
</tr>
</tbody>
</table>
<h4 id="githubcopilot">GitHub Copilot 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>VS Code 无建议</td>
<td>检查右下角“GitHub Copilot”状态</td>
</tr>
<tr>
<td>免费层过期</td>
<td>查看 <a href="https://docs.github.com/en/copilot/how-tos/manage-your-account/getting-free-access-to-copilot-pro-as-a-student-teacher-or-maintainer">学生/维护者免费访问</a></td>
</tr>
<tr>
<td>代理模式不起作用</td>
<td>尝试 <code>Shift+Cmd+I</code>（Mac）或 <code>Ctrl+Shift+I</code>（Windows/Linux）</td>
</tr>
<tr>
<td>聊天无响应</td>
<td>尝试重启 VS Code，检查网络连接</td>
</tr>
</tbody>
</table>
<h4 id="claudecode">Claude Code 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>“命令未找到”</td>
<td>重新安装：<code>npm uninstall -g @anthropic-ai/claude-code &amp;&amp; npm install -g @anthropic-ai/claude-code</code></td>
</tr>
<tr>
<td>认证失败</td>
<td>运行 <code>claude auth login</code>，检查剩余 API 信用额度</td>
</tr>
<tr>
<td>响应缓慢</td>
<td>网络检查：<code>ping api.anthropic.com</code>，尝试轻量模型：<code>--model claude-3-haiku</code></td>
</tr>
<tr>
<td>MCP 服务器不起作用</td>
<td>检查 <code>~/.claude/mcp.json</code> 语法，测试服务器：<code>npx @mcp/server-github --help</code></td>
</tr>
<tr>
<td>命令挂起/卡住</td>
<td>按 <code>Ctrl+C</code> 取消，重启终端，检查后台进程</td>
</tr>
</tbody>
</table>
<h4 id="geminicli">Gemini CLI 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>需要认证</td>
<td>运行 <code>gemini auth login</code>，检查 Google 账户权限</td>
</tr>
<tr>
<td>超过速率限制</td>
<td>检查使用情况：<code>gemini /stats</code>，等待 1 分钟或升级计划</td>
</tr>
<tr>
<td>无法安装</td>
<td>尝试 <code>npx @google/gemini-cli</code>，检查 Node.js 16+</td>
</tr>
<tr>
<td>图片上传失败</td>
<td>检查格式（JPG/PNG/WebP），大小小于 20MB，验证文件路径</td>
</tr>
<tr>
<td>上下文窗口错误</td>
<td>将大请求分割成小部分，清除历史记录</td>
</tr>
</tbody>
</table>
<p>如果一切无效，请按顺序尝试以下方法：</p>
<ol>
<li>
<p>重启你的编辑器/终端</p>
</li>
<li>
<p>检查互联网连接</p>
</li>
<li>
<p>确认你已登录到正确的账户</p>
</li>
<li>
<p>更新工具至最新版本</p>
</li>
<li>
<p>尝试不同的工具（如果一个失败了，通常其他的会有效）</p>
</li>
<li>
<p>询问 AI 自己：“帮助我排查 troubleshoottool_tool_setup”</p>
</li>
</ol>
<h2 id="whats-next-after-completing-all-stages">完成所有阶段后有哪些后续步骤？</h2>
<p>一旦你掌握了基础知识，这里有一些简单的后续步骤：</p>
<h3 id="">与团队合作</h3>
<h4 id="ai">团队 AI 工作流基础</h4>
<p><strong>共享提示库：</strong></p>
<p>构建团队提示库可以改变你整个团队使用 AI 的方式。首先创建一个共享的仓库，让开发者记录适用于你特定领域和代码库的有效提示。</p>
<p>例如，如果你正在构建电子商务软件，为常见任务创建标准化提示，如“生成符合我们 REST 约定的产品目录 API 端点”或“使用我们的标准模式创建支付处理错误处理”。</p>
<p>记录成功的代理模式工作流供团队成员重复使用。某位开发者可能会发现 Claude Code 在给定有关模式演化实践的特定上下文时，特别适合数据库迁移。通过共享这些工作流，你可以避免每个团队成员独立地发现有效的方法。</p>
<p><strong>工具标准化：</strong></p>
<p>当每个人都使用兼容的 AI 工具时，团队生产力会倍增。根据团队的需要确定主要工具——例如，所有开发者都使用 GitHub Copilot，以确保一致的内联支持，再加上 Claude Code 以处理需要深入推理的复杂架构任务。制定清晰的指南，规定何时使用自主的代理模式与协作会话，以防止冲突并确保代码质量。</p>
<p>配置共享的 MCP 服务器设置，以便所有团队成员都能访问相同的增强型 AI 功能。这可能包括内部 API 的团队专用服务器、共享数据库访问或了解你的部署管道的定制工具。当每个人都拥有相同的 AI 功能时，协作就会变得顺畅无比。</p>
<p><strong>AI 生成的代码审查：</strong></p>
<p>将你的代码审查过程转变为与 AI 生成的代码有效协作。为在拉取请求中标记 AI 生成的部分建立惯例——这有助于审查者将注意力集中在适当的地方。审查者可以专注于架构决策、业务逻辑正确性和需要人工判断的集成模式，而不是挑剔 AI 通常处理得很好的语法。</p>
<p>对 AI 生成的代码实施严格的测试，因为自动化测试比手动审查更可靠地捕捉 AI 错误。为测试 AI 输出创建团队标准，包括 AI 可能遗漏的边缘案例和集成场景。这样你就能在享受 AI 带来的速度的同时，通过系统的验证保持质量。</p>
<p><strong>在提交信息中记录 AI 工具决策。</strong></p>
<h4 id="">简单的团队设置</h4>
<p>从小处着手，逐步建立：</p>
<ul>
<li>
<p>首先让每个人使用相同的 AI 工具</p>
</li>
<li>
<p>创建一个适合你项目的提示共享文档</p>
</li>
<li>
<p>找出团队何时应该使用代理模式与常规协助模式</p>
</li>
<li>
<p>为你最重要的团队工具设置 MCP 服务器</p>
</li>
</ul>
<h3 id="">对于更大的项目</h3>
<p>随着项目的发展，你可能会想要：</p>
<ul>
<li>
<p>为不同的任务尝试不同的 AI 模型（简单代码用快速的，复杂问题用强大的）</p>
</li>
<li>
<p>为你经常执行的任务创建快捷方式</p>
</li>
<li>
<p>将 AI 工具与现有开发工作流连接</p>
</li>
</ul>
<h3 id="">持续学习</h3>
<p>AI 编码工具每个月都在进步！通过以下方式保持最新：</p>
<ul>
<li>
<p>关注工具的发行说明（他们会发送更新邮件）</p>
</li>
<li>
<p>加入 AI 编码的 Discord 社区</p>
</li>
<li>
<p>尝试新功能，因为它们不断出现</p>
</li>
</ul>
<h2 id="conclusion">结语</h2>
<p>恭喜你！你现在已经拥有了开启 AI 辅助编码之旅的所有必需品。记住，每个专家都曾是初学者，而有 AI 作为你的编码伙伴，你可以比以往更快地学习和成长。</p>
<p><strong>记住：</strong></p>
<ul>
<li>
<p>AI 不会取代你的创造力——它会放大创造力</p>
</li>
<li>
<p>每个建议都是一次学习的机会</p>
</li>
<li>
<p>错误是旅程的一部分</p>
</li>
<li>
<p>社区在这里帮助你</p>
</li>
</ul>
<p>你不仅仅是在学习用 AI 编码——你是在了解软件开发的未来。几个月后，你会想知道自己过去是如何在没有它的情况下编码的。今天拥抱 AI 辅助的开发者将成为明日的领袖。</p>
<p>Happy coding! 🚀</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 来自 freeCodeCamp 社区的圣诞礼物：学习 Python、SQL、西班牙语等课程 ]]>
                </title>
                <description>
                    <![CDATA[ 2025 年对全球 freeCodeCamp 社区来说是精彩纷呈的一年。我们非常激动地以一系列圣诞礼物为这一年画上圆满句号：  1. freeCodeCamp Python 认证  2. freeCodeCamp JavaScript 认证（第 10 版）  3. freeCodeCamp 响应式 Web 设计认证（第 10 版）  4. freeCodeCamp 关系型数据库 + SQL 认证  5. 我们的 A2 级开发者英语认证  6. 我们的 B1 级开发者英语认证  7. 我们的 A1 级西班牙语课程（测试版）  8. 我们的 A1 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/christmas-gifts-freecodecamp-community-2025/</link>
                <guid isPermaLink="false">694b60661acf9a04cd95df3e</guid>
                
                    <category>
                        <![CDATA[ 社区 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 24 Dec 2025 05:29:57 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/12/----_20251224114011_141_113.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/christmas-gifts-freecodecamp-community-2025/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Christmas gifts for you from the freeCodeCamp community: Learn Python, SQL, Spanish, and more</a>
      </p><p>2025 年对全球 freeCodeCamp 社区来说是精彩纷呈的一年。我们非常激动地以一系列圣诞礼物为这一年画上圆满句号：</p><ol><li>freeCodeCamp Python 认证</li><li>freeCodeCamp JavaScript 认证（第 10 版）</li><li>freeCodeCamp 响应式 Web 设计认证（第 10 版）</li><li>freeCodeCamp 关系型数据库 + SQL 认证</li><li>我们的 A2 级开发者英语认证</li><li>我们的 B1 级开发者英语认证</li><li>我们的 A1 级西班牙语课程（测试版）</li><li>我们的 A1 级中文课程（测试版）</li></ol><p>如此丰厚的礼物，我们开始拆封吧！</p><h2 id="-10-">编程认证与全栈开发课程第 10 版</h2><p>在过去的 11 年里，freeCodeCamp 社区对核心编程课程体系进行了多次构建与重构。</p><p>如今我们终于实现编程课程兼具全面性与互动性的愿景。</p><p>第 10 版课程包含 6 项认证，每项认证都包含十余个实践项目，帮助你扎实掌握核心技能。</p><!--kg-card-begin: markdown--><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766529240482/d07520b2-ba51-427d-a053-403381c4d185.webp" alt="我们刚刚发布的 Python 课程的截图" class="image--center mx-auto" width="1654" height="1446" loading="lazy"><!--kg-card-end: markdown--><p>在每项认证结束时，你将参加一次期末考试。如果你能通过考试，将获得一份免费的认证证书。你可以将此证书加入 LinkedIn 个人资料，或添加到你的简历、履历或个人作品集网站中。</p><p>截至目前，已有 4 项认证正式上线：</p><ul><li><a href="https://www.freecodecamp.org/news/freecodecamps-new-responsive-web-design-certification-is-now-live/">响应式 Web 设计认证发布</a></li><li><a href="https://www.freecodecamp.org/news/freecodecamps-new-javascript-certification-is-now-live/">JavaScript 认证发布</a></li><li><a href="https://www.freecodecamp.org/news/freecodecamps-new-python-certification-is-now-live/">Python 认证发布</a></li><li><a href="https://www.freecodecamp.org/news/freecodecamps-new-relational-databases-certification-is-now-live/">关系型数据库认证发布</a></li></ul><p>我们将在 2026 年发布前端库认证与后端开发认证。</p><p>在获得全部 6 项认证后，你可以构建一个最终的毕业项目，该项目将由经验丰富的开发者进行代码审查。随后你将参加综合期末考试，通过后即可获得我们最终的全栈开发工程师认证。</p><p>如果你现在就开始学习前四项认证，那么在你学完之前，后两项认证也会发布了。毕竟，每项认证都涵盖了数百小时的理论计算机科学知识和动手编程实践。</p><h2 id="-">语言课程</h2><p>你可能会问：freeCodeCamp 是从何时开始教授世界语言的？</p><p>实际上，我们从 2022 年就开始设计“开发者英语”课程体系。在过去的几年间，我们对其进行了大幅扩展。</p><p>该课程通过手绘动画角色进行互动教学。在学习过程中，你将进行大量的阅读、写作、听力练习，口语练习功能也将在 2026 年上线。</p><!--kg-card-begin: markdown--><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766527685142/e1cc58c5-a245-4777-b2cf-b97dd7790d27.webp" alt="语言学习的 6 个 CEFR 级别图表" class="image--center mx-auto" width="1024" height="768" loading="lazy"><!--kg-card-end: markdown--><p>这是一套以故事驱动的课程体系。你将化身为一位刚抵达加利福尼亚、即将加入科技初创企业的开发者，在新生活的日常互动中，你将逐步掌握语法、词汇、技术术语乃至地道俚语。</p><!--kg-card-begin: markdown--><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766527748381/abc33dff-e9bf-4efb-bd4c-46362e42f288.webp" alt="开发者英语课程的截图" class="image--center mx-auto" width="856" height="656" loading="lazy"><!--kg-card-end: markdown--><p>截至目前，已有两项语言认证正式上线：</p><ul><li><a href="https://www.freecodecamp.org/news/freecodecamps-a2-english-for-developers-certification-is-now-live/">A2 级别英语认证发布</a></li><li><a href="https://www.freecodecamp.org/news/freecodecamps-b1-english-for-developers-certification-is-now-live/">B1 级别英语认证发布</a></li></ul><p>我们正在开发 A1、B2、C1 和 C2 级别的课程，计划在未来几年陆续发布（是的，以年为单位。每个级别的开发都是庞大的工程）。</p><p>freeCodeCamp 社区不仅设计了数千节英语课程，还开发了大量定制软件工具来实现这些课程体系。因此在 2024 年，我们提出设想：能否用同样的工具来教授西班牙语和中文普通话？</p><p>如今，这项努力的成果已进入公开测试阶段。我们从这两种语言的 A1 级别课程起步，其余级别将在未来几年逐步推出。</p><ul><li><a href="https://www.freecodecamp.org/news/freecodecamps-a1-professional-spanish-curriculum-beta-is-now-live/">A1 级别西班牙语课程发布</a></li><li><a href="https://www.freecodecamp.org/news/freecodecamps-a1-professional-chinese-curriculum-beta-is-now-live/">A1 级别中文课程发布</a></li></ul><h2 id="--1">为何教授西班牙语和中文？</h2><p>除英语外，西班牙语和中文（普通话）是全球使用最广泛的两种语言。掌握这些语言可帮助你参与众多网络社区、探访主要城市，甚至获得新的工作机会。</p><p>学习外语对大脑神经可塑性大有裨益，并且可以与编程等其他技能学习同步开展。</p><p>现在你可以通过我们完整的端到端课程体系免费学习这些语言，所有课程均由教师、译者和母语者共同设计。</p><h2 id="-freecodecamp-">关于将 freeCodeCamp 课程翻译为世界主要语言的进展</h2><p>你可能知道，freeCodeCamp 自 2020 年起已支持多种世界主要语言。但每当我们发布新课程时，全部内容的翻译仍需数月时间。</p><p>值得欣慰的是，机器翻译技术在过去几年持续进步。</p><p>社区仍在人工翻译教程和书籍，但对于 freeCodeCamp 编程课程这类更新迅速的内容，我们需要加速这一进程。</p><p>我们已试点将全部新课程翻译为西班牙语和葡萄牙语。</p><ul><li>首先运用前沿大语言模型，结合大量术语表和风格指南，处理编程课程中数十万词的原始内容，</li><li>随后由母语者随机抽样审校以保证质量。</li><li>当我们确认翻译质量达标后，便开始建立数据管道，通过开源代码贡献实现英文原文更新时的自动翻译同步。</li></ul><p>这些工作的经济成本并不高昂，因此我们应该能在此基础上，进一步将 freeCodeCamp 编程课程拓展到此前未能支持的其他语言，例如阿拉伯语和法语。</p><!--kg-card-begin: markdown--><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766528132885/06da1977-85b8-438e-8e80-1818f3dba7e7.webp" alt="freeCodeCamp 的编程课程被翻译为葡萄牙语的截图" class="image--center mx-auto" width="1017" height="918" loading="lazy"><!--kg-card-end: markdown--><p>如果你是多年来参与过 freeCodeCamp 翻译的数百位贡献者之一，我们依然非常欢迎你帮助翻译书籍和教程，这些内容在首次发布后通常不会有太大改动。</p><p>毕竟，文档本地化的最高标准是由一位译者完整阅读并理解原文后再进行翻译。</p><h2 id="--2">我们的社区才刚刚启航。</h2><p>今年，freeCodeCamp 社区还实现了：</p><ul><li>在 freeCodeCamp YouTube 频道上发布 129 个免费视频课程</li><li>在 freeCodeCamp 专栏上发布 45 本免费完整书籍和手册</li><li>452 篇关于数学、编程和计算机科学的教程和文章</li><li>50 集 freeCodeCamp 播客节目，我采访了许多开发者，其中许多人都是 freeCodeCamp 开源项目的贡献者</li></ul><p>我们还向 freeCodeCamp 开源学习平台合并了 4,279 次代码提交，这些更新大幅提升了用户体验与可访问性。同时我们发布了安全的考试环境，让 campers 能够参加认证考试。</p><p>你可以查看我们的 <strong><a href="https://www.freecodecamp.org/chinese/news/freecodecamp-2025-top-contributors/">2025 年度的顶级开源贡献者榜单</a></strong>。</p><p>作为一个社区，我们才刚刚起步。免费的开源教育在当今时代的意义前所未有地重大。</p><h2 id="--3">我们也邀请你更深入地参与社区。</h2><p>我要感谢每月支持我们的慈善事业与使命的 10,221 位热心人士。请考虑加入这个行列：<a href="https://www.freecodecamp.org/donate/">捐赠支持 freeCodeCamp.org</a>。</p><p>这里有其他几种<a href="https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp/">可抵扣美国税款的年终捐赠方式</a>。</p><p>freeCodeCamp 拥有一个充满活力的全球社区，聚集了众多积极进取的学习者，他们正在掌握新技能并为职业发展的下一阶段做准备。我鼓励你<a href="https://discord.com/servers/freecodecamp-692816967895220344">加入 freeCodeCamp Discord 与我们交流互动</a>。</p><p>同时，请参与 <a href="https://forms.nhcarrigan.com/o/docs/forms/7LNb8jFoN4SPBvP7vRxDi2/4">Naomi 的 freeCodeCamp 社区调查</a>，帮助我们了解你喜爱 freeCodeCamp 的哪些方面，以及我们的社区可以在哪些方面做得更好。</p><p>我谨代表全球 freeCodeCamp 社区，祝愿你和家人在 2025 年收官之际一切圆满。也让我们共同举杯，迎接充满乐趣与雄心壮志的 2026 年。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ freeCodeCamp A1 专业中文课程（测试版）上线 ]]>
                </title>
                <description>
                    <![CDATA[ freeCodeCamp 社区刚刚发布了我们新的 A1 专业中文课程的前几个章节。你现在可以利用这些内容开始学习中文。 每一章都包含数百个互动任务，旨在帮助你自信地迈出学习中文的第一步。 新的 A1 专业中文课程是怎样的？ 在这个 A1 专业中文课程中，你将学习中文的基础知识。这将遵循欧洲共同语言参考标准（CEFR）的 A1 要求。我们专注于在专业环境中特别有用的词汇。 课程包含多个模块，包括热身、学习、练习、复习和测验，以确保你真正理解了一个模块再进入下一个。 热身是准备部分，为模块的主要内容提供背景。 热身中的任务将向你介绍新词汇，或复习你已经学习过并将在当前模块中使用的内容。 下面是在课程中你将会遇到的一个示例。 每个任务都有一个相应的问题，帮助你练习内容。如果你不知道如何回答问题或需要更多细节，你可以查看解释部分。 在热身之后，你将进入学习 部分。在这里，你会看到你新学的单词在实际中的应用！你将聆听短片段的独白或对话，并回答关于它们的问题，以确保你理解它们的含义以及它们在真实对话中的使用方式。在必要时，你还将在这里学习一些理论。 课程中还包含填 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/freecodecamps-a1-professional-chinese-curriculum-beta-is-now-live/</link>
                <guid isPermaLink="false">694a9c8c1acf9a04cd95df2a</guid>
                
                    <category>
                        <![CDATA[ 社区 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 23 Dec 2025 05:55:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/12/----_20251224135437_142_113.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/freecodecamps-a1-professional-chinese-curriculum-beta-is-now-live/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">freeCodeCamp's A1 Professional Chinese Curriculum (Beta) is Now Live</a>
      </p><!--kg-card-begin: markdown--><p>freeCodeCamp 社区刚刚发布了我们新的 A1 专业中文课程的前几个章节。你现在可以利用这些内容开始学习中文。</p>
<p>每一章都包含数百个互动任务，旨在帮助你自信地迈出学习中文的第一步。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766158559146/126837fd-0786-476d-b643-1eed59e87814.png" alt="A1 专业中文课程大纲。章节包括欢迎、拼音课程，如简单韵母和鼻音韵母，以及问候和介绍。每个章节的完成状态均有显示。" width="848" height="595" loading="lazy"></p>
<h2 id="a1">新的 A1 专业中文课程是怎样的？</h2>
<p>在这个 A1 专业中文课程中，你将学习中文的基础知识。这将遵循欧洲共同语言参考标准（CEFR）的 A1 要求。我们专注于在专业环境中特别有用的词汇。</p>
<p>课程包含多个模块，包括热身、学习、练习、复习和测验，以确保你真正理解了一个模块再进入下一个。</p>
<p><strong>热身</strong>是准备部分，为模块的主要内容提供背景。</p>
<p>热身中的任务将向你介绍新词汇，或复习你已经学习过并将在当前模块中使用的内容。</p>
<p>下面是在课程中你将会遇到的一个示例。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766158665243/cb5a3d23-3626-4b12-aae0-829a3db7b7cf.png" alt="一个穿条纹毛衣的人在一个现代办公室设置的虚拟教室中。文字写道，“还记得一个音节有三个部分：声母、韵母和声调吗？这些部分的任何变化都可能表示完全不同的字符。”对话框显示“你好”的拼音和汉字。" width="960" height="647" loading="lazy"></p>
<p>每个任务都有一个相应的问题，帮助你练习内容。如果你不知道如何回答问题或需要更多细节，你可以查看解释部分。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764863508777/7e6f988c-9bf7-454c-afaa-091920ff5a02.png" alt="7e6f988c-9bf7-454c-afaa-091920ff5a02" width="940" height="725" loading="lazy"></p>
<p>在<strong>热身</strong>之后，你将进入<strong>学习</strong>部分。在这里，你会看到你新学的单词在实际中的应用！你将聆听短片段的独白或对话，并回答关于它们的问题，以确保你理解它们的含义以及它们在真实对话中的使用方式。在必要时，你还将在这里学习一些理论。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766158808549/5a1b2945-149a-433b-a840-cff49224908c.png" alt="在一个有大窗的城市景观的办公室房间里站立的动画角色。上方文字解释如何用中文介绍国籍，“wǒ shì + 国籍”，下方字幕显示角色用拼音和简体中文字符说“我是中国人”。" width="835" height="654" loading="lazy"></p>
<p>课程中还包含填空题，帮助你练习使用拼音和汉字。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766158976101/208a38c7-850d-4d43-8794-c6c51fc92962.png" alt="一个背着背包的动画角色站在有绿色植物墙和电脑的办公室里。显示中文文本和拼音，内容为：“我是新加坡人。”下方有与句子相关的填空题目。" width="816" height="770" loading="lazy"></p>
<p>在<strong>学习</strong>之后，你将继续进入<strong>练习</strong>部分，在这里你将完成更多开放式任务，以测试你是否理解以及使用拼音和汉字写作的能力。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766159066500/9b4b962d-dcd8-4782-9526-07a4cc5e0219.png" alt="一个语言练习的截图，包含中文文本和拼音，指示用户将“我是、开发者、中国人、你好”按正确顺序放到相应位置。“wǒ shì” 意思是“我是”。这些短语需要放置在“wáng huá”和其他提示旁边的空白处。" width="805" height="444" loading="lazy"></p>
<p>在模块的末尾，有一个<strong>复习</strong>部分，包含语法要点、主要词汇和概念。你可以使用这些复习页面帮助你准备测验。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766097053581/bc30af81-4d64-42b2-84d5-624d9c3f87db.png" alt="这幅图片包含一个题为“语法要点”的文本，讨论中文中的问候类型。它介绍了三个短语：“你好” (nǐ hǎo) 用于随意的场合，“您好” (nín hǎo) 用于礼貌互动，“你们好” (nǐ men hǎo) 用于与多人打招呼。" width="1570" height="1042" loading="lazy"></p>
<p>模块的最后部分是<strong>测验</strong>，旨在检验你对模块中所涵盖材料的理解。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766162326716/7f0a788e-5f9b-4b3b-8499-1cd39eca1d37.png" alt="名为“问候与介绍测验”的页面检测你对基础中文问候的理解。要通过，必须正确回答至少 10 道题中的 9 道。第一个问题询问在向一群人问候时应该使用哪个短语，选项包括“nǐ men hǎo” (你们好)、“nín hǎo” (您好)、“wǒ shì” (我是) 和“nǐ hǎo” (你好)。" width="856" height="695" loading="lazy"></p>
<p>认证考试将作为该认证的最后一项发布。我们目前发布了前三章，后续章节将由我们的教学设计团队开发后逐步发布。当所有章节上线后，我们将发布认证考试。</p>
<h2 id="">致谢贡献者</h2>
<p>感谢以下贡献者在课程开发中提供帮助：</p>
<ul>
<li>
<p><a href="https://github.com/gikf">Krzysztof G.</a></p>
</li>
<li>
<p><a href="https://github.com/mrchenguozheng">Chen Guo Zheng</a></p>
</li>
<li>
<p><a href="https://github.com/stevending1st">Steven Ding</a></p>
</li>
</ul>
<p>我们特别感谢 <a href="https://github.com/s1ngs1ng">S1ng S1ng</a>，他录制了独白和对话音频，还录制了生动展示拼音发音的教学视频。这些拼音视频将在明年陆续添加到课程中。</p>
<h2 id="">常见问题</h2>
<h3 id="">这些内容真的全部免费吗？</h3>
<p>是的。freeCodeCamp 一直以来都是免费的，并且我们已经提供免费的认证超过十年。</p>
<h3 id="">我可以用除英语以外的语言学习中文课程吗？</h3>
<p>我们的目标是让 freeCodeCamp 支持的语言课程都能以各种语言提供。查看你的账户设置，看看你正在学习的课程是否已经提供你偏好的语言版本。</p>
<h3 id="">中文课程涵盖哪些语言技能？</h3>
<p>语言课程目前涵盖听力、阅读和写作。我们计划稍后添加口语部分。</p>
<h3 id="">语言课程和考试中的音频是由母语者录制的吗？</h3>
<p>是的。语言课程中的所有音频均由该语言的母语者录制。</p>
<h3 id="">我是聋人或听力不佳者。我还可以学习语言课程吗？</h3>
<p>可以！所有音频课程都提供字幕和可阅读的文本。</p>
<h3 id="">我是盲人或视力有限，并使用屏幕阅读器。我还可以学习语言课程吗？</h3>
<p>可以！freeCodeCamp 课程设计为无障碍，你可以使用屏幕阅读器学习语言课程。如果遇到任何无障碍问题，你可以在我们的 GitHub 仓库上反馈，以便社区解决。</p>
<h3 id="a1a2b1">课程名称旁边的字母和数字有什么意义？（例如：A1、A2、B1）</h3>
<p>这些标记指的是 CEFR（欧洲共同语言参考标准）等级，这是一种用于描述语言熟练程度的国际框架。A1 和 A2 代表初级水平，B1 和 B2 代表中级水平，C1 和 C2 代表高级水平。每个等级指示你在语言学习阶段所预期具备的技能和知识。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765475620837/6822cf7c-d05f-4a5c-8e8a-994c540ed369.png" alt="一张标题为“CEFR 等级详解”的图表展示了从 A1 到 C2 的六个 CEFR 语言能力等级。A1 等级描述为“在倾听者的帮助下进行交流”。A2 等级涉及“在有限范围内进行交流”。B1 等级重点在于“在熟悉的情境中交流核心要点”。B2 等级能够在“多种情境中较流畅地交流”。C1 等级强调“在多数情境中灵活流畅地沟通”。C2 等级代表“在绝大多数情境中实现精准细腻的交流”。freeCodeCamp 标识位于图表底部。" width="1024" height="768" loading="lazy"></p>
<h2 id="">其他</h2>
<p>祝你在 freeCodeCamp 的语言课程学习中好运。</p>
<p>祝学习愉快！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ freeCodeCamp 开源社区 2025 年度 Top Contributors ]]>
                </title>
                <description>
                    <![CDATA[ 2025 年对于全球 freeCodeCamp 社区来说是非常富有成效的一年。作为一个已经走过 12 个年头的社区，我们在各个方面都全速前进，比以往更加稳步推进。 今年，我们对新的全栈开发者课程进行了重大改进。这是 freeCodeCamp 课程的第 10 个版本，包含 7 个认证。在学习过程中，学习者将完成超过 100 个实践项目，并通过计算机科学理论的考试。 此外，在过去的一年里，freeCodeCamp 社区实现了：  * 在 freeCodeCamp YouTube 频道上发布 129 个免费视频课程          * 在 freeCodeCamp 专栏上发布 45 本免费完整书籍和手册          * ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/freecodecamp-2025-top-contributors/</link>
                <guid isPermaLink="false">6947ed6d1acf9a04cd95ded6</guid>
                
                    <category>
                        <![CDATA[ 社区 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Mon, 22 Dec 2025 11:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/12/----_20251222205523_138_113.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/freecodecamp-top-open-source-contributors-2025/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">freeCodeCamp's Top Open Source Contributors of 2025</a>
      </p><!--kg-card-begin: markdown--><p>2025 年对于全球 freeCodeCamp 社区来说是非常富有成效的一年。作为一个已经走过 12 个年头的社区，我们在各个方面都全速前进，比以往更加稳步推进。</p>
<p>今年，我们对新的全栈开发者课程进行了重大改进。这是 freeCodeCamp 课程的第 10 个版本，包含 7 个认证。在学习过程中，学习者将完成超过 100 个实践项目，并通过计算机科学理论的考试。</p>
<p>此外，在过去的一年里，freeCodeCamp 社区实现了：</p>
<ul>
<li>
<p>在 freeCodeCamp YouTube 频道上发布 129 个免费视频课程</p>
</li>
<li>
<p>在 freeCodeCamp 专栏上发布 45 本免费完整书籍和手册</p>
</li>
<li>
<p>452 篇关于数学、编程和计算机科学的教程和文章</p>
</li>
<li>
<p>50 集 freeCodeCamp 播客节目，我采访了许多开发者，其中许多人都是 freeCodeCamp 开源项目的贡献者</p>
</li>
</ul>
<p>我们还合并了 4,279 个提交到 freeCodeCamp 的开源学习平台，进行了大量用户体验和无障碍访问的改进。此外，我们还发布了安全的考试环境，供学习者参加认证考试。</p>
<p>最后，我们在开发者英语课程上取得了相当大的进展，并开始制作即将上线的西班牙语和中文课程。</p>
<p>我们才刚刚开始。我们已经在规划关于数学、数据科学、机器学习和其他深度、技能密集型课程的附加课程。</p>
<p>这一切都要感谢 10,342 名善良的人们<a href="https://donate.freecodecamp.org">捐助支持我们的慈善事业和使命</a>，以及那些投入时间和才华给社区的人们。</p>
<p>以下是 2025 年我们社区的 611 位最活跃的开源贡献者名单：</p>
<h2 id="githubtopcontributor">GitHub Top Contributor</h2>
<ul>
<li>
<p><a href="https://github.com/clarencepenz">Clarence Bakosi</a></p>
</li>
<li>
<p><a href="https://github.com/Supravisor">Supravisor</a></p>
</li>
<li>
<p><a href="https://github.com/Giftea">Giftea ☕</a></p>
</li>
<li>
<p><a href="https://github.com/pdtrang">Diem-Trang Pham</a></p>
</li>
<li>
<p><a href="https://github.com/a2937">Anna</a></p>
</li>
<li>
<p><a href="https://github.com/c0d1ng-ma5ter">c0d1ng_ma5ter</a></p>
</li>
<li>
<p><a href="https://github.com/JungLee-Dev">JungLee-Dev</a></p>
</li>
<li>
<p><a href="https://github.com/hbar1st">hbar1st</a></p>
</li>
<li>
<p><a href="https://github.com/dev-kamil">dev-kamil</a></p>
</li>
<li>
<p><a href="https://github.com/arizfaiyaz">Ariz Faiyaz</a></p>
</li>
<li>
<p><a href="https://github.com/agilan11">agilan11</a></p>
</li>
<li>
<p><a href="https://github.com/cuongpham24">Vinson Pham</a></p>
</li>
<li>
<p><a href="https://github.com/StuartMosquera">Stuart Mosquera</a></p>
</li>
<li>
<p><a href="https://github.com/StephenMuya">Stephen Mutheu Muya</a></p>
</li>
<li>
<p><a href="https://github.com/MohamadSalman11">Mohamad Salman</a></p>
</li>
<li>
<p><a href="https://github.com/alexgoldsmith">Alex Goldsmith</a></p>
</li>
<li>
<p><a href="https://github.com/vishnudt2004">Vishnu D</a></p>
</li>
<li>
<p><a href="https://github.com/kannan-ravi">Kannan</a></p>
</li>
<li>
<p><a href="https://github.com/tanmaygautam11">Tanmay Gautam</a></p>
</li>
<li>
<p><a href="https://github.com/l3onhard">l3onhard</a></p>
</li>
<li>
<p><a href="https://github.com/prabhakaryadav2003">Prabhakar Yadav</a></p>
</li>
<li>
<p><a href="https://github.com/errantpianist">Ezoh Zhang</a></p>
</li>
<li>
<p><a href="https://github.com/Ajay-2005">Ajay A</a></p>
</li>
<li>
<p><a href="https://github.com/dragon-slayer27">Vivaan Teotia</a></p>
</li>
<li>
<p><a href="https://github.com/hassanwaqa">Hassan Waqar</a></p>
</li>
<li>
<p><a href="https://github.com/gikf">Krzysztof G.</a></p>
</li>
<li>
<p><a href="https://github.com/roberiacono">Roberto Iacono</a></p>
</li>
<li>
<p><a href="https://github.com/MelvinManni">Melvin Kosisochukwu</a></p>
</li>
<li>
<p><a href="https://github.com/lasjorg">Lasse Jørgensen</a></p>
</li>
<li>
<p><a href="https://github.com/dennmar">dennmar</a></p>
</li>
<li>
<p><a href="https://github.com/Arif-Khalid">Arif Khalid</a></p>
</li>
<li>
<p><a href="https://github.com/soryaek">Sorya Ek</a></p>
</li>
<li>
<p><a href="https://github.com/RaymondLiu777">Raymond Liu</a></p>
</li>
<li>
<p><a href="https://github.com/AyushSharma72">Ayush Sharma</a></p>
</li>
<li>
<p><a href="https://github.com/yusufasur">Yusuf Can Aşur</a></p>
</li>
<li>
<p><a href="https://github.com/kb42">Karthik Bagavathy</a></p>
</li>
<li>
<p><a href="https://github.com/Sky-walkerX">Naman Khandelwal</a></p>
</li>
<li>
<p><a href="https://github.com/vkalakota18">Varshith Kalakota</a></p>
</li>
<li>
<p><a href="https://github.com/omarraf">Omar Rafiq</a></p>
</li>
<li>
<p><a href="https://github.com/raahthor">Prashant Rathore</a></p>
</li>
<li>
<p><a href="https://github.com/Agung1606">Agung Saputra</a></p>
</li>
<li>
<p><a href="https://github.com/sanchitkhthpalia">Sanchit Kathpalia</a></p>
</li>
<li>
<p><a href="https://github.com/shashankdangi">Shashank Dangi</a></p>
</li>
<li>
<p><a href="https://github.com/garyeung">Gary Yeung</a></p>
</li>
<li>
<p><a href="https://github.com/ppl-call-me-tima">Amit Upadhyay</a></p>
</li>
<li>
<p><a href="https://github.com/pkdvalis">pkdvalis</a></p>
</li>
<li>
<p><a href="https://github.com/sinha21Soumya">sinha21Soumya</a></p>
</li>
<li>
<p><a href="https://github.com/VishalTelukula">Telukula Vishal</a></p>
</li>
<li>
<p><a href="https://github.com/sskiragu">sskiragu</a></p>
</li>
<li>
<p><a href="https://github.com/AishwaryaRajput09">Aishwarya</a></p>
</li>
<li>
<p><a href="https://github.com/TrevorBrowning">Trevor Browning</a></p>
</li>
<li>
<p><a href="https://github.com/AilaLu">AilaLu</a></p>
</li>
<li>
<p><a href="https://github.com/zxc-w">zxc-w</a></p>
</li>
<li>
<p><a href="https://github.com/anishlukk123">Anish Lukkireddy</a></p>
</li>
<li>
<p><a href="https://github.com/josue-igiraneza">Josue Igiraneza</a></p>
</li>
<li>
<p><a href="https://github.com/adityaravichandran6">Aditya Ravichandran</a></p>
</li>
<li>
<p><a href="https://github.com/skyewm">Skye Mickens</a></p>
</li>
<li>
<p><a href="https://github.com/gagan-bhullar-tech">Gagan Bhullar</a></p>
</li>
<li>
<p><a href="https://github.com/asr1325">Aditya</a></p>
</li>
</ul>
<h2 id="topcontributor">论坛 Top Contributor</h2>
<ul>
<li>
<p><a href="https://forum.freecodecamp.org/u/Teller">Teller</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/pkdvalis">pkdvalis</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/hasanzaib1389">Hassan Zaib</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/a1legalfreelance">A1legalfreelance</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/igorgetmeabrain">Doug Badger</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/JeremyLT">Jeremy</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/fcc4b6d10c4-b540-4e2">fcc4b6d10c4-b540-4e2</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/JuniorQ">Arakhsh Q</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Ray13">Raymond</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/hbar1st">Hanaa B.</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/lasjorg">Lasse</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/sanity">sanity or not</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/MostafaElbadry">MostafaElbadry</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/a2937">Anna</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/seopostexpert">Muhammad Subhan</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/stephenmutheu">Stephen Mutheu</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/zs_akkaya">Zeynep Serra Akkaya</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/bappyasif">A.Bappy</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/PauloRodrigues">Paulo</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/StaySilent">StaySilent</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Klexvier">Klexvier</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/robheyays">Robert H.</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/BlindVisionMan">Marvin Hunkin</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/tracy.chacon.00">Tracy Chacon</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/bochard">bochard</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Cody_Biggs">CODY BIGGS</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/be_happy"><em>Infinity</em></a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/ShadyHBedda">Shady H. Bedda</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/booleanmethod9">Boolean Method</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/ArielLeslie">Ariel Leslie</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/DanielHuebschmann">Head in Cloud</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Dovb1ek">Dovb1ek</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/c0d1ng_ma5ter">c0d1ng_ma5ter</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/evaristoc">evaristoc</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/ahraitch">ahr aitch</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/jwhoisfondofit">Jay</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/AlexK">AlexanderTheDev</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Malcolm-Harrison">Malcolm Harrison</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Ethan1">Ethan1</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/vikramvi">Vikram Ingleshwar</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/constantcode9909">Amine (Mike)</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/nickrg">Nicolas Greenwood</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/Amunyelet-Ojala">Amunyelet-Ojala</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/anon75571083">anon75571083</a></p>
</li>
<li>
<p><a href="https://forum.freecodecamp.org/u/brendenhowlett96">Brenden Howlett</a></p>
</li>
</ul>
<h2 id="topcontributor">翻译 Top Contributor</h2>
<ul>
<li>
<p>Afonso Branco (AfonsoBranco)</p>
</li>
<li>
<p>Michael Qu (qubycn)</p>
</li>
<li>
<p>Alan Luo (iLtc)</p>
</li>
<li>
<p>Nadja Sellinat (biebricherin)</p>
</li>
<li>
<p>David Almeida (david.miguel.almeida)</p>
</li>
<li>
<p>jeigux</p>
</li>
<li>
<p>Nairobi (fanqie)</p>
</li>
<li>
<p>Ivan Forcati (IvanF)</p>
</li>
<li>
<p>Quang Nguyen (nguyendangquang126)</p>
</li>
<li>
<p>Ganebas</p>
</li>
<li>
<p>Kentaro Nareswara (K3N7)</p>
</li>
<li>
<p>Alana Maia (nicegrrrl)</p>
</li>
<li>
<p>Gustavo Birman (Gustavuspqr)</p>
</li>
<li>
<p>Nataliia Hrytsyk (nataliia.hrytsyk)</p>
</li>
<li>
<p>Filipe Oliveira (FilipeOliveira)</p>
</li>
<li>
<p>Діана Маркута (dianamarkuta)</p>
</li>
<li>
<p>Kostiantyn Krysenko (barkode)</p>
</li>
<li>
<p>v4n31aa</p>
</li>
<li>
<p>janni1288</p>
</li>
<li>
<p>Nastasia Milosev (nastasia.milosev)</p>
</li>
<li>
<p>Juan Diaz (JuanPabloDiaz)</p>
</li>
<li>
<p>Stas Kinash (stasKinash)</p>
</li>
<li>
<p>ToteM</p>
</li>
<li>
<p>Tihomir Manushev (haraGADygyl)</p>
</li>
<li>
<p>Maximo Sanchez (maxysanchez.06)</p>
</li>
<li>
<p>Сервило Галина (servilogalina)</p>
</li>
<li>
<p>Laureline Paris (LaurelineP)</p>
</li>
<li>
<p>mubinabegimxayrullayeva</p>
</li>
<li>
<p>mitegab</p>
</li>
<li>
<p>Berke Volkan (kzlpndx)</p>
</li>
<li>
<p>Dana Volovelsky (danavolovelsky)</p>
</li>
<li>
<p>Isauro Rodriguez (icaro5)</p>
</li>
<li>
<p>Yuliia Lishchuk (yuli)</p>
</li>
<li>
<p>Palak (palakkhan2002)</p>
</li>
<li>
<p>bahtiyorjonq777</p>
</li>
<li>
<p>Olha Boretska (olha.boretskaa)</p>
</li>
<li>
<p>yidev27</p>
</li>
<li>
<p>Ilona Sheremeta (lonasheremeta78)</p>
</li>
<li>
<p>Anna Shram (annashram53)</p>
</li>
<li>
<p>Anastasiia Perchyshyn (anastasijp2004)</p>
</li>
<li>
<p>Mariia Soloshenko (Mariia_S)</p>
</li>
<li>
<p>erickk (lucerile435)</p>
</li>
<li>
<p>Fran Sanabria (fransanabria)</p>
</li>
<li>
<p>Богдана Онищук (bohdanaon9001)</p>
</li>
<li>
<p>Shogo SENSUI (1000ch)</p>
</li>
<li>
<p>maysa42snow</p>
</li>
<li>
<p>Halia Senkiv (haliasenkiv)</p>
</li>
<li>
<p>Jiyoung Suh (JiyoungSuh)</p>
</li>
<li>
<p>Anairis Carballea (acarballea)</p>
</li>
<li>
<p>Johan Javier Gonzalez Perez (javiergonzalez045)</p>
</li>
<li>
<p>yosrmaalej47</p>
</li>
<li>
<p>Akram Dhib (akramdhib999)</p>
</li>
<li>
<p>Bảo Nam Trần Hoàng (kipi91212)</p>
</li>
<li>
<p>Eduarda Groehs (egroehs)</p>
</li>
<li>
<p>FlameC (anonymHe)</p>
</li>
<li>
<p>sohyun</p>
</li>
<li>
<p>Xiaoyan Zhang (Drwhooooo)</p>
</li>
<li>
<p>Gabriela Silva (gabrielaquintilho.s)</p>
</li>
<li>
<p>Fausto Chiacchietta (faustooch)</p>
</li>
<li>
<p>Seif-03</p>
</li>
<li>
<p>オメロ (homero304)</p>
</li>
<li>
<p>Msam</p>
</li>
<li>
<p>Eya (eyaaba)</p>
</li>
<li>
<p>mohamed ben haj salah (mohamedbhs7)</p>
</li>
<li>
<p>abdallah djarraya (abdallahswimmer)</p>
</li>
<li>
<p>Aylin Gümüş (aylingumus)</p>
</li>
<li>
<p>aminezribi03</p>
</li>
<li>
<p>Eloy Gutiérrez (eloy.alumnes)</p>
</li>
<li>
<p>Ameeri22</p>
</li>
<li>
<p>youssef1607</p>
</li>
<li>
<p>Pablo J Lebed (pjl1978)</p>
</li>
<li>
<p>Mergen N (mn)</p>
</li>
<li>
<p>Yuki Shibata (kyubashi)</p>
</li>
<li>
<p>ggfly666</p>
</li>
<li>
<p>Amine (ersu.amine)</p>
</li>
<li>
<p>WaifuXv</p>
</li>
<li>
<p>Jawnex</p>
</li>
<li>
<p>mamaruo</p>
</li>
<li>
<p>Eric Gigondan (Itsatsu)</p>
</li>
<li>
<p>Hamzalakoud</p>
</li>
<li>
<p>c.marget</p>
</li>
<li>
<p>Saki Basken (sbasken)</p>
</li>
<li>
<p>hashim rashid (hhashbrown)</p>
</li>
<li>
<p>yuan-minglongze</p>
</li>
<li>
<p>OKmimech</p>
</li>
<li>
<p>J.G. P.C. (kaiserpc)</p>
</li>
<li>
<p>Fatma Ajroud (faty_aj)</p>
</li>
<li>
<p>Yuna_707</p>
</li>
<li>
<p>Franklin Solar Navarrete (fsolarnavarrete)</p>
</li>
<li>
<p>zeinebBenRayana</p>
</li>
<li>
<p>Mark P. (Futuraura)</p>
</li>
<li>
<p>Ahmad Hassan (sUfi)</p>
</li>
<li>
<p>Ivrin Ivrin (Ivrin)</p>
</li>
<li>
<p>parapara0919</p>
</li>
<li>
<p>Panah (panah)</p>
</li>
<li>
<p>Mario Turtoi (MarioDev)</p>
</li>
<li>
<p>Edenilson Ulises Aguilar Diaz (UlisesDiaz0)</p>
</li>
<li>
<p>rustamdocstranslator</p>
</li>
<li>
<p>Cristian Salazar (Cristian-27)</p>
</li>
<li>
<p>IsabelaMB</p>
</li>
<li>
<p>Khalil Sassi (khalilsassi67)</p>
</li>
<li>
<p>Sorayadc</p>
</li>
<li>
<p>Franco Casafus (francocasafus22)</p>
</li>
<li>
<p>miwamiwamiwa (miwalaa)</p>
</li>
<li>
<p>franciscomelov</p>
</li>
<li>
<p>Juan Taroni (juanribeiro.taroni)</p>
</li>
<li>
<p>Alexander Liu Gao (aleliu)</p>
</li>
<li>
<p>YC liou (iop52896)</p>
</li>
<li>
<p>David Oliveira (EngDavidOlivr)</p>
</li>
<li>
<p>Campoz _ (campozzz)</p>
</li>
<li>
<p>Roberta Meyrelles (rmftelier)</p>
</li>
<li>
<p>Polina (minlaux)</p>
</li>
<li>
<p>Matheus G. Oliveira (PomboObeso)</p>
</li>
<li>
<p>Hou Bowei (houbowei)</p>
</li>
<li>
<p>Paul (ptijero)</p>
</li>
<li>
<p>Danilo Parada Garcés (dparada.sistemas)</p>
</li>
<li>
<p>Ana_Writer</p>
</li>
<li>
<p>Nathalia Oliveira (royalpython)</p>
</li>
<li>
<p>Amir (Amir_lvx)</p>
</li>
<li>
<p>Eltaj Mammadzada (eltajmammadzada)</p>
</li>
<li>
<p>Oliver Loza (THE_G3NES1S)</p>
</li>
<li>
<p>Jakhongir Murtazaev (jakhongir.murtazayev)</p>
</li>
<li>
<p>Siyana Zdravkova (BlueButterflies)</p>
</li>
<li>
<p>Vindishel (vindishel)</p>
</li>
<li>
<p>Daniel Jimenez (danjim82)</p>
</li>
<li>
<p>hk7math</p>
</li>
<li>
<p>KAWPHUNMAN</p>
</li>
<li>
<p>wdthor</p>
</li>
<li>
<p>Snow sita2 (EnmanuelTorres)</p>
</li>
<li>
<p>Aldo Vanegas (AldoLara)</p>
</li>
<li>
<p>nmo-genio</p>
</li>
<li>
<p>Pedro Daniel (pedrodanielgomes)</p>
</li>
<li>
<p>sadnessasha</p>
</li>
<li>
<p>Aby Prastya Palgunadi (arcanaxvi)</p>
</li>
<li>
<p>Halifolium</p>
</li>
<li>
<p>juan jose (Juanx64)</p>
</li>
<li>
<p>Henry Richard Flores Bazurto (hflores10)</p>
</li>
<li>
<p>Leah</p>
</li>
<li>
<p>Emma (emmaa5)</p>
</li>
<li>
<p>ANVAR ZIYODOV (ziyodovanvar1999)</p>
</li>
<li>
<p>dharris296</p>
</li>
<li>
<p>Juan Esteban Montoya Marín (montoyajuanes11)</p>
</li>
<li>
<p>Yiming Sun (sunyiming008)</p>
</li>
<li>
<p>Fauzi Kurniawan (kurniawan26)</p>
</li>
<li>
<p>Royyan Ahmad Zaydan (Kasehito)</p>
</li>
<li>
<p>Mikadifo</p>
</li>
<li>
<p>thelooter</p>
</li>
<li>
<p>Berkcan Gümüşışık (berkcangumusisik)</p>
</li>
<li>
<p>Stephen Mutheu (stephenmutheu)</p>
</li>
<li>
<p>athen</p>
</li>
<li>
<p>Wajahat (syedmuhammadwajahathusain)</p>
</li>
<li>
<p>Tanish Chauhan (tanishc4444)</p>
</li>
<li>
<p>Satya900</p>
</li>
<li>
<p>Luis V. (lvalderramavergara)</p>
</li>
<li>
<p>Sharvio</p>
</li>
<li>
<p>HibouDev</p>
</li>
<li>
<p>dTM99</p>
</li>
<li>
<p>wuzzjohn</p>
</li>
<li>
<p>Gavin Xu (gavinxu2)</p>
</li>
<li>
<p>ProjektMing</p>
</li>
<li>
<p>Saidkamol Saidjamolov (saidkamolxon)</p>
</li>
<li>
<p>beta filip (betafilip)</p>
</li>
<li>
<p>Deborah Porchia (deborah98)</p>
</li>
<li>
<p>teddy_ye</p>
</li>
<li>
<p>Rommel Hindap (rommel.b.hindap)</p>
</li>
<li>
<p>Jesús Lautaro Careglio Albornoz (JLCareglio)</p>
</li>
<li>
<p>SergioBlancoFtns</p>
</li>
<li>
<p>Nathalie Bour (nathalie.bour)</p>
</li>
<li>
<p>Qingfeng Huang (darrenhqf)</p>
</li>
<li>
<p>Omar (omar.fanzeres)</p>
</li>
<li>
<p>Atsushi Hatakeyama (atsushi729)</p>
</li>
<li>
<p>Diego Fierro (diegoefierro)</p>
</li>
<li>
<p>IsaRO (IsaR0d)</p>
</li>
<li>
<p>Abdulbosit Tuychiev (abdulbosit19980204)</p>
</li>
<li>
<p>NG KA YEE (hkscsheph)</p>
</li>
<li>
<p>Floman Dizwit (Hockman)</p>
</li>
<li>
<p>Karel Vanhelden (karelvanhelden)</p>
</li>
<li>
<p>khay56</p>
</li>
<li>
<p>Dostonbek Matyakubov (doston12)</p>
</li>
<li>
<p>MarcoGeldenhuis</p>
</li>
<li>
<p>immeteor2</p>
</li>
<li>
<p>Freedom Fighter (1543431a)</p>
</li>
<li>
<p>Ibn Hosain (ibnhosain014)</p>
</li>
<li>
<p>Vairus (e-oannis)</p>
</li>
<li>
<p>Elio Fang</p>
</li>
<li>
<p><a href="https://x.com/0x99Ethan3">YiWei</a></p>
</li>
<li>
<p><a href="https://x.com/TsukistarCN">Tsukistar</a></p>
</li>
<li>
<p>luojiyin</p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.linkedin.com/in/qingfeng-huang">Qingfeng Huang</a></p>
</li>
<li>
<p>wendy chen</p>
</li>
<li>
<p>zhizhan</p>
</li>
<li>
<p>HeZean</p>
</li>
<li>
<p>Ivan Forcati</p>
</li>
<li>
<p>Andrea Sisti</p>
</li>
<li>
<p><a href="https://x.com/Xuemei525">彭雪梅</a></p>
</li>
</ul>
<h2 id="youtubetopcontributor">YouTube Top Contributor</h2>
<ul>
<li>
<p><a href="https://github.com/s1ngs1ng">S1ng S1ng</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.linkedin.com/in/leoncarlo/">Carlos León</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@LuisCanary">Luis Canary</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@programacaocomramon">Ramon Rodrigues</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/github.com/jamesgpearce">James Pearce</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@officialtatevaslanyan">Tatev Aslanyan</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@EricWTech">Eric Tech</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@RivaanRanawat">Rivaan Ranawat</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@KhanamCoding">Khaiser Khanam</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@vincibits">Paulo Dichone</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@HiteshCodeLab">Hitesh Choudhary</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@EamonnCottrell">Eamonn Cottrell</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@harshbhatt7585">Harsh Bhatt</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@radu">Radu Mariescu-Istodor</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@3CodeCampers">Imad Saddik</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@EmbarkX">Faisal Memon</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@JSLegendDev">JSLegendDev</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@ExamProChannel">Andrew Brown</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@DaveGrayTeachesCode">Dave Gray</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@asabretech">Ebenezer Asabre</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@the-lisper">Alberto Lerda</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@deeplearningexplained">Yacine Mahdid</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@codeafuture">Alen Omeri</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@tungabayrak9765">Tunga Bayrak</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@CodingCleverly">Haris Iftikhar</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/rdali.github.io/">Rola Dali</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@OmarMAtef">Omar M. Atef</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/github.com/stevenGarciaDev">Steven Garcia</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@TrickSumo">Rishi Kumar Tiwari</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@coleblender">Cole Blender</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@AlterYourEnglish">Borys Cherednychenko</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@haidermalik3402">Haider Malik</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@excel.withgrant">Grant Huang</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@vukrosic">Vuk Rosić</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@LearnQtGuide">Daniel Gakwaya</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/github.com/BrijenMakwana">Brijen Makwana</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@TheCodeholic">Zura Sekhniashvili</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@turingtimemachine">Vladimirs Hisamutdinovs</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@tapasadhikary">Tapas Adhikary</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@richardtopchii">Richard Topchii</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@code-with-abel">Abel Gideon</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@MatkatMusic">Chuck Schiemeyer</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@programmingwithalex.585">Alexandru Cristian</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@masterspanishacademy">Virginia Ocana</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.linkedin.com/in/vaibhav-mehra-main/">Vaibhav Mehra</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/optimusprime09012004@gmail.com">Kshitij Sharma</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.dotnetmastery.com">Bhrugen Patel</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@programmingoceanacademy">Mohammad Fahd Abrah</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@ThePyCoach">Frank Andrade</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@ChadsPrep">Chad McAllister</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@ErikYuzwa">Erik Yuzwa</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@dswithbappy">Bappy Ahmed</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@Programming-Fluency">Noor Fakhry</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@AyushSinghSh">Ayush Singh</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@ever-greg">Gregory Kirchoff</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@algo.monster">Sheldon Chi</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@GlitchyDevs">Muhammad Omar Al Najjar</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@talltalksfromashortlady2798">Chumki Biswas</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/c/AlvinTheProgrammer">Alvin Zablan</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@structuredcs">Qiang Hao</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@datasciencewithmarco">Marco Peix</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@logicBaseLabs">Sumit Saha</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.linkedin.com/in/yilmazalaca">Yılmaz Alaca</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@robotbobby9">Bobby Roe</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/x.com/wagslane">Lane Wagner</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/%E2%80%AA@mobidevtalk">Shad Rayhan Mazumder</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@DotNetHow">Ervis Trupja</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/github.com/vivekkalyanarangan30">Vivek Kalyanarangan</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@cs50">David J. Malan</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.linkedin.com/in/leoncarlo/">Carlos Leon</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@BlossomBuild">Carlos Valentin</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@DestinationFAANG">Parth Vyas</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@codewithmuhammadabdullah">Muhammad Abdullah</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@andrewwoan">Andrew Woan</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/freecodecamp-2025-top-contributors/www.youtube.com/@AlexGordonHiFi">Alex Gordon</a></p>
</li>
<li>
<p><a href="http://youtube.com/@TwoWaysMath">Karol Kurek</a></p>
</li>
</ul>
<h2 id="topcontributor">专栏 Top Contributor</h2>
<ul>
<li>
<p><a href="https://freecodecamp.org/news/author/manishshivanandhan">Manish Shivanandhan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/nsavant">Nikheel Vishwas Savant</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/appinisurya">Surya Teja Appini</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tayo4christ">OMOTAYO OMOYEMI</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Casmir">Casmir Onyekani</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Chukwudinweze">Chukwudi Nweze</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/GerCocca">German Cocca</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Shejan-Mahamud">Shejan Mahamud</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/hew">Hew Hahn</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/curiousmoshe">Moshe Siegel</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/markm208">Mark Mahoney</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/atuoha">Atuoha Anthony</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/balapriyac">Bala Priya C</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ashutoshkrris">Ashutosh Krishna</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/hiteshchauhan2023">Hitesh Chauhan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tiredmahnoor">Mah Noor</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/andrewbaisden">Andrew Baisden</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/trayalex812">Alex Tray</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/gkoos">Gabor Koos</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Abhidave">Abhijeet Dave</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sumitsaha">Sumit Saha</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Tech-On-Diapers">Opaluwa Emidowojo</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/oluwatobiss">Oluwatobi Sofela</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sholajegede">Shola Jegede</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/michaelyuan">Michael Yuan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ChisomUma123">Chisom Uma</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/heywisdom">Wisdom Usa</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/kuriko">Kuriko Iwai</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ceddlyburge">Cedd Burge</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/CaesarSage">Destiny Erhabor</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/leomofthings">Ayodele Aransiola</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Koded001">Temitope Oyedele</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/emdadulislam">Emdadul Islam</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Tioluwani">Oyedele Tioluwani</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mehtasoham">Soham Mehta</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Agnes28">Agnes Olorundare</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/atapas">Tapas Adhikary</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/chiragagrawal">Chirag Agrawal</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ATechAjay">Ajay Yadav</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mayur9210">Mayur Vekariya</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Sharvin26">Sharvin Shah</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/adejumo">Adejumo Ridwan Suleiman</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sravankaruturi">Sravan Karuturi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/wagslane">Lane Wagner</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/nitheeshp">Nitheesh Poojary</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/almamohapatra">Alma Mohapatra</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/orimdominic">Orim Dominic Adah</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/arunachalamb">Arunachalam B</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ayush01Mishra">AYUSH MISHRA</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/shricodev">Shrijal Acharya</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Oliverkrane">Ikegah Oliver</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Jongbo">Olaleye Blessing</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ifycodes99">Ifeoma Udu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Tobilyn77">Oluwatobi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/oluwaseunoladeji">Oladeji Oluwaseun</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tarunsinghofficial">Tarun Singh</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/grantdotdev">Grant Riordan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/asfakahmed">Asfak Ahmed</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/LeeRenJie">Tech With RJ</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/timkleier">Tim Kleier</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/CodeHemaa">Ophy Boamah</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ezinnecodes">EZINNE ANNE EMILIA</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Codinghappiness">Happiness Omale</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/bertao">Pedro</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Lonercode">Amanda Ene Adoyi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/arunshanmugamkumar">Arun Shanmugam Kumar</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/andrewezeani">Andrew Ezeani</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ajay074">Ajay Kalal</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ijay">Ijeoma Igboagu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/prankurpandeyy">Prankur Pandey</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/augustinealul">Augustine Alul</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sanjayxr">Sanjay R</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/adiatiayu">Ayu Adiati</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/cardstdani">Daniel García Solla</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Clifftech">Isaiah Clifford Opoku</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ryan-michael-kay">Ryan Michael Kay</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/olanetsoft">Idris Olubisi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/wittycircuitry">Aditya Vikram Kashyap</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/brkln">brooklyn</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/TemiTope1">Tope Fasasi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ilknureren">Ilknur Eren</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sohamstars">Soham Banerjee</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/vaheaslanyan">Vahe Aslanyan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tatevaslanyan">Tatev Aslanyan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/henrywinnerman">Henry Adepegba</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ThatCoolGuy">Oluwadamilola Oshungboye</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/shrutikapoor">Shruti Kapoor</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/theladybella">Mfonobong Umondia</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Spruce">Spruce Emmanuel</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Derekvibe">Okoro Emmanuel Nzube</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/rajumanoj">Raju Manoj</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/udemezue">Udemezue John</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/lulunwenyi">Oluchi Nwenyi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/toobaj">Tooba Jamal</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/nwanduka">Victoria Nduka</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Nene23">Nneoma Uche</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/michaelikoko">Michael Ikoko</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Jude-Olowo">Olowo Jude</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ateev">Ateev Duggal</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Oluwadamisi">Oluwadamisi Samuel</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/menghnani">Mohit Menghnani</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/scriptedBytes">Brandon Wozniewicz</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/huhuhang">Hang Hu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mrufai">Rufai Mustapha</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/LolaVictoria">Damilola Oniyide</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/HijabiCoder">Fatuma Abdullahi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/montasser1988">Montasser Mossallem</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/gatwirival">valentine Gatwiri</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/SmoothTech">Timothy Olanrewaju</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/gitgithan">Han Qi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Omah">Eti Ijeoma</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Yazdun">Yazdun</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/desoga">deji adesoga</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/the_BrianB">Brian Barrow</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/chidiadi01">Chidiadi Anyanwu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/codelikeandrew">Andrew Maksimchenko</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mcasari">Mario Casari</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Balajeeasish">Balajee Asish Brahmandam</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/woai3c">Gordan Tan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tildaudufo">Tilda Udufo</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/rahulgupta32">Rahul gupta</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/edae">Eda Eren</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/josiahadesola">Josiah Adesola</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/smarttester">Venkata Sai Sandeep</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tiagomonteiro">Tiago Capelo Monteiro</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Preston56">Preston Osoro</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/TheAnkurTyagi">Ankur Tyagi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/justanothertechlead">Ben</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/askvikram">Vikram Aruchamy</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/dhruv-007">Dhruv Prajapati</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/onukwilip">Prince Onukwili</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/AdeboyeDN">Daniel Adeboye</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/kanand">Kumar Anand</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/lucasgarcez">Lucas</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ec001">evaristo.c</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/officialrajdeepsingh">Rajdeep Singh</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/chaitanyarahalkar">Chaitanya Rahalkar</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/MahamCodes">Maham Codes</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/divyasaratchandran">Divya Valsala Saratchandran</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/joanayebola">Joan Ayebola</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/omerros">Omer Rosenbaum</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Nazneen758">Nazneen Ahmad</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/KunalN25">Kunal Nalawade</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/sdranju">Shamsuddoha Ranju</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/anamol-rajbhandari">Anamol Rajbhandari</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/suleolanrewaju">Sule-Balogun Olanrewaju</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Eccentric-">Sara Jadhav</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/LifeofDan-EL">Daniel Anomfueme</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Michael-para">Michael Para</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ellabee">Elabonga Atuo</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/azubuikeduru">Azubuike Duru</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/AdalbertPungu">Adalbert Pungu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/gunkev">Kevine Nzapdi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/hamdaan">Hamdaan Ali</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/AbdullahInBytes">Abdullah Salaudeen</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/RAHULISM">Rahul</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Ayobami6">Alaran Ayobami</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/omoladeekpeni">Omolade Ekpeni</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/rwalters">Rob Walters</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/viv1">Vivek Sahu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Xtephen">oghenekparobo Stephen</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/psmohammedali">P S Mohammed Ali</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/gursimar">Gursimar Singh</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Olabisi09">Olabisi Olaoye</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/de">David Asaolu</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/initialcommit">Jacob Stopak</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Yoma">Emore Ogheneyoma Lawrence</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/hunor">Hunor Márton Borbély</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/techwithpraisejames">Praise James</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/pltvs">Alex Pliutau</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/tanishkamakode">Tanishka Makode</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/SonyaMoisset">Sonya Moisset</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/vkweb">Vivek Agrawal</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/syedamahamfahim">Syeda Maham Fahim</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/chiderahumphrey">Chidera Humphrey</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/DoableDanny">Danny</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/stefanmuzyka">Stefan Muzyka</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/samhitharamaprasad">Samhitha Rama Prasad</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/nitinfab">Nitin Sharma</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/codewithshahan">Programming with Shahan</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/jpromanonet">Juan P. Romano</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/anjanbaradwaj">Anjan Baradwaj</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/manocormen">Manoel</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mkbadeniyi">Kayode Adeniyi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/MuhToyyib">Akande Olalekan Toheeb</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/dbclinton">David Clinton</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/josevnz">Jose Vicente Nunez</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/joeattardi">Joe Attardi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/mihailgaberov">Mihail Gaberov</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/ashimi0x">Ashimi0x</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Zubs">Zubair Idris Aweda</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/IbrahimOgunbiyi">Ibrahim Ogunbiyi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/svlorman">Svitlana Lorman</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/timmy471">Ayantunji Timilehin</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/imkrishnasarathi">Krishna Sarathi Ghosh</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/Daiveed">David Jaja</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/francisihe">Francis Ihejirika</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/marco-venturi">Marco Venturi</a></p>
</li>
<li>
<p><a href="https://freecodecamp.org/news/author/nyayicfanny">Fanny Nyayic</a></p>
</li>
</ul>
<h2 id="discordtopcontributor">Discord Top Contributor</h2>
<ul>
<li>
<p>hana-banana</p>
</li>
<li>
<p>Science99</p>
</li>
<li>
<p>Razzle Dazzle</p>
</li>
<li>
<p>jeremylt (他/他们)</p>
</li>
<li>
<p>Makka Pakka jOoJ</p>
</li>
<li>
<p>bradtaniguchi</p>
</li>
<li>
<p>ʇɹǝqɯoɥɹ</p>
</li>
<li>
<p>Versailles</p>
</li>
<li>
<p>CapslockHero 🎃</p>
</li>
<li>
<p>plamoni</p>
</li>
<li>
<p>xCoffeeMan</p>
</li>
<li>
<p>Dylan</p>
</li>
<li>
<p>QC Failed (Brandon)</p>
</li>
<li>
<p>minjo70</p>
</li>
<li>
<p>tgrtim</p>
</li>
<li>
<p>Yu14</p>
</li>
<li>
<p>localhost</p>
</li>
<li>
<p>ArielLeslie</p>
</li>
<li>
<p>Wayloe</p>
</li>
<li>
<p>Hordian</p>
</li>
<li>
<p>Anna</p>
</li>
<li>
<p>Starbreeze</p>
</li>
<li>
<p>supertanno</p>
</li>
<li>
<p>Hermit</p>
</li>
<li>
<p>Aakash</p>
</li>
<li>
<p>Ganesh</p>
</li>
<li>
<p>Zino</p>
</li>
<li>
<p>Cristina</p>
</li>
<li>
<p>Pantalonians</p>
</li>
<li>
<p>Cy4er</p>
</li>
<li>
<p>himonshuuu</p>
</li>
<li>
<p>Dumb ninja</p>
</li>
<li>
<p>Kiseki 奇跡</p>
</li>
<li>
<p>Sebastian</p>
</li>
<li>
<p>alpox</p>
</li>
<li>
<p>BasCat</p>
</li>
</ul>
<p>再次说明，这些只是参与 freeCodeCamp 社区的成千上万的人中最活跃的一些。</p>
<p>如果你有兴趣以开源贡献者的身份参与 freeCodeCamp 社区，我建议你<a href="https://contribute.freecodecamp.org/#/">阅读我们的贡献者指南</a>，并加入我们的<a href="https://discord.gg/KVUmVXA">贡献者 Discord 聊天室</a>。</p>
<p>再次感谢，祝编码愉快。🏕️</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 介绍 freeCodeCamp 每日 Python 和 JavaScript 挑战 – 每天解决一个新的编程难题 ]]>
                </title>
                <description>
                    <![CDATA[ freeCodeCamp 社区很高兴地宣布，我们新的每日编程挑战已经为你准备好了。🎊 持续练习是提升编程技能最有效的方法之一。因此，除了核心编程课程外，你现在可以通过每天解决一个有趣的新编程挑战来保持编程熟练度。 每个午夜（美国中部时间） ，一道新的编程题目将在 freecodecamp.org [https://freecodecamp.org] 和 freeCodeCamp 移动应用上解锁，你可以使用 JavaScript 和 Python 来解决。 如何开始解决每日编程挑战 在 freeCodeCamp 移动应用上：  1. 从 Google Play Store 或 Apple App Store 下载 freeCodeCamp 移动应用并打开。            2. 如果你想要保存你的进度，别忘了登录。然后在顶部找到今天的挑战并点击“开始挑战”：       ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/introducing-freecodecamp-daily-python-and-javascript-challenges-solve-a-new-programming-puzzle-every-day/</link>
                <guid isPermaLink="false">68dca7a6ea47500467bc736e</guid>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 01 Oct 2025 10:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/10/66cb1d9b-b2e1-4911-bea5-ca1f1308ca35.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/introducing-freecodecamp-daily-python-and-javascript-challenges-solve-a-new-programming-puzzle-every-day/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Introducing freeCodeCamp Daily Python and JavaScript Challenges – Solve a New Programming Puzzle Every Day</a>
      </p><!--kg-card-begin: markdown--><p>freeCodeCamp 社区很高兴地宣布，我们新的每日编程挑战已经为你准备好了。🎊</p>
<p>持续练习是提升编程技能最有效的方法之一。因此，除了核心编程课程外，你现在可以通过每天解决一个有趣的新编程挑战来保持编程熟练度。</p>
<p>每个午夜（美国中部时间） ，一道新的编程题目将在 <a href="https://freecodecamp.org">freecodecamp.org</a> 和 freeCodeCamp 移动应用上解锁，你可以使用 JavaScript 和 Python 来解决。</p>
<h2 id="">如何开始解决每日编程挑战</h2>
<h3 id="freecodecamp">在 freeCodeCamp 移动应用上：</h3>
<ol>
<li>
<p>从 Google Play Store 或 Apple App Store 下载 freeCodeCamp 移动应用并打开。</p>
</li>
<li>
<p>如果你想要保存你的进度，别忘了登录。然后在顶部找到今天的挑战并点击“开始挑战”：</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757013682796/383439a2-12d5-4054-88d5-e0e6a6f052f0.png" alt="383439a2-12d5-4054-88d5-e0e6a6f052f0" width="281" height="609" loading="lazy"></p>
<ol start="3">
<li>选择你喜欢的语言：</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757013721085/873f1d66-93c5-47ac-805c-aa8ef99d058c.png" alt="873f1d66-93c5-47ac-805c-aa8ef99d058c" width="281" height="609" loading="lazy"></p>
<ol start="4">
<li>阅读挑战指令：</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757013769135/ff7d0843-1566-4349-8559-9626264bb87a.png" alt="ff7d0843-1566-4349-8559-9626264bb87a" width="281" height="609" loading="lazy"></p>
<ol start="5">
<li>编写代码：</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757013821639/a049756c-b873-4b6b-a7a9-ab020db48933.png" alt="a049756c-b873-4b6b-a7a9-ab020db48933" width="281" height="609" loading="lazy"></p>
<ol start="6">
<li>通过测试：</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757014651276/28dff232-0c54-41b3-ac2f-0ddbfa0d913c.png" alt="28dff232-0c54-41b3-ac2f-0ddbfa0d913c" width="281" height="609" loading="lazy"></p>
<p>这样你就完成了这项挑战。</p>
<h3 id="">在网站上：</h3>
<ol>
<li>访问 <a href="https://freecodecamp.org">freecodecamp.org</a> 并确保你已登录，以保存进度。然后向下滚动找到当天的编程挑战并点击“开始”：</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757014100541/aaf37fe9-d06e-4198-834c-6cd6ea37d330.png" alt="aaf37fe9-d06e-4198-834c-6cd6ea37d330" width="487" height="130" loading="lazy"></p>
<ol start="2">
<li>
<p>在顶部选择你喜欢的语言</p>
</li>
<li>
<p>阅读挑战指令，编写代码并通过测试：</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757014412721/ee3a6ed9-0628-41fe-acf0-fc10e5772ee8.png" alt="ee3a6ed9-0628-41fe-acf0-fc10e5772ee8" width="2094" height="1560" loading="lazy"></p>
<h2 id="">如果我错过了一天怎么办？</h2>
<p>如果你错过了一项挑战，不必担心。移动应用和网站都包含一个<strong>归档页面</strong>，你可以根据自己的节奏浏览和完成之前的挑战：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757015163098/3bdf35b5-d76c-49de-bb50-35f262716059.png" alt="3bdf35b5-d76c-49de-bb50-35f262716059" width="2094" height="1560" loading="lazy"></p>
<h2 id="">今天开始你的首个挑战</h2>
<p>今天的挑战正在等待你。打开 freeCodeCamp 应用或访问 <a href="https://freecodecamp.org">freecodecamp.org</a> 完成它吧。🚀</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 在你的浏览器中学习 React - freeCodeCamp 全栈课程 2025 年中更新 ]]>
                </title>
                <description>
                    <![CDATA[ Hey campers！freeCodeCamp 社区仍在全力开发我们的全栈课程 [https://www.freecodecamp.org/chinese/learn/full-stack-developer/]的剩余部分。 虽然才过去几个月，但已经有许多学员全力投入这些新课程和编程挑战中。 我很兴奋能与大家分享下一批更新。 新课程内容 我们刚刚发布了三个新的课程部分：React Hooks 和 State、性能、测试。 这些新课程包含：  * 50 个讲座视频  * 十几个工作坊和实验室  * 3 个新的复习模块  * 以及大量课程内容，帮助你保持学习进度（同时我们正在完成全栈课程的剩余部分） 你将构建的一些项目包括：  * 井字棋游戏  * 颜色选择器  * 超级英雄申请表 考试 我们知道许多人热切期待每个模块末尾的考试。这些考试快要准备好了。 我们还在构建一个自定义的考试环境，供你参加这些考试。这将平衡隐私保护和学术诚信。 下一步计划 社区现在正专注于 CSS 库和 TypeScript 模块。我们还在构建 Python 模块。 未来几个月我们将有很多 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-react-in-your-browser-freecodecamp-full-stack-curriculum-mid-2025-update/</link>
                <guid isPermaLink="false">68527f0aeb98e40467ef9ab5</guid>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 18 Jun 2025 09:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/06/----_20250618171556.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/learn-react-in-your-browser-freecodecamp-full-stack-curriculum-mid-2025-update/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn React in your Browser – freeCodeCamp Full Stack Curriculum Mid-2025 Update</a>
      </p><!--kg-card-begin: markdown--><p>Hey campers！freeCodeCamp 社区仍在全力开发我们的<a href="https://www.freecodecamp.org/chinese/learn/full-stack-developer/">全栈课程</a>的剩余部分。</p>
<p>虽然才过去几个月，但已经有许多学员全力投入这些新课程和编程挑战中。</p>
<p>我很兴奋能与大家分享下一批更新。</p>
<h2 id="">新课程内容</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750152183453/6e973e4a-4b7e-4294-b056-95a5f6155e77.png" alt="课程页面截图" width="717" height="766" loading="lazy"></p>
<p>我们刚刚发布了三个新的课程部分：React Hooks 和 State、性能、测试。</p>
<p>这些新课程包含：</p>
<ul>
<li>50 个讲座视频</li>
<li>十几个工作坊和实验室</li>
<li>3 个新的复习模块</li>
<li>以及大量课程内容，帮助你保持学习进度（同时我们正在完成全栈课程的剩余部分）</li>
</ul>
<p>你将构建的一些项目包括：</p>
<ul>
<li>井字棋游戏</li>
<li>颜色选择器</li>
<li>超级英雄申请表</li>
</ul>
<h2 id="">考试</h2>
<p>我们知道许多人热切期待每个模块末尾的考试。这些考试快要准备好了。</p>
<p>我们还在构建一个自定义的考试环境，供你参加这些考试。这将平衡隐私保护和学术诚信。</p>
<h2 id="">下一步计划</h2>
<p>社区现在正专注于 CSS 库和 TypeScript 模块。我们还在构建 Python 模块。</p>
<p>未来几个月我们将有很多内容发布。</p>
<p>一些即将上线的项目包括：</p>
<ul>
<li>构建你自己的 RPG 角色</li>
<li>交易卡牌游戏</li>
<li>医疗数据验证器</li>
<li>以及更多🏕️</li>
</ul>
<h2 id="">参与进来</h2>
<p>你有兴趣帮助我们开发这些全栈课程吗？欢迎你贡献，你可以在我们的 GitHub 仓库查看所有开放的<a href="https://github.com/freecodecamp/freecodecamp/issues">议题</a>。</p>
<p>请先阅读我们的<a href="https://contribute.freecodecamp.org/intro/">贡献指南</a>，然后加入我们的 <a href="https://discord.gg/KVUmVXA">Discord 社区</a>，你可以提出任何问题。</p>
<p>我们很兴奋看到大家学习所有这些新课程。保持动力，继续提升技能，Happy coding！💜</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 旅行者面临的网络安全风险及应对策略 ]]>
                </title>
                <description>
                    <![CDATA[ 旅行是人生最大乐趣之一，但也让你成为网络罪犯的重点目标。 游客常常行色匆匆、注意力分散或不熟悉当地服务商，而这正是黑客所希望看到的。 从虚假 Wi-Fi 网络到可疑 SIM 卡骗局，以下是出国旅行时可能遇到的五大常见数字威胁及防范措施。 虚假 Wi-Fi 网络 在繁忙车站或温馨咖啡馆看到名为 “Free_Public_WiFi” 的网络时，人们常会不假思索地连接。 问题在于：这可能是伪基站。 黑客会架设名称逼真的恶意热点诱骗旅客连接，随后就能监控流量、窃取登录凭证、短信甚至信用卡信息。这种“中间人攻击 [https://www.techtarget.com/iotagenda/definition/man-in-the-middle-attack-MitM] ”在公共场所出人意料地容易得手。 安全指南： • 避免用公共 Wi-Fi 进行敏感操作（特别是网银和网购） • 在设备设置中关闭自动连接开放网络功能 • 使用 VPN 加密网络流量，增加黑客窃密难度 公共 Wi-Fi 虽便利但风险极高。保持警惕、关闭自动连接并启用 VPN，方能在保持联网时守护安全。 虚假预订/签证 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-attackers-target-travelers-and-how-to-defend-yourself/</link>
                <guid isPermaLink="false">683e6fd6aeeaf6047bb88d6a</guid>
                
                    <category>
                        <![CDATA[ 安全 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 03 Jun 2025 04:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/06/----_20250603122250.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-attackers-target-travelers-and-how-to-defend-yourself/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How Attackers Target Travelers – and How to Defend Yourself</a>
      </p><p>旅行是人生最大乐趣之一，但也让你成为网络罪犯的重点目标。</p><p>游客常常行色匆匆、注意力分散或不熟悉当地服务商，而这正是黑客所希望看到的。</p><p>从虚假 Wi-Fi 网络到可疑 SIM 卡骗局，以下是出国旅行时可能遇到的五大常见数字威胁及防范措施。</p><h2 id="-wi-fi-">虚假 Wi-Fi 网络</h2><p>在繁忙车站或温馨咖啡馆看到名为 “Free_Public_WiFi” 的网络时，人们常会不假思索地连接。</p><p>问题在于：这可能是伪基站。</p><p>黑客会架设名称逼真的恶意热点诱骗旅客连接，随后就能监控流量、窃取登录凭证、短信甚至信用卡信息。这种“<a href="https://www.techtarget.com/iotagenda/definition/man-in-the-middle-attack-MitM">中间人攻击</a>”在公共场所出人意料地容易得手。</p><p>安全指南：<br>• 避免用公共 Wi-Fi 进行敏感操作（特别是网银和网购）<br>• 在设备设置中关闭自动连接开放网络功能<br>• 使用 VPN 加密网络流量，增加黑客窃密难度</p><p>公共 Wi-Fi 虽便利但风险极高。保持警惕、关闭自动连接并启用 VPN，方能在保持联网时守护安全。</p><h2 id="-">虚假预订/签证网站</h2><p>在线规划行程很方便——有时过于方便了。</p><p>诈骗者能伪造与真实网站几乎一模一样的假的预订页面。当你以为在预订酒店或申请签证时，实则将信用卡和护照信息送给了骗子。</p><p>常见手法是微调知名域名，如将 expedia.com 改为 expeida.com，并辅以令人难以置信的优惠。警告：这些都是陷阱！</p><p>应对策略：<br>• 手动输入网址而非点击邮件/广告链接<br>• 认准知名平台或直接访问航空/酒店官网<br>• 仔细检查 URL 拼写，确认以 “https://” 开头<br>• 通过可信搜索引擎或旅行咨询页查找政府签证官网</p><p>这类骗局不仅造成不便，更可能让你损失大量金钱并延误行程。</p><h2 id="sim-">SIM 卡调包与可疑商家</h2><p>许多国家会建议游客抵达后购买当地 SIM 卡，机场柜台就出售预付费套餐。</p><p>但交出护照并让他人安装 SIM 卡风险极大。</p><p>SIM 卡调包指他人获取你的 SIM 凭证后，将号码转移至新卡并接收双重验证码，继而轻松入侵银行账户或邮箱。</p><p>防范措施：<br>• 避免在街头随机摊位购买 SIM 卡（尤其是那种索要额外的个人信息的）<br>• 通过手机设置启用 SIM 卡 PIN 码锁<br>• 关闭重要账户的短信验证，改用 Google 身份验证器等 APP</p><p>出发前配置国际旅行 eSIM 更加安全，这比实体 SIM 更难被劫持。</p><h2 id="atm-">ATM 侧录与信用卡克隆</h2><p>当你在现金不足时在 ATM 取款，可能没注意到卡槽上的侧录装置或键盘上方的微型摄像头。这些设备能复制卡片数据，配合 PIN 码即可克隆卡片清空账户。餐厅和商店的便携读卡器同样可能暗藏玄机。</p><p>防护建议：<br>• 优先使用银行内或酒店大堂等监控区域的 ATM<br>• 轻拉卡槽检查是否松动异常<br>• 输入 PIN 码时遮挡键盘，即使当下只有你一个人<br>• 尽量使用信用卡，它比借记卡有更完善的防诈保护</p><p>设置交易实时提醒，旅行期间密切监控账户。</p><h2 id="--1">钓鱼邮件与社会工程学</h2><p>“检测到 Airbnb 账户异常活动，点击验证”——这类使用企业标识的逼真邮件实为陷阱。</p><p>诈骗者伪装成可信机构诱导泄露凭证，旅客因身处陌生环境且期待行程更新更易中招。</p><p>线下场景同样存在：穿制服者以“验证”为由索要护照，或谎称酒店预订异常。这类心理操控称为社会工程学。</p><p>应对方法：<br>• 核实邮件发件人，正规企业通常不会通过邮件索要敏感信息<br>• 勿点击可疑链接，直接登录官网查看<br>• 对索要证件者保持礼貌但谨慎，先核实其身份<br>• 为每个账户设置高强度唯一密码，并启用 APP 双重验证（非短信）</p><p>当你察觉异常时，应该相信直觉，宁可多问也别事后懊悔。</p><h2 id="--2">保持警惕，安全出行</h2><p>只要你不给机会，网络诈骗就毁不了你的旅程。简单准备就能事半功倍：始终使用 VPN 隐藏网络活动、认准安全网站、非必要不使用公共 Wi-Fi、设置强密码并保持设备更新。</p><p>旅行本该充满乐趣而非焦虑。掌握正确工具并养成安全习惯，你就能安心探索世界，在保持联网的同时守护信息安全。</p><p>希望你喜欢这篇文章。欢迎订阅 <a href="https://newsletter.stealthsecurity.sh/">Stealth Security newsletter</a>，获取更多网络安全的相关文章。想学习进攻性网络安全基础知识？不妨试试我们的 <a href="https://start.stealthsecurity.sh/">Security Starter</a> 课程。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 结合代码示例解析递归的工作原理 ]]>
                </title>
                <description>
                    <![CDATA[ 在本文中，你将学习递归的工作原理。 在学习递归之前，你需要很好地理解函数的工作原理。我在本文中使用了 Python 代码作为示例，因为它的语法简单，但递归的概念对于每种编程语言都是一样的。 什么是递归？ 在大多数编程语言中，一个函数可以调用另一个函数。但一个函数也可以调用自身。递归是一种函数调用自身的技术。 这是一个例子： def call_me():     call_me() 在这里，函数调用自身，这就是所谓的递归。 但是“调用自身”只是递归的程序定义。递归涉及将一个问题分解为更小的部分，直到无法进一步分解为止。你解决小问题并将它们组合起来以解决整个问题。 用真实生活中的例子类比递归 让我们通过一个例子来理解递归如何真正工作。 想象一下，你在迪士尼乐园排队等候，你不知道前面有多少人。 为了找出答案，你问你前面的人。 试图找出排在你前面的人数 那个人也不知道，所以她们询问她们前面的那个人。 这个过程继续进行，直到问题到达队伍最前面的人，他们看到前面没有人，回答说前面有零人。 然后队伍开始将回复传递回去。每个人在将信息传递回去之前，都会在他们被告知的数字 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-is-recursion/</link>
                <guid isPermaLink="false">67c06d188d004904442ab80a</guid>
                
                    <category>
                        <![CDATA[ 递归 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 27 Feb 2025 12:50:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/02/Frame-1--6-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-is-recursion/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How Does Recursion Work? Explained with Code Examples</a>
      </p><!--kg-card-begin: markdown--><p>在本文中，你将学习递归的工作原理。</p>
<p>在学习递归之前，你需要很好地理解函数的工作原理。我在本文中使用了 Python 代码作为示例，因为它的语法简单，但递归的概念对于每种编程语言都是一样的。</p>
<h2 id="">什么是递归？</h2>
<p>在大多数编程语言中，一个函数可以调用另一个函数。但一个函数也可以调用自身。递归是一种函数调用自身的技术。</p>
<p>这是一个例子：</p>
<pre><code>def call_me():
    call_me()
</code></pre>
<p>在这里，函数调用自身，这就是所谓的递归。</p>
<p>但是“调用自身”只是递归的程序定义。递归涉及将一个问题分解为更小的部分，直到无法进一步分解为止。你解决小问题并将它们组合起来以解决整个问题。</p>
<h2 id="">用真实生活中的例子类比递归</h2>
<p>让我们通过一个例子来理解递归如何真正工作。</p>
<p>想象一下，你在迪士尼乐园排队等候，你不知道前面有多少人。</p>
<p>为了找出答案，你问你前面的人。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/How-many--4-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>试图找出排在你前面的人数</em></p>
<p>那个人也不知道，所以她们询问她们前面的那个人。</p>
<p>这个过程继续进行，直到问题到达队伍最前面的人，他们看到前面没有人，回答说前面有零人。</p>
<p>然后队伍开始将回复传递回去。每个人在将信息传递回去之前，都会在他们被告知的数字上加 1。</p>
<p>当排在最前面的人回答，<strong>“前面有 0 人”<strong>时，下一个人加 1 然后回答，</strong>“前面有 1 人”</strong>，依此类推。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/How-many--5-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>每个人都知道他们前面有多少人</em></p>
<p>当回复到达直接在你前面的人时，他们再加上 1 并告诉你。通过这种方式，你可以通过将前面的人给的数字添加 <strong>1</strong>，来确定你在队伍中的位置。</p>
<p>这个例子说明了递归如何将问题分解为更小的子问题，然后结合它们的解决方案以解决原始问题。</p>
<p>队伍中的每个人代表同一问题的一个较小实例：确定前面的人数。通过解决这些较小的实例并结合它们的结果，整个问题得到了解决。这正是递归的工作原理。</p>
<h2 id="">递归的技术细节</h2>
<p>在编码递归时最重要的是要找出：</p>
<ul>
<li>
<p><strong>递归情况</strong>：我们可以做的最小工作。在上面的例子中，询问你前面的人他们前面有多少人是我们可以做的最小工作。</p>
</li>
<li>
<p><strong>基本情况</strong>：无需工作的条件。在上面的例子中，队伍最前面的人没有必要问任何问题，因此这是无需工作的条件。</p>
</li>
</ul>
<h2 id="">递归的简单例子</h2>
<p>计算阶乘是递归的最简单例子，它将真正帮助你理解其工作原理。</p>
<p>有很多方法可以计算一个数的阶乘。但在这里，我们将看到递归的方式来找到它。</p>
<p>在思考我们如何做到这一点之前，我们需要知道一个数的阶乘是什么。</p>
<p>一个数的阶乘是从 <strong>1</strong> 到该数的所有数字的乘积。</p>
<p>例如，<strong>5</strong> 的阶乘是 <strong>120</strong> —— 即 <strong>5</strong>×<strong>4</strong>×<strong>3</strong>×<strong>2</strong>×<strong>1</strong>。</p>
<p>我们还可以用数学方式表示如下：</p>
<p><code>5×(5−1)!</code></p>
<p>这意味着如果我们知道 <code>(5−1)!</code> 的值，我们就可以通过简单地将 <strong>5</strong> 乘以它来轻松得到阶乘。</p>
<p>这就是我们如何找到 <strong>4</strong>、<strong>3</strong>、<strong>2</strong>、<strong>1</strong> 和 <strong>0</strong> 的阶乘：</p>
<pre><code>Factorial of 4 = 4×(4−1)!
Factorial of 3 = 3×(3−1)!
Factorial of 2 = 2×(2−1)!
Factorial of 1 = 1
Factorial of 0 = 1
</code></pre>
<p>通过观察这些，很明显要找到 <strong>5</strong> 的阶乘，我们必须将 <strong>5</strong> 乘以 <code>4!</code>。</p>
<h3 id="">更普遍的例子</h3>
<p>要找到 <code>n</code> 的阶乘，我们需要将 <code>n</code> 乘以 <code>(n−1)!</code>。这就是你需要递归执行的过程。</p>
<p>现在，必须为递归设置一个停止条件。停止条件是我们不再执行其他操作的地方。当 <code>n</code> 是 <strong>1</strong> 或 <strong>0</strong> 时，我们可以简单地停止递归，因为这些值的阶乘是已知的。我们可以简单地说 <strong>1</strong> 的阶乘是 <strong>1</strong>，对于 <strong>0</strong> 也是如此。</p>
<p>因此，分解下来，要找到 n 的阶乘，所需做的最小工作量是 <code>n×(n−1)!</code>。当我们找到 <strong>1</strong> 或 <strong>0</strong> 的阶乘时，我们可以停止对它进行操作。</p>
<p>让我们看看代码是怎样的：</p>
<pre><code># 计算 n 的阶乘
def fact(n):
</code></pre>
<pre><code class="language-markdown"># 最少工作量
return n * fact(n - 1)

n = 5

# 计算阶乘
factorial = fact(n)
print(factorial)
</code></pre>
<p><strong>输出:</strong></p>
<pre><code>120
</code></pre>
<p>让我们看看它是如何工作的：</p>
<p>在第一次函数调用中，计算了 <strong>5</strong> 的阶乘。接着在第二次调用中，计算了 <strong>4</strong> 的阶乘，以此类推，直到计算 <strong>2</strong> 的阶乘。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-1--5-.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>递归计算 5 的阶乘</em></p>
<p>在调用 <strong>2</strong> 的阶乘时，我们有<code>2×fact(2−1)</code>，即<code>2×fact(1)</code>。</p>
<p>这达到了我们的基本条件。因此，递归停止，<code>2×fact(1)</code>返回<code>2×1</code>给前一个函数调用，并且结果从堆栈中弹出。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-3.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>第四次函数调用返回 2 给前一个函数调用并从堆栈中弹出</em></p>
<p>类似地，这里是其他内容的计算方式：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-4.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>第三次函数调用返回 6 给前一个函数调用并从堆栈中弹出</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-5.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>第二次函数调用返回 24 给前一个函数调用并从堆栈中弹出</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-6.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>第一次函数调用返回 120 给初始函数调用并从堆栈中弹出</em></p>
<p>所以函数最终将值 <strong>120</strong> 返回给初始函数调用。</p>
<h3 id="">为什么我们需要一个基本条件？</h3>
<p>在上面的例子中，我们为代码使用了停止条件。但是如果我们不添加停止条件，或者我们编写的函数永远不满足停止条件呢？</p>
<p>代码会永远运行吗？</p>
<p>不会 – 即使你不终止，代码也不会永远运行。让我们通过一个例子来理解为什么会这样。</p>
<pre><code>def print_five():
    print(5)

    # 调用自身
    print_five()

# 函数调用
print_five()
</code></pre>
<p><strong>输出:</strong></p>
<pre><code>5
5
5
...

RecursionError: maximum recursion depth exceeded
</code></pre>
<p>如果运行上面的代码，你会看到函数不会永远运行，并以消息 <code>RecursionError: maximum recursion depth exceeded</code> 结束。</p>
<p>当一个函数被调用时，它会被存储在一个调用堆栈中。下面是函数<code>print_five()</code>第一次被调用时在调用堆栈中的存储方式。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/How-many--6-.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>第一次函数调用时的调用堆栈</em></p>
<p>函数一次又一次地调用自身，并且每次调用时，函数被存储在调用堆栈中。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/How-many--8-.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>n 次函数调用后的调用堆栈</em></p>
<p>但是调用堆栈的大小是有限的，不能存储无限数量的函数。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/How-many--7-.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>调用堆栈空间不足导致栈溢出</em></p>
<p>当堆栈已满时，它无法再容纳更多调用，导致栈溢出错误。</p>
<p>因此，基本条件对于防止此类错误并确保递归正确终止是必不可少的。</p>
<p>现在让我们看看另一个例子，以便更深入地理解递归。</p>
<h2 id="">如何检查一个单词是否是回文</h2>
<p>在深入代码之前，你应当知道什么是回文。回文是指正向和反向读取都相同的单词。</p>
<p>例如，<code>racecar</code>正向和反向读取都是相同的。</p>
<p>要检查一个单词是否是回文，我们需要检查第一个和最后一个字母是否相同。如果相同，我们接着检查第二个和倒数第二个字母是否相同。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-7.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>检查 racecar 的第一个和最后一个字符是否相同</em></p>
<p>在<code>racecar</code>这一例子中，第一个和最后一个字母是相同的，所以我们检查第二个和倒数第二个字母是否相同。它们相同，所以现在我们检查第三个和倒数第三个字母是否相同。现在只剩下一个字母需要检查。一个单一字母始终是回文，因为它正反读都是相同的。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-8.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>如何检查 racecar 是否是回文</em></p>
<p>所以，现在让我们尝试以递归的方式思考，包括最少的工作量以及确定何时不需要工作。</p>
<h3 id="">最少工作量</h3>
<p>检查第一个和最后一个字母是否相同，如果相同，则移除单词中的第一个和最后一个字母。</p>
<h3 id="">不需要工作</h3>
<p>当只剩下一个或没有字母时，我们可以简单地说它是一个回文。</p>
<p>现在，让我们看看代码是什么样子的：</p>
<pre><code># 检查回文
def check_palindrome(text):

    # 停止条件
    # 如果文本长度是 1 或 0，返回真
    if len(text) == 0 or len(text) == 1:
        return True

    # 最少工作量
    # 检查第一个和最后一个字符是否相同
    # 如果相同，移除字符串中的第一个和最后一个字符
    if(text[0]==text[-1]):
        return(check_palindrome(text[1:-1]))
</code></pre>
<pre><code class="language-markdown"># 检查字符串是否为回文
text = "racecar"
is_palindrome = check_palindrome(text)
print(is_palindrome)
</code></pre>
<p>以下是上述代码的工作原理：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Frame-9.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>检查 racecar 是否为回文</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/checkpalindrome.png" alt="图像" width="600" height="400" loading="lazy"></p>
<p><em>检查 racecar 是否为回文</em></p>
<h2 id="">何时使用递归</h2>
<p>递归看似优雅且简单，但由于反复向堆栈添加方法带来的 CPU 开销，即便是简单问题常也需经过多个步骤方能解决。因此，在使用之前，请确保仔细考虑它是否为解决问题的正确方案。</p>
<p>当代码需要多个循环且显得混乱时，递归可以提供更清晰的解决方案。然而，是否使用递归取决于具体的代码以及所涉及的数据类型或数据结构。对于树和图这类数据结构，递归尤其有用。</p>
<p>尽管递归表面简单，但可能难以理解，即使处理简单问题也可能需要多个步骤。因此，再次提醒，一定要根据具体情况考虑使用。</p>
<h2 id="">总结</h2>
<p>这只是递归的入门介绍。递归的应用场景很多，你可能对其工作原理感到困惑。我将在下一篇文章中讨论更多递归的高级示例。</p>
<p>顺便说一下，以下是我在学习递归过程中找到的简单且有用的资源：</p>
<ul>
<li>
<p><a href="https://www.youtube.com/watch?v=IJDJ0kBx2LM&amp;t=657s">freeCodeCamp 的递归视频</a>：我必须感谢 freeCodeCamp，他们优秀的递归视频课程在我写这篇文章时给我了极大的启发。</p>
</li>
<li>
<p><a href="https://programiz.pro/course/learn-recursion-with-python">Programiz Pro 的递归课程</a>：另一个好资源是 Programiz 的递归课程。这是一个高级课程，需付费，但它经过精心设计。而且，你可以直接在他们的平台上进行练习，真的是很值得。</p>
</li>
</ul>
<p>无论你从哪里学习，都不要花太多时间寻找完美的资源。掌握概念并开始练习——这是你唯一能真正学会的方法。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 什么是进程 ID？如何使用 PID 进行进程管理 ]]>
                </title>
                <description>
                    <![CDATA[ 你是否曾经想过，当多个程序同时运行时，计算机如何知道显示哪个程序的输出？这一切都归功于进程 ID (PID)。 PID 是一个唯一标识符，帮助操作系统跟踪和管理运行中的程序。 在本文中，我们将探讨什么是进程 ID (PID)、它为何重要，以及如何使用它来管理进程，包括在必要时终止程序。 理解 PID：计算机如何识别正在运行的程序 让我们考虑两个 Python 脚本：  * hello_maham.py → print("Hello Maham")          * hello_amna.py → print("Hello Amna")         计算机如何知道 hello_maham.py 应该显示输出 "Hello Maham" 而不是来自 hello_amna.py 的 "Hello ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-is-a-process-id-process-management-tutorial/</link>
                <guid isPermaLink="false">67a3664c413508045ddca14f</guid>
                
                    <category>
                        <![CDATA[ 系统管理 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 05 Feb 2025 11:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/02/1738762164263.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-is-a-process-id-process-management-tutorial/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What is a Process ID? How to Use PIDs for Process Management</a>
      </p><!--kg-card-begin: markdown--><p>你是否曾经想过，当多个程序同时运行时，计算机如何知道显示哪个程序的输出？这一切都归功于进程 ID (PID)。</p>
<p>PID 是一个唯一标识符，帮助操作系统跟踪和管理运行中的程序。</p>
<p>在本文中，我们将探讨什么是进程 ID (PID)、它为何重要，以及如何使用它来管理进程，包括在必要时终止程序。</p>
<h2 id="pid">理解 PID：计算机如何识别正在运行的程序</h2>
<p>让我们考虑两个 Python 脚本：</p>
<ul>
<li>
<p><code>hello_maham.py</code> → <code>print("Hello Maham")</code></p>
</li>
<li>
<p><code>hello_amna.py</code> → <code>print("Hello Amna")</code></p>
</li>
</ul>
<p>计算机如何知道 <code>hello_maham.py</code> 应该显示输出 "Hello Maham" 而不是来自 <code>hello_amna.py</code> 的 "Hello Amna"？</p>
<p>如果你认为这只是魔法般地发生，那就再想想吧！这都要归功于一个叫做 <strong>进程 ID (PID)</strong> 的东西。</p>
<p>在任何操作系统中，进程都在后台不停地运行以执行任务。无论是你手动启动的程序还是自动运行的系统任务，这些进程都被分配了一个唯一的 PID。</p>
<p>让我们进一步解析这个过程。</p>
<h2 id="idpid">什么是进程 ID（PID）</h2>
<p>简单来说，</p>
<blockquote>
<p><em>一个</em> <strong><em>进程 ID (PID)</em></strong> <em>是分配给操作系统中每个运行进程的唯一标识符。</em></p>
</blockquote>
<p>让我们来了解后台发生了什么。</p>
<p>每当一个程序运行时，无论是什么语言，它都需要内存和时间来执行。因此，当你运行一个程序时，操作系统会为它创建一个新的进程。为了识别这个程序，计算机会为它分配一个独特的标识符——<strong>进程 ID</strong>——然后开始执行。</p>
<p>让我们再看看之前的例子：</p>
<ul>
<li>
<p>当你运行 <code>hello_maham.py</code> 时，系统为它分配一个唯一的 PID。</p>
</li>
<li>
<p>同样，当你运行 <code>hello_amna.py</code> 时，它也会有自己独特的 PID。</p>
</li>
</ul>
<p>这就是为什么两个脚本的输出不会重叠！</p>
<p>现在明白了吗？每当创建一个新进程时，系统会确保每个进程获得不同的 PID。系统使用这个 PID 来管理和与进程交互。这就叫做 <strong>PID 的唯一性</strong>。</p>
<h3 id="id">计算机如何处理这个 ID</h3>
<p>你可能会想，计算机是否拥有数百万个 PID？毕竟，我们可能同时运行很多程序。</p>
<p>答案是<strong>否</strong>。一旦一个进程结束，它的 PID 就可以被再次使用。这意味着 PID 是可重复使用的，并且不会短缺。</p>
<h3 id="pid">但为什么以及何时需要 PID</h3>
<p>现在你知道了 PID 是什么，你可能会想：<strong>我为什么需要这个？</strong></p>
<p>实际上，PID 对于系统管理员和开发人员非常有用。它们有助于：</p>
<p><strong>系统管理员和开发人员管理进程</strong>。例如，如果某个东西不正常工作，你需要能够找到并停止引起问题的具体进程，对吧？</p>
<p>PID 对于<strong>资源管理</strong>也很重要。操作系统使用它们来为每个进程分配内存和 CPU 时间，以确保没有一个程序占用所有资源。</p>
<h2 id="pid">如何查找正在运行程序的 PID</h2>
<p>到目前为止，我们已经讲解了理论概念。现在，你可能会想：我如何在计算机上找到一个程序的 PID？</p>
<p>以下是一些使用终端中各种命令查找正在运行程序的 <strong>PID</strong> 的简单方法。</p>
<p>注意，我使用的是 Bash 来执行 PID 命令，并附上了一个截图。但对于其他终端如 CMD 和 PowerShell，相关命令将在最后提到。</p>
<p>其中一些方法包括：</p>
<h3 id="1ps">1. 使用 <code>ps</code> 命令</h3>
<p><code>ps</code> 命令显示当前正在运行的进程及其 PID 的快照。</p>
<pre><code>ps aux | grep &lt;program_name&gt;
</code></pre>
<p>这里是一个例子：</p>
<pre><code>ps aux | grep python
</code></pre>
<p>此命令将显示系统上所有正在运行的 Python 进程及其 PID。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737914130884/686568b8-77d5-439d-a08b-61e18d4e5d56.png" alt="运行的 Python 进程及其 PID" width="600" height="400" loading="lazy"></p>
<h3 id="2top">2. 使用 <code>top</code> 命令</h3>
<p><code>top</code> 命令显示系统上正在运行的进程的实时信息，包括它们的 PID。</p>
<pre><code>top
</code></pre>
<p>在 <strong>PID</strong> 列下查看你感兴趣的进程。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737914189672/43f93890-2e06-42a8-b61b-9103227cf912.png" alt="运行 top 命令的结果" width="600" height="400" loading="lazy"></p>
<h2 id="pid">如何使用 PID 终止进程</h2>
<p>如果你想<strong>终止</strong>程序怎么办？无论是 cron 作业还是行为异常或运行时间过长的程序，如何使用 PID 停止它？</p>
<p>让我们了解一下如何做到这一点：</p>
<h3 id="1kill">1. 使用 <code>kill</code> 命令</h3>
<p>要终止进程，请使用 <code>kill</code> 命令和 PID：</p>
<pre><code>kill &lt;PID&gt;
</code></pre>
<p>这里是一个例子：</p>
<pre><code>kill 1234
</code></pre>
<h3 id="2kill9">2. 使用 <code>kill -9</code> 命令（强制终止）</h3>
<p>如果在使用常规 <code>kill</code> 命令之后进程仍未停止，可以使用 <code>kill -9</code> 命令强制终止它：</p>
<pre><code>kill -9 &lt;PID&gt;
</code></pre>
<p>这是一个例子：</p>
<pre><code>kill -9 1234
</code></pre>
<p>这会强制终止进程并绕过其可能有的任何关闭程序，因此请务必谨慎使用。</p>
<h2 id="pidcron">如何使用 PID 停止 cron 作业——实际示例</h2>
<p>假设你有一个 cron 作业正在运行，并且出现了问题。你该如何停止它？</p>
<p>cron 作业是指按特定时间间隔自动运行的计划任务。</p>
<p>如果你需要停止正在运行的 cron 作业，可以使用运行该 cron 作业的进程的 PID。</p>
<h3 id="cron">停止正在运行的 cron 作业</h3>
<p>如果你需要停止正在运行的 cron 作业，可以使用运行该 cron 作业的进程的 PID。</p>
<p>以下是终止 cron 作业的方法：</p>
<ol>
<li>
<p><strong>查找 PID</strong>：使用 <code>ps</code> 或 <code>pgrep</code> 命令查找 cron 作业的 PID。例子：</p>
<pre><code>ps aux | grep cron
</code></pre>
</li>
<li>
<p><strong>终止 Cron 作业</strong>：找到 cron 作业的 PID 后，使用 <code>kill</code> 或 <code>kill -9</code> 命令停止它。</p>
</li>
</ol>
<h2 id="">其他终端的命令</h2>
<p>以下是在不同终端中管理进程的方法：</p>
<table>
<thead>
<tr>
<th>操作</th>
<th>CMD 命令</th>
<th>PowerShell 命令</th>
<th>Bash 命令</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>列出所有进程</strong></td>
<td><code>tasklist</code></td>
<td><code>Get-Process</code></td>
<td><code>ps aux</code></td>
</tr>
<tr>
<td><strong>按名称查找进程</strong></td>
<td><code>tasklist</code></td>
<td><code>findstr &lt;name&gt;</code></td>
<td><code>Get-Process</code></td>
</tr>
<tr>
<td><strong>通过 PID 终止进程</strong></td>
<td><code>taskkill /PID &lt;PID&gt;</code></td>
<td><code>Stop-Process -Id &lt;PID&gt;</code></td>
<td><code>kill &lt;PID&gt;</code></td>
</tr>
<tr>
<td><strong>强制终止进程</strong></td>
<td><code>taskkill /F /PID &lt;PID&gt;</code></td>
<td><code>Stop-Process -Id &lt;PID&gt; -Force</code></td>
<td><code>kill -9 &lt;PID&gt;</code></td>
</tr>
</tbody>
</table>
<h2 id="">小结</h2>
<p>理解<strong>进程 ID</strong> 是管理计算机上进程的关键。通过简单的命令，你可以轻松找到并停止有问题的进程，确保系统顺畅运行。</p>
<p>通过使用 PID 来控制你的系统吧！</p>
<p><strong>保持联系 — @syedamahamfahim 🐬</strong></p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 子网检查单 - 24 子网掩码、30、26、27、29 和其他 IP 地址 CIDR 网络参考资料 ]]>
                </title>
                <description>
                    <![CDATA[ 作为开发人员或网络工程师，你可能需要时不时查阅子网掩码值并弄清其含义。 为了让你的生活更轻松，freeCodeCamp 社区制作了这个简单的备忘单。只需滚动或使用 Ctrl/Cmd + F 查找你要查找的值即可。 CIDR子网掩码通配符掩码IP 地址数量可用 IP 地址数量/32255.255.255.2550.0.0.011/31255.255.255.254 0.0.0.122*/30255.255.255.2520.0.0.342/29255.255.255.2480.0.0.786/28 255.255.255.2400.0.0.151614/27255.255.255.2240.0.0.313230/26255.255.255.192 0.0.0.636462/25255.255.255.1280.0.0.127128126/24255.255.255.00.0.0.255256254/23 255.255.254.00.0.1.255512510/22255.255.252.00.0.3.2551,0241,022/21255.255.248.0 0.0.7.2552, ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/subnet-cheat-sheet-24-subnet-mask-30-26-27-29-and-other-ip-address-cidr-network-references/</link>
                <guid isPermaLink="false">66835c6d9100b5049d88a6f8</guid>
                
                    <category>
                        <![CDATA[ 计算机网络 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Mon, 13 Jan 2025 08:59:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/10/5f9c9647740569d1a4ca10a9.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/subnet-cheat-sheet-24-subnet-mask-30-26-27-29-and-other-ip-address-cidr-network-references/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Subnet Cheat Sheet – 24 Subnet Mask, 30, 26, 27, 29, and other IP Address CIDR Network References</a>
      </p><!--kg-card-begin: markdown--><p>作为开发人员或网络工程师，你可能需要时不时查阅子网掩码值并弄清其含义。</p>
<p>为了让你的生活更轻松，freeCodeCamp 社区制作了这个简单的备忘单。只需滚动或使用 Ctrl/Cmd + F 查找你要查找的值即可。</p>
<table>
<thead>
<tr>
<th>CIDR</th>
<th>子网掩码</th>
<th>通配符掩码</th>
<th>IP 地址数量</th>
<th>可用 IP 地址数量</th>
</tr>
</thead>
<tbody>
<tr>
<td>/32</td>
<td>255.255.255.255</td>
<td>0.0.0.0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>/31</td>
<td>255.255.255.254</td>
<td>0.0.0.1</td>
<td>2</td>
<td>2*</td>
</tr>
<tr>
<td>/30</td>
<td>255.255.255.252</td>
<td>0.0.0.3</td>
<td>4</td>
<td>2</td>
</tr>
<tr>
<td>/29</td>
<td>255.255.255.248</td>
<td>0.0.0.7</td>
<td>8</td>
<td>6</td>
</tr>
<tr>
<td>/28</td>
<td>255.255.255.240</td>
<td>0.0.0.15</td>
<td>16</td>
<td>14</td>
</tr>
<tr>
<td>/27</td>
<td>255.255.255.224</td>
<td>0.0.0.31</td>
<td>32</td>
<td>30</td>
</tr>
<tr>
<td>/26</td>
<td>255.255.255.192</td>
<td>0.0.0.63</td>
<td>64</td>
<td>62</td>
</tr>
<tr>
<td>/25</td>
<td>255.255.255.128</td>
<td>0.0.0.127</td>
<td>128</td>
<td>126</td>
</tr>
<tr>
<td>/24</td>
<td>255.255.255.0</td>
<td>0.0.0.255</td>
<td>256</td>
<td>254</td>
</tr>
<tr>
<td>/23</td>
<td>255.255.254.0</td>
<td>0.0.1.255</td>
<td>512</td>
<td>510</td>
</tr>
<tr>
<td>/22</td>
<td>255.255.252.0</td>
<td>0.0.3.255</td>
<td>1,024</td>
<td>1,022</td>
</tr>
<tr>
<td>/21</td>
<td>255.255.248.0</td>
<td>0.0.7.255</td>
<td>2,048</td>
<td>2,046</td>
</tr>
<tr>
<td>/20</td>
<td>255.255.240.0</td>
<td>0.0.15.255</td>
<td>4,096</td>
<td>4,094</td>
</tr>
<tr>
<td>/19</td>
<td>255.255.224.0</td>
<td>0.0.31.255</td>
<td>8,192</td>
<td>8,190</td>
</tr>
<tr>
<td>/18</td>
<td>255.255.192.0</td>
<td>0.0.63.255</td>
<td>16,384</td>
<td>16,382</td>
</tr>
<tr>
<td>/17</td>
<td>255.255.128.0</td>
<td>0.0.127.255</td>
<td>32,768</td>
<td>32,766</td>
</tr>
<tr>
<td>/16</td>
<td>255.255.0.0</td>
<td>0.0.255.255</td>
<td>65,536</td>
<td>65,534</td>
</tr>
<tr>
<td>/15</td>
<td>255.254.0.0</td>
<td>0.1.255.255</td>
<td>131,072</td>
<td>131,070</td>
</tr>
<tr>
<td>/14</td>
<td>255.252.0.0</td>
<td>0.3.255.255</td>
<td>262,144</td>
<td>262,142</td>
</tr>
<tr>
<td>/13</td>
<td>255.248.0.0</td>
<td>0.7.255.255</td>
<td>524,288</td>
<td>524,286</td>
</tr>
<tr>
<td>/12</td>
<td>255.240.0.0</td>
<td>0.15.255.255</td>
<td>1,048,576</td>
<td>1,048,574</td>
</tr>
<tr>
<td>/11</td>
<td>255.224.0.0</td>
<td>0.31.255.255</td>
<td>2,097,152</td>
<td>2,097,150</td>
</tr>
<tr>
<td>/10</td>
<td>255.192.0.0</td>
<td>0.63.255.255</td>
<td>4,194,304</td>
<td>4,194,302</td>
</tr>
<tr>
<td>/9</td>
<td>255.128.0.0</td>
<td>0.127.255.255</td>
<td>8,388,608</td>
<td>8,388,606</td>
</tr>
<tr>
<td>/8</td>
<td>255.0.0.0</td>
<td>0.255.255.255</td>
<td>16,777,216</td>
<td>16,777,214</td>
</tr>
<tr>
<td>/7</td>
<td>254.0.0.0</td>
<td>1.255.255.255</td>
<td>33,554,432</td>
<td>33,554,430</td>
</tr>
<tr>
<td>/6</td>
<td>252.0.0.0</td>
<td>3.255.255.255</td>
<td>67,108,864</td>
<td>67,108,862</td>
</tr>
<tr>
<td>/5</td>
<td>248.0.0.0</td>
<td>7.255.255.255</td>
<td>134,217,728</td>
<td>134,217,726</td>
</tr>
<tr>
<td>/4</td>
<td>240.0.0.0</td>
<td>15.255.255.255</td>
<td>268,435,456</td>
<td>268,435,454</td>
</tr>
<tr>
<td>/3</td>
<td>224.0.0.0</td>
<td>31.255.255.255</td>
<td>536,870,912</td>
<td>536,870,910</td>
</tr>
<tr>
<td>/2</td>
<td>192.0.0.0</td>
<td>63.255.255.255</td>
<td>1,073,741,824</td>
<td>1,073,741,822</td>
</tr>
<tr>
<td>/1</td>
<td>128.0.0.0</td>
<td>127.255.255.255</td>
<td>2,147,483,648</td>
<td>2,147,483,646</td>
</tr>
<tr>
<td>/0</td>
<td>0.0.0.0</td>
<td>255.255.255.255</td>
<td>4,294,967,296</td>
<td>4,294,967,294</td>
</tr>
</tbody>
</table>
<ul>
<li>/31 是 <a href="https://datatracker.ietf.org/doc/html/rfc3021">RFC 3021</a> 中详细说明的一种特殊情况，这种子网掩码类型的网络可以分配两个 IP 地址作为点对点链路。</li>
</ul>
<p>以下是子网掩码和通配符的十进制到二进制的转换表：</p>
<table>
<thead>
<tr>
<th></th>
<th>子网掩码</th>
<th></th>
<th>通配符</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>00000000</td>
<td>255</td>
<td>11111111</td>
</tr>
<tr>
<td>128</td>
<td>10000000</td>
<td>127</td>
<td>01111111</td>
</tr>
<tr>
<td>192</td>
<td>11000000</td>
<td>63</td>
<td>00111111</td>
</tr>
<tr>
<td>224</td>
<td>11100000</td>
<td>31</td>
<td>00011111</td>
</tr>
<tr>
<td>240</td>
<td>11110000</td>
<td>15</td>
<td>00001111</td>
</tr>
<tr>
<td>248</td>
<td>11111000</td>
<td>7</td>
<td>00000111</td>
</tr>
<tr>
<td>252</td>
<td>11111100</td>
<td>3</td>
<td>00000011</td>
</tr>
<tr>
<td>254</td>
<td>11111110</td>
<td>1</td>
<td>00000001</td>
</tr>
<tr>
<td>255</td>
<td>11111111</td>
<td>0</td>
<td>00000000</td>
</tr>
</tbody>
</table>
<p>注意，通配符只是子网掩码的反码。</p>
<p>如果你是网络工程的新手，你可以<a href="https://www.freecodecamp.org/news/computer-networks-and-how-to-actually-understand-them-c1401908172d/">在这里更好地了解计算机网络的工作原理</a>。</p>
<p>最后，这张速查表和本文的其余部分重点介绍的是 IPv4 地址，而不是较新的 IPv6 协议。如果你想了解更多关于 IPv6 的信息，请查看上面关于计算机网络的文章。</p>
<h2 id="ip">IP 地址块如何工作？</h2>
<p>像 <code>192.168.0.1</code> 这样的 IPv4 地址实际上只是四个二进制块的十进制表示。</p>
<p>每个块是 8 位，表示从 0-255 的数字。由于这些块是 8 位的组，每个块被称为一个<strong>八位</strong>。由于有四个 8 位的块，每个 IPv4 地址共有 32 位。</p>
<p>例如，以下是 IP 地址 <code>172.16.254.1</code> 的二进制格式：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/1125px-Ipv4_address.png" alt="1125px-Ipv4_address" width="600" height="400" loading="lazy"></p>
<p>来源：<a href="https://www.wikiwand.com/en/articles/IPv4">IPv4</a></p>
<p>要将 IP 地址在十进制和二进制形式之间转换，可以使用下图：</p>
<table>
<thead>
<tr>
<th>128</th>
<th>64</th>
<th>32</th>
<th>16</th>
<th>8</th>
<th>4</th>
<th>2</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
</tr>
</tbody>
</table>
<p>上图表示一个 8 位的八位字节。</p>
<p>现在假设你要转换 IP 地址 <code>168.210.225.206</code>。你只需要将地址分成四块（<code>168</code>、<code>210</code>、<code>225</code> 和 <code>206</code>），并且使用上图将每一块转换为二进制。</p>
<p>记住在二进制中，1 等同于“开”，0 等同于“关”。所以要将第一个块 <code>168</code> 转换为二进制，只需从图表的开头开始，在单元格中放置 1 或 0，直到总和为 <code>168</code>。</p>
<p>例如：</p>
<table>
<thead>
<tr>
<th>128</th>
<th>64</th>
<th>32</th>
<th>16</th>
<th>8</th>
<th>4</th>
<th>2</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>128 + 32 + 8 = 168，在二进制中是 <code>10101000</code>。</p>
<p>如果对其余的块进行同样的操作，你会得到 <code>10101000.11010010.11100001.11001110</code>。</p>
<p>如果你看上面的表格，可能会觉得 IP 地址的数量几乎是无限的。毕竟，IPv4 地址有将近42亿个可用地址。</p>
<p>但是，考虑到互联网的发展，以及如今有多少设备连接到网络，当你听到已经存在 <a href="https://whatismyipaddress.com/ipv4-shortage">IPv4 地址短缺</a>，可能也不会感到惊讶。</p>
<p>因为开发人员多年前就已经意识到了短缺问题，他们想出了一个方法，将一个 IP 地址拆分成更小的网络，叫作“子网”。</p>
<p>这个过程叫作“子网划分”，使用 IP 地址的主机部分将其分解成更小的网络或子网。</p>
<p>通常，IP 地址由网络位和主机位组成：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/network-and-host-bits.png" alt="network-and-host-bits" width="600" height="400" loading="lazy"></p>
<p>来源：<a href="https://support.huawei.com/enterprise/en/doc/EDOC1100145159">什么是 IPv4</a></p>
<p>所以，一般来说，子网划分有两个作用：它为我们提供了一种将网络拆分成子网的方法，并允许设备确定另一个设备/IP 地址是否在同一个本地网络上。</p>
<p>在没有子网划分的情况下，每个连接到互联网的设备都需要一个独特的 IP 地址。</p>
<p>但由于你有一个无线路由器，你只需要一个为你的路由器分配的 IP 地址。这个公共或外部 IP 地址通常是自动处理的，并由你的互联网服务提供商（ISP）分配。</p>
<p>然后每个连接到该路由器的设备都有其自己的私有或内部 IP 地址：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/home-network-diagram.png" alt="home-network-diagram" width="600" height="400" loading="lazy"></p>
<p>来源：<a href="https://www.popularmechanics.com/technology/a32729384/how-to-find-ip-address/">我的 IP 地址是什么？</a></p>
<p>现在如果内部 IP 地址为 <code>192.168.1.101</code> 的设备想要与另一台设备通信，它将使用另一台设备的 IP 地址和子网掩码。</p>
<p>IP 地址和子网掩码的组合使得 <code>192.168.1.101</code> 设备能够判断其他设备是否在同一个网络中（例如 <code>192.168.1.103</code> 设备），或者是在互联网上其他地方的完全不同的网络中。</p>
<p>有趣的是，你的 ISP 为你的路由器分配的外部 IP 地址可能是某个子网的一部分，该子网可能包括附近家庭或企业的许多其他 IP 地址。和内部 IP 地址一样，它也需要一个子网掩码才能工作。</p>
<p>子网掩码可以被视为 IP 地址的一种过滤器。通过子网掩码，设备可以查看 IP 地址，并弄清楚哪些部分是网络位，哪些部分是主机位。</p>
<p>然后利用这些信息，设备可以确定最佳的通信方式。</p>
<p>如果你曾浏览过路由器或计算机上的网络设置，你可能见过这个数字：<code>255.255.255.0</code>。</p>
<p>如果见过，你已经看到了一个非常常见的适用于简单家庭网络的子网掩码。</p>
<p>像 IPv4 地址一样，子网掩码也是 32 位。就像将 IP 地址转换为二进制一样，你也可以对子网掩码进行相同的转换。</p>
<p>例如，这里是我们之前的图表：</p>
<table>
<thead>
<tr>
<th>128</th>
<th>64</th>
<th>32</th>
<th>16</th>
<th>8</th>
<th>4</th>
<th>2</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>x</td>
</tr>
</tbody>
</table>
<p>现在让我们转换第一个八比特，255：</p>
<table>
<thead>
<tr>
<th>128</th>
<th>64</th>
<th>32</th>
<th>16</th>
<th>8</th>
<th>4</th>
<th>2</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>很简单，对吧？所以任何一个八比特中的 <code>255</code> 在二进制中就是 <code>11111111</code>。这意味着 <code>255.255.255.0</code> 在二进制中实际上是 <code>11111111.11111111.11111111.00000000</code>。</p>
<p>以下是这两者的十进制和二进制表示：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>十进制</th>
<th>二进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>IP 地址</td>
<td>192.168.0.101</td>
<td>11000000.10101000.00000000.01100101</td>
</tr>
<tr>
<td>子网掩码</td>
<td>255.255.255.0</td>
<td>11111111.11111111.11111111.00000000</td>
</tr>
</tbody>
</table>
<p>这样排列后，很容易将 <code>192.168.0.101</code> 分成网络位和主机位。</p>
<p>只要二进制子网掩码中的某位为 1，二进制 IP 地址中的相同位就是网络的一部分，而不是主机的一部分。</p>
<p>由于八比特 <code>255</code> 的二进制形式是 <code>11111111</code>，所以 IP 地址中的整个八比特都是网络的一部分。因此，前三个八比特 <code>192.168.0</code> 是 IP 地址的网络部分，而 <code>101</code> 是主机部分。</p>
<p>换句话说，如果设备 <code>192.168.0.101</code> 想与另一个设备进行通信，使用子网掩码它知道任何IP地址为 <code>192.168.0.xxx</code> 的设备都在同一个本地网络上。</p>
<p>另一种表达方式是使用网络ID，它只是IP地址的网络部分。所以IP地址 <code>192.168.0.101</code> 的网络ID，子网掩码为 <code>255.255.255.0</code>，即 <code>192.168.0.0</code>。</p>
<h3 id="cidrcidr">CIDR 是什么及 CIDR 表示法？</h3>
<p><strong>CIDR</strong> 是无类域间路由的缩写，使用于 IPv4，最近也用于 IPv6 路由。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/1920px-IP_Address_Match.svg.png" alt="1920px-IP_Address_Match.svg" width="600" height="400" loading="lazy"></p>
<p>来源：<a href="https://www.wikiwand.com/zh/articles/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1">无类别域间路由</a></p>
<p>CIDR 于 1993 年引入，作为缓解 IPv4 地址消耗的一种方式。在此之前，互联网使用的是旧有的分类 IP 地址系统，该系统导致 IPv4 地址迅速枯竭。</p>
<p>CIDR 涵盖了几个主要概念。</p>
<p>第一个是可变长度子网掩码（VLSM），这基本上允许网络工程师在子网内创建子网。那些子网可以有不同的大小，从而减少了未使用的 IP 地址数量。</p>
<p>CIDR 引入的第二个主要概念是 CIDR 表示法。</p>
<p>CIDR 表示法实际上只是子网掩码的简写，表示 IP 地址可用的位数。例如，<code>192.168.0.101/24</code> 中的 <code>/24</code> 相当于 IP 地址 <code>192.168.0.101</code> 和子网掩码 <code>255.255.255.0</code>。</p>
<h3 id="cidr">CIDR 的计算方法</h3>
<p>要计算子网掩码的 CIDR 表示法，只需将子网掩码转换为二进制，然后计算其中的 1 的数量即可。例如：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>十进制</th>
<th>二进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>子网掩码</td>
<td>255.255.255.0</td>
<td>11111111.11111111.11111111.00000000</td>
</tr>
</tbody>
</table>
<p>由于有三个八比特是 1，所以有 24 个 "on" 位，这意味着 CIDR 表示法为 <code>/24</code>。</p>
<p>你可以用任何一种方式书写，但我相信你会同意用 <code>/24</code> 比写 <code>255.255.255.0</code> 要简单很多。</p>
<p>这通常是在使用 IP 地址时进行的，所以让我们看看带有 IP 地址的相同子网掩码：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>十进制</th>
<th>二进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>IP 地址</td>
<td>192.168.0.101</td>
<td>11000000.10101000.00000000.01100101</td>
</tr>
<tr>
<td>子网掩码</td>
<td>255.255.255.0</td>
<td>11111111.11111111.11111111.00000000</td>
</tr>
</tbody>
</table>
<p>子网掩码的前三个八比特全是 "on" 位，这意味着 IP 地址的前三个八比特全是网络位。</p>
<p>让我们更详细地看看最后的第四个八比特：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>十进制</th>
<th>二进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>IP 地址</td>
<td>101</td>
<td>01100101</td>
</tr>
<tr>
<td>子网掩码</td>
<td>0</td>
<td>00000000</td>
</tr>
</tbody>
</table>
<p>在这种情况下，由于子网掩码中这个八比特的所有比特都是 “off”，我们可以确定 IP 地址中这个八比特的所有相应比特都是主机的一部分。</p>
<p>当你使用 CIDR 表示法时，通常会使用网络 ID。因此，IP 地址 <code>192.168.0.101</code> 和子网掩码 <code>255.255.255.0</code> 的 CIDR 表示法是 <code>192.168.0.0/24</code>。</p>
<p>若要查看更多关于如何计算给定 IP 地址和子网掩码的 CIDR 表示法和网络 ID 的示例，请观看以下视频：</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.17977528089888%;" class="fluid-width-video-wrapper">
            <iframe width="356" height="200" src="https://www.youtube.com/embed/XQ3T14SIlV4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" name="fitvid0"></iframe>
          </div>
        </div>
      </figure>
<h2 id="ip">IP 地址分类</h2>
<p>现在我们已经了解了一些子网划分和 CIDR 的基本示例，让我们放大来看一下所谓的“分类网络”。</p>
<p>在开发子网划分之前，所有的 IP 地址都属于特定的类别：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/subnetting.png" alt="subnetting" width="600" height="400" loading="lazy"></p>
<p>来源：<a href="https://community.spiceworks.com/t/subnetting-for-dummies/970210">简单子网划分教程</a></p>
<p>注意，这里还有 D 类和 E 类 IP 地址，但我们将在稍后详细介绍这些内容。</p>
<p>分类 IP 地址为网络工程师提供了一种为不同组织提供一系列有效 IP 地址的方法。</p>
<p>这个方法有很多问题，最终导致了子网划分的出现。但在我们深入探讨这些问题之前，让我们先仔细看看不同的 IP 地址类别。</p>
<h3 id="aip">A 类 IP 地址</h3>
<p>对于 A 类 IP 地址，前八位（8 位 / 1 字节）表示网络 ID，其余三段（24 位 / 3 字节）表示主机 ID。</p>
<p>A 类 IP 地址范围从 <code>1.0.0.0</code> 到 <code>127.255.255.255</code>，默认子网掩码为 <code>255.0.0.0</code>（或 CIDR 的 <code>/8</code>）。</p>
<p>这意味着 A 类地址可以有 128（2^7）个网络，每个网络有 16,777,214（2^24-2）个可用地址。</p>
<p>另外，请注意，在 A 类地址范围内，<code>127.0.0.0</code> 到 <code>127.255.255.255</code> 这一范围被保留用于主机循环地址（参见 <a href="https://datatracker.ietf.org/doc/html/rfc5735">RFC5735</a>）。</p>
<h3 id="bip">B 类 IP 地址</h3>
<p>对于 B 类 IP 地址，前两段（16 位 / 2 字节）表示网络 ID，其余两段（16 位 / 2 字节）表示主机 ID。</p>
<p>B 类 IP 地址范围从 <code>128.0.0.0</code> 到 <code>191.255.255.255</code>，默认子网掩码为 <code>255.255.0.0</code>（或 CIDR 的 <code>/16</code>）。</p>
<p>B 类寻址每个网络可有 16,384（2^14）个网络地址和 65,534（2^16）个可用地址。</p>
<h3 id="cip">C 类 IP 地址</h3>
<p>对于 C 类 IP 地址，前三个八位字节（24 位 / 3 字节）代表网络 ID，最后一个八位字节（8 位 / 1 字节）是主机 ID。</p>
<p>C 类 IP 地址的范围是从 <code>192.0.0.0</code> 到 <code>223.255.255.255</code>，默认子网掩码为 <code>255.255.255.0</code>（或 CIDR 记法中的 <code>/24</code>）。</p>
<p>C 类 IP 地址对应 2,097,152（2^21）个网络，每个网络有 254（2^8 - 2）个可用地址。</p>
<h3 id="deip">D 类和 E 类 IP 地址</h3>
<p>最后两类是 D 类和 E 类。</p>
<p>D 类 IP 地址保留用于多播。它们的范围是从 <code>224.0.0.0</code> 到 <code>239.255.255.255</code>。</p>
<p>E 类 IP 地址是实验性的，范围是 <code>240.0.0.0</code> 以上。</p>
<h3 id="ip">类划分 IP 地址的问题</h3>
<p>类划分 IP 地址的主要问题是不高效，会导致大量 IP 地址的浪费。</p>
<p>例如，设想你当时是一个大型组织的一部分。你的公司有 1,000 名员工，这意味着它属于 B 类。</p>
<p>但如果你往上看，你会发现 B 类网络可以支持多达 65,534 个可用地址。这远远超过了你的组织可能需要的数量，即使每个员工有多个带唯一地址的设备。</p>
<p>并且你的组织不可能退回到 C 类，因为没有足够的可用 IP 地址。</p>
<p>因此，当 IPv4 地址变得普及时，使用类 IP 地址的那段时间里，人们很快就意识到需要一个更好的系统来确保我们不会用光所有约 42 亿个可用地址。</p>
<p>自从类 IP 地址在 1993 年被 CIDR 取代后，就不再使用了，现在主要被研究用来理解早期互联网架构，以及为什么子网划分很重要。</p>
<h2 id="">希望这个速查表对你有所帮助</h2>
<p>如果你觉得这有帮助，请与朋友分享，让更多人从中受益。</p>
<p>此外，欢迎在 <a href="https://x.com/kriskoishigawa">Twitter</a> 上联系我，告诉我你的想法。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ freeCodeCamp 课程重大更新在圣诞节上线 ]]>
                </title>
                <description>
                    <![CDATA[ 圣诞快乐，campers！🎄 freeCodeCamp 刚刚发布了大量免费的学习工具，方便大家在假期里体验： 🏗️ 新的认证全栈开发者课程上线啦！ 🗨️ 新的开发者英语 CEFR B1 级别课程 🤳 我们的移动应用程序进行了重大更新 freeCodeCamp 认证全栈开发者路径 以下是全新的认证全栈开发者路径的完整内容，非常丰富： 🛠️ 64 个工作坊 – 涵盖从 HTML 到 SQL 到 Python 的互动式分步骤编程课程（现已上线 42 个） 🏛️ 513 堂讲座 – 涵盖计算机科学概念的短视频，包含测试你是否理解的选择题（现已上线 313 个） ⚗️ 83 个实验室 – 从空白编辑器和测试套件开始，你需要构建项目以通过所有测试（现已上线 60 个） 📰 62 份复习材料 – 每个模块的综合主题列表，帮助你复习并准备测验和考试（现已上线 46 份） 🔬 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/christmas-2025-freecodecamp-curriculum-updates/</link>
                <guid isPermaLink="false">676a4d346b2a470461e6e4f3</guid>
                
                    <category>
                        <![CDATA[ 开源 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 社区 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 24 Dec 2024 05:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/12/19f1c48b-0d23-4bdf-882b-9f008e288719.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/christmas-2025-freecodecamp-curriculum-updates/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Major freeCodeCamp Curriculum Updates Now Live in 2025</a>
      </p><!--kg-card-begin: markdown--><p>圣诞快乐，campers！🎄 freeCodeCamp 刚刚发布了大量免费的学习工具，方便大家在假期里体验：</p>
<p>🏗️ 新的认证全栈开发者课程上线啦！</p>
<p>🗨️ 新的开发者英语 CEFR B1 级别课程</p>
<p>🤳 我们的移动应用程序进行了重大更新</p>
<h2 id="freecodecamp">freeCodeCamp 认证全栈开发者路径</h2>
<p>以下是全新的认证全栈开发者路径的完整内容，非常丰富：</p>
<p>🛠️ 64 个工作坊 – 涵盖从 HTML 到 SQL 到 Python 的互动式分步骤编程课程（现已上线 42 个）</p>
<p>🏛️ 513 堂讲座 – 涵盖计算机科学概念的短视频，包含测试你是否理解的选择题（现已上线 313 个）</p>
<p>⚗️ 83 个实验室 – 从空白编辑器和测试套件开始，你需要构建项目以通过所有测试（现已上线 60 个）</p>
<p>📰 62 份复习材料 – 每个模块的综合主题列表，帮助你复习并准备测验和考试（现已上线 46 份）</p>
<p>🔬 66 个测验 – 确认你掌握全栈开发概念和工具（现已上线 46 个）</p>
<p>🏔️ 1 个终极项目 – 你将为自己的作品集构建一个大型独特的项目，此项目将由 freeCodeCamp 社区的教师进行评审（2025 年上线）</p>
<p>🤺 1 次期末考试 – 全面的监考式认证考试，共 90 个问题，将在 freeCodeCamp 的新开源考试环境中进行（2025 年上线）</p>
<p>即使你以前已经学习过其中的一些主题，我还是建议你从头开始学习。</p>
<p>你可以立即开始学习<a href="https://www.freecodecamp.org/learn/full-stack-developer/">新的认证全栈开发者课程</a>。</p>
<h2 id="cefrb1">开发者英语课程 CEFR B1 级别</h2>
<p>去年圣诞节，我们发布了 CEFR A2 级别的英语课程。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735002125376/5eec4303-900b-4423-980a-cfa68a2962a0.png" alt="5eec4303-900b-4423-980a-cfa68a2962a0" width="1024" height="768" loading="lazy"></p>
<p>经过一年的努力，我很高兴地告诉大家，我们不仅上线了所有内容，还推出了相当一部分 B1 级别课程。</p>
<p>认证考试也将在 2025 年上线。</p>
<p>你可以尝试我们的 <a href="https://www.freecodecamp.org/learn/b1-english-for-developers/">CEFR B1 级别开发者英语认证课程</a>。</p>
<h2 id="freecodecampiphoneandroid">freeCodeCamp iPhone 和 Android 应用程序</h2>
<p>freeCodeCamp 移动应用程序比之前更加出色。我们的 Flutter 代码库刚刚收到 1,000 个 commits！</p>
<p>你现在可以：</p>
<p>📺 观看我们的课程</p>
<p>📚 阅读我们的书籍</p>
<p>🤳 直接在手机上快速学习课程</p>
<p>在 iPhone/Android 上下载并留下五星好评。⭐️⭐️⭐️⭐️⭐️ 😉</p>
<p><a href="https://play.google.com/store/apps/details?id=org.freecodecamp&amp;hl=zh_CN">获取 Android 应用</a></p>
<p><a href="https://apps.apple.com/cn/app/freecodecamp/id6446908151">获取 iPhone 应用</a></p>
<h2 id="2025">2025 年将会很精彩。</h2>
<p>再次感谢所有为 freeCodeCamp 课程和我们的众多开源项目做出贡献的人们。</p>
<p>任何人都可以随时在论坛和 Discord 上获得 camper 们的帮助，这太棒了。</p>
<p>鼓励你的朋友参与进来。大家可以从<a href="https://contribute.freecodecamp.org">我们的开源贡献文档</a>开始。</p>
<p>最后，感谢所有捐款支持 freeCodeCamp 使命的人们。我们才刚刚开始。🥞</p>
<p>请鼓励你的朋友也来支持我们的慈善事业。我们共同努力，让每个人都能获得免费的世界级教育。你可以和全球 11,043 位慷慨的人们一起成为我们的<a href="https://www.freecodecamp.org/donate">支持者</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
