a6f763ffd8
- 修复token泄露风险:改用X-Local-Creds-Token请求头传递token,从URL Hash读取set_token避免日志记录 - 重构全站认证逻辑:提取公用工具函数统一各服务的认证校验流程 - 升级投资账本工具:支持本地上传CSV和粘贴文本,移除硬编码路径,适配新参数格式 - 新增403/404/500标准错误响应文件,统一API错误返回格式 - 优化后端配置:关闭ETag,重构CORS中间件,新增全局错误捕获,限制敏感文件访问 - 优化web zen box的Service Worker,支持音频分片请求与断点续传 - 清理冗余前端代码,调整页面格式与权限控制
109 lines
2.9 KiB
JavaScript
109 lines
2.9 KiB
JavaScript
const CACHE = 'music-box-v5';
|
|
const AUDIO_CACHE = 'zen-audio-cache';
|
|
|
|
function isAudioRequest(request) {
|
|
return request.url.includes('.mp3') || request.url.includes('oss');
|
|
}
|
|
|
|
async function buildPartialAudioResponse(request, cachedResponse) {
|
|
const rangeHeader = request.headers.get('range');
|
|
if (!rangeHeader) return cachedResponse;
|
|
|
|
const match = /bytes=(\d*)-(\d*)/.exec(rangeHeader);
|
|
if (!match) return cachedResponse;
|
|
|
|
const blob = await cachedResponse.blob();
|
|
const size = blob.size;
|
|
let start = match[1] ? Number(match[1]) : 0;
|
|
let end = match[2] ? Number(match[2]) : size - 1;
|
|
|
|
if (!Number.isFinite(start) || start < 0) start = 0;
|
|
if (!Number.isFinite(end) || end >= size) end = size - 1;
|
|
|
|
if (start >= size || start > end) {
|
|
return new Response(null, {
|
|
status: 416,
|
|
headers: {
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Range': `bytes */${size}`
|
|
}
|
|
});
|
|
}
|
|
|
|
const partialBlob = blob.slice(start, end + 1, blob.type || cachedResponse.headers.get('content-type') || 'audio/mpeg');
|
|
return new Response(partialBlob, {
|
|
status: 206,
|
|
headers: {
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Length': String(end - start + 1),
|
|
'Content-Range': `bytes ${start}-${end}/${size}`,
|
|
'Content-Type': partialBlob.type || cachedResponse.headers.get('content-type') || 'audio/mpeg'
|
|
}
|
|
});
|
|
}
|
|
|
|
async function handleAudioRequest(request) {
|
|
const audioCache = await caches.open(AUDIO_CACHE);
|
|
const cached = await audioCache.match(request.url);
|
|
|
|
if (cached) {
|
|
return buildPartialAudioResponse(request, cached);
|
|
}
|
|
|
|
return fetch(request);
|
|
}
|
|
|
|
self.addEventListener('install', e => {
|
|
self.skipWaiting();
|
|
e.waitUntil(
|
|
caches.open(CACHE).then(c => c.addAll([
|
|
'./',
|
|
'./index.html',
|
|
'./zen_box.js',
|
|
'./zen_box.css'
|
|
]))
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', e => {
|
|
e.waitUntil(
|
|
caches.keys().then(keys =>
|
|
Promise.all(
|
|
keys.filter(k => k !== CACHE && k !== AUDIO_CACHE).map(k => caches.delete(k))
|
|
)
|
|
).then(() => clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', e => {
|
|
// 音频文件:优先命中 zen-audio-cache(离线播放必需),否则走网络
|
|
if (isAudioRequest(e.request)) {
|
|
e.respondWith(
|
|
handleAudioRequest(e.request).catch(() => fetch(e.request))
|
|
);
|
|
return;
|
|
}
|
|
|
|
// playlist.json 使用 Network First
|
|
if (e.request.url.includes('playlist.json')) {
|
|
e.respondWith(
|
|
fetch(e.request)
|
|
.then(networkResponse => {
|
|
return caches.open(CACHE).then(cache => {
|
|
cache.put(e.request, networkResponse.clone());
|
|
return networkResponse;
|
|
});
|
|
})
|
|
.catch(() => {
|
|
return caches.match(e.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 其他文件(App Shell)使用 Cache First
|
|
e.respondWith(
|
|
caches.match(e.request).then(r => r || fetch(e.request))
|
|
);
|
|
});
|