Files
MBTI_wang/api/app/controller/superadmin/AppUser.php
卡若 68c73f6ca9 fix: 仪表盘与用户管理、分析/测试接口、双端结果页与个人中心展示调整
- admin:Dashboard/Users 与超管 Users 联动后端统计
- api:Dashboard、Analyze、Test、超管 AppUser 逻辑更新
- 微信/抖音小程序:result、profile 页面与样式
- 新增 admin 公共组件与需求文档截图

Made-with: Cursor
2026-03-27 18:26:03 +08:00

743 lines
29 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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\superadmin;
use app\BaseController;
use think\facade\Db;
use think\facade\Request;
/**
* 超管 - 测试用户(小程序用户)管理
* 数据来源wechat_users测试记录表物理表名一般为 mbti_test_results逻辑使用 Db::name('test_results')
*/
class AppUser extends BaseController
{
/**
* 名称包含「存客宝」的企业(取 id 最小的一条),用于合并个人侧无归属测试数据
*/
private function resolveCunkbaoEnterpriseId(): ?int
{
try {
$row = Db::name('enterprises')->where('name', 'like', '%存客宝%')->order('id', 'asc')->find();
return $row ? (int) $row['id'] : null;
} catch (\Throwable $e) {
return null;
}
}
/**
* 概览用户统计、卡片、MBTI 分布
* GET /api/v1/superadmin/app-users/overview
*/
public function overview()
{
$user = $this->request->user ?? null;
if (!$user || ($user['role'] ?? '') !== 'superadmin') {
return error('无权限访问', 403);
}
// 用户数按 openid 去重
try {
$totalUsers = (int) Db::name('wechat_users')->count('openid', true);
} catch (\Throwable $e) {
$totalUsers = (int) Db::name('wechat_users')->count();
}
$last30d = time() - 30 * 86400;
// 全部池:去重后的测试用户 & 近 30 天活跃用户(按 userId 去重)
// 这里使用逻辑表名 test_results底层会自动加前缀生成 mbti_test_results
$testedUserIds = Db::name('test_results')->distinct(true)->column('userId');
$testedUsers = count(array_filter($testedUserIds));
$activeUserIds = Db::name('test_results')
->where('createdAt', '>=', $last30d)
->distinct(true)
->column('userId');
$activeUsers = count(array_filter($activeUserIds));
$userCards = [
[
'type' => 'all',
'name' => '全部用户',
'total' => $totalUsers,
'active' => $activeUsers,
'tested' => $testedUsers
]
];
$individualTotal = 0;
$individualActive = 0;
try {
$individualIds = Db::name('test_results')
->where(function ($q) {
$q->whereNull('enterpriseId')->whereOr('enterpriseId', '')->whereOr('enterpriseId', 0);
})
->distinct(true)
->column('userId');
$individualTotal = count(array_filter($individualIds));
$individualActiveIds = Db::name('test_results')
->where('createdAt', '>=', $last30d)
->where(function ($q) {
$q->whereNull('enterpriseId')->whereOr('enterpriseId', '')->whereOr('enterpriseId', 0);
})
->distinct(true)
->column('userId');
$individualActive = count(array_filter($individualActiveIds));
} catch (\Throwable $e) {
$individualTotal = 0;
$individualActive = 0;
}
$cunkbaoId = $this->resolveCunkbaoEnterpriseId();
$enterprises = Db::name('enterprises')->field('id,name')->select()->toArray();
foreach ($enterprises as $e) {
$eid = $e['id'];
try {
$ids = Db::name('test_results')
->where('enterpriseId', $eid)
->distinct(true)
->column('userId');
$total = count(array_filter($ids));
$activeIds = Db::name('test_results')
->where('enterpriseId', $eid)
->where('createdAt', '>=', $last30d)
->distinct(true)
->column('userId');
$active = count(array_filter($activeIds));
} catch (\Throwable $ex) {
$total = 0;
$active = 0;
}
if ($cunkbaoId !== null && (int) $eid === $cunkbaoId) {
$total += $individualTotal;
$active += $individualActive;
}
$userCards[] = [
'type' => 'enterprise',
'enterpriseId' => $eid,
'name' => $e['name'] ?? ('企业' . $eid),
'total' => $total,
'active' => $active,
'tested' => $total
];
}
if ($cunkbaoId === null && ($individualTotal > 0 || $individualActive > 0)) {
$userCards[] = [
'type' => 'individual',
'name' => '个人用户(无企业)',
'total' => $individualTotal,
'active' => $individualActive,
'tested' => $individualTotal
];
}
// MBTI 类型分布:按用户去重,每人只计其最新一次 MBTI 结果
$mbtiTypes = [];
try {
$rows = Db::name('test_results')
->where('testType', 'mbti')
->field('userId, resultData, createdAt')
->order('createdAt', 'desc')
->select()
->toArray();
$seenUserIds = [];
foreach ($rows as $r) {
$uid = (int) ($r['userId'] ?? 0);
if ($uid <= 0 || isset($seenUserIds[$uid])) {
continue;
}
$raw = $r['resultData'] ?? '';
$dec = is_string($raw) ? json_decode($raw, true) : (is_array($raw) ? $raw : null);
if (!is_array($dec)) {
$seenUserIds[$uid] = true;
continue;
}
$type = '';
if (isset($dec['mbtiType'])) {
$type = $dec['mbtiType'];
} elseif (isset($dec['mbti']['type'])) {
$type = $dec['mbti']['type'];
} elseif (isset($dec['type'])) {
$type = $dec['type'];
}
$type = strtoupper(trim((string) $type));
$seenUserIds[$uid] = true;
if ($type === '') {
continue;
}
$mbtiTypes[$type] = ($mbtiTypes[$type] ?? 0) + 1;
}
} catch (\Throwable $e) {
// ignore
}
$mbtiDistribution = [];
foreach ($mbtiTypes as $type => $count) {
$mbtiDistribution[] = ['type' => $type, 'count' => $count];
}
return success([
'totalUsers' => $totalUsers,
'testedUsers' => $testedUsers,
'activeUsers' => $activeUsers,
'userCards' => $userCards,
'mbtiDistribution' => $mbtiDistribution
]);
}
/**
* 测试用户列表分页、关键词、池筛选、MBTI 筛选
* GET /api/v1/superadmin/app-users?page=1&pageSize=20&keyword=&pool=all|individual|enterprise&enterpriseId=&mbti=
*/
public function index()
{
$user = $this->request->user ?? null;
if (!$user || ($user['role'] ?? '') !== 'superadmin') {
return error('无权限访问', 403);
}
$page = (int) Request::param('page', 1);
$pageSize = (int) Request::param('pageSize', 20);
$pageSize = min(max($pageSize, 1), 100);
$keyword = trim(Request::param('keyword', ''));
$pool = Request::param('pool', 'all');
$enterpriseId = Request::param('enterpriseId', '');
$mbti = trim(Request::param('mbti', ''));
$cunkbaoEnterpriseId = $this->resolveCunkbaoEnterpriseId();
if ($pool === 'individual' && $cunkbaoEnterpriseId) {
$pool = 'enterprise';
$enterpriseId = (string) $cunkbaoEnterpriseId;
}
$where = [];
if ($keyword !== '') {
$where[] = ['nickname|phone|city|province', 'like', '%' . $keyword . '%'];
}
$wechatIds = null;
if ($pool === 'individual' || ($pool === 'enterprise' && $enterpriseId !== '')) {
try {
if ($pool === 'individual') {
$trQuery = Db::name('test_results')->where(function ($q) {
$q->whereNull('enterpriseId')->whereOr('enterpriseId', '')->whereOr('enterpriseId', 0);
});
$wechatIds = $trQuery->distinct(true)->column('userId');
} else {
$eid = (int) $enterpriseId;
if ($cunkbaoEnterpriseId !== null && $eid === $cunkbaoEnterpriseId) {
$idsEnt = Db::name('test_results')->where('enterpriseId', $eid)->distinct(true)->column('userId');
$idsOrphan = Db::name('test_results')->where(function ($q) {
$q->whereNull('enterpriseId')->whereOr('enterpriseId', '')->whereOr('enterpriseId', 0);
})->distinct(true)->column('userId');
$wechatIds = array_values(array_unique(array_filter(array_merge($idsEnt, $idsOrphan))));
} else {
$wechatIds = Db::name('test_results')->where('enterpriseId', $eid)->distinct(true)->column('userId');
}
}
$wechatIds = array_values(array_unique(array_filter($wechatIds)));
} catch (\Throwable $e) {
$wechatIds = null;
}
}
if ($mbti !== '') {
$mbtiUserIds = Db::name('test_results')->where('testType', 'mbti')->distinct(true)->column('userId');
$mbtiUserIds = array_values(array_unique(array_filter($mbtiUserIds)));
if ($wechatIds !== null) {
$wechatIds = array_values(array_intersect($wechatIds, $mbtiUserIds));
} else {
$wechatIds = $mbtiUserIds;
}
}
// 按 openid 去重:每个 openid 只保留 id 最大的一条;失败则不去重
try {
$dedupIds = Db::name('wechat_users')->field('openid, MAX(id) as mid')->group('openid')->column('mid');
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
} catch (\Throwable $e) {
$dedupIds = Db::name('wechat_users')->column('id');
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
}
if (empty($dedupIds)) {
return paginate_response([], 0, $page, $pageSize);
}
$baseQuery = Db::name('wechat_users')->where('id', 'in', $dedupIds);
if ($where) {
$baseQuery->where($where);
}
if ($wechatIds !== null && !empty($wechatIds)) {
$baseQuery->where('id', 'in', array_intersect($dedupIds, $wechatIds));
} elseif ($wechatIds !== null && empty($wechatIds)) {
return paginate_response([], 0, $page, $pageSize);
}
$total = $baseQuery->count();
$list = (clone $baseQuery)
->field('id,openid,nickname,avatar,phone,gender,country,province,city,status,lastLoginAt,createdAt')
->order('createdAt', 'desc')
->page($page, $pageSize)
->select()
->toArray();
$ids = array_column($list, 'id');
$testCounts = [];
$lastTestAt = [];
$testTypes = [];
$userEnterprise = [];
$payStats = [];
if (!empty($ids)) {
$counts = Db::name('test_results')->where('userId', 'in', $ids)->group('userId')->column('COUNT(*) as cnt', 'userId');
$testCounts = $counts ?: [];
$lastRows = Db::name('test_results')
->where('userId', 'in', $ids)
->field('id, userId, testType, resultData, createdAt')
->order('createdAt', 'desc')
->select();
foreach ($lastRows as $row) {
$uid = $row['userId'];
if (!isset($lastTestAt[$uid])) {
$lastTestAt[$uid] = $row['createdAt'];
}
if (!isset($testTypes[$uid])) {
$testTypes[$uid] = [];
}
$testTypes[$uid][] = [
'testType' => $row['testType'],
'result' => is_string($row['resultData'] ?? '') ? ($row['resultData'] ?? '') : json_encode($row['resultData'] ?? '', JSON_UNESCAPED_UNICODE),
'createdAt' => $row['createdAt'],
];
}
try {
$trWithE = Db::name('test_results')
->where('userId', 'in', $ids)
->where('enterpriseId', '<>', null)
->where('enterpriseId', '<>', '')
->field('userId, enterpriseId')
->select();
$eids = array_unique(array_filter(array_column($trWithE, 'enterpriseId')));
$enterpriseNames = [];
if (!empty($eids)) {
$enterpriseNames = Db::name('enterprises')->where('id', 'in', $eids)->column('name', 'id');
}
foreach ($trWithE as $r) {
if (!isset($userEnterprise[$r['userId']])) {
$userEnterprise[$r['userId']] = $enterpriseNames[$r['enterpriseId']] ?? ('企业' . $r['enterpriseId']);
}
}
} catch (\Throwable $e) {
// test_results 可能无 enterpriseId 列
}
$cunkbaoName = null;
if ($cunkbaoEnterpriseId) {
$cunkbaoName = (string) (Db::name('enterprises')->where('id', $cunkbaoEnterpriseId)->value('name') ?: '存客宝');
}
foreach ($ids as $uid) {
if (!isset($userEnterprise[$uid])) {
$tc = (int) ($testCounts[$uid] ?? 0);
if ($cunkbaoName !== null && $tc > 0) {
$userEnterprise[$uid] = $cunkbaoName;
} else {
$userEnterprise[$uid] = '个人用户(无企业)';
}
}
}
// 从用户画像表汇总支付统计(付款次数与总金额)
try {
$profiles = Db::name('user_profile')
->where('userId', 'in', $ids)
->field('userId, SUM(paidOrders) AS paidOrders, SUM(totalPaidAmount) AS totalPaidAmount')
->group('userId')
->select()
->toArray();
foreach ($profiles as $p) {
$uid = (int) ($p['userId'] ?? 0);
if ($uid <= 0) {
continue;
}
$payStats[$uid] = [
'paidOrders' => (int) ($p['paidOrders'] ?? 0),
'totalPaidAmount' => (int) ($p['totalPaidAmount'] ?? 0),
];
}
} catch (\Throwable $e) {
$payStats = [];
}
}
foreach ($list as &$row) {
$id = $row['id'];
$testsForUser = $testTypes[$id] ?? [];
$row['username'] = $row['nickname'] ?? ('用户' . $id);
$row['testCount'] = (int) ($testCounts[$id] ?? 0);
$row['lastTestAt'] = $lastTestAt[$id] ?? null;
$row['tests'] = $testsForUser;
$row['mbtiType'] = $this->extractResultType($testsForUser, 'mbti');
$row['pdpType'] = $this->extractResultType($testsForUser, 'pdp');
$row['discType'] = $this->extractResultType($testsForUser, 'disc');
$row['faceType'] = $this->extractResultType($testsForUser, 'face');
$row['faceMbtiType'] = $this->extractFaceSubType($testsForUser, 'mbti');
$row['faceDiscType'] = $this->extractFaceSubType($testsForUser, 'disc');
$row['facePdpType'] = $this->extractFaceSubType($testsForUser, 'pdp');
$row['enterprise'] = $userEnterprise[$id] ?? '个人用户(无企业)';
$pay = $payStats[$id] ?? null;
$totalPaidFen = $pay ? (int) ($pay['totalPaidAmount'] ?? 0) : 0;
$row['paidOrders'] = $pay ? (int) ($pay['paidOrders'] ?? 0) : 0;
$row['totalPaidAmount'] = $totalPaidFen;
$row['totalPaidAmountYuan'] = $totalPaidFen > 0 ? round($totalPaidFen / 100, 2) : 0;
}
return paginate_response($list, $total, $page, $pageSize);
}
/**
* 测试用户详情
* GET /api/v1/superadmin/app-users/:id
*/
public function detail($id)
{
$user = $this->request->user ?? null;
if (!$user || ($user['role'] ?? '') !== 'superadmin') {
return error('无权限访问', 403);
}
$row = Db::name('wechat_users')->where('id', $id)->find();
if (!$row) {
return error('用户不存在', 404);
}
$data = [
'id' => (int) $row['id'],
'username' => $row['nickname'] ?? ('用户' . $row['id']),
'nickname' => $row['nickname'] ?? '',
'avatar' => $row['avatar'] ?? '',
'phone' => $row['phone'] ?? '',
'email' => '',
'gender' => (int) ($row['gender'] ?? 0),
'country' => $row['country'] ?? '',
'province' => $row['province'] ?? '',
'city' => $row['city'] ?? '',
'status' => (int) ($row['status'] ?? 1),
'lastLoginAt' => isset($row['lastLoginAt']) ? (int) $row['lastLoginAt'] : null,
'createdAt' => isset($row['createdAt']) ? (int) $row['createdAt'] : null,
'updatedAt' => isset($row['updatedAt']) ? (int) $row['updatedAt'] : null,
];
$tests = Db::name('test_results')
->where('userId', $id)
->field('id, testType, resultData, createdAt, requiresPayment, isPaid, paidAmount, paidAt, orderId')
->order('createdAt', 'desc')
->select()
->toArray();
foreach ($tests as &$t) {
$raw = $t['resultData'] ?? '';
$t['result'] = is_string($raw) ? $raw : json_encode($raw, JSON_UNESCAPED_UNICODE);
}
$data['testCount'] = count($tests);
$data['testList'] = $tests;
$data['mbtiType'] = $this->extractResultType($tests, 'mbti');
$data['pdpType'] = $this->extractResultType($tests, 'pdp');
$data['discType'] = $this->extractResultType($tests, 'disc');
$data['faceType'] = $this->extractResultType($tests, 'face');
$data['faceMbtiType'] = $this->extractFaceSubType($tests, 'mbti');
$data['faceDiscType'] = $this->extractFaceSubType($tests, 'disc');
$data['facePdpType'] = $this->extractFaceSubType($tests, 'pdp');
$data['matchingEnterprises'] = $this->buildMatchingEnterprises(
(int) $id,
(string) ($data['mbtiType'] ?? ''),
(string) ($data['pdpType'] ?? ''),
(string) ($data['discType'] ?? '')
);
return success($data);
}
/**
* 按企业测评池内与用户 MBTI/PDP/DISC 的同质比例推荐企业,并附带登记负责人联系方式。
* 仅超级管理后台使用;无测评维度时按池内活跃人数近似排序。
*
* @return array<int, array<string, mixed>>
*/
private function buildMatchingEnterprises(int $userId, string $userMbti, string $userPdp, string $userDisc): array
{
$userMbtiU = strtoupper(preg_replace('/[^A-Z]/', '', $userMbti));
$userPdpN = $this->normalizePoolTypeKey($userPdp);
$userDiscN = $this->normalizePoolTypeKey($userDisc);
try {
$entRows = Db::name('enterprises')
->whereNull('deletedAt')
->whereIn('status', ['operating', 'trial'])
->field('id,name,code,contactName,contactPhone,contactEmail,status')
->select()
->toArray();
} catch (\Throwable $e) {
return [];
}
if (!$entRows) {
return [];
}
$scored = [];
foreach ($entRows as $e) {
$eid = (int) ($e['id'] ?? 0);
if ($eid <= 0) {
continue;
}
$mbtiS = $this->enterprisePoolTypeHistogram($eid, 'mbti');
$pdpS = $this->enterprisePoolTypeHistogram($eid, 'pdp');
$discS = $this->enterprisePoolTypeHistogram($eid, 'disc');
$score = 36;
$reasons = [];
if ($userMbtiU !== '' && $mbtiS['total'] > 0) {
$hit = (int) ($mbtiS['byKey'][$userMbtiU] ?? 0);
$ratio = $hit / $mbtiS['total'];
$part = (int) round(44 * $ratio);
$score += $part;
$pct = (int) round($ratio * 100);
$reasons[] = 'MBTI 同质 ' . $pct . '%(池内 ' . $mbtiS['total'] . ' 人有效结果)';
}
if ($userPdpN !== '' && $pdpS['total'] > 0) {
$hit = (int) ($pdpS['byKey'][$userPdpN] ?? 0);
$ratio = $hit / $pdpS['total'];
$score += (int) round(12 * $ratio);
if ($ratio > 0) {
$reasons[] = 'PDP 同质 ' . (int) round($ratio * 100) . '%';
}
}
if ($userDiscN !== '' && $discS['total'] > 0) {
$hit = (int) ($discS['byKey'][$userDiscN] ?? 0);
$ratio = $hit / $discS['total'];
$score += (int) round(12 * $ratio);
if ($ratio > 0) {
$reasons[] = 'DISC 同质 ' . (int) round($ratio * 100) . '%';
}
}
$tested = max($mbtiS['total'], $pdpS['total'], $discS['total']);
if ($userMbtiU === '' && $userPdpN === '' && $userDiscN === '') {
$score = 40 + (int) min(38, $tested * 2);
$reasons[] = $tested > 0 ? '按池内测评活跃度推荐' : '暂无同质维度,展示登记企业';
}
$score = max(30, min(99, $score));
$typeLabel = '综合型';
if ($userMbtiU !== '' && $mbtiS['total'] > 0 && (($mbtiS['byKey'][$userMbtiU] ?? 0) / $mbtiS['total']) >= 0.25) {
$typeLabel = '文化相近MBTI 分布)';
} elseif ($userPdpN !== '' && $pdpS['total'] > 0) {
$typeLabel = '行为风格相近PDP 分布)';
} elseif ($userDiscN !== '' && $discS['total'] > 0) {
$typeLabel = '协作风格相近DISC 分布)';
}
$scored[] = [
'id' => $eid,
'name' => (string) ($e['name'] ?? ''),
'code' => (string) ($e['code'] ?? ''),
'contactName' => (string) ($e['contactName'] ?? ''),
'contactPhone' => (string) ($e['contactPhone'] ?? ''),
'contactEmail' => (string) ($e['contactEmail'] ?? ''),
'status' => (string) ($e['status'] ?? ''),
'matchScore' => $score,
'matchTypeLabel' => $typeLabel,
'matchReason' => $reasons ? implode('', $reasons) : '可与负责人沟通用人匹配',
'poolTestedUsers' => $tested,
];
}
usort($scored, static function ($a, $b) {
return ($b['matchScore'] ?? 0) <=> ($a['matchScore'] ?? 0);
});
return array_slice($scored, 0, 10);
}
/**
* 企业池内各用户对某测评类型的「最新一条」结果类型分布
*
* @return array{total:int,byKey:array<string,int>}
*/
private function enterprisePoolTypeHistogram(int $enterpriseId, string $testType): array
{
$targetType = strtolower($testType);
try {
$rows = Db::name('test_results')
->where('enterpriseId', $enterpriseId)
->where('testType', $testType)
->order('createdAt', 'desc')
->field('userId,resultData')
->select()
->toArray();
} catch (\Throwable $e) {
return ['total' => 0, 'byKey' => []];
}
$seen = [];
$byKey = [];
foreach ($rows as $r) {
$uid = (int) ($r['userId'] ?? 0);
if ($uid <= 0 || isset($seen[$uid])) {
continue;
}
$raw = $r['resultData'] ?? '';
$result = is_string($raw) ? $raw : json_encode($raw, JSON_UNESCAPED_UNICODE);
$mock = [['result' => $result, 'testType' => $testType]];
$label = $this->extractResultType($mock, $testType);
if ($label === '' || ($targetType === 'face' && $label === '人脸分析')) {
continue;
}
$key = $targetType === 'mbti'
? strtoupper(preg_replace('/[^A-Z]/', '', $label))
: $this->normalizePoolTypeKey($label);
if ($key === '') {
continue;
}
$seen[$uid] = true;
$byKey[$key] = ($byKey[$key] ?? 0) + 1;
}
$totalTyped = array_sum($byKey);
return ['total' => $totalTyped, 'byKey' => $byKey];
}
private function normalizePoolTypeKey(string $s): string
{
$s = trim($s);
if ($s === '') {
return '';
}
$s = str_replace([' ', ' '], '', $s);
return mb_strtolower($s, 'UTF-8');
}
private function parseMbtiFromResult($result): string
{
if (!is_string($result)) return '';
$dec = json_decode($result, true);
if (is_array($dec)) {
return (string) ($dec['type'] ?? $dec['result'] ?? $dec['mbtiType'] ?? '');
}
return trim($result);
}
private function extractResultType(array $tests, string $type): string
{
$targetType = strtolower($type);
foreach ($tests as $t) {
if (strtolower($t['testType'] ?? '') !== $targetType) {
continue;
}
$result = $t['result'] ?? '';
if (!is_string($result)) {
continue;
}
$dec = json_decode($result, true);
if (!is_array($dec)) {
// 无法解析 JSON 时,直接返回原始字符串
return $targetType === 'face' ? '人脸分析' : trim($result);
}
// 人脸分析:有记录就返回固定标签
if ($targetType === 'face') {
return '人脸分析';
}
// MBTI直接读 mbtiType/type
if ($targetType === 'mbti') {
return (string) ($dec['mbtiType'] ?? $dec['type'] ?? $dec['result'] ?? '');
}
// DISC优先 description.type然后 dominantType
if ($targetType === 'disc') {
$desc = $dec['description']['type'] ?? null;
if (is_string($desc) && $desc !== '') {
return $desc;
}
if (!empty($dec['dominantType'])) {
return (string) $dec['dominantType'];
}
return (string) ($dec['disc'] ?? '');
}
// PDP优先 description.type然后 dominantType
if ($targetType === 'pdp') {
$desc = $dec['description']['type'] ?? null;
if (is_string($desc) && $desc !== '') {
return $desc;
}
if (!empty($dec['dominantType'])) {
return (string) $dec['dominantType'];
}
return (string) ($dec['pdp'] ?? '');
}
// 兜底:尝试常见字段
return (string) ($dec['type'] ?? $dec['result'] ?? '');
}
return '';
}
/**
* 从人脸分析结果中提取对应的 MBTI / DISC / PDP 文本
*/
private function extractFaceSubType(array $tests, string $subType): string
{
$target = strtolower($subType);
foreach ($tests as $t) {
if (strtolower($t['testType'] ?? '') !== 'face') {
continue;
}
$result = $t['result'] ?? '';
if (!is_string($result)) {
continue;
}
$dec = json_decode($result, true);
if (!is_array($dec)) {
continue;
}
if ($target === 'mbti') {
if (!empty($dec['mbti']['type'])) {
return (string) $dec['mbti']['type'];
}
if (!empty($dec['mbtiType'])) {
return (string) $dec['mbtiType'];
}
} elseif ($target === 'disc') {
if (!empty($dec['disc']['primary'])) {
return (string) $dec['disc']['primary'];
}
if (!empty($dec['disc'])) {
return (string) $dec['disc'];
}
} elseif ($target === 'pdp') {
if (!empty($dec['pdp']['primary'])) {
return (string) $dec['pdp']['primary'];
}
if (!empty($dec['pdp'])) {
return (string) $dec['pdp'];
}
}
}
return '';
}
}