BOSh
文章355
标签611
分类151

文章分类

315晚会 36氪 80后 ADB AI AI Agent AI PC AI 代理 AI 助手 AI 网关 AI 评测 AIStudio AI代理 AI助手 AI处理器 AI大模型 AI安全 AI应用 AI智能体 AI网关 AMD API API 集成 AWS Active Directory Agent Agentic AI AionUi Alwaysdata Android Apple Arch Linux Argo Tunnel Automation Backup Bash Bosh Bot Bot-hosting C++ CDN CEO CI/CD CLI CLI Proxy API CLIProxyAPI COMPUTEX CRM CT8 CentOS 7 Chrome 插件 Cisco Claude Claude Opus 4.6 Cloudflare Code ConnectBot Credential Manager Ct8.pl CurrentEvents DHCP DNS Debian DeepSeek DenchClaw Deployment DevOps Devil Docker Elon Musk Express FSRM FTP FreeBSD FreeLLMAPI GCP GEO GPL GPO GPS GPT GPU Gemini Gemini 3.1 Pro Ghostty Git GitHub GitHub Actions Gmail Go Gog Golang Google Google AI Pro Google API Google Cloud Google Gemini Google Photos Google Pixel HDC HKUDS Hardware HarmonyOS Hermes Hermes Agent Hexo HidenCloud Hugo Hyper-V Hyprland IIS IPV6 Intel JavaScript Jetpack Compose John Turnus KDE KOSPI Karpathy Kimi-K2.5 Kotlin LINUX LLM LTS LVM LaTeX Linux LiveUSB MBR MTProto MW4L Markdow Markdown Matrix MemU Bot Mesh-Network MiniMax Musk MyDevil NAT NAT64 NIX NODE NTFS NVIDIA NVIDIA Build NanoClaw Netcatty Netlify Newsletter Node.js Nvidia Open WebUI OpenAI OpenAI 兼容接口 OpenAI兼容 OpenCLI OpenClaw P2P PDF 编译 PV Packet Tracer Pacman Phusion Passenger PicoClaw Pixel Pixel 1 Pixel 12 Pixel11 Plasma PowerShell Prismer Productivity Pura90 Python QClaw QQ机器人 Qualcomm Quickshell R22 RAG RAID RAID5 RIP RIPv2 RTX Spark Railway Reddit Rust SFTP SMB SSH SeleniumBase Serv00 Serverless Shell Sing-box Skills SoC Sub-Store Subagent SuperCall Synapse Syncthing TPU TSMC Tailscale Telegram Telegram Bot Tensor Tensor G7 Tim Cook Tunnel Turnstile Ubuntu VG VLESS VMware VPN VPS Vercel Vite Wayland WeChat Web3 WebSSH WebSocket WebTransport Web开发 Windows Windows 11 Windows Admin Center Windows Server Windows Server 2022 WireGuard WorkBuddy X XChat XHTTP X热榜 YUM源 YouTube ZeroClaw arXiv arch c++ cc-switch cloudflared ct8.pl email find git hdc hidumper hugo hyprmod i3wm iMessage iOS iSCSI icacls mdadm n8n nanobot node js ntfs pacman pnpm podman sconfig zz.ac 东海 两性关系 个人助理 中东 中东冲突 中东局势 中关村论坛 中南大学 中国 中美 中美关系 乌克兰 习惯养成 云同步 云服务器 亚洲 人性 人教版 代理 代金券 以色列 任务管理 伊朗 伊朗危机 伊朗战争 伦理 体育 使用 俄乌战争 俄罗斯 保护主义 信息流 信息管理 停火 健康管理 光通信 免费VPS 免费主机 免费试用 全球动态 六年级 共和党 关系 养老金 内存分析 内存管理 内容工厂 内容生产 内容筛选 内网穿透 军事冲突 军事动态 军民融合 农村 分享 分配 刘欢 创业 制裁 办公自动化 加密 加密货币 加沙 北斗 北韩 医学生 半导体 华为 协议分析 博客 博客助手 博客发布 博客部署成功 卫星 即时通讯 厄尔尼诺 原生 JS 去中心化 反向代理 反思 反爬虫 反重力 台海局势 台湾 命令 命令行 喷嚏网 国产 国产化 国产替代 国宴 国际 国际关系 国际局势 国际新闻 图卦 图说 地缘政治 域名解析 域名邮箱 域控 基础设施 多代理 多模态AI 大学分析 大模型 存储 存储空间 存储管理 孙少平 学习 安全 安装 实时监控 家庭助理 家庭服务器 家装设计 小学 嵌套虚拟化 工业策略 工作总结 工作效率 工作流编排 工具 工具链 平凡的世界 平台责任 庞氏骗局 开发 开发实录 开发指南 开发调试 开源 开源软件 开源项目 张雪峰 微信 心理健康 快捷键 怀念 性能调优 情感 慈善 战争 房地产 手机 技术分享 投资工具 指标看板 提示词工程 播客 收件箱清理 故障转移群集 效率 效率工具 教程 教育制度 数学 数据分析 数据投毒 文件服务器 文件管理 文献管理 新能源汽车 新闻汇总 日历聚合 时事 时事总结 显卡 晨报 智能体 智能体生态 智能手机 服务器管理 服务器部署 朝鲜 权限 极端天气 架构 架构实践 核协议 核武器 桌面Cowork 桌面环境 模型接入 模型配置 欧洲安全 每日图说 比亚迪 气候 油价 法律 活动运营 浏览器自动化 消息通道 消费者权益 深度学习 渔船 游戏开发 湘雅医院 潘石屹 热点新闻 熔断 版本更新 特朗普 生态系统 生活 生活自动化 生物识别 用例 甲骨文云 电池技术 症状追踪 白嫖 白嫖攻略 白山云 皮皮虾 监管 目标管理 省钱 知识库 磁盘管理 社交媒体 社会保障 社会公平 社会百态 社会观察 科技 科普 科研助手 窗口管理 笔记 第一财经 算法 算法推荐 系统管理 纽森 组策略 组网 经济 经济观察 经验分享 编程 网关 网络 网络安全 网络实验 网络摘录 网络穿透 网络规划 网络配置 美伊冲突 美伊谈判 美国 美国制裁 美国大选 美国政治 股灾 能源安全 脚本 腾讯 腾讯,龙虾,OpenClaw 腾讯云 自动化 自动化创作 自动化协作 自动化提醒 自动化流水线 自动化脚本 自动化运维 自建服务 自律教练 自托管 自由软件 节点管理 芯片 草榴 虚拟化 虚拟化运维 虚拟机 行为改变 视频摘要 解锁 计算摄影 订阅系统 记录 许可证 论文写作 论文阅读 语义搜索 语音代理 读书 读书笔记 读后感 谷歌云 财报季 账单分析 路由协议 路由器 路由配置 路遥 身份验证 轻量级 迁移 运维 进化论 远程桌面 远程访问 远程运维 选车 邀请确认 部署 部署指南 部署教程 重复数据删除 量子计算 销售自动化 阅读感悟 随笔 隐私 霍尔木兹 霍尔木兹海峡 青蛙机 韩国股市 韩红 韬定律 音乐 项目管理 风险管理 飞书 高中生活 高可用 高考 高考志愿 鸽巢原理 麒麟 麒麟9050 黄仁勋 黎巴嫩 龙虾

