# 考试与试卷数据模型重构方案
## 一、问题回顾
当前系统中 **考试(Exam)** 和 **试卷(Paper)** 共用同一数据对象 `et_exam_exampaper_and_editexampaper`,导致:
- 同一份试卷不能用于多次考试
- 考试时间属性绑定在试卷上
- 业务边界模糊,难以维护
---
## 二、目标数据模型
### 2.1 核心表结构设计
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 重构后数据模型 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ et_exam_paper │ │ et_exam_examination │ │
│ │ (试卷主表) │◄───────│ (考试主表) │ │
│ ├─────────────────────┤ paper_id├─────────────────────┤ │
│ │ id (PK) │ │ id (PK) │ │
│ │ name │ │ paper_id (FK) │ │
│ │ description │ │ name │ │
│ │ category │ │ starttime │ │
│ │ categoryid │ │ endtime │ │
│ │ passpoint │ │ duration │ │
│ │ totalpoints │ │ shouldjoin │ │
│ │ creatperson │ │ realjoin │ │
│ │ creatpersonid │ │ leader │ │
│ │ createdepartment │ │ leaderid │ │
│ │ edittime │ │ createtime │ │
│ │ state │ │ state │ │
│ │ pg (1考试/2问卷) │ │ pg (1考试/2问卷) │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │ │
│ │ exam_id │
│ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────┐ │
│ │ et_exam_question │ │ et_exam_paper_question│ │et_exam_usertest│ │
│ │ (题目题库) │◄───│ (试卷题目关联) │◄───│ (用户答题) │ │
│ ├─────────────────────┤ ├─────────────────────┤ ├─────────────────┤ │
│ │ id (PK) │ │ id (PK) │ │ id (PK) │ │
│ │ type │ │ paper_id (FK) │ │ exam_id (FK) │ │
│ │ subject │ │ question_id (FK) │ │ question_id(FK) │ │
│ │ answer │ │ score │ │ user_id │ │
│ │ optionA~F │ │ ismust │ │ user_answer │ │
│ │ ... │ │ num │ │ user_score │ │
│ └─────────────────────┘ └─────────────────────┘ │ mark_teacher │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 2.2 新旧表字段对照
| 新表 et_exam_paper | 来自 | 新表 et_exam_examinations | 来自 |
|-------------------|------|--------------------------|------|
| id | - | id | - |
| name | exampaper.name | paper_id | exampaper.id |
| description | exampaper.description | name | 关联 paper.name + "第N次考试" |
| category | exampaper.category | starttime | exampaper.startdate |
| categoryid | exampaper.categoryid | endtime | exampaper.enddate |
| passpoint | exampaper.passpoint | duration | exampaper.sc |
| totalpoints | 计算得出 | shouldjoin | limitation.count |
| creatperson | exampaper.creatperson | realjoin | 计算得出 |
| creatpersonid | exampaper.creatpersonid | leader | exampaper.leader |
| createdepartment | exampaper.createdepartment | leaderid | exampaper.leaderid |
| edittime | exampaper.edittime | createtime | exampaper.edittime |
| state | exampaper.state | state | exampaper.state |
| pg | exampaper.pg | pg | exampaper.pg |
---
## 三、详细重构方案
### 3.1 第一阶段:数据库层重构
#### 3.1.1 创建新表
```sql
-- 1. 创建试卷主表
CREATE TABLE `et_exam_paper` (
`id` varchar(32) NOT NULL COMMENT '试卷ID',
`name` varchar(50) NOT NULL COMMENT '试卷名称',
`description` varchar(500) DEFAULT NULL COMMENT '试卷描述',
`category` varchar(20) DEFAULT NULL COMMENT '所属知识点',
`categoryid` varchar(8) DEFAULT NULL COMMENT '知识点编码',
`passpoint` int(8) DEFAULT NULL COMMENT '及格分数',
`totalpoints` int(8) DEFAULT NULL COMMENT '试卷总分',
`creatperson` varchar(10) DEFAULT NULL COMMENT '创建人',
`creatpersonid` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`createdepartment` varchar(50) DEFAULT NULL COMMENT '部门',
`edittime` datetime(6) DEFAULT NULL COMMENT '最后编辑时间',
`state` int(8) DEFAULT 1 COMMENT '状态(1草稿/2已发布)',
`pg` int(1) DEFAULT 1 COMMENT '类型(1考试/2问卷)',
`isdeleted` int(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2. 创建考试主表
CREATE TABLE `et_exam_examinations` (
`id` varchar(32) NOT NULL COMMENT '考试ID',
`paper_id` varchar(32) NOT NULL COMMENT '关联试卷ID',
`name` varchar(50) NOT NULL COMMENT '考试名称',
`starttime` datetime(6) DEFAULT NULL COMMENT '开始时间',
`endtime` datetime(6) DEFAULT NULL COMMENT '结束时间',
`duration` int(8) DEFAULT NULL COMMENT '时长(分钟)',
`shouldjoin` int(8) DEFAULT 0 COMMENT '应考人数',
`realjoin` int(8) DEFAULT 0 COMMENT '实考人数',
`leader` varchar(20) DEFAULT NULL COMMENT '负责人',
`leaderid` varchar(32) DEFAULT NULL COMMENT '负责人ID',
`createtime` datetime(6) DEFAULT NULL COMMENT '创建时间',
`state` varchar(20) DEFAULT '未开始' COMMENT '状态',
`pg` int(1) DEFAULT 1 COMMENT '类型(1考试/2问卷)',
`isdeleted` int(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_paper_id` (`paper_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 3. 创建试卷题目关联表
CREATE TABLE `et_exam_paper_question` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`paper_id` varchar(32) NOT NULL COMMENT '试卷ID',
`question_id` varchar(32) NOT NULL COMMENT '题目ID',
`score` int(8) DEFAULT NULL COMMENT '分值',
`ismust` int(1) DEFAULT 1 COMMENT '是否必答',
`num` int(8) DEFAULT NULL COMMENT '题目序号',
`type` varchar(20) DEFAULT NULL COMMENT '题型',
`type_num` int(8) DEFAULT NULL COMMENT '题型序号',
PRIMARY KEY (`id`),
KEY `idx_paper_id` (`paper_id`),
KEY `idx_question_id` (`question_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 4. 创建考试限制表(新)
CREATE TABLE `et_exam_examination_limitation` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`exam_id` varchar(32) NOT NULL COMMENT '考试ID',
`limitation` int(1) DEFAULT NULL COMMENT '发放范围类型',
`user` varchar(20) DEFAULT NULL COMMENT '用户名',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
PRIMARY KEY (`id`),
KEY `idx_exam_id` (`exam_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 5. 修改用户答题表
ALTER TABLE `et_exam_usertest`
ADD COLUMN `exam_id` varchar(32) NOT NULL COMMENT '考试ID' AFTER `id`,
DROP COLUMN `name`,
DROP COLUMN `department`,
MODIFY COLUMN `user_id` varchar(32) NOT NULL COMMENT '用户ID',
ADD INDEX `idx_exam_id` (`exam_id`);
```
#### 3.1.2 数据迁移
```sql
-- =============================================
-- 数据迁移脚本
-- =============================================
-- 迁移试卷数据
INSERT INTO et_exam_paper (
id, name, description, category, categoryid,
passpoint, totalpoints, creatperson, creatpersonid,
createdepartment, edittime, state, pg
)
SELECT
id,
name,
description,
category,
categoryid,
passpoint,
(SELECT SUM(score) FROM et_exam_editexampaper WHERE edit_id = exampaper.id) AS totalpoints,
creatperson,
creatpersonid,
createdepartment,
edittime,
state,
pg
FROM et_exam_exampaper_and_editexampaper
WHERE pg IN (1, 2);
-- 迁移考试数据(每次唯一的试卷创建一条考试记录)
INSERT INTO et_exam_examinations (
id, paper_id, name, starttime, endtime,
duration, createtime, state, pg
)
SELECT
id, -- 考试ID(复用原试卷ID)
id AS paper_id, -- 关联试卷ID(自关联,因为现在是1:1)
CONCAT(name, '-首次考试') AS name,
startdate,
enddate,
sc,
edittime,
state,
pg
FROM et_exam_exampaper_and_editexampaper
WHERE pg IN (1, 2);
-- 迁移试卷题目关联
INSERT INTO et_exam_paper_question (
id, paper_id, question_id, score, ismust, num, type, type_num
)
SELECT
md5(UUID()) AS id,
edit_id AS paper_id,
id AS question_id,
score,
ismust,
num,
type,
type_num
FROM et_exam_editexampaper;
-- 迁移考试限制数据
INSERT INTO et_exam_examination_limitation (
id, exam_id, limitation, user, user_id
)
SELECT
id,
exam_id,
limitation,
user,
user_id
FROM et_exam_limitation;
-- 迁移用户答题数据
UPDATE et_exam_usertest ut
INNER JOIN et_exam_editexampaper eq ON ut.question_id = eq.id
SET ut.exam_id = eq.edit_id;
```
---
### 3.2 第二阶段:SQL 映射层重构
#### 3.2.1 新建映射文件
创建 `et_exam_paper.map.xml`:
```xml
| 选择试卷: | |
|---|---|
| 考试名称: | |
| 考试时间: | - |
| 考试时长: | 分钟 |