news 2026/9/29 3:51:59

NewAPI+Sub2API 手把手部署搭建教程:TaoToken 统一 Key 接入配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NewAPI+Sub2API 手把手部署搭建教程:TaoToken 统一 Key 接入配置

1. 为什么要把 NewAPI 和 Sub2API 放在一起部署

如果你手上有多个模型渠道,又想让团队或自己的几个客户端统一走一个入口,NewAPI 负责的是「统一网关 + 令牌分发 + 用量统计」,Sub2API 负责的是「订阅式额度管理 + 用户套餐」,两者拼起来才是一套完整的自用中转站。单独跑 NewAPI,你能拿到一个 OpenAI 兼容的/v1/chat/completions端点;单独跑 Sub2API,你能做用户和套餐,但缺少上游渠道聚合。合在一起,前端用户拿 Sub2API 发的订阅 Key,后端实际请求打到 NewAPI 聚合的渠道上,链路才闭环。

这篇教程面向的是需要统一管理多模型 API 通道的开发者,交付物很具体:一份可复制的docker-compose.yml、一份.env.secret骨架、TaoToken 统一 Key 的接入步骤,以及部署完成后的连通性验证命令。整套环境我在 Ubuntu 24.04 上跑通过,内存 2G 的机器也能起来,关键是几个参数必须显式限制,否则 MySQL 和 Postgres 会互相抢内存。

先说清楚整体拓扑,不然后面配置容易迷路。NewAPI 自带 MySQL 和 Redis,Sub2API 单独用 PostgreSQL,但复用 NewAPI 的 Redis(用 DB1 区分)。两个应用都只监听本地回环,公网暴露交给 cloudflared 隧道。这样做的原因是:数据库端口一个都不对外,攻击面小;隧道重启会换临时域名,所以要把域名写回 NewAPI 的ServerAddress,令牌详情页才能生成完整调用链接。

TaoToken 在这里的角色是上游统一 Key 提供方。你在 NewAPI 里新建渠道时,把 TaoToken 的 API 地址和 Key 填进去,就能把它的模型能力挂到自己的网关下,再由 NewAPI 分发给 Sub2API 的用户。下面从环境准备开始,一步步来。

2. 环境准备与 TaoToken 前置配置

2.1 系统确认与基础工具

先确认系统版本,命令输出里能看到VERSION_CODENAME就行:

cat /etc/os-release | head -5

装基础工具包:

sudo apt-get update -y sudo apt-get install -y ca-certificates curl gnupg lsb-release vim

2.2 安装 Docker 与 Docker Compose

加 Docker 官方 GPG 密钥,国内网络优先走清华镜像:

sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL --connect-timeout 8 https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc

写 apt 源,先探测清华镜像是否可达,不通再回退官方:

CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") ARCH=$(dpkg --print-architecture) REPO=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu curl -fsSI --connect-timeout 5 "$REPO/dists/$CODENAME/Release" >/dev/null \ || REPO=https://download.docker.com/linux/ubuntu echo "deb [arch=$ARCH signed-by=/etc/apt/keyrings/docker.asc] $REPO $CODENAME stable" \ | sudo tee /etc/apt/sources.list.d/docker.list sudo apt-get update -y

安装 Docker 组件:

sudo apt-get install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker sudo usermod -aG docker $USER

验证:

docker --version docker compose version

注意:usermod -aG docker加组之后要重新登录 SSH 才不用 sudo 跑 docker。本教程后面命令仍带sudo兼容。

配置镜像加速,同时限制容器日志大小,避免长跑把磁盘写满:

sudo tee /etc/docker/daemon.json >/dev/null <<'EOF' { "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.1panel.live" ], "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } EOF sudo systemctl restart docker docker info | grep -A3 'Registry Mirrors'

log-opts限制单容器日志最多 30 MB(10×3),这个在小盘 VPS 上很关键。

2.3 获取 TaoToken 统一 Key

打开 TaoToken 控制台,在 API Keys 页面创建一个新 Key,复制保存。这个 Key 就是后面填进 NewAPI 渠道的凭证。同时记下 API 地址https://taotoken.net/api,NewAPI 新建渠道时「代理地址」填这个,「密钥」填刚复制的 Key。

如果你还没注册,先走官网入口完成账号创建,再进控制台建 Key。整个流程不需要额外配置,建完 Key 就能直接用。