一言

文章归档

CT8 上安装 pnpm 并部署 Sub-Store 后端教程

CT8 上安装 pnpm 并部署 Sub-Store 后端教程

CT8 上安装 pnpm 并部署 Sub-Store 后端教程

本文介绍如何在 CT8 Hosting 的 Node.js 环境中,通过 SSH 使用 pnpm 部署 Sub-Store 后端。

本文以以下环境为例:

1
2
3
4
5
6
系统用户:your_username
Node.js:v22.22.2
pnpm:11.0.9
Sub-Store:v2.36.31
Node.js 端口:39439
后端访问前缀:/my_path

最终效果:

1
2
3
4
5
6
7
https://你的域名/my_path
↓
CT8 Node.js
↓
127.0.0.1:39439
↓
Sub-Store Backend

一、准备 CT8 Node.js 环境

首先通过 SSH 登录 CT8。

检查 Node.js:

1
node -v

例如:

1
v22.22.2

检查 npm:

1
npm -v

再检查 Corepack:

1
corepack --version

Corepack 可以管理项目指定的 pnpm 版本。Node.js 官方生态中的 Corepack 会根据项目 package.json 中的 packageManager 字段选择对应的包管理器版本。


二、在 CT8 安装 pnpm

方法一:使用 Corepack

推荐优先尝试:

