149 lines
5.5 KiB
Markdown
149 lines
5.5 KiB
Markdown
# 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 上传文件、或者插件重装时,这两个目录可能产生分歧:
|
||
|
||
1. `docker cp` 直接写入容器路径 → 如果写入 `app/` 但 `public/` 是镜像层预置,则不同步
|
||
2. 插件重装时 ShopXO 同步静态文件到 `public/` 但没有同步 `app/`
|
||
3. 容器内手动编辑 `public/` 后没有更新 `app/`
|
||
|
||
### 验证方法
|
||
|
||
```bash
|
||
# 对比两边的 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)
|
||
|
||
```javascript
|
||
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.js`
|
||
- `replace(/static\/js\/[^/]+$/, '')` = `http://localhost:10000/plugins/vr_ticket/`
|
||
- 拼接 `/api.php?...` → **`http://localhost:10000/plugins/vr_ticket/api.php?...`**(双斜杠,404)
|
||
|
||
### 修复方案
|
||
|
||
硬编码 `apiBase` 为绝对路径,绕过动态检测:
|
||
|
||
```javascript
|
||
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/` 后才生效。
|
||
|
||
---
|
||
|
||
## 经验总结
|
||
|
||
### 修改插件静态文件的标准流程
|
||
|
||
1. **同时修改 `app/` 和 `public/` 两个副本**:
|
||
```bash
|
||
# 方式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
|
||
```
|
||
|
||
2. **修改后立即验证 MD5**:
|
||
```bash
|
||
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 必须一致
|
||
```
|
||
|
||
3. **如果用了 ThinkPHP 模板缓存**,清理模板编译缓存:
|
||
```bash
|
||
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](EXPERIENCES.md) — 踩坑经验汇总
|
||
- [DEPLOYMENT.md](DEPLOYMENT.md) — 部署文档
|
||
- [docs/09_SHOPXO_CACHE_HANDBOOK.md](09_SHOPXO_CACHE_HANDBOOK.md) — 缓存机制
|