Files
ruoyi-vue-pro/sql/postgresql/education/009-education-idempotency-unified.sql

104 lines
5.6 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================
-- Education 模块 — 统一幂等表 DDL (PostgreSQL)
-- Ticket: 合并 Answer + Submit 幂等表为统一 education_idempotency
-- Migration: 009
-- Prerequisites: 002-education-practice-session.sql
-- =============================================
-- =============================================
-- Preconditions
-- =============================================
-- Verify table does not already exist:
-- SELECT COUNT(*) FROM information_schema.tables
-- WHERE table_catalog = current_database()
-- AND table_name = 'education_idempotency';
-- Result MUST be 0 before executing this migration.
--
-- NOTE: This table replaces education_answer_idempotency and education_submit_idempotency.
-- Those tables should be dropped after this migration is verified:
-- DROP TABLE IF EXISTS education_answer_idempotency;
-- DROP TABLE IF EXISTS education_submit_idempotency;
-- =============================================
-- 统一幂等表
-- =============================================
-- Purpose: Unified idempotency store for all education write operations.
-- operation field distinguishes: SUBMIT_ANSWER vs SUBMIT_SESSION.
-- Same (tenant, user, operation, idempotency_key) + same request_hash → replay original response.
-- Same key + different request_hash → conflict.
-- PostgreSQL ON CONFLICT DO NOTHING resolves concurrent same-key inserts.
--
-- Indexes:
-- uk_idempotency — per-tenant, per-actor, per-operation uniqueness for idempotency key.
-- Used by: IdempotencyStoreMapper.insertIgnore() ON CONFLICT resolution.
-- idx_tenant_session — covers lookup by session for audit/debug.
--
-- Fields specific to SUBMIT_ANSWER: question_id, selected_answer (nullable for SUBMIT_SESSION).
-- Fields specific to SUBMIT_SESSION: report_id (nullable for SUBMIT_ANSWER).
-- response_json: stores serialized response for replay after timeout/retry.
-- request_hash: SHA-256 of canonical payload for content-based dedup.
CREATE TABLE education_idempotency (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
operation VARCHAR(32) NOT NULL,
idempotency_key VARCHAR(64) NOT NULL,
request_hash VARCHAR(64) NOT NULL,
session_id BIGINT NOT NULL,
question_id VARCHAR(64) DEFAULT NULL,
selected_answer TEXT DEFAULT NULL,
report_id BIGINT DEFAULT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACCEPTED',
response_json TEXT DEFAULT NULL,
business_payload JSONB DEFAULT NULL,
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT false
);
COMMENT ON TABLE education_idempotency IS '教育-统一幂等记录(合并 answer + submit';
COMMENT ON COLUMN education_idempotency.id IS '主键';
COMMENT ON COLUMN education_idempotency.tenant_id IS '租户编号';
COMMENT ON COLUMN education_idempotency.user_id IS '用户编号';
COMMENT ON COLUMN education_idempotency.operation IS '操作类型SUBMIT_ANSWER-答题, SUBMIT_SESSION-交卷';
COMMENT ON COLUMN education_idempotency.idempotency_key IS '客户端幂等键UUID';
COMMENT ON COLUMN education_idempotency.request_hash IS '请求载荷 SHA-256 哈希';
COMMENT ON COLUMN education_idempotency.session_id IS '会话 ID';
COMMENT ON COLUMN education_idempotency.question_id IS '题目 IDSUBMIT_ANSWER';
COMMENT ON COLUMN education_idempotency.selected_answer IS '学生已选答案SUBMIT_ANSWER';
COMMENT ON COLUMN education_idempotency.report_id IS '关联报告 IDSUBMIT_SESSION';
COMMENT ON COLUMN education_idempotency.status IS '状态ACCEPTED-已接受, CONFLICT-冲突';
COMMENT ON COLUMN education_idempotency.response_json IS '首次成功响应 JSON用于重试重放';
COMMENT ON COLUMN education_idempotency.business_payload IS '业务扩展载荷JSONB';
COMMENT ON COLUMN education_idempotency.creator IS '创建者';
COMMENT ON COLUMN education_idempotency.create_time IS '创建时间';
COMMENT ON COLUMN education_idempotency.updater IS '更新者';
COMMENT ON COLUMN education_idempotency.update_time IS '更新时间';
COMMENT ON COLUMN education_idempotency.deleted IS '是否删除';
CREATE UNIQUE INDEX uk_idempotency ON education_idempotency (tenant_id, user_id, operation, idempotency_key);
CREATE INDEX idx_idempotency_tenant_session ON education_idempotency (tenant_id, session_id);
-- =============================================
-- PracticeSessionDO: add review_fingerprint column
-- =============================================
-- Purpose: Store canonical fingerprint for wrong-question review session idempotency.
-- SHA-256 of comma-joined sorted wrong_question IDs for deterministic comparison.
ALTER TABLE education_practice_session
ADD COLUMN review_fingerprint VARCHAR(64) DEFAULT NULL;
COMMENT ON COLUMN education_practice_session.review_fingerprint IS '错题复习指纹SHA-256用于幂等创建';
-- =============================================
-- Post-migration verification queries
-- =============================================
-- Verify new table exists:
-- \d education_idempotency
-- Verify unique index:
-- SELECT * FROM pg_indexes WHERE tablename = 'education_idempotency';
-- Verify column added:
-- SELECT column_name, data_type FROM information_schema.columns
-- WHERE table_name = 'education_practice_session' AND column_name = 'review_fingerprint';