feat(schedule): 定时任务 8 项 + 用量快照/压缩重试/到期提醒(V17)
PRD 附录 E。每个任务都是两段式:先以平台身份(tenantId=0)扫候选,再逐个租户摆上下文 干活——写数据必须在第二段,否则 tenant_id 被写成 0,通知进平台租户且不报错。 V17 新建 sys_tenant_usage_snapshot(唯一索引保证幂等;tenant_id 必须显式写)并给 collect_file 补 process_retry_count / process_error / last_retry_time + 只索引 「还没处理出规范件」的部分索引;压缩失败重试必须封顶,否则原图损坏的文件会被每 10 分钟重试一次而永远不成功。 跨租户的候选查询全部集中在 ScheduleMapper 一个文件、一律 @InterceptorIgnore + 只读; Map 结果的别名一律 AS "camelCase"(F-83:PG 会把未加引号的别名折成小写, 表现是任务照跑、日志一行 warn、什么都没做)。 验证:e2e-m7-schedule 38/38(含快照幂等与一致性、重试累加与封顶、到期提醒窗口)。
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- V17 · 定时任务收尾所需的 schema(PRD 附录 E)
|
||||
--
|
||||
-- 这一版只加三样东西,都是"不做就实现不了剩下三项定时任务"的:
|
||||
-- ① 文件处理失败的重试计数与失败原因("压缩失败重试"要用)
|
||||
-- ② 租户用量快照表("用量统计"要用 —— 快照必须落库,
|
||||
-- 否则"上个月的用量"永远算不出来:实时统计只能回答"现在多少")
|
||||
--
|
||||
-- ⚠️ 只加列/加表,**不改已有列** —— 滚动发布期间新旧两版同时在跑,
|
||||
-- 改列类型会让旧实例在新库上出错(见 docs/09-部署与运维.md §2.3)。
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- ── ① 文件处理失败:重试计数 + 失败原因 ──────────────────────
|
||||
|
||||
ALTER TABLE collect_file ADD COLUMN IF NOT EXISTS process_retry_count SMALLINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE collect_file ADD COLUMN IF NOT EXISTS process_error VARCHAR(256);
|
||||
ALTER TABLE collect_file ADD COLUMN IF NOT EXISTS last_retry_time TIMESTAMP;
|
||||
|
||||
COMMENT ON COLUMN collect_file.process_retry_count IS
|
||||
'处理失败后的重试次数。上限 3(PRD 附录 E「压缩失败重试:重试 ≤3 次」)——
|
||||
到上限就停,不然一个永远失败的图会被每 10 分钟重试一次,把磁盘与 CPU 白烧掉';
|
||||
COMMENT ON COLUMN collect_file.process_error IS
|
||||
'最近一次处理失败的原因(PRD FR-611「标记失败原因」)。
|
||||
⚠️ 与"保留原图"配套:只留原图而不知道为什么失败,运维只能靠猜';
|
||||
COMMENT ON COLUMN collect_file.last_retry_time IS '最近一次重试时间(观察重试是否在推进)';
|
||||
|
||||
-- 部分索引:只索引"还没处理出规范件"的行。
|
||||
-- 失败是**少数**(正常上传一步就压好了),全表索引会白白变大。
|
||||
CREATE INDEX IF NOT EXISTS idx_file_retry
|
||||
ON collect_file (process_retry_count, last_retry_time)
|
||||
WHERE deleted = 0 AND std_path IS NULL;
|
||||
|
||||
|
||||
-- ── ② 租户用量快照 ──────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_tenant_usage_snapshot (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
period VARCHAR(16) NOT NULL,
|
||||
student_count BIGINT NOT NULL DEFAULT 0,
|
||||
storage_mb BIGINT NOT NULL DEFAULT 0,
|
||||
ocr_count INTEGER NOT NULL DEFAULT 0,
|
||||
sms_count INTEGER NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sys_tenant_usage_snapshot IS
|
||||
'租户用量快照(PRD 附录 E「用量统计:每日 01:00」)。
|
||||
⚠️ 为什么必须落库而不是每次实时算:
|
||||
① 实时统计只能回答"现在多少",而计费与超额告警问的是"**上个月**用了多少";
|
||||
② 历史快照能回答"用量是什么时候涨上去的" —— 招生季与毕业季的形状完全不同;
|
||||
③ 存储量尤其重要:原始件 30 天后被清理,**清理前的占用在实时统计里再也看不到**';
|
||||
COMMENT ON COLUMN sys_tenant_usage_snapshot.period IS
|
||||
'统计周期,`2026-09`(月)或 `2026Q3`(季)—— 与 §6.7 的 period 同口径';
|
||||
COMMENT ON COLUMN sys_tenant_usage_snapshot.student_count IS
|
||||
'按证件号去重的学生数(同一个人多个任务只算一次)';
|
||||
COMMENT ON COLUMN sys_tenant_usage_snapshot.storage_mb IS
|
||||
'规范件体积合计(MB)。**不含原始件** —— 原始件 30 天后会被清掉,算进去会让曲线自己往下掉';
|
||||
COMMENT ON COLUMN sys_tenant_usage_snapshot.ocr_count IS
|
||||
'识别通道用量。一期恒为 0(M4 未接,等 OCR 选型),不编数字';
|
||||
|
||||
-- 一个租户一个周期一行:定时任务重复跑(或手工补跑)不会造出两行
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_usage_snapshot
|
||||
ON sys_tenant_usage_snapshot (tenant_id, period);
|
||||
-- 运营侧"某个月所有租户用了多少"的查询
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_snapshot_period
|
||||
ON sys_tenant_usage_snapshot (period, tenant_id);
|
||||
Reference in New Issue
Block a user