diff --git a/app/Http/Controllers/Api/SearchController.php b/app/Http/Controllers/Api/SearchController.php index 964305477..3eca124f7 100644 --- a/app/Http/Controllers/Api/SearchController.php +++ b/app/Http/Controllers/Api/SearchController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers\Api; -use DB; use Request; use App\Models\File; use App\Models\Project; @@ -10,7 +9,6 @@ use App\Models\ProjectTask; use App\Models\User; use App\Models\WebSocketDialog; use App\Models\WebSocketDialogMsg; -use App\Models\WebSocketDialogUser; use App\Module\Base; use App\Module\Apps; use App\Module\Manticore\ManticoreFile; @@ -93,28 +91,13 @@ class SearchController extends AbstractController */ private function searchContactByMysql(string $key, int $take): array { - $builder = User::select(User::$basicField) + $users = User::select(User::$basicField) ->where('bot', 0) - ->whereNull('disable_at'); - - if (str_contains($key, "@")) { - $builder->where("email", "like", "%{$key}%"); - } elseif (Base::isNumber($key)) { - $builder->where(function ($query) use ($key) { - $query->where("userid", intval($key)) - ->orWhere("nickname", "like", "%{$key}%") - ->orWhere("pinyin", "like", "%{$key}%") - ->orWhere("profession", "like", "%{$key}%"); - }); - } else { - $builder->where(function ($query) use ($key) { - $query->where("nickname", "like", "%{$key}%") - ->orWhere("pinyin", "like", "%{$key}%") - ->orWhere("profession", "like", "%{$key}%"); - }); - } - - $users = $builder->orderByDesc('line_at')->take($take)->get(); + ->whereNull('disable_at') + ->searchByKeyword($key) + ->orderByDesc('line_at') + ->take($take) + ->get(); return $users->map(function ($user) { return array_merge($user->toArray(), [ @@ -193,12 +176,12 @@ class SearchController extends AbstractController { $projects = Project::authData() ->whereNull('projects.archived_at') - ->where("projects.name", "like", "%{$key}%") + ->searchByKeyword($key) ->orderByDesc('projects.id') ->take($take) ->get(); - return $projects->map(function ($project) use ($userid) { + return $projects->map(function ($project) { $array = $project->toArray(); $array['relevance'] = 0; $array['desc_preview'] = null; @@ -275,29 +258,18 @@ class SearchController extends AbstractController */ private function searchTaskByMysql(int $userid, string $key, int $take): array { - $builder = ProjectTask::with(['taskUser', 'taskTag']) + $tasks = ProjectTask::with(['taskUser', 'taskTag']) ->whereIn('project_tasks.project_id', function ($query) use ($userid) { $query->select('project_id') ->from('project_users') ->where('userid', $userid); }) ->whereNull('project_tasks.archived_at') - ->whereNull('project_tasks.deleted_at'); - - if (Base::isNumber($key)) { - $builder->where(function ($query) use ($key) { - $query->where("project_tasks.id", intval($key)) - ->orWhere("project_tasks.name", "like", "%{$key}%") - ->orWhere("project_tasks.desc", "like", "%{$key}%"); - }); - } else { - $builder->where(function ($query) use ($key) { - $query->where("project_tasks.name", "like", "%{$key}%") - ->orWhere("project_tasks.desc", "like", "%{$key}%"); - }); - } - - $tasks = $builder->orderByDesc('project_tasks.id')->take($take)->get(); + ->whereNull('project_tasks.deleted_at') + ->searchByKeyword($key) + ->orderByDesc('project_tasks.id') + ->take($take) + ->get(); return $tasks->map(function ($task) { $array = $task->toArray(); @@ -381,15 +353,10 @@ class SearchController extends AbstractController $results = []; // 搜索用户自己的文件 - $builder = File::where('userid', $userid); - if (Base::isNumber($key)) { - $builder->where(function ($query) use ($key) { - $query->where("id", $key)->orWhere("name", "like", "%{$key}%"); - }); - } else { - $builder->where("name", "like", "%{$key}%"); - } - $ownFiles = $builder->take($take)->get(); + $ownFiles = File::where('userid', $userid) + ->searchByKeyword($key) + ->take($take) + ->get(); foreach ($ownFiles as $file) { $results[] = array_merge($file->toArray(), [ @@ -401,24 +368,11 @@ class SearchController extends AbstractController // 搜索共享给用户的文件 $remaining = $take - count($results); if ($remaining > 0) { - $builder = File::whereIn('pshare', function ($queryA) use ($userid) { - $queryA->select('files.id') - ->from('files') - ->join('file_users', 'files.id', '=', 'file_users.file_id') - ->where('files.userid', '!=', $userid) - ->where(function ($queryB) use ($userid) { - $queryB->whereIn('file_users.userid', [0, $userid]); - }); - }); - if (Base::isNumber($key)) { - $builder->where(function ($query) use ($key) { - $query->where("id", $key)->orWhere("name", "like", "%{$key}%"); - }); - } else { - $builder->where("name", "like", "%{$key}%"); - } + $sharedFiles = File::sharedToUser($userid) + ->searchByKeyword($key) + ->take($remaining) + ->get(); - $sharedFiles = $builder->take($remaining)->get(); foreach ($sharedFiles as $file) { $temp = $file->toArray(); if ($file->pshare === $file->id) { @@ -501,32 +455,28 @@ class SearchController extends AbstractController */ private function searchMessageByMysql(int $userid, string $key, int $take, int $dialogId = 0): array { - $builder = DB::table('web_socket_dialog_msgs as m') - ->select([ - 'm.id as msg_id', - 'm.dialog_id', - 'm.userid', - 'm.type', - 'm.msg', - 'm.created_at', + $builder = WebSocketDialogMsg::select([ + 'id as msg_id', + 'dialog_id', + 'userid', + 'type', + 'msg', + 'created_at', ]) - ->join('web_socket_dialog_users as u', 'm.dialog_id', '=', 'u.dialog_id') - ->where('u.userid', $userid) - ->where('m.bot', 0) - ->where('m.key', 'like', "%{$key}%"); + ->accessibleByUser($userid) + ->where('bot', 0) + ->searchByKeyword($key); if ($dialogId > 0) { - $builder->where('m.dialog_id', $dialogId); + $builder->where('dialog_id', $dialogId); } - $items = $builder->orderByDesc('m.id') + $items = $builder->orderByDesc('id') ->limit($take) - ->get() - ->all(); + ->get(); - $results = []; - foreach ($items as $item) { - $results[] = [ + return $items->map(function ($item) { + return [ 'msg_id' => $item->msg_id, 'dialog_id' => $item->dialog_id, 'userid' => $item->userid, @@ -536,9 +486,7 @@ class SearchController extends AbstractController 'relevance' => 0, 'content_preview' => null, ]; - } - - return $results; + })->toArray(); } /** diff --git a/app/Models/File.php b/app/Models/File.php index da4b20db3..c65ce8d39 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -128,6 +128,45 @@ class File extends AbstractModel */ const zipMaxSize = 1024 * 1024 * 1024; // 1G + /** + * 按关键词搜索文件(Scope) + * 支持:文件ID(纯数字)、文件名 + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $keyword 搜索关键词 + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSearchByKeyword($query, string $keyword) + { + if (is_numeric($keyword)) { + return $query->where(function ($q) use ($keyword) { + $q->where("id", intval($keyword)) + ->orWhere("name", "like", "%{$keyword}%"); + }); + } + return $query->where("name", "like", "%{$keyword}%"); + } + + /** + * 筛选用户可访问的共享文件(Scope) + * 不包括用户自己的文件,仅返回他人共享给该用户的文件 + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $userid 用户ID + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSharedToUser($query, int $userid) + { + return $query->whereIn('pshare', function ($subQuery) use ($userid) { + $subQuery->select('files.id') + ->from('files') + ->join('file_users', 'files.id', '=', 'file_users.file_id') + ->where('files.userid', '!=', $userid) + ->where(function ($q) use ($userid) { + $q->whereIn('file_users.userid', [0, $userid]); + }); + }); + } /** * 获取文件列表 diff --git a/app/Models/Project.php b/app/Models/Project.php index 10b26503e..ad9dc7b28 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -164,6 +164,18 @@ class Project extends AbstractModel return $query; } + /** + * 按关键词搜索项目(Scope) + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $keyword 搜索关键词 + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSearchByKeyword($query, string $keyword) + { + return $query->where("projects.name", "like", "%{$keyword}%"); + } + /** * 获取任务统计数据 * @param $userid diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index 6bd16fb94..faa6e8c1a 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -353,6 +353,32 @@ class ProjectTask extends AbstractModel return $query; } + /** + * 按关键词搜索任务(Scope) + * 支持:任务ID(纯数字)、任务名称、描述 + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $keyword 搜索关键词 + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSearchByKeyword($query, string $keyword) + { + if (is_numeric($keyword)) { + // 纯数字:匹配任务ID 或 名称/描述 + return $query->where(function ($q) use ($keyword) { + $q->where("project_tasks.id", intval($keyword)) + ->orWhere("project_tasks.name", "like", "%{$keyword}%") + ->orWhere("project_tasks.desc", "like", "%{$keyword}%"); + }); + } + + // 普通文本:搜索名称/描述 + return $query->where(function ($q) use ($keyword) { + $q->where("project_tasks.name", "like", "%{$keyword}%") + ->orWhere("project_tasks.desc", "like", "%{$keyword}%"); + }); + } + /** * 生成描述 * @param $content diff --git a/app/Models/User.php b/app/Models/User.php index 6f4160dac..eb3c5c642 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -773,24 +773,51 @@ class User extends AbstractModel }); } + /** + * 按关键词搜索用户(Scope) + * 支持:邮箱(含@)、用户ID(纯数字)、昵称/拼音/职业 + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $keyword 搜索关键词 + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSearchByKeyword($query, string $keyword) + { + if (str_contains($keyword, "@")) { + // 包含 @ 按邮箱搜索 + return $query->where("email", "like", "%{$keyword}%"); + } + + if (is_numeric($keyword)) { + // 纯数字:匹配用户ID 或 昵称/拼音/职业 + return $query->where(function ($q) use ($keyword) { + $q->where("userid", intval($keyword)) + ->orWhere("nickname", "like", "%{$keyword}%") + ->orWhere("pinyin", "like", "%{$keyword}%") + ->orWhere("profession", "like", "%{$keyword}%"); + }); + } + + // 普通文本:搜索昵称/拼音/职业 + return $query->where(function ($q) use ($keyword) { + $q->where("nickname", "like", "%{$keyword}%") + ->orWhere("pinyin", "like", "%{$keyword}%") + ->orWhere("profession", "like", "%{$keyword}%"); + }); + } + /** * 搜索用户 * @param $key * @param $take * @return User[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection + * @deprecated 建议使用 scopeSearchByKeyword */ public static function searchUser($key, $take = 20) { return User::select(User::$basicField) - ->where(function ($query) use ($key) { - if (str_contains($key, "@")) { - $query->where("email", "like", "%{$key}%"); - } else { - $query->where("nickname", "like", "%{$key}%") - ->orWhere("pinyin", "like", "%{$key}%") - ->orWhere("profession", "like", "%{$key}%"); - } - })->orderBy('userid') + ->searchByKeyword($key) + ->orderBy('userid') ->take($take) ->get(); } diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index 419fb17c6..b28c13fa6 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -111,6 +111,36 @@ class WebSocketDialogMsg extends AbstractModel return $this->hasOne(User::class, 'userid', 'userid'); } + /** + * 按关键词搜索消息(Scope) + * 搜索 key 字段(消息的可搜索内容) + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $keyword 搜索关键词 + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSearchByKeyword($query, string $keyword) + { + return $query->where('key', 'like', "%{$keyword}%"); + } + + /** + * 筛选用户可访问的对话消息(Scope) + * 通过 web_socket_dialog_users 表验证用户对对话的访问权限 + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $userid 用户ID + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeAccessibleByUser($query, int $userid) + { + return $query->whereIn('dialog_id', function ($subQuery) use ($userid) { + $subQuery->select('dialog_id') + ->from('web_socket_dialog_users') + ->where('userid', $userid); + }); + } + /** * 阅读占比 * @return int|mixed