修人脸识别排队被误判为失败:识别中应继续等待
问题(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) {
|
if cpd.NeedsFaceIdentify(detail.Rows) {
|
||||||
sendEvent("verifying", map[string]interface{}{"status": "verifying", "message": "正在核验人脸识别结果..."})
|
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}
|
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
|
rejected := false
|
||||||
var rejectMsg string
|
var rejectMsg string
|
||||||
settledOK := false
|
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 {
|
if d > 0 {
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
}
|
}
|
||||||
@@ -809,7 +825,14 @@ func executeSignTask(c *fiber.Ctx) error {
|
|||||||
settledOK = true
|
settledOK = true
|
||||||
break
|
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 rejected {
|
||||||
if rejectMsg == "" {
|
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.Progress = strings.TrimSpace(mapValueStr(rows, "recognitionProgress"))
|
||||||
res.ProgressCode = mapValueInt(rows["recognitionProgressCode"])
|
res.ProgressCode = mapValueInt(rows["recognitionProgressCode"])
|
||||||
|
|
||||||
// 有驳回文案 → 明确驳回
|
// 有进展文案时不能一律判驳回:今日校园在服务端识别排队期间,
|
||||||
|
// recognitionProgress 会是「识别中,预计等待4位」这类排队提示
|
||||||
|
// (线上日志实证)。此时应视为「仍在进行中」,交由调用方继续等待。
|
||||||
|
// 只有文案本身表达失败/驳回语义时才判定为驳回。
|
||||||
if res.Progress != "" {
|
if res.Progress != "" {
|
||||||
|
res.Message = res.Progress
|
||||||
|
if faceProgressIsTerminalFailure(res.Progress) {
|
||||||
res.Rejected = true
|
res.Rejected = true
|
||||||
res.Settled = true
|
res.Settled = true
|
||||||
res.Message = res.Progress
|
return res
|
||||||
|
}
|
||||||
|
if faceProgressIsPending(res.Progress) {
|
||||||
|
// 明确排队中:未出结果,继续等待
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
// 无法识别的文案:保守按「仍在进行中」处理,不误杀;
|
||||||
|
// 上层轮询到超时后按提交成功处理(现有兜底逻辑)。
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
// 有明确进展码(非 0)→ 已出结果;2 为驳回,其余视为已处理
|
// 有明确进展码(非 0)→ 已出结果;2 为驳回,其余视为已处理
|
||||||
@@ -1661,3 +1673,48 @@ func SubmitSign(cookieMap map[string]string, host string, taskType TaskType, sub
|
|||||||
|
|
||||||
return nil
|
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