Files
CKB-DataSourceCenter/app/controller/TagController.php
2026-01-05 10:11:01 +08:00

522 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\controller;
use app\repository\TagDefinitionRepository;
use app\repository\UserProfileRepository;
use app\repository\UserTagRepository;
use app\repository\TagHistoryRepository;
use app\service\TagService;
use app\service\TagRuleEngine\SimpleRuleEngine;
use app\utils\ApiResponseHelper;
use app\utils\DataMaskingHelper;
use app\utils\LoggerHelper;
use support\Request;
use support\Response;
class TagController
{
/**
* 查询用户的标签列表
*
* GET /api/users/{user_id}/tags
*/
public function listByUser(Request $request): Response
{
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)/tags#', $path, $matches)) {
$userId = $matches[1];
} else {
// 如果路径解析失败,尝试从查询参数获取
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('GET', $path, ['user_id' => $userId]);
$userTagRepo = new UserTagRepository();
$tagDefRepo = new TagDefinitionRepository();
// 查询用户的所有标签
$userTags = $userTagRepo->newQuery()
->where('user_id', $userId)
->get();
// 关联标签定义信息
$result = [];
foreach ($userTags as $userTag) {
$tagDef = $tagDefRepo->newQuery()
->where('tag_id', $userTag->tag_id)
->first();
$result[] = [
'tag_id' => $userTag->tag_id,
'tag_code' => $tagDef ? $tagDef->tag_code : null,
'tag_name' => $tagDef ? $tagDef->tag_name : null,
'category' => $tagDef ? $tagDef->category : null,
'tag_value' => $userTag->tag_value,
'tag_value_type' => $userTag->tag_value_type,
'confidence' => $userTag->confidence,
'effective_time' => $userTag->effective_time,
'expire_time' => $userTag->expire_time,
'update_time' => $userTag->update_time,
];
}
LoggerHelper::logBusiness('get_user_tags', [
'user_id' => $userId,
'tag_count' => count($result),
]);
return ApiResponseHelper::success([
'user_id' => $userId,
'tags' => $result,
'count' => count($result),
]);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 更新/计算用户标签
*
* PUT /api/users/{user_id}/tags
*/
public function calculate(Request $request): Response
{
$startTime = microtime(true);
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)/tags#', $path, $matches)) {
$userId = $matches[1];
} else {
// 如果路径解析失败,尝试从查询参数获取
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('PUT', $path, ['user_id' => $userId]);
$tagService = new TagService(
new TagDefinitionRepository(),
new UserProfileRepository(),
new UserTagRepository(),
new TagHistoryRepository(),
new SimpleRuleEngine()
);
$tags = $tagService->calculateTags($userId);
$duration = microtime(true) - $startTime;
LoggerHelper::logBusiness('calculate_tags', [
'user_id' => $userId,
'updated_count' => count($tags),
], 'info');
LoggerHelper::logPerformance('tag_calculation', $duration, [
'user_id' => $userId,
'tag_count' => count($tags),
]);
return ApiResponseHelper::success([
'user_id' => $userId,
'updated_tags' => $tags,
'count' => count($tags),
]);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 删除用户的指定标签
*
* DELETE /api/users/{user_id}/tags/{tag_id}
*/
public function destroy(Request $request): Response
{
try {
// 从请求路径中解析 user_id 和 tag_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)/tags/([^/]+)#', $path, $matches)) {
$userId = $matches[1];
$tagId = $matches[2];
} else {
throw new \InvalidArgumentException('缺少 user_id 或 tag_id 参数');
}
LoggerHelper::logRequest('DELETE', $path, ['user_id' => $userId, 'tag_id' => $tagId]);
$tagService = new TagService(
new TagDefinitionRepository(),
new UserProfileRepository(),
new UserTagRepository(),
new TagHistoryRepository(),
new SimpleRuleEngine()
);
$deleted = $tagService->deleteUserTag($userId, $tagId);
if (!$deleted) {
return ApiResponseHelper::error('标签不存在', 404, 404);
}
LoggerHelper::logBusiness('tag_deleted', [
'user_id' => $userId,
'tag_id' => $tagId,
]);
return ApiResponseHelper::success(null, '标签删除成功');
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 根据标签筛选用户
*
* POST /api/tags/filter
*/
public function filter(Request $request): Response
{
try {
LoggerHelper::logRequest('POST', '/api/tags/filter');
$rawBody = $request->rawBody();
if (empty($rawBody)) {
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
}
$body = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ApiResponseHelper::error('JSON 格式错误: ' . json_last_error_msg(), 400);
}
// 验证必填字段
if (empty($body['tag_conditions']) || !is_array($body['tag_conditions'])) {
throw new \InvalidArgumentException('缺少必填字段tag_conditions必须为数组');
}
// 验证条件格式
foreach ($body['tag_conditions'] as $condition) {
if (!isset($condition['tag_code']) || !isset($condition['operator']) || !isset($condition['value'])) {
throw new \InvalidArgumentException('每个条件必须包含 tag_code、operator 和 value 字段');
}
}
$tagService = new TagService(
new TagDefinitionRepository(),
new UserProfileRepository(),
new UserTagRepository(),
new TagHistoryRepository(),
new SimpleRuleEngine()
);
$conditions = $body['tag_conditions'];
$logic = $body['logic'] ?? 'AND';
$page = (int)($body['page'] ?? 1);
$pageSize = (int)($body['page_size'] ?? 20);
$includeUserInfo = (bool)($body['include_user_info'] ?? false);
if ($page < 1) {
$page = 1;
}
if ($pageSize < 1 || $pageSize > 100) {
$pageSize = 20;
}
$result = $tagService->filterUsersByTags(
$conditions,
$logic,
$page,
$pageSize,
$includeUserInfo
);
// 对返回的用户信息进行脱敏处理
if ($includeUserInfo && isset($result['users']) && is_array($result['users'])) {
foreach ($result['users'] as &$user) {
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
}
unset($user);
}
LoggerHelper::logBusiness('filter_users_by_tags', [
'conditions_count' => count($conditions),
'logic' => $logic,
'result_count' => $result['total'] ?? 0,
]);
return ApiResponseHelper::success($result);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 批量初始化标签定义
*
* POST /api/tag-definitions/batch
*/
public function init(Request $request): Response
{
try {
LoggerHelper::logRequest('POST', '/api/tag-definitions/batch');
$initService = new \app\service\TagInitService(
new TagDefinitionRepository()
);
$initService->initBasicTags();
LoggerHelper::logBusiness('init_tags', []);
return ApiResponseHelper::success([
'message' => '标签初始化完成',
]);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 获取标签统计信息
*
* GET /api/tags/statistics
*/
public function statistics(Request $request): Response
{
try {
LoggerHelper::logRequest('GET', '/api/tags/statistics');
$tagId = $request->get('tag_id');
$startDate = $request->get('start_date');
$endDate = $request->get('end_date');
$userTagRepo = new UserTagRepository();
$tagDefRepo = new TagDefinitionRepository();
$result = [
'value_distribution' => [],
'trend_data' => [],
'coverage_stats' => [],
];
// 如果指定了 tag_id统计该标签的值分布
if ($tagId) {
$tagDef = $tagDefRepo->newQuery()->where('tag_id', $tagId)->first();
if ($tagDef) {
// 统计标签值分布
$userTags = $userTagRepo->newQuery()
->where('tag_id', $tagId)
->get(['tag_value']);
$valueCounts = [];
foreach ($userTags as $userTag) {
$value = (string)$userTag->tag_value;
if (!isset($valueCounts[$value])) {
$valueCounts[$value] = 0;
}
$valueCounts[$value]++;
}
// 按数量排序
arsort($valueCounts);
$valueCounts = array_slice($valueCounts, 0, 20, true);
foreach ($valueCounts as $value => $count) {
$result['value_distribution'][] = [
'value' => $value,
'count' => $count
];
}
// 统计标签覆盖度
$totalUsers = $userTagRepo->newQuery()
->distinct('user_id')
->count();
$taggedUsers = $userTagRepo->newQuery()
->where('tag_id', $tagId)
->distinct('user_id')
->count();
$result['coverage_stats'][] = [
'tag_id' => $tagId,
'tag_name' => $tagDef->tag_name ?? '',
'total_users' => $totalUsers,
'tagged_users' => $taggedUsers,
'coverage_rate' => $totalUsers > 0 ? round($taggedUsers / $totalUsers * 100, 2) : 0
];
}
} else {
// 统计所有标签的覆盖度
$tagDefs = $tagDefRepo->newQuery()->where('status', 1)->get();
$totalUsers = $userTagRepo->newQuery()->distinct('user_id')->count();
foreach ($tagDefs as $tagDef) {
$taggedUsers = $userTagRepo->newQuery()
->where('tag_id', $tagDef->tag_id)
->distinct('user_id')
->count();
$result['coverage_stats'][] = [
'tag_id' => $tagDef->tag_id,
'tag_name' => $tagDef->tag_name ?? '',
'total_users' => $totalUsers,
'tagged_users' => $taggedUsers,
'coverage_rate' => $totalUsers > 0 ? round($taggedUsers / $totalUsers * 100, 2) : 0
];
}
}
// 趋势数据(如果有时间范围)
if ($startDate && $endDate) {
$historyRepo = new TagHistoryRepository();
$start = new \DateTime($startDate);
$end = new \DateTime($endDate);
// 按日期统计标签变更次数
$trendData = [];
$current = clone $start;
while ($current <= $end) {
$dateStr = $current->format('Y-m-d');
$nextDay = clone $current;
$nextDay->modify('+1 day');
$count = $historyRepo->newQuery()
->where('change_time', '>=', $current)
->where('change_time', '<', $nextDay)
->count();
$trendData[] = [
'date' => $dateStr,
'count' => $count
];
$current->modify('+1 day');
}
$result['trend_data'] = $trendData;
}
LoggerHelper::logBusiness('get_tag_statistics', [
'tag_id' => $tagId,
'start_date' => $startDate,
'end_date' => $endDate,
]);
return ApiResponseHelper::success($result);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 获取标签历史记录
*
* GET /api/tags/history
*/
public function history(Request $request): Response
{
try {
LoggerHelper::logRequest('GET', '/api/tags/history');
$userId = $request->get('user_id');
$tagId = $request->get('tag_id');
$startDate = $request->get('start_date');
$endDate = $request->get('end_date');
$page = (int)($request->get('page') ?? 1);
$pageSize = (int)($request->get('page_size') ?? 20);
if ($page < 1) {
$page = 1;
}
if ($pageSize < 1 || $pageSize > 100) {
$pageSize = 20;
}
$historyRepo = new TagHistoryRepository();
$tagDefRepo = new TagDefinitionRepository();
$query = $historyRepo->newQuery();
if ($userId) {
$query->where('user_id', $userId);
}
if ($tagId) {
$query->where('tag_id', $tagId);
}
if ($startDate) {
$query->where('change_time', '>=', new \DateTime($startDate));
}
if ($endDate) {
$endDateTime = new \DateTime($endDate);
$endDateTime->modify('+1 day');
$query->where('change_time', '<', $endDateTime);
}
$total = $query->count();
$histories = $query->orderBy('change_time', 'desc')
->skip(($page - 1) * $pageSize)
->take($pageSize)
->get();
$items = [];
foreach ($histories as $history) {
$tagDef = $tagDefRepo->newQuery()->where('tag_id', $history->tag_id)->first();
$items[] = [
'user_id' => $history->user_id,
'tag_id' => $history->tag_id,
'tag_name' => $tagDef ? $tagDef->tag_name : null,
'old_value' => $history->old_value,
'new_value' => $history->new_value,
'change_reason' => $history->change_reason,
'change_time' => $history->change_time ? $history->change_time->format('Y-m-d H:i:s') : null,
'operator' => $history->operator,
];
}
LoggerHelper::logBusiness('get_tag_history', [
'user_id' => $userId,
'tag_id' => $tagId,
'total' => $total,
]);
return ApiResponseHelper::success([
'items' => $items,
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
]);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
}