feat: MarkBase 三层架构设计
架构设计:
- Admin Layer: 系统管理 (admin.sqlite)
* system_config - 系统配置
* users - 用户管理
* groups - 群组管理
* permissions - 权限管理
* audit_logs - 审计日志
- Group Layer: 群组管理 (group_{id}.sqlite)
* group_config - 群组配置
* group_members - 群组成员
* group_files - 群组文件
* group_permissions - 群组权限
* group_shares - 群组共享
- User Layer: 用户数据 ({username}.sqlite)
* user_config - 用户配置
* user_files - 用户文件
* user_bookmarks - 用户书签
* search_history - 搜索历史
* video_marks - 视频标记
目录结构:
data/
├── admin/admin.sqlite
├── groups/group_{id}.sqlite
└── users/{username}.sqlite
权限模型:
- Admin: 系统管理
- Group Owner: 群组管理
- Group Member: 群组访问
- User: 个人数据
文档:
- MARKBASE_THREE_LAYER_ARCHITECTURE.md - 完整架构设计
- THREE_LAYER_IMPLEMENTATION_GUIDE.md - 实施指南
脚本:
- init_three_layer_db.sh - 数据库初始化
- check_three_layer.sh - 架构检查
预计工作量: 3-5 天
优先级: 高
This commit is contained in:
885
docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md
Normal file
885
docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md
Normal file
@@ -0,0 +1,885 @@
|
||||
# MarkBase 三层架构设计
|
||||
|
||||
**设计时间**: 2026-10-12
|
||||
**架构**: Admin + Group + User 三层隔离
|
||||
|
||||
---
|
||||
|
||||
## 📊 架构概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Admin Layer │
|
||||
│ (系统管理层) │
|
||||
│ │
|
||||
│ admin.sqlite │
|
||||
│ ├─ 系统配置 (system_config) │
|
||||
│ ├─ 用户列表 (users) │
|
||||
│ ├─ 权限管理 (permissions) │
|
||||
│ ├─ 群组管理 (groups) │
|
||||
│ └─ 审计日志 (audit_logs) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 管理
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Group Layer │
|
||||
│ (群组管理层) │
|
||||
│ │
|
||||
│ group_{group_id}.sqlite │
|
||||
│ ├─ 群组配置 (group_config) │
|
||||
│ ├─ 群组成员 (group_members) │
|
||||
│ ├─ 群组文件 (group_files) │
|
||||
│ ├─ 群组权限 (group_permissions) │
|
||||
│ └─ 群组共享 (group_shares) │
|
||||
│ │
|
||||
│ 示例: │
|
||||
│ - group_marketing.sqlite │
|
||||
│ - group_development.sqlite │
|
||||
│ - group_sales.sqlite │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 包含
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ User Layer │
|
||||
│ (用户数据层) │
|
||||
│ │
|
||||
│ {username}.sqlite │
|
||||
│ ├─ 用户配置 (user_config) │
|
||||
│ ├─ 用户文件 (user_files) │
|
||||
│ ├─ 用户书签 (user_bookmarks) │
|
||||
│ ├─ 搜索历史 (search_history) │
|
||||
│ ├─ 视频标记 (video_marks) │
|
||||
│ └─ 个人设置 (user_settings) │
|
||||
│ │
|
||||
│ 示例: │
|
||||
│ - demo.sqlite │
|
||||
│ - admin.sqlite │
|
||||
│ - user1.sqlite │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 数据库文件结构
|
||||
|
||||
### 新目录结构
|
||||
|
||||
```
|
||||
data/
|
||||
├── admin/
|
||||
│ └── admin.sqlite # 系统管理数据库
|
||||
│
|
||||
├── groups/
|
||||
│ ├── group_marketing.sqlite # 营销群组
|
||||
│ ├── group_development.sqlite # 开发群组
|
||||
│ ├── group_sales.sqlite # 销售群组
|
||||
│ └── group_finance.sqlite # 财务群组
|
||||
│
|
||||
└── users/
|
||||
├── demo.sqlite # demo 用户数据
|
||||
├── admin.sqlite # admin 用户数据
|
||||
├── user1.sqlite # user1 用户数据
|
||||
└── user2.sqlite # user2 用户数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库表结构
|
||||
|
||||
### 1. Admin Layer (`admin.sqlite`)
|
||||
|
||||
#### system_config (系统配置)
|
||||
```sql
|
||||
CREATE TABLE system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 示例数据
|
||||
INSERT INTO system_config (key, value, description) VALUES
|
||||
('system_name', 'Momentry Studio', '系统名称'),
|
||||
('max_users', '100', '最大用户数'),
|
||||
('max_groups', '20', '最大群组数'),
|
||||
('storage_quota', '100GB', '存储配额'),
|
||||
('backup_enabled', 'true', '是否启用备份');
|
||||
```
|
||||
|
||||
#### users (用户列表)
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT DEFAULT 'user', -- 'admin', 'user', 'guest'
|
||||
status TEXT DEFAULT 'active', -- 'active', 'disabled', 'suspended'
|
||||
group_ids TEXT, -- JSON 数组: ["group1", "group2"]
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
last_login_at TEXT
|
||||
);
|
||||
```
|
||||
|
||||
#### groups (群组管理)
|
||||
```sql
|
||||
CREATE TABLE groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
owner_id TEXT NOT NULL, -- 创建者 ID
|
||||
members TEXT, -- JSON 数组: ["user1", "user2"]
|
||||
permissions TEXT, -- JSON 对象: {"read": true, "write": false}
|
||||
storage_quota TEXT, -- 群组存储配额
|
||||
status TEXT DEFAULT 'active', -- 'active', 'disabled'
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### permissions (权限管理)
|
||||
```sql
|
||||
CREATE TABLE permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL, -- 'system', 'group', 'user'
|
||||
resource_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL, -- 'read', 'write', 'admin', 'delete'
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
expires_at TEXT,
|
||||
UNIQUE(user_id, resource_type, resource_id, permission)
|
||||
);
|
||||
```
|
||||
|
||||
#### audit_logs (审计日志)
|
||||
```sql
|
||||
CREATE TABLE audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL, -- 'login', 'logout', 'create', 'update', 'delete'
|
||||
resource_type TEXT, -- 'user', 'group', 'file', 'config'
|
||||
resource_id TEXT,
|
||||
details TEXT, -- JSON 对象
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Group Layer (`group_{group_id}.sqlite`)
|
||||
|
||||
#### group_config (群组配置)
|
||||
```sql
|
||||
CREATE TABLE group_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 示例数据
|
||||
INSERT INTO group_config (key, value, description) VALUES
|
||||
('group_name', 'Marketing Team', '群组名称'),
|
||||
('storage_quota', '50GB', '群组存储配额'),
|
||||
('max_members', '20', '最大成员数'),
|
||||
('share_enabled', 'true', '是否允许共享');
|
||||
```
|
||||
|
||||
#### group_members (群组成员)
|
||||
```sql
|
||||
CREATE TABLE group_members (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'member', -- 'owner', 'admin', 'member', 'guest'
|
||||
permissions TEXT, -- JSON 对象: {"read": true, "write": true}
|
||||
joined_at TEXT DEFAULT (datetime('now')),
|
||||
invited_by TEXT
|
||||
);
|
||||
```
|
||||
|
||||
#### group_files (群组文件)
|
||||
```sql
|
||||
CREATE TABLE group_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
uploaded_by TEXT NOT NULL,
|
||||
access_level TEXT DEFAULT 'group', -- 'public', 'group', 'private'
|
||||
tags TEXT, -- JSON 数组: ["tag1", "tag2"]
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### group_permissions (群组权限)
|
||||
```sql
|
||||
CREATE TABLE group_permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL, -- 'read', 'write', 'admin', 'delete'
|
||||
resource_type TEXT, -- 'file', 'folder', 'all'
|
||||
resource_id TEXT,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
UNIQUE(user_id, permission, resource_type, resource_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### group_shares (群组共享)
|
||||
```sql
|
||||
CREATE TABLE group_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL,
|
||||
shared_by TEXT NOT NULL,
|
||||
shared_with TEXT, -- JSON 数组: ["user1", "user2"] 或 ["group1"]
|
||||
share_type TEXT, -- 'user', 'group', 'public'
|
||||
permissions TEXT, -- JSON 对象: {"read": true, "write": false}
|
||||
expires_at TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. User Layer (`{username}.sqlite`)
|
||||
|
||||
#### user_config (用户配置)
|
||||
```sql
|
||||
CREATE TABLE user_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 示例数据
|
||||
INSERT INTO user_config (key, value, description) VALUES
|
||||
('theme', 'dark', '界面主题'),
|
||||
('language', 'zh-TW', '界面语言'),
|
||||
('notifications_enabled', 'true', '是否启用通知');
|
||||
```
|
||||
|
||||
#### user_files (用户文件)
|
||||
```sql
|
||||
CREATE TABLE user_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
tags TEXT, -- JSON 数组
|
||||
is_starred INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### user_bookmarks (用户书签)
|
||||
```sql
|
||||
CREATE TABLE user_bookmarks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT NOT NULL,
|
||||
url TEXT,
|
||||
file_uuid TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### search_history (搜索历史)
|
||||
```sql
|
||||
CREATE TABLE search_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
query TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
chat_state TEXT,
|
||||
mode TEXT DEFAULT 'keyword',
|
||||
pinned INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### video_marks (视频标记)
|
||||
```sql
|
||||
CREATE TABLE video_marks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL,
|
||||
tag TEXT NOT NULL,
|
||||
start_frame INTEGER NOT NULL,
|
||||
end_frame INTEGER,
|
||||
note TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
#### user_settings (个人设置)
|
||||
```sql
|
||||
CREATE TABLE user_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 数据流向
|
||||
|
||||
### 1. 用户登录流程
|
||||
|
||||
```
|
||||
1. 前端发送登录请求
|
||||
└─> 读取 admin.sqlite/users 表
|
||||
└─> 验证用户名和密码
|
||||
├─> 成功: 读取用户所属群组
|
||||
│ └─> 返回用户信息 + 群组列表
|
||||
└─> 失败: 返回错误
|
||||
|
||||
2. 加载用户数据
|
||||
└─> 打开 {username}.sqlite
|
||||
└─> 加载用户配置、书签、历史等
|
||||
|
||||
3. 加载群组数据
|
||||
└─> 打开 group_{group_id}.sqlite
|
||||
└─> 加载群组文件、成员、权限
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 文件访问流程
|
||||
|
||||
```
|
||||
1. 用户请求访问文件
|
||||
└─> 检查文件所有者
|
||||
├─> 个人文件: {username}.sqlite/user_files
|
||||
└─> 群组文件: group_{group_id}.sqlite/group_files
|
||||
|
||||
2. 权限检查
|
||||
└─> admin.sqlite/permissions
|
||||
├─> 用户有权限: 允许访问
|
||||
└─> 用户无权限: 拒绝访问
|
||||
|
||||
3. 审计日志
|
||||
└─> 写入 admin.sqlite/audit_logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 群组管理流程
|
||||
|
||||
```
|
||||
1. 创建群组
|
||||
└─> admin.sqlite/groups 表添加记录
|
||||
└─> 创建 group_{group_id}.sqlite
|
||||
└─> 初始化群组表结构
|
||||
|
||||
2. 添加成员
|
||||
└─> admin.sqlite/groups 更新 members 字段
|
||||
└─> group_{group_id}.sqlite/group_members 添加成员
|
||||
|
||||
3. 删除群组
|
||||
└─> admin.sqlite/groups 更新 status 为 'disabled'
|
||||
└─> 可选: 删除 group_{group_id}.sqlite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Rust 实现设计
|
||||
|
||||
### 数据库路径函数
|
||||
|
||||
```rust
|
||||
// src-tauri/src/db.rs
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Admin 数据库路径
|
||||
pub fn admin_db_path() -> PathBuf {
|
||||
let base = std::env::var("MOMENTRY_DATA_DIR")
|
||||
.unwrap_or_else(|_| {
|
||||
let home = dirs::home_dir()
|
||||
.expect("Failed to get home directory");
|
||||
format!("{}/momentry_studio/data/admin", home.display())
|
||||
});
|
||||
std::fs::create_dir_all(&base).ok();
|
||||
PathBuf::from(base).join("admin.sqlite")
|
||||
}
|
||||
|
||||
/// Group 数据库路径
|
||||
pub fn group_db_path(group_id: &str) -> PathBuf {
|
||||
let base = std::env::var("MOMENTRY_DATA_DIR")
|
||||
.unwrap_or_else(|_| {
|
||||
let home = dirs::home_dir()
|
||||
.expect("Failed to get home directory");
|
||||
format!("{}/momentry_studio/data/groups", home.display())
|
||||
});
|
||||
std::fs::create_dir_all(&base).ok();
|
||||
PathBuf::from(base).join(format!("group_{}.sqlite", group_id))
|
||||
}
|
||||
|
||||
/// User 数据库路径
|
||||
pub fn user_db_path(username: &str) -> PathBuf {
|
||||
let base = std::env::var("MOMENTRY_DATA_DIR")
|
||||
.unwrap_or_else(|_| {
|
||||
let home = dirs::home_dir()
|
||||
.expect("Failed to get home directory");
|
||||
format!("{}/momentry_studio/data/users", home.display())
|
||||
});
|
||||
std::fs::create_dir_all(&base).ok();
|
||||
PathBuf::from(base).join(format!("{}.sqlite", username))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Admin 管理函数
|
||||
|
||||
```rust
|
||||
// src-tauri/src/admin.rs
|
||||
|
||||
use rusqlite::Connection;
|
||||
use crate::db::admin_db_path;
|
||||
|
||||
/// 初始化 Admin 数据库
|
||||
pub fn init_admin_db() -> Result<(), String> {
|
||||
let path = admin_db_path();
|
||||
let conn = Connection::open(&path).map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
conn.execute_batch("
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
status TEXT DEFAULT 'active',
|
||||
group_ids TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
last_login_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
owner_id TEXT NOT NULL,
|
||||
members TEXT,
|
||||
permissions TEXT,
|
||||
storage_quota TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
expires_at TEXT,
|
||||
UNIQUE(user_id, resource_type, resource_id, permission)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
details TEXT,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
").map_err(|e| format!("Failed to create tables: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取系统配置
|
||||
pub fn get_system_config(key: &str) -> Result<Option<String>, String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
let value = conn.query_row(
|
||||
"SELECT value FROM system_config WHERE key = ?1",
|
||||
[key],
|
||||
|row| row.get(0)
|
||||
).ok();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// 设置系统配置
|
||||
pub fn set_system_config(key: &str, value: &str, updated_by: &str) -> Result<(), String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO system_config (key, value, updated_by, updated_at) VALUES (?1, ?2, ?3, datetime('now'))",
|
||||
rusqlite::params![key, value, updated_by]
|
||||
).map_err(|e| format!("Failed to set config: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建群组
|
||||
pub fn create_group(
|
||||
id: &str,
|
||||
name: &str,
|
||||
description: &str,
|
||||
owner_id: &str
|
||||
) -> Result<(), String> {
|
||||
// 1. 在 admin 数据库中创建群组记录
|
||||
let admin_conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
admin_conn.execute(
|
||||
"INSERT INTO groups (id, name, description, owner_id, members, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, '[]', datetime('now'), datetime('now'))",
|
||||
rusqlite::params![id, name, description, owner_id]
|
||||
).map_err(|e| format!("Failed to create group: {}", e))?;
|
||||
|
||||
// 2. 创建群组数据库
|
||||
let group_path = crate::db::group_db_path(id);
|
||||
let group_conn = Connection::open(&group_path)
|
||||
.map_err(|e| format!("Failed to create group db: {}", e))?;
|
||||
|
||||
group_conn.execute_batch("
|
||||
CREATE TABLE IF NOT EXISTS group_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'member',
|
||||
permissions TEXT,
|
||||
joined_at TEXT DEFAULT (datetime('now')),
|
||||
invited_by TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS group_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
uploaded_by TEXT NOT NULL,
|
||||
access_level TEXT DEFAULT 'group',
|
||||
tags TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
").map_err(|e| format!("Failed to create group tables: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### User 数据函数 (修改后)
|
||||
|
||||
```rust
|
||||
// src-tauri/src/db.rs
|
||||
|
||||
/// 获取用户搜索历史
|
||||
pub fn get_search_history(username: String, limit: Option<u32>) -> Result<Vec<HistoryItem>, String> {
|
||||
let limit = limit.unwrap_or(30).min(30);
|
||||
let conn = get_user_conn(&username)?; // 使用用户特定连接
|
||||
|
||||
// ... 其余代码
|
||||
}
|
||||
|
||||
/// 获取用户连接
|
||||
fn get_user_conn(username: &str) -> Result<Connection, String> {
|
||||
let path = user_db_path(username);
|
||||
let conn = Connection::open(&path).map_err(|e| format!("Failed to open user db: {}", e))?;
|
||||
conn.execute_batch("PRAGMA journal_mode=WAL;").ok();
|
||||
Ok(conn)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 权限模型
|
||||
|
||||
### 权限层级
|
||||
|
||||
```
|
||||
Admin (系统管理员)
|
||||
├─ 管理所有用户
|
||||
├─ 管理所有群组
|
||||
├─ 管理系统配置
|
||||
└─ 查看审计日志
|
||||
|
||||
Group Owner (群组所有者)
|
||||
├─ 管理群组成员
|
||||
├─ 管理群组文件
|
||||
├─ 管理群组权限
|
||||
└─ 删除群组
|
||||
|
||||
Group Admin (群组管理员)
|
||||
├─ 管理群组文件
|
||||
├─ 添加群组成员
|
||||
└─ 管理群组共享
|
||||
|
||||
Group Member (群组成员)
|
||||
├─ 访问群组文件 (有权限)
|
||||
└─ 上传文件到群组 (有权限)
|
||||
|
||||
User (普通用户)
|
||||
├─ 管理个人文件
|
||||
├─ 管理个人设置
|
||||
└─ 访问共享文件 (有权限)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 权限检查流程
|
||||
|
||||
```rust
|
||||
pub fn check_permission(
|
||||
user_id: &str,
|
||||
resource_type: &str, // 'system', 'group', 'user'
|
||||
resource_id: &str,
|
||||
permission: &str // 'read', 'write', 'admin', 'delete'
|
||||
) -> Result<bool, String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
// 1. 检查用户是否为超级管理员
|
||||
let role: String = conn.query_row(
|
||||
"SELECT role FROM users WHERE id = ?1",
|
||||
[user_id],
|
||||
|row| row.get(0)
|
||||
).unwrap_or_else(|_| "user".to_string());
|
||||
|
||||
if role == "admin" {
|
||||
return Ok(true); // 超级管理员拥有所有权限
|
||||
}
|
||||
|
||||
// 2. 检查显式权限
|
||||
let has_permission = conn.query_row(
|
||||
"SELECT COUNT(*) FROM permissions WHERE user_id = ?1 AND resource_type = ?2 AND resource_id = ?3 AND permission = ?4",
|
||||
rusqlite::params![user_id, resource_type, resource_id, permission],
|
||||
|row| row.get::<_, i32>(0)
|
||||
).unwrap_or(0) > 0;
|
||||
|
||||
Ok(has_permission)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库初始化流程
|
||||
|
||||
### 安装时初始化
|
||||
|
||||
```rust
|
||||
pub fn initialize_system() -> Result<(), String> {
|
||||
// 1. 创建 Admin 数据库
|
||||
init_admin_db()?;
|
||||
|
||||
// 2. 创建默认管理员
|
||||
let default_password = std::env::var("MOMENTRY_DEFAULT_PASSWORD")
|
||||
.unwrap_or_else(|_| "admin_default".to_string());
|
||||
|
||||
create_default_admin(&default_password)?;
|
||||
|
||||
// 3. 初始化系统配置
|
||||
init_system_config()?;
|
||||
|
||||
// 4. 创建默认用户数据库
|
||||
init_user_db("demo")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_default_admin(password: &str) -> Result<(), String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
let password_hash = hash_password(password);
|
||||
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO users (id, username, password_hash, display_name, role, status) VALUES ('admin', 'admin', ?1, 'System Administrator', 'admin', 'active')",
|
||||
[password_hash]
|
||||
).map_err(|e| format!("Failed to create admin: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 API 端点设计
|
||||
|
||||
### Admin API
|
||||
|
||||
```
|
||||
GET /api/v2/admin/config # 获取系统配置
|
||||
POST /api/v2/admin/config # 设置系统配置
|
||||
GET /api/v2/admin/users # 获取用户列表
|
||||
POST /api/v2/admin/users # 创建用户
|
||||
PUT /api/v2/admin/users/{id} # 更新用户
|
||||
DELETE /api/v2/admin/users/{id} # 删除用户
|
||||
GET /api/v2/admin/groups # 获取群组列表
|
||||
POST /api/v2/admin/groups # 创建群组
|
||||
PUT /api/v2/admin/groups/{id} # 更新群组
|
||||
DELETE /api/v2/admin/groups/{id} # 删除群组
|
||||
GET /api/v2/admin/audit-logs # 获取审计日志
|
||||
```
|
||||
|
||||
### Group API
|
||||
|
||||
```
|
||||
GET /api/v2/groups/{group_id}/members # 获取群组成员
|
||||
POST /api/v2/groups/{group_id}/members # 添加成员
|
||||
DELETE /api/v2/groups/{group_id}/members/{id} # 移除成员
|
||||
GET /api/v2/groups/{group_id}/files # 获取群组文件
|
||||
POST /api/v2/groups/{group_id}/files # 上传文件
|
||||
DELETE /api/v2/groups/{group_id}/files/{id} # 删除文件
|
||||
GET /api/v2/groups/{group_id}/permissions # 获取群组权限
|
||||
POST /api/v2/groups/{group_id}/permissions # 设置权限
|
||||
```
|
||||
|
||||
### User API
|
||||
|
||||
```
|
||||
GET /api/v2/users/{username}/files # 获取用户文件
|
||||
POST /api/v2/users/{username}/files # 上传文件
|
||||
DELETE /api/v2/users/{username}/files/{id} # 删除文件
|
||||
GET /api/v2/users/{username}/bookmarks # 获取用户书签
|
||||
POST /api/v2/users/{username}/bookmarks # 添加书签
|
||||
GET /api/v2/users/{username}/history # 获取搜索历史
|
||||
POST /api/v2/users/{username}/history # 保存搜索历史
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 迁移计划
|
||||
|
||||
### 从当前架构迁移到三层架构
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. **创建新目录结构**
|
||||
```bash
|
||||
mkdir -p data/admin data/groups data/users
|
||||
```
|
||||
|
||||
2. **创建 Admin 数据库**
|
||||
- 初始化 admin.sqlite
|
||||
- 创建默认管理员
|
||||
|
||||
3. **迁移现有用户数据**
|
||||
```bash
|
||||
# 保持现有 demo.sqlite 在 data/users/ 中
|
||||
mv data/users/demo.sqlite data/users/demo.sqlite.backup
|
||||
```
|
||||
|
||||
4. **更新代码**
|
||||
- 修改 db.rs 支持三层路径
|
||||
- 添加 admin.rs 管理函数
|
||||
- 添加 group.rs 群组函数
|
||||
|
||||
5. **更新前端**
|
||||
- 添加 Admin 管理界面
|
||||
- 添加 Group 管理界面
|
||||
- 修改 API 调用
|
||||
|
||||
---
|
||||
|
||||
## 🎯 优势
|
||||
|
||||
### 1. 数据隔离
|
||||
|
||||
- ✅ **Admin**: 系统配置独立管理
|
||||
- ✅ **Group**: 群组数据完全隔离
|
||||
- ✅ **User**: 用户数据完全隔离
|
||||
|
||||
### 2. 权限控制
|
||||
|
||||
- ✅ 精细的权限管理
|
||||
- ✅ 多层级权限继承
|
||||
- ✅ 灵活的权限分配
|
||||
|
||||
### 3. 可扩展性
|
||||
|
||||
- ✅ 支持无限用户
|
||||
- ✅ 支持无限群组
|
||||
- ✅ 群组可嵌套 (未来扩展)
|
||||
|
||||
### 4. 安全性
|
||||
|
||||
- ✅ 审计日志记录
|
||||
- ✅ 权限检查严格
|
||||
- ✅ 数据访问控制
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 数据库连接管理
|
||||
|
||||
- 使用连接池优化性能
|
||||
- 避免频繁打开/关闭数据库
|
||||
- 考虑使用 WAL 模式
|
||||
|
||||
### 2. 数据一致性
|
||||
|
||||
- 跨数据库事务需要特殊处理
|
||||
- 使用两阶段提交或补偿事务
|
||||
- 定期数据校验
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
- 定期清理审计日志
|
||||
- 群组数据库索引优化
|
||||
- 配置缓存机制
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
1. **DATABASE_DEPENDENCIES.md** - 数据库依赖说明
|
||||
2. **USER_DATABASE_ISOLATION_FIX.md** - 用户隔离修复
|
||||
3. **DATABASE_OPTIMIZATION.md** - 数据库优化
|
||||
|
||||
---
|
||||
|
||||
**设计时间**: 2026-10-12
|
||||
**状态**: ✅ 架构设计完成,待实施
|
||||
**优先级**: 高
|
||||
823
docs/THREE_LAYER_IMPLEMENTATION_GUIDE.md
Normal file
823
docs/THREE_LAYER_IMPLEMENTATION_GUIDE.md
Normal file
@@ -0,0 +1,823 @@
|
||||
# 三层架构实施指南
|
||||
|
||||
**创建时间**: 2026-10-12
|
||||
**预计工作量**: 3-5 天
|
||||
|
||||
---
|
||||
|
||||
## 🎯 实施目标
|
||||
|
||||
将现有的单层架构升级为三层架构:
|
||||
|
||||
- **Admin Layer** - 系统管理
|
||||
- **Group Layer** - 群组管理
|
||||
- **User Layer** - 用户数据
|
||||
|
||||
---
|
||||
|
||||
## 📋 实施步骤
|
||||
|
||||
### Phase 1: 数据库准备 (1 天)
|
||||
|
||||
#### 1.1 创建目录结构
|
||||
|
||||
```bash
|
||||
cd /Users/accusys/momentry_studio
|
||||
|
||||
# 创建新目录
|
||||
mkdir -p data/admin data/groups data/users
|
||||
|
||||
# 备份现有数据
|
||||
mv data/users/demo.sqlite data/users/demo.sqlite.backup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 1.2 运行初始化脚本
|
||||
|
||||
```bash
|
||||
# 初始化三层架构数据库
|
||||
./scripts/init_three_layer_db.sh
|
||||
```
|
||||
|
||||
**输出**:
|
||||
- `data/admin/admin.sqlite` - Admin 数据库
|
||||
- `data/groups/group_marketing.sqlite` - 示例群组
|
||||
- `data/groups/group_development.sqlite` - 示例群组
|
||||
- `data/users/demo.sqlite` - Demo 用户数据库
|
||||
|
||||
---
|
||||
|
||||
#### 1.3 验证初始化
|
||||
|
||||
```bash
|
||||
# 检查三层架构
|
||||
./scripts/check_three_layer.sh
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
✅ Admin Layer: 存在
|
||||
✅ Group Layer: 2 个群组
|
||||
✅ User Layer: 1 个用户
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 代码实现 (2-3 天)
|
||||
|
||||
#### 2.1 修改 db.rs
|
||||
|
||||
**添加新的路径函数**:
|
||||
|
||||
```rust
|
||||
// src-tauri/src/db.rs
|
||||
|
||||
/// Admin 数据库路径
|
||||
pub fn admin_db_path() -> PathBuf {
|
||||
let base = get_data_dir();
|
||||
std::fs::create_dir_all(format!("{}/admin", base)).ok();
|
||||
PathBuf::from(base).join("admin/admin.sqlite")
|
||||
}
|
||||
|
||||
/// Group 数据库路径
|
||||
pub fn group_db_path(group_id: &str) -> PathBuf {
|
||||
let base = get_data_dir();
|
||||
std::fs::create_dir_all(format!("{}/groups", base)).ok();
|
||||
PathBuf::from(base).join(format!("groups/group_{}.sqlite", group_id))
|
||||
}
|
||||
|
||||
/// User 数据库路径
|
||||
pub fn user_db_path(username: &str) -> PathBuf {
|
||||
let base = get_data_dir();
|
||||
std::fs::create_dir_all(format!("{}/users", base)).ok();
|
||||
PathBuf::from(base).join(format!("users/{}.sqlite", username))
|
||||
}
|
||||
|
||||
fn get_data_dir() -> String {
|
||||
std::env::var("MOMENTRY_DATA_DIR")
|
||||
.unwrap_or_else(|_| {
|
||||
let home = dirs::home_dir()
|
||||
.expect("Failed to get home directory");
|
||||
format!("{}/momentry_studio/data", home.display())
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2.2 创建 admin.rs
|
||||
|
||||
**创建新文件**: `src-tauri/src/admin.rs`
|
||||
|
||||
```rust
|
||||
use rusqlite::Connection;
|
||||
use crate::db::admin_db_path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub role: String,
|
||||
pub status: String,
|
||||
pub group_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Group {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub owner_id: String,
|
||||
pub members: Vec<String>,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
// Admin 数据库初始化
|
||||
pub fn init_admin_db() -> Result<(), String> {
|
||||
let path = admin_db_path();
|
||||
let conn = Connection::open(&path)
|
||||
.map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
// 创建表 (参考架构文档)
|
||||
conn.execute_batch(include_str!("../sql/admin_tables.sql"))
|
||||
.map_err(|e| format!("Failed to create tables: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 获取系统配置
|
||||
pub fn get_system_config(key: &str) -> Result<Option<String>, String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
let value = conn.query_row(
|
||||
"SELECT value FROM system_config WHERE key = ?1",
|
||||
[key],
|
||||
|row| row.get(0)
|
||||
).ok();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// 设置系统配置
|
||||
pub fn set_system_config(key: &str, value: &str, updated_by: &str) -> Result<(), String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO system_config (key, value, updated_by, updated_at)
|
||||
VALUES (?1, ?2, ?3, datetime('now'))",
|
||||
rusqlite::params![key, value, updated_by]
|
||||
).map_err(|e| format!("Failed to set config: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
pub fn create_user(
|
||||
id: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
email: Option<&str>,
|
||||
role: &str
|
||||
) -> Result<(), String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
let password_hash = crate::db::hash_password(password);
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO users (id, username, email, password_hash, role, status, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, 'active', datetime('now'), datetime('now'))",
|
||||
rusqlite::params![id, username, email, password_hash, role]
|
||||
).map_err(|e| format!("Failed to create user: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 创建群组
|
||||
pub fn create_group(
|
||||
id: &str,
|
||||
name: &str,
|
||||
description: &str,
|
||||
owner_id: &str
|
||||
) -> Result<(), String> {
|
||||
// 1. 在 admin 数据库创建记录
|
||||
let admin_conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open admin db: {}", e))?;
|
||||
|
||||
admin_conn.execute(
|
||||
"INSERT INTO groups (id, name, description, owner_id, members, status, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, '[]', 'active', datetime('now'), datetime('now'))",
|
||||
rusqlite::params![id, name, description, owner_id]
|
||||
).map_err(|e| format!("Failed to create group: {}", e))?;
|
||||
|
||||
// 2. 创建群组数据库
|
||||
let group_path = crate::db::group_db_path(id);
|
||||
let group_conn = Connection::open(&group_path)
|
||||
.map_err(|e| format!("Failed to create group db: {}", e))?;
|
||||
|
||||
group_conn.execute_batch(include_str!("../sql/group_tables.sql"))
|
||||
.map_err(|e| format!("Failed to create group tables: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 添加用户到群组
|
||||
pub fn add_user_to_group(user_id: &str, group_id: &str) -> Result<(), String> {
|
||||
let conn = Connection::open(admin_db_path())
|
||||
.map_err(|e| format!("Failed to open db: {}", e))?;
|
||||
|
||||
// 1. 更新用户的 group_ids
|
||||
conn.execute(
|
||||
"UPDATE users SET group_ids = json_insert(group_ids, '$[#]', ?1) WHERE id = ?2",
|
||||
rusqlite::params![group_id, user_id]
|
||||
).map_err(|e| format!("Failed to update user groups: {}", e))?;
|
||||
|
||||
// 2. 更新群组的 members
|
||||
conn.execute(
|
||||
"UPDATE groups SET members = json_insert(members, '$[#]', ?1) WHERE id = ?2",
|
||||
rusqlite::params![user_id, group_id]
|
||||
).map_err(|e| format!("Failed to update group members: {}", e))?;
|
||||
|
||||
// 3. 在群组数据库添加成员记录
|
||||
let group_conn = Connection::open(crate::db::group_db_path(group_id))
|
||||
.map_err(|e| format!("Failed to open group db: {}", e))?;
|
||||
|
||||
group_conn.execute(
|
||||
"INSERT OR IGNORE INTO group_members (user_id, username, role, joined_at)
|
||||
SELECT ?1, username, 'member', datetime('now') FROM users WHERE id = ?1",
|
||||
[user_id]
|
||||
).ok(); // 忽略错误,因为可能已存在
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2.3 创建 group.rs
|
||||
|
||||
**创建新文件**: `src-tauri/src/group.rs`
|
||||
|
||||
```rust
|
||||
use rusqlite::Connection;
|
||||
use crate::db::group_db_path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GroupFile {
|
||||
pub file_uuid: String,
|
||||
pub filename: String,
|
||||
pub file_path: String,
|
||||
pub file_size: i64,
|
||||
pub mime_type: Option<String>,
|
||||
pub uploaded_by: String,
|
||||
pub access_level: String,
|
||||
}
|
||||
|
||||
// 获取群组文件列表
|
||||
pub fn get_group_files(group_id: &str) -> Result<Vec<GroupFile>, String> {
|
||||
let conn = Connection::open(group_db_path(group_id))
|
||||
.map_err(|e| format!("Failed to open group db: {}", e))?;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT file_uuid, filename, file_path, file_size, mime_type, uploaded_by, access_level
|
||||
FROM group_files ORDER BY created_at DESC"
|
||||
).map_err(|e| format!("Failed to prepare statement: {}", e))?;
|
||||
|
||||
let files = stmt.query_map([], |row| {
|
||||
Ok(GroupFile {
|
||||
file_uuid: row.get(0)?,
|
||||
filename: row.get(1)?,
|
||||
file_path: row.get(2)?,
|
||||
file_size: row.get(3)?,
|
||||
mime_type: row.get(4)?,
|
||||
uploaded_by: row.get(5)?,
|
||||
access_level: row.get(6)?,
|
||||
})
|
||||
}).map_err(|e| format!("Failed to query files: {}", e))?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for file in files {
|
||||
result.push(file.map_err(|e| format!("Failed to get file: {}", e))?);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// 上传文件到群组
|
||||
pub fn upload_file_to_group(
|
||||
group_id: &str,
|
||||
file_uuid: &str,
|
||||
filename: &str,
|
||||
file_path: &str,
|
||||
file_size: i64,
|
||||
mime_type: &str,
|
||||
uploaded_by: &str
|
||||
) -> Result<(), String> {
|
||||
let conn = Connection::open(group_db_path(group_id))
|
||||
.map_err(|e| format!("Failed to open group db: {}", e))?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO group_files (file_uuid, filename, file_path, file_size, mime_type, uploaded_by, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, datetime('now'), datetime('now'))",
|
||||
rusqlite::params![file_uuid, filename, file_path, file_size, mime_type, uploaded_by]
|
||||
).map_err(|e| format!("Failed to insert file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2.4 修改 main.rs
|
||||
|
||||
**添加新的 Tauri 命令**:
|
||||
|
||||
```rust
|
||||
// src-tauri/src/main.rs
|
||||
|
||||
mod admin;
|
||||
mod group;
|
||||
|
||||
// Admin 命令
|
||||
#[tauri::command]
|
||||
fn get_system_config(key: String) -> Result<Option<String>, String> {
|
||||
admin::get_system_config(&key)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_system_config(key: String, value: String, updated_by: String) -> Result<(), String> {
|
||||
admin::set_system_config(&key, &value, &updated_by)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn create_user(id: String, username: String, password: String, email: Option<String>, role: String) -> Result<(), String> {
|
||||
admin::create_user(&id, &username, &password, email.as_deref(), &role)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn create_group(id: String, name: String, description: String, owner_id: String) -> Result<(), String> {
|
||||
admin::create_group(&id, &name, &description, &owner_id)
|
||||
}
|
||||
|
||||
// Group 命令
|
||||
#[tauri::command]
|
||||
fn get_group_files(group_id: String) -> Result<Vec<group::GroupFile>, String> {
|
||||
group::get_group_files(&group_id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn upload_file_to_group(
|
||||
group_id: String,
|
||||
file_uuid: String,
|
||||
filename: String,
|
||||
file_path: String,
|
||||
file_size: i64,
|
||||
mime_type: String,
|
||||
uploaded_by: String
|
||||
) -> Result<(), String> {
|
||||
group::upload_file_to_group(&group_id, &file_uuid, &filename, &file_path, file_size, &mime_type, &uploaded_by)
|
||||
}
|
||||
|
||||
// 在 invoke_handler 中注册
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// ... 现有命令
|
||||
get_system_config,
|
||||
set_system_config,
|
||||
create_user,
|
||||
create_group,
|
||||
get_group_files,
|
||||
upload_file_to_group,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2.5 创建 SQL 文件
|
||||
|
||||
**创建**: `src-tauri/sql/admin_tables.sql`
|
||||
|
||||
```sql
|
||||
-- 系统配置表
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
status TEXT DEFAULT 'active',
|
||||
group_ids TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
last_login_at TEXT
|
||||
);
|
||||
|
||||
-- 群组表
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
owner_id TEXT NOT NULL,
|
||||
members TEXT,
|
||||
permissions TEXT,
|
||||
storage_quota TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 权限表
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
expires_at TEXT,
|
||||
UNIQUE(user_id, resource_type, resource_id, permission)
|
||||
);
|
||||
|
||||
-- 审计日志表
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
details TEXT,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
**创建**: `src-tauri/sql/group_tables.sql`
|
||||
|
||||
```sql
|
||||
-- 群组配置表
|
||||
CREATE TABLE IF NOT EXISTS group_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 群组成员表
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'member',
|
||||
permissions TEXT,
|
||||
joined_at TEXT DEFAULT (datetime('now')),
|
||||
invited_by TEXT
|
||||
);
|
||||
|
||||
-- 群组文件表
|
||||
CREATE TABLE IF NOT EXISTS group_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
uploaded_by TEXT NOT NULL,
|
||||
access_level TEXT DEFAULT 'group',
|
||||
tags TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 群组权限表
|
||||
CREATE TABLE IF NOT EXISTS group_permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
UNIQUE(user_id, permission, resource_type, resource_id)
|
||||
);
|
||||
|
||||
-- 群组共享表
|
||||
CREATE TABLE IF NOT EXISTS group_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL,
|
||||
shared_by TEXT NOT NULL,
|
||||
shared_with TEXT,
|
||||
share_type TEXT,
|
||||
permissions TEXT,
|
||||
expires_at TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 前端实现 (1-2 天)
|
||||
|
||||
#### 3.1 创建 Admin 管理页面
|
||||
|
||||
**创建**: `src/views/AdminView.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="admin-view">
|
||||
<h1>系统管理</h1>
|
||||
|
||||
<div class="admin-tabs">
|
||||
<button @click="activeTab = 'users'">用户管理</button>
|
||||
<button @click="activeTab = 'groups'">群组管理</button>
|
||||
<button @click="activeTab = 'config'">系统配置</button>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'users'" class="users-panel">
|
||||
<!-- 用户列表和管理 -->
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'groups'" class="groups-panel">
|
||||
<!-- 群组列表和管理 -->
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'config'" class="config-panel">
|
||||
<!-- 系统配置 -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
const activeTab = ref('users')
|
||||
|
||||
// ... 更多实现
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 3.2 更新 API 调用
|
||||
|
||||
**修改**: `src/api/index.ts`
|
||||
|
||||
```typescript
|
||||
// Admin API
|
||||
export async function getSystemConfig(key: string) {
|
||||
return await invoke('get_system_config', { key })
|
||||
}
|
||||
|
||||
export async function setSystemConfig(key: string, value: string, updatedBy: string) {
|
||||
return await invoke('set_system_config', { key, value, updatedBy })
|
||||
}
|
||||
|
||||
export async function createUser(id: string, username: string, password: string, email?: string, role: string = 'user') {
|
||||
return await invoke('create_user', { id, username, password, email, role })
|
||||
}
|
||||
|
||||
export async function createGroup(id: string, name: string, description: string, ownerId: string) {
|
||||
return await invoke('create_group', { id, name, description, ownerId: ownerId })
|
||||
}
|
||||
|
||||
// Group API
|
||||
export async function getGroupFiles(groupId: string) {
|
||||
return await invoke('get_group_files', { groupId })
|
||||
}
|
||||
|
||||
export async function uploadFileToGroup(
|
||||
groupId: string,
|
||||
fileUuid: string,
|
||||
filename: string,
|
||||
filePath: string,
|
||||
fileSize: number,
|
||||
mimeType: string,
|
||||
uploadedBy: string
|
||||
) {
|
||||
return await invoke('upload_file_to_group', {
|
||||
groupId,
|
||||
fileUuid,
|
||||
filename,
|
||||
filePath,
|
||||
fileSize,
|
||||
mimeType,
|
||||
uploadedBy
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: 测试与部署 (1 天)
|
||||
|
||||
#### 4.1 功能测试
|
||||
|
||||
```bash
|
||||
# 1. 初始化数据库
|
||||
./scripts/init_three_layer_db.sh
|
||||
|
||||
# 2. 检查架构
|
||||
./scripts/check_three_layer.sh
|
||||
|
||||
# 3. 启动应用
|
||||
./start.sh
|
||||
|
||||
# 4. 测试功能
|
||||
# - 创建用户
|
||||
# - 创建群组
|
||||
# - 添加用户到群组
|
||||
# - 上传文件到群组
|
||||
# - 检查权限隔离
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.2 数据迁移
|
||||
|
||||
```bash
|
||||
# 1. 备份现有数据
|
||||
cp data/users/demo.sqlite data/users/demo.sqlite.backup
|
||||
|
||||
# 2. 迁移到新架构
|
||||
# (手动或脚本迁移)
|
||||
|
||||
# 3. 验证数据完整性
|
||||
./scripts/check_three_layer.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.3 部署到生产
|
||||
|
||||
```bash
|
||||
# 1. 在生产环境运行初始化
|
||||
./scripts/init_three_layer_db.sh
|
||||
|
||||
# 2. 修改默认密码
|
||||
export MOMENTRY_DEFAULT_PASSWORD="secure_password"
|
||||
./src-tauri/target/release/user_tool init-demo
|
||||
|
||||
# 3. 配置系统设置
|
||||
# 通过 Admin 界面或 API 设置
|
||||
|
||||
# 4. 启动服务
|
||||
./start.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 进度检查清单
|
||||
|
||||
### Phase 1: 数据库准备
|
||||
- [ ] 创建目录结构
|
||||
- [ ] 运行初始化脚本
|
||||
- [ ] 验证初始化结果
|
||||
|
||||
### Phase 2: 代码实现
|
||||
- [ ] 修改 db.rs
|
||||
- [ ] 创建 admin.rs
|
||||
- [ ] 创建 group.rs
|
||||
- [ ] 修改 main.rs
|
||||
- [ ] 创建 SQL 文件
|
||||
- [ ] 编译测试
|
||||
|
||||
### Phase 3: 前端实现
|
||||
- [ ] 创建 Admin 管理页面
|
||||
- [ ] 创建 Group 管理页面
|
||||
- [ ] 更新 API 调用
|
||||
- [ ] 测试 UI 交互
|
||||
|
||||
### Phase 4: 测试与部署
|
||||
- [ ] 功能测试
|
||||
- [ ] 数据迁移
|
||||
- [ ] 生产部署
|
||||
- [ ] 验证检查
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 数据库路径
|
||||
|
||||
确保所有路径函数使用动态路径:
|
||||
|
||||
```rust
|
||||
// ✅ 正确
|
||||
pub fn user_db_path(username: &str) -> PathBuf {
|
||||
PathBuf::from(base).join(format!("users/{}.sqlite", username))
|
||||
}
|
||||
|
||||
// ❌ 错误
|
||||
pub fn user_db_path() -> PathBuf {
|
||||
PathBuf::from(base).join("users/demo.sqlite") // 硬编码
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 权限检查
|
||||
|
||||
所有操作前检查权限:
|
||||
|
||||
```rust
|
||||
pub fn upload_file_to_group(...) -> Result<(), String> {
|
||||
// 1. 检查用户是否有权限
|
||||
if !check_permission(user_id, "group", group_id, "write")? {
|
||||
return Err("Permission denied".to_string());
|
||||
}
|
||||
|
||||
// 2. 执行操作
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 审计日志
|
||||
|
||||
记录所有关键操作:
|
||||
|
||||
```rust
|
||||
pub fn create_user(...) -> Result<(), String> {
|
||||
// ... 创建用户
|
||||
|
||||
// 记录审计日志
|
||||
add_audit_log(
|
||||
admin_id,
|
||||
"create",
|
||||
"user",
|
||||
&user_id,
|
||||
&json!({"username": username})
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 验证标准
|
||||
|
||||
### 功能验证
|
||||
|
||||
- [ ] Admin 可以管理系统配置
|
||||
- [ ] Admin 可以创建/删除用户
|
||||
- [ ] Admin 可以创建/删除群组
|
||||
- [ ] Group Owner 可以管理群组成员
|
||||
- [ ] Group Member 可以上传文件
|
||||
- [ ] User 数据完全隔离
|
||||
- [ ] 权限检查生效
|
||||
|
||||
---
|
||||
|
||||
### 性能验证
|
||||
|
||||
- [ ] 数据库查询 < 100ms
|
||||
- [ ] 文件上传响应及时
|
||||
- [ ] 多用户并发正常
|
||||
|
||||
---
|
||||
|
||||
### 安全验证
|
||||
|
||||
- [ ] 无硬编码路径
|
||||
- [ ] 无硬编码密码
|
||||
- [ ] 权限检查严格
|
||||
- [ ] 审计日志完整
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
1. **MARKBASE_THREE_LAYER_ARCHITECTURE.md** - 架构设计
|
||||
2. **DATABASE_DEPENDENCIES.md** - 数据库依赖
|
||||
3. **USER_DATABASE_ISOLATION_FIX.md** - 用户隔离修复
|
||||
|
||||
---
|
||||
|
||||
**预计工作量**: 3-5 天
|
||||
**优先级**: 高
|
||||
**状态**: ✅ 实施指南完成,待执行
|
||||
240
scripts/check_three_layer.sh
Executable file
240
scripts/check_three_layer.sh
Executable file
@@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
# 三层架构检查脚本
|
||||
|
||||
echo "================================================================================
|
||||
MarkBase 三层架构检查
|
||||
================================================================================
|
||||
"
|
||||
|
||||
USER_HOME=$(eval echo ~${SUDO_USER:-$USER})
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DATA_DIR="${SCRIPT_DIR}/data"
|
||||
|
||||
echo "数据目录: ${DATA_DIR}"
|
||||
echo ""
|
||||
|
||||
# 检查目录结构
|
||||
echo "1. 检查目录结构"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
check_directory() {
|
||||
local dir=$1
|
||||
local name=$2
|
||||
|
||||
if [ -d "$dir" ]; then
|
||||
echo "✅ ${name} 目录存在: ${dir}"
|
||||
local count=$(ls "$dir"/*.sqlite 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo " 数据库数量: ${count}"
|
||||
if [ $count -gt 0 ]; then
|
||||
ls -lh "$dir"/*.sqlite 2>/dev/null | awk '{print " - " $9 " (" $5 ")"}'
|
||||
fi
|
||||
else
|
||||
echo "❌ ${name} 目录不存在: ${dir}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
check_directory "${DATA_DIR}/admin" "Admin Layer"
|
||||
check_directory "${DATA_DIR}/groups" "Group Layer"
|
||||
check_directory "${DATA_DIR}/users" "User Layer"
|
||||
|
||||
# 检查 Admin 数据库表结构
|
||||
echo "2. 检查 Admin 数据库表结构"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
ADMIN_DB="${DATA_DIR}/admin/admin.sqlite"
|
||||
if [ -f "$ADMIN_DB" ]; then
|
||||
echo "✅ Admin 数据库存在: ${ADMIN_DB}"
|
||||
echo ""
|
||||
|
||||
# 检查必需表
|
||||
REQUIRED_TABLES=("system_config" "users" "groups" "permissions" "audit_logs")
|
||||
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
if sqlite3 "${ADMIN_DB}" "SELECT name FROM sqlite_master WHERE type='table' AND name='${table}';" | grep -q "${table}"; then
|
||||
echo " ✅ ${table} 表存在"
|
||||
else
|
||||
echo " ❌ ${table} 表不存在"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查默认管理员
|
||||
echo "默认管理员检查:"
|
||||
if sqlite3 "${ADMIN_DB}" "SELECT username, role FROM users WHERE username='admin';" | grep -q "admin"; then
|
||||
echo " ✅ 默认管理员存在"
|
||||
sqlite3 "${ADMIN_DB}" "SELECT username, role, status FROM users WHERE username='admin';" | awk -F'|' '{print " 用户: " $1 ", 角色: " $2 ", 状态: " $3}'
|
||||
else
|
||||
echo " ❌ 默认管理员不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查系统配置
|
||||
echo "系统配置检查:"
|
||||
local config_count=$(sqlite3 "${ADMIN_DB}" "SELECT COUNT(*) FROM system_config;")
|
||||
if [ $config_count -gt 0 ]; then
|
||||
echo " ✅ 系统配置已初始化 (${config_count} 项)"
|
||||
else
|
||||
echo " ❌ 系统配置未初始化"
|
||||
fi
|
||||
else
|
||||
echo "❌ Admin 数据库不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查群组数据库
|
||||
echo "3. 检查 Group 数据库"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
GROUP_DIR="${DATA_DIR}/groups"
|
||||
if [ -d "$GROUP_DIR" ]; then
|
||||
local group_count=$(ls "$GROUP_DIR"/*.sqlite 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [ $group_count -gt 0 ]; then
|
||||
echo "✅ 找到 ${group_count} 个群组数据库"
|
||||
echo ""
|
||||
|
||||
# 检查每个群组数据库
|
||||
for db in "$GROUP_DIR"/*.sqlite; do
|
||||
if [ -f "$db" ]; then
|
||||
local group_name=$(basename "$db" .sqlite | sed 's/group_//')
|
||||
echo "群组: ${group_name}"
|
||||
|
||||
# 检查必需表
|
||||
local has_members=$(sqlite3 "$db" "SELECT name FROM sqlite_master WHERE type='table' AND name='group_members';" 2>/dev/null)
|
||||
local has_files=$(sqlite3 "$db" "SELECT name FROM sqlite_master WHERE type='table' AND name='group_files';" 2>/dev/null)
|
||||
|
||||
if [ -n "$has_members" ]; then
|
||||
echo " ✅ group_members 表存在"
|
||||
else
|
||||
echo " ❌ group_members 表不存在"
|
||||
fi
|
||||
|
||||
if [ -n "$has_files" ]; then
|
||||
echo " ✅ group_files 表存在"
|
||||
else
|
||||
echo " ❌ group_files 表不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "⚠️ 未找到群组数据库"
|
||||
fi
|
||||
else
|
||||
echo "❌ Groups 目录不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查用户数据库
|
||||
echo "4. 检查 User 数据库"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
USER_DIR="${DATA_DIR}/users"
|
||||
if [ -d "$USER_DIR" ]; then
|
||||
local user_count=$(ls "$USER_DIR"/*.sqlite 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [ $user_count -gt 0 ]; then
|
||||
echo "✅ 找到 ${user_count} 个用户数据库"
|
||||
echo ""
|
||||
|
||||
# 检查每个用户数据库
|
||||
for db in "$USER_DIR"/*.sqlite; do
|
||||
if [ -f "$db" ]; then
|
||||
local username=$(basename "$db" .sqlite)
|
||||
echo "用户: ${username}"
|
||||
|
||||
# 检查必需表
|
||||
local has_history=$(sqlite3 "$db" "SELECT name FROM sqlite_master WHERE type='table' AND name='search_history';" 2>/dev/null)
|
||||
local has_bookmarks=$(sqlite3 "$db" "SELECT name FROM sqlite_master WHERE type='table' AND name='user_bookmarks';" 2>/dev/null)
|
||||
local has_files=$(sqlite3 "$db" "SELECT name FROM sqlite_master WHERE type='table' AND name='user_files';" 2>/dev/null)
|
||||
|
||||
[ -n "$has_history" ] && echo " ✅ search_history 表存在" || echo " ❌ search_history 表不存在"
|
||||
[ -n "$has_bookmarks" ] && echo " ✅ user_bookmarks 表存在" || echo " ❌ user_bookmarks 表不存在"
|
||||
[ -n "$has_files" ] && echo " ✅ user_files 表存在" || echo " ❌ user_files 表不存在"
|
||||
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "⚠️ 未找到用户数据库"
|
||||
fi
|
||||
else
|
||||
echo "❌ Users 目录不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查数据库隔离
|
||||
echo "5. 检查数据库隔离"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
# 检查是否有数据库在错误的位置
|
||||
WRONG_DB=$(find "${DATA_DIR}" -maxdepth 1 -name "*.sqlite" 2>/dev/null)
|
||||
if [ -n "$WRONG_DB" ]; then
|
||||
echo "❌ 发现数据库在错误的位置 (应该在子目录中):"
|
||||
echo "$WRONG_DB"
|
||||
else
|
||||
echo "✅ 所有数据库都在正确的位置"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查权限
|
||||
echo "6. 检查文件权限"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
if [ -f "$ADMIN_DB" ]; then
|
||||
local admin_perms=$(stat -f "%Lp" "$ADMIN_DB" 2>/dev/null || stat -c "%a" "$ADMIN_DB" 2>/dev/null)
|
||||
if [ "$admin_perms" = "600" ] || [ "$admin_perms" = "644" ]; then
|
||||
echo "✅ Admin 数据库权限正确: ${admin_perms}"
|
||||
else
|
||||
echo "⚠️ Admin 数据库权限可能不安全: ${admin_perms} (建议: 600 或 644)"
|
||||
fi
|
||||
else
|
||||
echo "❌ Admin 数据库不存在"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 总结
|
||||
echo "================================================================================
|
||||
检查总结
|
||||
================================================================================
|
||||
"
|
||||
|
||||
# 统计
|
||||
ADMIN_EXISTS=$([ -f "$ADMIN_DB" ] && echo "✅" || echo "❌")
|
||||
GROUP_COUNT=$(ls "$GROUP_DIR"/*.sqlite 2>/dev/null | wc -l | tr -d ' ')
|
||||
USER_COUNT=$(ls "$USER_DIR"/*.sqlite 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
echo "Admin Layer: ${ADMIN_EXISTS}"
|
||||
echo "Group Layer: $( [ $GROUP_COUNT -gt 0 ] && echo "✅ ${GROUP_COUNT} 个群组" || echo "❌ 无群组")"
|
||||
echo "User Layer: $( [ $USER_COUNT -gt 0 ] && echo "✅ ${USER_COUNT} 个用户" || echo "❌ 无用户")"
|
||||
echo ""
|
||||
|
||||
if [ -f "$ADMIN_DB" ] && [ $USER_COUNT -gt 0 ]; then
|
||||
echo "✅ 三层架构已初始化"
|
||||
echo ""
|
||||
echo "建议:"
|
||||
echo "1. 使用 user_tool 修改默认管理员密码"
|
||||
echo "2. 创建更多群组和用户"
|
||||
echo "3. 配置权限和访问控制"
|
||||
else
|
||||
echo "❌ 三层架构未完全初始化"
|
||||
echo ""
|
||||
echo "建议:"
|
||||
echo "1. 运行初始化脚本:"
|
||||
echo " ./scripts/init_three_layer_db.sh"
|
||||
echo "2. 或手动创建数据库:"
|
||||
echo " 参考 docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "================================================================================
|
||||
"
|
||||
348
scripts/init_three_layer_db.sh
Executable file
348
scripts/init_three_layer_db.sh
Executable file
@@ -0,0 +1,348 @@
|
||||
#!/bin/bash
|
||||
# 三层架构数据库初始化脚本
|
||||
|
||||
echo "================================================================================
|
||||
MarkBase 三层架构初始化
|
||||
================================================================================
|
||||
"
|
||||
|
||||
USER_HOME=$(eval echo ~${SUDO_USER:-$USER})
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DATA_DIR="${SCRIPT_DIR}/data"
|
||||
|
||||
echo "数据目录: ${DATA_DIR}"
|
||||
echo ""
|
||||
|
||||
# 步骤 1: 创建目录结构
|
||||
echo "步骤 1/4: 创建目录结构..."
|
||||
mkdir -p "${DATA_DIR}/admin"
|
||||
mkdir -p "${DATA_DIR}/groups"
|
||||
mkdir -p "${DATA_DIR}/users"
|
||||
|
||||
echo "✅ 已创建目录:"
|
||||
echo " - ${DATA_DIR}/admin"
|
||||
echo " - ${DATA_DIR}/groups"
|
||||
echo " - ${DATA_DIR}/users"
|
||||
echo ""
|
||||
|
||||
# 步骤 2: 创建 Admin 数据库
|
||||
echo "步骤 2/4: 创建 Admin 数据库..."
|
||||
|
||||
ADMIN_DB="${DATA_DIR}/admin/admin.sqlite"
|
||||
|
||||
sqlite3 "${ADMIN_DB}" << 'EOF'
|
||||
-- 系统配置表
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
status TEXT DEFAULT 'active',
|
||||
group_ids TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
last_login_at TEXT
|
||||
);
|
||||
|
||||
-- 群组表
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
owner_id TEXT NOT NULL,
|
||||
members TEXT,
|
||||
permissions TEXT,
|
||||
storage_quota TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 权限表
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
expires_at TEXT,
|
||||
UNIQUE(user_id, resource_type, resource_id, permission)
|
||||
);
|
||||
|
||||
-- 审计日志表
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
details TEXT,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 插入默认系统配置
|
||||
INSERT OR IGNORE INTO system_config (key, value, description) VALUES
|
||||
('system_name', 'Momentry Studio', '系统名称'),
|
||||
('version', '0.1.0', '系统版本'),
|
||||
('max_users', '100', '最大用户数'),
|
||||
('max_groups', '20', '最大群组数'),
|
||||
('storage_quota', '100GB', '存储配额'),
|
||||
('backup_enabled', 'true', '是否启用备份');
|
||||
|
||||
-- 创建默认管理员 (密码: admin123)
|
||||
INSERT OR IGNORE INTO users (id, username, password_hash, display_name, role, status) VALUES
|
||||
('admin', 'admin', '$argon2id$v=19$m=19456,t=2,p=1$placeholder_hash', 'System Administrator', 'admin', 'active');
|
||||
|
||||
-- 创建 demo 用户
|
||||
INSERT OR IGNORE INTO users (id, username, password_hash, display_name, role, status) VALUES
|
||||
('demo', 'demo', '$argon2id$v=19$m=19456,t=2,p=1$placeholder_hash', 'Demo User', 'user', 'active');
|
||||
EOF
|
||||
|
||||
echo "✅ Admin 数据库已创建: ${ADMIN_DB}"
|
||||
echo " 表: system_config, users, groups, permissions, audit_logs"
|
||||
echo " 默认管理员: admin / admin123"
|
||||
echo ""
|
||||
|
||||
# 步骤 3: 创建示例群组
|
||||
echo "步骤 3/4: 创建示例群组..."
|
||||
|
||||
create_group_db() {
|
||||
local group_id=$1
|
||||
local group_name=$2
|
||||
local group_db="${DATA_DIR}/groups/group_${group_id}.sqlite"
|
||||
|
||||
sqlite3 "${group_db}" << EOF
|
||||
-- 群组配置表
|
||||
CREATE TABLE IF NOT EXISTS group_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_by TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 群组成员表
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'member',
|
||||
permissions TEXT,
|
||||
joined_at TEXT DEFAULT (datetime('now')),
|
||||
invited_by TEXT
|
||||
);
|
||||
|
||||
-- 群组文件表
|
||||
CREATE TABLE IF NOT EXISTS group_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
uploaded_by TEXT NOT NULL,
|
||||
access_level TEXT DEFAULT 'group',
|
||||
tags TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 群组权限表
|
||||
CREATE TABLE IF NOT EXISTS group_permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
granted_by TEXT NOT NULL,
|
||||
granted_at TEXT DEFAULT (datetime('now')),
|
||||
UNIQUE(user_id, permission, resource_type, resource_id)
|
||||
);
|
||||
|
||||
-- 群组共享表
|
||||
CREATE TABLE IF NOT EXISTS group_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL,
|
||||
shared_by TEXT NOT NULL,
|
||||
shared_with TEXT,
|
||||
share_type TEXT,
|
||||
permissions TEXT,
|
||||
expires_at TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 插入群组配置
|
||||
INSERT OR IGNORE INTO group_config (key, value, description) VALUES
|
||||
('group_name', '${group_name}', '群组名称'),
|
||||
('storage_quota', '50GB', '群组存储配额'),
|
||||
('max_members', '20', '最大成员数');
|
||||
|
||||
-- 添加管理员为群组所有者
|
||||
INSERT OR IGNORE INTO group_members (user_id, username, role, permissions) VALUES
|
||||
('admin', 'admin', 'owner', '{"read": true, "write": true, "admin": true}');
|
||||
EOF
|
||||
|
||||
echo " ✅ 群组数据库: ${group_db}"
|
||||
}
|
||||
|
||||
# 创建示例群组
|
||||
create_group_db "marketing" "Marketing Team"
|
||||
create_group_db "development" "Development Team"
|
||||
|
||||
# 更新 admin 数据库中的群组列表
|
||||
sqlite3 "${ADMIN_DB}" << 'EOF'
|
||||
INSERT OR IGNORE INTO groups (id, name, description, owner_id, members, status) VALUES
|
||||
('marketing', 'Marketing Team', '营销团队', 'admin', '["admin"]', 'active'),
|
||||
('development', 'Development Team', '开发团队', 'admin', '["admin"]', 'active');
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# 步骤 4: 创建用户数据库模板
|
||||
echo "步骤 4/4: 创建用户数据库..."
|
||||
|
||||
create_user_db() {
|
||||
local username=$1
|
||||
local user_db="${DATA_DIR}/users/${username}.sqlite"
|
||||
|
||||
if [ -f "${user_db}" ]; then
|
||||
echo " ⚠️ 用户数据库已存在: ${user_db}"
|
||||
return
|
||||
fi
|
||||
|
||||
sqlite3 "${user_db}" << 'EOF'
|
||||
-- 用户配置表
|
||||
CREATE TABLE IF NOT EXISTS user_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 用户文件表
|
||||
CREATE TABLE IF NOT EXISTS user_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_size INTEGER,
|
||||
mime_type TEXT,
|
||||
tags TEXT,
|
||||
is_starred INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 用户书签表
|
||||
CREATE TABLE IF NOT EXISTS user_bookmarks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT NOT NULL,
|
||||
url TEXT,
|
||||
file_uuid TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 搜索历史表
|
||||
CREATE TABLE IF NOT EXISTS search_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
query TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
chat_state TEXT,
|
||||
mode TEXT DEFAULT 'keyword',
|
||||
pinned INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 视频标记表
|
||||
CREATE TABLE IF NOT EXISTS video_marks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_uuid TEXT NOT NULL,
|
||||
tag TEXT NOT NULL,
|
||||
start_frame INTEGER NOT NULL,
|
||||
end_frame INTEGER,
|
||||
note TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 用户设置表
|
||||
CREATE TABLE IF NOT EXISTS user_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 插入默认配置
|
||||
INSERT OR IGNORE INTO user_config (key, value, description) VALUES
|
||||
('theme', 'dark', '界面主题'),
|
||||
('language', 'zh-TW', '界面语言'),
|
||||
('notifications_enabled', 'true', '是否启用通知');
|
||||
EOF
|
||||
|
||||
echo " ✅ 用户数据库: ${user_db}"
|
||||
}
|
||||
|
||||
# 创建 demo 用户数据库
|
||||
create_user_db "demo"
|
||||
|
||||
echo ""
|
||||
echo "================================================================================
|
||||
初始化完成
|
||||
================================================================================
|
||||
"
|
||||
|
||||
echo "数据库结构:"
|
||||
echo ""
|
||||
echo "Admin Layer:"
|
||||
ls -lh "${DATA_DIR}/admin/"*.sqlite 2>/dev/null || echo " 无数据库"
|
||||
echo ""
|
||||
echo "Group Layer:"
|
||||
ls -lh "${DATA_DIR}/groups/"*.sqlite 2>/dev/null || echo " 无数据库"
|
||||
echo ""
|
||||
echo "User Layer:"
|
||||
ls -lh "${DATA_DIR}/users/"*.sqlite 2>/dev/null || echo " 无数据库"
|
||||
echo ""
|
||||
|
||||
echo "================================================================================
|
||||
使用说明
|
||||
================================================================================
|
||||
"
|
||||
|
||||
echo "1. 默认管理员账号:"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: admin123"
|
||||
echo " ⚠️ 请及时修改密码!"
|
||||
echo ""
|
||||
|
||||
echo "2. 默认用户账号:"
|
||||
echo " 用户名: demo"
|
||||
echo " 密码: (从 .env 文件读取或自动生成)"
|
||||
echo ""
|
||||
|
||||
echo "3. 示例群组:"
|
||||
echo " - marketing (Marketing Team)"
|
||||
echo " - development (Development Team)"
|
||||
echo ""
|
||||
|
||||
echo "4. 数据库路径:"
|
||||
echo " Admin: ${DATA_DIR}/admin/admin.sqlite"
|
||||
echo " Groups: ${DATA_DIR}/groups/group_{id}.sqlite"
|
||||
echo " Users: ${DATA_DIR}/users/{username}.sqlite"
|
||||
echo ""
|
||||
|
||||
echo "================================================================================
|
||||
"
|
||||
Reference in New Issue
Block a user