diff --git a/docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md b/docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md new file mode 100644 index 0000000..0a39753 --- /dev/null +++ b/docs/MARKBASE_THREE_LAYER_ARCHITECTURE.md @@ -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, 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) -> Result, String> { + let limit = limit.unwrap_or(30).min(30); + let conn = get_user_conn(&username)?; // 使用用户特定连接 + + // ... 其余代码 +} + +/// 获取用户连接 +fn get_user_conn(username: &str) -> Result { + 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 { + 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 +**状态**: ✅ 架构设计完成,待实施 +**优先级**: 高 \ No newline at end of file diff --git a/docs/THREE_LAYER_IMPLEMENTATION_GUIDE.md b/docs/THREE_LAYER_IMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000..7c49474 --- /dev/null +++ b/docs/THREE_LAYER_IMPLEMENTATION_GUIDE.md @@ -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, + pub display_name: Option, + pub role: String, + pub status: String, + pub group_ids: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Group { + pub id: String, + pub name: String, + pub description: Option, + pub owner_id: String, + pub members: Vec, + 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, 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, + pub uploaded_by: String, + pub access_level: String, +} + +// 获取群组文件列表 +pub fn get_group_files(group_id: &str) -> Result, 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, 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, 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, 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 + + + +``` + +--- + +#### 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 天 +**优先级**: 高 +**状态**: ✅ 实施指南完成,待执行 \ No newline at end of file diff --git a/scripts/check_three_layer.sh b/scripts/check_three_layer.sh new file mode 100755 index 0000000..0fc39e9 --- /dev/null +++ b/scripts/check_three_layer.sh @@ -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 "================================================================================ +" \ No newline at end of file diff --git a/scripts/init_three_layer_db.sh b/scripts/init_three_layer_db.sh new file mode 100755 index 0000000..1165f59 --- /dev/null +++ b/scripts/init_three_layer_db.sh @@ -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 "================================================================================ +" \ No newline at end of file