From c08323e1eac5f719a21ad4aa1c1ed5a90c3b815e Mon Sep 17 00:00:00 2001 From: kuaifan Date: Fri, 2 Jan 2026 02:03:21 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=81=E7=A7=BB=E8=87=B3=20MVA=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 表结构:为 file/project/task_vectors 添加 allowed_users MULTI 字段 - 删除关系表:file_users, project_users, task_users - 搜索:使用 allowed_users = userid 进行权限过滤 - 同步:sync 时自动计算并写入 allowed_users - 级联:项目成员变更异步级联 v=1 任务,任务成员变更递归更新子任务 - 覆盖场景:visibility/parent_id/project_id 变更、子任务升级主任务等 --- app/Console/Commands/SyncFileToManticore.php | 46 +- .../Commands/SyncProjectToManticore.php | 44 +- app/Console/Commands/SyncTaskToManticore.php | 44 +- app/Module/Manticore/ManticoreBase.php | 962 ++++++------------ app/Module/Manticore/ManticoreFile.php | 197 +--- app/Module/Manticore/ManticoreProject.php | 181 +--- app/Module/Manticore/ManticoreTask.php | 401 +++----- app/Observers/FileUserObserver.php | 18 +- app/Observers/ProjectTaskObserver.php | 14 +- app/Observers/ProjectTaskUserObserver.php | 15 +- .../ProjectTaskVisibilityUserObserver.php | 20 +- app/Observers/ProjectUserObserver.php | 18 +- app/Tasks/ManticoreSyncTask.php | 110 +- 13 files changed, 593 insertions(+), 1477 deletions(-) diff --git a/app/Console/Commands/SyncFileToManticore.php b/app/Console/Commands/SyncFileToManticore.php index 7edd7f22e..393a41089 100644 --- a/app/Console/Commands/SyncFileToManticore.php +++ b/app/Console/Commands/SyncFileToManticore.php @@ -12,17 +12,16 @@ use Illuminate\Console\Command; class SyncFileToManticore extends Command { /** - * 更新数据 + * 更新数据(MVA 方案:allowed_users 在同步时自动写入) * --f: 全量更新 (默认) * --i: 增量更新(从上次更新的最后一个ID接上) - * --u: 仅同步文件用户关系(不同步文件内容) * * 清理数据 * --c: 清除索引 */ - protected $signature = 'manticore:sync-files {--f} {--i} {--c} {--u} {--batch=100}'; - protected $description = '同步文件内容到 Manticore Search'; + protected $signature = 'manticore:sync-files {--f} {--i} {--c} {--batch=100}'; + protected $description = '同步文件内容到 Manticore Search(MVA 权限方案)'; /** * @return int @@ -61,47 +60,11 @@ class SyncFileToManticore extends Command return 0; } - // 仅同步文件用户关系 - if ($this->option('u')) { - $this->info('开始同步文件用户关系...'); - $count = ManticoreFile::syncAllFileUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("文件用户关系同步完成,共 {$count} 条"); - $this->releaseLock(); - return 0; - } - - $this->info('开始同步文件数据...'); + $this->info('开始同步文件数据(MVA 方案:allowed_users 自动内联)...'); // 同步文件数据 $this->syncFiles(); - // 同步文件用户关系 - if ($this->option('f') || (!$this->option('i') && !$this->option('u'))) { - // 全量同步:清空后重建 - $this->info("\n全量同步文件用户关系..."); - $count = ManticoreFile::syncAllFileUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("文件用户关系同步完成,共 {$count} 条"); - } elseif ($this->option('i')) { - // 增量同步:只同步新增的 - $this->info("\n增量同步文件用户关系..."); - $count = ManticoreFile::syncFileUsersIncremental(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - if ($count > 0) { - $this->info("新增文件用户关系 {$count} 条"); - } - } - // 完成 $this->info("\n同步完成"); $this->releaseLock(); @@ -220,4 +183,3 @@ class SyncFileToManticore extends Command $this->info("已索引文件数量: " . ManticoreFile::getIndexedCount()); } } - diff --git a/app/Console/Commands/SyncProjectToManticore.php b/app/Console/Commands/SyncProjectToManticore.php index ad52b03a8..1d0ffb3af 100644 --- a/app/Console/Commands/SyncProjectToManticore.php +++ b/app/Console/Commands/SyncProjectToManticore.php @@ -12,17 +12,16 @@ use Illuminate\Console\Command; class SyncProjectToManticore extends Command { /** - * 更新数据 + * 更新数据(MVA 方案:allowed_users 在同步时自动写入) * --f: 全量更新 (默认) * --i: 增量更新(从上次更新的最后一个ID接上) - * --u: 仅同步项目成员关系(不同步项目内容) * * 清理数据 * --c: 清除索引 */ - protected $signature = 'manticore:sync-projects {--f} {--i} {--c} {--u} {--batch=100}'; - protected $description = '同步项目数据到 Manticore Search'; + protected $signature = 'manticore:sync-projects {--f} {--i} {--c} {--batch=100}'; + protected $description = '同步项目数据到 Manticore Search(MVA 权限方案)'; /** * @return int @@ -59,43 +58,9 @@ class SyncProjectToManticore extends Command return 0; } - // 仅同步项目成员关系 - if ($this->option('u')) { - $this->info('开始同步项目成员关系...'); - $count = ManticoreProject::syncAllProjectUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("项目成员关系同步完成,共 {$count} 条"); - $this->releaseLock(); - return 0; - } - - $this->info('开始同步项目数据...'); + $this->info('开始同步项目数据(MVA 方案:allowed_users 自动内联)...'); $this->syncProjects(); - // 同步项目成员关系 - if ($this->option('f') || (!$this->option('i') && !$this->option('u'))) { - $this->info("\n全量同步项目成员关系..."); - $count = ManticoreProject::syncAllProjectUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("项目成员关系同步完成,共 {$count} 条"); - } elseif ($this->option('i')) { - $this->info("\n增量同步项目成员关系..."); - $count = ManticoreProject::syncProjectUsersIncremental(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - if ($count > 0) { - $this->info("新增项目成员关系 {$count} 条"); - } - } - $this->info("\n同步完成"); $this->releaseLock(); return 0; @@ -178,4 +143,3 @@ class SyncProjectToManticore extends Command $this->info("已索引项目数量: " . ManticoreProject::getIndexedCount()); } } - diff --git a/app/Console/Commands/SyncTaskToManticore.php b/app/Console/Commands/SyncTaskToManticore.php index ec8862580..a14d47e82 100644 --- a/app/Console/Commands/SyncTaskToManticore.php +++ b/app/Console/Commands/SyncTaskToManticore.php @@ -12,17 +12,16 @@ use Illuminate\Console\Command; class SyncTaskToManticore extends Command { /** - * 更新数据 + * 更新数据(MVA 方案:allowed_users 在同步时自动写入) * --f: 全量更新 (默认) * --i: 增量更新(从上次更新的最后一个ID接上) - * --u: 仅同步任务成员关系(不同步任务内容) * * 清理数据 * --c: 清除索引 */ - protected $signature = 'manticore:sync-tasks {--f} {--i} {--c} {--u} {--batch=100}'; - protected $description = '同步任务数据到 Manticore Search'; + protected $signature = 'manticore:sync-tasks {--f} {--i} {--c} {--batch=100}'; + protected $description = '同步任务数据到 Manticore Search(MVA 权限方案)'; /** * @return int @@ -59,43 +58,9 @@ class SyncTaskToManticore extends Command return 0; } - // 仅同步任务成员关系 - if ($this->option('u')) { - $this->info('开始同步任务成员关系...'); - $count = ManticoreTask::syncAllTaskUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("任务成员关系同步完成,共 {$count} 条"); - $this->releaseLock(); - return 0; - } - - $this->info('开始同步任务数据...'); + $this->info('开始同步任务数据(MVA 方案:allowed_users 自动内联)...'); $this->syncTasks(); - // 同步任务成员关系 - if ($this->option('f') || (!$this->option('i') && !$this->option('u'))) { - $this->info("\n全量同步任务成员关系..."); - $count = ManticoreTask::syncAllTaskUsers(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - $this->info("任务成员关系同步完成,共 {$count} 条"); - } elseif ($this->option('i')) { - $this->info("\n增量同步任务成员关系..."); - $count = ManticoreTask::syncTaskUsersIncremental(function ($count) { - if ($count % 1000 === 0) { - $this->info(" 已同步 {$count} 条关系..."); - } - }); - if ($count > 0) { - $this->info("新增任务成员关系 {$count} 条"); - } - } - $this->info("\n同步完成"); $this->releaseLock(); return 0; @@ -180,4 +145,3 @@ class SyncTaskToManticore extends Command $this->info("已索引任务数量: " . ManticoreTask::getIndexedCount()); } } - diff --git a/app/Module/Manticore/ManticoreBase.php b/app/Module/Manticore/ManticoreBase.php index 42c3f4c21..3359c0693 100644 --- a/app/Module/Manticore/ManticoreBase.php +++ b/app/Module/Manticore/ManticoreBase.php @@ -68,11 +68,13 @@ class ManticoreBase /** * 初始化表结构 + * + * MVA 方案:使用 allowed_users MULTI 字段内联权限,无需单独的关系表 */ private function initializeTables(PDO $pdo): void { try { - // 创建文件向量表 + // 创建文件向量表(含 allowed_users MVA 权限字段) $pdo->exec(" CREATE TABLE IF NOT EXISTS file_vectors ( id BIGINT, @@ -83,6 +85,7 @@ class ManticoreBase file_type STRING, file_ext STRING, content TEXT, + allowed_users MULTI, content_vector float_vector knn_type='hnsw' knn_dims='1536' hnsw_similarity='cosine' ) charset_table='chinese' morphology='icu_chinese' "); @@ -96,17 +99,7 @@ class ManticoreBase ) "); - // 创建文件用户关系表(用于权限过滤) - $pdo->exec(" - CREATE TABLE IF NOT EXISTS file_users ( - id BIGINT, - file_id BIGINT, - userid BIGINT, - permission INTEGER - ) - "); - - // 创建用户向量表(联系人搜索) + // 创建用户向量表(联系人搜索,无需权限过滤) $pdo->exec(" CREATE TABLE IF NOT EXISTS user_vectors ( id BIGINT, @@ -120,7 +113,7 @@ class ManticoreBase ) charset_table='chinese' morphology='icu_chinese' "); - // 创建项目向量表 + // 创建项目向量表(含 allowed_users MVA 权限字段) $pdo->exec(" CREATE TABLE IF NOT EXISTS project_vectors ( id BIGINT, @@ -129,20 +122,12 @@ class ManticoreBase personal INTEGER, project_name TEXT, project_desc TEXT, + allowed_users MULTI, content_vector float_vector knn_type='hnsw' knn_dims='1536' hnsw_similarity='cosine' ) charset_table='chinese' morphology='icu_chinese' "); - // 创建项目成员表(用于权限过滤) - $pdo->exec(" - CREATE TABLE IF NOT EXISTS project_users ( - id BIGINT, - project_id BIGINT, - userid BIGINT - ) - "); - - // 创建任务向量表 + // 创建任务向量表(含 allowed_users MVA 权限字段) $pdo->exec(" CREATE TABLE IF NOT EXISTS task_vectors ( id BIGINT, @@ -153,19 +138,11 @@ class ManticoreBase task_name TEXT, task_desc TEXT, task_content TEXT, + allowed_users MULTI, content_vector float_vector knn_type='hnsw' knn_dims='1536' hnsw_similarity='cosine' ) charset_table='chinese' morphology='icu_chinese' "); - // 创建任务成员表(用于 visibility=2,3 的权限过滤) - $pdo->exec(" - CREATE TABLE IF NOT EXISTS task_users ( - id BIGINT, - task_id BIGINT, - userid BIGINT - ) - "); - Log::info('Manticore tables initialized successfully'); } catch (PDOException $e) { Log::warning('Manticore initialization warning: ' . $e->getMessage()); @@ -351,7 +328,7 @@ class ManticoreBase // ============================== /** - * 全文搜索文件 + * 全文搜索文件(使用 MVA allowed_users 权限过滤) * * @param string $keyword 关键词 * @param int $userid 用户ID(0表示不限制权限) @@ -369,8 +346,25 @@ class ManticoreBase $escapedKeyword = self::escapeMatch($keyword); if ($userid > 0) { - // 带权限过滤的搜索 - // 先搜索文件,再通过应用层过滤权限 + // 使用 MVA 权限过滤:allowed_users = 0(公开)或 allowed_users = userid + $sql = " + SELECT + id, + file_id, + userid, + pshare, + file_name, + file_type, + file_ext, + content, + WEIGHT() as relevance + FROM file_vectors + WHERE MATCH('@(file_name,content) {$escapedKeyword}') + AND (allowed_users = 0 OR allowed_users = " . (int)$userid . ") + ORDER BY relevance DESC + LIMIT " . (int)$limit . " OFFSET " . (int)$offset; + } else { + // 不限制权限 $sql = " SELECT id, @@ -386,56 +380,13 @@ class ManticoreBase WHERE MATCH('@(file_name,content) {$escapedKeyword}') ORDER BY relevance DESC LIMIT " . (int)$limit . " OFFSET " . (int)$offset; - - $results = $instance->query($sql); - - // 应用层权限过滤:用户自己的文件 或 共享文件 - if (!empty($results)) { - // 获取用户有权限的共享文件夹 - $shareFileIds = $instance->query( - "SELECT file_id FROM file_users WHERE userid IN (0, ?)", - [$userid] - ); - $allowedShares = array_column($shareFileIds, 'file_id'); - - $results = array_filter($results, function ($item) use ($userid, $allowedShares) { - // 自己的文件 - if ($item['userid'] == $userid) { - return true; - } - // 共享文件(pshare 在允许的共享列表中) - if ($item['pshare'] > 0 && in_array($item['pshare'], $allowedShares)) { - return true; - } - return false; - }); - $results = array_values($results); - } - - return $results; - } else { - // 不限制权限 - $sql = " - SELECT - id, - file_id, - userid, - file_name, - file_type, - file_ext, - content, - WEIGHT() as relevance - FROM file_vectors - WHERE MATCH('@(file_name,content) {$escapedKeyword}') - ORDER BY relevance DESC - LIMIT " . (int)$limit . " OFFSET " . (int)$offset; - - return $instance->query($sql); } + + return $instance->query($sql); } /** - * 向量相似度搜索 + * 向量相似度搜索(使用 MVA allowed_users 权限过滤) * * @param array $queryVector 查询向量 * @param int $userid 用户ID(0表示不限制权限) @@ -451,6 +402,10 @@ class ManticoreBase $instance = new self(); $vectorStr = '(' . implode(',', $queryVector) . ')'; + // KNN 搜索需要先获取更多结果,再在应用层过滤权限 + // 因为 KNN 的 WHERE 条件在 Manticore 中有限制 + $fetchLimit = $userid > 0 ? $limit * 5 : $limit; + $sql = " SELECT id, @@ -460,10 +415,10 @@ class ManticoreBase file_name, file_type, file_ext, - SUBSTRING(content, 1, 500) as content_preview, + content, KNN_DIST() as distance FROM file_vectors - WHERE KNN(content_vector, " . (int)$limit . ", {$vectorStr}) + WHERE KNN(content_vector, " . (int)$fetchLimit . ", {$vectorStr}) ORDER BY distance ASC "; @@ -474,22 +429,17 @@ class ManticoreBase $item['similarity'] = 1 - ($item['distance'] ?? 0); } - // 权限过滤 + // MVA 权限过滤 if ($userid > 0 && !empty($results)) { - $shareFileIds = $instance->query( - "SELECT file_id FROM file_users WHERE userid IN (0, ?)", + // 获取有权限的文件列表(allowed_users 包含 0 或 userid) + $allowedFileIds = $instance->query( + "SELECT file_id FROM file_vectors WHERE allowed_users = 0 OR allowed_users = ? LIMIT 100000", [$userid] ); - $allowedShares = array_column($shareFileIds, 'file_id'); + $allowedIds = array_column($allowedFileIds, 'file_id'); - $results = array_filter($results, function ($item) use ($userid, $allowedShares) { - if ($item['userid'] == $userid) { - return true; - } - if ($item['pshare'] > 0 && in_array($item['pshare'], $allowedShares)) { - return true; - } - return false; + $results = array_filter($results, function ($item) use ($allowedIds) { + return in_array($item['file_id'], $allowedIds); }); $results = array_values($results); } @@ -516,7 +466,7 @@ class ManticoreBase float $textWeight = 0.5, float $vectorWeight = 0.5 ): array { - // 分别执行两种搜索 + // 分别执行两种搜索(已包含权限过滤) $textResults = self::fullTextSearch($keyword, $userid, 50, 0); $vectorResults = !empty($queryVector) ? self::vectorSearch($queryVector, $userid, 50) @@ -563,9 +513,18 @@ class ManticoreBase } /** - * 插入或更新文件向量 + * 插入或更新文件向量(含 allowed_users MVA 权限字段) * - * @param array $data 文件数据 + * @param array $data 文件数据,包含: + * - file_id: 文件ID + * - userid: 所有者ID + * - pshare: 共享文件夹ID + * - file_name: 文件名 + * - file_type: 文件类型 + * - file_ext: 文件扩展名 + * - content: 文件内容 + * - content_vector: 向量值 + * - allowed_users: 有权限的用户ID数组(0表示公开) * @return bool 是否成功 */ public static function upsertFileVector(array $data): bool @@ -580,44 +539,59 @@ class ManticoreBase // 先尝试删除已存在的记录 $instance->execute("DELETE FROM file_vectors WHERE file_id = ?", [$fileId]); - // 插入新记录(向量值必须内联到 SQL,Manticore 的 float_vector 不支持参数绑定) + // 构建 allowed_users MVA 值 + $allowedUsers = $data['allowed_users'] ?? []; + $allowedUsersStr = !empty($allowedUsers) ? '(' . implode(',', array_map('intval', $allowedUsers)) . ')' : '()'; + + // 插入新记录(向量值和 MVA 必须内联到 SQL) $vectorValue = $data['content_vector'] ?? null; if ($vectorValue) { $vectorValue = str_replace(['[', ']'], ['(', ')'], $vectorValue); $sql = "INSERT INTO file_vectors - (id, file_id, userid, pshare, file_name, file_type, file_ext, content, content_vector) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$vectorValue})"; - - $params = [ - $fileId, - $fileId, - $data['userid'] ?? 0, - $data['pshare'] ?? 0, - $data['file_name'] ?? '', - $data['file_type'] ?? '', - $data['file_ext'] ?? '', - $data['content'] ?? '' - ]; + (id, file_id, userid, pshare, file_name, file_type, file_ext, content, allowed_users, content_vector) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$allowedUsersStr}, {$vectorValue})"; } else { $sql = "INSERT INTO file_vectors - (id, file_id, userid, pshare, file_name, file_type, file_ext, content) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; - - $params = [ - $fileId, - $fileId, - $data['userid'] ?? 0, - $data['pshare'] ?? 0, - $data['file_name'] ?? '', - $data['file_type'] ?? '', - $data['file_ext'] ?? '', - $data['content'] ?? '' - ]; + (id, file_id, userid, pshare, file_name, file_type, file_ext, content, allowed_users) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$allowedUsersStr})"; } + $params = [ + $fileId, + $fileId, + $data['userid'] ?? 0, + $data['pshare'] ?? 0, + $data['file_name'] ?? '', + $data['file_type'] ?? '', + $data['file_ext'] ?? '', + $data['content'] ?? '' + ]; + return $instance->execute($sql, $params); } + /** + * 更新文件的 allowed_users 权限列表 + * + * @param int $fileId 文件ID + * @param array $userids 有权限的用户ID数组 + * @return bool 是否成功 + */ + public static function updateFileAllowedUsers(int $fileId, array $userids): bool + { + if ($fileId <= 0) { + return false; + } + + $instance = new self(); + $allowedUsersStr = !empty($userids) ? '(' . implode(',', array_map('intval', $userids)) . ')' : '()'; + + return $instance->execute( + "UPDATE file_vectors SET allowed_users = {$allowedUsersStr} WHERE file_id = ?", + [$fileId] + ); + } + /** * 删除文件向量 * @@ -717,136 +691,6 @@ class ManticoreBase return $result ? (int) ($result['max_id'] ?? 0) : 0; } - // ============================== - // 文件用户关系方法 - // ============================== - - /** - * 插入或更新文件用户关系 - * - * @param int $fileId 文件ID - * @param int $userid 用户ID(0表示公开) - * @param int $permission 权限(0只读,1读写) - * @return bool 是否成功 - */ - public static function upsertFileUser(int $fileId, int $userid, int $permission = 0): bool - { - if ($fileId <= 0) { - return false; - } - - $instance = new self(); - - // 先删除已存在的记录 - $instance->execute( - "DELETE FROM file_users WHERE file_id = ? AND userid = ?", - [$fileId, $userid] - ); - - // 插入新记录 - $id = $fileId * 1000000 + $userid; // 生成唯一 ID - return $instance->execute( - "INSERT INTO file_users (id, file_id, userid, permission) VALUES (?, ?, ?, ?)", - [$id, $fileId, $userid, $permission] - ); - } - - /** - * 批量同步文件用户关系(替换指定文件的所有关系) - * - * @param int $fileId 文件ID - * @param array $users 用户列表 [['userid' => int, 'permission' => int], ...] - * @return bool 是否成功 - */ - public static function syncFileUsers(int $fileId, array $users): bool - { - if ($fileId <= 0) { - return false; - } - - $instance = new self(); - - try { - // 删除旧关系 - $instance->execute("DELETE FROM file_users WHERE file_id = ?", [$fileId]); - - // 插入新关系 - foreach ($users as $user) { - $userid = (int)($user['userid'] ?? 0); - $permission = (int)($user['permission'] ?? 0); - $id = $fileId * 1000000 + $userid; - $instance->execute( - "INSERT INTO file_users (id, file_id, userid, permission) VALUES (?, ?, ?, ?)", - [$id, $fileId, $userid, $permission] - ); - } - - return true; - } catch (\Exception $e) { - Log::error('Manticore syncFileUsers error: ' . $e->getMessage()); - return false; - } - } - - /** - * 删除文件的所有用户关系 - * - * @param int $fileId 文件ID - * @return bool 是否成功 - */ - public static function deleteFileUsers(int $fileId): bool - { - if ($fileId <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute("DELETE FROM file_users WHERE file_id = ?", [$fileId]); - } - - /** - * 删除指定文件和用户的关系 - * - * @param int $fileId 文件ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function deleteFileUser(int $fileId, int $userid): bool - { - if ($fileId <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute( - "DELETE FROM file_users WHERE file_id = ? AND userid = ?", - [$fileId, $userid] - ); - } - - /** - * 获取文件用户关系数量 - * - * @return int 关系数量 - */ - public static function getFileUserCount(): int - { - $instance = new self(); - $result = $instance->queryOne("SELECT COUNT(*) as cnt FROM file_users"); - return $result ? (int) $result['cnt'] : 0; - } - - /** - * 清空所有文件用户关系 - * - * @return bool 是否成功 - */ - public static function clearAllFileUsers(): bool - { - $instance = new self(); - return $instance->execute("TRUNCATE TABLE file_users"); - } - // ============================== // 用户向量方法(联系人搜索) // ============================== @@ -1072,7 +916,7 @@ class ManticoreBase // ============================== /** - * 项目全文搜索 + * 项目全文搜索(使用 MVA allowed_users 权限过滤) * * @param string $keyword 关键词 * @param int $userid 用户ID(权限过滤) @@ -1089,41 +933,43 @@ class ManticoreBase $instance = new self(); $escapedKeyword = self::escapeMatch($keyword); - $sql = " - SELECT - id, - project_id, - userid, - personal, - project_name, - project_desc, - WEIGHT() as relevance - FROM project_vectors - WHERE MATCH('@(project_name,project_desc) {$escapedKeyword}') - ORDER BY relevance DESC - LIMIT " . (int)$limit . " OFFSET " . (int)$offset; - - $results = $instance->query($sql); - - // 权限过滤 - if ($userid > 0 && !empty($results)) { - $memberProjects = $instance->query( - "SELECT project_id FROM project_users WHERE userid = ?", - [$userid] - ); - $allowedProjects = array_column($memberProjects, 'project_id'); - - $results = array_filter($results, function ($item) use ($allowedProjects) { - return in_array($item['project_id'], $allowedProjects); - }); - $results = array_values($results); + if ($userid > 0) { + // 使用 MVA 权限过滤 + $sql = " + SELECT + id, + project_id, + userid, + personal, + project_name, + project_desc, + WEIGHT() as relevance + FROM project_vectors + WHERE MATCH('@(project_name,project_desc) {$escapedKeyword}') + AND allowed_users = " . (int)$userid . " + ORDER BY relevance DESC + LIMIT " . (int)$limit . " OFFSET " . (int)$offset; + } else { + $sql = " + SELECT + id, + project_id, + userid, + personal, + project_name, + project_desc, + WEIGHT() as relevance + FROM project_vectors + WHERE MATCH('@(project_name,project_desc) {$escapedKeyword}') + ORDER BY relevance DESC + LIMIT " . (int)$limit . " OFFSET " . (int)$offset; } - return $results; + return $instance->query($sql); } /** - * 项目向量搜索 + * 项目向量搜索(使用 MVA allowed_users 权限过滤) * * @param array $queryVector 查询向量 * @param int $userid 用户ID(权限过滤) @@ -1139,6 +985,9 @@ class ManticoreBase $instance = new self(); $vectorStr = '(' . implode(',', $queryVector) . ')'; + // KNN 搜索需要先获取更多结果,再在应用层过滤权限 + $fetchLimit = $userid > 0 ? $limit * 5 : $limit; + $sql = " SELECT id, @@ -1149,7 +998,7 @@ class ManticoreBase project_desc, KNN_DIST() as distance FROM project_vectors - WHERE KNN(content_vector, " . (int)$limit . ", {$vectorStr}) + WHERE KNN(content_vector, " . (int)$fetchLimit . ", {$vectorStr}) ORDER BY distance ASC "; @@ -1159,16 +1008,16 @@ class ManticoreBase $item['similarity'] = 1 - ($item['distance'] ?? 0); } - // 权限过滤 + // MVA 权限过滤 if ($userid > 0 && !empty($results)) { - $memberProjects = $instance->query( - "SELECT project_id FROM project_users WHERE userid = ?", + $allowedProjectIds = $instance->query( + "SELECT project_id FROM project_vectors WHERE allowed_users = ? LIMIT 100000", [$userid] ); - $allowedProjects = array_column($memberProjects, 'project_id'); + $allowedIds = array_column($allowedProjectIds, 'project_id'); - $results = array_filter($results, function ($item) use ($allowedProjects) { - return in_array($item['project_id'], $allowedProjects); + $results = array_filter($results, function ($item) use ($allowedIds) { + return in_array($item['project_id'], $allowedIds); }); $results = array_values($results); } @@ -1224,9 +1073,16 @@ class ManticoreBase } /** - * 插入或更新项目向量 + * 插入或更新项目向量(含 allowed_users MVA 权限字段) * - * @param array $data 项目数据 + * @param array $data 项目数据,包含: + * - project_id: 项目ID + * - userid: 创建者ID + * - personal: 是否个人项目 + * - project_name: 项目名称 + * - project_desc: 项目描述 + * - content_vector: 向量值 + * - allowed_users: 有权限的用户ID数组 * @return bool 是否成功 */ public static function upsertProjectVector(array $data): bool @@ -1241,40 +1097,57 @@ class ManticoreBase // 先删除已存在的记录 $instance->execute("DELETE FROM project_vectors WHERE project_id = ?", [$projectId]); - // 插入新记录(向量值必须内联到 SQL,Manticore 的 float_vector 不支持参数绑定) + // 构建 allowed_users MVA 值 + $allowedUsers = $data['allowed_users'] ?? []; + $allowedUsersStr = !empty($allowedUsers) ? '(' . implode(',', array_map('intval', $allowedUsers)) . ')' : '()'; + + // 插入新记录 $vectorValue = $data['content_vector'] ?? null; if ($vectorValue) { $vectorValue = str_replace(['[', ']'], ['(', ')'], $vectorValue); $sql = "INSERT INTO project_vectors - (id, project_id, userid, personal, project_name, project_desc, content_vector) - VALUES (?, ?, ?, ?, ?, ?, {$vectorValue})"; - - $params = [ - $projectId, - $projectId, - $data['userid'] ?? 0, - $data['personal'] ?? 0, - $data['project_name'] ?? '', - $data['project_desc'] ?? '' - ]; + (id, project_id, userid, personal, project_name, project_desc, allowed_users, content_vector) + VALUES (?, ?, ?, ?, ?, ?, {$allowedUsersStr}, {$vectorValue})"; } else { $sql = "INSERT INTO project_vectors - (id, project_id, userid, personal, project_name, project_desc) - VALUES (?, ?, ?, ?, ?, ?)"; - - $params = [ - $projectId, - $projectId, - $data['userid'] ?? 0, - $data['personal'] ?? 0, - $data['project_name'] ?? '', - $data['project_desc'] ?? '' - ]; + (id, project_id, userid, personal, project_name, project_desc, allowed_users) + VALUES (?, ?, ?, ?, ?, ?, {$allowedUsersStr})"; } + $params = [ + $projectId, + $projectId, + $data['userid'] ?? 0, + $data['personal'] ?? 0, + $data['project_name'] ?? '', + $data['project_desc'] ?? '' + ]; + return $instance->execute($sql, $params); } + /** + * 更新项目的 allowed_users 权限列表 + * + * @param int $projectId 项目ID + * @param array $userids 有权限的用户ID数组 + * @return bool 是否成功 + */ + public static function updateProjectAllowedUsers(int $projectId, array $userids): bool + { + if ($projectId <= 0) { + return false; + } + + $instance = new self(); + $allowedUsersStr = !empty($userids) ? '(' . implode(',', array_map('intval', $userids)) . ')' : '()'; + + return $instance->execute( + "UPDATE project_vectors SET allowed_users = {$allowedUsersStr} WHERE project_id = ?", + [$projectId] + ); + } + /** * 删除项目向量 * @@ -1314,137 +1187,12 @@ class ManticoreBase return $result ? (int) $result['cnt'] : 0; } - // ============================== - // 项目成员关系方法 - // ============================== - - /** - * 插入或更新项目成员关系 - * - * @param int $projectId 项目ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function upsertProjectUser(int $projectId, int $userid): bool - { - if ($projectId <= 0 || $userid <= 0) { - return false; - } - - $instance = new self(); - - // 先删除已存在的记录 - $instance->execute( - "DELETE FROM project_users WHERE project_id = ? AND userid = ?", - [$projectId, $userid] - ); - - // 插入新记录 - $id = $projectId * 1000000 + $userid; - return $instance->execute( - "INSERT INTO project_users (id, project_id, userid) VALUES (?, ?, ?)", - [$id, $projectId, $userid] - ); - } - - /** - * 删除项目成员关系 - * - * @param int $projectId 项目ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function deleteProjectUser(int $projectId, int $userid): bool - { - if ($projectId <= 0 || $userid <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute( - "DELETE FROM project_users WHERE project_id = ? AND userid = ?", - [$projectId, $userid] - ); - } - - /** - * 删除项目的所有成员关系 - * - * @param int $projectId 项目ID - * @return bool 是否成功 - */ - public static function deleteAllProjectUsers(int $projectId): bool - { - if ($projectId <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute("DELETE FROM project_users WHERE project_id = ?", [$projectId]); - } - - /** - * 批量同步项目成员关系 - * - * @param int $projectId 项目ID - * @param array $userids 用户ID列表 - * @return bool 是否成功 - */ - public static function syncProjectUsers(int $projectId, array $userids): bool - { - if ($projectId <= 0) { - return false; - } - - $instance = new self(); - - try { - $instance->execute("DELETE FROM project_users WHERE project_id = ?", [$projectId]); - - foreach ($userids as $userid) { - $id = $projectId * 1000000 + (int)$userid; - $instance->execute( - "INSERT INTO project_users (id, project_id, userid) VALUES (?, ?, ?)", - [$id, $projectId, (int)$userid] - ); - } - - return true; - } catch (\Exception $e) { - Log::error('Manticore syncProjectUsers error: ' . $e->getMessage()); - return false; - } - } - - /** - * 清空所有项目成员关系 - * - * @return bool 是否成功 - */ - public static function clearAllProjectUsers(): bool - { - $instance = new self(); - return $instance->execute("TRUNCATE TABLE project_users"); - } - - /** - * 获取项目成员关系数量 - * - * @return int 关系数量 - */ - public static function getProjectUserCount(): int - { - $instance = new self(); - $result = $instance->queryOne("SELECT COUNT(*) as cnt FROM project_users"); - return $result ? (int) $result['cnt'] : 0; - } - // ============================== // 任务向量方法 // ============================== /** - * 任务全文搜索 + * 任务全文搜索(使用 MVA allowed_users 权限过滤) * * @param string $keyword 关键词 * @param int $userid 用户ID(权限过滤) @@ -1461,63 +1209,47 @@ class ManticoreBase $instance = new self(); $escapedKeyword = self::escapeMatch($keyword); - $sql = " - SELECT - id, - task_id, - project_id, - userid, - visibility, - task_name, - task_desc, - task_content, - WEIGHT() as relevance - FROM task_vectors - WHERE MATCH('@(task_name,task_desc,task_content) {$escapedKeyword}') - ORDER BY relevance DESC - LIMIT " . (int)$limit . " OFFSET " . (int)$offset; - - $results = $instance->query($sql); - - // 权限过滤 - if ($userid > 0 && !empty($results)) { - // 获取用户参与的项目 - $memberProjects = $instance->query( - "SELECT project_id FROM project_users WHERE userid = ?", - [$userid] - ); - $allowedProjects = array_column($memberProjects, 'project_id'); - - // 获取用户参与的任务 - $memberTasks = $instance->query( - "SELECT task_id FROM task_users WHERE userid = ?", - [$userid] - ); - $allowedTasks = array_column($memberTasks, 'task_id'); - - $results = array_filter($results, function ($item) use ($userid, $allowedProjects, $allowedTasks) { - // 自己创建的任务 - if ($item['userid'] == $userid) { - return true; - } - // visibility=1 且是项目成员 - if ($item['visibility'] == 1 && in_array($item['project_id'], $allowedProjects)) { - return true; - } - // visibility=2,3 且是任务成员 - if (in_array($item['visibility'], [2, 3]) && in_array($item['task_id'], $allowedTasks)) { - return true; - } - return false; - }); - $results = array_values($results); + if ($userid > 0) { + // 使用 MVA 权限过滤 + $sql = " + SELECT + id, + task_id, + project_id, + userid, + visibility, + task_name, + task_desc, + task_content, + WEIGHT() as relevance + FROM task_vectors + WHERE MATCH('@(task_name,task_desc,task_content) {$escapedKeyword}') + AND allowed_users = " . (int)$userid . " + ORDER BY relevance DESC + LIMIT " . (int)$limit . " OFFSET " . (int)$offset; + } else { + $sql = " + SELECT + id, + task_id, + project_id, + userid, + visibility, + task_name, + task_desc, + task_content, + WEIGHT() as relevance + FROM task_vectors + WHERE MATCH('@(task_name,task_desc,task_content) {$escapedKeyword}') + ORDER BY relevance DESC + LIMIT " . (int)$limit . " OFFSET " . (int)$offset; } - return $results; + return $instance->query($sql); } /** - * 任务向量搜索 + * 任务向量搜索(使用 MVA allowed_users 权限过滤) * * @param array $queryVector 查询向量 * @param int $userid 用户ID(权限过滤) @@ -1533,6 +1265,9 @@ class ManticoreBase $instance = new self(); $vectorStr = '(' . implode(',', $queryVector) . ')'; + // KNN 搜索需要先获取更多结果,再在应用层过滤权限 + $fetchLimit = $userid > 0 ? $limit * 5 : $limit; + $sql = " SELECT id, @@ -1545,7 +1280,7 @@ class ManticoreBase task_content, KNN_DIST() as distance FROM task_vectors - WHERE KNN(content_vector, " . (int)$limit . ", {$vectorStr}) + WHERE KNN(content_vector, " . (int)$fetchLimit . ", {$vectorStr}) ORDER BY distance ASC "; @@ -1555,31 +1290,16 @@ class ManticoreBase $item['similarity'] = 1 - ($item['distance'] ?? 0); } - // 权限过滤 + // MVA 权限过滤 if ($userid > 0 && !empty($results)) { - $memberProjects = $instance->query( - "SELECT project_id FROM project_users WHERE userid = ?", + $allowedTaskIds = $instance->query( + "SELECT task_id FROM task_vectors WHERE allowed_users = ? LIMIT 100000", [$userid] ); - $allowedProjects = array_column($memberProjects, 'project_id'); + $allowedIds = array_column($allowedTaskIds, 'task_id'); - $memberTasks = $instance->query( - "SELECT task_id FROM task_users WHERE userid = ?", - [$userid] - ); - $allowedTasks = array_column($memberTasks, 'task_id'); - - $results = array_filter($results, function ($item) use ($userid, $allowedProjects, $allowedTasks) { - if ($item['userid'] == $userid) { - return true; - } - if ($item['visibility'] == 1 && in_array($item['project_id'], $allowedProjects)) { - return true; - } - if (in_array($item['visibility'], [2, 3]) && in_array($item['task_id'], $allowedTasks)) { - return true; - } - return false; + $results = array_filter($results, function ($item) use ($allowedIds) { + return in_array($item['task_id'], $allowedIds); }); $results = array_values($results); } @@ -1635,9 +1355,18 @@ class ManticoreBase } /** - * 插入或更新任务向量 + * 插入或更新任务向量(含 allowed_users MVA 权限字段) * - * @param array $data 任务数据 + * @param array $data 任务数据,包含: + * - task_id: 任务ID + * - project_id: 项目ID + * - userid: 创建者ID + * - visibility: 可见性 + * - task_name: 任务名称 + * - task_desc: 任务描述 + * - task_content: 任务内容 + * - content_vector: 向量值 + * - allowed_users: 有权限的用户ID数组 * @return bool 是否成功 */ public static function upsertTaskVector(array $data): bool @@ -1652,44 +1381,59 @@ class ManticoreBase // 先删除已存在的记录 $instance->execute("DELETE FROM task_vectors WHERE task_id = ?", [$taskId]); - // 插入新记录(向量值必须内联到 SQL,Manticore 的 float_vector 不支持参数绑定) + // 构建 allowed_users MVA 值 + $allowedUsers = $data['allowed_users'] ?? []; + $allowedUsersStr = !empty($allowedUsers) ? '(' . implode(',', array_map('intval', $allowedUsers)) . ')' : '()'; + + // 插入新记录 $vectorValue = $data['content_vector'] ?? null; if ($vectorValue) { $vectorValue = str_replace(['[', ']'], ['(', ')'], $vectorValue); $sql = "INSERT INTO task_vectors - (id, task_id, project_id, userid, visibility, task_name, task_desc, task_content, content_vector) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$vectorValue})"; - - $params = [ - $taskId, - $taskId, - $data['project_id'] ?? 0, - $data['userid'] ?? 0, - $data['visibility'] ?? 1, - $data['task_name'] ?? '', - $data['task_desc'] ?? '', - $data['task_content'] ?? '' - ]; + (id, task_id, project_id, userid, visibility, task_name, task_desc, task_content, allowed_users, content_vector) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$allowedUsersStr}, {$vectorValue})"; } else { $sql = "INSERT INTO task_vectors - (id, task_id, project_id, userid, visibility, task_name, task_desc, task_content) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; - - $params = [ - $taskId, - $taskId, - $data['project_id'] ?? 0, - $data['userid'] ?? 0, - $data['visibility'] ?? 1, - $data['task_name'] ?? '', - $data['task_desc'] ?? '', - $data['task_content'] ?? '' - ]; + (id, task_id, project_id, userid, visibility, task_name, task_desc, task_content, allowed_users) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, {$allowedUsersStr})"; } + $params = [ + $taskId, + $taskId, + $data['project_id'] ?? 0, + $data['userid'] ?? 0, + $data['visibility'] ?? 1, + $data['task_name'] ?? '', + $data['task_desc'] ?? '', + $data['task_content'] ?? '' + ]; + return $instance->execute($sql, $params); } + /** + * 更新任务的 allowed_users 权限列表 + * + * @param int $taskId 任务ID + * @param array $userids 有权限的用户ID数组 + * @return bool 是否成功 + */ + public static function updateTaskAllowedUsers(int $taskId, array $userids): bool + { + if ($taskId <= 0) { + return false; + } + + $instance = new self(); + $allowedUsersStr = !empty($userids) ? '(' . implode(',', array_map('intval', $userids)) . ')' : '()'; + + return $instance->execute( + "UPDATE task_vectors SET allowed_users = {$allowedUsersStr} WHERE task_id = ?", + [$taskId] + ); + } + /** * 更新任务可见性 * @@ -1749,129 +1493,5 @@ class ManticoreBase return $result ? (int) $result['cnt'] : 0; } - // ============================== - // 任务成员关系方法 - // ============================== - - /** - * 插入或更新任务成员关系 - * - * @param int $taskId 任务ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function upsertTaskUser(int $taskId, int $userid): bool - { - if ($taskId <= 0 || $userid <= 0) { - return false; - } - - $instance = new self(); - - // 先删除已存在的记录 - $instance->execute( - "DELETE FROM task_users WHERE task_id = ? AND userid = ?", - [$taskId, $userid] - ); - - // 插入新记录 - $id = $taskId * 1000000 + $userid; - return $instance->execute( - "INSERT INTO task_users (id, task_id, userid) VALUES (?, ?, ?)", - [$id, $taskId, $userid] - ); - } - - /** - * 删除任务成员关系 - * - * @param int $taskId 任务ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function deleteTaskUser(int $taskId, int $userid): bool - { - if ($taskId <= 0 || $userid <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute( - "DELETE FROM task_users WHERE task_id = ? AND userid = ?", - [$taskId, $userid] - ); - } - - /** - * 删除任务的所有成员关系 - * - * @param int $taskId 任务ID - * @return bool 是否成功 - */ - public static function deleteAllTaskUsers(int $taskId): bool - { - if ($taskId <= 0) { - return false; - } - - $instance = new self(); - return $instance->execute("DELETE FROM task_users WHERE task_id = ?", [$taskId]); - } - - /** - * 批量同步任务成员关系 - * - * @param int $taskId 任务ID - * @param array $userids 用户ID列表 - * @return bool 是否成功 - */ - public static function syncTaskUsers(int $taskId, array $userids): bool - { - if ($taskId <= 0) { - return false; - } - - $instance = new self(); - - try { - $instance->execute("DELETE FROM task_users WHERE task_id = ?", [$taskId]); - - foreach ($userids as $userid) { - $id = $taskId * 1000000 + (int)$userid; - $instance->execute( - "INSERT INTO task_users (id, task_id, userid) VALUES (?, ?, ?)", - [$id, $taskId, (int)$userid] - ); - } - - return true; - } catch (\Exception $e) { - Log::error('Manticore syncTaskUsers error: ' . $e->getMessage()); - return false; - } - } - - /** - * 清空所有任务成员关系 - * - * @return bool 是否成功 - */ - public static function clearAllTaskUsers(): bool - { - $instance = new self(); - return $instance->execute("TRUNCATE TABLE task_users"); - } - - /** - * 获取任务成员关系数量 - * - * @return int 关系数量 - */ - public static function getTaskUserCount(): int - { - $instance = new self(); - $result = $instance->queryOne("SELECT COUNT(*) as cnt FROM task_users"); - return $result ? (int) $result['cnt'] : 0; - } } diff --git a/app/Module/Manticore/ManticoreFile.php b/app/Module/Manticore/ManticoreFile.php index 9f1741021..0cbc3a9ca 100644 --- a/app/Module/Manticore/ManticoreFile.php +++ b/app/Module/Manticore/ManticoreFile.php @@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\DB; /** - * Manticore Search 文件搜索类 + * Manticore Search 文件搜索类(MVA 权限方案) * * 使用方法: * @@ -25,7 +25,10 @@ use Illuminate\Support\Facades\DB; * - 批量同步: batchSync($files); * - 删除索引: delete($fileId); * - * 3. 工具方法 + * 3. 权限更新方法 + * - 更新权限: updateAllowedUsers($fileId); + * + * 4. 工具方法 * - 清空索引: clear(); */ class ManticoreFile @@ -204,12 +207,39 @@ class ManticoreFile })->toArray(); } + // ============================== + // 权限计算方法(MVA 方案核心) + // ============================== + + /** + * 获取文件的 allowed_users 列表 + * + * 有权限查看此文件的用户列表: + * - 文件所有者 (userid) + * - 共享用户(FileUser 表中的 userid) + * - userid=0 表示公开共享 + * + * @param File $file 文件模型 + * @return array 有权限的用户ID数组 + */ + public static function getAllowedUsers(File $file): array + { + $userids = [$file->userid]; // 所有者 + + // 获取共享用户(包括 userid=0 表示公开) + $shareUsers = FileUser::where('file_id', $file->id) + ->pluck('userid') + ->toArray(); + + return array_unique(array_merge($userids, $shareUsers)); + } + // ============================== // 同步方法 // ============================== /** - * 同步单个文件到 Manticore + * 同步单个文件到 Manticore(含 allowed_users) * * @param File $file 文件模型 * @return bool 是否成功 @@ -248,7 +278,10 @@ class ManticoreFile } } - // 写入 Manticore + // 获取文件的 allowed_users + $allowedUsers = self::getAllowedUsers($file); + + // 写入 Manticore(含 allowed_users) $result = ManticoreBase::upsertFileVector([ 'file_id' => $file->id, 'userid' => $file->userid, @@ -258,6 +291,7 @@ class ManticoreFile 'file_ext' => $file->ext, 'content' => $content, 'content_vector' => $embedding, + 'allowed_users' => $allowedUsers, ]); return $result; @@ -414,166 +448,33 @@ class ManticoreFile } // ============================== - // 文件用户关系同步方法 + // 权限更新方法(MVA 方案) // ============================== /** - * 同步单个文件的用户关系到 Manticore + * 更新文件的 allowed_users 权限列表 + * 从 MySQL 获取最新的共享用户并更新到 Manticore * * @param int $fileId 文件ID * @return bool 是否成功 */ - public static function syncFileUsers(int $fileId): bool + public static function updateAllowedUsers(int $fileId): bool { if (!Apps::isInstalled("manticore") || $fileId <= 0) { return false; } try { - // 从 MySQL 获取文件的用户关系 - $users = FileUser::where('file_id', $fileId) - ->select(['userid', 'permission']) - ->get() - ->map(function ($item) { - return [ - 'userid' => $item->userid, - 'permission' => $item->permission, - ]; - }) - ->toArray(); + $file = File::find($fileId); + if (!$file) { + return false; + } - // 同步到 Manticore - return ManticoreBase::syncFileUsers($fileId, $users); + $userids = self::getAllowedUsers($file); + return ManticoreBase::updateFileAllowedUsers($fileId, $userids); } catch (\Exception $e) { - Log::error('Manticore syncFileUsers error: ' . $e->getMessage(), ['file_id' => $fileId]); + Log::error('Manticore updateAllowedUsers error: ' . $e->getMessage(), ['file_id' => $fileId]); return false; } } - - /** - * 添加文件用户关系到 Manticore - * - * @param int $fileId 文件ID - * @param int $userid 用户ID - * @param int $permission 权限 - * @return bool 是否成功 - */ - public static function addFileUser(int $fileId, int $userid, int $permission = 0): bool - { - if (!Apps::isInstalled("manticore") || $fileId <= 0) { - return false; - } - - return ManticoreBase::upsertFileUser($fileId, $userid, $permission); - } - - /** - * 删除文件用户关系 - * - * @param int $fileId 文件ID - * @param int|null $userid 用户ID,null 表示删除所有 - * @return bool 是否成功 - */ - public static function removeFileUser(int $fileId, ?int $userid = null): bool - { - if (!Apps::isInstalled("manticore") || $fileId <= 0) { - return false; - } - - if ($userid === null) { - return ManticoreBase::deleteFileUsers($fileId); - } - - return ManticoreBase::deleteFileUser($fileId, $userid); - } - - /** - * 批量同步所有文件用户关系(全量同步) - * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 - */ - public static function syncAllFileUsers(?callable $progressCallback = null): int - { - if (!Apps::isInstalled("manticore")) { - return 0; - } - - $count = 0; - $lastId = 0; - $batchSize = 1000; - - // 先清空 Manticore 中的 file_users 表 - ManticoreBase::clearAllFileUsers(); - - // 分批同步 - while (true) { - $records = FileUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertFileUser($record->file_id, $record->userid, $record->permission); - $count++; - $lastId = $record->id; - } - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; - } - - /** - * 增量同步文件用户关系(只同步新增的) - * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 - */ - public static function syncFileUsersIncremental(?callable $progressCallback = null): int - { - if (!Apps::isInstalled("manticore")) { - return 0; - } - - $count = 0; - $batchSize = 1000; - $lastKey = "sync:manticoreFileUserLastId"; - $lastId = intval(ManticoreKeyValue::get($lastKey, 0)); - - // 分批同步新增的记录 - while (true) { - $records = FileUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertFileUser($record->file_id, $record->userid, $record->permission); - $count++; - $lastId = $record->id; - } - - // 保存进度 - ManticoreKeyValue::set($lastKey, $lastId); - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; - } } - diff --git a/app/Module/Manticore/ManticoreProject.php b/app/Module/Manticore/ManticoreProject.php index df7eb1ece..8d567d9cb 100644 --- a/app/Module/Manticore/ManticoreProject.php +++ b/app/Module/Manticore/ManticoreProject.php @@ -10,7 +10,7 @@ use App\Module\AI; use Illuminate\Support\Facades\Log; /** - * Manticore Search 项目搜索类 + * Manticore Search 项目搜索类(MVA 权限方案) * * 使用方法: * @@ -22,10 +22,8 @@ use Illuminate\Support\Facades\Log; * - 批量同步: batchSync($projects); * - 删除索引: delete($projectId); * - * 3. 成员关系方法 - * - 添加成员: addProjectUser($projectId, $userid); - * - 删除成员: removeProjectUser($projectId, $userid); - * - 同步所有成员: syncProjectUsers($projectId); + * 3. 权限更新方法 + * - 更新权限: updateAllowedUsers($projectId); * * 4. 工具方法 * - 清空索引: clear(); @@ -134,7 +132,20 @@ class ManticoreProject // ============================== /** - * 同步单个项目到 Manticore + * 获取项目的 allowed_users 列表 + * + * @param int $projectId 项目ID + * @return array 有权限的用户ID数组 + */ + public static function getAllowedUsers(int $projectId): array + { + return ProjectUser::where('project_id', $projectId) + ->pluck('userid') + ->toArray(); + } + + /** + * 同步单个项目到 Manticore(含 allowed_users) * * @param Project $project 项目模型 * @return bool 是否成功 @@ -163,7 +174,10 @@ class ManticoreProject } } - // 写入 Manticore + // 获取项目成员列表(作为 allowed_users) + $allowedUsers = self::getAllowedUsers($project->id); + + // 写入 Manticore(含 allowed_users) $result = ManticoreBase::upsertProjectVector([ 'project_id' => $project->id, 'userid' => $project->userid ?? 0, @@ -171,6 +185,7 @@ class ManticoreProject 'project_name' => $project->name ?? '', 'project_desc' => $project->desc ?? '', 'content_vector' => $embedding, + 'allowed_users' => $allowedUsers, ]); return $result; @@ -236,12 +251,7 @@ class ManticoreProject return false; } - // 删除项目索引 - ManticoreBase::deleteProjectVector($projectId); - // 删除项目成员关系 - ManticoreBase::deleteAllProjectUsers($projectId); - - return true; + return ManticoreBase::deleteProjectVector($projectId); } /** @@ -255,10 +265,7 @@ class ManticoreProject return false; } - ManticoreBase::clearAllProjectVectors(); - ManticoreBase::clearAllProjectUsers(); - - return true; + return ManticoreBase::clearAllProjectVectors(); } /** @@ -276,154 +283,28 @@ class ManticoreProject } // ============================== - // 成员关系方法 + // 权限更新方法(MVA 方案) // ============================== /** - * 添加项目成员到 Manticore - * - * @param int $projectId 项目ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function addProjectUser(int $projectId, int $userid): bool - { - if (!Apps::isInstalled("manticore") || $projectId <= 0 || $userid <= 0) { - return false; - } - - return ManticoreBase::upsertProjectUser($projectId, $userid); - } - - /** - * 删除项目成员 - * - * @param int $projectId 项目ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function removeProjectUser(int $projectId, int $userid): bool - { - if (!Apps::isInstalled("manticore") || $projectId <= 0 || $userid <= 0) { - return false; - } - - return ManticoreBase::deleteProjectUser($projectId, $userid); - } - - /** - * 同步项目的所有成员到 Manticore + * 更新项目的 allowed_users 权限列表 + * 从 MySQL 获取最新的项目成员并更新到 Manticore * * @param int $projectId 项目ID * @return bool 是否成功 */ - public static function syncProjectUsers(int $projectId): bool + public static function updateAllowedUsers(int $projectId): bool { if (!Apps::isInstalled("manticore") || $projectId <= 0) { return false; } try { - // 从 MySQL 获取项目成员 - $userids = ProjectUser::where('project_id', $projectId) - ->pluck('userid') - ->toArray(); - - // 同步到 Manticore - return ManticoreBase::syncProjectUsers($projectId, $userids); + $userids = self::getAllowedUsers($projectId); + return ManticoreBase::updateProjectAllowedUsers($projectId, $userids); } catch (\Exception $e) { - Log::error('Manticore syncProjectUsers error: ' . $e->getMessage(), ['project_id' => $projectId]); + Log::error('Manticore updateAllowedUsers error: ' . $e->getMessage(), ['project_id' => $projectId]); return false; } } - - /** - * 批量同步所有项目成员关系(全量同步) - * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 - */ - public static function syncAllProjectUsers(?callable $progressCallback = null): int - { - if (!Apps::isInstalled("manticore")) { - return 0; - } - - $count = 0; - $lastId = 0; - $batchSize = 1000; - - // 先清空 Manticore 中的 project_users 表 - ManticoreBase::clearAllProjectUsers(); - - // 分批同步 - while (true) { - $records = ProjectUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertProjectUser($record->project_id, $record->userid); - $count++; - $lastId = $record->id; - } - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; - } - - /** - * 增量同步项目成员关系(只同步新增的) - * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 - */ - public static function syncProjectUsersIncremental(?callable $progressCallback = null): int - { - if (!Apps::isInstalled("manticore")) { - return 0; - } - - $count = 0; - $batchSize = 1000; - $lastKey = "sync:manticoreProjectUserLastId"; - $lastId = intval(ManticoreKeyValue::get($lastKey, 0)); - - // 分批同步新增的记录 - while (true) { - $records = ProjectUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertProjectUser($record->project_id, $record->userid); - $count++; - $lastId = $record->id; - } - - // 保存进度 - ManticoreKeyValue::set($lastKey, $lastId); - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; - } } - diff --git a/app/Module/Manticore/ManticoreTask.php b/app/Module/Manticore/ManticoreTask.php index f4948a13d..33de4d152 100644 --- a/app/Module/Manticore/ManticoreTask.php +++ b/app/Module/Manticore/ManticoreTask.php @@ -6,18 +6,20 @@ use App\Models\ProjectTask; use App\Models\ProjectTaskContent; use App\Models\ProjectTaskUser; use App\Models\ProjectTaskVisibilityUser; +use App\Models\ProjectUser; use App\Module\Apps; use App\Module\Base; use App\Module\AI; use Illuminate\Support\Facades\Log; /** - * Manticore Search 任务搜索类 + * Manticore Search 任务搜索类(MVA 权限方案) * * 权限逻辑说明: - * - visibility = 1: 项目人员可见,通过 project_users 表过滤 - * - visibility = 2: 任务人员可见,通过 task_users 表过滤(ProjectTaskUser) - * - visibility = 3: 指定成员可见,通过 task_users 表过滤(ProjectTaskUser + ProjectTaskVisibilityUser) + * - visibility = 1: 项目人员可见,通过项目成员计算 allowed_users + * - visibility = 2: 任务人员可见,通过任务成员计算 allowed_users + * - visibility = 3: 指定成员可见,通过任务成员 + 可见性成员计算 allowed_users + * - 子任务继承父任务的 allowed_users * * 使用方法: * @@ -29,10 +31,10 @@ use Illuminate\Support\Facades\Log; * - 批量同步: batchSync($tasks); * - 删除索引: delete($taskId); * - * 3. 成员关系方法 - * - 添加成员: addTaskUser($taskId, $userid); - * - 删除成员: removeTaskUser($taskId, $userid); - * - 同步所有成员: syncTaskUsers($taskId); + * 3. 权限更新方法 + * - 更新权限: updateAllowedUsers($taskId); + * - 项目成员变更级联更新: cascadeUpdateByProject($projectId); + * - 父任务变更级联到子任务: cascadeToChildren($taskId); * * 4. 工具方法 * - 清空索引: clear(); @@ -143,12 +145,66 @@ class ManticoreTask return $formatted; } + // ============================== + // 权限计算方法(MVA 方案核心) + // ============================== + + /** + * 获取任务的 allowed_users 列表 + * + * 根据 visibility 计算有权限查看此任务的用户列表: + * - visibility=1: 项目成员 + * - visibility=2: 任务成员(负责人/协作人) + * - visibility=3: 任务成员 + 可见性指定成员 + * - 子任务: 还需要继承父任务的成员 + * + * @param ProjectTask $task 任务模型 + * @return array 有权限的用户ID数组 + */ + public static function getAllowedUsers(ProjectTask $task): array + { + $userids = []; + + // 1. 根据 visibility 获取基础成员 + if ($task->visibility == 1) { + // visibility=1: 项目成员 + $userids = ProjectUser::where('project_id', $task->project_id) + ->pluck('userid') + ->toArray(); + } else { + // visibility=2,3: 任务成员(负责人/协作人) + $userids = ProjectTaskUser::where('task_id', $task->id) + ->orWhere('task_pid', $task->id) + ->pluck('userid') + ->toArray(); + + // visibility=3: 加上可见性指定成员 + if ($task->visibility == 3) { + $visUsers = ProjectTaskVisibilityUser::where('task_id', $task->id) + ->pluck('userid') + ->toArray(); + $userids = array_merge($userids, $visUsers); + } + } + + // 2. 如果是子任务,继承父任务成员 + if ($task->parent_id > 0) { + $parentTask = ProjectTask::find($task->parent_id); + if ($parentTask) { + $parentUsers = self::getAllowedUsers($parentTask); + $userids = array_merge($userids, $parentUsers); + } + } + + return array_unique($userids); + } + // ============================== // 同步方法 // ============================== /** - * 同步单个任务到 Manticore + * 同步单个任务到 Manticore(含 allowed_users) * * @param ProjectTask $task 任务模型 * @return bool 是否成功 @@ -180,7 +236,10 @@ class ManticoreTask } } - // 写入 Manticore + // 获取任务的 allowed_users + $allowedUsers = self::getAllowedUsers($task); + + // 写入 Manticore(含 allowed_users) $result = ManticoreBase::upsertTaskVector([ 'task_id' => $task->id, 'project_id' => $task->project_id ?? 0, @@ -190,6 +249,7 @@ class ManticoreTask 'task_desc' => $task->desc ?? '', 'task_content' => $taskContent, 'content_vector' => $embedding, + 'allowed_users' => $allowedUsers, ]); return $result; @@ -322,28 +382,7 @@ class ManticoreTask return false; } - // 删除任务索引 - ManticoreBase::deleteTaskVector($taskId); - // 删除任务成员关系 - ManticoreBase::deleteAllTaskUsers($taskId); - - return true; - } - - /** - * 更新任务可见性 - * - * @param int $taskId 任务ID - * @param int $visibility 可见性 - * @return bool 是否成功 - */ - public static function updateVisibility(int $taskId, int $visibility): bool - { - if (!Apps::isInstalled("manticore") || $taskId <= 0) { - return false; - } - - return ManticoreBase::updateTaskVisibility($taskId, $visibility); + return ManticoreBase::deleteTaskVector($taskId); } /** @@ -357,10 +396,7 @@ class ManticoreTask return false; } - ManticoreBase::clearAllTaskVectors(); - ManticoreBase::clearAllTaskUsers(); - - return true; + return ManticoreBase::clearAllTaskVectors(); } /** @@ -378,269 +414,110 @@ class ManticoreTask } // ============================== - // 成员关系方法 + // 权限更新方法(MVA 方案) // ============================== /** - * 添加任务成员到 Manticore - * - * @param int $taskId 任务ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function addTaskUser(int $taskId, int $userid): bool - { - if (!Apps::isInstalled("manticore") || $taskId <= 0 || $userid <= 0) { - return false; - } - - return ManticoreBase::upsertTaskUser($taskId, $userid); - } - - /** - * 删除任务成员 - * - * @param int $taskId 任务ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function removeTaskUser(int $taskId, int $userid): bool - { - if (!Apps::isInstalled("manticore") || $taskId <= 0 || $userid <= 0) { - return false; - } - - return ManticoreBase::deleteTaskUser($taskId, $userid); - } - - /** - * 删除指定可见成员(visibility=3 场景) - * - * 特殊处理:需要检查该用户是否仍是任务的负责人/协作人 - * 如果是,则不应该从 task_users 中删除 - * - * @param int $taskId 任务ID - * @param int $userid 用户ID - * @return bool 是否成功 - */ - public static function removeVisibilityUser(int $taskId, int $userid): bool - { - if (!Apps::isInstalled("manticore") || $taskId <= 0 || $userid <= 0) { - return false; - } - - try { - // 检查用户是否仍是任务的负责人/协作人 - $isTaskMember = ProjectTaskUser::where('task_id', $taskId) - ->where('userid', $userid) - ->exists(); - - // 检查是否是父任务的成员(子任务场景) - $task = \App\Models\ProjectTask::find($taskId); - $isParentTaskMember = false; - if ($task && $task->parent_id > 0) { - $isParentTaskMember = ProjectTaskUser::where('task_id', $task->parent_id) - ->where('userid', $userid) - ->exists(); - } - - // 如果仍是任务成员,不删除 - if ($isTaskMember || $isParentTaskMember) { - return true; - } - - // 从 Manticore 删除 - return ManticoreBase::deleteTaskUser($taskId, $userid); - } catch (\Exception $e) { - Log::error('Manticore removeVisibilityUser error: ' . $e->getMessage(), [ - 'task_id' => $taskId, - 'userid' => $userid, - ]); - return false; - } - } - - /** - * 同步任务的所有成员到 Manticore - * - * 包括:ProjectTaskUser 和 ProjectTaskVisibilityUser + * 更新任务的 allowed_users 权限列表 + * 重新计算并更新 Manticore 中的权限 * * @param int $taskId 任务ID * @return bool 是否成功 */ - public static function syncTaskUsers(int $taskId): bool + public static function updateAllowedUsers(int $taskId): bool { if (!Apps::isInstalled("manticore") || $taskId <= 0) { return false; } try { - // 获取任务成员(负责人/协作人) - $taskUserIds = ProjectTaskUser::where('task_id', $taskId) - ->orWhere('task_pid', $taskId) - ->pluck('userid') - ->toArray(); + $task = ProjectTask::find($taskId); + if (!$task) { + return false; + } - // 获取可见性指定成员 - $visibilityUserIds = ProjectTaskVisibilityUser::where('task_id', $taskId) - ->pluck('userid') - ->toArray(); - - // 合并去重 - $allUserIds = array_unique(array_merge($taskUserIds, $visibilityUserIds)); - - // 同步到 Manticore - return ManticoreBase::syncTaskUsers($taskId, $allUserIds); + $userids = self::getAllowedUsers($task); + return ManticoreBase::updateTaskAllowedUsers($taskId, $userids); } catch (\Exception $e) { - Log::error('Manticore syncTaskUsers error: ' . $e->getMessage(), ['task_id' => $taskId]); + Log::error('Manticore updateAllowedUsers error: ' . $e->getMessage(), ['task_id' => $taskId]); return false; } } /** - * 批量同步所有任务成员关系(全量同步) + * 级联更新项目下所有 visibility=1 任务的 allowed_users + * 当项目成员变更时调用(异步执行) * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 + * @param int $projectId 项目ID + * @return int 更新的任务数量 */ - public static function syncAllTaskUsers(?callable $progressCallback = null): int + public static function cascadeUpdateByProject(int $projectId): int { - if (!Apps::isInstalled("manticore")) { + if (!Apps::isInstalled("manticore") || $projectId <= 0) { return 0; } - $count = 0; - $lastId = 0; - $batchSize = 1000; + try { + // 获取项目成员 + $projectUsers = ProjectUser::where('project_id', $projectId) + ->pluck('userid') + ->toArray(); - // 先清空 Manticore 中的 task_users 表 - ManticoreBase::clearAllTaskUsers(); + // 分批更新该项目下所有 visibility=1 的任务 + $count = 0; + ProjectTask::where('project_id', $projectId) + ->where('visibility', 1) + ->whereNull('deleted_at') + ->whereNull('archived_at') + ->chunk(100, function ($tasks) use ($projectUsers, &$count) { + foreach ($tasks as $task) { + // 对于子任务,需要合并父任务成员 + $allowedUsers = $projectUsers; + if ($task->parent_id > 0) { + $parentTask = ProjectTask::find($task->parent_id); + if ($parentTask) { + $parentUsers = self::getAllowedUsers($parentTask); + $allowedUsers = array_unique(array_merge($allowedUsers, $parentUsers)); + } + } + + ManticoreBase::updateTaskAllowedUsers($task->id, $allowedUsers); + $count++; + } + }); - // 同步 ProjectTaskUser - while (true) { - $records = ProjectTaskUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertTaskUser($record->task_id, $record->userid); - // 如果有父任务,也添加到父任务 - if ($record->task_pid) { - ManticoreBase::upsertTaskUser($record->task_pid, $record->userid); - } - $count++; - $lastId = $record->id; - } - - if ($progressCallback) { - $progressCallback($count); - } + return $count; + } catch (\Exception $e) { + Log::error('Manticore cascadeUpdateByProject error: ' . $e->getMessage(), ['project_id' => $projectId]); + return 0; } - - // 同步 ProjectTaskVisibilityUser - $lastId = 0; - while (true) { - $records = ProjectTaskVisibilityUser::where('id', '>', $lastId) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertTaskUser($record->task_id, $record->userid); - $count++; - $lastId = $record->id; - } - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; } /** - * 增量同步任务成员关系(只同步新增的) + * 级联更新所有子任务的 allowed_users + * 当父任务的成员变更时调用 * - * @param callable|null $progressCallback 进度回调 - * @return int 同步数量 + * @param int $taskId 父任务ID + * @return void */ - public static function syncTaskUsersIncremental(?callable $progressCallback = null): int + public static function cascadeToChildren(int $taskId): void { - if (!Apps::isInstalled("manticore")) { - return 0; + if (!Apps::isInstalled("manticore") || $taskId <= 0) { + return; } - $count = 0; - $batchSize = 1000; - - // 同步 ProjectTaskUser 新增 - $lastKey1 = "sync:manticoreTaskUserLastId"; - $lastId1 = intval(ManticoreKeyValue::get($lastKey1, 0)); - - while (true) { - $records = ProjectTaskUser::where('id', '>', $lastId1) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertTaskUser($record->task_id, $record->userid); - if ($record->task_pid) { - ManticoreBase::upsertTaskUser($record->task_pid, $record->userid); - } - $count++; - $lastId1 = $record->id; - } - - ManticoreKeyValue::set($lastKey1, $lastId1); - - if ($progressCallback) { - $progressCallback($count); - } + try { + ProjectTask::where('parent_id', $taskId) + ->whereNull('deleted_at') + ->whereNull('archived_at') + ->each(function ($child) { + $allowedUsers = self::getAllowedUsers($child); + ManticoreBase::updateTaskAllowedUsers($child->id, $allowedUsers); + // 递归处理子任务的子任务 + self::cascadeToChildren($child->id); + }); + } catch (\Exception $e) { + Log::error('Manticore cascadeToChildren error: ' . $e->getMessage(), ['task_id' => $taskId]); } - - // 同步 ProjectTaskVisibilityUser 新增 - $lastKey2 = "sync:manticoreTaskVisibilityUserLastId"; - $lastId2 = intval(ManticoreKeyValue::get($lastKey2, 0)); - - while (true) { - $records = ProjectTaskVisibilityUser::where('id', '>', $lastId2) - ->orderBy('id') - ->limit($batchSize) - ->get(); - - if ($records->isEmpty()) { - break; - } - - foreach ($records as $record) { - ManticoreBase::upsertTaskUser($record->task_id, $record->userid); - $count++; - $lastId2 = $record->id; - } - - ManticoreKeyValue::set($lastKey2, $lastId2); - - if ($progressCallback) { - $progressCallback($count); - } - } - - return $count; } } - diff --git a/app/Observers/FileUserObserver.php b/app/Observers/FileUserObserver.php index 29513fc29..236366201 100644 --- a/app/Observers/FileUserObserver.php +++ b/app/Observers/FileUserObserver.php @@ -5,6 +5,9 @@ namespace App\Observers; use App\Models\FileUser; use App\Tasks\ManticoreSyncTask; +/** + * FileUser 观察者(MVA 权限方案) + */ class FileUserObserver extends AbstractObserver { /** @@ -15,10 +18,9 @@ class FileUserObserver extends AbstractObserver */ public function created(FileUser $fileUser) { - self::taskDeliver(new ManticoreSyncTask('file_user_add', [ + // MVA 方案:更新文件的 allowed_users + self::taskDeliver(new ManticoreSyncTask('update_file_allowed_users', [ 'file_id' => $fileUser->file_id, - 'userid' => $fileUser->userid, - 'permission' => $fileUser->permission, ])); } @@ -30,10 +32,9 @@ class FileUserObserver extends AbstractObserver */ public function updated(FileUser $fileUser) { - self::taskDeliver(new ManticoreSyncTask('file_user_add', [ + // MVA 方案:更新文件的 allowed_users + self::taskDeliver(new ManticoreSyncTask('update_file_allowed_users', [ 'file_id' => $fileUser->file_id, - 'userid' => $fileUser->userid, - 'permission' => $fileUser->permission, ])); } @@ -45,10 +46,9 @@ class FileUserObserver extends AbstractObserver */ public function deleted(FileUser $fileUser) { - self::taskDeliver(new ManticoreSyncTask('file_user_remove', [ + // MVA 方案:更新文件的 allowed_users + self::taskDeliver(new ManticoreSyncTask('update_file_allowed_users', [ 'file_id' => $fileUser->file_id, - 'userid' => $fileUser->userid, ])); } } - diff --git a/app/Observers/ProjectTaskObserver.php b/app/Observers/ProjectTaskObserver.php index f7a346f08..46a0f0108 100644 --- a/app/Observers/ProjectTaskObserver.php +++ b/app/Observers/ProjectTaskObserver.php @@ -32,11 +32,6 @@ class ProjectTaskObserver extends AbstractObserver { if ($projectTask->isDirty('visibility')) { self::visibilityUpdate($projectTask); - // 同步 visibility 变化到 Manticore - self::taskDeliver(new ManticoreSyncTask('task_visibility_update', [ - 'task_id' => $projectTask->id, - 'visibility' => $projectTask->visibility, - ])); } if ($projectTask->isDirty('archived_at')) { if ($projectTask->archived_at) { @@ -46,9 +41,11 @@ class ProjectTaskObserver extends AbstractObserver } } - // 检查是否有搜索相关字段变化 - // project_id 变化时也需要同步(任务移动到其他项目) - $searchableFields = ['name', 'desc', 'archived_at', 'project_id']; + // MVA 方案:检查是否有搜索相关字段变化或权限相关字段变化 + // visibility 变化会影响 allowed_users 来源 + // parent_id 变化会影响子任务继承 + // project_id 变化会影响 visibility=1 的任务权限 + $searchableFields = ['name', 'desc', 'archived_at', 'project_id', 'visibility', 'parent_id']; $isDirty = false; foreach ($searchableFields as $field) { if ($projectTask->isDirty($field)) { @@ -61,6 +58,7 @@ class ProjectTaskObserver extends AbstractObserver if ($projectTask->archived_at) { self::taskDeliver(new ManticoreSyncTask('task_delete', ['task_id' => $projectTask->id])); } else { + // 重新同步任务(会重新计算 allowed_users) self::taskDeliver(new ManticoreSyncTask('task_sync', $projectTask->toArray())); } } diff --git a/app/Observers/ProjectTaskUserObserver.php b/app/Observers/ProjectTaskUserObserver.php index 2ef54b710..cff4aecbf 100644 --- a/app/Observers/ProjectTaskUserObserver.php +++ b/app/Observers/ProjectTaskUserObserver.php @@ -22,16 +22,14 @@ class ProjectTaskUserObserver extends AbstractObserver Deleted::forget('projectTask', $projectTaskUser->task_pid, $projectTaskUser->userid); } - // 同步任务成员到 Manticore - self::taskDeliver(new ManticoreSyncTask('task_user_add', [ + // MVA 方案:更新任务的 allowed_users(会自动 cascadeToChildren) + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $projectTaskUser->task_id, - 'userid' => $projectTaskUser->userid, ])); - // 如果是子任务,同时添加到父任务 + // 如果是子任务,也更新父任务 if ($projectTaskUser->task_pid) { - self::taskDeliver(new ManticoreSyncTask('task_user_add', [ + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $projectTaskUser->task_pid, - 'userid' => $projectTaskUser->userid, ])); } } @@ -59,10 +57,9 @@ class ProjectTaskUserObserver extends AbstractObserver Deleted::record('projectTask', $projectTaskUser->task_id, $projectTaskUser->userid); } - // 从 Manticore 删除任务成员关系 - self::taskDeliver(new ManticoreSyncTask('task_user_remove', [ + // MVA 方案:更新任务的 allowed_users(会自动 cascadeToChildren) + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $projectTaskUser->task_id, - 'userid' => $projectTaskUser->userid, ])); } diff --git a/app/Observers/ProjectTaskVisibilityUserObserver.php b/app/Observers/ProjectTaskVisibilityUserObserver.php index 10f7c2424..c421e800f 100644 --- a/app/Observers/ProjectTaskVisibilityUserObserver.php +++ b/app/Observers/ProjectTaskVisibilityUserObserver.php @@ -6,7 +6,7 @@ use App\Models\ProjectTaskVisibilityUser; use App\Tasks\ManticoreSyncTask; /** - * ProjectTaskVisibilityUser 观察者 + * ProjectTaskVisibilityUser 观察者(MVA 权限方案) * * 用于处理任务 visibility=3(指定成员可见)时的成员变更同步 */ @@ -20,10 +20,9 @@ class ProjectTaskVisibilityUserObserver extends AbstractObserver */ public function created(ProjectTaskVisibilityUser $visibilityUser) { - // 将指定成员添加到 Manticore 的 task_users 表 - self::taskDeliver(new ManticoreSyncTask('task_user_add', [ + // MVA 方案:更新任务的 allowed_users(会自动 cascadeToChildren) + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $visibilityUser->task_id, - 'userid' => $visibilityUser->userid, ])); } @@ -35,10 +34,9 @@ class ProjectTaskVisibilityUserObserver extends AbstractObserver */ public function updated(ProjectTaskVisibilityUser $visibilityUser) { - // 通常不会更新,但如果更新了也同步 - self::taskDeliver(new ManticoreSyncTask('task_user_add', [ + // MVA 方案:更新任务的 allowed_users(会自动 cascadeToChildren) + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $visibilityUser->task_id, - 'userid' => $visibilityUser->userid, ])); } @@ -50,12 +48,9 @@ class ProjectTaskVisibilityUserObserver extends AbstractObserver */ public function deleted(ProjectTaskVisibilityUser $visibilityUser) { - // 从 Manticore 的 task_users 表删除该成员 - // 注意:需要检查该用户是否仍是任务的负责人/协作人 - // 如果是,则不应该删除(因为 ProjectTaskUser 仍存在) - self::taskDeliver(new ManticoreSyncTask('task_visibility_user_remove', [ + // MVA 方案:更新任务的 allowed_users(会自动 cascadeToChildren) + self::taskDeliver(new ManticoreSyncTask('update_task_allowed_users', [ 'task_id' => $visibilityUser->task_id, - 'userid' => $visibilityUser->userid, ])); } @@ -81,4 +76,3 @@ class ProjectTaskVisibilityUserObserver extends AbstractObserver // } } - diff --git a/app/Observers/ProjectUserObserver.php b/app/Observers/ProjectUserObserver.php index 8436d1c1f..bd6066a3e 100644 --- a/app/Observers/ProjectUserObserver.php +++ b/app/Observers/ProjectUserObserver.php @@ -17,9 +17,14 @@ class ProjectUserObserver extends AbstractObserver public function created(ProjectUser $projectUser) { Deleted::forget('project', $projectUser->project_id, $projectUser->userid); - self::taskDeliver(new ManticoreSyncTask('project_user_add', [ + + // MVA 方案:更新项目的 allowed_users + self::taskDeliver(new ManticoreSyncTask('update_project_allowed_users', [ + 'project_id' => $projectUser->project_id, + ])); + // 异步级联更新该项目下所有 visibility=1 的任务 + self::taskDeliver(new ManticoreSyncTask('cascade_project_users', [ 'project_id' => $projectUser->project_id, - 'userid' => $projectUser->userid, ])); } @@ -43,9 +48,14 @@ class ProjectUserObserver extends AbstractObserver public function deleted(ProjectUser $projectUser) { Deleted::record('project', $projectUser->project_id, $projectUser->userid); - self::taskDeliver(new ManticoreSyncTask('project_user_remove', [ + + // MVA 方案:更新项目的 allowed_users + self::taskDeliver(new ManticoreSyncTask('update_project_allowed_users', [ + 'project_id' => $projectUser->project_id, + ])); + // 异步级联更新该项目下所有 visibility=1 的任务 + self::taskDeliver(new ManticoreSyncTask('cascade_project_users', [ 'project_id' => $projectUser->project_id, - 'userid' => $projectUser->userid, ])); } diff --git a/app/Tasks/ManticoreSyncTask.php b/app/Tasks/ManticoreSyncTask.php index 4f704069f..8c1bd3eab 100644 --- a/app/Tasks/ManticoreSyncTask.php +++ b/app/Tasks/ManticoreSyncTask.php @@ -16,9 +16,10 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Cache; /** - * 通用 Manticore Search 同步任务 + * 通用 Manticore Search 同步任务(MVA 权限方案) * * 支持文件、用户、项目、任务的同步操作 + * 使用 MVA (Multi-Value Attribute) 内联权限过滤 */ class ManticoreSyncTask extends AbstractTask { @@ -57,30 +58,6 @@ class ManticoreSyncTask extends AbstractTask } break; - case 'file_user_sync': - $fileId = $this->data['file_id'] ?? 0; - if ($fileId > 0) { - ManticoreFile::syncFileUsers($fileId); - } - break; - - case 'file_user_add': - $fileId = $this->data['file_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - $permission = $this->data['permission'] ?? 0; - if ($fileId > 0) { - ManticoreFile::addFileUser($fileId, $userid, $permission); - } - break; - - case 'file_user_remove': - $fileId = $this->data['file_id'] ?? 0; - $userid = $this->data['userid'] ?? null; - if ($fileId > 0) { - ManticoreFile::removeFileUser($fileId, $userid); - } - break; - case 'file_pshare_update': $fileIds = $this->data['file_ids'] ?? []; $pshare = $this->data['pshare'] ?? 0; @@ -89,6 +66,14 @@ class ManticoreSyncTask extends AbstractTask } break; + case 'update_file_allowed_users': + // 更新文件的 allowed_users(共享变更时调用) + $fileId = $this->data['file_id'] ?? 0; + if ($fileId > 0) { + ManticoreFile::updateAllowedUsers($fileId); + } + break; + // ============================== // 用户同步动作 // ============================== @@ -123,26 +108,20 @@ class ManticoreSyncTask extends AbstractTask } break; - case 'project_user_add': - $projectId = $this->data['project_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - if ($projectId > 0 && $userid > 0) { - ManticoreProject::addProjectUser($projectId, $userid); - } - break; - - case 'project_user_remove': - $projectId = $this->data['project_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - if ($projectId > 0 && $userid > 0) { - ManticoreProject::removeProjectUser($projectId, $userid); - } - break; - - case 'project_users_sync': + case 'update_project_allowed_users': + // 更新项目的 allowed_users(成员变更时调用) $projectId = $this->data['project_id'] ?? 0; if ($projectId > 0) { - ManticoreProject::syncProjectUsers($projectId); + ManticoreProject::updateAllowedUsers($projectId); + } + break; + + case 'cascade_project_users': + // 项目成员变更时,级联更新该项目下所有 visibility=1 的任务 + // 异步执行,避免阻塞 + $projectId = $this->data['project_id'] ?? 0; + if ($projectId > 0) { + ManticoreTask::cascadeUpdateByProject($projectId); } break; @@ -163,43 +142,13 @@ class ManticoreSyncTask extends AbstractTask } break; - case 'task_visibility_update': - $taskId = $this->data['task_id'] ?? 0; - $visibility = $this->data['visibility'] ?? 1; - if ($taskId > 0) { - ManticoreTask::updateVisibility($taskId, $visibility); - } - break; - - case 'task_user_add': - $taskId = $this->data['task_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - if ($taskId > 0 && $userid > 0) { - ManticoreTask::addTaskUser($taskId, $userid); - } - break; - - case 'task_user_remove': - $taskId = $this->data['task_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - if ($taskId > 0 && $userid > 0) { - ManticoreTask::removeTaskUser($taskId, $userid); - } - break; - - case 'task_visibility_user_remove': - // 特殊处理:删除 visibility user 时需要检查是否仍是任务成员 - $taskId = $this->data['task_id'] ?? 0; - $userid = $this->data['userid'] ?? 0; - if ($taskId > 0 && $userid > 0) { - ManticoreTask::removeVisibilityUser($taskId, $userid); - } - break; - - case 'task_users_sync': + case 'update_task_allowed_users': + // 更新任务的 allowed_users(成员变更时调用) $taskId = $this->data['task_id'] ?? 0; if ($taskId > 0) { - ManticoreTask::syncTaskUsers($taskId); + ManticoreTask::updateAllowedUsers($taskId); + // 级联更新子任务 + ManticoreTask::cascadeToChildren($taskId); } break; @@ -212,7 +161,7 @@ class ManticoreSyncTask extends AbstractTask /** * 增量更新(定时执行) - * 使用 --i 参数执行增量同步,会同步新增的向量数据和用户关系数据 + * 使用 --i 参数执行增量同步,会同步新增的向量数据 * * @return void */ @@ -227,7 +176,7 @@ class ManticoreSyncTask extends AbstractTask // 执行开始 Cache::put("ManticoreSyncTask:Time", time(), Carbon::now()->addMinutes(60)); - // 执行增量同步(同时同步向量表和用户关系表的新增数据) + // 执行增量同步(MVA 方案不需要单独同步关系表) @shell_exec("php /var/www/artisan manticore:sync-files --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-users --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-projects --i 2>&1 &"); @@ -241,4 +190,3 @@ class ManticoreSyncTask extends AbstractTask { } } -