1
corepack enable

然后:

1
pnpm -v

如果能够显示版本号,说明 pnpm 已经可以使用。


方法二:安装到用户目录

如果 CT8 不允许 corepack enable 修改系统目录,可以安装到自己的 Home 目录:

1
npm install --prefix "$HOME/.local" pnpm

安装完成后检查:

1
find "$HOME" -type f -name pnpm 2>/dev/null | head -20

例如:

1
/home/your_username/.local/bin/pnpm

将用户目录加入 PATH:

1
export PATH="$HOME/.local/bin:$PATH"

然后:

1
pnpm -v

三、解决 CT8 上 pnpm: command not found

CT8 上可能出现这样的情况:

1
pnpm start

可以运行,但是:

1
./start.sh

却提示:

1
pnpm: command not found

这是因为脚本运行时的 PATH 与 SSH 交互式 Shell 不一定完全相同。

可以检查:

1
which pnpm

如果没有输出,再执行:

1
find "$HOME" -type f -name pnpm 2>/dev/null | head -20

假设得到:

1
/home/your_username/.local/bin/pnpm

那么启动脚本中建议直接使用绝对路径:

1
/home/your_username/.local/bin/pnpm

同时加入:

1
export PATH="/home/your_username/.local/bin:$PATH"

这样最稳定。


四、下载 Sub-Store 后端源码

进入 Home 目录:

1
cd ~

克隆官方仓库:

1
git clone https://github.com/sub-store-org/Sub-Store.git

进入 backend:

1
cd ~/Sub-Store/backend

Sub-Store 官方仓库目前将后端源码放在 backend 目录,并使用 pnpm 管理依赖。

如果已经下载过:

1
2
cd ~/Sub-Store/backend
git pull

五、检查 package.json

执行:

1
cat package.json

你应该能看到类似:

1
2
3
4
5
6
7
8
9
10
{
"name": "sub-store",
"version": "2.36.31",
"main": "src/main.js",
"packageManager": "pnpm@11.0.9",
"scripts": {
"serve": "node sub-store.min.js",
"start": "nodemon -w src -w package.json --exec babel-node src/main.js"
}
}

这里有两个重要信息:

1
packageManager: pnpm@11.0.9

说明项目期望使用 pnpm 11.0.9。

以及:

1
start

实际执行:

1
2
3
4
5
nodemon
↓
babel-node
↓
src/main.js

因此不要直接:

1
node src/main.js

否则可能遇到:

1
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/core'

因为源码启动依赖项目的 Babel/路径别名配置。


六、安装项目依赖

进入:

1
cd ~/Sub-Store/backend

安装:

1
pnpm install

如果项目已经存在 lockfile,并且希望严格按照 lockfile 安装:

1
pnpm install --frozen-lockfile

安装完成后会生成:

1
node_modules/

七、确认 pnpm 版本

执行:

1
pnpm -v

如果项目要求:

1
11.0.9

可以通过 Corepack 管理指定版本。

例如:

1
corepack prepare pnpm@11.0.9 --activate

然后:

1
pnpm -v

