Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
c03a4904
Commit
c03a4904
authored
Oct 23, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(ai): stop stale QR requests from appearing as generating
parent
156d9ab6
Pipeline
#3871
failed in 0 seconds
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
28 additions
and
10 deletions
+28
-10
apps/ai-service/src/modules/auth/qr-login.service.ts
+9
-10
apps/ai-service/tests/qr-login-routing.spec.ts
+19
-0
No files found.
apps/ai-service/src/modules/auth/qr-login.service.ts
View file @
c03a4904
...
...
@@ -572,10 +572,8 @@ export class QrLoginService {
* ⚠️ **更正(2026-08-27 实测)**:这里原来写「一轮扫码只有 2 分钟」——**是错的**。
* 实测一轮普通登录码 10:29:18 下发、10:37:18 才 state=3 过期,活了 **8 分钟**。
* 120 秒是**新设备验证码**(qrCodeType=1)的固定有效期,被张冠李戴了。
* ⚠️ 于是下面那个 3 分钟兜底窗**偏短**:audit 查不到、而二维码是 5 分钟前发的时,
* 会把本轮的 [11028] 也滤掉,页面停在「正在生成二维码」。
* ⛔ 但**不能**简单调大 —— 调大就把上面那个串轮问题放回来了。
* ⇒ 正解是别走到兜底:audit 行是 start() 必写的,走到这里说明那一轮不是我们开的。
* 审计窗口内查不到本轮时,明确提示核实并重新授权。旧席位可能仍保存 deviceId,
* ⛔ 不能退回滚动时间窗口并显示「正在生成」,也不能自动再次申请设备。
*/
await
this
.
seats
.
assertRound
(
user
.
tenantId
,
user
.
id
,
deviceId
);
let
floor
=
since
??
null
;
...
...
@@ -596,14 +594,15 @@ export class QrLoginService {
WHERE occurred_at > now() - interval '30 minutes'
AND action = 'auth.qr.start' AND object_id =
${
deviceId
}
ORDER BY occurred_at DESC LIMIT 1`
;
// 席位可以继续保留旧 deviceId,但不能把过旧/缺失的轮次当成正在生成。
// ⛔ 不自动重新下单:旧设备是否结束必须先核实,避免重复占用供应商设备。
if
(
!
started
[
0
])
{
return
{
phase
:
'failed'
,
hint
:
'本次绑号请求已无法继续,请联系管理员核实旧请求并重新授权。'
};
}
// ⚠️ 减 5 秒:下单和写 audit 之间有间隔,而 [11028] 可能在写完之前就到了。
// 卡得太死会把本轮的二维码事件也滤掉。
floor
=
started
[
0
]
?
new
Date
(
started
[
0
].
occurred_at
.
getTime
()
-
5
_000
)
:
new
Date
(
Date
.
now
()
-
3
*
60
_000
);
round
=
started
[
0
]
?
{
reuse_vid
:
started
[
0
].
reuse_vid
,
started_by
:
started
[
0
].
started_by
}
:
null
;
floor
=
new
Date
(
started
[
0
].
occurred_at
.
getTime
()
-
5
_000
);
round
=
{
reuse_vid
:
started
[
0
].
reuse_vid
,
started_by
:
started
[
0
].
started_by
};
}
// ⚠️ 带 received_at 谓词:webhook_deliveries 按它分区,不带就要扫每个分区
const
events
=
await
this
.
prisma
.
$queryRaw
<
...
...
apps/ai-service/tests/qr-login-routing.spec.ts
View file @
c03a4904
...
...
@@ -4,6 +4,25 @@ import { randomUUID } from 'node:crypto';
describe
(
'扫码环境归属'
,
()
=>
{
const
user
=
{
id
:
'user'
,
tenantId
:
'tenant'
}
as
never
;
it
(
'复用旧设备但查不到有效扫码轮次时明确结束,不无限显示生成中或再次下单'
,
async
()
=>
{
const
prisma
=
{
$queryRaw
:
jest
.
fn
().
mockResolvedValue
([])
};
const
ecom
=
{
platformCallRaw
:
jest
.
fn
()
};
const
service
=
new
QrLoginService
(
prisma
as
never
,
ecom
as
never
,
{
assertRound
:
jest
.
fn
()
}
as
never
);
const
result
=
await
service
.
status
(
user
,
'old-device'
);
expect
(
result
.
phase
).
toBe
(
'failed'
);
expect
(
result
.
hint
).
toContain
(
'重新授权'
);
expect
(
prisma
.
$queryRaw
).
toHaveBeenCalledTimes
(
1
);
expect
(
ecom
.
platformCallRaw
).
not
.
toHaveBeenCalled
();
});
it
(
'新轮次回调已到达时返回二维码;尚未到达时仍等待'
,
async
()
=>
{
const
started
=
{
occurred_at
:
new
Date
(),
reuse_vid
:
null
,
started_by
:
'user'
};
const
prisma
=
{
$queryRaw
:
jest
.
fn
().
mockResolvedValueOnce
([
started
]).
mockResolvedValueOnce
([
{
event_type
:
'11028'
,
payload
:
{
data
:
{
qrCodeUrl
:
'https://example.test/qr'
}
}
},
]).
mockResolvedValueOnce
([
started
]).
mockResolvedValueOnce
([])
};
const
service
=
new
QrLoginService
(
prisma
as
never
,
{}
as
never
,
{
assertRound
:
jest
.
fn
()
}
as
never
);
expect
(
await
service
.
status
(
user
,
'new-device'
)).
toMatchObject
({
phase
:
'waiting'
,
qrCodeUrl
:
'https://example.test/qr'
});
expect
((
await
service
.
status
(
user
,
'new-device'
)).
phase
).
toBe
(
'pending'
);
});
it
.
each
([
'15002'
,
'11026'
])(
'冲突 %s 显示失败,不绑定或启动同步'
,
async
(
event_type
)
=>
{
const
prisma
=
{
$queryRaw
:
jest
.
fn
().
mockResolvedValue
([{
event_type
,
payload
:
{
__ingressLoginConflict
:
true
}
}])
};
const
service
=
new
QrLoginService
(
prisma
as
never
,
{}
as
never
,
{
assertRound
:
jest
.
fn
()
}
as
never
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment