# Workdizhi 数据持久化修复方案 ## 问题分析 **当前问题**: - 数据存储在浏览器的 `localStorage` 中 - `docker-compose.yml` 中没有数据卷挂载 - 在新电脑打开时,浏览器的 localStorage 是空的,导致数据丢失 **根本原因**: - `localStorage` 是浏览器本地存储,不是服务器端存储 - 容器中没有数据持久化配置 - 数据没有保存到服务器文件系统 ## 解决方案 ### 方案 1: 添加后端 API + 文件存储(推荐) 这是最彻底的解决方案,需要修改代码添加后端 API。 #### 步骤 1: 创建简单的 Node.js 后端 创建 `server.js`: ```javascript const express = require('express'); const fs = require('fs').promises; const path = require('path'); const cors = require('cors'); const app = express(); const DATA_FILE = path.join(__dirname, 'data', 'urls.json'); app.use(cors()); app.use(express.json()); app.use(express.static('public')); // 确保数据目录存在 async function ensureDataDir() { const dataDir = path.dirname(DATA_FILE); await fs.mkdir(dataDir, { recursive: true }); } // 读取数据 app.get('/api/urls', async (req, res) => { try { await ensureDataDir(); const data = await fs.readFile(DATA_FILE, 'utf8'); res.json(JSON.parse(data)); } catch (error) { if (error.code === 'ENOENT') { res.json({ urls: [], categories: [] }); } else { res.status(500).json({ error: error.message }); } } }); // 保存数据 app.post('/api/urls', async (req, res) => { try { await ensureDataDir(); await fs.writeFile(DATA_FILE, JSON.stringify(req.body, null, 2)); res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); ``` #### 步骤 2: 修改前端代码 修改 `script.js` 中的数据加载和保存方法: ```javascript // 修改 loadData 方法 async loadData() { try { const response = await fetch('/api/urls'); const data = await response.json(); this.urls = data.urls || this.getDefaultUrls(); this.categories = data.categories || this.getDefaultCategories(); } catch (error) { console.error('从服务器加载数据失败,使用默认数据:', error); // 降级到 localStorage const savedUrls = localStorage.getItem('urlManager_urls'); const savedCategories = localStorage.getItem('urlManager_categories'); if (savedUrls) { this.urls = JSON.parse(savedUrls); } else { this.urls = this.getDefaultUrls(); } if (savedCategories) { this.categories = JSON.parse(savedCategories); } else { this.categories = this.getDefaultCategories(); } } } // 修改 saveData 方法 async saveData() { try { // 先保存到服务器 const response = await fetch('/api/urls', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ urls: this.urls, categories: this.categories }) }); if (!response.ok) throw new Error('服务器保存失败'); } catch (error) { console.error('保存到服务器失败,降级到 localStorage:', error); // 降级到 localStorage try { localStorage.setItem('urlManager_urls', JSON.stringify(this.urls)); localStorage.setItem('urlManager_categories', JSON.stringify(this.categories)); } catch (e) { console.error('保存到 localStorage 也失败:', e); } } } ``` #### 步骤 3: 更新 Dockerfile ```dockerfile FROM node:18-alpine WORKDIR /app # 安装依赖 COPY package.json . RUN npm install # 复制文件 COPY server.js . COPY index.html public/ COPY script.js public/ COPY style.css public/ # 创建数据目录 RUN mkdir -p data EXPOSE 3000 CMD ["node", "server.js"] ``` #### 步骤 4: 更新 docker-compose.yml ```yaml version: '3.8' services: web: build: . container_name: workdizhi-web ports: - "3006:3000" restart: unless-stopped volumes: # 挂载数据目录,持久化存储 - ./data:/app/data networks: - workdizhi-network networks: workdizhi-network: driver: bridge ``` ### 方案 2: 使用 Nginx + 文件存储(简单方案) 如果不想添加后端,可以使用 Nginx 的静态文件服务 + JavaScript 文件操作。 #### 步骤 1: 修改 script.js 使用 fetch API 保存到文件 ```javascript // 添加文件保存方法 async saveToFile() { try { const data = { urls: this.urls, categories: this.categories, timestamp: new Date().toISOString() }; // 使用 Blob 和下载方式保存(需要用户手动操作) const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'workdizhi_backup.json'; a.click(); URL.revokeObjectURL(url); } catch (error) { console.error('保存文件失败:', error); } } // 添加文件加载方法 async loadFromFile(file) { try { const text = await file.text(); const data = JSON.parse(text); this.urls = data.urls || this.getDefaultUrls(); this.categories = data.categories || this.getDefaultCategories(); this.saveData(); this.render(); } catch (error) { console.error('加载文件失败:', error); alert('文件格式错误或损坏'); } } ``` ### 方案 3: 导出/导入功能(临时方案) 添加导出和导入功能,让用户可以手动备份和恢复数据。 #### 修改 script.js 添加导出/导入功能 ```javascript // 导出数据 exportData() { const data = { urls: this.urls, categories: this.categories, exportDate: new Date().toISOString() }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `workdizhi_backup_${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } // 导入数据 importData(file) { const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); this.urls = data.urls || []; this.categories = data.categories || []; this.saveData(); this.render(); alert('数据导入成功!'); } catch (error) { alert('文件格式错误,导入失败'); } }; reader.readAsText(file); } ``` ## 推荐实施步骤 ### 快速修复(方案 3 - 导出/导入) 1. 添加导出/导入按钮到界面 2. 用户可以定期导出数据备份 3. 在新电脑上导入备份文件 ### 长期方案(方案 1 - 后端 API) 1. 创建 Node.js 后端服务 2. 修改前端代码使用 API 3. 添加数据卷挂载 4. 重新构建和部署 ## 当前数据位置 - **浏览器 localStorage**: `urlManager_urls`, `urlManager_categories` - **服务器**: 无持久化存储 ## 修复后的数据位置 - **服务器文件**: `/home/renjianbo/devops/workdizhi/workdizhi/data/urls.json` - **数据卷**: 挂载到容器内的 `/app/data` 或 `/usr/share/nginx/html/data` ## 注意事项 1. **备份现有数据**: 在修改前,先导出当前 localStorage 中的数据 2. **测试**: 修改后充分测试数据保存和加载功能 3. **迁移**: 如果已有数据,需要提供迁移脚本