包含: 1. 目标数据模型设计(3NF规范化) 2. 数据库层重构方案(新建4张表) 3. SQL映射层重构(6个文件修改) 4. Java控制器层重构 5. 前端页面重构方案 6. 影响范围分析(18+文件) 7. 风险评估矩阵(6大风险) 8. 数据迁移详细方案(含验证SQL) 9. 实施计划(25个工作日,5周) 10. 测试方案与部署方案
680 lines
23 KiB
Markdown
680 lines
23 KiB
Markdown
# 考试与试卷数据模型重构方案
|
||
|
||
## 一、问题回顾
|
||
|
||
当前系统中 **考试(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
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<dynamic-sql>
|
||
<!-- 获取试卷列表 -->
|
||
<sql id="getPaperList"><![CDATA[
|
||
SELECT * FROM et_exam_paper
|
||
WHERE isdeleted = 0 AND pg = ?
|
||
<@p p=" AND name LIKE ?">name</@p>
|
||
<@p p=" AND category LIKE ?">category</@p>
|
||
<@p p=" AND createdepartment LIKE ?">createdepartment</@p>
|
||
]]></sql>
|
||
|
||
<!-- 获取试卷详情(含题目) -->
|
||
<sql id="getPaperDetail"><![CDATA[
|
||
SELECT p.*, q.id AS ques_id, q.subject, q.type, q.score, q.num, q.ismust
|
||
FROM et_exam_paper p
|
||
INNER JOIN et_exam_paper_question pq ON p.id = pq.paper_id
|
||
INNER JOIN et_exam_question q ON pq.question_id = q.id
|
||
WHERE p.id = ?
|
||
ORDER BY pq.num
|
||
]]></sql>
|
||
</dynamic-sql>
|
||
```
|
||
|
||
创建 `et_exam_examinations.map.xml`:
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<dynamic-sql>
|
||
<!-- 获取考试列表 -->
|
||
<sql id="getExamList"><![CDATA[
|
||
SELECT e.*, p.name AS paper_name, p.description AS paper_desc
|
||
FROM et_exam_examinations e
|
||
INNER JOIN et_exam_paper p ON e.paper_id = p.id
|
||
WHERE e.isdeleted = 0 AND e.pg = ?
|
||
<@p p=" AND e.name LIKE ?">name</@p>
|
||
]]></sql>
|
||
|
||
<!-- 获取考试学员列表 -->
|
||
<sql id="getExamUserList"><![CDATA[
|
||
SELECT ut.*, u.username, u.orgname AS department
|
||
FROM et_exam_usertest ut
|
||
INNER JOIN et_train_baseuser u ON ut.user_id = u.usercode
|
||
WHERE ut.exam_id = ?
|
||
<@p p=" AND ut.user LIKE ?">name</@p>
|
||
]]></sql>
|
||
</dynamic-sql>
|
||
```
|
||
|
||
#### 3.2.2 修改现有映射文件
|
||
|
||
**修改 `et_exam_usertest.map.xml`**:
|
||
|
||
| 原 SQL | 修改为 |
|
||
|---------|--------|
|
||
| `from et_exam_exampaper_and_editexampaper` | `from et_exam_examinations e INNER JOIN et_exam_paper p ON e.paper_id = p.id` |
|
||
| `exampaper.id` | `e.id` |
|
||
| `exampaper.name` | `p.name` |
|
||
| `exampaper.sc` | `e.duration` |
|
||
| `et_exam_editexampaper.edit_id` | `et_exam_paper_question.paper_id` |
|
||
|
||
**修改 `et_exam_limitation.map.xml`**:
|
||
|
||
| 原 SQL | 修改为 |
|
||
|---------|--------|
|
||
| `et_exam_limitation.exam_id` | `et_exam_examination_limitation.exam_id` |
|
||
| `et_exam_exampaper_and_editexampaper.id` | `et_exam_examinations.paper_id` |
|
||
|
||
---
|
||
|
||
### 3.3 第三阶段:Java 控制器层重构
|
||
|
||
#### 3.3.1 新增控制器
|
||
|
||
```java
|
||
// 新增 ExamExaminationController.java
|
||
@Controller
|
||
@RequestMapping("/exam/examination")
|
||
public class ExamExaminationController {
|
||
|
||
// 考试管理 CRUD
|
||
// 考试发布/关闭
|
||
// 考试时间修改
|
||
// 考试统计
|
||
}
|
||
```
|
||
|
||
#### 3.3.2 修改现有控制器
|
||
|
||
**ExampaperController.java** 修改:
|
||
- 移除考试时间相关字段 (startdate, enddate, sc)
|
||
- 新增 `paper_id` 关联
|
||
- 保留试卷内容管理
|
||
|
||
**ExamController.java** 增强:
|
||
- 分离考试管理和试卷管理
|
||
- 新增"基于试卷创建考试"功能
|
||
|
||
---
|
||
|
||
### 3.4 第四阶段:前端页面重构
|
||
|
||
#### 3.4.1 页面拆分
|
||
|
||
```
|
||
原结构:
|
||
exam/exampaper.ftl → 试卷+考试混用
|
||
├── exampaper_list.ftl → 试卷列表
|
||
├── exampaper_edit.ftl → 试卷+考试信息编辑
|
||
|
||
重构后:
|
||
exam/paper/
|
||
├── paper_list.ftl → 试卷列表
|
||
├── paper_edit.ftl → 试卷编辑(仅内容)
|
||
├── paper_detail.ftl → 试卷预览
|
||
|
||
exam/examination/
|
||
├── exam_list.ftl → 考试列表
|
||
├── exam_edit.ftl → 考试编辑(仅时间/人员)
|
||
├── exam_start.ftl → 开始答题
|
||
└── exam_result.ftl → 考试成绩
|
||
```
|
||
|
||
#### 3.4.2 新增"创建考试"功能
|
||
|
||
```html
|
||
<!-- exam/examination/exam_create.ftl -->
|
||
<div class="formTitle"><span class="icon icon_menu"></span>创建考试</div>
|
||
<table class="fromTable">
|
||
<tr>
|
||
<th>选择试卷:</th>
|
||
<td>
|
||
<select id="paperSelector" name="paper_id">
|
||
<!-- 从 et_exam_paper 加载 -->
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th>考试名称:</th>
|
||
<td><input id="examName" name="name" type="text"/></td>
|
||
</tr>
|
||
<tr>
|
||
<th>考试时间:</th>
|
||
<td>
|
||
<input id="starttime" name="starttime"/> -
|
||
<input id="endtime" name="endtime"/>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th>考试时长:</th>
|
||
<td><input id="duration" name="duration" type="number"/> 分钟</td>
|
||
</tr>
|
||
</table>
|
||
```
|
||
|
||
---
|
||
|
||
## 四、影响范围分析
|
||
|
||
### 4.1 涉及文件清单
|
||
|
||
| 类型 | 文件数 | 主要文件 |
|
||
|------|--------|----------|
|
||
| SQL 映射 | 6 | et_exam_*.map.xml |
|
||
| FTL 模板 | 18 | exam/*.ftl |
|
||
| Java 控制器 | 6 | Exam*Controller.class |
|
||
| Java Service | 4 | Exam*Service*.class |
|
||
| JavaScript | 3 | exam/*.js |
|
||
| 报表 | 3 | reportlets/exam*.cpt |
|
||
|
||
### 4.2 业务功能影响
|
||
|
||
| 功能模块 | 影响程度 | 说明 |
|
||
|----------|----------|------|
|
||
| 试卷管理 | 中 | 需分离考试时间字段 |
|
||
| 考试管理 | 高 | 完全重构 |
|
||
| 题库管理 | 低 | 无变化 |
|
||
| 答题功能 | 高 | 需关联考试ID |
|
||
| 成绩管理 | 中 | 需调整查询逻辑 |
|
||
| 统计分析 | 中 | 需新增统计维度 |
|
||
| 培训计划关联 | 高 | 需调整关联方式 |
|
||
|
||
---
|
||
|
||
## 五、重构风险评估
|
||
|
||
### 5.1 风险矩阵
|
||
|
||
| 风险项 | 可能性 | 影响 | 等级 | 缓解措施 |
|
||
|--------|--------|------|------|----------|
|
||
| 历史数据丢失 | 中 | 高 | 🔴 高 | 完整备份 + 试运行环境验证 |
|
||
| 业务逻辑遗漏 | 高 | 高 | 🔴 高 | 逐模块测试 + 功能清单核对 |
|
||
| 性能下降 | 低 | 中 | 🟡 中 | SQL 优化 + 索引添加 |
|
||
| 用户体验变化 | 中 | 中 | 🟡 中 | 保持 UI 一致 + 充分培训 |
|
||
| 报表不可用 | 中 | 中 | 🟡 中 | 同步更新 FineReport 模板 |
|
||
| 回滚困难 | 中 | 高 | 🔴 高 | 蓝绿部署 + 快速回滚脚本 |
|
||
|
||
### 5.2 回滚方案
|
||
|
||
```sql
|
||
-- 紧急回滚脚本(保留原表备份)
|
||
RENAME TABLE
|
||
et_exam_paper TO et_exam_paper_backup_20260416,
|
||
et_exam_examinations TO et_exam_examinations_backup_20260416,
|
||
et_exam_paper_question TO et_exam_paper_question_backup_20260416,
|
||
et_exam_examination_limitation TO et_exam_examination_limitation_backup_20260416;
|
||
|
||
-- 恢复旧表
|
||
RENAME TABLE
|
||
et_exam_exampaper_backup TO et_exam_exampaper_and_editexampaper;
|
||
```
|
||
|
||
---
|
||
|
||
## 六、数据迁移详细方案
|
||
|
||
### 6.1 迁移前准备
|
||
|
||
```sql
|
||
-- 1. 完整备份(必须)
|
||
mysqldump -u root -p --single-transaction \
|
||
--databases etms \
|
||
> backup_etms_20260416.sql
|
||
|
||
-- 2. 创建迁移日志表
|
||
CREATE TABLE et_exam_migration_log (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
step_name VARCHAR(100),
|
||
status VARCHAR(20),
|
||
records_count INT,
|
||
error_message TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
### 6.2 分步迁移
|
||
|
||
```sql
|
||
-- Step 1: 创建新表结构(无数据)
|
||
CREATE TABLE `et_exam_paper` (...) -- 如上述
|
||
|
||
-- Step 2: 迁移试卷数据
|
||
INSERT INTO et_exam_migration_log (step_name, status, records_count)
|
||
VALUES ('migrate_paper', 'starting', 0);
|
||
|
||
INSERT INTO et_exam_paper (...) -- 如上述
|
||
|
||
INSERT INTO et_exam_migration_log (step_name, status, records_count)
|
||
SELECT 'migrate_paper', 'completed', COUNT(*) FROM et_exam_paper;
|
||
|
||
-- Step 3: 迁移考试数据
|
||
INSERT INTO et_exam_migration_log (step_name, status, records_count)
|
||
VALUES ('migrate_examination', 'starting', 0);
|
||
|
||
INSERT INTO et_exam_examinations (...) -- 如上述
|
||
|
||
INSERT INTO et_exam_migration_log (step_name, status, records_count)
|
||
SELECT 'migrate_examination', 'completed', COUNT(*) FROM et_exam_examinations;
|
||
|
||
-- Step 4: 迁移题目关联
|
||
INSERT INTO et_exam_paper_question (...)
|
||
SELECT ... FROM et_exam_editexampaper;
|
||
|
||
-- Step 5: 迁移用户答题数据
|
||
UPDATE et_exam_usertest ut
|
||
INNER JOIN et_exam_editexampaper eq ON ut.question_id = eq.id
|
||
SET ut.exam_id = eq.edit_id;
|
||
|
||
-- Step 6: 数据验证
|
||
SELECT
|
||
(SELECT COUNT(*) FROM et_exam_paper) AS paper_count,
|
||
(SELECT COUNT(*) FROM et_exam_examinations) AS exam_count,
|
||
(SELECT COUNT(*) FROM et_exam_paper_question) AS question_count,
|
||
(SELECT COUNT(*) FROM et_exam_usertest WHERE exam_id IS NOT NULL) AS user_answer_count;
|
||
```
|
||
|
||
### 6.3 迁移后验证
|
||
|
||
```sql
|
||
-- 1. 数据完整性检查
|
||
SELECT '试卷数据' AS check_item, COUNT(*) AS count FROM et_exam_paper
|
||
UNION ALL SELECT '考试数据', COUNT(*) FROM et_exam_examinations
|
||
UNION ALL SELECT '题目关联', COUNT(*) FROM et_exam_paper_question
|
||
UNION ALL SELECT '用户答题', COUNT(*) FROM et_exam_usertest
|
||
UNION ALL SELECT '答题关联', COUNT(*) FROM et_exam_usertest WHERE exam_id IS NOT NULL;
|
||
|
||
-- 2. 外键关系验证
|
||
SELECT '孤立考试' AS issue, COUNT(*) AS count
|
||
FROM et_exam_examinations e
|
||
LEFT JOIN et_exam_paper p ON e.paper_id = p.id
|
||
WHERE p.id IS NULL;
|
||
|
||
-- 3. 分数汇总验证
|
||
SELECT
|
||
p.id, p.name,
|
||
p.totalpoints AS stored_total,
|
||
COALESCE(SUM(pq.score), 0) AS calculated_total,
|
||
IF(p.totalpoints = COALESCE(SUM(pq.score), 0), 'OK', 'MISMATCH') AS check_result
|
||
FROM et_exam_paper p
|
||
LEFT JOIN et_exam_paper_question pq ON p.id = pq.paper_id
|
||
GROUP BY p.id;
|
||
```
|
||
|
||
---
|
||
|
||
## 七、实施计划
|
||
|
||
### 7.1 时间估算
|
||
|
||
| 阶段 | 工作内容 | 工期 | 累计 |
|
||
|------|----------|------|------|
|
||
| 第一阶段 | 数据库设计与建表 | 1天 | 1天 |
|
||
| 第二阶段 | 数据迁移脚本开发 | 2天 | 3天 |
|
||
| 第三阶段 | SQL 映射层重构 | 3天 | 6天 |
|
||
| 第四阶段 | Java 控制器重构 | 4天 | 10天 |
|
||
| 第五阶段 | 前端页面重构 | 3天 | 13天 |
|
||
| 第六阶段 | 报表适配 | 2天 | 15天 |
|
||
| 第七阶段 | 集成测试 | 3天 | 18天 |
|
||
| 第八阶段 | 性能测试与优化 | 2天 | 20天 |
|
||
| 第九阶段 | 培训与文档 | 2天 | 22天 |
|
||
| 第十阶段 | 灰度发布与上线 | 3天 | 25天 |
|
||
|
||
**总工期:约 25 个工作日(5 周)**
|
||
|
||
### 7.2 里程碑
|
||
|
||
| 里程碑 | 日期 | 交付物 |
|
||
|--------|------|--------|
|
||
| M1 - 数据库完成 | 第1天 | 新表结构 + 迁移脚本 |
|
||
| M2 - 后端完成 | 第10天 | 重构后的 Java 代码 |
|
||
| M3 - 前端完成 | 第13天 | 新页面 |
|
||
| M4 - 测试完成 | 第18天 | 测试报告 |
|
||
| M5 - 上线 | 第25天 | 正式环境 |
|
||
|
||
---
|
||
|
||
## 八、测试方案
|
||
|
||
### 8.1 测试用例清单
|
||
|
||
| 模块 | 测试项 | 优先级 |
|
||
|------|--------|--------|
|
||
| 试卷管理 | 创建试卷 | P0 |
|
||
| 试卷管理 | 编辑试卷内容 | P0 |
|
||
| 试卷管理 | 删除试卷 | P1 |
|
||
| 考试管理 | 基于试卷创建考试 | P0 |
|
||
| 考试管理 | 修改考试时间 | P0 |
|
||
| 考试管理 | 发布/关闭考试 | P0 |
|
||
| 答题功能 | 学员答题 | P0 |
|
||
| 答题功能 | 答题记录关联考试 | P0 |
|
||
| 成绩管理 | 成绩统计 | P0 |
|
||
| 统计分析 | 考试通过率 | P1 |
|
||
| 复用场景 | 同一试卷创建多次考试 | P0 |
|
||
|
||
### 8.2 回归测试重点
|
||
|
||
- [ ] 原有考试功能不受影响
|
||
- [ ] 问卷功能不受影响
|
||
- [ ] 与培训计划的关联正常
|
||
- [ ] 报表数据准确
|
||
|
||
---
|
||
|
||
## 九、部署方案
|
||
|
||
### 9.1 环境规划
|
||
|
||
| 环境 | 用途 | 数据 |
|
||
|------|------|------|
|
||
| 开发环境 | 开发调试 | 脱敏测试数据 |
|
||
| 测试环境 | 功能测试 | 生产数据副本(脱敏) |
|
||
| 预生产环境 | 灰度验证 | 生产数据副本 |
|
||
| 生产环境 | 正式运行 | 生产数据 |
|
||
|
||
### 9.2 部署步骤
|
||
|
||
```bash
|
||
# 1. 备份生产数据库
|
||
./backup_prod.sh
|
||
|
||
# 2. 执行数据迁移脚本
|
||
mysql -u root -p etms < migrate_exam_paper.sql
|
||
|
||
# 3. 部署应用
|
||
./deploy.sh --env=prod --module=exam
|
||
|
||
# 4. 健康检查
|
||
curl http://exam-api/health
|
||
|
||
# 5. 监控告警
|
||
# 检查 error.log, slow_query_log
|
||
```
|
||
|
||
---
|
||
|
||
## 十、附录
|
||
|
||
### 10.1 字段类型对照表
|
||
|
||
| 原字段 | 新位置 | 新字段 | 类型变化 |
|
||
|--------|--------|--------|----------|
|
||
| exampaper.name | 试卷表 | name | 保留 |
|
||
| exampaper.startdate | 考试表 | starttime | datetime |
|
||
| exampaper.enddate | 考试表 | endtime | datetime |
|
||
| exampaper.sc | 考试表 | duration | int |
|
||
| exampaper.state | 试卷表 | state | 保留 |
|
||
| limitation.exam_id | 新表 | exam_id | 重命名 |
|
||
| usertest.exam_id | 同表 | exam_id | 新增 |
|
||
|
||
### 10.2 SQL 变更清单
|
||
|
||
```sql
|
||
-- 需要执行的 DDL(按顺序)
|
||
1. CREATE TABLE et_exam_paper
|
||
2. CREATE TABLE et_exam_examinations
|
||
3. CREATE TABLE et_exam_paper_question
|
||
4. CREATE TABLE et_exam_examination_limitation
|
||
5. ALTER TABLE et_exam_usertest ADD exam_id
|
||
6. INSERT INTO et_exam_paper (...)
|
||
7. INSERT INTO et_exam_examinations (...)
|
||
8. INSERT INTO et_exam_paper_question (...)
|
||
9. INSERT INTO et_exam_examination_limitation (...)
|
||
10. UPDATE et_exam_usertest SET exam_id = ...
|
||
11. CREATE INDEX idx_paper_id ON et_exam_paper_question(paper_id)
|
||
12. CREATE INDEX idx_exam_id ON et_exam_usertest(exam_id)
|
||
```
|
||
|
||
---
|
||
|
||
*文档版本:v1.0*
|
||
*创建时间:2026-04-16*
|
||
*作者:AI 助手*
|