mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-06 09:27:05 +00:00
feat: 新增对话ID参数支持,优化搜索功能以支持对话过滤
This commit is contained in:
parent
a52dc14369
commit
908171a977
@ -47,7 +47,7 @@ class SearchController extends AbstractController
|
||||
|
||||
$key = trim(Request::input('key'));
|
||||
$searchType = Request::input('search_type', 'hybrid');
|
||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
||||
$take = Base::getPaginate(50, 20, 'take');
|
||||
|
||||
if (empty($key)) {
|
||||
return Base::retSuccess('success', []);
|
||||
@ -103,7 +103,7 @@ class SearchController extends AbstractController
|
||||
|
||||
$key = trim(Request::input('key'));
|
||||
$searchType = Request::input('search_type', 'hybrid');
|
||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
||||
$take = Base::getPaginate(50, 20, 'take');
|
||||
|
||||
if (empty($key)) {
|
||||
return Base::retSuccess('success', []);
|
||||
@ -158,7 +158,7 @@ class SearchController extends AbstractController
|
||||
|
||||
$key = trim(Request::input('key'));
|
||||
$searchType = Request::input('search_type', 'hybrid');
|
||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
||||
$take = Base::getPaginate(50, 20, 'take');
|
||||
|
||||
if (empty($key)) {
|
||||
return Base::retSuccess('success', []);
|
||||
@ -214,7 +214,7 @@ class SearchController extends AbstractController
|
||||
|
||||
$key = trim(Request::input('key'));
|
||||
$searchType = Request::input('search_type', 'hybrid');
|
||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
||||
$take = Base::getPaginate(50, 20, 'take');
|
||||
|
||||
if (empty($key)) {
|
||||
return Base::retSuccess('success', []);
|
||||
@ -256,6 +256,11 @@ class SearchController extends AbstractController
|
||||
* @apiParam {String} key 搜索关键词
|
||||
* @apiParam {String} [search_type] 搜索类型(text/vector/hybrid,默认:hybrid)
|
||||
* @apiParam {Number} [take] 获取数量(默认:20,最大:50)
|
||||
* @apiParam {String} [mode] 返回模式(message/position/dialog,默认:message)
|
||||
* - message: 返回消息详细信息
|
||||
* - position: 只返回消息ID
|
||||
* - dialog: 返回对话级数据
|
||||
* @apiParam {Number} [dialog_id] 对话ID(筛选指定对话内的消息)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -271,46 +276,89 @@ class SearchController extends AbstractController
|
||||
|
||||
$key = trim(Request::input('key'));
|
||||
$searchType = Request::input('search_type', 'hybrid');
|
||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
||||
$take = Base::getPaginate(50, 20, 'take');
|
||||
$mode = Request::input('mode', 'message');
|
||||
$dialogId = intval(Request::input('dialog_id', 0));
|
||||
|
||||
// 验证 mode 参数
|
||||
if (!in_array($mode, ['message', 'position', 'dialog'])) {
|
||||
$mode = 'message';
|
||||
}
|
||||
|
||||
if (empty($key)) {
|
||||
return Base::retSuccess('success', []);
|
||||
}
|
||||
|
||||
$results = ManticoreMsg::search($user->userid, $key, $searchType, 0, $take);
|
||||
|
||||
// 补充消息完整信息
|
||||
$msgIds = array_column($results, 'msg_id');
|
||||
if (!empty($msgIds)) {
|
||||
$msgs = WebSocketDialogMsg::whereIn('id', $msgIds)
|
||||
->with(['user' => function ($query) {
|
||||
$query->select(User::$basicField);
|
||||
}])
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
$formattedResults = [];
|
||||
foreach ($results as $item) {
|
||||
$msgData = $msgs->get($item['msg_id']);
|
||||
if ($msgData) {
|
||||
$formattedResults[] = [
|
||||
'id' => $msgData->id,
|
||||
'msg_id' => $msgData->id,
|
||||
'dialog_id' => $msgData->dialog_id,
|
||||
'userid' => $msgData->userid,
|
||||
'type' => $msgData->type,
|
||||
'msg' => $msgData->msg,
|
||||
'created_at' => $msgData->created_at,
|
||||
'user' => $msgData->user,
|
||||
'relevance' => $item['relevance'] ?? 0,
|
||||
'content_preview' => $item['content_preview'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', $formattedResults);
|
||||
// 如果指定了 dialog_id,需要验证用户有权限访问该对话
|
||||
if ($dialogId > 0) {
|
||||
\App\Models\WebSocketDialog::checkDialog($dialogId);
|
||||
}
|
||||
|
||||
return Base::retSuccess('success', []);
|
||||
$results = ManticoreMsg::search($user->userid, $key, $searchType, 0, $take, $dialogId);
|
||||
|
||||
// 根据 mode 返回不同格式的数据
|
||||
switch ($mode) {
|
||||
case 'position':
|
||||
// 只返回消息ID
|
||||
$data = array_column($results, 'msg_id');
|
||||
return Base::retSuccess('success', compact('data'));
|
||||
|
||||
case 'dialog':
|
||||
// 返回对话级数据
|
||||
$list = [];
|
||||
$seenDialogs = [];
|
||||
foreach ($results as $item) {
|
||||
$dialogIdFromResult = $item['dialog_id'];
|
||||
// 每个对话只返回一次
|
||||
if (isset($seenDialogs[$dialogIdFromResult])) {
|
||||
continue;
|
||||
}
|
||||
$seenDialogs[$dialogIdFromResult] = true;
|
||||
|
||||
if ($dialog = \App\Models\WebSocketDialog::find($dialogIdFromResult)) {
|
||||
$dialogData = array_merge($dialog->toArray(), [
|
||||
'search_msg_id' => $item['msg_id'],
|
||||
]);
|
||||
$list[] = \App\Models\WebSocketDialog::synthesizeData($dialogData, $user->userid);
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', ['data' => $list]);
|
||||
|
||||
case 'message':
|
||||
default:
|
||||
// 返回消息详细信息(默认行为)
|
||||
$msgIds = array_column($results, 'msg_id');
|
||||
if (!empty($msgIds)) {
|
||||
$msgs = WebSocketDialogMsg::whereIn('id', $msgIds)
|
||||
->with(['user' => function ($query) {
|
||||
$query->select(User::$basicField);
|
||||
}])
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
$formattedResults = [];
|
||||
foreach ($results as $item) {
|
||||
$msgData = $msgs->get($item['msg_id']);
|
||||
if ($msgData) {
|
||||
$formattedResults[] = [
|
||||
'id' => $msgData->id,
|
||||
'msg_id' => $msgData->id,
|
||||
'dialog_id' => $msgData->dialog_id,
|
||||
'userid' => $msgData->userid,
|
||||
'type' => $msgData->type,
|
||||
'msg' => $msgData->msg,
|
||||
'created_at' => $msgData->created_at,
|
||||
'user' => $msgData->user,
|
||||
'relevance' => $item['relevance'] ?? 0,
|
||||
'content_preview' => $item['content_preview'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', $formattedResults);
|
||||
}
|
||||
|
||||
return Base::retSuccess('success', []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1519,9 +1519,10 @@ class ManticoreBase
|
||||
* @param int $userid 用户ID(权限过滤)
|
||||
* @param int $limit 返回数量
|
||||
* @param int $offset 偏移量
|
||||
* @param int $dialogId 对话ID(0表示不限制)
|
||||
* @return array 搜索结果
|
||||
*/
|
||||
public static function msgFullTextSearch(string $keyword, int $userid = 0, int $limit = 20, int $offset = 0): array
|
||||
public static function msgFullTextSearch(string $keyword, int $userid = 0, int $limit = 20, int $offset = 0, int $dialogId = 0): array
|
||||
{
|
||||
if (empty($keyword)) {
|
||||
return [];
|
||||
@ -1530,39 +1531,30 @@ class ManticoreBase
|
||||
$instance = new self();
|
||||
$escapedKeyword = self::escapeMatch($keyword);
|
||||
|
||||
// 构建过滤条件
|
||||
$conditions = ["MATCH('@content {$escapedKeyword}')"];
|
||||
if ($userid > 0) {
|
||||
// 使用 MVA 权限过滤
|
||||
$sql = "
|
||||
SELECT
|
||||
id,
|
||||
msg_id,
|
||||
dialog_id,
|
||||
userid,
|
||||
msg_type,
|
||||
content,
|
||||
created_at,
|
||||
WEIGHT() as relevance
|
||||
FROM msg_vectors
|
||||
WHERE MATCH('@content {$escapedKeyword}')
|
||||
AND allowed_users = " . (int)$userid . "
|
||||
ORDER BY relevance DESC
|
||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||
} else {
|
||||
$sql = "
|
||||
SELECT
|
||||
id,
|
||||
msg_id,
|
||||
dialog_id,
|
||||
userid,
|
||||
msg_type,
|
||||
content,
|
||||
created_at,
|
||||
WEIGHT() as relevance
|
||||
FROM msg_vectors
|
||||
WHERE MATCH('@content {$escapedKeyword}')
|
||||
ORDER BY relevance DESC
|
||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||
$conditions[] = "allowed_users = " . (int)$userid;
|
||||
}
|
||||
if ($dialogId > 0) {
|
||||
$conditions[] = "dialog_id = " . (int)$dialogId;
|
||||
}
|
||||
$whereClause = implode(' AND ', $conditions);
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
id,
|
||||
msg_id,
|
||||
dialog_id,
|
||||
userid,
|
||||
msg_type,
|
||||
content,
|
||||
created_at,
|
||||
WEIGHT() as relevance
|
||||
FROM msg_vectors
|
||||
WHERE {$whereClause}
|
||||
ORDER BY relevance DESC
|
||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||
|
||||
return $instance->query($sql);
|
||||
}
|
||||
@ -1573,9 +1565,10 @@ class ManticoreBase
|
||||
* @param array $queryVector 查询向量
|
||||
* @param int $userid 用户ID(权限过滤)
|
||||
* @param int $limit 返回数量
|
||||
* @param int $dialogId 对话ID(0表示不限制)
|
||||
* @return array 搜索结果
|
||||
*/
|
||||
public static function msgVectorSearch(array $queryVector, int $userid = 0, int $limit = 20): array
|
||||
public static function msgVectorSearch(array $queryVector, int $userid = 0, int $limit = 20, int $dialogId = 0): array
|
||||
{
|
||||
if (empty($queryVector)) {
|
||||
return [];
|
||||
@ -1584,8 +1577,9 @@ class ManticoreBase
|
||||
$instance = new self();
|
||||
$vectorStr = '(' . implode(',', $queryVector) . ')';
|
||||
|
||||
// KNN 搜索需要先获取更多结果,再在应用层过滤权限
|
||||
$fetchLimit = $userid > 0 ? $limit * 5 : $limit;
|
||||
// KNN 搜索需要先获取更多结果,再在应用层过滤权限和对话
|
||||
$needFilter = $userid > 0 || $dialogId > 0;
|
||||
$fetchLimit = $needFilter ? $limit * 5 : $limit;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
@ -1622,6 +1616,14 @@ class ManticoreBase
|
||||
$results = array_values($results);
|
||||
}
|
||||
|
||||
// 对话过滤
|
||||
if ($dialogId > 0 && !empty($results)) {
|
||||
$results = array_filter($results, function ($item) use ($dialogId) {
|
||||
return $item['dialog_id'] == $dialogId;
|
||||
});
|
||||
$results = array_values($results);
|
||||
}
|
||||
|
||||
return array_slice($results, 0, $limit);
|
||||
}
|
||||
|
||||
@ -1632,12 +1634,13 @@ class ManticoreBase
|
||||
* @param array $queryVector 查询向量
|
||||
* @param int $userid 用户ID(权限过滤)
|
||||
* @param int $limit 返回数量
|
||||
* @param int $dialogId 对话ID(0表示不限制)
|
||||
* @return array 搜索结果
|
||||
*/
|
||||
public static function msgHybridSearch(string $keyword, array $queryVector, int $userid = 0, int $limit = 20): array
|
||||
public static function msgHybridSearch(string $keyword, array $queryVector, int $userid = 0, int $limit = 20, int $dialogId = 0): array
|
||||
{
|
||||
$textResults = self::msgFullTextSearch($keyword, $userid, 50, 0);
|
||||
$vectorResults = !empty($queryVector) ? self::msgVectorSearch($queryVector, $userid, 50) : [];
|
||||
$textResults = self::msgFullTextSearch($keyword, $userid, 50, 0, $dialogId);
|
||||
$vectorResults = !empty($queryVector) ? self::msgVectorSearch($queryVector, $userid, 50, $dialogId) : [];
|
||||
|
||||
$scores = [];
|
||||
$items = [];
|
||||
|
||||
@ -77,9 +77,10 @@ class ManticoreMsg
|
||||
* @param string $searchType 搜索类型: text/vector/hybrid
|
||||
* @param int $from 起始位置
|
||||
* @param int $size 返回数量
|
||||
* @param int $dialogId 对话ID(0表示不限制)
|
||||
* @return array 搜索结果
|
||||
*/
|
||||
public static function search(int $userid, string $keyword, string $searchType = 'hybrid', int $from = 0, int $size = 20): array
|
||||
public static function search(int $userid, string $keyword, string $searchType = 'hybrid', int $from = 0, int $size = 20, int $dialogId = 0): array
|
||||
{
|
||||
if (empty($keyword)) {
|
||||
return [];
|
||||
@ -94,7 +95,7 @@ class ManticoreMsg
|
||||
case 'text':
|
||||
// 纯全文搜索
|
||||
return self::formatSearchResults(
|
||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from)
|
||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from, $dialogId)
|
||||
);
|
||||
|
||||
case 'vector':
|
||||
@ -103,11 +104,11 @@ class ManticoreMsg
|
||||
if (empty($embedding)) {
|
||||
// embedding 获取失败,降级到全文搜索
|
||||
return self::formatSearchResults(
|
||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from)
|
||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from, $dialogId)
|
||||
);
|
||||
}
|
||||
return self::formatSearchResults(
|
||||
ManticoreBase::msgVectorSearch($embedding, $userid, $size)
|
||||
ManticoreBase::msgVectorSearch($embedding, $userid, $size, $dialogId)
|
||||
);
|
||||
|
||||
case 'hybrid':
|
||||
@ -115,7 +116,7 @@ class ManticoreMsg
|
||||
// 混合搜索
|
||||
$embedding = self::getEmbedding($keyword);
|
||||
return self::formatSearchResults(
|
||||
ManticoreBase::msgHybridSearch($keyword, $embedding, $userid, $size)
|
||||
ManticoreBase::msgHybridSearch($keyword, $embedding, $userid, $size, $dialogId)
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user