确认版本。

项目在 package.json 中指定 packageManager 后,可以让 Corepack 根据项目配置使用相应的包管理器版本。


八、确认 CT8 分配的 Node.js 端口

CT8 Node.js 应用通常需要使用 platform 分配的端口。

本文使用:

1
39439

因此 Sub-Store 需要监听:

1
0.0.0.0:39439

或者:

1
:::39439

九、设置 Sub-Store 环境变量

本教程使用:

1
2
3
export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

三个变量的作用:

变量 作用
PORT Node.js 服务监听端口
SUB_STORE_FRONTEND_BACKEND_PATH 前端访问后端时使用的路径前缀
SUB_STORE_BACKEND_PREFIX 让后端自身处理该路径前缀

这里最容易遗漏的是:

1
SUB_STORE_BACKEND_PREFIX=/my_path

如果只设置:

1
SUB_STORE_FRONTEND_BACKEND_PATH=/my_path

前端访问:

1
https://你的域名/my_path

可能得到:

1
404 Not Found

而同时设置:

1
SUB_STORE_BACKEND_PREFIX=/my_path

后端也会使用这个前缀。


十、测试启动 Sub-Store

进入:

1
cd ~/Sub-Store/backend

执行:

1
2
3
4
5
export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

pnpm start

如果正常,日志应该类似:

1
2
3
4
5
6
7
Sub-Store -- v2.36.31

[sub-store] INFO: [CORS] allowed origins: * (default:node)

[sub-store] INFO: [BACKEND] body JSON limit: 1mb

[sub-store] INFO: [BACKEND] listening on :::39439

看到:

1
listening on :::39439

就说明 Node.js 已经成功监听 CT8 分配的端口。


十一、检查环境变量是否生效

可以通过 Sub-Store 启动后的信息确认:

1
SUB_STORE_FRONTEND_BACKEND_PATH=/my_path

以及:

1
SUB_STORE_BACKEND_PREFIX=/my_path

确保没有拼写错误。


十二、测试本地后端

另外打开一个 SSH 窗口。

执行:

1
curl -i http://127.0.0.1:39439/

如果返回 HTTP 响应,说明 Node.js 后端正常。

也可以检查端口:

1
ss -lnt | grep 39439

应该看到类似:

1
LISTEN ... 0.0.0.0:39439

或者:

1
LISTEN ... [::]:39439

三、配置 CT8 网站

假设你的域名是:

1
https://yourdomain.ct8.pl

Node.js 后端实际监听:

1
127.0.0.1:39439

那么最终访问:

1
https://yourdomain.ct8.pl/my_path

应该由 CT8 的 Node.js 网站映射到:

1
127.0.0.1:39439

如果 CT8 Node.js 网站本身已经负责端口转发,就不需要再额外使用 Nginx。

最终结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
浏览器
│
│ HTTPS
▼
https://yourdomain.ct8.pl/my_path
│
▼
CT8 Node.js
│
▼
39439
│
▼
Sub-Store

十四、创建启动脚本

为了避免每次输入环境变量,可以创建:

1
2
cd ~/Sub-Store/backend
vim start.sh

写入:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

export PATH="/home/your_username/.local/bin:$PATH"

export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

cd "$(dirname "$0")" || exit 1

# 使用 pnpm start
exec /home/your_username/.local/bin/pnpm start

保存后:

1
chmod +x start.sh

以后:

1
./start.sh

即可启动。


十五、为什么推荐 pnpm 绝对路径

不要只写:

1
pnpm start

而推荐:

1
/home/your_username/.local/bin/pnpm start

because CT8 的 cron、后台脚本 and SSH Shell 的 PATH 可能不同。

这样即使:

1
which pnpm

没有结果,脚本仍然可以找到 pnpm.


十六、让 Sub-Store 后台运行

测试确认正常后,可以使用:

1
nohup ./start.sh > sub-store.log 2>&1 &

查看日志:

1
tail -f sub-store.log

