yeg
2026-09-17 dd22571b369970b35ca6c48f32010e2876b43d25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 商户端手输 6 位数字核销码的失败计数与锁定。
--
-- 核销码本来就下发一个 6 位数字码给顾客(order_dynamic_verification_code.display_code),但系统里
-- 没有任何地方能输入它:相机不好用或屏幕反光时,商户只能让顾客反复刷新二维码。补上手输入口的同时
-- 必须限次——数字码只有 100 万空间,不限次就是一个可以暴力枚举的核销入口。
--
-- 计数按「谁在输」记(商户 + 门店 + 操作员):连续输错到上限就锁定一段时间,锁定期间连校验都不做,
-- 否则攻击者仍然可以用「这次报错是无效码还是已锁定」把枚举继续下去。
--
-- 幂等:`create table if not exists` 在 MySQL 5.7 与 8.0 上都能直接重复执行,所以这份迁移也可以
-- 用 scripts/apply-sql-file.ps1 直接落到开发库(开发库 Flyway 关闭、结构不由迁移维护)。
create table if not exists merchant_numeric_code_attempt (
    id bigint not null auto_increment,
    merchant_id bigint not null,
    store_id bigint not null,
    operator_id bigint not null,
    consecutive_failures int not null default 0 comment '连续输错次数,成功或锁定时归零',
    locked_until datetime default null comment '锁定到期时间,未锁定为 NULL',
    last_attempt_at datetime not null default current_timestamp,
    primary key (id),
    unique key uk_merchant_numeric_code_attempt (merchant_id, store_id, operator_id)
) engine=InnoDB default charset=utf8mb4 comment='商户端手输数字核销码的失败计数与锁定';