feat: 迁移至 MVA 权限方案

- 表结构:为 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 变更、子任务升级主任务等
This commit is contained in:
kuaifan 2026-01-02 02:03:21 +00:00
parent fdf5ceeaab
commit c08323e1ea
13 changed files with 593 additions and 1477 deletions

View File

@ -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 SearchMVA 权限方案)';
/**
* @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());
}
}

View File

@ -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 SearchMVA 权限方案)';
/**
* @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());
}
}

View File

@ -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 SearchMVA 权限方案)';
/**
* @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());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 用户IDnull 表示删除所有
* @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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,
]));
}
}

View File

@ -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()));
}
}

View File

@ -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,
]));
}

View File

@ -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
//
}
}

View File

@ -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,
]));
}

View File

@ -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
{
}
}