feat: 龙虾记忆同步系统完整版本

功能特性:
- 文件树展示
- 差异对比
- 双向同步(本地 <-> 数据库)
- 版本历史追踪
- 统计信息展示

核心补丁:
1. 分块读取与流式传输(防止大文件内存飙升)
2. .lobsterignore 机制(排除临时文件)
3. 操作溯源(Audit Log,记录同步历史)

技术栈:
- 后端: Django + DRF + PostgreSQL
- 前端: React + Ant Design
- 部署: Docker + Docker Compose

项目已完整部署,可直接使用 docker-compose up -d 启动
This commit is contained in:
道童
2026-04-05 12:43:24 +00:00
commit 4374379d3f
26 changed files with 3270 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
import React, { useState, useEffect } from 'react';
import { Spin, Alert, Tabs } from 'antd';
import ReactDiffViewer from 'react-diff-viewer-continued';
import api from '../api';
export default function FileDiff({ filePath, lobsterId }) {
const [loading, setLoading] = useState(false);
const [diffData, setDiffData] = useState(null);
const [error, setError] = useState(null);
const loadDiff = async () => {
setLoading(true);
setError(null);
try {
const response = await api.get('/diff/', {
params: { file_path: filePath, lobster_id: lobsterId }
});
if (response.success) {
setDiffData(response.data);
} else {
setError(response.error || '加载失败');
}
} catch (err) {
setError(err.message || '网络错误');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (filePath) {
loadDiff();
}
}, [filePath]);
if (loading) {
return <Spin tip="加载中..." />;
}
if (error) {
return <Alert message={error} type="error" />;
}
if (!diffData) {
return <Alert message="请选择文件" type="info" />;
}
const { local_content, db_content, status, diff } = diffData;
// 文件不存在的情况
if (!local_content && !db_content) {
return <Alert message="文件不存在" type="warning" />;
}
if (!local_content) {
return (
<Alert
message="文件仅存在于数据库"
description="点击「同步到本地」将文件恢复到本地"
type="info"
showIcon
/>
);
}
if (!db_content) {
return (
<Alert
message="文件仅存在于本地"
description="点击「同步到数据库」将文件备份到数据库"
type="warning"
showIcon
/>
);
}
const STATUS_MESSAGES = {
consistent: '文件内容一致',
local_newer: '本地文件有更新',
db_newer: '数据库版本更新',
conflict: '文件内容冲突',
};
return (
<div>
<Alert
message={STATUS_MESSAGES[status] || '未知状态'}
type={status === 'consistent' ? 'success' : 'warning'}
style={{ marginBottom: 16 }}
showIcon
/>
<Tabs
defaultActiveKey="diff"
items={[
{
key: 'diff',
label: '差异对比',
children: (
<div style={{ overflowX: 'auto' }}>
<ReactDiffViewer
oldValue={db_content || ''}
newValue={local_content || ''}
splitView={true}
useDarkTheme={false}
leftTitle="数据库版本"
rightTitle="本地版本"
/>
</div>
),
},
{
key: 'local',
label: '本地内容',
children: (
<pre style={{
padding: '16px',
background: '#f5f5f5',
borderRadius: '4px',
maxHeight: '500px',
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
}}>
{local_content}
</pre>
),
},
{
key: 'db',
label: '数据库内容',
children: (
<pre style={{
padding: '16px',
background: '#f5f5f5',
borderRadius: '4px',
maxHeight: '500px',
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
}}>
{db_content}
</pre>
),
},
]}
/>
</div>
);
}