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
ffce47db
Commit
ffce47db
authored
Oct 23, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
Merge stale QR request recovery into test
parents
a7ab3e83
c03a4904
Pipeline
#3872
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 @
ffce47db
...
...
@@ -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 @
ffce47db
...
...
@@ -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