Files
workdizhi/workdizhi数据持久化修复方案.md

7.8 KiB
Raw Permalink Blame History

Workdizhi 数据持久化修复方案

问题分析

当前问题

  • 数据存储在浏览器的 localStorage
  • docker-compose.yml 中没有数据卷挂载
  • 在新电脑打开时,浏览器的 localStorage 是空的,导致数据丢失

根本原因

  • localStorage 是浏览器本地存储,不是服务器端存储
  • 容器中没有数据持久化配置
  • 数据没有保存到服务器文件系统

解决方案

方案 1: 添加后端 API + 文件存储(推荐)

这是最彻底的解决方案,需要修改代码添加后端 API。

步骤 1: 创建简单的 Node.js 后端

创建 server.js

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 中的数据加载和保存方法:

// 修改 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

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

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 保存到文件

// 添加文件保存方法
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 添加导出/导入功能

// 导出数据
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. 迁移: 如果已有数据,需要提供迁移脚本