<!-- title: JWT认证：Token到OAuth2实战 -->

# Claude Code 认证鉴权实战：从 JWT 到 OAuth2.0 四步交付

> **学习目标**：掌握使用 Claude Code 从零构建完整的认证鉴权系统——涵盖密码哈希、JWT 签发验证、Refresh Token 轮转、OAuth2.0 授权码流程，以及 RBAC 权限控制。读完即可在项目中直接复用。
>
> **适用人群**：有 Node.js 基础、正在使用 Claude Code 进行后端开发的中级开发者。

## 前置条件

| 项目 | 要求 |
|------|------|
| Node.js | ≥ 20.x |
| Claude Code | 最新版（`claude update`） |
| 数据库 | SQLite（教程内使用，生产换 PostgreSQL） |
| 浏览器 | 用于 OAuth2 回调测试 |

在项目根目录准备好 `CLAUDE.md`，写入以下内容让 Claude Code 了解项目上下文：

```markdown
# 项目说明
- 技术栈：Node.js + Express + SQLite
- 认证方案：JWT + OAuth2.0
- 密码哈希：argon2
- Token 存储：httpOnly cookie
- 项目结构遵循 MVC 分层
```

---

## 第一步：项目脚手架与密码安全

### 操作说明

在终端中启动 Claude Code，输入以下提示词：

```
帮我初始化一个 Express 项目，要求：
1. 使用 ES Module（type: module）
2. 安装 argon2、jsonwebtoken、cookie-parser、cors、dotenv
3. 创建以下目录结构：
   src/
   ├── routes/auth.js
   ├── controllers/authController.js
   ├── services/authService.js
   ├── middleware/auth.js
   ├── models/user.js
   └── utils/token.js
4. 生成 .env 文件，包含 JWT_SECRET 和 PORT
5. 创建 server.js 入口文件
```

Claude Code 会一次性生成完整的项目骨架。核心的密码注册逻辑如下：

```javascript
// src/services/authService.js
import argon2 from 'argon2';
import db from '../models/user.js';

export async function register({ email, password, name }) {
  // 检查邮箱唯一性
  const existing = db.findByEmail(email);
  if (existing) {
    throw new Error('EMAIL_DUPLICATE');
  }

  // argon2id 是 OWASP 推荐的密码哈希算法
  const hash = await argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536,   // 64 MB
    timeCost: 3,          // 3 次迭代
    parallelism: 4        // 4 线程
  });

  const user = db.create({ email, passwordHash: hash, name });
  return { id: user.id, email: user.email, name: user.name };
}

export async function verifyPassword(hash, plain) {
  return argon2.verify(hash, plain);
}
```

### 预期结果

运行 `npm run dev` 后，服务器在 `localhost:3000` 启动。用 curl 测试注册：

```bash
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"Str0ng!Pass","name":"Test"}'
```

返回：

```json
{"user":{"id":1,"email":"test@example.com","name":"Test"}}
```

### 常见问题

| 问题 | 原因 | 解决 |
|------|------|------|
| `argon2` 安装报错 | 缺少编译工具链 | macOS: `xcode-select --install`; Linux: `apt install build-essential` |
| `EMAIL_DUPLICATE` | 邮箱已注册 | 前端做邮箱格式校验 + 后端捕获错误返回 409 |
| 密码明文入库 | 忘记调用 hash | 永远不要存明文，用 argon2.hash 处理 |

---

## 第二步：JWT 签发与验证中间件

### 操作说明

这一步是整个认证系统的核心。向 Claude Code 输入：

```
实现 JWT 认证，要求：
1. Access Token: 15分钟过期，存在 httpOnly cookie
2. Refresh Token: 7天过期，存在 httpOnly cookie，同时存入数据库
3. 登录接口 POST /api/auth/login
4. Token 刷新接口 POST /api/auth/refresh
5. 登出接口 POST /api/auth/logout（清除 cookie + 吊销 refresh token）
6. 认证中间件，从 cookie 取 token 验证，将 user 信息挂载到 req.user
```

Claude Code 生成的 Token 工具模块：

