PVQ-UI 部署方案(腾讯云 COS)
唯一真源:本文件。配置/脚本变更须先更新本文件再改代码。 关联:《PVQ-UI组件库开发方案.md》§3(产物 CDN 单一源)、《PVQ-UI组件库调用手册.md》。
1. 目标
组件库产物(CSS/JS)通过 coscli(腾讯云官方命令行)一键部署到腾讯云 COS;各插件经 pvq-ui-loader.php 以固定 URL 调用:
https://cdn-buke.pvq.ink/pvq-ui/v{ver}/css/pvq-ui.css
版本号隔离在路径中,旧版本保留不删;调用方按「全站一处定义版本」(推荐,见《调用手册》§2.3)或显式锁版本(见《调用手册》§2)。
2. 前置条件
- 安装
coscli:https://cloud.tencent.com/document/product/436/63144- macOS:
brew install coscli - 其他平台:下载官方二进制并加入
PATH
- macOS:
- 腾讯云账号 + 已建存储桶(如
pvq-ui-125xxxxxxx) - 将自定义域名
cdn-buke.pvq.ink绑定到该桶的 CDN 加速域名(未绑定时见 §6 临时方案)
3. 配置文件
3.1 cos.config.example.json(入库模板)
真实文件 cos.config.json 不入库(.gitignore 已忽略),本地复制后填入。
{
"secretId": "YOUR_SECRET_ID",
"secretKey": "YOUR_SECRET_KEY",
"bucket": "pvq-ui-125xxxxxxx",
"region": "ap-guangzhou",
"customDomain": "https://cdn-buke.pvq.ink",
"localDir": "assets",
"remotePrefix": "pvq-ui",
"cacheControl": "max-age=31536000,immutable"
}
| 字段 | 说明 |
|---|---|
secretId / secretKey |
腾讯云 API 密钥(仅本地,绝不入库) |
bucket |
存储桶名(含 APPID,如 pvq-ui-125xxxxxxx) |
region |
地域,如 ap-guangzhou |
customDomain |
绑定的自定义域名,用于自检与 loader 默认 URL |
localDir |
本地产物目录,默认 assets |
remotePrefix |
COS 内前缀,默认 pvq-ui |
cacheControl |
上传时设置的 Cache-Control 响应头 |
3.2 coscli 认证(独立于仓库)
coscli 读 ~/.cos.yaml(由 coscli config 生成,含 secretId/secretKey/buckets/region)。deploy.sh 不传任何密钥,只调用 coscli,密钥安全由 coscli 本地配置承担,与仓库解耦。
4. 部署流程
cp cos.config.example.json cos.config.json # 填入真实 bucket / region / 密钥
coscli config # 按提示生成 ~/.cos.yaml
./scripts/deploy.sh # 自动读取 assets/css/pvq-ui.css 的 @version 部署
./scripts/deploy.sh 1.1.3 # 或显式指定版本号
脚本步骤:校验 coscli 与 cos.config.json → 读取版本号 →
coscli sync assets/ cos://{bucket}/{prefix}/v{ver}/ -e {endpoint} --meta "Cache-Control: {cacheControl}"
→ curl 自检自定义域名 URL 返回 200 →
php scripts/purge-cdn.php 刷新 CDN 边缘缓存(调用腾讯云 PurgeUrlsCache API,清除刚上传文件的边缘节点缓存,约 5 分钟生效;需 cos.config.json 内 secretId/secretKey,缺密钥则跳过并提示)。
依赖:本地需有 php CLI(用于解析 JSON 与读取 @version)。
4.1 为什么脚本显式传 -e(endpoint)
coscli 自行拼 endpoint 的前提是目标桶写在 ~/.cos.yaml 的 buckets 里。本机的该文件曾多次被重置,只剩 pvq-moe-1251529261、丢掉 CDN 实际使用的 buke-1251529261,于是报:
Head "https://buke-1251529261./pvq-ui%2Fv1.4.2%2Fcss%2Fpvq-ui.css": EOF
判据:报错 URL 的域名畸形(https://<bucket>./,缺 endpoint 后缀)= 该桶没进 ~/.cos.yaml 的 buckets,而不是网络问题。 治本做法即脚本现在这样:由 cos.config.json 的 region 推出 endpoint 显式传入,不再依赖 ~/.cos.yaml 的桶清单:
ENDPOINT="cos.$REGION.myqcloud.com" # ap-shanghai → cos.ap-shanghai.myqcloud.com
coscli sync assets/ cos://{bucket}/{prefix}/v{ver}/ -r -e "$ENDPOINT" --meta "Cache-Control:$CACHE"
⚠️
coscli的-e只接受主机名(不带https://);写成https://cos.ap-shanghai.myqcloud.com会报invalid bucket format, please check your cos.BaseURL。 兜底修复(若仍需修配置):备份~/.cos.yaml后,在buckets列表补一条- name: buke-1251529261 / alias: 同名 / region: ap-shanghai(密钥字段沿用 base 段,勿打印)。
5. COS 存储结构 ↔ loader URL
cos://pvq-ui-125xxxxxxx/pvq-ui/v1.1.2/ # 版本目录(URL 的一部分,旧版本永久保留)
├─ css/
│ ├─ pvq-ui.css
│ ├─ pvq-ui-admin.css
│ ├─ pvq-ui-editor.css
│ └─ pvq-ui-frontend.css
├─ js/
│ └─ pvq-ui.js
└─ components.json # 组件清单机器可读唯一真源(§3.5),与库同版本、位于版本目录根
层级约定:
deploy.sh直接sync assets/(内含css/、js/两个子目录),故 CSS 落在v{ver}/css/、JS 落在v{ver}/js/;components.json由脚本单独cp到版本目录根。loader 拼 URL 时必须带上这两个子目录名(漏写会 404)。components.json与库 CSS/JS 同版本发布,官网画廊(site/inc/gallery.php)线上运行时读它,库一升级发布,画廊自动跟随多组件、变稳定性标签(详见《组件库开发方案》§3.5)。 对应 loader 默认 URL(CDN_BASE = https://cdn-buke.pvq.ink/pvq-ui,按v{ver}/+ 子目录拼接):https://cdn-buke.pvq.ink/pvq-ui/v1.1.2/css/pvq-ui.css等。
6. 调用方式
require_once 'pvq-ui-loader.php';
PVQ_UI_Loader::enqueue( array( 'admin' ), '1.3.1' ); // 版本号按当前锁定版本替换
自定义域名尚未绑定到 COS 时,站点可用常量覆盖为 COS 默认访问域名(注意 CSS 在 css/、JS 在 js/ 子目录,且常量名须与 loader 完全一致,前端层是 PVQ_UI_FRONT_URL):
define( 'PVQ_UI_URL', 'https://pvq-ui-125xxxxxxx.cos.ap-guangzhou.myqcloud.com/pvq-ui/v1.3.1/css/pvq-ui.css' );
define( 'PVQ_UI_ADMIN_URL', 'https://pvq-ui-125xxxxxxx.cos.ap-guangzhou.myqcloud.com/pvq-ui/v1.3.1/css/pvq-ui-admin.css' );
define( 'PVQ_UI_EDITOR_URL', 'https://pvq-ui-125xxxxxxx.cos.ap-guangzhou.myqcloud.com/pvq-ui/v1.3.1/css/pvq-ui-editor.css' );
define( 'PVQ_UI_FRONT_URL', 'https://pvq-ui-125xxxxxxx.cos.ap-guangzhou.myqcloud.com/pvq-ui/v1.3.1/css/pvq-ui-frontend.css' );
define( 'PVQ_UI_JS_URL', 'https://pvq-ui-125xxxxxxx.cos.ap-guangzhou.myqcloud.com/pvq-ui/v1.3.1/js/pvq-ui.js' );
7. 缓存策略
上传即设 Cache-Control: max-age=31536000, immutable。版本号已在路径中,更新即换路径,旧 URL 永久有效,长缓存零风险。
8. 安全
cos.config.json(真实密钥)与~/.cos.yaml均 不入库;仅cos.config.example.json入库。- 严禁将
SecretId/SecretKey提交到任何仓库或文档。 - 本仓库
.gitignore已忽略cos.config.json、.cos.yaml、本地临时文件。
9. 多版本共存与回滚
每次 deploy 上传到独立 v{ver}/ 目录,旧版本保留。回滚只需调用方把 enqueue 的版本号改回旧版,COS 上无需删除旧目录。
10. 本方案不 bump 库产物版本
本次新增的是部署工具链(配置 + 脚本 + 文档),CSS/JS 产物内容未变,不提升库版本号;仅在 readme.md 增加「部署」段、CHANGELOG.md 补一条部署工具记录。
11. 官网部署(FTP → pvq-ui.pvq.ink)
官网是库的对外门面:展示全部组件、说明与调用方法,位于仓库 site/。采用 PHP 动态渲染 doc/ 下的 Markdown(文档为单一真源,改一处网站自动同步),组件资源直接引用 COS CDN(cdn-buke.pvq.ink),不另存一份。
目标主机为 PHP 虚拟主机,部署走 FTP:
cp ftp.config.example.json ftp.config.json # 填入真实 FTP 信息(host/user/pass/remoteDir…)
php scripts/deploy-ftp.php # PHP 原生 ftp 扩展,镜像 site/ + doc/ 到 remoteDir
脚本行为:
- 读
ftp.config.json(真实文件,不入库)→ 连接(优先 FTPSftp_ssl_connect,失败回退普通 FTP)→ 被动模式。 - 递归上传
site/→remoteDir,并把doc/→remoteDir/doc(官网实时渲染用)。 site/lib/Parsedown.php(Markdown 解析器)随site/一并上传。- 排除本地专用文件(v1.2.10 新增,重要):
site/.local是本地预览标记,config.php检测到它就把PVQ_CDN指向/assets/lib(本地软链副本、零缓存);线上必须走 CDN 模式,绝不能有.local。早期脚本无排除逻辑,.local被一并上传 → 线上进入本地模式 → 引用/assets/lib/*,而该路径依赖site/assets/lib -> ../../assets的符号链接(FTP 无法在线上复现软链)→ 全部 404 → 官网组件无样式。故deploy-ftp.php内置排除名单(.local、.gitignore、.git、.DS_Store、node_modules、.codebuddy、index.html、404.html、.htaccess、.user.ini),只影响上传,不动仓库本地文件,本地预览照旧。index.html/404.html(v1.4.0 新增排除):二者是宝塔等 PHP 主机生成的「站点创建成功」占位页,.gitignore已标注为非源码。不排除就会被上传成公网可访问的/index.html,与官网首页并存(本主机 DirectoryIndex 以index.php优先、不会遮蔽首页,但占位页仍不该对公网可见)。若线上已存在旧副本,手动 FTPDELE /index.html、DELE /404.html即可清除,无需重传全站。.htaccess/.user.ini(v1.4.0 新增排除):同属主机本地环境文件(.gitignore已标注)。site/.htaccess只有 1 字节,若上传会覆盖主机自己的 rewrite 配置;site/.user.ini含open_basedir等环境配置,线上为 root 所有,每次上传都以553 Can't open that file失败、只刷错误日志。二者一律不传。- 配套:
site/assets/lib软链仅供本地使用(单一真源),线上组件资源一律走 CDN(cdn-buke.pvq.ink/pvq-ui/v{ver}/),因此软链不可上传也不必上传。 - 若线上已误传
.local:手动 FTP 删除(DELE /.local)即可切回 CDN 模式,无需重传全站。
安全与耦合:
- 真实
ftp.config.json含 FTP 密码,不入库(.gitignore已忽略),仅ftp.config.example.json入库。 - 网站版本与库版本耦合:
site/inc/config.php的PVQ_UI_VERSION必须与当前库版本一致;升库版本时同步修改它,否则网站引用的 CDN 资源路径会失效。 - 域名
pvq-ui.pvq.ink的 web 根指向remoteDir,访问https://pvq-ui.pvq.ink/即官网首页。 - 组件清单 JSON 双轨(与《组件库开发方案》§3.5 一致):
components.json位于site/inc/,随site/由deploy-ftp.php上传到 web 根;同时deploy.sh把它同步到 COSpvq-ui/v{ver}/components.json。官网画廊gallery.php本地开发时读仓库内site/inc/components.json(改 JSON 刷新即见),线上运行时读 COS 同版本 JSON——新增组件只改components.json,画廊自动出现,无需改gallery.php。 doc/必须随部署进入 web 根内的doc/目录:线上deploy-ftp.php把doc/上传到remoteDir/doc,与铺在 web 根的site/程序同级;docs.php探测is_dir(__DIR__.'/doc')为真 → 走「同级 doc」分支,不越级访问。这是线上唯一正确形态,切勿让docs.php走到../doc分支。
11.1 本地预览与 open_basedir 约束
docs.php 的文档根探测(site/docs.php:8):
$docBase = is_dir(__DIR__ . '/doc') ? __DIR__ . '/doc' : __DIR__ . '/../doc';
- 线上(web 根 =
remoteDir,site/内容铺在 web 根,doc/在web根/doc):__DIR__.'/doc'存在 → 走前半段,安全。 - 本地直接把
site/当 web 根跑(宝塔建站 / 本地 nginx 根指向site/):若主机对站点设open_basedir锁到site/(+/tmp),则仓库根doc/(在site/之外)不会被纳入 →docs.php走到../doc分支 →is_file()被open_basedir拦截,报Warning: is_file(): open_basedir restriction in effect …并落到「文档未找到」。
方案 A(推荐,贴合线上架构):让 site/doc 存在即可,不动环境安全配置。
- 本地执行:
cp -r doc site/doc(真实复制,不可用软链——open_basedir会解析软链真实路径,仍判越界)。 - 把
site/doc/加入.gitignore:仓库根的doc/才是单一真源,site/doc仅本地预览派生产物,不得入库。 - 之后
docs.php探测is_dir(__DIR__.'/doc')为真,warning 消失、文档正常渲染。 - 改了仓库根
doc/后需重新cp -r doc site/doc才反映到本地预览(线上由deploy-ftp.php自动同步,无需手动)。 - 备选方案 B(不推荐):放宽
open_basedir把仓库根纳入。代价是削弱站点安全隔离,仅为迁就代码,非长久之计。