高考版优化

审核模式优化
This commit is contained in:
Ghost
2026-04-29 18:01:26 +08:00
parent 59419c4ce9
commit a411bf7b21
134 changed files with 19898 additions and 3371 deletions

View File

@@ -5,6 +5,7 @@ use app\BaseController;
use app\common\service\ResumeUploadsAdminService;
use app\controller\admin\concern\ExtractsTestResults;
use think\facade\Db;
use think\facade\Log;
use think\facade\Request;
/**
@@ -419,6 +420,200 @@ class AppUser extends BaseController
return paginate_response($list, $total, $page, $pageSize);
}
/**
* JOIN 后按去重用户计数(别名 w = wechat_users
* 不可用 fieldRaw + value(别名)ThinkPHP 的 value() 会清空 field 并把参数当成列名,聚合会丢失。
*
* @param \think\db\Query $query
*/
private function countDistinctAppUsers($query): int
{
try {
// find() 在无 where 时不执行 SQL仅 JOIN 也会被跳过),聚合恒错成 0
$row = (clone $query)
->whereRaw('1=1')
->fieldRaw('COUNT(DISTINCT `w`.`id`) AS `_agg_u`')
->find();
if (is_object($row) && method_exists($row, 'toArray')) {
$row = $row->toArray();
}
if (!is_array($row)) {
return 0;
}
return (int) ($row['_agg_u'] ?? 0);
} catch (\Throwable $e) {
Log::warning('[AppUser::countDistinctAppUsers] ' . $e->getMessage());
return 0;
}
}
/**
* 用户画像汇总卡片(与列表同一企业口径)
* GET /api/v1/admin/app-users/stats
*/
public function stats()
{
$user = $this->request->user ?? null;
if (!$user) {
return error('未登录', 401);
}
if (!in_array($user['role'] ?? '', ['admin', 'enterprise_admin'])) {
return error('无权限访问', 403);
}
$enterpriseId = $user['enterpriseId'] ?? null;
if (!$enterpriseId) {
$adminRow = Db::name('users')->where('id', $user['userId'] ?? 0)->find();
$enterpriseId = $adminRow['enterpriseId'] ?? null;
}
$out = [
'faceCount' => 0,
'resumeCount' => 0,
'phoneCount' => 0,
'anyTestCount' => 0,
'cooperationCount' => 0,
];
try {
if ($enterpriseId) {
$eid = (int) $enterpriseId;
$poolSql = Db::name('user_profile')
->whereRaw('enterpriseId = ' . $eid)
->group('userId')
->field('userId')
->buildSql(true);
$dedupSql = Db::name('wechat_users')
->alias('w2')
->join([$poolSql => 'p2'], 'w2.id = p2.userId')
->field('w2.openid, MAX(w2.id) AS mid')
->group('w2.openid')
->buildSql(true);
$base = Db::name('wechat_users')->alias('w')
->join([$poolSql => 'p'], 'w.id = p.userId')
->join([$dedupSql => 'd'], 'w.id = d.mid');
$tblUp = Db::name('user_profile')->getTable();
$tblTr = Db::name('test_results')->getTable();
$out['faceCount'] = $this->countDistinctAppUsers(
(clone $base)->whereRaw(
'EXISTS (SELECT 1 FROM `' . $tblUp
. '` up WHERE up.userId = `w`.`id` AND up.enterpriseId = ? AND (TRIM(IFNULL(up.coldFaceLevel,\'\')) <> \'\' OR up.coldFaceScore IS NOT NULL))'
. ' OR EXISTS (SELECT 1 FROM `' . $tblTr
. '` tf WHERE tf.userId = `w`.`id` AND tf.enterpriseId = ? AND tf.testType IN (\'face\',\'ai\'))',
[$eid, $eid]
)
);
$out['phoneCount'] = $this->countDistinctAppUsers(
(clone $base)->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
);
$out['anyTestCount'] = $this->countDistinctAppUsers(
(clone $base)
->join('test_results tr', 'tr.userId = w.id')
->where('tr.enterpriseId', $eid)
);
// 与「合作意向」Tab 一致:本企业 user_cooperation_choices 去重人数,不依赖 user_profile 池
$coopRow = Db::name('user_cooperation_choices')
->where('enterpriseId', $eid)
->fieldRaw('COUNT(DISTINCT `userId`) AS `_c`')
->find();
$out['cooperationCount'] = (int) (is_array($coopRow) ? ($coopRow['_c'] ?? 0) : 0);
$cntResumeUpload = $this->countDistinctAppUsers(
(clone $base)
->join('enterprise_resume_uploads eru', 'eru.userId = w.id')
->where('eru.enterpriseId', $eid)
);
$cntPhoneAndTest = $this->countDistinctAppUsers(
(clone $base)
->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
->join('test_results tr2', 'tr2.userId = w.id')
->where('tr2.enterpriseId', $eid)
);
$cntBoth = $this->countDistinctAppUsers(
(clone $base)
->join('enterprise_resume_uploads eru', 'eru.userId = w.id')
->where('eru.enterpriseId', $eid)
->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
->join('test_results tr3', 'tr3.userId = w.id')
->where('tr3.enterpriseId', $eid)
);
$out['resumeCount'] = max(0, $cntResumeUpload + $cntPhoneAndTest - $cntBoth);
} else {
$dedupSql = Db::name('wechat_users')
->alias('w2')
->field('w2.openid, MAX(w2.id) AS mid')
->group('w2.openid')
->buildSql(true);
$base = Db::name('wechat_users')->alias('w')
->join([$dedupSql => 'd'], 'w.id = d.mid');
$tblUp = Db::name('user_profile')->getTable();
$tblTr = Db::name('test_results')->getTable();
$out['faceCount'] = $this->countDistinctAppUsers(
(clone $base)->whereRaw(
'EXISTS (SELECT 1 FROM `' . $tblUp
. '` up WHERE up.userId = `w`.`id` AND (TRIM(IFNULL(up.coldFaceLevel,\'\')) <> \'\' OR up.coldFaceScore IS NOT NULL))'
. ' OR EXISTS (SELECT 1 FROM `' . $tblTr
. '` tf WHERE tf.userId = `w`.`id` AND tf.testType IN (\'face\',\'ai\'))'
)
);
$out['phoneCount'] = $this->countDistinctAppUsers(
(clone $base)->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
);
$out['anyTestCount'] = $this->countDistinctAppUsers(
(clone $base)
->join('test_results tr', 'tr.userId = w.id')
);
// 与「合作意向」Tab 一致:全平台去重 userId无企业归属账号
$coopRowAll = Db::name('user_cooperation_choices')
->whereRaw('1=1')
->fieldRaw('COUNT(DISTINCT `userId`) AS `_c`')
->find();
$out['cooperationCount'] = (int) (is_array($coopRowAll) ? ($coopRowAll['_c'] ?? 0) : 0);
$cntResumeUpload = $this->countDistinctAppUsers(
(clone $base)
->join('enterprise_resume_uploads eru', 'eru.userId = w.id')
);
$cntPhoneAndTest = $this->countDistinctAppUsers(
(clone $base)
->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
->join('test_results tr2', 'tr2.userId=w.id')
);
$cntBoth = $this->countDistinctAppUsers(
(clone $base)
->join('enterprise_resume_uploads eru', 'eru.userId=w.id')
->whereRaw('TRIM(IFNULL(w.phone,\'\')) <> \'\'')
->join('test_results tr3', 'tr3.userId=w.id')
);
$out['resumeCount'] = max(0, $cntResumeUpload + $cntPhoneAndTest - $cntBoth);
}
} catch (\Throwable $e) {
Log::warning('[AppUser::stats] ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
}
return success($out);
}
/**
* 测试用户详情:基本信息 + 测试记录列表
* GET /api/v1/admin/app-users/:id