```javascript
// src/utils/token.js
import jwt from 'jsonwebtoken';

const ACCESS_SECRET = process.env.JWT_SECRET;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;
const ACCESS_EXPIRY = '15m';
const REFRESH_EXPIRY = '7d';

export function signAccessToken(payload) {
  return jwt.sign(payload, ACCESS_SECRET, {
    expiresIn: ACCESS_EXPIRY,
    issuer: 'myapp',
    audience: 'myapp-users'
  });
}

export function signRefreshToken(payload) {
  return jwt.sign(payload, REFRESH_SECRET, {
    expiresIn: REFRESH_EXPIRY,
    issuer: 'myapp',
    audience: 'myapp-refresh'
  });
}

export function verifyAccessToken(token) {
  return jwt.verify(token, ACCESS_SECRET, {
    issuer: 'myapp',
    audience: 'myapp-users'
  });
}

export function verifyRefreshToken(token) {
  return jwt.verify(token, REFRESH_SECRET, {
    issuer: 'myapp',
    audience: 'myapp-refresh'
  });
}
```

认证中间件：

```javascript
// src/middleware/auth.js
import { verifyAccessToken } from '../utils/token.js';

export function requireAuth(req, res, next) {
  const token = req.cookies?.access_token;
  if (!token) {
    return res.status(401).json({ error: 'NO_TOKEN', message: '未登录' });
  }

  try {
    const decoded = verifyAccessToken(token);
    req.user = { id: decoded.sub, email: decoded.email, role: decoded.role };
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'TOKEN_EXPIRED', message: 'Token 已过期' });
    }
    return res.status(401).json({ error: 'INVALID_TOKEN', message: '无效 Token' });
  }
}

// RBAC 权限检查
export function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user) return res.status(401).json({ error: 'UNAUTHORIZED' });
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'FORBIDDEN', message: '权限不足' });
    }
    next();
  };
}
```

Cookie 设置逻辑（在 Controller 中）：

```javascript
// src/controllers/authController.js
const COOKIE_OPTIONS = {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  path: '/'
};

const ACCESS_COOKIE = { ...COOKIE_OPTIONS, maxAge: 15 * 60 * 1000 };      // 15 分钟
const REFRESH_COOKIE = { ...COOKIE_OPTIONS, maxAge: 7 * 24 * 60 * 60 * 1000, path: '/api/auth/refresh' };

function setTokenCookies(res, { accessToken, refreshToken }) {
  res.cookie('access_token', accessToken, ACCESS_COOKIE);
  res.cookie('refresh_token', refreshToken, REFRESH_COOKIE);
}

function clearTokenCookies(res) {
  res.clearCookie('access_token', { path: '/' });
  res.clearCookie('refresh_token', { path: '/api/auth/refresh' });
}
```

### 预期结果

```bash
# 登录
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{"email":"test@example.com","password":"Str0ng!Pass"}'

# 访问受保护资源
curl http://localhost:3000/api/me -b cookies.txt

# 刷新 Token
curl -X POST http://localhost:3000/api/auth/refresh -b cookies.txt -c cookies.txt
```

### 常见问题

| 问题 | 原因 | 解决 |
|------|------|------|
| Cookie 不生效 | 前后端跨域 | CORS 配置 `credentials: true`，前端 fetch 加 `credentials: 'include'` |
| Token 过期后无限重定向 | 前端没有区分 401 错误类型 | 检查 `TOKEN_EXPIRED` 错误码，自动触发 refresh |
| Refresh Token 被盗用 | 无绑定机制 | 加入设备指纹或 IP 绑定，异常时吊销全部 refresh token |

---

## 第三步：OAuth2.0 授权码流程

### 操作说明

向 Claude Code 输入：

```
实现 OAuth2.0 授权码流程（GitHub OAuth），要求：
1. GET /api/auth/github — 重定向到 GitHub 授权页
2. GET /api/auth/github/callback — 处理回调，换取 access_token，获取用户信息
3. 如果用户已注册，直接登录；如果未注册，自动创建账号
4. 用 state 参数防 CSRF
5. 在 .env 中添加 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET
```

Claude Code 生成的 OAuth 路由：

```javascript
// src/routes/oauth.js
import { Router } from 'express';
import crypto from 'crypto';
import { getGithubUser, findOrCreateUser } from '../services/oauthService.js';
import { signAccessToken, signRefreshToken } from '../utils/token.js';

const router = Router();

// 内存存储 state（生产环境用 Redis）
const states = new Map();

router.get('/github', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  states.set(state, { createdAt: Date.now() });

  const params = new URLSearchParams({
    client_id: process.env.GITHUB_CLIENT_ID,
    redirect_uri: `${process.env.BASE_URL}/api/auth/github/callback`,
    scope: 'user:email',
    state
  });

  res.redirect(`https://github.com/login/oauth/authorize?${params}`);
});

