From ca1026d166e85b81cbd5421d1a7ec7e9d21053a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E6=82=A6?= Date: Tue, 6 Jan 2026 11:20:06 +0800 Subject: [PATCH] feat(sync): add cloud synchronization for favorites Add a new sync service and frontend integration to allow syncing favorites across devices using a token. - Configure `sync-service` in docker-compose.yml on port 7482 - Add sync token input and manual sync button to SideDrawer - Implement auto-sync logic to persist favorites to the KV store - Add logic to merge cloud favorites with local data on initialization --- docker-compose.yml | 8 ++++ index.html | 88 +++++++++++++++++++++++++++++++++++++++++- nginx.conf.example | 27 +++++++++++++ sync-server/Dockerfile | 9 +++++ sync-server/server.js | 85 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 nginx.conf.example create mode 100644 sync-server/Dockerfile create mode 100644 sync-server/server.js diff --git a/docker-compose.yml b/docker-compose.yml index d7f6363..4687484 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,4 +5,12 @@ services: build: . ports: - "7481:3000" + restart: unless-stopped + + sync-service: + build: ./sync-server + ports: + - "7482:3001" + volumes: + - ./data:/app/data restart: unless-stopped \ No newline at end of file diff --git a/index.html b/index.html index 049df05..30a9121 100644 --- a/index.html +++ b/index.html @@ -139,6 +139,9 @@ const { useState, useEffect, useRef, useMemo, useCallback } = React; const API_BASE = "https://music-dl.sayqz.com"; + // Use relative path for sync service, assuming Nginx proxy is configured to forward /api/kv to the sync service + const SYNC_API_BASE = "/api"; + const SOURCES = [ { id: 'netease', name: '网易云' }, { id: 'kuwo', name: '酷我' }, @@ -245,6 +248,34 @@ } }; + // --- Sync Service --- + const syncService = { + get: async (key, token) => { + if (!token) return null; + try { + const res = await fetch(`${SYNC_API_BASE}/kv/${key}?token=${encodeURIComponent(token)}`); + if (res.ok) { + return await res.json(); + } + } catch (e) { + console.error("Sync get failed", e); + } + return null; + }, + set: async (key, data, token) => { + if (!token) return; + try { + await fetch(`${SYNC_API_BASE}/kv/${key}?token=${encodeURIComponent(token)}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + } catch (e) { + console.error("Sync set failed", e); + } + } + }; + // --- Components --- const Icon = ({ name, size = "", className = "", onClick }) => ( @@ -319,7 +350,7 @@ ); - const SideDrawer = ({ isOpen, onClose, view, setView, quality, setQuality, onClearCache }) => { + const SideDrawer = ({ isOpen, onClose, view, setView, quality, setQuality, onClearCache, syncToken, setSyncToken, onSyncNow }) => { if (!isOpen) return null; return ( @@ -366,6 +397,26 @@ +
+ +
+ setSyncToken(e.target.value)} + placeholder="输入任意密钥以同步" + className="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-sm text-white w-full focus:outline-none focus:border-primary" + /> + +
+

使用相同的密钥在多端同步收藏列表

+
+