vr-shopxo-plugin/docs/DEBUG_STATIC_FILE_SYNC.md

149 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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/` 目录读取。
PHPThinkPHP运行时加载插件类文件时使用 `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
```
---
## 本次 Caseticket_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) — 缓存机制