feat: 实现 KPL 系统功能改进计划
Some checks failed
continuous-integration/drone/push Build is failing

1. 课程学习进度追踪
   - 新增 UserCourseProgress 和 UserMaterialProgress 模型
   - 新增 /api/v1/progress/* 进度追踪 API
   - 更新 admin.py 使用真实课程完成率数据

2. 路由权限检查完善
   - 新增前端 permissionChecker.ts 权限检查工具
   - 更新 router/guard.ts 实现团队和课程权限验证
   - 新增后端 permission_service.py

3. AI 陪练音频转文本
   - 新增 speech_recognition.py 语音识别服务
   - 新增 /api/v1/speech/* API
   - 更新 ai-practice-coze.vue 支持语音输入

4. 双人对练报告生成
   - 更新 practice_room_service.py 添加报告生成功能
   - 新增 /rooms/{room_code}/report API
   - 更新 duo-practice-report.vue 调用真实 API

5. 学习提醒推送
   - 新增 notification_service.py 通知服务
   - 新增 scheduler_service.py 定时任务服务
   - 支持钉钉、企微、站内消息推送

6. 智能学习推荐
   - 新增 recommendation_service.py 推荐服务
   - 新增 /api/v1/recommendations/* API
   - 支持错题、能力、进度、热门多维度推荐

7. 安全问题修复
   - DEBUG 默认值改为 False
   - 添加 SECRET_KEY 安全警告
   - 新增 check_security_settings() 检查函数

8. 证书 PDF 生成
   - 更新 certificate_service.py 添加 PDF 生成
   - 添加 weasyprint、Pillow、qrcode 依赖
   - 更新下载 API 支持 PDF 和 PNG 格式
This commit is contained in:
yuliang_guo
2026-01-30 14:22:35 +08:00
parent 9793013a56
commit 64f5d567fa
66 changed files with 18067 additions and 14330 deletions

View File

@@ -1,82 +1,82 @@
# 数据库迁移说明
本目录包含 KPL 考培练系统的数据库迁移脚本。
## 迁移脚本列表
| 脚本 | 说明 | 创建时间 |
|------|------|----------|
| `add_level_badge_system.sql` | 等级与奖章系统 | 2026-01-29 |
## 执行迁移
### 测试环境Docker
KPL 测试环境数据库在服务器 Docker 容器中运行:
```bash
# 1. SSH 登录 KPL 服务器
ssh root@<KPL服务器IP>
# 2. 进入项目目录
cd /www/wwwroot/kpl.ireborn.com.cn
# 3. 执行迁移(方法一:直接执行)
docker exec -i kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev < backend/migrations/add_level_badge_system.sql
# 或者(方法二:交互式执行)
docker exec -it kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev
# 然后复制粘贴 SQL 脚本内容执行
# 方法三从本地执行需要先上传SQL文件到服务器
# scp backend/migrations/add_level_badge_system.sql root@<服务器IP>:/tmp/
# ssh root@<服务器IP> "docker exec -i kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev < /tmp/add_level_badge_system.sql"
```
**注意**MySQL 容器密码为 `nj861021`(之前通过 `docker exec kpl-mysql-dev env | grep MYSQL` 确认)
### 生产环境
生产环境迁移前请确保:
1. 已备份数据库
2. 在低峰期执行
3. 测试环境验证通过
```bash
# 执行迁移(替换为实际的生产数据库配置)
mysql -h<host> -u<user> -p<password> <database> < backend/migrations/add_level_badge_system.sql
```
## 回滚方法
如需回滚,执行以下 SQL
```sql
DROP TABLE IF EXISTS user_badges;
DROP TABLE IF EXISTS badge_definitions;
DROP TABLE IF EXISTS exp_history;
DROP TABLE IF EXISTS level_configs;
DROP TABLE IF EXISTS user_levels;
```
## 验证迁移
执行以下查询验证表是否创建成功:
```sql
SHOW TABLES LIKE '%level%';
SHOW TABLES LIKE '%badge%';
SHOW TABLES LIKE '%exp%';
-- 查看表结构
DESCRIBE user_levels;
DESCRIBE exp_history;
DESCRIBE badge_definitions;
DESCRIBE user_badges;
DESCRIBE level_configs;
-- 验证初始数据
SELECT COUNT(*) FROM level_configs; -- 应该是 10 条
SELECT COUNT(*) FROM badge_definitions; -- 应该是 20 条
SELECT COUNT(*) FROM user_levels; -- 应该等于用户数
```
# 数据库迁移说明
本目录包含 KPL 考培练系统的数据库迁移脚本。
## 迁移脚本列表
| 脚本 | 说明 | 创建时间 |
|------|------|----------|
| `add_level_badge_system.sql` | 等级与奖章系统 | 2026-01-29 |
## 执行迁移
### 测试环境Docker
KPL 测试环境数据库在服务器 Docker 容器中运行:
```bash
# 1. SSH 登录 KPL 服务器
ssh root@<KPL服务器IP>
# 2. 进入项目目录
cd /www/wwwroot/kpl.ireborn.com.cn
# 3. 执行迁移(方法一:直接执行)
docker exec -i kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev < backend/migrations/add_level_badge_system.sql
# 或者(方法二:交互式执行)
docker exec -it kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev
# 然后复制粘贴 SQL 脚本内容执行
# 方法三从本地执行需要先上传SQL文件到服务器
# scp backend/migrations/add_level_badge_system.sql root@<服务器IP>:/tmp/
# ssh root@<服务器IP> "docker exec -i kpl-mysql-dev mysql -uroot -pnj861021 kpl_dev < /tmp/add_level_badge_system.sql"
```
**注意**MySQL 容器密码为 `nj861021`(之前通过 `docker exec kpl-mysql-dev env | grep MYSQL` 确认)
### 生产环境
生产环境迁移前请确保:
1. 已备份数据库
2. 在低峰期执行
3. 测试环境验证通过
```bash
# 执行迁移(替换为实际的生产数据库配置)
mysql -h<host> -u<user> -p<password> <database> < backend/migrations/add_level_badge_system.sql
```
## 回滚方法
如需回滚,执行以下 SQL
```sql
DROP TABLE IF EXISTS user_badges;
DROP TABLE IF EXISTS badge_definitions;
DROP TABLE IF EXISTS exp_history;
DROP TABLE IF EXISTS level_configs;
DROP TABLE IF EXISTS user_levels;
```
## 验证迁移
执行以下查询验证表是否创建成功:
```sql
SHOW TABLES LIKE '%level%';
SHOW TABLES LIKE '%badge%';
SHOW TABLES LIKE '%exp%';
-- 查看表结构
DESCRIBE user_levels;
DESCRIBE exp_history;
DESCRIBE badge_definitions;
DESCRIBE user_badges;
DESCRIBE level_configs;
-- 验证初始数据
SELECT COUNT(*) FROM level_configs; -- 应该是 10 条
SELECT COUNT(*) FROM badge_definitions; -- 应该是 20 条
SELECT COUNT(*) FROM user_levels; -- 应该等于用户数
```

View File

@@ -1,166 +1,166 @@
-- ================================================================
-- 证书系统数据库迁移脚本
-- 创建日期: 2026-01-29
-- 功能: 添加证书模板表和用户证书表
-- ================================================================
-- 事务开始
START TRANSACTION;
-- ================================================================
-- 1. 创建证书模板表
-- ================================================================
CREATE TABLE IF NOT EXISTS certificate_templates (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL COMMENT '模板名称',
type ENUM('course', 'exam', 'achievement') NOT NULL COMMENT '证书类型: course=课程结业, exam=考试合格, achievement=成就证书',
background_url VARCHAR(500) COMMENT '证书背景图URL',
template_html TEXT COMMENT 'HTML模板内容',
template_style TEXT COMMENT 'CSS样式',
is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用',
sort_order INT DEFAULT 0 COMMENT '排序顺序',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_type (type),
INDEX idx_is_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='证书模板表';
-- ================================================================
-- 2. 创建用户证书表
-- ================================================================
CREATE TABLE IF NOT EXISTS user_certificates (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL COMMENT '用户ID',
template_id INT NOT NULL COMMENT '模板ID',
certificate_no VARCHAR(50) UNIQUE NOT NULL COMMENT '证书编号 KPL-年份-序号',
title VARCHAR(200) NOT NULL COMMENT '证书标题',
description TEXT COMMENT '证书描述/成就说明',
issued_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '颁发时间',
valid_until DATETIME COMMENT '有效期至NULL表示永久',
-- 关联信息
course_id INT COMMENT '关联课程ID',
exam_id INT COMMENT '关联考试ID',
badge_id INT COMMENT '关联奖章ID',
-- 成绩信息
score DECIMAL(5,2) COMMENT '考试分数',
completion_rate DECIMAL(5,2) COMMENT '完成率',
-- 生成的文件
pdf_url VARCHAR(500) COMMENT 'PDF文件URL',
image_url VARCHAR(500) COMMENT '分享图片URL',
-- 元数据
meta_data JSON COMMENT '扩展元数据',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (template_id) REFERENCES certificate_templates(id),
INDEX idx_user_id (user_id),
INDEX idx_certificate_no (certificate_no),
INDEX idx_course_id (course_id),
INDEX idx_exam_id (exam_id),
INDEX idx_issued_at (issued_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户证书表';
-- ================================================================
-- 3. 插入默认证书模板
-- ================================================================
INSERT INTO certificate_templates (name, type, template_html, template_style, is_active, sort_order) VALUES
-- 课程结业证书模板
('课程结业证书', 'course',
'<div class="certificate">
<div class="header">
<div class="logo">考培练系统</div>
<h1>结业证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<p class="content">已完成课程《{{course_name}}》的全部学习内容</p>
<p class="completion">完成率:{{completion_rate}}%</p>
<p class="date">颁发日期:{{issue_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); font-family: "Microsoft YaHei", sans-serif; }
.header { text-align: center; margin-bottom: 30px; }
.header .logo { font-size: 24px; color: #667eea; font-weight: bold; }
.header h1 { font-size: 36px; color: #333; margin: 20px 0; }
.body { text-align: center; padding: 30px 60px; }
.body .recipient { font-size: 20px; margin-bottom: 20px; }
.body .content { font-size: 18px; color: #555; margin-bottom: 15px; }
.body .completion { font-size: 16px; color: #667eea; }
.body .date { font-size: 14px; color: #888; margin-top: 30px; }
.footer { display: flex; justify-content: space-between; align-items: center; margin-top: 40px; }
.cert-no { font-size: 12px; color: #999; }
.qrcode { width: 80px; height: 80px; }',
TRUE, 1),
-- 考试合格证书模板
('考试合格证书', 'exam',
'<div class="certificate exam-cert">
<div class="header">
<div class="logo">考培练系统</div>
<h1>考试合格证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<p class="content">在《{{exam_name}}》考试中成绩合格</p>
<div class="score-badge">
<span class="score">{{score}}</span>
<span class="unit">分</span>
</div>
<p class="date">考试日期:{{exam_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate.exam-cert { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); font-family: "Microsoft YaHei", sans-serif; }
.exam-cert .header h1 { color: #2e7d32; }
.score-badge { display: inline-block; padding: 20px 40px; background: #fff; border-radius: 50%; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 20px 0; }
.score-badge .score { font-size: 48px; font-weight: bold; color: #2e7d32; }
.score-badge .unit { font-size: 18px; color: #666; }',
TRUE, 2),
-- 成就证书模板
('成就证书', 'achievement',
'<div class="certificate achievement-cert">
<div class="header">
<div class="logo">考培练系统</div>
<h1>成就证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<div class="achievement-icon">{{badge_icon}}</div>
<p class="achievement-name">{{badge_name}}</p>
<p class="achievement-desc">{{badge_description}}</p>
<p class="date">获得日期:{{achieve_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate.achievement-cert { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); font-family: "Microsoft YaHei", sans-serif; }
.achievement-cert .header h1 { color: #e65100; }
.achievement-icon { font-size: 64px; margin: 20px 0; }
.achievement-name { font-size: 24px; font-weight: bold; color: #e65100; margin: 10px 0; }
.achievement-desc { font-size: 16px; color: #666; }',
TRUE, 3);
-- 提交事务
COMMIT;
-- ================================================================
-- 验证脚本
-- ================================================================
-- SELECT * FROM certificate_templates;
-- SELECT COUNT(*) AS template_count FROM certificate_templates;
-- ================================================================
-- 证书系统数据库迁移脚本
-- 创建日期: 2026-01-29
-- 功能: 添加证书模板表和用户证书表
-- ================================================================
-- 事务开始
START TRANSACTION;
-- ================================================================
-- 1. 创建证书模板表
-- ================================================================
CREATE TABLE IF NOT EXISTS certificate_templates (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL COMMENT '模板名称',
type ENUM('course', 'exam', 'achievement') NOT NULL COMMENT '证书类型: course=课程结业, exam=考试合格, achievement=成就证书',
background_url VARCHAR(500) COMMENT '证书背景图URL',
template_html TEXT COMMENT 'HTML模板内容',
template_style TEXT COMMENT 'CSS样式',
is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用',
sort_order INT DEFAULT 0 COMMENT '排序顺序',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_type (type),
INDEX idx_is_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='证书模板表';
-- ================================================================
-- 2. 创建用户证书表
-- ================================================================
CREATE TABLE IF NOT EXISTS user_certificates (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL COMMENT '用户ID',
template_id INT NOT NULL COMMENT '模板ID',
certificate_no VARCHAR(50) UNIQUE NOT NULL COMMENT '证书编号 KPL-年份-序号',
title VARCHAR(200) NOT NULL COMMENT '证书标题',
description TEXT COMMENT '证书描述/成就说明',
issued_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '颁发时间',
valid_until DATETIME COMMENT '有效期至NULL表示永久',
-- 关联信息
course_id INT COMMENT '关联课程ID',
exam_id INT COMMENT '关联考试ID',
badge_id INT COMMENT '关联奖章ID',
-- 成绩信息
score DECIMAL(5,2) COMMENT '考试分数',
completion_rate DECIMAL(5,2) COMMENT '完成率',
-- 生成的文件
pdf_url VARCHAR(500) COMMENT 'PDF文件URL',
image_url VARCHAR(500) COMMENT '分享图片URL',
-- 元数据
meta_data JSON COMMENT '扩展元数据',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (template_id) REFERENCES certificate_templates(id),
INDEX idx_user_id (user_id),
INDEX idx_certificate_no (certificate_no),
INDEX idx_course_id (course_id),
INDEX idx_exam_id (exam_id),
INDEX idx_issued_at (issued_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户证书表';
-- ================================================================
-- 3. 插入默认证书模板
-- ================================================================
INSERT INTO certificate_templates (name, type, template_html, template_style, is_active, sort_order) VALUES
-- 课程结业证书模板
('课程结业证书', 'course',
'<div class="certificate">
<div class="header">
<div class="logo">考培练系统</div>
<h1>结业证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<p class="content">已完成课程《{{course_name}}》的全部学习内容</p>
<p class="completion">完成率:{{completion_rate}}%</p>
<p class="date">颁发日期:{{issue_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); font-family: "Microsoft YaHei", sans-serif; }
.header { text-align: center; margin-bottom: 30px; }
.header .logo { font-size: 24px; color: #667eea; font-weight: bold; }
.header h1 { font-size: 36px; color: #333; margin: 20px 0; }
.body { text-align: center; padding: 30px 60px; }
.body .recipient { font-size: 20px; margin-bottom: 20px; }
.body .content { font-size: 18px; color: #555; margin-bottom: 15px; }
.body .completion { font-size: 16px; color: #667eea; }
.body .date { font-size: 14px; color: #888; margin-top: 30px; }
.footer { display: flex; justify-content: space-between; align-items: center; margin-top: 40px; }
.cert-no { font-size: 12px; color: #999; }
.qrcode { width: 80px; height: 80px; }',
TRUE, 1),
-- 考试合格证书模板
('考试合格证书', 'exam',
'<div class="certificate exam-cert">
<div class="header">
<div class="logo">考培练系统</div>
<h1>考试合格证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<p class="content">在《{{exam_name}}》考试中成绩合格</p>
<div class="score-badge">
<span class="score">{{score}}</span>
<span class="unit">分</span>
</div>
<p class="date">考试日期:{{exam_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate.exam-cert { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); font-family: "Microsoft YaHei", sans-serif; }
.exam-cert .header h1 { color: #2e7d32; }
.score-badge { display: inline-block; padding: 20px 40px; background: #fff; border-radius: 50%; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 20px 0; }
.score-badge .score { font-size: 48px; font-weight: bold; color: #2e7d32; }
.score-badge .unit { font-size: 18px; color: #666; }',
TRUE, 2),
-- 成就证书模板
('成就证书', 'achievement',
'<div class="certificate achievement-cert">
<div class="header">
<div class="logo">考培练系统</div>
<h1>成就证书</h1>
</div>
<div class="body">
<p class="recipient">兹证明 <strong>{{user_name}}</strong></p>
<div class="achievement-icon">{{badge_icon}}</div>
<p class="achievement-name">{{badge_name}}</p>
<p class="achievement-desc">{{badge_description}}</p>
<p class="date">获得日期:{{achieve_date}}</p>
</div>
<div class="footer">
<div class="cert-no">证书编号:{{certificate_no}}</div>
<div class="qrcode">{{qrcode}}</div>
</div>
</div>',
'.certificate.achievement-cert { width: 800px; height: 600px; padding: 40px; background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); font-family: "Microsoft YaHei", sans-serif; }
.achievement-cert .header h1 { color: #e65100; }
.achievement-icon { font-size: 64px; margin: 20px 0; }
.achievement-name { font-size: 24px; font-weight: bold; color: #e65100; margin: 10px 0; }
.achievement-desc { font-size: 16px; color: #666; }',
TRUE, 3);
-- 提交事务
COMMIT;
-- ================================================================
-- 验证脚本
-- ================================================================
-- SELECT * FROM certificate_templates;
-- SELECT COUNT(*) AS template_count FROM certificate_templates;

View File

@@ -1,41 +1,41 @@
-- =====================================================
-- 钉钉免密登录功能 - 数据库迁移脚本
-- 创建时间: 2026-01-28
-- 说明: 为考培练系统添加钉钉免密登录支持
-- =====================================================
-- 1. 用户表添加 dingtalk_id 字段
-- -----------------------------------------------------
ALTER TABLE users ADD COLUMN dingtalk_id VARCHAR(64) UNIQUE COMMENT '钉钉用户ID';
CREATE INDEX idx_users_dingtalk_id ON users(dingtalk_id);
-- 2. 配置模板表添加钉钉配置项
-- -----------------------------------------------------
INSERT INTO config_templates (config_group, config_key, display_name, description, value_type, is_required, is_secret, sort_order) VALUES
('dingtalk', 'DINGTALK_APP_KEY', 'AppKey', '钉钉应用的AppKey从钉钉开放平台获取', 'string', 1, 0, 1),
('dingtalk', 'DINGTALK_APP_SECRET', 'AppSecret', '钉钉应用的AppSecret敏感信息', 'string', 1, 1, 2),
('dingtalk', 'DINGTALK_AGENT_ID', 'AgentId', '钉钉应用的AgentId', 'string', 1, 0, 3),
('dingtalk', 'DINGTALK_CORP_ID', 'CorpId', '钉钉企业的CorpId', 'string', 1, 0, 4);
-- 3. 功能开关表添加钉钉免密登录开关(默认禁用)
-- -----------------------------------------------------
INSERT INTO feature_switches (tenant_id, feature_code, feature_name, feature_group, is_enabled, description) VALUES
(NULL, 'dingtalk_login', '钉钉免密登录', 'auth', 0, '启用后,用户可通过钉钉免密登录系统');
-- =====================================================
-- 回滚脚本如需回滚执行以下SQL
-- =====================================================
/*
-- 回滚步骤1: 删除功能开关
DELETE FROM feature_switches WHERE feature_code = 'dingtalk_login';
-- 回滚步骤2: 删除配置模板
DELETE FROM config_templates WHERE config_group = 'dingtalk';
-- 回滚步骤3: 删除用户表字段
ALTER TABLE users DROP INDEX idx_users_dingtalk_id;
ALTER TABLE users DROP COLUMN dingtalk_id;
*/
-- =====================================================
-- 钉钉免密登录功能 - 数据库迁移脚本
-- 创建时间: 2026-01-28
-- 说明: 为考培练系统添加钉钉免密登录支持
-- =====================================================
-- 1. 用户表添加 dingtalk_id 字段
-- -----------------------------------------------------
ALTER TABLE users ADD COLUMN dingtalk_id VARCHAR(64) UNIQUE COMMENT '钉钉用户ID';
CREATE INDEX idx_users_dingtalk_id ON users(dingtalk_id);
-- 2. 配置模板表添加钉钉配置项
-- -----------------------------------------------------
INSERT INTO config_templates (config_group, config_key, display_name, description, value_type, is_required, is_secret, sort_order) VALUES
('dingtalk', 'DINGTALK_APP_KEY', 'AppKey', '钉钉应用的AppKey从钉钉开放平台获取', 'string', 1, 0, 1),
('dingtalk', 'DINGTALK_APP_SECRET', 'AppSecret', '钉钉应用的AppSecret敏感信息', 'string', 1, 1, 2),
('dingtalk', 'DINGTALK_AGENT_ID', 'AgentId', '钉钉应用的AgentId', 'string', 1, 0, 3),
('dingtalk', 'DINGTALK_CORP_ID', 'CorpId', '钉钉企业的CorpId', 'string', 1, 0, 4);
-- 3. 功能开关表添加钉钉免密登录开关(默认禁用)
-- -----------------------------------------------------
INSERT INTO feature_switches (tenant_id, feature_code, feature_name, feature_group, is_enabled, description) VALUES
(NULL, 'dingtalk_login', '钉钉免密登录', 'auth', 0, '启用后,用户可通过钉钉免密登录系统');
-- =====================================================
-- 回滚脚本如需回滚执行以下SQL
-- =====================================================
/*
-- 回滚步骤1: 删除功能开关
DELETE FROM feature_switches WHERE feature_code = 'dingtalk_login';
-- 回滚步骤2: 删除配置模板
DELETE FROM config_templates WHERE config_group = 'dingtalk';
-- 回滚步骤3: 删除用户表字段
ALTER TABLE users DROP INDEX idx_users_dingtalk_id;
ALTER TABLE users DROP COLUMN dingtalk_id;
*/