3. 部署 NewAPI 并接入 TaoToken 渠道

3.1 创建目录与生成密码

sudo mkdir -p /opt/new-api/{data,logs,mysql,redis} sudo chown -R $USER:$USER /opt/new-api cd /opt/new-api

一次性生成 4 个随机密码写入.env.secret,只生成一次,丢了就要全部重置:

cat > /opt/new-api/.env.secret <<EOF MYSQL_ROOT_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24) MYSQL_USER=newapi MYSQL_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24) SESSION_SECRET=$(openssl rand -hex 32) EOF chmod 600 /opt/new-api/.env.secret cat /opt/new-api/.env.secret

把输出的 4 行抄到密码管理器。

3.2 编写 docker-compose.yml

set -a; source /opt/new-api/.env.secret; set +a cat > /opt/new-api/docker-compose.yml <<EOF services: new-api: image: calciumion/new-api:latest container_name: new-api restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_started ports: - "3000:3000" environment: TZ: Asia/Shanghai SQL_DSN: "newapi:${MYSQL_PASSWORD}@tcp(mysql:3306)/new-api?charset=utf8mb4&parseTime=True&loc=Local" REDIS_CONN_STRING: "redis://redis:6379/0" SESSION_SECRET: "${SESSION_SECRET}" CRYPTO_SECRET: "${SESSION_SECRET}" SYSTEM_NAME: "WeskyApi" GIN_MODE: release ERROR_LOG_ENABLED: "true" volumes: - ./data:/data - ./logs:/app/logs healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/status >/dev/null || exit 1"] interval: 30s timeout: 5s retries: 10 mysql: image: mysql:8.4 container_name: new-api-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}" MYSQL_DATABASE: "new-api" MYSQL_USER: "newapi" MYSQL_PASSWORD: "${MYSQL_PASSWORD}" TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --innodb-buffer-pool-size=128M - --max-connections=200 - --performance-schema=OFF volumes: - ./mysql:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 20 redis: image: redis:7-alpine container_name: new-api-redis restart: unless-stopped command: ["redis-server", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru", "--save", "900", "1"] volumes: - ./redis:/data EOF

关键调优(小内存机器必须):MySQL 的--innodb-buffer-pool-size=128M限制缓冲池,默认会自适应到 25% 内存;--performance-schema=OFF省约 50MB;Redis 的--maxmemory 128mb加 LRU 策略防止无限增长。

3.3 启动并初始化管理员

cd /opt/new-api sudo docker compose pull sudo docker compose up -d sudo docker compose ps

等服务就绪:

for i in $(seq 1 36); do if curl -fsS http://127.0.0.1:3000/api/status >/dev/null; then echo "ok"; break; fi echo "等待中 $i/36"; sleep 5 done

当前版本 NewAPI 不再预置默认账户,必须主动调/api/setup接口创建 root:

ROOT_USER=root ROOT_PASSWORD="Wesky-$(openssl rand -base64 16 | tr -d '=+/' | cut -c1-16)" echo "ROOT_USERNAME=$ROOT_USER" | sudo tee -a /opt/new-api/.env.secret echo "ROOT_PASSWORD=$ROOT_PASSWORD" | sudo tee -a /opt/new-api/.env.secret echo ">>> 务必记下: $ROOT_USER / $ROOT_PASSWORD" curl -sS -X POST http://127.0.0.1:3000/api/setup \ -H 'Content-Type: application/json' \ -d "{\"Username\":\"$ROOT_USER\",\"Password\":\"$ROOT_PASSWORD\",\"ConfirmPassword\":\"$ROOT_PASSWORD\",\"SelfUseModeEnabled\":false,\"DemoSiteEnabled\":false}" echo

期望返回{"message":"系统初始化成功","success":true}。

登录拿 cookie 和 uid:

LOGIN_RESP=$(curl -sS -c /tmp/newapi_cookie.txt -X POST http://127.0.0.1:3000/api/user/login \ -H 'Content-Type: application/json' \ -d "{\"username\":\"$ROOT_USER\",\"password\":\"$ROOT_PASSWORD\"}") echo "$LOGIN_RESP" UID=$(echo "$LOGIN_RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["id"])') echo "UID=$UID"

关键坑:调任何管理员接口都要带New-Api-User: $UID这个 HTTP 头,仅 cookie 不够。否则会得到Unauthorized, New-Api-User header not provided。

3.4 在 NewAPI 里新建 TaoToken 渠道

登录 NewAPI 后台,进入「渠道」→「添加渠道」。关键字段这样填:

字段值
类型OpenAI
名称TaoToken
代理地址https://taotoken.net/api
密钥你在 TaoToken 控制台创建的 Key
模型按需勾选,或填gpt-4o,gpt-4o-mini,claude-3-5-sonnet等

保存后点「测试」按钮,返回绿色即渠道可用。这一步做完,NewAPI 就已经能通过 TaoToken 转发请求了。

4. 部署 Sub2API 并复用 NewAPI 的 Redis

4.1 创建目录与生成机密

sudo mkdir -p /opt/sub2api/{data,postgres} sudo chown -R $USER:$USER /opt/sub2api cd /opt/sub2api cat > /opt/sub2api/.env.secret <<EOF POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24) JWT_SECRET=$(openssl rand -hex 32) TOTP_ENCRYPTION_KEY=$(openssl rand -hex 32) ADMIN_EMAIL=admin@weskyapi.local ADMIN_PASSWORD=Wesky-$(openssl rand -base64 16 | tr -d '=+/' | cut -c1-16) EOF chmod 600 /opt/sub2api/.env.secret cat /opt/sub2api/.env.secret

JWT_SECRET必须固定,变了所有用户被踢下线;TOTP_ENCRYPTION_KEY必须固定,变了所有 2FA 失效;ADMIN_EMAIL是登录用户名。

4.2 编写 docker-compose.yml

set -a; source /opt/sub2api/.env.secret; set +a cat > /opt/sub2api/docker-compose.yml <<EOF services: sub2api: image: weishaw/sub2api:latest container_name: sub2api restart: unless-stopped ulimits: nofile: { soft: 65535, hard: 65535 } ports: - "127.0.0.1:8080:8080" depends_on: postgres: condition: service_healthy volumes: - ./data:/app/data environment: AUTO_SETUP: "true" SERVER_HOST: "0.0.0.0" SERVER_PORT: "8080" SERVER_MODE: "release" RUN_MODE: "standard" DATABASE_HOST: postgres DATABASE_PORT: "5432" DATABASE_USER: sub2api DATABASE_PASSWORD: "${POSTGRES_PASSWORD}" DATABASE_DBNAME: sub2api DATABASE_SSLMODE: disable DATABASE_MAX_OPEN_CONNS: "30" DATABASE_MAX_IDLE_CONNS: "5" REDIS_HOST: new-api-redis REDIS_PORT: "6379" REDIS_DB: "1" REDIS_POOL_SIZE: "128" REDIS_MIN_IDLE_CONNS: "4" JWT_SECRET: "${JWT_SECRET}" JWT_EXPIRE_HOUR: "168" TOTP_ENCRYPTION_KEY: "${TOTP_ENCRYPTION_KEY}" ADMIN_EMAIL: "${ADMIN_EMAIL}" ADMIN_PASSWORD: "${ADMIN_PASSWORD}" TZ: Asia/Shanghai SECURITY_URL_ALLOWLIST_ENABLED: "false" SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP: "true" SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS: "true" networks: - sub2api-network - newapi-shared healthcheck: test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 5 start_period: 60s postgres: image: postgres:18-alpine container_name: sub2api-postgres restart: unless-stopped shm_size: 128mb environment: PGDATA: /var/lib/postgresql/data POSTGRES_USER: sub2api POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" POSTGRES_DB: sub2api TZ: Asia/Shanghai command: - postgres - -c - shared_buffers=64MB - -c - effective_cache_size=192MB - -c - max_connections=80 volumes: - ./postgres:/var/lib/postgresql/data networks: - sub2api-network healthcheck: test: ["CMD-SHELL", "pg_isready -U sub2api -d sub2api"] interval: 10s timeout: 5s retries: 10 start_period: 30s networks: sub2api-network: driver: bridge newapi-shared: external: true name: new-api_default EOF

三个不能省的细节:

  1. PGDATA: /var/lib/postgresql/data必须显式设置。postgres:18-alpine默认PGDATA=/var/lib/postgresql/18/docker,不显式设的话挂载到./postgres的卷里不会落数据,重启就 initdb,账号订阅全丢。
  2. shm_size: 128mb:PG18 默认 64MB 太小,复杂查询会报共享内存不足。
  3. networks.newapi-shared.external: true name: new-api_default:把 sub2api 接入 new-api 的网络,才能用容器名new-api-redis解析到那个 redis 容器。

4.3 启动并验证

cd /opt/sub2api sudo docker compose pull sudo docker compose up -d sudo docker compose ps

期望两个容器都Up (healthy),且sub2api的 PORTS 列必须是127.0.0.1:8080,不是0.0.0.0。

for i in $(seq 1 36); do if curl -fsS http://127.0.0.1:8080/health >/dev/null; then echo ok; break; fi echo "等待中 $i"; sleep 5 done curl -sS http://127.0.0.1:8080/health

期望返回{"status":"ok"}。

5. 公网暴露与调用验证

5.1 安装 cloudflared 并起两个隧道

sudo mkdir -p --mode=0755 /usr/share/keyrings curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \ | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' \ | sudo tee /etc/apt/sources.list.d/cloudflared.list sudo apt-get update -y sudo apt-get install -y cloudflared cloudflared --version

写 NewAPI 的 systemd 单元:

sudo tee /etc/systemd/system/cloudflared-newapi.service >/dev/null <<'EOF' [Unit] Description=cloudflared quick tunnel for new-api After=network-online.target docker.service Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:3000 --no-autoupdate --metrics 127.0.0.1:20241 Restart=on-failure RestartSec=5 User=root StandardOutput=append:/var/log/cloudflared-newapi.log StandardError=append:/var/log/cloudflared-newapi.log ExecStartPre=/bin/sh -c 'command -v cloudflared | xargs -I{} ln -sf {} /usr/local/bin/cloudflared' [Install] WantedBy=multi-user.target EOF sudo touch /var/log/cloudflared-newapi.log sudo chmod 640 /var/log/cloudflared-newapi.log sudo systemctl daemon-reload sudo systemctl enable --now cloudflared-newapi.service

Sub2API 的单元,三处差异:--url指向 8080、--metrics用 20242、日志路径不同:

sudo tee /etc/systemd/system/cloudflared-sub2api.service >/dev/null <<'EOF' [Unit] Description=cloudflared quick tunnel for Sub2API After=network-online.target docker.service Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:8080 --no-autoupdate --metrics 127.0.0.1:20242 Restart=on-failure RestartSec=5 User=root StandardOutput=append:/var/log/cloudflared-sub2api.log StandardError=append:/var/log/cloudflared-sub2api.log ExecStartPre=/bin/sh -c 'command -v cloudflared | xargs -I{} ln -sf {} /usr/local/bin/cloudflared' [Install] WantedBy=multi-user.target EOF sudo touch /var/log/cloudflared-sub2api.log sudo chmod 640 /var/log/cloudflared-sub2api.log sudo systemctl daemon-reload sudo systemctl enable --now cloudflared-sub2api.service

拿两个临时域名:

for i in $(seq 1 30); do URL=$(sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-newapi.log | head -1) [ -n "$URL" ] && { echo "NewAPI URL: $URL"; break; } sleep 3 done echo "$URL" | sudo tee /opt/new-api/tunnel_url.txt for i in $(seq 1 30); do URL2=$(sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-sub2api.log | head -1) [ -n "$URL2" ] && { echo "Sub2API URL: $URL2"; break; } sleep 3 done echo "$URL2" | sudo tee /opt/sub2api/tunnel_url.txt

把 NewAPI 的公网地址写回ServerAddress,令牌详情页才能生成完整链接:

curl -sS -b /tmp/newapi_cookie.txt -X PUT http://127.0.0.1:3000/api/option/ \ -H 'Content-Type: application/json' -H "New-Api-User: $UID" \ -d "{\"key\":\"ServerAddress\",\"value\":\"$URL\"}" echo

5.2 从外部验证连通性

从你自己的电脑(不是服务器)执行:

curl -sS https://你的tunnel域名.trycloudflare.com/api/status | head -c 200 curl -sS https://你的sub2api域名.trycloudflare.com/health

期望分别返回 NewAPI 状态 JSON 和{"status":"ok"}。

5.3 用 TaoToken Key 跑一次真实请求

在 NewAPI 后台「令牌」页面创建一个令牌,复制sk-开头的 Key。然后从外部发起一次对话请求:

curl -sS https://你的tunnel域名.trycloudflare.com/v1/chat/completions \ -H "Authorization: Bearer sk-你的NewAPI令牌" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "用一句话说明什么是统一 API 网关"}], "max_tokens": 100 }'

返回里能看到choices[0].message.content就说明整条链路通了:客户端 → NewAPI → TaoToken → 模型 → 原路返回。这一步是整个部署的验收动作,跑通它比看任何日志都直观。

6. 常见报错排查

症状可能原因处置
docker compose pull卡死镜像源不可达加加速器后sudo systemctl restart docker
MySQL OOM 自动重启buffer-pool 没限制确认 compose 里有--innodb-buffer-pool-size=128M,加 swap
Unauthorized, New-Api-User header not provided调管理员接口忘带 header所有/api/option、/api/user/...必须带-H "New-Api-User: $UID"
第二次跑 setup 报「用户名/密码不正确」DB 已有 root,密码不对用.env.secret里的 ROOT_PASSWORD,不要重新生成
Sub2API 启动报POSTGRES_PASSWORD is requiredenv 没带过去检查set -a; source .env.secret; set +a后再执行 compose
Sub2API 重启数据全丢PGDATA没显式设见 4.2 节关键细节 1
Sub2API 连不上 redisexternal 网络名错docker network ls看 new-api 的网络名是不是new-api_default
Tunnel URL 突然不通cloudflared 重启换了 URLcat /opt/*/tunnel_url.txt拿新地址,或看日志
服务器自己 curl trycloudflare 失败国内 DNS 屏蔽不影响外部,从本机/手机访问验证
两个 tunnel 同时只起来一个metrics 端口撞了第二个改成 20242

排查时优先看容器日志,sudo docker logs --tail 100 new-api和sudo docker logs --tail 100 sub2api能覆盖八成问题。隧道问题看sudo journalctl -u cloudflared-newapi.service -n 100 --no-pager。

7. 后续接入与运维建议

部署完成后,日常运维主要盯三件事:容器健康状态、隧道 URL 变化、磁盘占用。sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'一眼能看完所有容器;free -h和df -h各看一眼内存和磁盘。

如果你打算长期跑编码类或 Agent 类任务,建议把 NewAPI 的令牌换成 Coding Plan 对应的额度类型,这样用量统计更清晰,也方便按项目拆分。模型验证阶段可以直接用模型对话页面快速试通,不用每次都写 curl。

备份脚本建议接 cron,每天凌晨跑一次:

sudo mkdir -p /opt/backup sudo docker exec new-api-mysql sh -c \ 'MYSQL_PWD=$(grep ^MYSQL_PASSWORD /opt/new-api/.env.secret | cut -d= -f2) \ mysqldump -unewapi --single-transaction --quick --lock-tables=false new-api' \ | sudo tee /opt/backup/new-api-$(date +%F).sql >/dev/null sudo docker exec sub2api-postgres pg_dump -U sub2api sub2api \ | gzip | sudo tee /opt/backup/sub2api-$(date +%F).sql.gz >/dev/null

安全清单里最容易被忽略的是 SSH:部署完把PasswordAuthentication no打开,只留密钥登录,同时用 ufw 或安全组只放 22/80/443,封掉 3000 和 8080。数据库端口一个都不要对外,这是底线。

最后提醒一点:trycloudflare.com的临时域名每次重启 cloudflared 都会变,只适合测试。要稳定用自有域名,走 Named Tunnel 把api.yourdomain.com和pincc.yourdomain.com分别指向 3000 和 8080,然后把ServerAddress同步更新成正式域名。这样令牌详情页生成的链接才不会隔天就失效。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 3:51:33

postman-mcp-server 配 TaoToken:settings.json 骨架与连通性验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 3:51:02

接口自动化登录实战:图形验证码识别与pytest集成

做接口自动化的朋友大概都有过这种体验&#xff1a;框架搭好了&#xff0c;断言封装好了&#xff0c;数据驱动也跑通了&#xff0c;结果卡在登录这一步——因为登录页多了个图形验证码。上一篇文章聊接口自动化的整体骨架时&#xff0c;我特意把登录这块留了个尾巴&#xff0c;…

作者头像 李华
网站建设 2026/9/29 3:50:53

JS判空陷阱:空格、0、NaN、false、null、undefined的区别

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华