← 项目列表
课程已完成2026年6月

数据库大作业 — 数据库设计与实施

沉浸式剧本杀平台的完整数据库设计——从 E-R 建模到 3NF 规范化,覆盖 10 张业务表、触发器、存储过程、RBAC 权限体系。

数据库E-R图SQL3NFSQLMySQL视图触发器存储过程RBAC
📋 项目概览
技术栈
SQLMySQL视图触发器存储过程RBAC
功能特性
  • 沉浸式剧本平台数据库设计(10 张表)
  • 用户/剧本/角色/线索/打卡点/场次/订单/排班/进度/评价
  • 触发器:订单支付自动更新场次人数 + NPC 排班冲突检测
  • 视图:场次概览 + 剧本评分汇总
  • 存储过程:场次运营报表生成
  • RBAC 四级角色权限体系(游客/NPC/运营/管理员)
还有 2 项
  • 50+ 条测试数据覆盖全部业务场景
  • 复杂查询:嵌套子查询 / EXISTS / GROUP BY HAVING
📖 技术分析报告在 GitHub 查看 ↗

数据库设计与实施

项目概述

一个完整的沉浸式剧本杀业务数据库设计,涵盖用户管理、剧本管理、角色分配、线索链、打卡点、场次排期、订单支付、NPC 排班、游玩进度追踪和评价系统等 10 张核心表。同时包含一个独立的 SQL 实战操练部分,覆盖 20+ 道 SQL 查询练习。

架构设计

数据库以 Script(剧本)为业务核心,向外辐射关联 9 张表:Role 和 Clue 属于剧本的内容资产,CheckPoint 将虚拟线索映射到物理 GPS 位置,Session 和 Order 构成商家的运营和营收模块,NpcSchedule 管理人力排班,PlayerProgress 追踪玩家状态,Review 收集反馈。

外键约束确保数据完整性:Role/Clue 通过 script_id 级联删除,Order 通过 user_id 和 session_id 关联用户和场次,PlayerProgress 通过 uk_user_session 唯一约束保证同一用户在同一场次只能有一条进度记录。

视图与存储过程:View_SessionOverview 聚合场次概览(含余位和已支付订单数),View_ScriptRating 计算剧本评分统计。proc_session_report 存储过程接受剧本 ID 和日期范围参数,输出总订单数、总营收和逐场次明细。

技术亮点

触发器实现业务自动化:trg_order_payment_update_session 在订单支付状态变更时自动更新场次的 current_players 计数,无需应用层处理。trg_npcschedule_conflict_check 在插入排班前通过时间重叠检测防止 NPC 排班冲突,使用 SIGNAL SQLSTATE '45000' 抛出自定义错误。

RBAC 权限体系:使用 MySQL 的角色机制创建了 role_tourist/role_npc/role_operator/role_admin 四个数据库角色,每个角色精确控制可操作的表和列。tourist 只能查询和下单,npc 可更新游玩进度,operator 管理剧本内容,admin 拥有全部权限。

CHECK 约束与索引优化:通过 CHECK 约束确保角色类型、评分范围、难度等级的数据合法性。为常用查询字段建立 12 个索引,覆盖用户角色查询、订单状态查询、场次时间范围扫描等场景。

设计决策

命名规范遵循"表名+ID"的主键命名惯例,外键显式命名(fk_来源_目标)便于维护和错误定位。GPS 坐标使用 DECIMAL(10,7) 保证精度,price 使用 DECIMAL(10,2) 避免浮点误差。AR 相关内容预留 url 字段,为未来扩展留有余地。

关键代码解读

CREATE TRIGGER trg_npcschedule_conflict_check
BEFORE INSERT ON NpcSchedule
FOR EACH ROW
BEGIN
    SELECT COUNT(*) INTO conflict_count
    FROM NpcSchedule ns
    JOIN `Session` s ON ns.session_id = s.session_id
    WHERE ns.npc_id = NEW.npc_id
      AND (
          (s.start_time <= s_new.start_time AND s.end_time > s_new.start_time)
          OR (s_new.start_time <= s.start_time AND s_new.end_time > s.start_time)
      );
    IF conflict_count > 0 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '排班时间冲突';
    END IF;
END;

该触发器利用两段时间重叠的判断逻辑(时间段 A 的开始在 B 结束之前且 A 的结束在 B 开始之后),在插入排班前自动检测时间冲突。相比应用层检查,触发器从数据库层面保证排班数据的绝对一致性。

🚀 在线演示新窗口打开 ↗
💻 核心代码SQL · MySQL · 视图 · 触发器 · 存储过程 · RBAC
sql核心表结构(建表 DDL)大作业二.sql(建表部分) · 69 行

沉浸式剧本平台的核心数据模型,包含用户、剧本、角色、线索、打卡点 5 张主表。

-- =============================================
-- 沉浸式剧本平台 — 核心表结构
-- 作者: 童国睿
-- =============================================

