docs: MIGRATION-GUIDE — дополнен фиксами из v0.0.27-29 (fetch, dedup, Blob download)
Deploy drhider / validate (push) Waiting to run
Deploy drhider / validate (push) Waiting to run
This commit is contained in:
+40
-5
@@ -57,13 +57,13 @@ Flask → ZIP + CSV → браузеру
|
|||||||
| `ss(idx, html)` | Обновление ячейки статуса в таблице |
|
| `ss(idx, html)` | Обновление ячейки статуса в таблице |
|
||||||
| `downloadZip()` / `downloadCsv()` | Скачивание результатов |
|
| `downloadZip()` / `downloadCsv()` | Скачивание результатов |
|
||||||
|
|
||||||
**Критичные исправления (v0.0.26):**
|
**Критичные исправления (v0.0.26 — v0.0.29):**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 1. Прогрев upstream при загрузке страницы — обязательно!
|
// 1. Прогрев upstream при загрузке страницы — обязательно!
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
sf = []; fi.value = ''; rr();
|
sf = []; fi.value = ''; rr();
|
||||||
fetch('/health').catch(() => {}); // ← вот это
|
fetch('/health').catch(() => {}); // ← обязательно! греет upstream
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Abort предыдущих запросов перед новым
|
// 2. Abort предыдущих запросов перед новым
|
||||||
@@ -76,23 +76,56 @@ window.addEventListener('beforeunload', () => resetAll());
|
|||||||
// 4. Сохранение списка файлов до очистки
|
// 4. Сохранение списка файлов до очистки
|
||||||
const files = sf.slice(); // копия перед resetAll()
|
const files = sf.slice(); // копия перед resetAll()
|
||||||
|
|
||||||
// 5. Цикл загрузки (по одному файлу)
|
// 5. Выбор файлов — ДОБАВЛЯЕТ в таблицу (не стирает, с дедупом)
|
||||||
|
fi.addEventListener('change', () => {
|
||||||
|
const incoming = Array.from(fi.files);
|
||||||
|
const seen = new Set(sf.map(f => f.name + '|' + f.size));
|
||||||
|
for (const f of incoming) {
|
||||||
|
if (!seen.has(f.name + '|' + f.size)) sf.push(f);
|
||||||
|
}
|
||||||
|
const d = new DataTransfer();
|
||||||
|
sf.forEach(f => d.items.add(f));
|
||||||
|
fi.files = d.files;
|
||||||
|
rr();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Цикл загрузки (fetch, не XHR — XHR давал RST на первом запросе)
|
||||||
for (let i = 0; i < total; i++) {
|
for (let i = 0; i < total; i++) {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('files', f, f.name);
|
fd.append('files', f, f.name);
|
||||||
if (currentSid) fd.append('session', currentSid);
|
if (currentSid) fd.append('session', currentSid);
|
||||||
const resp = await fetch('/api/upload?_=' + Date.now(), {
|
const resp = await fetch('/api/upload?_=' + Date.now(), { // ← fetch, не XHR
|
||||||
method: 'POST', body: fd
|
method: 'POST', body: fd
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
currentSid = data.session;
|
currentSid = data.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. SSE-обработка
|
// 7. SSE-обработка
|
||||||
activeES = new EventSource('/api/process_stream/' + currentSid);
|
activeES = new EventSource('/api/process_stream/' + currentSid);
|
||||||
activeES.addEventListener('start', ...); // файл начат
|
activeES.addEventListener('start', ...); // файл начат
|
||||||
activeES.addEventListener('done', ...); // файл готов
|
activeES.addEventListener('done', ...); // файл готов
|
||||||
activeES.addEventListener('complete', ...); // всё готово
|
activeES.addEventListener('complete', ...); // всё готово
|
||||||
|
|
||||||
|
// 8. Скачивание ZIP/CSV — через fetch+Blob (не window.location!)
|
||||||
|
function downloadZip() {
|
||||||
|
if (!currentSid) return;
|
||||||
|
fetch('/api/download/' + currentSid)
|
||||||
|
.then(r => {
|
||||||
|
const disp = r.headers.get('Content-Disposition');
|
||||||
|
const m = disp && disp.match(/filename="?(.+?)"?$/);
|
||||||
|
return Promise.all([r.blob(), m ? m[1] : 'result.zip']);
|
||||||
|
})
|
||||||
|
.then(([blob, fname]) => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = fname;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
});
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. `site/routes/api_bp.py` — API эндпоинты
|
### 2. `site/routes/api_bp.py` — API эндпоинты
|
||||||
@@ -236,6 +269,8 @@ def твоя_функция(files, **kwargs):
|
|||||||
- [ ] `fetch('/health')` при загрузке страницы
|
- [ ] `fetch('/health')` при загрузке страницы
|
||||||
- [ ] `beforeunload` → `resetAll()`
|
- [ ] `beforeunload` → `resetAll()`
|
||||||
- [ ] `activeXHR` и `activeES` очищаются перед новым запуском
|
- [ ] `activeXHR` и `activeES` очищаются перед новым запуском
|
||||||
|
- [ ] `fetch` вместо XHR (XHR на первом запросе даёт RST)
|
||||||
|
- [ ] Выбор файлов — добавляет, не стирает (с дедупом)
|
||||||
- [ ] `Connection: close` — **НЕ ставить** (Waitress/PEP 3333 запрещает hop-by-hop)
|
- [ ] `Connection: close` — **НЕ ставить** (Waitress/PEP 3333 запрещает hop-by-hop)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user