修人脸识别排队被误判为失败:识别中应继续等待
问题(162 服务器日志实证):
ERROR 人脸识别被系统驳回,判定签到失败 userId=1043 message=识别中,预计等待4位
ERROR 人脸识别被系统驳回,判定签到失败 userId=1043 message=识别中,预计等待8位
「识别中,预计等待N位」是服务端识别队列的进度提示,不是失败。
但 evalFaceIdentify 里只要 recognitionProgress 非空就判 Rejected+Settled,
于是提交后的复核立刻判定签到失败,用户看到报错,实际签到可能仍在排队后成功。
根因:
if res.Progress != "" { res.Rejected = true; res.Settled = true; return }
修复:
1. 新增 faceProgressIsPending / faceProgressIsTerminalFailure 两个判定:
- 含「识别中/排队/处理中/等待」等 → 进行中,继续等待;
- 含「未通过/失败/驳回/人脸不匹配/未检测到人脸」等 → 终止失败;
- 两者皆无的未知文案 → 保守按进行中处理,不误杀(超时后仍走原有
「未出结果按成功」兜底);
- 判定失败前先排除排队文案,避免「识别中」被误伤。
2. handler 轮询策略调整:原先固定 4 轮(0/1/2/3 秒)就收手,排队靠后
必然等不到结果。现在前 4 轮快速取结果,之后每 3 秒一轮,最长再等
90 秒;期间把最新排队提示透传给前端(queued=true),用户能看见进度。
测试:用日志里的真实文案断言不判驳回;同时保留 FRS.0036 等真实失败
文案必须判驳回;progressCode=2 无文案时仍判驳回;未知文案不误杀。
This commit is contained in:
@@ -785,12 +785,28 @@ func executeSignTask(c *fiber.Ctx) error {
|
||||
// 但今日校园侧实为未签到。故此处提交成功后回查详情确认结果,只有真正成功才扣费。
|
||||
if cpd.NeedsFaceIdentify(detail.Rows) {
|
||||
sendEvent("verifying", map[string]interface{}{"status": "verifying", "message": "正在核验人脸识别结果..."})
|
||||
// 轮询等待:0s → 1s → 2s → 3s(即查询时刻 ≈ t0/t1/t3/t6,逐次间隔递增到 3 秒)
|
||||
// 轮询等待:识别在服务端排队,可能返回「识别中,预计等待N位」。
|
||||
// 这种情况必须继续等待而不是判失败,因此:
|
||||
// - 前 4 轮间隔 0/1/2/3 秒,快速拿到常见情形(多数几秒内出结果);
|
||||
// - 之后转为每 3 秒一轮,最多再等 faceQueueMaxWait,
|
||||
// 覆盖排队靠后的情况;
|
||||
// - 期间若出现明确失败文案则立即判失败,出结果(signStatus=4)则成功。
|
||||
delays := []time.Duration{0, 1 * time.Second, 2 * time.Second, 3 * time.Second}
|
||||
const faceQueueMaxWait = 90 * time.Second
|
||||
deadline := time.Now().Add(faceQueueMaxWait)
|
||||
pendingHint := ""
|
||||
rejected := false
|
||||
var rejectMsg string
|
||||
settledOK := false
|
||||
for _, d := range delays {
|
||||
for i := 0; ; i++ {
|
||||
var d time.Duration
|
||||
if i < len(delays) {
|
||||
d = delays[i]
|
||||
} else if time.Now().Before(deadline) {
|
||||
d = 3 * time.Second
|
||||
} else {
|
||||
break // 等够了,走「未出结果按成功」兜底
|
||||
}
|
||||
if d > 0 {
|
||||
time.Sleep(d)
|
||||
}
|
||||
@@ -809,7 +825,14 @@ func executeSignTask(c *fiber.Ctx) error {
|
||||
settledOK = true
|
||||
break
|
||||
}
|
||||
// 尚未出结果:继续等待下一轮
|
||||
// 尚未出结果(含「识别中,预计等待N位」排队提示):继续等待。
|
||||
// 把最新排队提示透传给前端,让用户看到进度而不是干等。
|
||||
if res.Message != "" && res.Message != pendingHint {
|
||||
pendingHint = res.Message
|
||||
sendEvent("verifying", map[string]interface{}{
|
||||
"status": "verifying", "message": pendingHint, "queued": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
if rejected {
|
||||
if rejectMsg == "" {
|
||||
|
||||
@@ -120,3 +120,88 @@ func TestRawCodeString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 线上实证:人脸识别在服务端排队时,recognitionProgress 会返回
|
||||
// 「识别中,预计等待N位」。这类文案表示仍在进行,绝不能判为驳回。
|
||||
// 依据 162 服务器日志(v1.2.160 之前版本误判为签到失败)。
|
||||
func TestEvalFaceIdentifyQueueing(t *testing.T) {
|
||||
cases := []string{
|
||||
"识别中,预计等待4位",
|
||||
"识别中,预计等待8位",
|
||||
"识别中,预计等待1位",
|
||||
"识别中",
|
||||
"排队中,请稍候",
|
||||
"处理中",
|
||||
"预计等待3位",
|
||||
}
|
||||
for _, msg := range cases {
|
||||
t.Run(msg, func(t *testing.T) {
|
||||
got := evalFaceIdentify(map[string]interface{}{
|
||||
"isFaceIdentify": float64(1),
|
||||
"recognitionProgress": msg,
|
||||
"recognitionProgressCode": float64(0),
|
||||
"signStatus": "0",
|
||||
})
|
||||
if got.Rejected {
|
||||
t.Fatalf("排队文案 %q 不应判为驳回", msg)
|
||||
}
|
||||
if got.Settled {
|
||||
t.Fatalf("排队文案 %q 不应视为已出结果", msg)
|
||||
}
|
||||
if got.Message != msg {
|
||||
t.Fatalf("Message 应透传排队提示,got %q want %q", got.Message, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 真正的失败文案仍必须判驳回,不能因为放宽而漏判。
|
||||
func TestEvalFaceIdentifyRealFailure(t *testing.T) {
|
||||
cases := []string{
|
||||
"系统自动驳回。被驳回原因:网络连接故障,请重新尝试签到(FRS.0036)",
|
||||
"人脸识别未通过",
|
||||
"人脸不匹配",
|
||||
"未检测到人脸",
|
||||
"签到失败",
|
||||
}
|
||||
for _, msg := range cases {
|
||||
t.Run(msg, func(t *testing.T) {
|
||||
got := evalFaceIdentify(map[string]interface{}{
|
||||
"isFaceIdentify": float64(1),
|
||||
"recognitionProgress": msg,
|
||||
"signStatus": "0",
|
||||
})
|
||||
if !got.Rejected {
|
||||
t.Fatalf("失败文案 %q 应判驳回", msg)
|
||||
}
|
||||
if !got.Settled {
|
||||
t.Fatalf("失败文案 %q 应视为已出结果", msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 未识别文案保守处理:不确定就当进行中,不误杀正常签到。
|
||||
func TestEvalFaceIdentifyUnknownMessageNotRejected(t *testing.T) {
|
||||
got := evalFaceIdentify(map[string]interface{}{
|
||||
"isFaceIdentify": float64(1),
|
||||
"recognitionProgress": "系统繁忙,请稍后再试一下",
|
||||
"signStatus": "0",
|
||||
})
|
||||
if got.Rejected {
|
||||
t.Fatal("未知文案不应判驳回(保守当进行中)")
|
||||
}
|
||||
}
|
||||
|
||||
// recognitionProgressCode=2 仍应独立判驳回(无文案时靠码判断)。
|
||||
func TestEvalFaceIdentifyCode2WithoutMessage(t *testing.T) {
|
||||
got := evalFaceIdentify(map[string]interface{}{
|
||||
"isFaceIdentify": float64(1),
|
||||
"recognitionProgressCode": float64(2),
|
||||
"recognitionProgress": "",
|
||||
"signStatus": "0",
|
||||
})
|
||||
if !got.Rejected || !got.Settled {
|
||||
t.Fatalf("progressCode=2 应判驳回,got %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,11 +565,23 @@ func evalFaceIdentify(rows map[string]interface{}) SignCheckResult {
|
||||
res.Progress = strings.TrimSpace(mapValueStr(rows, "recognitionProgress"))
|
||||
res.ProgressCode = mapValueInt(rows["recognitionProgressCode"])
|
||||
|
||||
// 有驳回文案 → 明确驳回
|
||||
// 有进展文案时不能一律判驳回:今日校园在服务端识别排队期间,
|
||||
// recognitionProgress 会是「识别中,预计等待4位」这类排队提示
|
||||
// (线上日志实证)。此时应视为「仍在进行中」,交由调用方继续等待。
|
||||
// 只有文案本身表达失败/驳回语义时才判定为驳回。
|
||||
if res.Progress != "" {
|
||||
res.Message = res.Progress
|
||||
if faceProgressIsTerminalFailure(res.Progress) {
|
||||
res.Rejected = true
|
||||
res.Settled = true
|
||||
res.Message = res.Progress
|
||||
return res
|
||||
}
|
||||
if faceProgressIsPending(res.Progress) {
|
||||
// 明确排队中:未出结果,继续等待
|
||||
return res
|
||||
}
|
||||
// 无法识别的文案:保守按「仍在进行中」处理,不误杀;
|
||||
// 上层轮询到超时后按提交成功处理(现有兜底逻辑)。
|
||||
return res
|
||||
}
|
||||
// 有明确进展码(非 0)→ 已出结果;2 为驳回,其余视为已处理
|
||||
@@ -1661,3 +1673,48 @@ func SubmitSign(cookieMap map[string]string, host string, taskType TaskType, sub
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// faceProgressIsPending 判断 receptionProgress 文案是否表示「仍在排队/识别中」。
|
||||
//
|
||||
// 线上实证文案:「识别中,预计等待4位」「识别中,预计等待8位」。
|
||||
// 这类提示表示服务端识别队列尚未处理完,不是失败,必须继续等待。
|
||||
func faceProgressIsPending(msg string) bool {
|
||||
s := strings.TrimSpace(msg)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
// 常见排队/进行中关键词
|
||||
pendingWords := []string{
|
||||
"识别中", "排队中", "排队", "处理中", "等待", "预计等待", "审核中",
|
||||
}
|
||||
for _, w := range pendingWords {
|
||||
if strings.Contains(s, w) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// faceProgressIsTerminalFailure 判断文案是否明确表示失败/驳回(终止态)。
|
||||
//
|
||||
// 只有明确的否定结论才算终止,避免把排队提示误判成失败。
|
||||
func faceProgressIsTerminalFailure(msg string) bool {
|
||||
s := strings.TrimSpace(msg)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
// 先排除排队类文案(可能同时含"识别"字样)
|
||||
if faceProgressIsPending(s) {
|
||||
return false
|
||||
}
|
||||
failWords := []string{
|
||||
"未通过", "不通过", "失败", "驳回", "驳回申请", "无效",
|
||||
"未检测到人脸", "人脸不匹配", "请重试", "请重新", "超时",
|
||||
}
|
||||
for _, w := range failWords {
|
||||
if strings.Contains(s, w) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user