184 lines
7.0 KiB
PHP
184 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use app\adminapi\logic\firstvisit\FirstVisitConversionLogic;
|
|
use app\adminapi\logic\stats\ConversionLogic;
|
|
use think\facade\Db;
|
|
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
|
|
$app = new think\App();
|
|
$app->initialize();
|
|
|
|
$admin = Db::name('admin')->where('root', 1)->whereNull('delete_time')->find();
|
|
if (!$admin) {
|
|
echo "FAN_DETAIL_DB_SMOKE_SKIP no_root\n";
|
|
exit(0);
|
|
}
|
|
|
|
$params = [
|
|
'time_type' => 'custom',
|
|
'start_date' => '2026-08-01',
|
|
'end_date' => '2026-08-25',
|
|
];
|
|
$overviewStartedAt = microtime(true);
|
|
$overview = FirstVisitConversionLogic::overview($params, (int) $admin['id'], $admin);
|
|
$overviewElapsedMs = (microtime(true) - $overviewStartedAt) * 1000;
|
|
$candidates = [];
|
|
$collect = static function (array $rows, array $path = []) use (&$collect, &$candidates): void {
|
|
foreach ($rows as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$rowPath = array_merge($path, [(string) ($row['name'] ?? $row['id'] ?? '')]);
|
|
if ((int) ($row['add_fans_count'] ?? 0) > 0) {
|
|
$row['_smoke_path'] = implode('/', $rowPath);
|
|
$candidates[] = $row;
|
|
}
|
|
$collect(is_array($row['children'] ?? null) ? $row['children'] : [], $rowPath);
|
|
}
|
|
};
|
|
$collect(is_array($overview['rows'] ?? null) ? $overview['rows'] : []);
|
|
if ($candidates === []) {
|
|
echo "FAN_DETAIL_DB_SMOKE_SKIP no_fans\n";
|
|
exit(0);
|
|
}
|
|
|
|
usort($candidates, static function (array $left, array $right): int {
|
|
$leftCenter = str_contains((string) ($left['_smoke_path'] ?? ''), '郑州二中心') ? 0 : 1;
|
|
$rightCenter = str_contains((string) ($right['_smoke_path'] ?? ''), '郑州二中心') ? 0 : 1;
|
|
$leftMember = in_array((string) ($left['type'] ?? ''), ['member', 'unbound'], true) ? 0 : 1;
|
|
$rightMember = in_array((string) ($right['type'] ?? ''), ['member', 'unbound'], true) ? 0 : 1;
|
|
|
|
return ($leftCenter <=> $rightCenter)
|
|
?: ($leftMember <=> $rightMember)
|
|
?: ((int) ($left['add_fans_count'] ?? 0) <=> (int) ($right['add_fans_count'] ?? 0));
|
|
});
|
|
|
|
$row = $candidates[0];
|
|
$entityType = in_array((string) ($row['type'] ?? ''), ['member', 'unbound'], true)
|
|
? 'member'
|
|
: 'dept';
|
|
$detailStartedAt = microtime(true);
|
|
$detail = FirstVisitConversionLogic::fansDetail($params + [
|
|
'entity_type' => $entityType,
|
|
'entity_id' => (string) ($row['id'] ?? ''),
|
|
'admin_id' => (int) ($row['admin_id'] ?? 0),
|
|
'page_no' => 1,
|
|
'page_size' => 100,
|
|
], (int) $admin['id'], $admin);
|
|
$detailElapsedMs = (microtime(true) - $detailStartedAt) * 1000;
|
|
|
|
if ((int) ($detail['count'] ?? -1) !== (int) ($row['add_fans_count'] ?? 0)) {
|
|
throw new RuntimeException('Fan detail count does not match the clicked add_fans_count row');
|
|
}
|
|
if ((int) ($detail['entity']['deleted_fans_count'] ?? -1) !== (int) ($row['deleted_fans_count'] ?? 0)) {
|
|
throw new RuntimeException('Fan detail entity metadata lost deleted_fans_count');
|
|
}
|
|
|
|
foreach ($detail['lists'] ?? [] as $fan) {
|
|
foreach (['external_userid', 'customer_name', 'wecom_userid', 'wecom_staff_name', 'add_time', 'is_deleted', 'delete_time'] as $field) {
|
|
if (!array_key_exists($field, $fan)) {
|
|
throw new RuntimeException("Fan detail row is missing {$field}");
|
|
}
|
|
}
|
|
}
|
|
|
|
$forgedDetail = FirstVisitConversionLogic::fansDetail($params + [
|
|
'entity_type' => 'member',
|
|
'entity_id' => 'M999999_-2',
|
|
'admin_id' => 999999,
|
|
'page_no' => 1,
|
|
'page_size' => 1,
|
|
], (int) $admin['id'], $admin);
|
|
if ((int) ($forgedDetail['count'] ?? -1) !== 0 || ($forgedDetail['entity'] ?? null) !== null) {
|
|
throw new RuntimeException('Forged fan-detail entity did not fail closed');
|
|
}
|
|
|
|
$centerDetailElapsedMs = 0.0;
|
|
$centerDetailCount = -1;
|
|
$centerRows = array_values(array_filter($candidates, static fn (array $candidate): bool =>
|
|
!in_array((string) ($candidate['type'] ?? ''), ['member', 'unbound'], true)
|
|
&& str_contains((string) ($candidate['name'] ?? ''), '郑州二中心')
|
|
));
|
|
if ($centerRows !== []) {
|
|
$centerRow = $centerRows[0];
|
|
$centerStartedAt = microtime(true);
|
|
$centerDetail = FirstVisitConversionLogic::fansDetail($params + [
|
|
'entity_type' => 'dept',
|
|
'entity_id' => (string) ($centerRow['id'] ?? ''),
|
|
'page_no' => 1,
|
|
'page_size' => 100,
|
|
], (int) $admin['id'], $admin);
|
|
$centerDetailElapsedMs = (microtime(true) - $centerStartedAt) * 1000;
|
|
$centerDetailCount = (int) ($centerDetail['count'] ?? -1);
|
|
if ($centerDetailCount !== (int) ($centerRow['add_fans_count'] ?? 0)
|
|
|| (int) ($centerDetail['entity']['deleted_fans_count'] ?? -1)
|
|
!== (int) ($centerRow['deleted_fans_count'] ?? 0)
|
|
) {
|
|
throw new RuntimeException('郑州二中心 detail count does not match overview');
|
|
}
|
|
}
|
|
|
|
$parentCandidates = array_values(array_filter($candidates, static function (array $candidate): bool {
|
|
foreach (is_array($candidate['children'] ?? null) ? $candidate['children'] : [] as $child) {
|
|
if (is_array($child) && !in_array((string) ($child['type'] ?? ''), ['member', 'unbound'], true)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}));
|
|
$parentChecked = false;
|
|
if ($parentCandidates !== []) {
|
|
usort(
|
|
$parentCandidates,
|
|
static fn (array $left, array $right): int => ((int) ($left['add_fans_count'] ?? 0))
|
|
<=> ((int) ($right['add_fans_count'] ?? 0))
|
|
);
|
|
$parentRow = $parentCandidates[0];
|
|
$parentDeptIds = [];
|
|
$collectDeptIds = static function (array $node) use (&$collectDeptIds, &$parentDeptIds): void {
|
|
if (in_array((string) ($node['type'] ?? ''), ['member', 'unbound'], true)) {
|
|
return;
|
|
}
|
|
$parentDeptIds[(int) ($node['id'] ?? 0)] = true;
|
|
foreach (is_array($node['children'] ?? null) ? $node['children'] : [] as $child) {
|
|
if (is_array($child)) {
|
|
$collectDeptIds($child);
|
|
}
|
|
}
|
|
};
|
|
$collectDeptIds($parentRow);
|
|
$parentDetail = ConversionLogic::fanDetails([
|
|
'dimension' => 'dept',
|
|
'time_type' => 'custom',
|
|
'start_date' => (string) ($overview['meta']['start_date'] ?? ''),
|
|
'end_date' => (string) ($overview['meta']['end_date'] ?? ''),
|
|
'page_no' => 1,
|
|
'page_size' => 1,
|
|
], [
|
|
'type' => 'dept',
|
|
'dept_ids' => array_keys($parentDeptIds),
|
|
], (int) $admin['id'], $admin);
|
|
if ((int) ($parentDetail['count'] ?? -1) !== (int) ($parentRow['add_fans_count'] ?? 0)) {
|
|
throw new RuntimeException('Parent department fan detail does not include the same descendant total as overview');
|
|
}
|
|
$parentChecked = true;
|
|
}
|
|
|
|
echo sprintf(
|
|
"FAN_DETAIL_DB_SMOKE_OK range=2026-08-01..2026-08-25 type=%s entity=%s path=%s count=%d deleted=%d parent=%d overview_ms=%.1f detail_ms=%.1f center_count=%d center_detail_ms=%.1f forged=0\n",
|
|
$entityType,
|
|
(string) ($row['name'] ?? $row['id'] ?? ''),
|
|
(string) ($row['_smoke_path'] ?? ''),
|
|
(int) $detail['count'],
|
|
(int) ($detail['entity']['deleted_fans_count'] ?? 0),
|
|
$parentChecked ? 1 : 0,
|
|
$overviewElapsedMs,
|
|
$detailElapsedMs,
|
|
$centerDetailCount,
|
|
$centerDetailElapsedMs
|
|
);
|