修复考试白名单驱动缺/待考名单:改用卷王项目参与者(Project Partners)真实模型
旧代码误读 surveySetting.whiteLists(实为题目选项白名单),导致线上从未触发。现改为读取 /api/project/partner/list 的 RESPONDENT_SYS_USER/IMP_USER 参与者作为应考名单,并保留 whiteLists 作为极老版本兜底。 dashboard.php 移除未实现的提交率卡片;全_score 默认 100 已在 data 层修正。e2e 测试 mock 与断言同步改为真实模型,全量 132 测试通过。
This commit is contained in:
+102
-25
@@ -484,38 +484,78 @@ class SurveyKingClient
|
||||
}
|
||||
|
||||
/**
|
||||
* 取问卷设置的「答题白名单」成员列表(待考名单来源)
|
||||
* 取「应考名单」来源(待考 / 缺考名单由此驱动)。
|
||||
*
|
||||
* 卷王不同版本字段形状不一,这里尽量兼容:
|
||||
* - survey.surveySetting.whiteLists:{ 题ID: [值,...] }(社区版常见)
|
||||
* - survey.surveySetting.whiteList:{ 题ID: [值,...] }
|
||||
* - 也可能直接是字符串或嵌套数组
|
||||
* 返回去重后的纯值数组(顺序保持首次出现),值本身通常是学号 / 姓名 / 登陆账号。
|
||||
* 未设置白名单返回空数组(上层据此「按现在的方案显示」)。
|
||||
* 卷王真实模型(社区版 v1.13 与开源版一致):
|
||||
* 限制谁可以答卷的是项目「参与者(受访者)」—— t_project_partner 中
|
||||
* type = RESPONDENT_SYS_USER(2) / RESPONDENT_IMP_USER(3) 的记录,
|
||||
* 经 /api/project/partner/list 返回,每条带 status(0未访问 / 1已访问未答 / 2已答题)。
|
||||
* 这才是「考试白名单 / 通讯录名单」的真实载体。
|
||||
*
|
||||
* 旧代码误把 surveySetting.whiteLists(按「题目选项」白名单,限制某题可选项)当成应考名单,
|
||||
* 字段形状根本不对,导致线上从未触发。这里以真实模型为准,whiteLists 仅作极老版本的兜底。
|
||||
*
|
||||
* @param string $projectId 问卷 ID
|
||||
* @param array|null $survey 可传入已解析的 survey 对象,避免重复请求 API(DataService::build 已取过 project)
|
||||
* @return array<int,string>
|
||||
* @param array|null $survey 已解析的 survey(仅极老版本白名单兜底用,新模型不依赖它)
|
||||
* @return array<int,string> 应考成员标识:优先姓名(便于与本地名单按姓名匹配),其次登陆账号 / 学号
|
||||
*/
|
||||
public function getWhitelist(string $projectId, ?array $survey = null): array
|
||||
{
|
||||
if ($survey === null) {
|
||||
$proj = $this->getProject($projectId);
|
||||
if (!is_array($proj)) {
|
||||
return [];
|
||||
}
|
||||
$survey = $proj['survey'] ?? null;
|
||||
if (is_string($survey) && $survey !== '') {
|
||||
$decoded = json_decode($survey, true);
|
||||
if (is_array($decoded)) {
|
||||
$survey = $decoded;
|
||||
// 1) 真实模型:项目「受访者」参与者 = 应考名单
|
||||
$partners = [];
|
||||
try {
|
||||
$partners = $this->getRespondentPartners($projectId);
|
||||
} catch (Throwable $e) {
|
||||
$this->log[] = 'getRespondentPartners 失败(回退白名单兜底):' . $e->getMessage();
|
||||
}
|
||||
|
||||
if ($partners !== []) {
|
||||
$out = [];
|
||||
foreach ($partners as $p) {
|
||||
if (!is_array($p)) {
|
||||
continue;
|
||||
}
|
||||
$type = (int)($p['type'] ?? 0);
|
||||
if ($type !== 2 && $type !== 3) {
|
||||
continue; // 仅取受访者(系统用户 / 导入用户),排除 OWNER / COLLABORATOR
|
||||
}
|
||||
$name = trim((string)($p['userName'] ?? ''));
|
||||
$userId = trim((string)($p['userId'] ?? ''));
|
||||
$user = $p['user'] ?? null;
|
||||
$login = '';
|
||||
if (is_array($user)) {
|
||||
if ($name === '') {
|
||||
$name = trim((string)($user['name'] ?? ''));
|
||||
}
|
||||
$login = trim((string)($user['login'] ?? $user['account'] ?? $user['username'] ?? ''));
|
||||
if ($userId === '') {
|
||||
$userId = trim((string)($user['id'] ?? ''));
|
||||
}
|
||||
}
|
||||
// 与本地名单 / 答卷姓名匹配的标识:姓名 > 登陆账号 > userId
|
||||
$key = $name !== '' ? $name : ($login !== '' ? $login : $userId);
|
||||
if ($key !== '') {
|
||||
$out[] = $key;
|
||||
}
|
||||
}
|
||||
// 去重(大小写不敏感),保留首次出现顺序
|
||||
$seen = [];
|
||||
$res = [];
|
||||
foreach ($out as $item) {
|
||||
$k = strtolower($item);
|
||||
if (!isset($seen[$k])) {
|
||||
$seen[$k] = true;
|
||||
$res[] = $item;
|
||||
}
|
||||
}
|
||||
if ($res !== []) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
// 兼容 { surveySetting: ... } 与 { survey: { surveySetting: ... } } 两种结构,
|
||||
// 也允许白名单直接挂在 survey 顶层(whiteLists / whiteList / whitelist)。
|
||||
$setting = null;
|
||||
|
||||
// 2) 兜底:极老 / 定制版本可能把应考名单放在 surveySetting.whiteLists(实为题目选项白名单)
|
||||
if (is_array($survey)) {
|
||||
$setting = null;
|
||||
if (isset($survey['surveySetting']) && is_array($survey['surveySetting'])) {
|
||||
$setting = $survey['surveySetting'];
|
||||
} elseif (isset($survey['survey']) && is_array($survey['survey']) && isset($survey['survey']['surveySetting'])) {
|
||||
@@ -529,11 +569,48 @@ class SurveyKingClient
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($setting)) {
|
||||
return self::parseWhitelist($setting);
|
||||
}
|
||||
}
|
||||
if (!is_array($setting)) {
|
||||
return [];
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取项目的「受访者」参与者列表(应考名单真实来源)。
|
||||
* GET /api/project/partner/list?projectId=...&types=2,3(RESPONDENT_SYS_USER / RESPONDENT_IMP_USER),自动翻页。
|
||||
* @return array<int,array{id:string,type:int,userName:string,userId:string,status:int,user?:array}>
|
||||
*/
|
||||
public function getRespondentPartners(string $projectId): array
|
||||
{
|
||||
$size = 200;
|
||||
$all = [];
|
||||
for ($page = 1; $page <= 50; $page++) {
|
||||
$r = $this->request('/project/partner/list', [
|
||||
'projectId' => $projectId,
|
||||
'types' => '2,3',
|
||||
'current' => $page,
|
||||
'pageSize' => $size,
|
||||
]);
|
||||
$data = $r['data'];
|
||||
$list = is_array($data) ? ($data['list'] ?? $data) : [];
|
||||
if (!is_array($list) || $list === []) {
|
||||
break;
|
||||
}
|
||||
foreach ($list as $p) {
|
||||
if (is_array($p)) {
|
||||
$all[] = $p;
|
||||
}
|
||||
}
|
||||
$total = is_array($data) ? (int)($data['total'] ?? 0) : 0;
|
||||
if ($total > 0 && count($all) >= $total) {
|
||||
break;
|
||||
}
|
||||
if (count($list) < $size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return self::parseWhitelist($setting);
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user