feat(notify,log): 催报分钟级限流 + 学生微信登录写登录日志(两处合规补漏)

一、催报分钟级限流(F-29,契约 §3「10 次/分钟/任务」此前未实现)
  · UrgeService 加 Redis 计数:定向与一键催报共用,超限 42901
    且话说清等多久、24h 去重还在(去重防「学生被重复催」,限流防「老师连点
    把系统打爆」—— 两件事);限流放在「确实有活可干」之后(空结果连点不计数)
  · Redis 不可用时放行不瘫痪(限流是保护,不是业务前提)

二、学生微信登录也写 sys_login_log(F-31 —— 此前只有 PC 账号密码登录留痕,
   按 §13.4 查「学生登录记录」永远为空,合规缺口)
  · 新增 WechatLoginLogger:小程序登录(MpLoginService)与 H5 授权回调/
    打桩登录(MpH5AuthController)三条路径都写
  · login_type=2 与 DDL 注释对齐(1 PC 2 小程序 3 开放接口);H5 与小程序
    共用 2(DDL 只定义到 3,不造新值),要区分看 userAgent —— 字段里本来就带着
  · 写失败不影响登录(日志表满了不能没人能登录);不记 code/openid

验证:e2e-m5-urge → 42/42(新增「连点第 11 次被限流」断言,先清计数保证确定性)
      e2e-m3-h5-auth → 24/24(新增「两次 stub-login 各留一条 login_type=2」断言,
      清理时连登录日志一起清)
This commit is contained in:
2026-09-30 12:38:48 +08:00
parent 8f80744d63
commit 57bf39d5a1
7 changed files with 165 additions and 10 deletions
+11 -1
View File
@@ -192,6 +192,15 @@ def main() -> int:
f"WHERE ur.user_id = {uid1.strip()} AND r.role_code = 'STUDENT'")
check("已授予学生角色(用户类型由角色推导)", okk4 and role.strip() == "1", f"{role}")
# ── 4b. 登录留痕(2026-09-30 补上,F-31)────────────────
# 此前只有 PC 账号密码登录写 sys_login_log,学生从微信进来完全无痕 ——
# 按 §13.4 查"学生登录记录"永远是空的,合规上是个缺口
okk5, lls = sql("SELECT count(*) FROM sys_login_log "
f"WHERE user_id = {uid1.strip()} AND login_type = 2 AND success = 1")
check("⭐ 微信登录也写 sys_login_log(login_type=2)",
okk5 and int(lls.strip()) >= 2,
f"行数={lls.strip()}(本脚本已做两次 stub-login,应各留一条)")
# ── 5. 没有 scene 时不猜租户 ─────────────────────────
print("\n[5] 没带 scene 时")
code3, headers3 = redirect_of(f"{base}/api/v1/mp/h5/stub-login?openid={openid}")
@@ -219,8 +228,9 @@ def main() -> int:
print("\n[7] 清理测试数据")
for uid in CREATED_USER_IDS:
sql(f"DELETE FROM sys_user_role WHERE user_id = {uid}")
sql(f"DELETE FROM sys_login_log WHERE user_id = {uid}")
sql(f"DELETE FROM sys_user WHERE id = {uid}")
ok(f"已清理 {len(CREATED_USER_IDS)} 个测试账号")
ok(f"已清理 {len(CREATED_USER_IDS)} 个测试账号(含其登录日志)")
print("\n" + "=" * 72)
print(f"通过 {len(PASSED)} 失败 {len(FAILED)} 共 {len(PASSED) + len(FAILED)}")