router.get('/github/callback', async (req, res) => {
  const { code, state } = req.query;

  // 验证 state
  if (!state || !states.has(state)) {
    return res.status(400).json({ error: 'INVALID_STATE' });
  }
  states.delete(state);

  try {
    // 用 code 换 access_token
    const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
      body: JSON.stringify({
        client_id: process.env.GITHUB_CLIENT_ID,
        client_secret: process.env.GITHUB_CLIENT_SECRET,
        code
      })
    });
    const { access_token } = await tokenRes.json();

    // 用 access_token 获取用户信息
    const ghUser = await getGithubUser(access_token);
    const user = await findOrCreateUser(ghUser);

    // 签发 JWT
    const accessToken = signAccessToken({ sub: user.id, email: user.email, role: user.role });
    const refreshToken = signRefreshToken({ sub: user.id });

    setTokenCookies(res, { accessToken, refreshToken });
    res.redirect(process.env.FRONTEND_URL || '/');
  } catch (err) {
    res.redirect(`${process.env.FRONTEND_URL}/login?error=oauth_failed`);
  }
});

export default router;
```

OAuth 服务层：

```javascript
// src/services/oauthService.js
import db from '../models/user.js';

export async function getGithubUser(accessToken) {
  const [profileRes, emailsRes] = await Promise.all([
    fetch('https://api.github.com/user', {
      headers: { Authorization: `Bearer ${accessToken}` }
    }),
    fetch('https://api.github.com/user/emails', {
      headers: { Authorization: `Bearer ${accessToken}` }
    })
  ]);

  const profile = await profileRes.json();
  const emails = await emailsRes.json();
  const primaryEmail = emails.find(e => e.primary)?.email;

  return {
    githubId: profile.id,
    name: profile.name || profile.login,
    email: primaryEmail,
    avatar: profile.avatar_url
  };
}

export async function findOrCreateUser(ghUser) {
  // 先按 GitHub ID 查找
  let user = db.findByGithubId(ghUser.githubId);
  if (user) return user;

  // 再按邮箱查找（已有账号绑定）
  user = db.findByEmail(ghUser.email);
  if (user) {
    db.updateGithubId(user.id, ghUser.githubId);
    return user;
  }

  // 新用户注册
  return db.create({
    email: ghUser.email,
    name: ghUser.name,
    avatar: ghUser.avatar,
    githubId: ghUser.githubId,
    passwordHash: null,   // OAuth 用户无密码
    role: 'user'
  });
}
```

### 预期结果

1. 访问 `http://localhost:3000/api/auth/github`，重定向到 GitHub 授权页
2. 授权后回调到 `/api/auth/github/callback?code=xxx&state=yyy`
3. 自动完成注册/登录，设置 JWT cookie，重定向到前端页面

### 常见问题

| 问题 | 原因 | 解决 |
|------|------|------|
| `redirect_uri_mismatch` | GitHub App 配置的回调地址与代码不一致 | 在 GitHub Settings → Developer settings 中检查 |
| 用户邮箱为 null | GitHub 默认不暴露私有邮箱 | 请求 `user:email` scope 并调用 emails API |
| state 验证失败 | 用户刷新页面或多次回调 | state 加过期时间，5 分钟后自动失效 |

---

## 第四步：RBAC 权限控制与安全加固

### 操作说明

向 Claude Code 输入：

```
实现 RBAC 权限系统，要求：
1. 用户角色：admin / editor / user
2. 权限表：
   - admin: 所有操作
   - editor: 读写自己创建的内容
   - user: 只读
3. 数据库 users 表增加 role 字段，默认 'user'
4. 新增中间件 requireOwnership(model, paramIdField)
   用于检查资源是否属于当前用户
5. 管理接口 PUT /api/admin/users/:id/role — 修改角色
   仅 admin 可访问
```

资源归属检查中间件：

```javascript
// src/middleware/ownership.js
export function requireOwnership(getOwner) {
  return async (req, res, next) => {
    if (req.user.role === 'admin') return next(); // admin 跳过检查

    const ownerId = await getOwner(req);
    if (req.user.id !== ownerId) {
      return res.status(403).json({ error: 'NOT_OWNER', message: '只能操作自己的资源' });
    }
    next();
  };
}

// 使用示例
import { requireAuth, requireRole } from '../middleware/auth.js';
import { requireOwnership } from '../middleware/ownership.js';

// 只有 admin 能修改角色
router.put('/admin/users/:id/role',
  requireAuth,
  requireRole('admin'),
  adminController.updateRole
);

// 编辑者只能修改自己的文章
router.put('/articles/:id',
  requireAuth,
  requireRole('admin', 'editor'),
  requireOwnership((req) => Article.getOwnerId(req.params.id)),
  articleController.update
);
```

