mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-26 23:33:01 +00:00
feat: 添加用户标签功能,更新用户索引以支持标签创建、更新和删除事件
This commit is contained in:
parent
f42250b8b7
commit
90a5624877
@ -7,6 +7,7 @@ use App\Models\File;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectTask;
|
use App\Models\ProjectTask;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserTag;
|
||||||
use App\Models\WebSocketDialog;
|
use App\Models\WebSocketDialog;
|
||||||
use App\Models\WebSocketDialogMsg;
|
use App\Models\WebSocketDialogMsg;
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
@ -67,9 +68,14 @@ class SearchController extends AbstractController
|
|||||||
foreach ($results as &$item) {
|
foreach ($results as &$item) {
|
||||||
$userData = $users->get($item['userid']);
|
$userData = $users->get($item['userid']);
|
||||||
if ($userData) {
|
if ($userData) {
|
||||||
|
// 标签直接从 Manticore 搜索结果获取(空格分隔的字符串转数组)
|
||||||
|
$tagsStr = $item['tags'] ?? '';
|
||||||
|
$searchTags = !empty($tagsStr) ? preg_split('/\s+/', trim($tagsStr)) : [];
|
||||||
|
|
||||||
$item = array_merge($userData->toArray(), [
|
$item = array_merge($userData->toArray(), [
|
||||||
'relevance' => $item['relevance'] ?? 0,
|
'relevance' => $item['relevance'] ?? 0,
|
||||||
'introduction_preview' => $item['introduction_preview'] ?? null,
|
'introduction_preview' => $item['introduction_preview'] ?? null,
|
||||||
|
'search_tags' => $searchTags,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,10 +105,15 @@ class SearchController extends AbstractController
|
|||||||
->take($take)
|
->take($take)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $users->map(function ($user) {
|
// 获取用户标签
|
||||||
|
$userids = $users->pluck('userid')->toArray();
|
||||||
|
$userTags = $this->getUserTagsMap($userids);
|
||||||
|
|
||||||
|
return $users->map(function ($user) use ($userTags) {
|
||||||
return array_merge($user->toArray(), [
|
return array_merge($user->toArray(), [
|
||||||
'relevance' => 0,
|
'relevance' => 0,
|
||||||
'introduction_preview' => null,
|
'introduction_preview' => null,
|
||||||
|
'search_tags' => $userTags[$user->userid] ?? [],
|
||||||
]);
|
]);
|
||||||
})->toArray();
|
})->toArray();
|
||||||
}
|
}
|
||||||
@ -569,4 +580,40 @@ class SearchController extends AbstractController
|
|||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取用户标签映射
|
||||||
|
*
|
||||||
|
* @param array $userids 用户ID数组
|
||||||
|
* @return array 用户ID => 标签名称数组的映射
|
||||||
|
*/
|
||||||
|
private function getUserTagsMap(array $userids): array
|
||||||
|
{
|
||||||
|
if (empty($userids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有用户的标签(带认可数)
|
||||||
|
$tags = UserTag::whereIn('user_id', $userids)
|
||||||
|
->withCount('recognitions')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// 按用户分组,每个用户取 Top 10 标签
|
||||||
|
$result = [];
|
||||||
|
foreach ($userids as $userid) {
|
||||||
|
$result[$userid] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userTags = $tags->groupBy('user_id');
|
||||||
|
foreach ($userTags as $userid => $tagCollection) {
|
||||||
|
$result[$userid] = $tagCollection
|
||||||
|
->sortByDesc('recognitions_count')
|
||||||
|
->take(10)
|
||||||
|
->pluck('name')
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,8 +107,8 @@ class ManticoreBase
|
|||||||
userid BIGINT,
|
userid BIGINT,
|
||||||
nickname TEXT,
|
nickname TEXT,
|
||||||
email STRING,
|
email STRING,
|
||||||
tel STRING,
|
|
||||||
profession TEXT,
|
profession TEXT,
|
||||||
|
tags TEXT,
|
||||||
introduction TEXT,
|
introduction TEXT,
|
||||||
content_vector float_vector knn_type='hnsw' knn_dims='1536' hnsw_similarity='cosine'
|
content_vector float_vector knn_type='hnsw' knn_dims='1536' hnsw_similarity='cosine'
|
||||||
) charset_table='chinese' morphology='icu_chinese'
|
) charset_table='chinese' morphology='icu_chinese'
|
||||||
@ -769,12 +769,12 @@ class ManticoreBase
|
|||||||
userid,
|
userid,
|
||||||
nickname,
|
nickname,
|
||||||
email,
|
email,
|
||||||
tel,
|
|
||||||
profession,
|
profession,
|
||||||
|
tags,
|
||||||
introduction,
|
introduction,
|
||||||
WEIGHT() as relevance
|
WEIGHT() as relevance
|
||||||
FROM user_vectors
|
FROM user_vectors
|
||||||
WHERE MATCH('@(nickname,profession,introduction) {$escapedKeyword}')
|
WHERE MATCH('@(nickname,profession,tags,introduction) {$escapedKeyword}')
|
||||||
ORDER BY relevance DESC
|
ORDER BY relevance DESC
|
||||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||||
|
|
||||||
@ -803,8 +803,8 @@ class ManticoreBase
|
|||||||
userid,
|
userid,
|
||||||
nickname,
|
nickname,
|
||||||
email,
|
email,
|
||||||
tel,
|
|
||||||
profession,
|
profession,
|
||||||
|
tags,
|
||||||
introduction,
|
introduction,
|
||||||
KNN_DIST() as distance
|
KNN_DIST() as distance
|
||||||
FROM user_vectors
|
FROM user_vectors
|
||||||
@ -892,7 +892,7 @@ class ManticoreBase
|
|||||||
if ($vectorValue) {
|
if ($vectorValue) {
|
||||||
$vectorValue = str_replace(['[', ']'], ['(', ')'], $vectorValue);
|
$vectorValue = str_replace(['[', ']'], ['(', ')'], $vectorValue);
|
||||||
$sql = "INSERT INTO user_vectors
|
$sql = "INSERT INTO user_vectors
|
||||||
(id, userid, nickname, email, tel, profession, introduction, content_vector)
|
(id, userid, nickname, email, profession, tags, introduction, content_vector)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, {$vectorValue})";
|
VALUES (?, ?, ?, ?, ?, ?, ?, {$vectorValue})";
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
@ -900,13 +900,13 @@ class ManticoreBase
|
|||||||
$userid,
|
$userid,
|
||||||
$data['nickname'] ?? '',
|
$data['nickname'] ?? '',
|
||||||
$data['email'] ?? '',
|
$data['email'] ?? '',
|
||||||
$data['tel'] ?? '',
|
|
||||||
$data['profession'] ?? '',
|
$data['profession'] ?? '',
|
||||||
|
$data['tags'] ?? '',
|
||||||
$data['introduction'] ?? ''
|
$data['introduction'] ?? ''
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$sql = "INSERT INTO user_vectors
|
$sql = "INSERT INTO user_vectors
|
||||||
(id, userid, nickname, email, tel, profession, introduction)
|
(id, userid, nickname, email, profession, tags, introduction)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
@ -914,8 +914,8 @@ class ManticoreBase
|
|||||||
$userid,
|
$userid,
|
||||||
$data['nickname'] ?? '',
|
$data['nickname'] ?? '',
|
||||||
$data['email'] ?? '',
|
$data['email'] ?? '',
|
||||||
$data['tel'] ?? '',
|
|
||||||
$data['profession'] ?? '',
|
$data['profession'] ?? '',
|
||||||
|
$data['tags'] ?? '',
|
||||||
$data['introduction'] ?? ''
|
$data['introduction'] ?? ''
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -250,10 +250,14 @@ class ManticoreFile
|
|||||||
|
|
||||||
// 只有明确要求时才生成向量(默认不生成,由后台任务处理)
|
// 只有明确要求时才生成向量(默认不生成,由后台任务处理)
|
||||||
$embedding = null;
|
$embedding = null;
|
||||||
if ($withVector && !empty($content) && Apps::isInstalled('ai')) {
|
if ($withVector && Apps::isInstalled('ai')) {
|
||||||
$embeddingResult = ManticoreBase::getEmbedding($content);
|
// 向量内容包含文件名和文件内容
|
||||||
if (!empty($embeddingResult)) {
|
$vectorContent = self::buildVectorContent($file->name, $content);
|
||||||
$embedding = '[' . implode(',', $embeddingResult) . ']';
|
if (!empty($vectorContent)) {
|
||||||
|
$embeddingResult = ManticoreBase::getEmbedding($vectorContent);
|
||||||
|
if (!empty($embeddingResult)) {
|
||||||
|
$embedding = '[' . implode(',', $embeddingResult) . ']';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,6 +403,28 @@ class ManticoreFile
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建用于生成向量的内容
|
||||||
|
* 包含文件名和文件内容,确保语义搜索能匹配文件名
|
||||||
|
*
|
||||||
|
* @param string $fileName 文件名
|
||||||
|
* @param string $content 文件内容
|
||||||
|
* @return string 用于生成向量的文本
|
||||||
|
*/
|
||||||
|
private static function buildVectorContent(string $fileName, string $content): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
|
||||||
|
if (!empty($fileName)) {
|
||||||
|
$parts[] = $fileName;
|
||||||
|
}
|
||||||
|
if (!empty($content)) {
|
||||||
|
$parts[] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' ', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空所有索引
|
* 清空所有索引
|
||||||
*
|
*
|
||||||
@ -486,7 +512,7 @@ class ManticoreFile
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 提取每个文件的内容
|
// 2. 提取每个文件的内容(包含文件名)
|
||||||
$fileContents = [];
|
$fileContents = [];
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
// 检查文件大小限制
|
// 检查文件大小限制
|
||||||
@ -496,10 +522,12 @@ class ManticoreFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
$content = self::extractFileContent($file);
|
$content = self::extractFileContent($file);
|
||||||
if (!empty($content)) {
|
// 向量内容包含文件名和文件内容
|
||||||
|
$vectorContent = self::buildVectorContent($file->name, $content);
|
||||||
|
if (!empty($vectorContent)) {
|
||||||
// 限制内容长度
|
// 限制内容长度
|
||||||
$content = mb_substr($content, 0, self::MAX_CONTENT_LENGTH);
|
$vectorContent = mb_substr($vectorContent, 0, self::MAX_CONTENT_LENGTH);
|
||||||
$fileContents[$file->id] = $content;
|
$fileContents[$file->id] = $vectorContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Module\Manticore;
|
namespace App\Module\Manticore;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserTag;
|
||||||
use App\Module\Apps;
|
use App\Module\Apps;
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
use App\Module\AI;
|
use App\Module\AI;
|
||||||
@ -90,8 +91,8 @@ class ManticoreUser
|
|||||||
'userid' => $item['userid'],
|
'userid' => $item['userid'],
|
||||||
'nickname' => $item['nickname'],
|
'nickname' => $item['nickname'],
|
||||||
'email' => $item['email'],
|
'email' => $item['email'],
|
||||||
'tel' => $item['tel'],
|
|
||||||
'profession' => $item['profession'],
|
'profession' => $item['profession'],
|
||||||
|
'tags' => $item['tags'] ?? '',
|
||||||
'introduction_preview' => isset($item['introduction']) ? mb_substr($item['introduction'], 0, 200) : null,
|
'introduction_preview' => isset($item['introduction']) ? mb_substr($item['introduction'], 0, 200) : null,
|
||||||
'relevance' => $item['relevance'] ?? $item['similarity'] ?? $item['rrf_score'] ?? 0,
|
'relevance' => $item['relevance'] ?? $item['similarity'] ?? $item['rrf_score'] ?? 0,
|
||||||
];
|
];
|
||||||
@ -103,6 +104,24 @@ class ManticoreUser
|
|||||||
// 同步方法
|
// 同步方法
|
||||||
// ==============================
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的标签(按认可数排序,最多10个)
|
||||||
|
*
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @return string 标签名称,空格分隔
|
||||||
|
*/
|
||||||
|
public static function getUserTags(int $userid): string
|
||||||
|
{
|
||||||
|
$tags = UserTag::where('user_id', $userid)
|
||||||
|
->withCount('recognitions')
|
||||||
|
->orderByDesc('recognitions_count')
|
||||||
|
->limit(10)
|
||||||
|
->pluck('name')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return implode(' ', $tags);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步单个用户到 Manticore
|
* 同步单个用户到 Manticore
|
||||||
*
|
*
|
||||||
@ -127,8 +146,11 @@ class ManticoreUser
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 获取用户标签(Top 10)
|
||||||
|
$tags = self::getUserTags($user->userid);
|
||||||
|
|
||||||
// 构建用于搜索的文本内容
|
// 构建用于搜索的文本内容
|
||||||
$searchableContent = self::buildSearchableContent($user);
|
$searchableContent = self::buildSearchableContent($user, $tags);
|
||||||
|
|
||||||
// 只有明确要求时才生成向量(默认不生成,由后台任务处理)
|
// 只有明确要求时才生成向量(默认不生成,由后台任务处理)
|
||||||
$embedding = null;
|
$embedding = null;
|
||||||
@ -144,8 +166,8 @@ class ManticoreUser
|
|||||||
'userid' => $user->userid,
|
'userid' => $user->userid,
|
||||||
'nickname' => $user->nickname ?? '',
|
'nickname' => $user->nickname ?? '',
|
||||||
'email' => $user->email ?? '',
|
'email' => $user->email ?? '',
|
||||||
'tel' => $user->tel ?? '',
|
|
||||||
'profession' => $user->profession ?? '',
|
'profession' => $user->profession ?? '',
|
||||||
|
'tags' => $tags,
|
||||||
'introduction' => $user->introduction ?? '',
|
'introduction' => $user->introduction ?? '',
|
||||||
'content_vector' => $embedding,
|
'content_vector' => $embedding,
|
||||||
]);
|
]);
|
||||||
@ -164,9 +186,10 @@ class ManticoreUser
|
|||||||
* 构建可搜索的文本内容
|
* 构建可搜索的文本内容
|
||||||
*
|
*
|
||||||
* @param User $user 用户模型
|
* @param User $user 用户模型
|
||||||
|
* @param string $tags 用户标签(空格分隔)
|
||||||
* @return string 可搜索的文本
|
* @return string 可搜索的文本
|
||||||
*/
|
*/
|
||||||
private static function buildSearchableContent(User $user): string
|
private static function buildSearchableContent(User $user, string $tags = ''): string
|
||||||
{
|
{
|
||||||
$parts = [];
|
$parts = [];
|
||||||
|
|
||||||
@ -179,6 +202,9 @@ class ManticoreUser
|
|||||||
if (!empty($user->profession)) {
|
if (!empty($user->profession)) {
|
||||||
$parts[] = $user->profession;
|
$parts[] = $user->profession;
|
||||||
}
|
}
|
||||||
|
if (!empty($tags)) {
|
||||||
|
$parts[] = $tags;
|
||||||
|
}
|
||||||
if (!empty($user->introduction)) {
|
if (!empty($user->introduction)) {
|
||||||
$parts[] = $user->introduction;
|
$parts[] = $user->introduction;
|
||||||
}
|
}
|
||||||
@ -280,10 +306,11 @@ class ManticoreUser
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 提取每个用户的内容
|
// 2. 提取每个用户的内容(包含标签)
|
||||||
$userContents = [];
|
$userContents = [];
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$searchableContent = self::buildSearchableContent($user);
|
$tags = self::getUserTags($user->userid);
|
||||||
|
$searchableContent = self::buildSearchableContent($user, $tags);
|
||||||
if (!empty($searchableContent)) {
|
if (!empty($searchableContent)) {
|
||||||
$userContents[$user->userid] = $searchableContent;
|
$userContents[$user->userid] = $searchableContent;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,8 +35,8 @@ class UserObserver extends AbstractObserver
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有搜索相关字段变化
|
// 检查是否有搜索相关字段变化(不含 tel,因为 Manticore 不索引电话)
|
||||||
$searchableFields = ['nickname', 'email', 'tel', 'profession', 'introduction', 'disable_at'];
|
$searchableFields = ['nickname', 'email', 'profession', 'introduction', 'disable_at'];
|
||||||
$isDirty = false;
|
$isDirty = false;
|
||||||
foreach ($searchableFields as $field) {
|
foreach ($searchableFields as $field) {
|
||||||
if ($user->isDirty($field)) {
|
if ($user->isDirty($field)) {
|
||||||
|
|||||||
69
app/Observers/UserTagObserver.php
Normal file
69
app/Observers/UserTagObserver.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserTag;
|
||||||
|
use App\Tasks\ManticoreSyncTask;
|
||||||
|
|
||||||
|
class UserTagObserver extends AbstractObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the UserTag "created" event.
|
||||||
|
* 标签创建时,触发用户索引更新
|
||||||
|
*
|
||||||
|
* @param \App\Models\UserTag $userTag
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function created(UserTag $userTag)
|
||||||
|
{
|
||||||
|
$this->syncUserToManticore($userTag->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the UserTag "updated" event.
|
||||||
|
* 标签更新时,触发用户索引更新
|
||||||
|
*
|
||||||
|
* @param \App\Models\UserTag $userTag
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updated(UserTag $userTag)
|
||||||
|
{
|
||||||
|
// 只有标签名称变化时才需要更新
|
||||||
|
if ($userTag->isDirty('name')) {
|
||||||
|
$this->syncUserToManticore($userTag->user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the UserTag "deleted" event.
|
||||||
|
* 标签删除时,触发用户索引更新
|
||||||
|
*
|
||||||
|
* @param \App\Models\UserTag $userTag
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleted(UserTag $userTag)
|
||||||
|
{
|
||||||
|
$this->syncUserToManticore($userTag->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发用户同步到 Manticore
|
||||||
|
*
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function syncUserToManticore(int $userid)
|
||||||
|
{
|
||||||
|
if ($userid <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::find($userid);
|
||||||
|
if (!$user || $user->bot || $user->disable_at) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::taskDeliver(new ManticoreSyncTask('user_sync', $user->toArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/Observers/UserTagRecognitionObserver.php
Normal file
60
app/Observers/UserTagRecognitionObserver.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserTag;
|
||||||
|
use App\Models\UserTagRecognition;
|
||||||
|
use App\Tasks\ManticoreSyncTask;
|
||||||
|
|
||||||
|
class UserTagRecognitionObserver extends AbstractObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the UserTagRecognition "created" event.
|
||||||
|
* 认可创建时,标签排序可能变化,触发用户索引更新
|
||||||
|
*
|
||||||
|
* @param \App\Models\UserTagRecognition $recognition
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function created(UserTagRecognition $recognition)
|
||||||
|
{
|
||||||
|
$this->syncUserByTagId($recognition->tag_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the UserTagRecognition "deleted" event.
|
||||||
|
* 认可删除时,标签排序可能变化,触发用户索引更新
|
||||||
|
*
|
||||||
|
* @param \App\Models\UserTagRecognition $recognition
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleted(UserTagRecognition $recognition)
|
||||||
|
{
|
||||||
|
$this->syncUserByTagId($recognition->tag_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID触发用户同步
|
||||||
|
*
|
||||||
|
* @param int $tagId 标签ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function syncUserByTagId(int $tagId)
|
||||||
|
{
|
||||||
|
if ($tagId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = UserTag::find($tagId);
|
||||||
|
if (!$tag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::find($tag->user_id);
|
||||||
|
if (!$user || $user->bot || $user->disable_at) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::taskDeliver(new ManticoreSyncTask('user_sync', $user->toArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,8 @@ use App\Models\ProjectTaskUser;
|
|||||||
use App\Models\ProjectTaskVisibilityUser;
|
use App\Models\ProjectTaskVisibilityUser;
|
||||||
use App\Models\ProjectUser;
|
use App\Models\ProjectUser;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserTag;
|
||||||
|
use App\Models\UserTagRecognition;
|
||||||
use App\Models\WebSocketDialog;
|
use App\Models\WebSocketDialog;
|
||||||
use App\Models\WebSocketDialogMsg;
|
use App\Models\WebSocketDialogMsg;
|
||||||
use App\Models\WebSocketDialogUser;
|
use App\Models\WebSocketDialogUser;
|
||||||
@ -21,6 +23,8 @@ use App\Observers\ProjectTaskUserObserver;
|
|||||||
use App\Observers\ProjectTaskVisibilityUserObserver;
|
use App\Observers\ProjectTaskVisibilityUserObserver;
|
||||||
use App\Observers\ProjectUserObserver;
|
use App\Observers\ProjectUserObserver;
|
||||||
use App\Observers\UserObserver;
|
use App\Observers\UserObserver;
|
||||||
|
use App\Observers\UserTagObserver;
|
||||||
|
use App\Observers\UserTagRecognitionObserver;
|
||||||
use App\Observers\WebSocketDialogMsgObserver;
|
use App\Observers\WebSocketDialogMsgObserver;
|
||||||
use App\Observers\WebSocketDialogObserver;
|
use App\Observers\WebSocketDialogObserver;
|
||||||
use App\Observers\WebSocketDialogUserObserver;
|
use App\Observers\WebSocketDialogUserObserver;
|
||||||
@ -56,6 +60,8 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
ProjectTaskVisibilityUser::observe(ProjectTaskVisibilityUserObserver::class);
|
ProjectTaskVisibilityUser::observe(ProjectTaskVisibilityUserObserver::class);
|
||||||
ProjectUser::observe(ProjectUserObserver::class);
|
ProjectUser::observe(ProjectUserObserver::class);
|
||||||
User::observe(UserObserver::class);
|
User::observe(UserObserver::class);
|
||||||
|
UserTag::observe(UserTagObserver::class);
|
||||||
|
UserTagRecognition::observe(UserTagRecognitionObserver::class);
|
||||||
WebSocketDialog::observe(WebSocketDialogObserver::class);
|
WebSocketDialog::observe(WebSocketDialogObserver::class);
|
||||||
WebSocketDialogMsg::observe(WebSocketDialogMsgObserver::class);
|
WebSocketDialogMsg::observe(WebSocketDialogMsgObserver::class);
|
||||||
WebSocketDialogUser::observe(WebSocketDialogUserObserver::class);
|
WebSocketDialogUser::observe(WebSocketDialogUserObserver::class);
|
||||||
|
|||||||
@ -505,11 +505,24 @@ export default {
|
|||||||
},
|
},
|
||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
const items = data.map(item => {
|
const items = data.map(item => {
|
||||||
|
// 构建标签:显示匹配搜索关键词的标签
|
||||||
|
const tags = [];
|
||||||
|
if (item.search_tags && item.search_tags.length > 0) {
|
||||||
|
const keyLower = key.toLowerCase();
|
||||||
|
item.search_tags.forEach(tagName => {
|
||||||
|
if (tagName.toLowerCase().includes(keyLower)) {
|
||||||
|
tags.push({
|
||||||
|
name: tagName,
|
||||||
|
style: 'background-color:#2d8cf0',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
type: 'contact',
|
type: 'contact',
|
||||||
icons: ['user', item.userid],
|
icons: ['user', item.userid],
|
||||||
tags: [],
|
tags,
|
||||||
|
|
||||||
id: item.userid,
|
id: item.userid,
|
||||||
title: item.nickname,
|
title: item.nickname,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user