查看进程:

1
ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep

查看端口:

1
ss -lnt | grep 39439

十七、增加自动保活

CT8 上可以使用 cron 定时检查。

编辑:

1
crontab -e

例如每 5 分钟检查一次:

1
*/5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1

但是这里有一个重要问题:

如果 start.sh 每次都直接:

1
pnpm start

就会产生多个 Sub-Store 实例。

所以必须增加防重复启动。


十八、推荐的最终保活脚本

把 start.sh 修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash

export PATH="/home/your_username/.local/bin:$PATH"

export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

cd "$(dirname "$0")" || exit 1

# 检查 Sub-Store 主进程
if pgrep -f "/Sub-Store/backend.*babel-node.*src/main.js" >/dev/null 2>&1; then
echo "[Sub-Store] 已经在运行,无需启动。"
exit 0
fi

# 检查 nodemon
if pgrep -f "/Sub-Store/backend.*nodemon.*src" >/dev/null 2>&1; then
echo "[Sub-Store] nodemon 已经在运行,无需启动。"
exit 0
fi

# 检查 39439 端口
if ss -lnt 2>/dev/null | grep -q ':39439 '; then
echo "[Sub-Store] 39439 端口已经被占用,无需启动。"
exit 0
fi

echo "[Sub-Store] 服务未运行,正在启动..."

nohup /home/your_username/.local/bin/pnpm start >> sub-store.log 2>&1 &

echo "[Sub-Store] 启动命令已执行,PID: $!"

十九、为什么不要只用 curl 判断

有些情况下:

1
curl http://127.0.0.1:39439/

不一定能准确判断 Node.js 是否已经运行。

例如:

  • / 返回非 2xx
  • 后端要求 /my_path
  • 服务刚启动还没有完成初始化
  • Node.js 已监听端口但 HTTP 尚未完全准备好

这时:

1
curl -sf

可能返回失败。

如果 cron 频繁调用脚本,就可能导致:

1
2
3
4
pnpm start
pnpm start
pnpm start
pnpm start

最终产生多个 Node.js 实例。


二十、清理重复的 Sub-Store 进程

如果之前误启动了多个实例,可以先查看:

1
ps -aux | grep "Sub-Store/backend" | grep -v grep

如果确认都是重复的 Sub-Store,可以停止:

1
pkill -f "/Sub-Store/backend.*"

二十一、检查 Sub-Store 是否正常运行

查看进程

1
ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep

查看端口

1
ss -lnt | grep 39439

查看日志

1
tail -100 sub-store.log

实时查看:

1
tail -f sub-store.log

测试本地接口

1
curl -i http://127.0.0.1:39439/

测试网站

浏览器打开:

1
https://你的域名/my_path

二十二、常见问题

问题 1:pnpm: command not found

执行:

1
find "$HOME" -type f -name pnpm 2>/dev/null | head

如果找到:

1
/home/your_username/.local/bin/pnpm

使用:

1
export PATH="$HOME/.local/bin:$PATH"

或者直接使用:

1
/home/your_username/.local/bin/pnpm

问题 2:直接 node src/main.js 报错

例如:

1
Cannot find package '@/core'

不要直接:

1
node src/main.js

使用:

1
pnpm start

because pnpm start 会通过项目配置启动:

1
2
3
nodemon
→ babel-node
→ src/main.js

问题 3:启动后监听 3000

如果看到:

1
listen EPERM: operation not permitted :::3000

说明项目没有使用你设置 of 端口,或者 PORT 没有被正确读取。

检查项目源码中实际使用的端口变量:

1
grep -RniE "3000|PORT|listen\(" src | head -100

对于当前 Sub-Store 版本,实际部署时应以项目自身支持的环境变量为准。

官方仓库的开发说明也使用 SUB_STORE_BACKEND_API_PORT 指定开发后端端口,例如:

1
SUB_STORE_BACKEND_API_PORT=3000 pnpm esbuild:dev