安全响应头中间件：

```javascript
// src/middleware/security.js
export function securityHeaders(req, res, next) {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '0');   // 现代浏览器不推荐开启
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.removeHeader('X-Powered-By');
  next();
}
```

### 预期结果

```bash
# 普通用户尝试修改角色 → 403
curl -X PUT http://localhost:3000/api/admin/users/2/role \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"role":"admin"}'
# {"error":"FORBIDDEN","message":"权限不足"}

# admin 修改角色 → 200
curl -X PUT http://localhost:3000/api/admin/users/2/role \
  -b admin-cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"role":"editor"}'
# {"user":{"id":2,"role":"editor"}}
```

---

## 实战案例：完整认证系统的项目结构

把以上四步整合为一个可运行的项目：

```
auth-demo/
├── .env
├── package.json
├── server.js
├── src/
│   ├── routes/
│   │   ├── auth.js          # 注册、登录、登出、刷新
│   │   └── oauth.js         # GitHub OAuth 回调
│   ├── controllers/
│   │   └── authController.js
│   ├── services/
│   │   ├── authService.js   # 密码哈希、用户查找
│   │   └── oauthService.js  # GitHub API 调用
│   ├── middleware/
│   │   ├── auth.js          # requireAuth, requireRole
│   │   ├── ownership.js     # 资源归属检查
│   │   └── security.js      # 安全头
│   ├── models/
│   │   └── user.js          # SQLite 用户模型
│   ├── utils/
│   │   └── token.js         # JWT 签发/验证
│   └── db/
│       └── init.js          # 建表脚本
└── CLAUDE.md
```

数据库建表脚本：

```javascript
// src/db/init.js
import Database from 'better-sqlite3';

const db = new Database('auth.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL,
    password_hash TEXT,              -- OAuth 用户为 NULL
    name TEXT NOT NULL,
    avatar TEXT,
    github_id INTEGER UNIQUE,
    role TEXT NOT NULL DEFAULT 'user',
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
  );

  CREATE TABLE IF NOT EXISTS refresh_tokens (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL REFERENCES users(id),
    token_hash TEXT NOT NULL UNIQUE,
    expires_at TEXT NOT NULL,
    revoked_at TEXT,
    created_at TEXT DEFAULT (datetime('now'))
  );

  CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id);
  CREATE INDEX idx_refresh_tokens_hash ON refresh_tokens(token_hash);
`);

export default db;
```

启动命令：

```bash
# 安装依赖
npm install express argon2 jsonwebtoken cookie-parser cors dotenv better-sqlite3

# 启动开发
node server.js
```

---

## 架构流程图

整个认证系统的请求流转如下：

```mermaid
sequenceDiagram
    participant C as Client
    participant S as Server
    participant DB as SQLite
    participant GH as GitHub OAuth

    Note over C,GH: 场景1：邮箱密码注册登录
    C->>S: POST /api/auth/register {email, password}
    S->>DB: argon2.hash(password) → 存储
    S-->>C: {user}

    C->>S: POST /api/auth/login {email, password}
    S->>DB: 查找用户，argon2.verify
    S->>S: signAccessToken + signRefreshToken
    S-->>C: Set-Cookie: access_token, refresh_token

    C->>S: GET /api/me (带 cookie)
    S->>S: verifyAccessToken → req.user
    S-->>C: {user profile}

    Note over C,GH: 场景2：Token 刷新轮转
    C->>S: POST /api/auth/refresh (带 refresh_token cookie)
    S->>DB: 查找 token_hash，检查未吊销
    S->>DB: 吊销旧 refresh token
    S->>S: 签发新 access + refresh token
    S-->>C: Set-Cookie: 新 token pair

    Note over C,GH: 场景3：GitHub OAuth 登录
    C->>S: GET /api/auth/github
    S-->>C: 302 → GitHub 授权页
    C->>GH: 授权确认
    GH-->>C: 302 → /callback?code=xxx&state=yyy
    C->>S: GET /api/auth/github/callback
    S->>GH: POST access_token (换 code)
    S->>GH: GET /api/github/user
    S->>DB: findOrCreateUser
    S->>S: 签发 JWT
    S-->>C: 302 → 前端首页 (带 cookie)

    Note over C,GH: 场景4：RBAC 权限检查
    C->>S: PUT /api/admin/users/:id/role
    S->>S: requireAuth → requireRole('admin')
    alt role = admin
        S->>DB: UPDATE role
        S-->>C: 200 {user}
    else role ≠ admin
        S-->>C: 403 FORBIDDEN
    end
