This commit is contained in:
gr
2026-09-24 11:48:20 +08:00
parent 68fc86a167
commit b7e91f5fe3
5 changed files with 240 additions and 67 deletions
+64 -9
View File
@@ -24,7 +24,7 @@ class Tools
self::tool('zyt_describe', '查看某个数据资源的说明:可用查询参数及含义、类型(列表/详情/统计)、口径说明。', [
'resource' => ['type' => 'string', 'description' => '资源标识,来自 zyt_catalog,如 doctor.appointment/lists'],
], ['resource']),
self::tool('zyt_query', '查询列表或统计类资源,结果与该账号在甄养堂后台看到的一致(按其权限和数据范围)。列表默认每页 20 条、最多 50 条,返回 total 和 has_more。', [
self::tool('zyt_query', '查询列表或统计类资源,结果与该账号在甄养堂后台看到的一致(按其权限和数据范围)。翻页一律用外层 page/page_size(列表默认每页 20 条、最多 50 条;自带分页的统计明细最多 100 条),结果里有 total 和 has_more(统计明细在 result.paging)。', [
'resource' => ['type' => 'string', 'description' => '资源标识,如 tcm.diagnosis/lists'],
'params' => ['type' => 'object', 'description' => '查询参数,名称见 zyt_describe;日期用 YYYY-MM-DD', 'additionalProperties' => true],
'page' => ['type' => 'integer', 'minimum' => 1, 'description' => '页码,从 1 开始'],
@@ -174,7 +174,41 @@ class Tools
if (in_array($resource['kind'], ['list', 'table'], true)) {
return self::runList($identity, $resource, $params, (int) ($args['page'] ?? 1), (int) ($args['page_size'] ?? McpConfig::defaultPageSize()), (array) ($args['fields'] ?? []), $audit);
}
return self::runReport($identity, $resource, $params, $audit);
return self::runReport($identity, $resource, self::sinkPaging($resource, $params, $args), $audit);
}
/**
* 自带分页的统计明细(参数叫 page/page_no 和 page_size,如进线明细、被指派明细):外层的 page/page_size 放进参数。
* 以前只看 params,模型照工具说明用外层 page 翻页时每次拿到的都是第一页,看起来像同一批记录反复出现。
*/
private static function sinkPaging(array $resource, array $params, array $args): array
{
$allowed = array_flip(Catalog::allowedParams($resource));
$pageKey = isset($allowed['page']) ? 'page' : (isset($allowed['page_no']) ? 'page_no' : null);
if ($pageKey !== null && !isset($params[$pageKey])) {
$params[$pageKey] = max(1, (int) ($args['page'] ?? 1));
}
if (isset($allowed['page_size']) && !isset($params['page_size'])) {
$params['page_size'] = max(1, min(100, (int) ($args['page_size'] ?? McpConfig::defaultPageSize())));
}
return $params;
}
/** 同一页里 id 相同的行只留一条(后台列表的联表偶尔会把一条记录拆成多行);有行没有 id 时原样返回 */
private static function dropDuplicateIds(array $rows): array
{
$seen = [];
$out = [];
foreach ($rows as $row) {
if (!is_array($row) || !isset($row['id']) || !is_scalar($row['id'])) {
return [$rows, 0];
}
if (!isset($seen[(string) $row['id']])) {
$seen[(string) $row['id']] = true;
$out[] = $row;
}
}
return [$out, count($rows) - count($out)];
}
private static function get(Identity $identity, array $args, array &$audit): array
@@ -296,7 +330,7 @@ class Tools
if ($envelope['code'] !== 1) {
throw new McpException(self::failText($resource, $envelope), 'denied');
}
$rows = array_values((array) ($envelope['data']['lists'] ?? []));
[$rows, $duplicates] = self::dropDuplicateIds(array_values((array) ($envelope['data']['lists'] ?? [])));
$total = (int) ($envelope['data']['count'] ?? count($rows));
if (count($rows) > $size) {
// 个别列表不分页、总是返回全部行:在这里按页切片,避免超出篇幅和每日额度
@@ -314,6 +348,9 @@ class Tools
$audit['record_ids'] = AuditLogger::recordIds($rows);
$data = ['resource' => $resource['key'], 'name' => $resource['name'], 'total' => $total, 'page' => $page, 'page_size' => $size,
'has_more' => $page * $size < $total, 'rows' => $rows, 'masked' => $policy->maskedFields()];
if ($duplicates > 0) {
$data['duplicates_removed'] = $duplicates;
}
if (!empty($envelope['data']['extend'])) {
$data['extend'] = $policy->apply($envelope['data']['extend']);
}
@@ -321,8 +358,8 @@ class Tools
$data['note'] = $resource['note'];
}
$data = self::fit($data);
$summary = sprintf('「%s」共 %d 条,本页第 %d 页 %d 条%s%s。', $resource['name'], $total, $page, count($data['rows']),
$data['has_more'] ? ',还有更多(page=' . ($page + 1) . ')' : '', self::maskNote($policy));
$summary = sprintf('「%s」共 %d 条,本页第 %d 页 %d 条%s%s%s。', $resource['name'], $total, $page, count($data['rows']),
$data['has_more'] ? ',还有更多(page=' . ($page + 1) . ')' : '', $duplicates > 0 ? ';后台返回的重复记录 ' . $duplicates . ' 条已去掉' : '', self::maskNote($policy));
return self::ok($summary, $data);
}
@@ -335,15 +372,33 @@ class Tools
}
$policy = FieldPolicy::forIdentity($identity, 5000);
$result = $policy->apply($envelope['data']);
$rows = is_array($result) && isset($result['lists']) && is_array($result['lists']) ? count($result['lists']) : 1;
$extra = '';
$hasLists = is_array($result) && isset($result['lists']) && is_array($result['lists']);
if ($hasLists) {
[$result['lists'], $duplicates] = self::dropDuplicateIds(array_values($result['lists']));
if ($duplicates > 0) {
$result['duplicates_removed'] = $duplicates;
$extra .= ';后台返回的重复记录 ' . $duplicates . ' 条已去掉';
}
// 自带分页的明细:告诉模型总数和下一页怎么取
$total = $result['count'] ?? $result['total'] ?? null;
$pageKey = isset($params['page']) ? 'page' : (isset($params['page_no']) ? 'page_no' : null);
if (is_numeric($total) && $pageKey !== null && isset($params['page_size'])) {
$page = (int) $params[$pageKey];
$hasMore = $page * (int) $params['page_size'] < (int) $total;
$result['paging'] = ['total' => (int) $total, 'page' => $page, 'page_size' => (int) $params['page_size'], 'has_more' => $hasMore];
$extra .= sprintf(';共 %d 条,本页第 %d 页 %d 条%s', (int) $total, $page, count($result['lists']), $hasMore ? ',还有更多(page=' . ($page + 1) . ')' : '');
}
}
$rows = $hasLists ? count($result['lists']) : 1;
RateLimiter::addRows($identity->adminId, $rows);
$audit['result_rows'] = $rows;
if (is_array($result) && isset($result['lists']) && is_array($result['lists'])) {
if ($hasLists) {
$audit['record_ids'] = AuditLogger::recordIds($result['lists']);
}
$data = self::fit(['resource' => $resource['key'], 'name' => $resource['name'], 'params' => $params, 'result' => $result,
'masked' => $policy->maskedFields(), 'note' => (string) ($resource['note'] ?? '')]);
return self::ok('「' . $resource['name'] . '」统计结果' . self::maskNote($policy) . '。', $data);
return self::ok('「' . $resource['name'] . '」统计结果' . $extra . self::maskNote($policy) . '。', $data);
}
/**
@@ -424,7 +479,7 @@ class Tools
return array_merge($clean, (array) ($resource['force'] ?? []));
}
private static function listParams(array $resource, array $params, int $page, int $size): array
public static function listParams(array $resource, array $params, int $page, int $size): array
{
return array_merge($params, ['page_no' => $page, 'page_size' => $size, 'page_type' => 1], (array) ($resource['force'] ?? []));
}