因此不同启动脚本可能使用不同的端口变量,不能一概而论。


问题 4:/my_path 返回 404

检查:

1
2
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

然后重新启动:

1
./start.sh

同时确认 CT8 Node.js 应用确实将域名请求转发到了:

1
39439

问题 5:重复运行 start.sh

如果出现多个:

1
pnpm start

或者多个:

1
nodemon

不要继续运行脚本。

先:

1
ps -aux | grep "Sub-Store/backend" | grep -v grep

确认后清理:

1
pkill -f "/Sub-Store/backend.*"

然后只启动一个:

1
./start.sh

二十三、完整部署流程

以后重新部署 Sub-Store,可以直接按照下面流程操作。

1. 登录 CT8

1
ssh 用户名@服务器

2. 检查 Node.js

1
node -v

3. 检查 pnpm

1
pnpm -v

如果找不到:

1
export PATH="$HOME/.local/bin:$PATH"

4. 获取源码

1
2
cd ~
git clone https://github.com/sub-store-org/Sub-Store.git

5. 进入 backend

1
cd ~/Sub-Store/backend

6. 安装依赖

1
pnpm install

7. 创建启动脚本

1
vim start.sh

内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash

export PATH="/home/your_username/.local/bin:$PATH"

export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

cd "$(dirname "$0")" || exit 1

if pgrep -f "/Sub-Store/backend.*babel-node.*src/main.js" >/dev/null 2>&1; then
echo "[Sub-Store] 已经在运行,无需启动。"
exit 0
fi

if pgrep -f "/Sub-Store/backend.*nodemon.*src" >/dev/null 2>&1; then
echo "[Sub-Store] nodemon 已经在运行,无需启动。"
exit 0
fi

if ss -lnt 2>/dev/null | grep -q ':39439 '; then
echo "[Sub-Store] 39439 端口已经被占用,无需启动。"
exit 0
fi

echo "[Sub-Store] 服务未运行,正在启动..."

nohup /home/your_username/.local/bin/pnpm start >> sub-store.log 2>&1 &

echo "[Sub-Store] 启动命令已执行,PID: $!"

8. 添加执行权限

1
chmod +x start.sh

9. 启动

1
./start.sh

10. 检查

1
ss -lnt | grep 39439

11. 设置 cron

1
crontab -e

加入:

1
*/5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1

最终形成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
             CT8
│
▼
Node.js v22
│
▼
pnpm 11.x
│
▼
~/Sub-Store/backend
│
▼
pnpm start
│
┌──────┴──────┐
▼ ▼
nodemon babel-node
│ │
└──────┬──────┘
▼
Sub-Store
│
▼
:39439
│
▼
/my_path
│
▼
https://你的域名/my_path

二十四、更新 Sub-Store

以后更新源码:

1
2
cd ~/Sub-Store
git pull

进入后端:

1
cd backend

重新安装依赖:

1
pnpm install

然后重启:

1
2
pkill -f "/Sub-Store/backend.*"
./start.sh

查看:

1
tail -f sub-store.log

二十五、最终推荐配置

对于 CT8 + Sub-Store 后端,推荐保持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Node.js
v22.22.2

pnpm
11.0.9

项目
~/Sub-Store/backend

端口
39439

前端/后端路径
/my_path

环境变量:

1
2
3
PORT=39439
SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
SUB_STORE_BACKEND_PREFIX=/my_path

启动:

1
./start.sh

后台:

1
nohup ./start.sh > sub-store.log 2>&1 &

保活:

1
*/5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1

这样就可以在 CT8 上完成一个比较完整的 Sub-Store 后端部署。


参考资料


本文由 BOSH 的博客助手 HerMes 整理 🚀

原文链接:[CT8 上安装 pnpm 并部署 Sub-Store 后端教程]

本文作者:BOSh
本文链接:http://bosh.zz.ac/posts/4073.html
版权声明:本文由BoSh发布,部分内容来源于网络。