-- 1. 用户表
CREATE TABLE User (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    phone VARCHAR(11) NOT NULL UNIQUE,
    nickname VARCHAR(50) NOT NULL,
    role_type TINYINT NOT NULL DEFAULT 0
        COMMENT &#39;0-游客 1-运营 2-NPC 3-管理员&#39;,
    status TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. 剧本表
CREATE TABLE Script (
    script_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL COMMENT &#39;剧本名称&#39;,
    description TEXT,
    difficulty TINYINT NOT NULL DEFAULT 1,
    duration INT NOT NULL COMMENT &#39;时长(分)&#39;,
    min_players INT NOT NULL DEFAULT 1,
    max_players INT NOT NULL,
    cover_url VARCHAR(255),
    status TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 3. 角色表
CREATE TABLE Role (
    role_id INT AUTO_INCREMENT PRIMARY KEY,
    script_id INT NOT NULL,
    name VARCHAR(50) NOT NULL,
    description TEXT,
    is_key_role TINYINT NOT NULL DEFAULT 0,
    sort_order INT NOT NULL DEFAULT 0,
    FOREIGN KEY (script_id) REFERENCES Script(script_id)
        ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 4. 线索表
CREATE TABLE Clue (
    clue_id INT AUTO_INCREMENT PRIMARY KEY,
    script_id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    content TEXT NOT NULL,
    clue_type TINYINT NOT NULL DEFAULT 0,
    trigger_type TINYINT NOT NULL DEFAULT 0,
    sequence_num INT NOT NULL,
    FOREIGN KEY (script_id) REFERENCES Script(script_id)
        ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 5. 打卡点表
CREATE TABLE CheckPoint (
    point_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    latitude DECIMAL(10,7) NOT NULL,
    longitude DECIMAL(10,7) NOT NULL,
    ar_content_url VARCHAR(255),
    binding_clue_id INT,
    is_required TINYINT NOT NULL DEFAULT 0,
    FOREIGN KEY (binding_clue_id) REFERENCES Clue(clue_id)
        ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql触发器:订单支付与排班冲突检测大作业二.sql(触发器) · 43 行

两个关键触发器:支付状态变更自动更新场次报名人数,NPC 排班时间冲突检测防止重复调度。

-- 触发器1:订单支付 -&gt; 场次人数自动更新
DELIMITER //
CREATE TRIGGER trg_order_payment_update_session
AFTER UPDATE ON `Order`
FOR EACH ROW
BEGIN
    IF OLD.payment_status = 0 AND NEW.payment_status = 1 THEN
        UPDATE `Session`
        SET current_players = current_players + 1
        WHERE session_id = NEW.session_id;
    END IF;
    IF OLD.payment_status = 1 AND NEW.payment_status = 2 THEN
        UPDATE `Session`
        SET current_players = current_players - 1
        WHERE session_id = NEW.session_id;
    END IF;
END//
DELIMITER ;

-- 触发器2:NPC排班时间冲突检测
DELIMITER //
CREATE TRIGGER trg_npcschedule_conflict_check
BEFORE INSERT ON NpcSchedule
FOR EACH ROW
BEGIN
    DECLARE conflict_count INT;
    SELECT COUNT(*) INTO conflict_count
    FROM NpcSchedule ns
    JOIN `Session` s ON ns.session_id = s.session_id
    JOIN `Session` s_new ON s_new.session_id = NEW.session_id
    WHERE ns.npc_id = NEW.npc_id
      AND ns.schedule_date = NEW.schedule_date
      AND (
          (s.start_time &lt;= s_new.start_time AND s.end_time &gt; s_new.start_time)
          OR
          (s_new.start_time &lt;= s.start_time AND s_new.end_time &gt; s.start_time)
      );
    IF conflict_count &gt; 0 THEN
        SIGNAL SQLSTATE &#39;45000&#39;
        SET MESSAGE_TEXT = &#39;该NPC在此时间段已有排班冲突&#39;;
    END IF;
END//
DELIMITER ;
sql存储过程:场次运营报表大作业二.sql(存储过程) · 44 行

按剧本和时间范围生成运营数据,包含总订单数、总收入、逐场次明细、上座率。

DELIMITER //
CREATE PROCEDURE proc_session_report(
    IN p_script_id INT,
    IN p_start_date DATE,
    IN p_end_date DATE
)
BEGIN
    DECLARE v_total_orders INT DEFAULT 0;
    DECLARE v_total_revenue DECIMAL(12,2) DEFAULT 0;

    SELECT COUNT(o.order_id), COALESCE(SUM(o.amount), 0)
    INTO v_total_orders, v_total_revenue
    FROM `Session` se
    LEFT JOIN `Order` o
        ON se.session_id = o.session_id AND o.payment_status = 1
    WHERE (p_script_id IS NULL OR se.script_id = p_script_id)
      AND DATE(se.start_time) BETWEEN p_start_date AND p_end_date;

    -- 汇总输出
    SELECT p_script_id AS script_id,
           p_start_date AS date_from, p_end_date AS date_to,
           v_total_orders AS paid_orders,
           v_total_revenue AS revenue;

    -- 逐场次明细
    SELECT s.name AS script_name, se.session_id,
           se.start_time,
           CONCAT(se.current_players, &#39;/&#39;, se.max_players) AS occupancy,
           ROUND(se.current_players / se.max_players * 100, 1) AS rate,
           se.price,
           COUNT(o.order_id) AS order_count,
           COALESCE(SUM(o.amount), 0) AS session_revenue
    FROM `Session` se
    JOIN Script s ON se.script_id = s.script_id
    LEFT JOIN `Order` o
        ON se.session_id = o.session_id AND o.payment_status = 1
    WHERE (p_script_id IS NULL OR se.script_id = p_script_id)
      AND DATE(se.start_time) BETWEEN p_start_date AND p_end_date
    GROUP BY se.session_id
    ORDER BY se.start_time;
END//
DELIMITER ;

-- 调用:CALL proc_session_report(1, &#39;2026-06-01&#39;, &#39;2026-06-30&#39;);
📁 源文件清单GitHub 仓库 ↗
路径说明行数
大作业二.sql完整数据库设计(建表/视图/触发器/存储过程/权限/测试数据)709
网站智能助手
💬 和我聊聊
🤖
你好呀 👋 有什么想聊的?