View File

@@ -1,192 +1,192 @@
-- =====================================================
-- 等级与奖章系统数据库迁移脚本
-- 版本: 1.0.0
-- 创建时间: 2026-01-29
-- 说明: 添加用户等级系统和奖章系统相关表
-- =====================================================
-- 使用事务确保原子性
START TRANSACTION;
-- =====================================================
-- 1. 用户等级表 (user_levels)
-- 存储用户的等级和经验值信息
-- =====================================================
CREATE TABLE IF NOT EXISTS user_levels (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
level INT NOT NULL DEFAULT 1 COMMENT '当前等级',
exp INT NOT NULL DEFAULT 0 COMMENT '当前经验值',
total_exp INT NOT NULL DEFAULT 0 COMMENT '累计获得经验值',
login_streak INT NOT NULL DEFAULT 0 COMMENT '连续登录天数',
max_login_streak INT NOT NULL DEFAULT 0 COMMENT '历史最长连续登录天数',
last_login_date DATE NULL COMMENT '最后登录日期(用于计算连续登录)',
last_checkin_at DATETIME NULL COMMENT '最后签到时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_id (user_id),
INDEX idx_level (level),
INDEX idx_total_exp (total_exp),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户等级表';
-- =====================================================
-- 2. 经验值历史表 (exp_history)
-- 记录每次经验值变化的详细信息
-- =====================================================
CREATE TABLE IF NOT EXISTS exp_history (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
exp_change INT NOT NULL COMMENT '经验值变化(正为获得,负为扣除)',
exp_type VARCHAR(50) NOT NULL COMMENT '类型exam/practice/training/task/login/badge/other',
source_id INT NULL COMMENT '来源记录ID如考试ID、练习ID等',
description VARCHAR(255) NOT NULL COMMENT '描述',
level_before INT NULL COMMENT '变化前等级',
level_after INT NULL COMMENT '变化后等级',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_exp_type (exp_type),
INDEX idx_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经验值历史表';
-- =====================================================
-- 3. 奖章定义表 (badge_definitions)
-- 定义所有可获得的奖章及其解锁条件
-- =====================================================
CREATE TABLE IF NOT EXISTS badge_definitions (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) NOT NULL COMMENT '奖章编码(唯一标识)',
name VARCHAR(100) NOT NULL COMMENT '奖章名称',
description VARCHAR(255) NOT NULL COMMENT '奖章描述',
icon VARCHAR(100) NOT NULL DEFAULT 'Medal' COMMENT '图标名称Element Plus 图标)',
category VARCHAR(50) NOT NULL COMMENT '分类learning/exam/practice/streak/special',
condition_type VARCHAR(50) NOT NULL COMMENT '条件类型count/score/streak/level/duration',
condition_field VARCHAR(100) NULL COMMENT '条件字段(用于复杂条件)',
condition_value INT NOT NULL DEFAULT 1 COMMENT '条件数值',
exp_reward INT NOT NULL DEFAULT 0 COMMENT '解锁奖励经验值',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序顺序',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_code (code),
INDEX idx_category (category),
INDEX idx_is_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='奖章定义表';
-- =====================================================
-- 4. 用户奖章表 (user_badges)
-- 记录用户已解锁的奖章
-- =====================================================
CREATE TABLE IF NOT EXISTS user_badges (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
badge_id INT NOT NULL COMMENT '奖章ID',
unlocked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '解锁时间',
is_notified TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已通知用户',
notified_at DATETIME NULL COMMENT '通知时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_badge (user_id, badge_id),
INDEX idx_user_id (user_id),
INDEX idx_badge_id (badge_id),
INDEX idx_unlocked_at (unlocked_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (badge_id) REFERENCES badge_definitions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户奖章表';
-- =====================================================
-- 5. 等级配置表 (level_configs)
-- 定义每个等级所需的经验值和称号
-- =====================================================
CREATE TABLE IF NOT EXISTS level_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
level INT NOT NULL COMMENT '等级',
exp_required INT NOT NULL COMMENT '升到此级所需经验值',
total_exp_required INT NOT NULL COMMENT '累计所需经验值',
title VARCHAR(50) NOT NULL COMMENT '等级称号',
color VARCHAR(20) NULL COMMENT '等级颜色(十六进制)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='等级配置表';
-- =====================================================
-- 6. 插入等级配置数据
-- =====================================================
INSERT INTO level_configs (level, exp_required, total_exp_required, title, color) VALUES
(1, 0, 0, '初学者', '#909399'),
(2, 100, 100, '入门学徒', '#67C23A'),
(3, 200, 300, '勤奋学员', '#67C23A'),
(4, 400, 700, '进阶学员', '#409EFF'),
(5, 600, 1300, '优秀学员', '#409EFF'),
(6, 1000, 2300, '精英学员', '#E6A23C'),
(7, 1500, 3800, '资深学员', '#E6A23C'),
(8, 2000, 5800, '学习达人', '#F56C6C'),
(9, 3000, 8800, '学霸', '#F56C6C'),
(10, 5000, 13800, '大师', '#9B59B6');
-- =====================================================
-- 7. 插入奖章定义数据
-- =====================================================
-- 7.1 学习进度类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('first_login', '初来乍到', '首次登录系统', 'Star', 'learning', 'count', 'login_count', 1, 10, 101),
('course_1', '求知若渴', '完成1门课程学习', 'Reading', 'learning', 'count', 'course_completed', 1, 30, 102),
('course_5', '博学多才', '完成5门课程学习', 'Collection', 'learning', 'count', 'course_completed', 5, 100, 103),
('course_10', '学识渊博', '完成10门课程学习', 'Files', 'learning', 'count', 'course_completed', 10, 200, 104);
-- 7.2 考试成绩类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('exam_pass_1', '初试牛刀', '通过1次考试', 'Select', 'exam', 'count', 'exam_passed', 1, 20, 201),
('exam_pass_10', '身经百战', '通过10次考试', 'Finished', 'exam', 'count', 'exam_passed', 10, 100, 202),
('exam_pass_50', '考试达人', '通过50次考试', 'Trophy', 'exam', 'count', 'exam_passed', 50, 300, 203),
('exam_perfect', '完美答卷', '考试获得满分', 'Medal', 'exam', 'score', 'exam_perfect_count', 1, 150, 204),
('exam_excellent_10', '学霸之路', '10次考试90分以上', 'TrendCharts', 'exam', 'count', 'exam_excellent', 10, 200, 205);
-- 7.3 练习时长类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('practice_1h', '初窥门径', '累计练习1小时', 'Clock', 'practice', 'duration', 'practice_hours', 1, 30, 301),
('practice_10h', '勤学苦练', '累计练习10小时', 'Timer', 'practice', 'duration', 'practice_hours', 10, 100, 302),
('practice_50h', '炉火纯青', '累计练习50小时', 'Stopwatch', 'practice', 'duration', 'practice_hours', 50, 300, 303),
('practice_100', '练习狂人', '完成100次练习', 'Operation', 'practice', 'count', 'practice_count', 100, 200, 304);
-- 7.4 连续打卡类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('streak_3', '小试身手', '连续登录3天', 'Calendar', 'streak', 'streak', 'login_streak', 3, 20, 401),
('streak_7', '坚持一周', '连续登录7天', 'Calendar', 'streak', 'streak', 'login_streak', 7, 50, 402),
('streak_30', '持之以恒', '连续登录30天', 'Calendar', 'streak', 'streak', 'login_streak', 30, 200, 403),
('streak_100', '百日不懈', '连续登录100天', 'Calendar', 'streak', 'streak', 'login_streak', 100, 500, 404);
-- 7.5 特殊成就类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('level_5', '初露锋芒', '达到5级', 'Rank', 'special', 'level', 'user_level', 5, 100, 501),
('level_10', '登峰造极', '达到满级', 'Crown', 'special', 'level', 'user_level', 10, 500, 502),
('training_master', '陪练大师', '完成50次陪练', 'Headset', 'special', 'count', 'training_count', 50, 300, 503),
('first_perfect_practice', '首次完美', '陪练首次获得90分以上', 'StarFilled', 'special', 'score', 'first_practice_90', 1, 100, 504);
-- =====================================================
-- 8. 为现有用户初始化等级数据
-- =====================================================
INSERT INTO user_levels (user_id, level, exp, total_exp, login_streak, last_login_date)
SELECT
id as user_id,
1 as level,
0 as exp,
0 as total_exp,
0 as login_streak,
NULL as last_login_date
FROM users
WHERE is_deleted = 0
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP;
-- 提交事务
COMMIT;
-- =====================================================
-- 回滚脚本(如需回滚,执行以下语句)
-- =====================================================
-- DROP TABLE IF EXISTS user_badges;
-- DROP TABLE IF EXISTS badge_definitions;
-- DROP TABLE IF EXISTS exp_history;
-- DROP TABLE IF EXISTS level_configs;
-- DROP TABLE IF EXISTS user_levels;
-- =====================================================
-- 等级与奖章系统数据库迁移脚本
-- 版本: 1.0.0
-- 创建时间: 2026-01-29
-- 说明: 添加用户等级系统和奖章系统相关表
-- =====================================================
-- 使用事务确保原子性
START TRANSACTION;
-- =====================================================
-- 1. 用户等级表 (user_levels)
-- 存储用户的等级和经验值信息
-- =====================================================
CREATE TABLE IF NOT EXISTS user_levels (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
level INT NOT NULL DEFAULT 1 COMMENT '当前等级',
exp INT NOT NULL DEFAULT 0 COMMENT '当前经验值',
total_exp INT NOT NULL DEFAULT 0 COMMENT '累计获得经验值',
login_streak INT NOT NULL DEFAULT 0 COMMENT '连续登录天数',
max_login_streak INT NOT NULL DEFAULT 0 COMMENT '历史最长连续登录天数',
last_login_date DATE NULL COMMENT '最后登录日期(用于计算连续登录)',
last_checkin_at DATETIME NULL COMMENT '最后签到时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_id (user_id),
INDEX idx_level (level),
INDEX idx_total_exp (total_exp),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户等级表';
-- =====================================================
-- 2. 经验值历史表 (exp_history)
-- 记录每次经验值变化的详细信息
-- =====================================================
CREATE TABLE IF NOT EXISTS exp_history (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
exp_change INT NOT NULL COMMENT '经验值变化(正为获得,负为扣除)',
exp_type VARCHAR(50) NOT NULL COMMENT '类型exam/practice/training/task/login/badge/other',
source_id INT NULL COMMENT '来源记录ID如考试ID、练习ID等',
description VARCHAR(255) NOT NULL COMMENT '描述',
level_before INT NULL COMMENT '变化前等级',
level_after INT NULL COMMENT '变化后等级',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_exp_type (exp_type),
INDEX idx_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经验值历史表';
-- =====================================================
-- 3. 奖章定义表 (badge_definitions)
-- 定义所有可获得的奖章及其解锁条件
-- =====================================================
CREATE TABLE IF NOT EXISTS badge_definitions (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) NOT NULL COMMENT '奖章编码(唯一标识)',
name VARCHAR(100) NOT NULL COMMENT '奖章名称',
description VARCHAR(255) NOT NULL COMMENT '奖章描述',
icon VARCHAR(100) NOT NULL DEFAULT 'Medal' COMMENT '图标名称Element Plus 图标)',
category VARCHAR(50) NOT NULL COMMENT '分类learning/exam/practice/streak/special',
condition_type VARCHAR(50) NOT NULL COMMENT '条件类型count/score/streak/level/duration',
condition_field VARCHAR(100) NULL COMMENT '条件字段(用于复杂条件)',
condition_value INT NOT NULL DEFAULT 1 COMMENT '条件数值',
exp_reward INT NOT NULL DEFAULT 0 COMMENT '解锁奖励经验值',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序顺序',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_code (code),
INDEX idx_category (category),
INDEX idx_is_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='奖章定义表';
-- =====================================================
-- 4. 用户奖章表 (user_badges)
-- 记录用户已解锁的奖章
-- =====================================================
CREATE TABLE IF NOT EXISTS user_badges (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
badge_id INT NOT NULL COMMENT '奖章ID',
unlocked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '解锁时间',
is_notified TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已通知用户',
notified_at DATETIME NULL COMMENT '通知时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_badge (user_id, badge_id),
INDEX idx_user_id (user_id),
INDEX idx_badge_id (badge_id),
INDEX idx_unlocked_at (unlocked_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (badge_id) REFERENCES badge_definitions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户奖章表';
-- =====================================================
-- 5. 等级配置表 (level_configs)
-- 定义每个等级所需的经验值和称号
-- =====================================================
CREATE TABLE IF NOT EXISTS level_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
level INT NOT NULL COMMENT '等级',
exp_required INT NOT NULL COMMENT '升到此级所需经验值',
total_exp_required INT NOT NULL COMMENT '累计所需经验值',
title VARCHAR(50) NOT NULL COMMENT '等级称号',
color VARCHAR(20) NULL COMMENT '等级颜色(十六进制)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='等级配置表';
-- =====================================================
-- 6. 插入等级配置数据
-- =====================================================
INSERT INTO level_configs (level, exp_required, total_exp_required, title, color) VALUES
(1, 0, 0, '初学者', '#909399'),
(2, 100, 100, '入门学徒', '#67C23A'),
(3, 200, 300, '勤奋学员', '#67C23A'),
(4, 400, 700, '进阶学员', '#409EFF'),
(5, 600, 1300, '优秀学员', '#409EFF'),
(6, 1000, 2300, '精英学员', '#E6A23C'),
(7, 1500, 3800, '资深学员', '#E6A23C'),
(8, 2000, 5800, '学习达人', '#F56C6C'),
(9, 3000, 8800, '学霸', '#F56C6C'),
(10, 5000, 13800, '大师', '#9B59B6');
-- =====================================================
-- 7. 插入奖章定义数据
-- =====================================================
-- 7.1 学习进度类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('first_login', '初来乍到', '首次登录系统', 'Star', 'learning', 'count', 'login_count', 1, 10, 101),
('course_1', '求知若渴', '完成1门课程学习', 'Reading', 'learning', 'count', 'course_completed', 1, 30, 102),
('course_5', '博学多才', '完成5门课程学习', 'Collection', 'learning', 'count', 'course_completed', 5, 100, 103),
('course_10', '学识渊博', '完成10门课程学习', 'Files', 'learning', 'count', 'course_completed', 10, 200, 104);
-- 7.2 考试成绩类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('exam_pass_1', '初试牛刀', '通过1次考试', 'Select', 'exam', 'count', 'exam_passed', 1, 20, 201),
('exam_pass_10', '身经百战', '通过10次考试', 'Finished', 'exam', 'count', 'exam_passed', 10, 100, 202),
('exam_pass_50', '考试达人', '通过50次考试', 'Trophy', 'exam', 'count', 'exam_passed', 50, 300, 203),
('exam_perfect', '完美答卷', '考试获得满分', 'Medal', 'exam', 'score', 'exam_perfect_count', 1, 150, 204),
('exam_excellent_10', '学霸之路', '10次考试90分以上', 'TrendCharts', 'exam', 'count', 'exam_excellent', 10, 200, 205);
-- 7.3 练习时长类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('practice_1h', '初窥门径', '累计练习1小时', 'Clock', 'practice', 'duration', 'practice_hours', 1, 30, 301),
('practice_10h', '勤学苦练', '累计练习10小时', 'Timer', 'practice', 'duration', 'practice_hours', 10, 100, 302),
('practice_50h', '炉火纯青', '累计练习50小时', 'Stopwatch', 'practice', 'duration', 'practice_hours', 50, 300, 303),
('practice_100', '练习狂人', '完成100次练习', 'Operation', 'practice', 'count', 'practice_count', 100, 200, 304);
-- 7.4 连续打卡类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('streak_3', '小试身手', '连续登录3天', 'Calendar', 'streak', 'streak', 'login_streak', 3, 20, 401),
('streak_7', '坚持一周', '连续登录7天', 'Calendar', 'streak', 'streak', 'login_streak', 7, 50, 402),
('streak_30', '持之以恒', '连续登录30天', 'Calendar', 'streak', 'streak', 'login_streak', 30, 200, 403),
('streak_100', '百日不懈', '连续登录100天', 'Calendar', 'streak', 'streak', 'login_streak', 100, 500, 404);
-- 7.5 特殊成就类奖章
INSERT INTO badge_definitions (code, name, description, icon, category, condition_type, condition_field, condition_value, exp_reward, sort_order) VALUES
('level_5', '初露锋芒', '达到5级', 'Rank', 'special', 'level', 'user_level', 5, 100, 501),
('level_10', '登峰造极', '达到满级', 'Crown', 'special', 'level', 'user_level', 10, 500, 502),
('training_master', '陪练大师', '完成50次陪练', 'Headset', 'special', 'count', 'training_count', 50, 300, 503),
('first_perfect_practice', '首次完美', '陪练首次获得90分以上', 'StarFilled', 'special', 'score', 'first_practice_90', 1, 100, 504);
-- =====================================================
-- 8. 为现有用户初始化等级数据
-- =====================================================
INSERT INTO user_levels (user_id, level, exp, total_exp, login_streak, last_login_date)
SELECT
id as user_id,
1 as level,
0 as exp,
0 as total_exp,
0 as login_streak,
NULL as last_login_date
FROM users
WHERE is_deleted = 0
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP;
-- 提交事务
COMMIT;
-- =====================================================
-- 回滚脚本(如需回滚,执行以下语句)
-- =====================================================
-- DROP TABLE IF EXISTS user_badges;
-- DROP TABLE IF EXISTS badge_definitions;
-- DROP TABLE IF EXISTS exp_history;
-- DROP TABLE IF EXISTS level_configs;
-- DROP TABLE IF EXISTS user_levels;

View File

@@ -1,186 +1,186 @@
-- ============================================================================
-- 双人对练功能数据库迁移脚本
-- 版本: 2026-01-28
-- 功能: 新增对练房间表,扩展现有表支持多人对练
-- ============================================================================
-- ============================================================================
-- 1. 创建对练房间表 practice_rooms
-- ============================================================================
CREATE TABLE IF NOT EXISTS `practice_rooms` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '房间ID',
`room_code` VARCHAR(10) NOT NULL UNIQUE COMMENT '6位邀请码',
`room_name` VARCHAR(200) COMMENT '房间名称',
-- 场景信息
`scene_id` INT COMMENT '关联场景ID',
`scene_name` VARCHAR(200) COMMENT '场景名称',
`scene_type` VARCHAR(50) COMMENT '场景类型',
`scene_background` TEXT COMMENT '场景背景',
-- 角色设置
`role_a_name` VARCHAR(50) DEFAULT '角色A' COMMENT '角色A名称如销售顾问',
`role_b_name` VARCHAR(50) DEFAULT '角色B' COMMENT '角色B名称如顾客',
`role_a_description` TEXT COMMENT '角色A描述',
`role_b_description` TEXT COMMENT '角色B描述',
-- 参与者信息
`host_user_id` INT NOT NULL COMMENT '房主用户ID',
`guest_user_id` INT COMMENT '加入者用户ID',
`host_role` VARCHAR(10) DEFAULT 'A' COMMENT '房主选择的角色(A/B)',
`max_participants` INT DEFAULT 2 COMMENT '最大参与人数',
-- 状态和时间
`status` VARCHAR(20) DEFAULT 'waiting' COMMENT '状态: waiting/ready/practicing/completed/canceled',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`started_at` DATETIME COMMENT '开始时间',
`ended_at` DATETIME COMMENT '结束时间',
`duration_seconds` INT DEFAULT 0 COMMENT '对练时长(秒)',
-- 对话统计
`total_turns` INT DEFAULT 0 COMMENT '总对话轮次',
`role_a_turns` INT DEFAULT 0 COMMENT '角色A发言次数',
`role_b_turns` INT DEFAULT 0 COMMENT '角色B发言次数',
-- 软删除
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除',
`deleted_at` DATETIME COMMENT '删除时间',
-- 索引
INDEX `idx_room_code` (`room_code`),
INDEX `idx_host_user` (`host_user_id`),
INDEX `idx_guest_user` (`guest_user_id`),
INDEX `idx_status` (`status`),
INDEX `idx_created_at` (`created_at`),
-- 外键(可选,根据实际需求决定是否启用)
-- FOREIGN KEY (`scene_id`) REFERENCES `practice_scenes`(`id`) ON DELETE SET NULL,
-- FOREIGN KEY (`host_user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
-- FOREIGN KEY (`guest_user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
CONSTRAINT `chk_host_role` CHECK (`host_role` IN ('A', 'B'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='双人对练房间表';
-- ============================================================================
-- 2. 扩展对话记录表 practice_dialogues
-- ============================================================================
-- 添加用户ID字段区分说话人
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `user_id` INT COMMENT '说话人用户ID' AFTER `speaker`;
-- 添加角色名称字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `role_name` VARCHAR(50) COMMENT '角色名称(如销售顾问/顾客)' AFTER `user_id`;
-- 添加房间ID字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `session_id`;
-- 添加消息类型字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `message_type` VARCHAR(20) DEFAULT 'text' COMMENT '消息类型: text/voice/system' AFTER `content`;
-- 添加索引
ALTER TABLE `practice_dialogues`
ADD INDEX IF NOT EXISTS `idx_user_id` (`user_id`),
ADD INDEX IF NOT EXISTS `idx_room_id` (`room_id`);
-- ============================================================================
-- 3. 扩展会话表 practice_sessions
-- ============================================================================
-- 添加房间ID字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `scene_type`;
-- 添加参与者角色字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `participant_role` VARCHAR(50) COMMENT '用户在房间中的角色' AFTER `room_id`;
-- 添加会话类型字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `session_type` VARCHAR(20) DEFAULT 'solo' COMMENT '会话类型: solo/duo' AFTER `participant_role`;
-- 添加索引
ALTER TABLE `practice_sessions`
ADD INDEX IF NOT EXISTS `idx_room_id` (`room_id`);
-- ============================================================================
-- 4. 扩展报告表 practice_reports支持双人报告
-- ============================================================================
-- 添加房间ID字段
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `session_id`;
-- 添加用户ID字段双人模式下每人一份报告
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `user_id` INT COMMENT '用户ID' AFTER `room_id`;
-- 添加报告类型字段
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `report_type` VARCHAR(20) DEFAULT 'solo' COMMENT '报告类型: solo/duo' AFTER `user_id`;
-- 添加对方评价字段(双人模式)
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `partner_feedback` JSON COMMENT '对方表现评价' AFTER `suggestions`;
-- 添加互动质量评分
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `interaction_score` INT COMMENT '互动质量评分0-100' AFTER `partner_feedback`;
-- 修改唯一索引允许同一session有多个报告
-- 注意:需要先删除旧的唯一索引
-- ALTER TABLE `practice_reports` DROP INDEX `session_id`;
-- ALTER TABLE `practice_reports` ADD UNIQUE INDEX `idx_session_user` (`session_id`, `user_id`);
-- ============================================================================
-- 5. 创建房间消息表(用于实时同步)
-- ============================================================================
CREATE TABLE IF NOT EXISTS `practice_room_messages` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID',
`room_id` INT NOT NULL COMMENT '房间ID',
`user_id` INT COMMENT '发送者用户ID系统消息为NULL',
`message_type` VARCHAR(20) NOT NULL COMMENT '消息类型: chat/system/join/leave/start/end',
`content` TEXT COMMENT '消息内容',
`role_name` VARCHAR(50) COMMENT '角色名称',
`sequence` INT NOT NULL COMMENT '消息序号',
`created_at` DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间(毫秒精度)',
INDEX `idx_room_id` (`room_id`),
INDEX `idx_room_sequence` (`room_id`, `sequence`),
INDEX `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间实时消息表';
-- ============================================================================
-- 回滚脚本(如需回滚,执行以下语句)
-- ============================================================================
/*
-- 删除新增的表
DROP TABLE IF EXISTS `practice_room_messages`;
DROP TABLE IF EXISTS `practice_rooms`;
-- 删除 practice_dialogues 新增的列
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `user_id`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `role_name`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `message_type`;
-- 删除 practice_sessions 新增的列
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `participant_role`;
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `session_type`;
-- 删除 practice_reports 新增的列
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `user_id`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `report_type`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `partner_feedback`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `interaction_score`;
*/
-- ============================================================================
-- 双人对练功能数据库迁移脚本
-- 版本: 2026-01-28
-- 功能: 新增对练房间表,扩展现有表支持多人对练
-- ============================================================================
-- ============================================================================
-- 1. 创建对练房间表 practice_rooms
-- ============================================================================
CREATE TABLE IF NOT EXISTS `practice_rooms` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '房间ID',
`room_code` VARCHAR(10) NOT NULL UNIQUE COMMENT '6位邀请码',
`room_name` VARCHAR(200) COMMENT '房间名称',
-- 场景信息
`scene_id` INT COMMENT '关联场景ID',
`scene_name` VARCHAR(200) COMMENT '场景名称',
`scene_type` VARCHAR(50) COMMENT '场景类型',
`scene_background` TEXT COMMENT '场景背景',
-- 角色设置
`role_a_name` VARCHAR(50) DEFAULT '角色A' COMMENT '角色A名称如销售顾问',
`role_b_name` VARCHAR(50) DEFAULT '角色B' COMMENT '角色B名称如顾客',
`role_a_description` TEXT COMMENT '角色A描述',
`role_b_description` TEXT COMMENT '角色B描述',
-- 参与者信息
`host_user_id` INT NOT NULL COMMENT '房主用户ID',
`guest_user_id` INT COMMENT '加入者用户ID',
`host_role` VARCHAR(10) DEFAULT 'A' COMMENT '房主选择的角色(A/B)',
`max_participants` INT DEFAULT 2 COMMENT '最大参与人数',
-- 状态和时间
`status` VARCHAR(20) DEFAULT 'waiting' COMMENT '状态: waiting/ready/practicing/completed/canceled',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`started_at` DATETIME COMMENT '开始时间',
`ended_at` DATETIME COMMENT '结束时间',
`duration_seconds` INT DEFAULT 0 COMMENT '对练时长(秒)',
-- 对话统计
`total_turns` INT DEFAULT 0 COMMENT '总对话轮次',
`role_a_turns` INT DEFAULT 0 COMMENT '角色A发言次数',
`role_b_turns` INT DEFAULT 0 COMMENT '角色B发言次数',
-- 软删除
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除',
`deleted_at` DATETIME COMMENT '删除时间',
-- 索引
INDEX `idx_room_code` (`room_code`),
INDEX `idx_host_user` (`host_user_id`),
INDEX `idx_guest_user` (`guest_user_id`),
INDEX `idx_status` (`status`),
INDEX `idx_created_at` (`created_at`),
-- 外键(可选,根据实际需求决定是否启用)
-- FOREIGN KEY (`scene_id`) REFERENCES `practice_scenes`(`id`) ON DELETE SET NULL,
-- FOREIGN KEY (`host_user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
-- FOREIGN KEY (`guest_user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
CONSTRAINT `chk_host_role` CHECK (`host_role` IN ('A', 'B'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='双人对练房间表';
-- ============================================================================
-- 2. 扩展对话记录表 practice_dialogues
-- ============================================================================
-- 添加用户ID字段区分说话人
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `user_id` INT COMMENT '说话人用户ID' AFTER `speaker`;
-- 添加角色名称字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `role_name` VARCHAR(50) COMMENT '角色名称(如销售顾问/顾客)' AFTER `user_id`;
-- 添加房间ID字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `session_id`;
-- 添加消息类型字段
ALTER TABLE `practice_dialogues`
ADD COLUMN IF NOT EXISTS `message_type` VARCHAR(20) DEFAULT 'text' COMMENT '消息类型: text/voice/system' AFTER `content`;
-- 添加索引
ALTER TABLE `practice_dialogues`
ADD INDEX IF NOT EXISTS `idx_user_id` (`user_id`),
ADD INDEX IF NOT EXISTS `idx_room_id` (`room_id`);
-- ============================================================================
-- 3. 扩展会话表 practice_sessions
-- ============================================================================
-- 添加房间ID字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `scene_type`;
-- 添加参与者角色字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `participant_role` VARCHAR(50) COMMENT '用户在房间中的角色' AFTER `room_id`;
-- 添加会话类型字段
ALTER TABLE `practice_sessions`
ADD COLUMN IF NOT EXISTS `session_type` VARCHAR(20) DEFAULT 'solo' COMMENT '会话类型: solo/duo' AFTER `participant_role`;
-- 添加索引
ALTER TABLE `practice_sessions`
ADD INDEX IF NOT EXISTS `idx_room_id` (`room_id`);
-- ============================================================================
-- 4. 扩展报告表 practice_reports支持双人报告
-- ============================================================================
-- 添加房间ID字段
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `room_id` INT COMMENT '房间ID双人对练时使用' AFTER `session_id`;
-- 添加用户ID字段双人模式下每人一份报告
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `user_id` INT COMMENT '用户ID' AFTER `room_id`;
-- 添加报告类型字段
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `report_type` VARCHAR(20) DEFAULT 'solo' COMMENT '报告类型: solo/duo' AFTER `user_id`;
-- 添加对方评价字段(双人模式)
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `partner_feedback` JSON COMMENT '对方表现评价' AFTER `suggestions`;
-- 添加互动质量评分
ALTER TABLE `practice_reports`
ADD COLUMN IF NOT EXISTS `interaction_score` INT COMMENT '互动质量评分0-100' AFTER `partner_feedback`;
-- 修改唯一索引允许同一session有多个报告
-- 注意:需要先删除旧的唯一索引
-- ALTER TABLE `practice_reports` DROP INDEX `session_id`;
-- ALTER TABLE `practice_reports` ADD UNIQUE INDEX `idx_session_user` (`session_id`, `user_id`);
-- ============================================================================
-- 5. 创建房间消息表(用于实时同步)
-- ============================================================================
CREATE TABLE IF NOT EXISTS `practice_room_messages` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID',
`room_id` INT NOT NULL COMMENT '房间ID',
`user_id` INT COMMENT '发送者用户ID系统消息为NULL',
`message_type` VARCHAR(20) NOT NULL COMMENT '消息类型: chat/system/join/leave/start/end',
`content` TEXT COMMENT '消息内容',
`role_name` VARCHAR(50) COMMENT '角色名称',
`sequence` INT NOT NULL COMMENT '消息序号',
`created_at` DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间(毫秒精度)',
INDEX `idx_room_id` (`room_id`),
INDEX `idx_room_sequence` (`room_id`, `sequence`),
INDEX `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间实时消息表';
-- ============================================================================
-- 回滚脚本(如需回滚,执行以下语句)
-- ============================================================================
/*
-- 删除新增的表
DROP TABLE IF EXISTS `practice_room_messages`;
DROP TABLE IF EXISTS `practice_rooms`;
-- 删除 practice_dialogues 新增的列
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `user_id`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `role_name`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_dialogues` DROP COLUMN IF EXISTS `message_type`;
-- 删除 practice_sessions 新增的列
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `participant_role`;
ALTER TABLE `practice_sessions` DROP COLUMN IF EXISTS `session_type`;
-- 删除 practice_reports 新增的列
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `room_id`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `user_id`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `report_type`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `partner_feedback`;
ALTER TABLE `practice_reports` DROP COLUMN IF EXISTS `interaction_score`;
*/