5.5 KiB
ShopXO 插件静态文件同步陷阱
2026-04-24 票夹 API 请求 404 根因分析
关键词:ShopXO、插件静态文件、public/ vs app/、Nginx root、docker cp、双目录陷阱
现象
票夹页面加载正常,但所有 API 请求返回 404:
curl http://localhost:10000/plugins/vr_ticket//api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=list
→ 404
浏览器 Network 面板显示 JS 正在请求 /plugins/vr_ticket//api.php(双斜杠),但该路径不存在。
根因:ShopXO 插件静态文件有两套副本
ShopXO 插件文件在容器内存在于两个位置:
| 路径 | 用途 | Nginx 是否 serve |
|---|---|---|
/var/www/html/app/plugins/vr_ticket/ |
PHP 运行时源码 | ❌ PHP 代码目录 |
/var/www/html/public/plugins/vr_ticket/ |
Nginx webroot 静态副本 | ✅ Nginx 直接 serve |
Nginx 的 root 指令是 /var/www/html/public,因此所有浏览器请求的 URL 路径 /plugins/vr_ticket/static/js/xxx.js 都会被 Nginx 从 public/plugins/ 目录读取。
PHP(ThinkPHP)运行时加载插件类文件时,使用 app/plugins/ 目录。
为什么会产生两份不同的文件?
正常情况(bind mount):如果 app/ 和 public/ 都是同一个 bind mount 的子目录(/var/www/html → host 的 shopxo 目录),它们应该是同一个源。
异常情况:当手动在容器内修改文件、Docker cp 上传文件、或者插件重装时,这两个目录可能产生分歧:
docker cp直接写入容器路径 → 如果写入app/但public/是镜像层预置,则不同步- 插件重装时 ShopXO 同步静态文件到
public/但没有同步app/ - 容器内手动编辑
public/后没有更新app/
验证方法
# 对比两边的 MD5(不一致 = 有问题)
docker exec shopxo-php md5sum \
/var/www/html/app/plugins/vr_ticket/static/js/ticket_card.js \
/var/www/html/public/plugins/vr_ticket/static/js/ticket_card.js
# 如果 MD5 不同,需要手动同步
docker cp /path/to/local/file.js \
shopxo-php:/var/www/html/public/plugins/vr_ticket/static/js/file.js
# 同时也要更新 app/(运行时需要)
docker cp /path/to/local/file.js \
shopxo-php:/var/www/html/app/plugins/vr_ticket/static/js/file.js
本次 Case:ticket_card.js 的 apiBase 构造错误
问题代码(ticket_card.js)
var apiBase = document.currentScript ?
document.currentScript.src.replace(/static\/js\/[^/]+$/, '') + '/api.php?...':
'/api.php?...';
当脚本被加载为 /plugins/vr_ticket/static/js/ticket_card.js 时:
document.currentScript.src=http://localhost:10000/plugins/vr_ticket/static/js/ticket_card.jsreplace(/static\/js\/[^/]+$/, '')=http://localhost:10000/plugins/vr_ticket/- 拼接
/api.php?...→http://localhost:10000/plugins/vr_ticket/api.php?...(双斜杠,404)
修复方案
硬编码 apiBase 为绝对路径,绕过动态检测:
var apiBase = '/api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=';
为什么之前修错了位置?
用 docker cp 更新了 app/plugins/vr_ticket/static/js/ticket_card.js,但浏览器被 Nginx serve 的是 public/plugins/vr_ticket/static/js/ticket_card.js(旧版)。直到把修复也同步到 public/ 后才生效。
经验总结
修改插件静态文件的标准流程
-
同时修改
app/和public/两个副本:# 方式A:先改源码,再同步副本 vim app/plugins/vr_ticket/static/js/xxx.js cp app/plugins/vr_ticket/static/js/xxx.js public/plugins/vr_ticket/static/js/xxx.js # 方式B:直接用 docker cp 同步到两边 docker cp local.js shopxo-php:/var/www/html/app/plugins/vr_ticket/static/js/xxx.js docker cp local.js shopxo-php:/var/www/html/public/plugins/vr_ticket/static/js/xxx.js -
修改后立即验证 MD5:
docker exec shopxo-php md5sum \ /var/www/html/app/plugins/vr_ticket/static/js/xxx.js \ /var/www/html/public/plugins/vr_ticket/static/js/xxx.js # 两个 MD5 必须一致 -
如果用了 ThinkPHP 模板缓存,清理模板编译缓存:
docker exec shopxo-php find /var/www/html/runtime/index/temp -name "*.php" -delete docker exec shopxo-php find /var/www/html/runtime/cache/shopxo -name "*.php" -delete
ShopXO 静态文件分布图
宿主机(bind mount 源)
/Users/bigemon/WorkSpace/vr-shopxo-plugin/shopxo/
├── app/plugins/vr_ticket/ ← PHP 运行时读取
│ └── static/js/ticket_card.js
└── public/plugins/vr_ticket/ ← Nginx webroot(浏览器访问)
└── static/js/ticket_card.js
↓ bind mount
容器 /var/www/html/
├── app/plugins/vr_ticket/ ← PHP runtime
└── public/plugins/vr_ticket/ ← Nginx root
预防措施
- 不要在容器内直接修改文件(用
docker cp写app/后手动复制到public/) - 插件重装后立即检查两边 MD5
- 如果 bind mount 正常,两边应该永远同步;如果不同步,检查是否有额外的 Docker 镜像层覆盖了
public/
相关文档
- EXPERIENCES.md — 踩坑经验汇总
- DEPLOYMENT.md — 部署文档
- docs/09_SHOPXO_CACHE_HANDBOOK.md — 缓存机制