```

Token 生命周期管理：

```mermaid
stateDiagram-v2
    [*] --> Issued: 用户登录
    Issued --> Active: 首次使用
    Active --> Renewing: access_token 过期
    Renewing --> Active: refresh 成功，新 token pair
    Renewing --> Expired: refresh token 也过期
    Active --> Revoked: 用户登出 / 安全事件
    Expired --> [*]: 需重新登录
    Revoked --> [*]: 需重新登录
```

---

## Claude Code 提效技巧

在构建认证系统时，以下 Claude Code 用法特别有效：

### 1. 分层生成，逐层验证

不要一次性让 Claude Code 生成所有代码。按层生成：

```
第一步：生成 models/user.js 和 db/init.js → 运行 node -e "require('./src/db/init.js')" 验证建表
第二步：生成 services/authService.js → 写测试验证注册逻辑
第三步：生成 routes + controller → 用 curl 集成测试
第四步：生成中间件 → 接入路由验证权限
```

### 2. 利用 CLAUDE.md 保持上下文

随着代码增长，在 `CLAUDE.md` 中追加：

```markdown
## 已完成的模块
- src/db/init.js: 用户表 + refresh_tokens 表
- src/services/authService.js: register(), verifyPassword()
- src/utils/token.js: signAccessToken, verifyAccessToken
## 当前安全约定
- Access Token 15分钟过期
- Refresh Token 7天过期 + 数据库存储 + 轮转
- Cookie: httpOnly, secure, sameSite=strict
```

Claude Code 每次对话都会读取这个文件，保持与已有代码的一致性。

### 3. 让 Claude Code 写测试

```
为 src/services/authService.js 编写 Vitest 单元测试：
1. 测试注册成功返回用户信息
2. 测试重复邮箱抛出 EMAIL_DUPLICATE
3. 测试密码验证正确和错误两种情况
4. 使用内存 SQLite 数据库，测试完自动清理
```

### 4. 安全审计

认证系统写完后，让 Claude Code 做一次安全审查：

```
审查当前认证系统的安全性，检查以下方面：
1. 密码存储是否安全（哈希算法、加盐）
2. JWT 是否有可能被伪造（密钥强度、算法限制）
3. 是否存在时序攻击（timing attack）风险
4. OAuth state 是否防 CSRF
5. 是否有信息泄露（错误消息过于详细）
输出发现的问题，按严重程度排序
```

---

## 生产环境检查清单

| 检查项 | 状态 | 说明 |
|--------|------|------|
| JWT_SECRET ≥ 256 位随机 | ☐ | `openssl rand -hex 32` |
| argon2 参数 OWASP 合规 | ☐ | memoryCost≥64MB, timeCost≥3 |
| httpOnly + secure + sameSite | ☐ | 生产必须 `secure: true` |
| Refresh Token 轮转 | ☐ | 每次刷新生成新 pair，吊销旧的 |
| Rate Limiting | ☐ | 登录接口 5次/分钟/IP |
| CORS 白名单 | ☐ | 禁止 `*`，明确列出域名 |
| 数据库连接加密 | ☐ | 生产用 PostgreSQL + SSL |
| 日志脱敏 | ☐ | 不记录密码、token |
| OAuth state 过期 | ☐ | 5 分钟自动失效 |
| 管理接口二次验证 | ☐ | 改角色需重新输入密码 |

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | 全栈训练营、安全训练营、Node.js 专项营 |
| 预计教学时长 | 4-6 小时（含实操） |
| 难度曲线 | 中等，需要异步编程和 HTTP 基础 |
| 实操占比 | 70%（大量代码生成和 curl 测试） |
| 延伸方向 | 添加 Google OAuth、多因子认证、密码重置流程 |

---

## 参考来源

1. [Extend Claude with skills - Claude Code Docs](https://code.claude.com/docs/en/skills) — Claude Code Skills 官方文档
2. [Skill authoring best practices - Claude API Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — Anthropic 官方 Skill 编写最佳实践
3. [Claude Code Skills Complete Guide - Duet.so](https://duet.so/guides/claude-code-skills-complete-guide) — Skills 生态综合指南（含 MCP、Subagents 对比）
4. [anthropics/skills - GitHub](https://github.com/anthropics/skills) — Anthropic 官方 Skills 参考仓库
5. [SKILL.md Format Specification - DeepWiki](https://deepwiki.com/anthropics/skills/2.2-skill.md-format-specification) — SKILL.md 文件格式完整规格

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
