修改完善大部分内容,剩余进行测试

This commit is contained in:
xiao12feng8 2025-12-06 14:53:35 +08:00
parent 3fe0be6357
commit 9659660b1e
115 changed files with 454 additions and 26709 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,189 +0,0 @@
# App 环境监控功能说明
## 概述
在 HBuilder 打包的 App 环境中,由于不支持 WebRTC 的 `getDisplayMedia` API系统会自动回退到**截图方式**进行监控。
## 技术实现
### 1. 平台检测
- **H5 环境**:使用 WebRTC 屏幕共享(高质量、低延迟)
- **App 环境**:自动使用截图方式(通过 `plus.screen.capture` API
### 2. App 环境截图流程
```
用户进入课程页面
检测到 App 环境(#ifndef H5
启动 monitor.start()(截图方式)
连接 WebSocket 到后端
等待后端发送 start_capture 指令
开始定时截图(默认 500ms 间隔)
使用 plus.screen.capture 截图
转换为 Base64 格式
通过 WebSocket 实时传输给监控端
```
### 3. 关键代码位置
#### 学生端App
- **文件**: `fronted_uniapp/src/pages/course/detail.vue`
- **逻辑**: 在 `onShow()` 中检测平台App 环境直接使用截图方式
- **截图实现**: `fronted_uniapp/src/utils/monitor.js`
- `captureScreenshotApp()`: 使用 `plus.screen.capture` 截图
- `imageToBase64()`: 将截图转换为 Base64
- `startScreenCapture()`: 定时截图并传输
#### 监控端
- **文件**: `Study-Vue-redis/study-ui/src/views/study/screenStream/index.vue`
- **逻辑**: 自动检测并显示截图方式的画面Base64 图片)
## 功能特性
### ✅ 已实现功能
1. **自动平台检测**:根据运行环境自动选择 WebRTC 或截图方式
2. **App 截图**:使用 `plus.screen.capture` 捕获整个屏幕
3. **实时传输**:通过 WebSocket 实时传输截图Base64 格式)
4. **自动重连**WebSocket 断开后自动重连
5. **性能优化**:限制截图大小,避免消息过大
### ⚠️ 注意事项
1. **权限要求**
- App 需要屏幕录制/截图权限
- 需要在 `manifest.json` 中配置相应权限
2. **性能考虑**
- 截图频率:默认 500ms2帧/秒)
- 截图大小:限制在 200KB 以内
- 建议在真机上测试,模拟器可能性能较差
3. **兼容性**
- Android支持 `plus.screen.capture`
- iOS需要检查是否支持可能需要额外配置
## 配置说明
### 1. manifest.json 配置
在 HBuilder 项目的 `manifest.json` 中,需要确保:
```json
{
"app-plus": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CAPTURE_SCREEN\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>"
]
},
"ios": {
// iOS 可能需要额外的配置
}
}
}
}
```
### 2. 截图间隔调整
`fronted_uniapp/src/utils/monitor.js` 中:
```javascript
this.captureInterval = 500 // 默认 500ms2帧/秒)
// 可以根据需要调整建议范围300-1000ms
```
### 3. 截图大小限制
`fronted_uniapp/src/utils/monitor.js` 中:
```javascript
const maxSize = 200 * 1024 // 200KB
// 如果截图过大,会自动跳过,避免 WebSocket 断开
```
## 测试步骤
### 1. 打包 App
```bash
# 在 HBuilder 中
1. 选择"发行" -> "原生App-云打包"
2. 选择 Android 或 iOS
3. 配置权限(见上方配置说明)
4. 打包并安装到设备
```
### 2. 测试监控功能
1. **学生端App**
- 登录并进入课程页面
- 查看控制台日志,应该看到 "🚀 开始启动监控(截图方式)..."
- 应该看到 WebSocket 连接成功
2. **监控端(浏览器)**
- 打开监控页面
- 选择该学生
- 应该能看到实时截图画面
### 3. 调试日志
**学生端App关键日志**
- `🚀 开始启动监控(截图方式)...`
- `✅ WebSocket连接成功`
- `📹 启动实时屏幕捕获`
- `📹 已发送 X 帧`
**监控端关键日志**
- `开始接收实时视频流`(截图方式)
- 应该能看到 Base64 图片数据
## 常见问题
### Q1: App 中截图失败
**A**: 检查:
1. 是否配置了截图权限
2. `plus.screen` 是否可用(在 `onReady` 后使用)
3. 真机测试(模拟器可能不支持)
### Q2: 截图传输慢或不流畅
**A**: 可以:
1. 调整截图间隔(增大间隔降低频率)
2. 降低截图质量(在 `plus.screen.capture` 中配置)
3. 检查网络连接
### Q3: WebSocket 连接失败
**A**: 检查:
1. App 中的网络配置(是否允许 HTTP/WebSocket
2. 后端地址是否正确
3. 防火墙/代理设置
## 性能优化建议
1. **截图频率**
- 监控场景500ms2帧/秒)已足够
- 如需更流畅:可降至 300ms3帧/秒),但会增加性能负担
2. **截图质量**
- 默认质量已优化
- 如需降低带宽:可以在截图时降低分辨率
3. **网络优化**
- 使用 WebSocket 压缩(如果后端支持)
- 限制截图大小,避免消息过大
## 未来改进方向
1. **原生插件**:开发原生屏幕录制插件,实现类似 WebRTC 的效果
2. **自适应质量**:根据网络状况自动调整截图频率和质量
3. **离线缓存**:网络断开时缓存截图,连接恢复后批量上传

View File

@ -1,80 +0,0 @@
# SQL生成说明
## 问题说明
如果生成的SQL文件中出现中文乱码是因为文件编码问题导致的。
## 解决方案
### 方法一使用PowerShell脚本推荐
1. **运行脚本**
```powershell
cd E:\ry_study\Study-Vue-redis
powershell -ExecutionPolicy Bypass -File generate_sql_fixed.ps1
```
2. **检查生成的文件**
- 打开 `courseware_import_generated.sql`
- 确保中文字符显示正常
### 方法二手动编写SQL参考示例
参考 `courseware_import_example.sql` 文件中的格式:
```sql
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
('课件标题', 'video', '/profile/upload/2025/11/18/文件名.mp4', 文件大小, '文件名.mp4', NULL, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
```
### 方法三:使用数据库工具导入
如果SQL文件仍有乱码可以
1. **使用Navicat、phpMyAdmin等工具**
- 打开工具
- 选择数据库
- 导入SQL文件时选择正确的字符集UTF-8
2. **使用MySQL命令行**
```bash
mysql -u用户名 -p密码 数据库名 --default-character-set=utf8 < courseware_import_generated.sql
```
## 字段说明
| 字段名 | 说明 | 示例值 |
|--------|------|--------|
| `title` | 课件标题 | '01 第1课·春天的到来' |
| `type` | 文件类型 | 'video' / 'document' / 'image' |
| `file_path` | 文件路径 | '/profile/upload/2025/11/18/文件名.mp4' |
| `file_size` | 文件大小(字节) | 52428800 |
| `file_name` | 原始文件名 | '01 第1课·春天的到来.mp4' |
| `subject_id` | 学科ID | NULL 或 2 |
| `upload_user_id` | 上传人ID | 1管理员 |
## 注意事项
1. **文件编码**SQL文件必须使用UTF-8编码保存
2. **特殊字符**:如果文件名包含单引号,需要转义为两个单引号(''
3. **文件大小**`file_size`字段需要根据实际文件大小填写(单位:字节)
4. **文件路径**:路径格式为 `/profile/upload/2025/11/18/文件名`注意只有一个upload
## 文件类型判断
- **视频**.mp4, .avi, .mov, .wmv, .flv, .mkv, .webm, .m4v, .3gp, .ts
- **文档**.doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .pdf
- **图片**.jpg, .jpeg, .png, .gif, .bmp, .webp
## 执行SQL后
执行SQL后可以在课件管理界面看到这些文件。如果文件无法访问请检查
1. 文件是否真的存在于服务器上
2. 文件路径是否正确
3. Web服务器配置是否正确

View File

@ -1,63 +0,0 @@
# WebRTC 平台支持说明
## 支持的平台
### ✅ H5 浏览器(完全支持)
- **桌面浏览器**Chrome、Firefox、Edge、Safari 等主流浏览器
- **移动浏览器**iOS Safari、Android Chrome 等
- **功能**:完整的 WebRTC 屏幕共享功能
- **实现方式**:使用 `getDisplayMedia()` API
### ⚠️ uni-app App 环境(部分支持)
- **Android App**:不支持 WebRTC 屏幕共享,自动回退到截图方式
- **iOS App**:不支持 WebRTC 屏幕共享,自动回退到截图方式
- **原因**uni-app 的 App 环境不支持 `getDisplayMedia()` API
- **解决方案**
1. **自动回退**系统会自动回退到截图方式WebSocket + Base64 图片)
2. **原生插件**:如需在 App 中使用 WebRTC需要开发原生插件Android MediaProjection API / iOS ReplayKit
### ❌ 小程序环境(不支持)
- **微信小程序**:不支持 WebRTC
- **其他小程序**:不支持 WebRTC
- **解决方案**:使用截图方式
## 技术实现
### H5 环境
```javascript
// 使用 getDisplayMedia API
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 30 } },
audio: false
})
```
### App 环境(自动回退)
```javascript
// 系统会自动检测平台并回退到截图方式
// #ifdef APP-PLUS
// 自动回退到 monitor.js截图方式
// #endif
```
## 性能对比
| 特性 | WebRTC (H5) | 截图方式 (App/回退) |
|------|------------|-------------------|
| 延迟 | 低(<100ms | 较高500ms-3s |
| 画质 | 高(可配置) | 中等(压缩后) |
| 带宽 | 中等 | 较高Base64 编码) |
| 流畅度 | 流畅30fps | 一般(取决于间隔) |
## 使用建议
1. **H5 环境**:优先使用 WebRTC提供最佳体验
2. **App 环境**:自动使用截图方式,无需额外配置
3. **混合部署**:系统会自动检测平台并选择最佳方案
## 未来改进方向
1. **原生插件开发**:为 Android/iOS 开发原生屏幕录制插件
2. **性能优化**:优化截图方式的压缩和传输效率
3. **自适应**:根据网络条件自动调整画质和帧率

View File

@ -1,162 +0,0 @@
# WebRTC 迁移说明
## 概述
已将实时监控功能从 **WebSocket + 截图** 方案迁移到 **WebRTC** 技术,实现更低延迟和更高效率的屏幕流传输。
## 技术架构
### 1. 信令服务器(后端)
- **文件**: `WebRTCSignalingHandler.java`
- **路径**: `/ws/webrtc/{userId}`
- **功能**: 处理 WebRTC 信令交换offer/answer/ICE candidate
### 2. 学生端(前端 UniApp
- **文件**: `fronted_uniapp/src/utils/webrtc.js`
- **功能**:
- 使用 `getDisplayMedia()` 获取屏幕流
- 创建 RTCPeerConnection
- 发送屏幕流给监控端
### 3. 监控端(后端管理界面)
- **文件**: `Study-Vue-redis/study-ui/src/utils/webrtc.js`
- **功能**:
- 接收学生的屏幕流
- 在 `<video>` 元素中显示实时视频
## 工作流程
```
1. 学生端:
- 连接信令服务器 (/ws/webrtc/{userId})
- 获取屏幕流 (getDisplayMedia)
- 创建 PeerConnection
- 发送 offer
2. 信令服务器:
- 接收学生端的 offer
- 转发给所有监控该学生的监控端
3. 监控端:
- 连接信令服务器 (/ws/webrtc/{studentId})
- 接收学生端的 offer
- 创建 answer
- 发送 answer 给学生端
4. 双方交换 ICE candidates建立 P2P 连接
5. 学生端屏幕流通过 WebRTC 传输到监控端
```
## 文件变更
### 新增文件
1. `Study-Vue-redis/ry-study-admin/src/main/java/com/ddnai/web/websocket/WebRTCSignalingHandler.java`
- WebRTC 信令服务器
2. `fronted_uniapp/src/utils/webrtc.js`
- 学生端 WebRTC 客户端
3. `Study-Vue-redis/study-ui/src/utils/webrtc.js`
- 监控端 WebRTC 客户端
### 修改文件
1. `fronted_uniapp/src/pages/course/detail.vue`
- 添加 WebRTC 支持H5 环境优先使用 WebRTC
2. `Study-Vue-redis/study-ui/src/views/study/screenStream/index.vue`
- 添加 WebRTC 视频显示
- 优先使用 WebRTC失败时回退到截图方式
3. `Study-Vue-redis/ry-study-admin/src/main/java/com/ddnai/web/controller/study/ScreenStreamController.java`
- 合并 WebRTC 和 WebSocket 的在线状态查询
## 兼容性
### 自动回退机制
- **H5 环境**: 优先使用 WebRTC失败时自动回退到截图方式
- **App 环境**: 使用截图方式WebRTC 在 App 中需要额外配置)
### 浏览器支持
- Chrome/Edge: ✅ 完全支持
- Firefox: ✅ 完全支持
- Safari: ⚠️ 部分支持(需要用户手动授权)
- 移动浏览器: ⚠️ 支持有限
## STUN/TURN 服务器配置
当前使用 Google 的公共 STUN 服务器:
```javascript
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
```
### 如果需要配置 TURN 服务器(用于 NAT 穿透)
在以下文件中修改 `iceServers` 配置:
- `fronted_uniapp/src/utils/webrtc.js`
- `Study-Vue-redis/study-ui/src/utils/webrtc.js`
添加 TURN 服务器:
```javascript
{
urls: 'turn:your-turn-server.com:3478',
username: 'your-username',
credential: 'your-password'
}
```
## 使用说明
### 学生端
1. 登录并进入课程详情页面
2. 系统自动启动 WebRTC 屏幕共享H5 环境)
3. 浏览器会弹出权限请求,选择要共享的屏幕/窗口
### 监控端
1. 进入实时监控页面 (`/study/screenStream`)
2. 选择在线学生
3. 系统自动连接 WebRTC 并显示实时视频流
## 优势对比
| 特性 | WebSocket + 截图 | WebRTC |
|------|----------------|--------|
| 延迟 | 500ms+ | <100ms |
| 带宽效率 | 低(每帧完整图片) | 高(视频编码压缩) |
| 帧率 | 2 FPS | 30 FPS |
| 画质 | 静态图片 | 流畅视频 |
| 实现复杂度 | 简单 | 中等 |
## 注意事项
1. **HTTPS 要求**: WebRTC 的 `getDisplayMedia()` API 在大多数浏览器中需要 HTTPS 环境
2. **权限请求**: 用户需要授权屏幕共享权限
3. **NAT 穿透**: 如果双方都在 NAT 后面,可能需要 TURN 服务器
4. **浏览器兼容性**: 确保使用现代浏览器Chrome 72+, Firefox 66+, Safari 13+
## 故障排查
### 学生端无法启动 WebRTC
- 检查浏览器是否支持 `getDisplayMedia`
- 检查是否在 HTTPS 环境(或 localhost
- 查看控制台错误信息
### 监控端无法接收视频流
- 检查信令服务器连接是否正常
- 检查 ICE 连接状态
- 查看浏览器控制台和网络标签页
### 连接建立但无画面
- 检查 STUN/TURN 服务器配置
- 检查防火墙设置
- 尝试配置 TURN 服务器
## 后续优化建议
1. 添加 TURN 服务器支持(提高 NAT 穿透成功率)
2. 实现自适应码率(根据网络状况调整画质)
3. 添加音频支持(如果需要)
4. 优化移动端支持

View File

@ -1,63 +0,0 @@
-- 检查用户权限配置
-- 用于排查登录日志和操作日志权限差异问题
-- 1. 检查菜单权限配置
SELECT
menu_id,
menu_name,
perms,
status
FROM sys_menu
WHERE perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY perms
LIMIT 1000;
-- 2. 检查角色权限关联
SELECT
r.role_id,
r.role_name,
r.role_key,
m.menu_name,
m.perms
FROM sys_role r
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY r.role_id, m.perms
LIMIT 1000;
-- 3. 检查用户角色关联
SELECT
u.user_id,
u.user_name,
r.role_name,
r.role_key
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.role_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
ORDER BY u.user_id
LIMIT 1000;
-- 4. 检查用户的所有权限(通过角色)
SELECT DISTINCT
u.user_name,
m.perms
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
AND m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY m.perms
LIMIT 1000;
-- 5. 如果发现缺少权限可以使用以下SQL添加权限
-- 注意:需要先确认 menu_id 和 role_id
-- INSERT INTO sys_role_menu (role_id, menu_id)
-- SELECT role_id, menu_id
-- FROM sys_role r, sys_menu m
-- WHERE r.role_key = 'admin' -- 替换为你的角色
-- AND m.perms = 'monitor:logininfor:list';

View File

@ -1,60 +0,0 @@
-- 检查用户权限配置(单个查询版本,避免 phpMyAdmin 错误)
-- 用于排查登录日志和操作日志权限差异问题
-- 注意:请逐个执行每个查询,不要一次性执行所有查询
-- ============================================
-- 查询 1检查菜单权限配置
-- ============================================
SELECT
menu_id,
menu_name,
perms,
status
FROM sys_menu
WHERE perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY perms;
-- ============================================
-- 查询 2检查角色权限关联
-- ============================================
SELECT
r.role_id,
r.role_name,
r.role_key,
m.menu_name,
m.perms
FROM sys_role r
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY r.role_id, m.perms;
-- ============================================
-- 查询 3检查用户角色关联请替换用户名
-- ============================================
SELECT
u.user_id,
u.user_name,
r.role_name,
r.role_key
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.role_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
ORDER BY u.user_id;
-- ============================================
-- 查询 4检查用户的所有权限请替换用户名
-- ============================================
SELECT DISTINCT
u.user_name,
m.perms
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
AND m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY m.perms;

View File

@ -1,193 +0,0 @@
LOCK TABLES `courseware` WRITE;
/*!40000 ALTER TABLE `courseware` DISABLE KEYS */;
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
-- 从妙解成语——黄粱美梦开始的批量插入
('48.妙解成语——黄粱美梦', 'video', '/profile/upload/2025/11/18/48.妙解成语——黄粱美梦.mp4', 47185920, '48.妙解成语——黄粱美梦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('48.忆江南', 'video', '/profile/upload/2025/11/18/48.忆江南.mp4', 42949672, '48.忆江南.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('49 综合性学习--难忘小学生活(二)', 'video', '/profile/upload/2025/11/18/49 综合性学习--难忘小学生活(二).mp4', 72477184, '49 综合性学习--难忘小学生活(二).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('49 语文园地六 (第一课时)', 'video', '/profile/upload/2025/11/18/49 语文园地六 (第一课时).mp4', 66060288, '49 语文园地六 (第一课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('49.妙解成语——有教无类', 'video', '/profile/upload/2025/11/18/49.妙解成语——有教无类.mp4', 47185920, '49.妙解成语——有教无类.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('49.长相思', 'video', '/profile/upload/2025/11/18/49.长相思.mp4', 42949672, '49.长相思.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('5.写作小灵通-一比高下', 'video', '/profile/upload/2025/11/18/5.写作小灵通-一比高下.mp4', 44040192, '5.写作小灵通-一比高下.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('50 综合性学习--难忘小学生活(三)', 'video', '/profile/upload/2025/11/18/50 综合性学习--难忘小学生活(三).mp4', 73400320, '50 综合性学习--难忘小学生活(三).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('50 第18课《威尼斯的小艇》第一课时', 'video', '/profile/upload/2025/11/18/50 第18课《威尼斯的小艇》第一课时.mp4', 68157440, '50 第18课《威尼斯的小艇》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('50.妙解成语——巧夺天工', 'video', '/profile/upload/2025/11/18/50.妙解成语——巧夺天工.mp4', 47185920, '50.妙解成语——巧夺天工.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('50.渔歌子', 'video', '/profile/upload/2025/11/18/50.渔歌子.mp4', 42949672, '50.渔歌子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('51 综合性学习--难忘小学生活(四)', 'video', '/profile/upload/2025/11/18/51 综合性学习--难忘小学生活(四).mp4', 72477184, '51 综合性学习--难忘小学生活(四).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('51 第18课《威尼斯的小艇》第二课时', 'video', '/profile/upload/2025/11/18/51 第18课《威尼斯的小艇》第二课时.mp4', 73400320, '51 第18课《威尼斯的小艇》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('51.妙解成语——叹为观止', 'video', '/profile/upload/2025/11/18/51.妙解成语——叹为观止.mp4', 47185920, '51.妙解成语——叹为观止.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('51.清平乐。村居', 'video', '/profile/upload/2025/11/18/51.清平乐。村居.mp4', 42949672, '51.清平乐。村居.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('52 综合性学习--难忘小学生活(五)', 'video', '/profile/upload/2025/11/18/52 综合性学习--难忘小学生活(五).mp4', 72477184, '52 综合性学习--难忘小学生活(五).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('52 第19课《牧场之国》第一课时', 'video', '/profile/upload/2025/11/18/52 第19课《牧场之国》第一课时 .mp4', 68157440, '52 第19课《牧场之国》第一课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('52.妙解成语——居安思危', 'video', '/profile/upload/2025/11/18/52.妙解成语——居安思危.mp4', 47185920, '52.妙解成语——居安思危.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('52.西江月夜行黄沙道中', 'video', '/profile/upload/2025/11/18/52.西江月夜行黄沙道中.mp4', 42949672, '52.西江月夜行黄沙道中.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('53 综合性学习--难忘小学生活(六)', 'video', '/profile/upload/2025/11/18/53 综合性学习--难忘小学生活(六).mp4', 73400320, '53 综合性学习--难忘小学生活(六).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('53 第19课《牧场之国》第二课时', 'video', '/profile/upload/2025/11/18/53 第19课《牧场之国》第二课时.mp4', 72477184, '53 第19课《牧场之国》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('53.天净沙.秋', 'video', '/profile/upload/2025/11/18/53.天净沙.秋.mp4', 42949672, '53.天净沙.秋.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('53.妙解成语——千钧一发', 'video', '/profile/upload/2025/11/18/53.妙解成语——千钧一发.mp4', 47185920, '53.妙解成语——千钧一发.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('54 古诗词诵读(一)(《采薇》、《送元二使安西》、《春夜喜雨》、《早春呈水部张十八员外》、《江上渔者》)', 'video', '/profile/upload/2025/11/18/54 古诗词诵读(一)(《采薇》、《送元二使安西》、《春夜喜雨》、《早春呈水部张十八员外》、《江上渔者》).mp4', 83886080, '54 古诗词诵读(一)(《采薇》、《送元二使安西》、《春夜喜雨》、《早春呈水部张十八员外》、《江上渔者》).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('54 第20课《金字塔》', 'video', '/profile/upload/2025/11/18/54 第20课《金字塔》.mp4', 72477184, '54 第20课《金字塔》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('54.妙解成语——不知所措', 'video', '/profile/upload/2025/11/18/54.妙解成语——不知所措.mp4', 47185920, '54.妙解成语——不知所措.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('54.杨氏之子', 'video', '/profile/upload/2025/11/18/54.杨氏之子.mp4', 48234496, '54.杨氏之子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('55 古诗词诵读(二)(《泊船瓜洲》、《游园不值》、《卜算子》、《浣溪沙》、《清平乐》)', 'video', '/profile/upload/2025/11/18/55 古诗词诵读(二)(《泊船瓜洲》、《游园不值》、《卜算子》、《浣溪沙》、《清平乐》).mp4', 83886080, '55 古诗词诵读(二)(《泊船瓜洲》、《游园不值》、《卜算子》、《浣溪沙》、《清平乐》).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('55 第七单元 习作:中国的世界文化遗产', 'video', '/profile/upload/2025/11/18/55 第七单元 习作:中国的世界文化遗产.mp4', 73400320, '55 第七单元 习作:中国的世界文化遗产.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('55.伯牙绝铉', 'video', '/profile/upload/2025/11/18/55.伯牙绝铉.mp4', 48234496, '55.伯牙绝铉.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('55.妙解成语——心急如焚', 'video', '/profile/upload/2025/11/18/55.妙解成语——心急如焚.mp4', 47185920, '55.妙解成语——心急如焚.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('56 语文园地七(第一课时)', 'video', '/profile/upload/2025/11/18/56 语文园地七(第一课时).mp4', 66060288, '56 语文园地七(第一课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('56.妙解成语——波澜壮阔', 'video', '/profile/upload/2025/11/18/56.妙解成语——波澜壮阔.mp4', 47185920, '56.妙解成语——波澜壮阔.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('56.楚王好细腰', 'video', '/profile/upload/2025/11/18/56.楚王好细腰.mp4', 42949672, '56.楚王好细腰.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('57 语文园地七(第二课时)', 'video', '/profile/upload/2025/11/18/57 语文园地七(第二课时).mp4', 66060288, '57 语文园地七(第二课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('57.妙解成语——走马观花', 'video', '/profile/upload/2025/11/18/57.妙解成语——走马观花.mp4', 47185920, '57.妙解成语——走马观花.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('58 五年级语文下册第21课《杨氏之子》第一课时', 'video', '/profile/upload/2025/11/18/58 五年级语文下册第21课《杨氏之子》第一课时.mp4', 68157440, '58 五年级语文下册第21课《杨氏之子》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('58.妙解成语——鬼斧神工', 'video', '/profile/upload/2025/11/18/58.妙解成语——鬼斧神工.mp4', 47185920, '58.妙解成语——鬼斧神工.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('59 五年级语文下册第21课《杨氏之子》第二课时', 'video', '/profile/upload/2025/11/18/59 五年级语文下册第21课《杨氏之子》第二课时.mp4', 72477184, '59 五年级语文下册第21课《杨氏之子》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('59.妙解成语——一箭双雕', 'video', '/profile/upload/2025/11/18/59.妙解成语——一箭双雕.mp4', 47185920, '59.妙解成语——一箭双雕.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('6.写作小灵通-外貌描写---', 'video', '/profile/upload/2025/11/18/6.写作小灵通-外貌描写---.mp4', 44040192, '6.写作小灵通-外貌描写---.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('60 第22课《手指》第一课时', 'video', '/profile/upload/2025/11/18/60 第22课《手指》第一课时.mp4', 68157440, '60 第22课《手指》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('60.妙解成语——哄堂大笑', 'video', '/profile/upload/2025/11/18/60.妙解成语——哄堂大笑.mp4', 47185920, '60.妙解成语——哄堂大笑.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('61 第23课《童年的发现》', 'video', '/profile/upload/2025/11/18/61 第23课《童年的发现》.mp4', 72477184, '61 第23课《童年的发现》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('61.妙解成语——缘木求鱼', 'video', '/profile/upload/2025/11/18/61.妙解成语——缘木求鱼.mp4', 47185920, '61.妙解成语——缘木求鱼.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('62.妙解成语——化险为夷', 'video', '/profile/upload/2025/11/18/62.妙解成语——化险为夷.mp4', 47185920, '62.妙解成语——化险为夷.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('63.妙解成语——再接再厉', 'video', '/profile/upload/2025/11/18/63.妙解成语——再接再厉.mp4', 47185920, '63.妙解成语——再接再厉.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('64.妙解成语——沧海桑田', 'video', '/profile/upload/2025/11/18/64.妙解成语——沧海桑田.mp4', 47185920, '64.妙解成语——沧海桑田.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('65.妙解成语——踉踉跄跄', 'video', '/profile/upload/2025/11/18/65.妙解成语——踉踉跄跄.mp4', 47185920, '65.妙解成语——踉踉跄跄.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('66.妙解成语——弄巧成拙', 'video', '/profile/upload/2025/11/18/66.妙解成语——弄巧成拙.mp4', 47185920, '66.妙解成语——弄巧成拙.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('67.妙解成语——风平浪静', 'video', '/profile/upload/2025/11/18/67.妙解成语——风平浪静.mp4', 47185920, '67.妙解成语——风平浪静.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('68.妙解成语——恋恋不舍', 'video', '/profile/upload/2025/11/18/68.妙解成语——恋恋不舍.mp4', 47185920, '68.妙解成语——恋恋不舍.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('69.妙解成语——心悦诚服', 'video', '/profile/upload/2025/11/18/69.妙解成语——心悦诚服.mp4', 47185920, '69.妙解成语——心悦诚服.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('7.写作小灵通-场景描写', 'video', '/profile/upload/2025/11/18/7.写作小灵通-场景描写.mp4', 44040192, '7.写作小灵通-场景描写.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('70.妙解成语——“雪中送炭”和“锦上添花”', 'video', '/profile/upload/2025/11/18/70.妙解成语——“雪中送炭”和“锦上添花”.mp4', 53084160, '70.妙解成语——“雪中送炭”和“锦上添花”.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('71.妙解成语——忐忑不安', 'video', '/profile/upload/2025/11/18/71.妙解成语——忐忑不安.mp4', 47185920, '71.妙解成语——忐忑不安.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('72.妙解成语——锲而不舍', 'video', '/profile/upload/2025/11/18/72.妙解成语——锲而不舍.mp4', 47185920, '72.妙解成语——锲而不舍.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('73.妙解成语——五颜六色', 'video', '/profile/upload/2025/11/18/73.妙解成语——五颜六色.mp4', 47185920, '73.妙解成语——五颜六色.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('74.妙解成语——兴致勃勃', 'video', '/profile/upload/2025/11/18/74.妙解成语——兴致勃勃.mp4', 47185920, '74.妙解成语——兴致勃勃.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('75.妙解成语——翩翩起舞', 'video', '/profile/upload/2025/11/18/75.妙解成语——翩翩起舞.mp4', 47185920, '75.妙解成语——翩翩起舞.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('76.妙解成语——豁然开朗', 'video', '/profile/upload/2025/11/18/76.妙解成语——豁然开朗.mp4', 47185920, '76.妙解成语——豁然开朗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('77.妙解成语——茹毛饮血', 'video', '/profile/upload/2025/11/18/77.妙解成语——茹毛饮血.mp4', 47185920, '77.妙解成语——茹毛饮血.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('78.妙解成语——铿锵有力', 'video', '/profile/upload/2025/11/18/78.妙解成语——铿锵有力.mp4', 47185920, '78.妙解成语——铿锵有力.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('79.妙解成语——三眼一板', 'video', '/profile/upload/2025/11/18/79.妙解成语——三眼一板.mp4', 47185920, '79.妙解成语——三眼一板.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('8.写作小灵通-个性化的语言描写', 'video', '/profile/upload/2025/11/18/8.写作小灵通-个性化的语言描写.mp4', 44040192, '8.写作小灵通-个性化的语言描写.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('80.妙解成语——栉风沐雨', 'video', '/profile/upload/2025/11/18/80.妙解成语——栉风沐雨.mp4', 47185920, '80.妙解成语——栉风沐雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('81.妙解成语——偃旗息鼓', 'video', '/profile/upload/2025/11/18/81.妙解成语——偃旗息鼓.mp4', 47185920, '81.妙解成语——偃旗息鼓.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('82.妙解成语——振聋发聩', 'video', '/profile/upload/2025/11/18/82.妙解成语——振聋发聩.mp4', 47185920, '82.妙解成语——振聋发聩.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('83.妙解成语——千锤百炼', 'video', '/profile/upload/2025/11/18/83.妙解成语——千锤百炼.mp4', 47185920, '83.妙解成语——千锤百炼.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('84.妙解成语——饥肠辘辘', 'video', '/profile/upload/2025/11/18/84.妙解成语——饥肠辘辘.mp4', 47185920, '84.妙解成语——饥肠辘辘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('85.妙解成语——余音绕梁', 'video', '/profile/upload/2025/11/18/85.妙解成语——余音绕梁.mp4', 47185920, '85.妙解成语——余音绕梁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('86.妙解成语——美轮美奂', 'video', '/profile/upload/2025/11/18/86.妙解成语——美轮美奂.mp4', 47185920, '86.妙解成语——美轮美奂.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('87.妙解成语——可见一斑', 'video', '/profile/upload/2025/11/18/87.妙解成语——可见一斑.mp4', 47185920, '87.妙解成语——可见一斑.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('88.妙解成语——终天之恨', 'video', '/profile/upload/2025/11/18/88.妙解成语——终天之恨.mp4', 47185920, '88.妙解成语——终天之恨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('89.妙解成语——束手无策', 'video', '/profile/upload/2025/11/18/89.妙解成语——束手无策.mp4', 47185920, '89.妙解成语——束手无策.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('9.写作小灵通-一切景语皆情语', 'video', '/profile/upload/2025/11/18/9.写作小灵通-一切景语皆情语.mp4', 44040192, '9.写作小灵通-一切景语皆情语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('90.妙解成语——万籁俱寂', 'video', '/profile/upload/2025/11/18/90.妙解成语——万籁俱寂.mp4', 47185920, '90.妙解成语——万籁俱寂.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('91.妙解成语——摩肩接踵', 'video', '/profile/upload/2025/11/18/91.妙解成语——摩肩接踵.mp4', 47185920, '91.妙解成语——摩肩接踵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('92.妙解成语——莫衷一是', 'video', '/profile/upload/2025/11/18/92.妙解成语——莫衷一是.mp4', 47185920, '92.妙解成语——莫衷一是.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('93.妙解成语——釜底抽薪(上)', 'video', '/profile/upload/2025/11/18/93.妙解成语——釜底抽薪(上).mp4', 47185920, '93.妙解成语——釜底抽薪(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('94.妙解成语——釜底抽薪(下)', 'video', '/profile/upload/2025/11/18/94.妙解成语——釜底抽薪(下).mp4', 47185920, '94.妙解成语——釜底抽薪(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('95.妙解成语——杯水车薪', 'video', '/profile/upload/2025/11/18/95.妙解成语——杯水车薪.mp4', 47185920, '95.妙解成语——杯水车薪.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('Holiday', 'video', '/profile/upload/2025/11/18/Holiday.mp4', 38580224, 'Holiday.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('中国传统文化——二十四节气(上)', 'video', '/profile/upload/2025/11/18/中国传统文化——二十四节气(上).mp4', 68157440, '中国传统文化——二十四节气(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('中国传统文化——二十四节气(下)', 'video', '/profile/upload/2025/11/18/中国传统文化——二十四节气(下).mp4', 68157440, '中国传统文化——二十四节气(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('中国传统文化——茶文化', 'video', '/profile/upload/2025/11/18/中国传统文化——茶文化.mp4', 72477184, '中国传统文化——茶文化.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('中国传统节日——端午节', 'video', '/profile/upload/2025/11/18/中国传统节日——端午节.mp4', 66060288, '中国传统节日——端午节.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('中国古代神话故事——盘古开天地', 'video', '/profile/upload/2025/11/18/中国古代神话故事——盘古开天地.mp4', 73400320, '中国古代神话故事——盘古开天地.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('人类认识地球及其运动的历史', 'video', '/profile/upload/2025/11/18/人类认识地球及其运动的历史.mp4', 60311552, '人类认识地球及其运动的历史.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('你不可不知的音乐家——约翰·塞巴斯蒂安·巴赫', 'video', '/profile/upload/2025/11/18/你不可不知的音乐家——约翰·塞巴斯蒂安·巴赫.mp4', 68157440, '你不可不知的音乐家——约翰·塞巴斯蒂安·巴赫.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('兔子肖肖找朋友——认识肖字家族', 'video', '/profile/upload/2025/11/18/兔子肖肖找朋友——认识肖字家族.mp4', 53084160, '兔子肖肖找朋友——认识肖字家族.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('历史文化名人系列——徐霞客', 'video', '/profile/upload/2025/11/18/历史文化名人系列——徐霞客.mp4', 66060288, '历史文化名人系列——徐霞客.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('历史文化名人系列——李时珍', 'video', '/profile/upload/2025/11/18/历史文化名人系列——李时珍.mp4', 66060288, '历史文化名人系列——李时珍.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('历史文化名城系列——杭州西湖(上)', 'video', '/profile/upload/2025/11/18/历史文化名城系列——杭州西湖(上).mp4', 68157440, '历史文化名城系列——杭州西湖(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('历史文化名城系列——杭州西湖(下)', 'video', '/profile/upload/2025/11/18/历史文化名城系列——杭州西湖(下).mp4', 68157440, '历史文化名城系列——杭州西湖(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——冒和帽', 'video', '/profile/upload/2025/11/18/同音字辨析——冒和帽.mp4', 48234496, '同音字辨析——冒和帽.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——圆和园', 'video', '/profile/upload/2025/11/18/同音字辨析——圆和园.mp4', 48234496, '同音字辨析——圆和园.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——在和再', 'video', '/profile/upload/2025/11/18/同音字辨析——在和再.mp4', 48234496, '同音字辨析——在和再.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——坐和座', 'video', '/profile/upload/2025/11/18/同音字辨析——坐和座.mp4', 48234496, '同音字辨析——坐和座.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——奈和耐', 'video', '/profile/upload/2025/11/18/同音字辨析——奈和耐.mp4', 48234496, '同音字辨析——奈和耐.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——崖VS涯', 'video', '/profile/upload/2025/11/18/同音字辨析——崖VS涯.mp4', 48234496, '同音字辨析——崖VS涯.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——州和洲', 'video', '/profile/upload/2025/11/18/同音字辨析——州和洲.mp4', 48234496, '同音字辨析——州和洲.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——度和渡', 'video', '/profile/upload/2025/11/18/同音字辨析——度和渡.mp4', 48234496, '同音字辨析——度和渡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——形和型', 'video', '/profile/upload/2025/11/18/同音字辨析——形和型.mp4', 48234496, '同音字辨析——形和型.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——戴VS带', 'video', '/profile/upload/2025/11/18/同音字辨析——戴VS带.mp4', 48234496, '同音字辨析——戴VS带.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——捡VS拣', 'video', '/profile/upload/2025/11/18/同音字辨析——捡VS拣.mp4', 48234496, '同音字辨析——捡VS拣.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——查VS察', 'video', '/profile/upload/2025/11/18/同音字辨析——查VS察.mp4', 48234496, '同音字辨析——查VS察.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——气和汽', 'video', '/profile/upload/2025/11/18/同音字辨析——气和汽.mp4', 48234496, '同音字辨析——气和汽.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——熔VS融', 'video', '/profile/upload/2025/11/18/同音字辨析——熔VS融.mp4', 48234496, '同音字辨析——熔VS融.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——碧和璧', 'video', '/profile/upload/2025/11/18/同音字辨析——碧和璧.mp4', 48234496, '同音字辨析——碧和璧.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——秘和密', 'video', '/profile/upload/2025/11/18/同音字辨析——秘和密.mp4', 48234496, '同音字辨析——秘和密.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——竟和竞', 'video', '/profile/upload/2025/11/18/同音字辨析——竟和竞.mp4', 48234496, '同音字辨析——竟和竞.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——脑VS恼', 'video', '/profile/upload/2025/11/18/同音字辨析——脑VS恼.mp4', 48234496, '同音字辨析——脑VS恼.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——蓝和篮', 'video', '/profile/upload/2025/11/18/同音字辨析——蓝和篮.mp4', 48234496, '同音字辨析——蓝和篮.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——讯和迅', 'video', '/profile/upload/2025/11/18/同音字辨析——讯和迅.mp4', 48234496, '同音字辨析——讯和迅.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——象和像', 'video', '/profile/upload/2025/11/18/同音字辨析——象和像.mp4', 48234496, '同音字辨析——象和像.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——采和彩', 'video', '/profile/upload/2025/11/18/同音字辨析——采和彩.mp4', 48234496, '同音字辨析——采和彩.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('同音字辨析——颗和棵', 'video', '/profile/upload/2025/11/18/同音字辨析——颗和棵.mp4', 48234496, '同音字辨析——颗和棵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('和氏璧的故事(上)', 'video', '/profile/upload/2025/11/18/和氏璧的故事(上).mp4', 66060288, '和氏璧的故事(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('和氏璧的故事(下)', 'video', '/profile/upload/2025/11/18/和氏璧的故事(下).mp4', 66060288, '和氏璧的故事(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('唐朝概况', 'video', '/profile/upload/2025/11/18/唐朝概况.mp4', 60311552, '唐朝概况.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('地球的形状', 'video', '/profile/upload/2025/11/18/地球的形状.mp4', 53084160, '地球的形状.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('地球的形状_20251118150824A003', 'video', '/profile/upload/2025/11/18/地球的形状_20251118150824A003.mp4', 53084160, '地球的形状_20251118150824A003.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('地球的形状_20251118162621A004', 'video', '/profile/upload/2025/11/18/地球的形状_20251118162621A004.mp4', 53084160, '地球的形状_20251118162621A004.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解古文——伯牙绝弦', 'video', '/profile/upload/2025/11/18/妙解古文——伯牙绝弦.mp4', 48234496, '妙解古文——伯牙绝弦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解古文——楚王好细腰', 'video', '/profile/upload/2025/11/18/妙解古文——楚王好细腰.mp4', 48234496, '妙解古文——楚王好细腰.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解成语——东施效颦', 'video', '/profile/upload/2025/11/18/妙解成语——东施效颦.mp4', 47185920, '妙解成语——东施效颦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解成语——自相矛盾', 'video', '/profile/upload/2025/11/18/妙解成语——自相矛盾.mp4', 47185920, '妙解成语——自相矛盾.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——乘', 'video', '/profile/upload/2025/11/18/妙解生字——乘.mp4', 48234496, '妙解生字——乘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——休', 'video', '/profile/upload/2025/11/18/妙解生字——休.mp4', 48234496, '妙解生字——休.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——初', 'video', '/profile/upload/2025/11/18/妙解生字——初.mp4', 48234496, '妙解生字——初.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——喇', 'video', '/profile/upload/2025/11/18/妙解生字——喇.mp4', 48234496, '妙解生字——喇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——器', 'video', '/profile/upload/2025/11/18/妙解生字——器.mp4', 48234496, '妙解生字——器.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——天', 'video', '/profile/upload/2025/11/18/妙解生字——天.mp4', 48234496, '妙解生字——天.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——家', 'video', '/profile/upload/2025/11/18/妙解生字——家.mp4', 48234496, '妙解生字——家.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——巢', 'video', '/profile/upload/2025/11/18/妙解生字——巢.mp4', 48234496, '妙解生字——巢.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——引', 'video', '/profile/upload/2025/11/18/妙解生字——引.mp4', 48234496, '妙解生字——引.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——片', 'video', '/profile/upload/2025/11/18/妙解生字——片.mp4', 48234496, '妙解生字——片.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——牢', 'video', '/profile/upload/2025/11/18/妙解生字——牢.mp4', 48234496, '妙解生字——牢.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——甜', 'video', '/profile/upload/2025/11/18/妙解生字——甜.mp4', 48234496, '妙解生字——甜.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——算', 'video', '/profile/upload/2025/11/18/妙解生字——算.mp4', 48234496, '妙解生字——算.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——纠', 'video', '/profile/upload/2025/11/18/妙解生字——纠.mp4', 48234496, '妙解生字——纠.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——纳', 'video', '/profile/upload/2025/11/18/妙解生字——纳.mp4', 48234496, '妙解生字——纳.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——美', 'video', '/profile/upload/2025/11/18/妙解生字——美.mp4', 48234496, '妙解生字——美.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——耙', 'video', '/profile/upload/2025/11/18/妙解生字——耙.mp4', 48234496, '妙解生字——耙.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——聚', 'video', '/profile/upload/2025/11/18/妙解生字——聚.mp4', 48234496, '妙解生字——聚.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——舟', 'video', '/profile/upload/2025/11/18/妙解生字——舟.mp4', 48234496, '妙解生字——舟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——裹', 'video', '/profile/upload/2025/11/18/妙解生字——裹.mp4', 48234496, '妙解生字——裹.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——足', 'video', '/profile/upload/2025/11/18/妙解生字——足.mp4', 48234496, '妙解生字——足.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——车', 'video', '/profile/upload/2025/11/18/妙解生字——车.mp4', 48234496, '妙解生字——车.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生字——集', 'video', '/profile/upload/2025/11/18/妙解生字——集.mp4', 48234496, '妙解生字——集.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生词——焦点', 'video', '/profile/upload/2025/11/18/妙解生词——焦点.mp4', 48234496, '妙解生词——焦点.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生词——突兀森郁', 'video', '/profile/upload/2025/11/18/妙解生词——突兀森郁.mp4', 48234496, '妙解生词——突兀森郁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生词——蜿蜒盘旋', 'video', '/profile/upload/2025/11/18/妙解生词——蜿蜒盘旋.mp4', 48234496, '妙解生词——蜿蜒盘旋.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解生词——袅袅', 'video', '/profile/upload/2025/11/18/妙解生词——袅袅.mp4', 48234496, '妙解生词——袅袅.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解词语——居然', 'video', '/profile/upload/2025/11/18/妙解词语——居然.mp4', 48234496, '妙解词语——居然.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解词语——沐浴', 'video', '/profile/upload/2025/11/18/妙解词语——沐浴.mp4', 48234496, '妙解词语——沐浴.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('妙解词语——襁褓', 'video', '/profile/upload/2025/11/18/妙解词语——襁褓.mp4', 48234496, '妙解词语——襁褓.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('小汉字,大故事——火', 'video', '/profile/upload/2025/11/18/小汉字,大故事——火.mp4', 53084160, '小汉字,大故事——火.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('小汉字,大故事——牵', 'video', '/profile/upload/2025/11/18/小汉字,大故事——牵.mp4', 53084160, '小汉字,大故事——牵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('小苏打和白醋的变化', 'video', '/profile/upload/2025/11/18/小苏打和白醋的变化.mp4', 60311552, '小苏打和白醋的变化.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识关联词——虽然……但是……', 'video', '/profile/upload/2025/11/18/巧识关联词——虽然……但是…….mp4', 48234496, '巧识关联词——虽然……但是…….mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识包字家族', 'video', '/profile/upload/2025/11/18/巧识包字家族.mp4', 53084160, '巧识包字家族.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识反义词——大小、多少', 'video', '/profile/upload/2025/11/18/巧识反义词——大小、多少.mp4', 48234496, '巧识反义词——大小、多少.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——倒', 'video', '/profile/upload/2025/11/18/巧识多音字——倒.mp4', 48234496, '巧识多音字——倒.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——冠', 'video', '/profile/upload/2025/11/18/巧识多音字——冠.mp4', 48234496, '巧识多音字——冠.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——分', 'video', '/profile/upload/2025/11/18/巧识多音字——分.mp4', 48234496, '巧识多音字——分.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——壳', 'video', '/profile/upload/2025/11/18/巧识多音字——壳.mp4', 48234496, '巧识多音字——壳.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——处', 'video', '/profile/upload/2025/11/18/巧识多音字——处.mp4', 48234496, '巧识多音字——处.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——奔', 'video', '/profile/upload/2025/11/18/巧识多音字——奔.mp4', 48234496, '巧识多音字——奔.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——差', 'video', '/profile/upload/2025/11/18/巧识多音字——差.mp4', 48234496, '巧识多音字——差.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——强', 'video', '/profile/upload/2025/11/18/巧识多音字——强.mp4', 48234496, '巧识多音字——强.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——当', 'video', '/profile/upload/2025/11/18/巧识多音字——当.mp4', 48234496, '巧识多音字——当.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——扒', 'video', '/profile/upload/2025/11/18/巧识多音字——扒.mp4', 48234496, '巧识多音字——扒.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——挨', 'video', '/profile/upload/2025/11/18/巧识多音字——挨.mp4', 48234496, '巧识多音字——挨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——撒', 'video', '/profile/upload/2025/11/18/巧识多音字——撒.mp4', 48234496, '巧识多音字——撒.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——数', 'video', '/profile/upload/2025/11/18/巧识多音字——数.mp4', 48234496, '巧识多音字——数.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——曲', 'video', '/profile/upload/2025/11/18/巧识多音字——曲.mp4', 48234496, '巧识多音字——曲.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——朝', 'video', '/profile/upload/2025/11/18/巧识多音字——朝.mp4', 48234496, '巧识多音字——朝.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——模', 'video', '/profile/upload/2025/11/18/巧识多音字——模.mp4', 48234496, '巧识多音字——模.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
/*!40000 ALTER TABLE `courseware` ENABLE KEYS */;
UNLOCK TABLES;

View File

@ -1,145 +0,0 @@
LOCK TABLES `courseware` WRITE;
/*!40000 ALTER TABLE `courseware` DISABLE KEYS */;
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
('巧识多音字——横', 'video', '/profile/upload/2025/11/18/巧识多音字——横.mp4', 48234496, '巧识多音字——横.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——没', 'video', '/profile/upload/2025/11/18/巧识多音字——没.mp4', 48234496, '巧识多音字——没.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——涨', 'video', '/profile/upload/2025/11/18/巧识多音字——涨.mp4', 48234496, '巧识多音字——涨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——的', 'video', '/profile/upload/2025/11/18/巧识多音字——的.mp4', 48234496, '巧识多音字——的.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——磨', 'video', '/profile/upload/2025/11/18/巧识多音字——磨.mp4', 48234496, '巧识多音字——磨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——种', 'video', '/profile/upload/2025/11/18/巧识多音字——种.mp4', 48234496, '巧识多音字——种.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——空', 'video', '/profile/upload/2025/11/18/巧识多音字——空.mp4', 48234496, '巧识多音字——空.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——系', 'video', '/profile/upload/2025/11/18/巧识多音字——系.mp4', 48234496, '巧识多音字——系.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——累', 'video', '/profile/upload/2025/11/18/巧识多音字——累.mp4', 48234496, '巧识多音字——累.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——结', 'video', '/profile/upload/2025/11/18/巧识多音字——结.mp4', 48234496, '巧识多音字——结.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——脉', 'video', '/profile/upload/2025/11/18/巧识多音字——脉.mp4', 48234496, '巧识多音字——脉.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——脯', 'video', '/profile/upload/2025/11/18/巧识多音字——脯.mp4', 48234496, '巧识多音字——脯.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——落', 'video', '/profile/upload/2025/11/18/巧识多音字——落.mp4', 48234496, '巧识多音字——落.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——角', 'video', '/profile/upload/2025/11/18/巧识多音字——角.mp4', 48234496, '巧识多音字——角.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——调', 'video', '/profile/upload/2025/11/18/巧识多音字——调.mp4', 48234496, '巧识多音字——调.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——载', 'video', '/profile/upload/2025/11/18/巧识多音字——载.mp4', 48234496, '巧识多音字——载.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——重', 'video', '/profile/upload/2025/11/18/巧识多音字——重.mp4', 48234496, '巧识多音字——重.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——间', 'video', '/profile/upload/2025/11/18/巧识多音字——间.mp4', 48234496, '巧识多音字——间.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——闷', 'video', '/profile/upload/2025/11/18/巧识多音字——闷.mp4', 48234496, '巧识多音字——闷.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——难', 'video', '/profile/upload/2025/11/18/巧识多音字——难.mp4', 48234496, '巧识多音字——难.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('巧识多音字——露', 'video', '/profile/upload/2025/11/18/巧识多音字——露.mp4', 48234496, '巧识多音字——露.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——予VS矛', 'video', '/profile/upload/2025/11/18/形近字辨析——予VS矛.mp4', 48234496, '形近字辨析——予VS矛.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——人和入', 'video', '/profile/upload/2025/11/18/形近字辨析——人和入.mp4', 48234496, '形近字辨析——人和入.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——侯和候', 'video', '/profile/upload/2025/11/18/形近字辨析——侯和候.mp4', 48234496, '形近字辨析——侯和候.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——俩VS两', 'video', '/profile/upload/2025/11/18/形近字辨析——俩VS两.mp4', 48234496, '形近字辨析——俩VS两.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——决和抉', 'video', '/profile/upload/2025/11/18/形近字辨析——决和抉.mp4', 48234496, '形近字辨析——决和抉.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——刮VS舌', 'video', '/profile/upload/2025/11/18/形近字辨析——刮VS舌.mp4', 48234496, '形近字辨析——刮VS舌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——副VS幅', 'video', '/profile/upload/2025/11/18/形近字辨析——副VS幅.mp4', 48234496, '形近字辨析——副VS幅.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——即和既', 'video', '/profile/upload/2025/11/18/形近字辨析——即和既.mp4', 48234496, '形近字辨析——即和既.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——土和士', 'video', '/profile/upload/2025/11/18/形近字辨析——土和士.mp4', 48234496, '形近字辨析——土和士.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——己和已', 'video', '/profile/upload/2025/11/18/形近字辨析——己和已.mp4', 48234496, '形近字辨析——己和已.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——戍、戒、戎', 'video', '/profile/upload/2025/11/18/形近字辨析——戍、戒、戎.mp4', 53084160, '形近字辨析——戍、戒、戎.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——折VS拆', 'video', '/profile/upload/2025/11/18/形近字辨析——折VS拆.mp4', 48234496, '形近字辨析——折VS拆.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——抵VS低', 'video', '/profile/upload/2025/11/18/形近字辨析——抵VS低.mp4', 48234496, '形近字辨析——抵VS低.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——拔和拨', 'video', '/profile/upload/2025/11/18/形近字辨析——拔和拨.mp4', 48234496, '形近字辨析——拔和拨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——摄VS聂', 'video', '/profile/upload/2025/11/18/形近字辨析——摄VS聂.mp4', 48234496, '形近字辨析——摄VS聂.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——旅VS旋', 'video', '/profile/upload/2025/11/18/形近字辨析——旅VS旋.mp4', 48234496, '形近字辨析——旅VS旋.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——日和目', 'video', '/profile/upload/2025/11/18/形近字辨析——日和目.mp4', 48234496, '形近字辨析——日和目.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——杆VS竿VS秆', 'video', '/profile/upload/2025/11/18/形近字辨析——杆VS竿VS秆.mp4', 53084160, '形近字辨析——杆VS竿VS秆.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——渴和喝', 'video', '/profile/upload/2025/11/18/形近字辨析——渴和喝.mp4', 48234496, '形近字辨析——渴和喝.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——炮VS跑', 'video', '/profile/upload/2025/11/18/形近字辨析——炮VS跑.mp4', 48234496, '形近字辨析——炮VS跑.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——炼和练', 'video', '/profile/upload/2025/11/18/形近字辨析——炼和练.mp4', 48234496, '形近字辨析——炼和练.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——燥VS躁', 'video', '/profile/upload/2025/11/18/形近字辨析——燥VS躁.mp4', 48234496, '形近字辨析——燥VS躁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——碟和牒', 'video', '/profile/upload/2025/11/18/形近字辨析——碟和牒.mp4', 48234496, '形近字辨析——碟和牒.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——街和衔', 'video', '/profile/upload/2025/11/18/形近字辨析——街和衔.mp4', 48234496, '形近字辨析——街和衔.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——足和是', 'video', '/profile/upload/2025/11/18/形近字辨析——足和是.mp4', 48234496, '形近字辨析——足和是.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——辨、辫、瓣', 'video', '/profile/upload/2025/11/18/形近字辨析——辨、辫、瓣.mp4', 53084160, '形近字辨析——辨、辫、瓣.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——镜VS境', 'video', '/profile/upload/2025/11/18/形近字辨析——镜VS境.mp4', 48234496, '形近字辨析——镜VS境.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——阴和荫', 'video', '/profile/upload/2025/11/18/形近字辨析——阴和荫.mp4', 48234496, '形近字辨析——阴和荫.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('形近字辨析——鸟和乌', 'video', '/profile/upload/2025/11/18/形近字辨析——鸟和乌.mp4', 48234496, '形近字辨析——鸟和乌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('我们知道的动物', 'video', '/profile/upload/2025/11/18/我们知道的动物.mp4', 60311552, '我们知道的动物.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('我们身边的物质', 'video', '/profile/upload/2025/11/18/我们身边的物质.mp4', 60311552, '我们身边的物质.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('文字岛的故事——来自反义词岛的邀请', 'video', '/profile/upload/2025/11/18/文字岛的故事——来自反义词岛的邀请.mp4', 53084160, '文字岛的故事——来自反义词岛的邀请.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('文字的世界和横划的介绍', 'video', '/profile/upload/2025/11/18/文字的世界和横划的介绍.mp4', 53084160, '文字的世界和横划的介绍.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('易混词巧辨析——化妆和化装', 'video', '/profile/upload/2025/11/18/易混词巧辨析——化妆和化装.mp4', 48234496, '易混词巧辨析——化妆和化装.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('昼夜交替', 'video', '/profile/upload/2025/11/18/昼夜交替.mp4', 60311552, '昼夜交替.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('最会画虾的老爷爷', 'video', '/profile/upload/2025/11/18/最会画虾的老爷爷.mp4', 53084160, '最会画虾的老爷爷.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('有趣的地名(上)', 'video', '/profile/upload/2025/11/18/有趣的地名(上).mp4', 66060288, '有趣的地名(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('有趣的地名(下)', 'video', '/profile/upload/2025/11/18/有趣的地名(下).mp4', 66060288, '有趣的地名(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('有趣的字谜', 'video', '/profile/upload/2025/11/18/有趣的字谜.mp4', 53084160, '有趣的字谜.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('有趣的春节系列(一)——年的来历', 'video', '/profile/upload/2025/11/18/有趣的春节系列(一)——年的来历.mp4', 66060288, '有趣的春节系列(一)——年的来历.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('有趣的生字——开、关', 'video', '/profile/upload/2025/11/18/有趣的生字——开、关.mp4', 48234496, '有趣的生字——开、关.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('毕加索简介', 'video', '/profile/upload/2025/11/18/毕加索简介.mp4', 60311552, '毕加索简介.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('物质发生了什么变化', 'video', '/profile/upload/2025/11/18/物质发生了什么变化.mp4', 60311552, '物质发生了什么变化.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('生活中的杠杆', 'video', '/profile/upload/2025/11/18/生活中的杠杆.mp4', 60311552, '生活中的杠杆.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('癌细胞', 'video', '/profile/upload/2025/11/18/癌细胞.mp4', 60311552, '癌细胞.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——三点水', 'video', '/profile/upload/2025/11/18/神奇的偏旁——三点水.mp4', 48234496, '神奇的偏旁——三点水.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——两点水旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——两点水旁.mp4', 48234496, '神奇的偏旁——两点水旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——倒八', 'video', '/profile/upload/2025/11/18/神奇的偏旁——倒八.mp4', 48234496, '神奇的偏旁——倒八.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——刀字旁与立刀旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——刀字旁与立刀旁.mp4', 53084160, '神奇的偏旁——刀字旁与立刀旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——包字头', 'video', '/profile/upload/2025/11/18/神奇的偏旁——包字头.mp4', 48234496, '神奇的偏旁——包字头.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——四点底', 'video', '/profile/upload/2025/11/18/神奇的偏旁——四点底.mp4', 48234496, '神奇的偏旁——四点底.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——女字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——女字旁.mp4', 48234496, '神奇的偏旁——女字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——子字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——子字旁.mp4', 48234496, '神奇的偏旁——子字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——弓字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——弓字旁.mp4', 48234496, '神奇的偏旁——弓字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——心字底', 'video', '/profile/upload/2025/11/18/神奇的偏旁——心字底.mp4', 48234496, '神奇的偏旁——心字底.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——提土旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——提土旁.mp4', 48234496, '神奇的偏旁——提土旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——月字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——月字旁.mp4', 48234496, '神奇的偏旁——月字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——爪字头', 'video', '/profile/upload/2025/11/18/神奇的偏旁——爪字头.mp4', 48234496, '神奇的偏旁——爪字头.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——竖心旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——竖心旁.mp4', 48234496, '神奇的偏旁——竖心旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——竹字头', 'video', '/profile/upload/2025/11/18/神奇的偏旁——竹字头.mp4', 48234496, '神奇的偏旁——竹字头.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——米字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——米字旁.mp4', 48234496, '神奇的偏旁——米字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——走之旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——走之旁.mp4', 48234496, '神奇的偏旁——走之旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的偏旁——金字旁', 'video', '/profile/upload/2025/11/18/神奇的偏旁——金字旁.mp4', 48234496, '神奇的偏旁——金字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的校车序章', 'video', '/profile/upload/2025/11/18/神奇的校车序章.mp4', 53084160, '神奇的校车序章.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——反犬旁', 'video', '/profile/upload/2025/11/18/神奇的部首——反犬旁.mp4', 48234496, '神奇的部首——反犬旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——右耳刀', 'video', '/profile/upload/2025/11/18/神奇的部首——右耳刀.mp4', 48234496, '神奇的部首——右耳刀.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——左耳刀', 'video', '/profile/upload/2025/11/18/神奇的部首——左耳刀.mp4', 48234496, '神奇的部首——左耳刀.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——广字头', 'video', '/profile/upload/2025/11/18/神奇的部首——广字头.mp4', 48234496, '神奇的部首——广字头.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——示字旁', 'video', '/profile/upload/2025/11/18/神奇的部首——示字旁.mp4', 48234496, '神奇的部首——示字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——绞丝旁', 'video', '/profile/upload/2025/11/18/神奇的部首——绞丝旁.mp4', 48234496, '神奇的部首——绞丝旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——雨字头', 'video', '/profile/upload/2025/11/18/神奇的部首——雨字头.mp4', 48234496, '神奇的部首——雨字头.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神奇的部首——页字旁', 'video', '/profile/upload/2025/11/18/神奇的部首——页字旁.mp4', 48234496, '神奇的部首——页字旁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('神话人物——哼哈二将', 'video', '/profile/upload/2025/11/18/神话人物——哼哈二将.mp4', 53084160, '神话人物——哼哈二将.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('米饭、淀粉和碘酒的变化', 'video', '/profile/upload/2025/11/18/米饭、淀粉和碘酒的变化.mp4', 60311552, '米饭、淀粉和碘酒的变化.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('细菌与病毒', 'video', '/profile/upload/2025/11/18/细菌与病毒.mp4', 60311552, '细菌与病毒.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('给动物分类', 'video', '/profile/upload/2025/11/18/给动物分类.mp4', 60311552, '给动物分类.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('脚掌侧拉球', 'video', '/profile/upload/2025/11/18/脚掌侧拉球.mp4', 53084160, '脚掌侧拉球.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('英语Happy Halloween!', 'video', '/profile/upload/2025/11/18/英语Happy Halloween!.mp4', 53084160, '英语Happy Halloween!.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('营养要均衡', 'video', '/profile/upload/2025/11/18/营养要均衡.mp4', 60311552, '营养要均衡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('蚕变了新模样', 'video', '/profile/upload/2025/11/18/蚕变了新模样.mp4', 60311552, '蚕变了新模样.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('蚕长大了', 'video', '/profile/upload/2025/11/18/蚕长大了.mp4', 60311552, '蚕长大了.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('西方礼仪', 'video', '/profile/upload/2025/11/18/西方礼仪.mp4', 66060288, '西方礼仪.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('西方礼仪_20251118150718A001', 'video', '/profile/upload/2025/11/18/西方礼仪_20251118150718A001.mp4', 66060288, '西方礼仪_20251118150718A001.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('西方礼仪_20251118162650A005', 'video', '/profile/upload/2025/11/18/西方礼仪_20251118162650A005.mp4', 66060288, '西方礼仪_20251118162650A005.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('观察我们的身体', 'video', '/profile/upload/2025/11/18/观察我们的身体.mp4', 60311552, '观察我们的身体.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('认识会意字', 'video', '/profile/upload/2025/11/18/认识会意字.mp4', 48234496, '认识会意字.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('认识其他动物的卵', 'video', '/profile/upload/2025/11/18/认识其他动物的卵.mp4', 60311552, '认识其他动物的卵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('认识反义词——稀和稠', 'video', '/profile/upload/2025/11/18/认识反义词——稀和稠.mp4', 48234496, '认识反义词——稀和稠.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('认识青字家族', 'video', '/profile/upload/2025/11/18/认识青字家族.mp4', 48234496, '认识青字家族.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('证明地球在自转', 'video', '/profile/upload/2025/11/18/证明地球在自转.mp4', 60311552, '证明地球在自转.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('词语系列——AABB式词语', 'video', '/profile/upload/2025/11/18/词语系列——AABB式词语.mp4', 48234496, '词语系列——AABB式词语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('词语系列——AABB式词语', 'video', '/profile/upload/2025/11/18/词语系列——AABB式词语.mp4', 48234496, '词语系列——AABB式词语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('词语系列——ABB式词语', 'video', '/profile/upload/2025/11/18/词语系列——ABB式词语.mp4', 48234496, '词语系列——ABB式词语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('语文:有趣的句子', 'video', '/profile/upload/2025/11/18/语文:有趣的句子.mp4', 53084160, '语文:有趣的句子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('语文有趣的句子_20251118150758A002', 'video', '/profile/upload/2025/11/18/语文有趣的句子_20251118150758A002.mp4', 53084160, '语文有趣的句子_20251118150758A002.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('语文有趣的句子_20251118162733A006', 'video', '/profile/upload/2025/11/18/语文有趣的句子_20251118162733A006.mp4', 53084160, '语文有趣的句子_20251118162733A006.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('说文解字——手', 'video', '/profile/upload/2025/11/18/说文解字——手.mp4', 48234496, '说文解字——手.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('谁先迎来黎明', 'video', '/profile/upload/2025/11/18/谁先迎来黎明.mp4', 60311552, '谁先迎来黎明.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('趣味语文——十二生肖(上)', 'video', '/profile/upload/2025/11/18/趣味语文——十二生肖(上).mp4', 66060288, '趣味语文——十二生肖(上).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('趣味语文——十二生肖(下)', 'video', '/profile/upload/2025/11/18/趣味语文——十二生肖(下).mp4', 66060288, '趣味语文——十二生肖(下).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('迎接蚕宝宝的到来', 'video', '/profile/upload/2025/11/18/迎接蚕宝宝的到来.mp4', 60311552, '迎接蚕宝宝的到来.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('近义词辨析——师傅和师父', 'video', '/profile/upload/2025/11/18/近义词辨析——师傅和师父.mp4', 48234496, '近义词辨析——师傅和师父.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('近义词辨析——必需VS必须', 'video', '/profile/upload/2025/11/18/近义词辨析——必需VS必须.mp4', 48234496, '近义词辨析——必需VS必须.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('近义词辨析——愿望、希望、盼望', 'video', '/profile/upload/2025/11/18/近义词辨析——愿望、希望、盼望.mp4', 53084160, '近义词辨析——愿望、希望、盼望.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('近义词辨析——流传和留传', 'video', '/profile/upload/2025/11/18/近义词辨析——流传和留传.mp4', 48234496, '近义词辨析——流传和留传.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('近义词辨析——震动和振动', 'video', '/profile/upload/2025/11/18/近义词辨析——震动和振动.mp4', 48234496, '近义词辨析——震动和振动.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('通过感官来发现', 'video', '/profile/upload/2025/11/18/通过感官来发现.mp4', 60311552, '通过感官来发现.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('面包发霉了', 'video', '/profile/upload/2025/11/18/面包发霉了.mp4', 60311552, '面包发霉了.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('食品包装上的信息', 'video', '/profile/upload/2025/11/18/食品包装上的信息.mp4', 60311552, '食品包装上的信息.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('食物中的营养', 'video', '/profile/upload/2025/11/18/食物中的营养.mp4', 60311552, '食物中的营养.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
/*!40000 ALTER TABLE `courseware` ENABLE KEYS */;
UNLOCK TABLES;

View File

@ -1,198 +0,0 @@
LOCK TABLES `courseware` WRITE;
/*!40000 ALTER TABLE `courseware` DISABLE KEYS */;
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
-- 从13.夜色.mp4开始的批量插入
('13.夜色', 'video', '/profile/upload/2025/11/18/13.夜色.mp4', 38580224, '13.夜色.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.妙解成语——连绵不断', 'video', '/profile/upload/2025/11/18/13.妙解成语——连绵不断.mp4', 47185920, '13.妙解成语——连绵不断.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.江畔独步寻花其六', 'video', '/profile/upload/2025/11/18/13.江畔独步寻花其六.mp4', 42949672, '13.江畔独步寻花其六.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13古诗三首-题临安邸', 'video', '/profile/upload/2025/11/18/13古诗三首-题临安邸.MP4', 56623104, '13古诗三首-题临安邸.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13故宫博物馆', 'video', '/profile/upload/2025/11/18/13故宫博物馆.MP4', 60311552, '13故宫博物馆.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13白桦', 'video', '/profile/upload/2025/11/18/13白桦.ts', 638976000, '13白桦.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14 普罗米修斯', 'video', '/profile/upload/2025/11/18/14 普罗米修斯.ts', 644608000, '14 普罗米修斯.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14 第6课《景阳冈》第二课时', 'video', '/profile/upload/2025/11/18/14 第6课《景阳冈》第二课时 .mp4', 72477184, '14 第6课《景阳冈》第二课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14 第7课《汤姆·索亚历险记节选', 'video', '/profile/upload/2025/11/18/14 第7课《汤姆·索亚历险记节选.mp4', 73400320, '14 第7课《汤姆·索亚历险记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.ai、ei、ui', 'video', '/profile/upload/2025/11/18/14.ai、ei、ui.mp4', 36700160, '14.ai、ei、ui.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.【课文9】枫树上的喜鹊', 'video', '/profile/upload/2025/11/18/14.【课文9】枫树上的喜鹊.mp4', 50331648, '14.【课文9】枫树上的喜鹊.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.写作小灵通-“巧用连续动词活化人物”', 'video', '/profile/upload/2025/11/18/14.写作小灵通-“巧用连续动词活化人物”.mp4', 49152000, '14.写作小灵通-“巧用连续动词活化人物”.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.妙解成语——天壤之别', 'video', '/profile/upload/2025/11/18/14.妙解成语——天壤之别.mp4', 44040192, '14.妙解成语——天壤之别.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.总也倒不了的老屋', 'video', '/profile/upload/2025/11/18/14.总也倒不了的老屋.mp4', 38580224, '14.总也倒不了的老屋.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.端午粽', 'video', '/profile/upload/2025/11/18/14.端午粽.mp4', 39845888, '14.端午粽.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.纸的发明', 'video', '/profile/upload/2025/11/18/14.纸的发明.mp4', 53084160, '14.纸的发明.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.题西林壁', 'video', '/profile/upload/2025/11/18/14.题西林壁.mp4', 42949672, '14.题西林壁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14.黄山奇石', 'video', '/profile/upload/2025/11/18/14.黄山奇石.mp4', 51609600, '14.黄山奇石.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14古诗三首-己亥杂诗', 'video', '/profile/upload/2025/11/18/14古诗三首-己亥杂诗.MP4', 56623104, '14古诗三首-己亥杂诗.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14在天晴了的时候', 'video', '/profile/upload/2025/11/18/14在天晴了的时候.ts', 60311552, '14在天晴了的时候.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('14桥', 'video', '/profile/upload/2025/11/18/14桥.MP4', 638976000, '14桥.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15 女娲补天', 'video', '/profile/upload/2025/11/18/15 女娲补天.ts', 644608000, '15 女娲补天.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15 第7课《汤姆·索亚历险记节选', 'video', '/profile/upload/2025/11/18/15 第7课《汤姆·索亚历险记节选.mp4', 71722496, '15 第7课《汤姆·索亚历险记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15 第7课《猴王出世》', 'video', '/profile/upload/2025/11/18/15 第7课《猴王出世》 .mp4', 72477184, '15 第7课《猴王出世》 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.ao、ou、iu', 'video', '/profile/upload/2025/11/18/15.ao、ou、iu.mp4', 36700160, '15.ao、ou、iu.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.【课文10】沙滩上的童话', 'video', '/profile/upload/2025/11/18/15.【课文10】沙滩上的童话.mp4', 50331648, '15.【课文10】沙滩上的童话.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.写作小灵通-柚子茶里的母爱', 'video', '/profile/upload/2025/11/18/15.写作小灵通-柚子茶里的母爱.mp4', 49152000, '15.写作小灵通-柚子茶里的母爱.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.妙解成语——张冠李戴', 'video', '/profile/upload/2025/11/18/15.妙解成语——张冠李戴.mp4', 44040192, '15.妙解成语——张冠李戴.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.彩虹', 'video', '/profile/upload/2025/11/18/15.彩虹.mp4', 38011904, '15.彩虹.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.日月潭', 'video', '/profile/upload/2025/11/18/15.日月潭.mp4', 48234496, '15.日月潭.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.游山西村', 'video', '/profile/upload/2025/11/18/15.游山西村.mp4', 42949672, '15.游山西村.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.胡萝卜先生的长胡子', 'video', '/profile/upload/2025/11/18/15.胡萝卜先生的长胡子.mp4', 53084160, '15.胡萝卜先生的长胡子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15.赵州桥', 'video', '/profile/upload/2025/11/18/15.赵州桥.mp4', 51609600, '15.赵州桥.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15少年中国说节选', 'video', '/profile/upload/2025/11/18/15少年中国说节选.MP4', 56623104, '15少年中国说节选.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15猫', 'video', '/profile/upload/2025/11/18/15猫.ts', 60311552, '15猫.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('15穷人', 'video', '/profile/upload/2025/11/18/15穷人.MP4', 638976000, '15穷人.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16 第8课《红楼春趣》', 'video', '/profile/upload/2025/11/18/16 第8课《红楼春趣》 .mp4', 72477184, '16 第8课《红楼春趣》 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16 第二单元 口语交际:同读一本书', 'video', '/profile/upload/2025/11/18/16 第二单元 口语交际:同读一本书.mp4', 73400320, '16 第二单元 口语交际:同读一本书.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16 麻雀', 'video', '/profile/upload/2025/11/18/16 麻雀.ts', 650117120, '16 麻雀.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.ie、ue、er', 'video', '/profile/upload/2025/11/18/16.ie、ue、er.mp4', 36700160, '16.ie、ue、er.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.【课文11】我是一只小虫子', 'video', '/profile/upload/2025/11/18/16.【课文11】我是一只小虫子.mp4', 50331648, '16.【课文11】我是一只小虫子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.一幅名扬中外的画', 'video', '/profile/upload/2025/11/18/16.一幅名扬中外的画.mp4', 48234496, '16.一幅名扬中外的画.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.不会叫的狗', 'video', '/profile/upload/2025/11/18/16.不会叫的狗.mp4', 53084160, '16.不会叫的狗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.写作小灵通-学习通知', 'video', '/profile/upload/2025/11/18/16.写作小灵通-学习通知.mp4', 49152000, '16.写作小灵通-学习通知.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.动物儿歌', 'video', '/profile/upload/2025/11/18/16.动物儿歌.mp4', 38580224, '16.动物儿歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.妙解成语——东施效颦', 'video', '/profile/upload/2025/11/18/16.妙解成语——东施效颦.mp4', 47185920, '16.妙解成语——东施效颦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.葡萄沟', 'video', '/profile/upload/2025/11/18/16.葡萄沟.mp4', 39845888, '16.葡萄沟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16.黄鹤楼送孟浩然之广陵一', 'video', '/profile/upload/2025/11/18/16.黄鹤楼送孟浩然之广陵一.mp4', 42949672, '16.黄鹤楼送孟浩然之广陵一.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16圆明园的毁灭', 'video', '/profile/upload/2025/11/18/16圆明园的毁灭.MP4', 56623104, '16圆明园的毁灭.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16在柏林', 'video', '/profile/upload/2025/11/18/16在柏林.MP4', 60311552, '16在柏林.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('16母鸡', 'video', '/profile/upload/2025/11/18/16母鸡.ts', 638976000, '16母鸡.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17 爬天都峰', 'video', '/profile/upload/2025/11/18/17 爬天都峰.ts', 644608000, '17 爬天都峰.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17 第二单元 习作:写作品梗概', 'video', '/profile/upload/2025/11/18/17 第二单元 习作:写作品梗概.mp4', 704643072, '17 第二单元 习作:写作品梗概.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17 第二单元 口语交际', 'video', '/profile/upload/2025/11/18/17 第二单元 口语交际 .mp4', 68157440, '17 第二单元 口语交际 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.an、en、in、un、ün', 'video', '/profile/upload/2025/11/18/17.an、en、in、un、ün.mp4', 36700160, '17.an、en、in、un、ün.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.【课文12】寓言二则之亡羊补牢', 'video', '/profile/upload/2025/11/18/17.【课文12】寓言二则之亡羊补牢.mp4', 50331648, '17.【课文12】寓言二则之亡羊补牢.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.写作小灵通-写好春天的秘诀', 'video', '/profile/upload/2025/11/18/17.写作小灵通-写好春天的秘诀.mp4', 49152000, '17.写作小灵通-写好春天的秘诀.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.古对今', 'video', '/profile/upload/2025/11/18/17.古对今.mp4', 38580224, '17.古对今.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.坐井观天', 'video', '/profile/upload/2025/11/18/17.坐井观天.mp4', 48234496, '17.坐井观天.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.妙解成语——亡羊补牢', 'video', '/profile/upload/2025/11/18/17.妙解成语——亡羊补牢.mp4', 44040192, '17.妙解成语——亡羊补牢.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.搭船的鸟', 'video', '/profile/upload/2025/11/18/17.搭船的鸟.mp4', 53084160, '17.搭船的鸟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.花钟', 'video', '/profile/upload/2025/11/18/17.花钟.mp4', 51609600, '17.花钟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17.黄鹤楼送孟浩然之广陵', 'video', '/profile/upload/2025/11/18/17.黄鹤楼送孟浩然之广陵.mp4', 42949672, '17.黄鹤楼送孟浩然之广陵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17夏天里的成长', 'video', '/profile/upload/2025/11/18/17夏天里的成长.MP4', 56623104, '17夏天里的成长.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17小岛', 'video', '/profile/upload/2025/11/18/17小岛.MP4', 60311552, '17小岛.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('17白鹅', 'video', '/profile/upload/2025/11/18/17白鹅.ts', 638976000, '17白鹅.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18 语文园地二', 'video', '/profile/upload/2025/11/18/18 语文园地二.mp4', 66060288, '18 语文园地二.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18 海上日出', 'video', '/profile/upload/2025/11/18/18 海上日出.MP4', 59183104, '18 海上日出.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18 牛和鹅', 'video', '/profile/upload/2025/11/18/18 牛和鹅.ts', 644608000, '18 牛和鹅.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18 第二单元 习作 写读后感', 'video', '/profile/upload/2025/11/18/18 第二单元 习作 写读后感 .mp4', 71722496, '18 第二单元 习作 写读后感 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.ang、eng、ing、ong', 'video', '/profile/upload/2025/11/18/18.ang、eng、ing、ong.mp4', 36700160, '18.ang、eng、ing、ong.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.【课文12】寓言二则之揠苗助长', 'video', '/profile/upload/2025/11/18/18.【课文12】寓言二则之揠苗助长.mp4', 50331648, '18.【课文12】寓言二则之揠苗助长.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.写作小灵通-描写小动物', 'video', '/profile/upload/2025/11/18/18.写作小灵通-描写小动物.mp4', 49152000, '18.写作小灵通-描写小动物.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.妙解成语——揠苗助长', 'video', '/profile/upload/2025/11/18/18.妙解成语——揠苗助长.mp4', 44040192, '18.妙解成语——揠苗助长.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.寒号鸟', 'video', '/profile/upload/2025/11/18/18.寒号鸟.mp4', 38580224, '18.寒号鸟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.操场上', 'video', '/profile/upload/2025/11/18/18.操场上.mp4', 39845888, '18.操场上.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.蜜蜂', 'video', '/profile/upload/2025/11/18/18.蜜蜂.mp4', 53084160, '18.蜜蜂.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.送元二使安西', 'video', '/profile/upload/2025/11/18/18.送元二使安西.mp4', 42949672, '18.送元二使安西.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18.金色的草地', 'video', '/profile/upload/2025/11/18/18.金色的草地.mp4', 51609600, '18.金色的草地.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18太阳', 'video', '/profile/upload/2025/11/18/18太阳.MP4', 56623104, '18太阳.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('18盼', 'video', '/profile/upload/2025/11/18/18盼.MP4', 60311552, '18盼.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19 第二单元 快乐读书吧--漫步世界名著花园', 'video', '/profile/upload/2025/11/18/19 第二单元 快乐读书吧--漫步世界名著花园.mp4', 73400320, '19 第二单元 快乐读书吧--漫步世界名著花园.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19 语文园地 二', 'video', '/profile/upload/2025/11/18/19 语文园地 二 .mp4', 66060288, '19 语文园地 二 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19 一只窝囊的大老虎', 'video', '/profile/upload/2025/11/18/19 一只窝囊的大老虎.ts', 644608000, '19 一只窝囊的大老虎.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.【课文13】画杨桃', 'video', '/profile/upload/2025/11/18/19.【课文13】画杨桃.mp4', 50331648, '19.【课文13】画杨桃.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.人之初', 'video', '/profile/upload/2025/11/18/19.人之初.mp4', 38580224, '19.人之初.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.写作小灵通-描写雨', 'video', '/profile/upload/2025/11/18/19.写作小灵通-描写雨.mp4', 49152000, '19.写作小灵通-描写雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.古诗三首 望天门山', 'video', '/profile/upload/2025/11/18/19.古诗三首 望天门山.mp4', 44040192, '19.古诗三首 望天门山.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.妙解成语——自相矛盾', 'video', '/profile/upload/2025/11/18/19.妙解成语——自相矛盾.mp4', 47185920, '19.妙解成语——自相矛盾.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.小虾', 'video', '/profile/upload/2025/11/18/19.小虾.mp4', 39845888, '19.小虾.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.我要的是葫芦', 'video', '/profile/upload/2025/11/18/19.我要的是葫芦.mp4', 48234496, '19.我要的是葫芦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.泊船瓜洲', 'video', '/profile/upload/2025/11/18/19.泊船瓜洲.mp4', 42949672, '19.泊船瓜洲.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19.秋天', 'video', '/profile/upload/2025/11/18/19.秋天.mp4', 38011904, '19.秋天.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19古诗三首-浪淘沙', 'video', '/profile/upload/2025/11/18/19古诗三首-浪淘沙.MP4', 56623104, '19古诗三首-浪淘沙.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19松鼠', 'video', '/profile/upload/2025/11/18/19松鼠.MP4', 60311552, '19松鼠.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('19记金华的双龙洞', 'video', '/profile/upload/2025/11/18/19记金华的双龙洞.ts', 638976000, '19记金华的双龙洞.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('2.妙解修辞-拟人', 'video', '/profile/upload/2025/11/18/2.妙解修辞-拟人.mp4', 44040192, '2.妙解修辞-拟人.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20 快乐读书吧—读古典名著,品百味人生', 'video', '/profile/upload/2025/11/18/20 快乐读书吧—读古典名著,品百味人生 .mp4', 72477184, '20 快乐读书吧—读古典名著,品百味人生 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20 第8课《匆匆》', 'video', '/profile/upload/2025/11/18/20 第8课《匆匆》.mp4', 68157440, '20 第8课《匆匆》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20 陀螺', 'video', '/profile/upload/2025/11/18/20 陀螺.ts', 650117120, '20 陀螺.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.【课文14】小马过河', 'video', '/profile/upload/2025/11/18/20.【课文14】小马过河.mp4', 50331648, '20.【课文14】小马过河.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.古诗三首 饮湖上初晴后雨', 'video', '/profile/upload/2025/11/18/20.古诗三首 饮湖上初晴后雨.mp4', 44040192, '20.古诗三首 饮湖上初晴后雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.古诗二首之池上', 'video', '/profile/upload/2025/11/18/20.古诗二首之池上.mp4', 42949672, '20.古诗二首之池上.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.大禹治水', 'video', '/profile/upload/2025/11/18/20.大禹治水.mp4', 48234496, '20.大禹治水.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.妙解成语——迫在眉睫', 'video', '/profile/upload/2025/11/18/20.妙解成语——迫在眉睫.mp4', 47185920, '20.妙解成语——迫在眉睫.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.小小的船', 'video', '/profile/upload/2025/11/18/20.小小的船.mp4', 38011904, '20.小小的船.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.小真的长头发', 'video', '/profile/upload/2025/11/18/20.小真的长头发.mp4', 53084160, '20.小真的长头发.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.秋思', 'video', '/profile/upload/2025/11/18/20.秋思.mp4', 40265312, '20.秋思.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20.规则的故事', 'video', '/profile/upload/2025/11/18/20.规则的故事.mp4', 39845888, '20.规则的故事.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20古诗三首-江南春', 'video', '/profile/upload/2025/11/18/20古诗三首-江南春.MP4', 56623104, '20古诗三首-江南春.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20小英雄雨来节选', 'video', '/profile/upload/2025/11/18/20小英雄雨来节选.ts', 60311552, '20小英雄雨来节选.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('20慈母情深', 'video', '/profile/upload/2025/11/18/20慈母情深.MP4', 638976000, '20慈母情深.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21 古诗三首', 'video', '/profile/upload/2025/11/18/21 古诗三首.ts', 644608000, '21 古诗三首.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21 第8课《匆匆》', 'video', '/profile/upload/2025/11/18/21 第8课《匆匆》.mp4', 71722496, '21 第8课《匆匆》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21 综合性学习—遨游汉字王国(第一课时)', 'video', '/profile/upload/2025/11/18/21 综合性学习—遨游汉字王国(第一课时).mp4', 72477184, '21 综合性学习—遨游汉字王国(第一课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.【课文15】古诗二首之晓出净慈寺送林子方', 'video', '/profile/upload/2025/11/18/21.【课文15】古诗二首之晓出净慈寺送林子方.mp4', 50331648, '21.【课文15】古诗二首之晓出净慈寺送林子方.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.写作小灵通-童年趣事', 'video', '/profile/upload/2025/11/18/21.写作小灵通-童年趣事.mp4', 49152000, '21.写作小灵通-童年趣事.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.古诗三首 望洞庭', 'video', '/profile/upload/2025/11/18/21.古诗三首 望洞庭.mp4', 44040192, '21.古诗三首 望洞庭.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.古诗二首之小池', 'video', '/profile/upload/2025/11/18/21.古诗二首之小池.mp4', 42949672, '21.古诗二首之小池.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.妙解成语——喜出望外', 'video', '/profile/upload/2025/11/18/21.妙解成语——喜出望外.mp4', 47185920, '21.妙解成语——喜出望外.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.我变成了一棵树', 'video', '/profile/upload/2025/11/18/21.我变成了一棵树.mp4', 53084160, '21.我变成了一棵树.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.朱德的扁担', 'video', '/profile/upload/2025/11/18/21.朱德的扁担.mp4', 48234496, '21.朱德的扁担.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.江南', 'video', '/profile/upload/2025/11/18/21.江南.mp4', 38011904, '21.江南.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21.牧童', 'video', '/profile/upload/2025/11/18/21.牧童.mp4', 40265312, '21.牧童.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21古诗三首-书湖阴先生壁', 'video', '/profile/upload/2025/11/18/21古诗三首-书湖阴先生壁.MP4', 56623104, '21古诗三首-书湖阴先生壁.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21我们家的男子汉', 'video', '/profile/upload/2025/11/18/21我们家的男子汉.ts', 60311552, '21我们家的男子汉.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('21父爱之舟', 'video', '/profile/upload/2025/11/18/21父爱之舟.MP4', 638976000, '21父爱之舟.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22 为中华之崛起而读书', 'video', '/profile/upload/2025/11/18/22 为中华之崛起而读书.ts', 644608000, '22 为中华之崛起而读书.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22 第9课《那个星期天》', 'video', '/profile/upload/2025/11/18/22 第9课《那个星期天》.mp4', 68157440, '22 第9课《那个星期天》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22 综合性学习—遨游汉字王国(第二课时)', 'video', '/profile/upload/2025/11/18/22 综合性学习—遨游汉字王国(第二课时).mp4', 72477184, '22 综合性学习—遨游汉字王国(第二课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.【课文15】古诗二首之绝句', 'video', '/profile/upload/2025/11/18/22.【课文15】古诗二首之绝句.mp4', 50331648, '22.【课文15】古诗二首之绝句.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.写作小灵通-明信片', 'video', '/profile/upload/2025/11/18/22.写作小灵通-明信片.mp4', 49152000, '22.写作小灵通-明信片.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.四季', 'video', '/profile/upload/2025/11/18/22.四季.mp4', 38011904, '22.四季.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.妙解成语——背井离乡', 'video', '/profile/upload/2025/11/18/22.妙解成语——背井离乡.mp4', 47185920, '22.妙解成语——背井离乡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.富饶的西沙群岛', 'video', '/profile/upload/2025/11/18/22.富饶的西沙群岛.mp4', 53084160, '22.富饶的西沙群岛.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.童年的水墨画', 'video', '/profile/upload/2025/11/18/22.童年的水墨画.mp4', 48234496, '22.童年的水墨画.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.舟过安仁', 'video', '/profile/upload/2025/11/18/22.舟过安仁.mp4', 42949672, '22.舟过安仁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.荷叶圆圆', 'video', '/profile/upload/2025/11/18/22.荷叶圆圆.mp4', 38580224, '22.荷叶圆圆.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22.难忘的泼水节', 'video', '/profile/upload/2025/11/18/22.难忘的泼水节.mp4', 39845888, '22.难忘的泼水节.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22只有一个地球', 'video', '/profile/upload/2025/11/18/22只有一个地球.MP4', 56623104, '22只有一个地球.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22精彩极了和糟糕透了', 'video', '/profile/upload/2025/11/18/22精彩极了和糟糕透了.MP4', 60311552, '22精彩极了和糟糕透了.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('22芦花鞋', 'video', '/profile/upload/2025/11/18/22芦花鞋.ts', 638976000, '22芦花鞋.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23 梅兰芳蓄须', 'video', '/profile/upload/2025/11/18/23 梅兰芳蓄须.ts', 644608000, '23 梅兰芳蓄须.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23 第9课《那个星期天》', 'video', '/profile/upload/2025/11/18/23 第9课《那个星期天》.mp4', 71722496, '23 第9课《那个星期天》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23 综合性学习—遨游汉字王国(第三课时)', 'video', '/profile/upload/2025/11/18/23 综合性学习—遨游汉字王国(第三课时).mp4', 72477184, '23 综合性学习—遨游汉字王国(第三课时).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.【课文16】雷雨', 'video', '/profile/upload/2025/11/18/23.【课文16】雷雨.mp4', 50331648, '23.【课文16】雷雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.亲情', 'video', '/profile/upload/2025/11/18/23.亲情.mp4', 39845888, '23.亲情.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.剃头大师', 'video', '/profile/upload/2025/11/18/23.剃头大师.mp4', 53084160, '23.剃头大师.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.夜宿山寺', 'video', '/profile/upload/2025/11/18/23.夜宿山寺.mp4', 40265312, '23.夜宿山寺.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.妙解成语——琳琅满目', 'video', '/profile/upload/2025/11/18/23.妙解成语——琳琅满目.mp4', 47185920, '23.妙解成语——琳琅满目.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.海滨小城', 'video', '/profile/upload/2025/11/18/23.海滨小城.mp4', 48234496, '23.海滨小城.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.画', 'video', '/profile/upload/2025/11/18/23.画.mp4', 38011904, '23.画.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.要下雨了', 'video', '/profile/upload/2025/11/18/23.要下雨了.mp4', 38580224, '23.要下雨了.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23.诗经采薇', 'video', '/profile/upload/2025/11/18/23.诗经采薇.mp4', 42949672, '23.诗经采薇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23古诗三首-芙蓉楼送辛渐', 'video', '/profile/upload/2025/11/18/23古诗三首-芙蓉楼送辛渐.ts', 60311552, '23古诗三首-芙蓉楼送辛渐.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23古诗词三首-山居秋暝', 'video', '/profile/upload/2025/11/18/23古诗词三首-山居秋暝.MP4', 56623104, '23古诗词三首-山居秋暝.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('23青山不老', 'video', '/profile/upload/2025/11/18/23青山不老.MP4', 638976000, '23青山不老.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24 延安,我把你追寻', 'video', '/profile/upload/2025/11/18/24 延安,我把你追寻.ts', 644608000, '24 延安,我把你追寻.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24 汉字真有趣 课后练习', 'video', '/profile/upload/2025/11/18/24 汉字真有趣 课后练习.mp4', 66060288, '24 汉字真有趣 课后练习.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24 第三单元 交流平台 初试身手', 'video', '/profile/upload/2025/11/18/24 第三单元 交流平台 初试身手.mp4', 68157440, '24 第三单元 交流平台 初试身手.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.【课文17】要是你在野外迷了路', 'video', '/profile/upload/2025/11/18/24.【课文17】要是你在野外迷了路.mp4', 50331648, '24.【课文17】要是你在野外迷了路.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.大小多少', 'video', '/profile/upload/2025/11/18/24.大小多少.mp4', 38011904, '24.大小多少.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.妙解成语——栩栩如生', 'video', '/profile/upload/2025/11/18/24.妙解成语——栩栩如生.mp4', 47185920, '24.妙解成语——栩栩如生.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.敕勒歌', 'video', '/profile/upload/2025/11/18/24.敕勒歌.mp4', 40265312, '24.敕勒歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.文具的家', 'video', '/profile/upload/2025/11/18/24.文具的家.mp4', 38580224, '24.文具的家.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.春夜喜雨', 'video', '/profile/upload/2025/11/18/24.春夜喜雨.mp4', 42949672, '24.春夜喜雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.美丽的小兴安岭', 'video', '/profile/upload/2025/11/18/24.美丽的小兴安岭.mp4', 53084160, '24.美丽的小兴安岭.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24.肥皂泡', 'video', '/profile/upload/2025/11/18/24.肥皂泡.mp4', 48234496, '24.肥皂泡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24三黑和土地', 'video', '/profile/upload/2025/11/18/24三黑和土地.MP4', 56623104, '24三黑和土地.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24古诗三首-塞下曲', 'video', '/profile/upload/2025/11/18/24古诗三首-塞下曲.ts', 60311552, '24古诗三首-塞下曲.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('24古诗词三首-枫桥夜泊', 'video', '/profile/upload/2025/11/18/24古诗词三首-枫桥夜泊.MP4', 638976000, '24古诗词三首-枫桥夜泊.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25 第三单元 习作例文', 'video', '/profile/upload/2025/11/18/25 第三单元 习作例文.mp4', 72477184, '25 第三单元 习作例文.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25 汉字真有趣 综合训练', 'video', '/profile/upload/2025/11/18/25 汉字真有趣 综合训练.mp4', 73400320, '25 汉字真有趣 综合训练.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25 王戎不取道旁李', 'video', '/profile/upload/2025/11/18/25 王戎不取道旁李.ts', 650117120, '25 王戎不取道旁李.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.【课文18】太空生活趣事多', 'video', '/profile/upload/2025/11/18/25.【课文18】太空生活趣事多.mp4', 50331648, '25.【课文18】太空生活趣事多.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.一分钟', 'video', '/profile/upload/2025/11/18/25.一分钟.mp4', 38011904, '25.一分钟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.大自然的声音', 'video', '/profile/upload/2025/11/18/25.大自然的声音.mp4', 53084160, '25.大自然的声音.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.妙解成语——提心吊胆', 'video', '/profile/upload/2025/11/18/25.妙解成语——提心吊胆.mp4', 47185920, '25.妙解成语——提心吊胆.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.小书包', 'video', '/profile/upload/2025/11/18/25.小书包.mp4', 38580224, '25.小书包.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.我不能失信', 'video', '/profile/upload/2025/11/18/25.我不能失信.mp4', 48234496, '25.我不能失信.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.房兵曹胡马', 'video', '/profile/upload/2025/11/18/25.房兵曹胡马.mp4', 42949672, '25.房兵曹胡马.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25.雾在哪里', 'video', '/profile/upload/2025/11/18/25.雾在哪里.mp4', 39845888, '25.雾在哪里.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25古诗三首-墨梅', 'video', '/profile/upload/2025/11/18/25古诗三首-墨梅.ts', 60311552, '25古诗三首-墨梅.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25古诗词三首-长相思', 'video', '/profile/upload/2025/11/18/25古诗词三首-长相思.MP4', 56623104, '25古诗词三首-长相思.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('25文言文二则-伯牙鼓琴', 'video', '/profile/upload/2025/11/18/25文言文二则-伯牙鼓琴.MP4', 638976000, '25文言文二则-伯牙鼓琴.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
/*!40000 ALTER TABLE `courseware` ENABLE KEYS */;
UNLOCK TABLES;

View File

@ -1,193 +0,0 @@
LOCK TABLES `courseware` WRITE;
/*!40000 ALTER TABLE `courseware` DISABLE KEYS */;
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
-- 从《我爱你,汉字》开始的批量插入
('26 《我爱你,汉字》', 'video', '/profile/upload/2025/11/18/26 《我爱你,汉字》.mp4', 66060288, '26 《我爱你,汉字》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26 第三单元 习作:让真情自然流露', 'video', '/profile/upload/2025/11/18/26 第三单元 习作:让真情自然流露.mp4', 72477184, '26 第三单元 习作:让真情自然流露.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26 西门豹治邺', 'video', '/profile/upload/2025/11/18/26 西门豹治邺.ts', 650117120, '26 西门豹治邺.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.【课文19】大象的耳朵', 'video', '/profile/upload/2025/11/18/26.【课文19】大象的耳朵.mp4', 50331648, '26.【课文19】大象的耳朵.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.以古诗《过分水岭》为例', 'video', '/profile/upload/2025/11/18/26.以古诗《过分水岭》为例.mp4', 49152000, '26.以古诗《过分水岭》为例.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.动物王国开大会', 'video', '/profile/upload/2025/11/18/26.动物王国开大会.mp4', 38580224, '26.动物王国开大会.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.妙解成语——浩浩荡荡', 'video', '/profile/upload/2025/11/18/26.妙解成语——浩浩荡荡.mp4', 47185920, '26.妙解成语——浩浩荡荡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.我们奇妙的世界', 'video', '/profile/upload/2025/11/18/26.我们奇妙的世界.mp4', 53084160, '26.我们奇妙的世界.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.日月明', 'video', '/profile/upload/2025/11/18/26.日月明.mp4', 38011904, '26.日月明.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.父亲、树林和鸟', 'video', '/profile/upload/2025/11/18/26.父亲、树林和鸟.mp4', 48234496, '26.父亲、树林和鸟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26.雪孩子', 'video', '/profile/upload/2025/11/18/26.雪孩子.mp4', 39845888, '26.雪孩子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26四季之美', 'video', '/profile/upload/2025/11/18/26四季之美.MP4', 56623104, '26四季之美.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26文言文二则-书戴嵩画牛', 'video', '/profile/upload/2025/11/18/26文言文二则-书戴嵩画牛.MP4', 60311552, '26文言文二则-书戴嵩画牛.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('26文言文二则-囊萤夜读', 'video', '/profile/upload/2025/11/18/26文言文二则-囊萤夜读.ts', 638976000, '26文言文二则-囊萤夜读.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27 故事二则', 'video', '/profile/upload/2025/11/18/27 故事二则.ts', 644608000, '27 故事二则.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27 第10课《古诗三首》马诗', 'video', '/profile/upload/2025/11/18/27 第10课《古诗三首》马诗.mp4', 68157440, '27 第10课《古诗三首》马诗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27 第9课《古诗三首》从军行', 'video', '/profile/upload/2025/11/18/27 第9课《古诗三首》从军行.mp4', 72477184, '27 第9课《古诗三首》从军行.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.【课文20】蜘蛛开店', 'video', '/profile/upload/2025/11/18/27.【课文20】蜘蛛开店.mp4', 50331648, '27.【课文20】蜘蛛开店.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.十五从军征上', 'video', '/profile/upload/2025/11/18/27.十五从军征上.mp4', 42949672, '27.十五从军征上.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.升国旗', 'video', '/profile/upload/2025/11/18/27.升国旗.mp4', 38011904, '27.升国旗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.妙解成语——金碧辉煌', 'video', '/profile/upload/2025/11/18/27.妙解成语——金碧辉煌.mp4', 47185920, '27.妙解成语——金碧辉煌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.小猴子下山', 'video', '/profile/upload/2025/11/18/27.小猴子下山.mp4', 38580224, '27.小猴子下山.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.带刺的朋友', 'video', '/profile/upload/2025/11/18/27.带刺的朋友.mp4', 48234496, '27.带刺的朋友.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.海底世界', 'video', '/profile/upload/2025/11/18/27.海底世界.mp4', 53084160, '27.海底世界.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27.狐假虎威', 'video', '/profile/upload/2025/11/18/27.狐假虎威.mp4', 39845888, '27.狐假虎威.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27文言文二则-铁杵成针', 'video', '/profile/upload/2025/11/18/27文言文二则-铁杵成针.ts', 60311552, '27文言文二则-铁杵成针.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27月光曲', 'video', '/profile/upload/2025/11/18/27月光曲.MP4', 56623104, '27月光曲.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('27鸟的天堂', 'video', '/profile/upload/2025/11/18/27鸟的天堂.MP4', 638976000, '27鸟的天堂.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28 专题一词语大闯关', 'video', '/profile/upload/2025/11/18/28 专题一词语大闯关.ts', 644608000, '28 专题一词语大闯关.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28 第10课《古诗三首》石灰吟', 'video', '/profile/upload/2025/11/18/28 第10课《古诗三首》石灰吟.mp4', 68157440, '28 第10课《古诗三首》石灰吟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28 第9课《古诗三首》秋夜将晓出篱门迎凉有感', 'video', '/profile/upload/2025/11/18/28 第9课《古诗三首》秋夜将晓出篱门迎凉有感.mp4', 72477184, '28 第9课《古诗三首》秋夜将晓出篱门迎凉有感.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.【课文21】青蛙卖泥塘', 'video', '/profile/upload/2025/11/18/28.【课文21】青蛙卖泥塘.mp4', 50331648, '28.【课文21】青蛙卖泥塘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.十五从军征下', 'video', '/profile/upload/2025/11/18/28.十五从军征下.mp4', 44040192, '28.十五从军征下.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.司马光', 'video', '/profile/upload/2025/11/18/28.司马光.mp4', 42949672, '28.司马光.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.妙解成语——赏心悦目', 'video', '/profile/upload/2025/11/18/28.妙解成语——赏心悦目.mp4', 47185920, '28.妙解成语——赏心悦目.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.影子', 'video', '/profile/upload/2025/11/18/28.影子.mp4', 38011904, '28.影子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.棉花姑娘', 'video', '/profile/upload/2025/11/18/28.棉花姑娘.mp4', 38580224, '28.棉花姑娘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.火烧云', 'video', '/profile/upload/2025/11/18/28.火烧云.mp4', 53084160, '28.火烧云.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28.狐狸分奶酪', 'video', '/profile/upload/2025/11/18/28.狐狸分奶酪.mp4', 48234496, '28.狐狸分奶酪.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28诺曼D号遇难记', 'video', '/profile/upload/2025/11/18/28诺曼D号遇难记.ts', 60311552, '28诺曼D号遇难记.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28京剧趣谈', 'video', '/profile/upload/2025/11/18/28京剧趣谈.MP4', 56623104, '28京剧趣谈.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('28月迹', 'video', '/profile/upload/2025/11/18/28月迹.MP4', 638976000, '28月迹.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29 专题二成语争霸赛', 'video', '/profile/upload/2025/11/18/29 专题二成语争霸赛.ts', 644608000, '29 专题二成语争霸赛.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29 第10课《古诗三首》竹石', 'video', '/profile/upload/2025/11/18/29 第10课《古诗三首》竹石.mp4', 68157440, '29 第10课《古诗三首》竹石.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29 第9课《古诗三首》闻官军收河南河北', 'video', '/profile/upload/2025/11/18/29 第9课《古诗三首》闻官军收河南河北.mp4', 72477184, '29 第9课《古诗三首》闻官军收河南河北.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.【课文22】小毛虫', 'video', '/profile/upload/2025/11/18/29.【课文22】小毛虫.mp4', 50331648, '29.【课文22】小毛虫.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.咕咚', 'video', '/profile/upload/2025/11/18/29.咕咚.mp4', 38580224, '29.咕咚.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.妙解成语——依依不舍', 'video', '/profile/upload/2025/11/18/29.妙解成语——依依不舍.mp4', 47185920, '29.妙解成语——依依不舍.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.慢性子裁缝和急性子顾客', 'video', '/profile/upload/2025/11/18/29.慢性子裁缝和急性子顾客.mp4', 53084160, '29.慢性子裁缝和急性子顾客.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.掌声', 'video', '/profile/upload/2025/11/18/29.掌声.mp4', 48234496, '29.掌声.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.比尾巴', 'video', '/profile/upload/2025/11/18/29.比尾巴.mp4', 38011904, '29.比尾巴.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.池上', 'video', '/profile/upload/2025/11/18/29.池上.mp4', 40265312, '29.池上.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29.纸船和风筝', 'video', '/profile/upload/2025/11/18/29.纸船和风筝.mp4', 39845888, '29.纸船和风筝.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29古人谈读书', 'video', '/profile/upload/2025/11/18/29古人谈读书.MP4', 56623104, '29古人谈读书.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29少年闰土', 'video', '/profile/upload/2025/11/18/29少年闰土.MP4', 60311552, '29少年闰土.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('29黄继光', 'video', '/profile/upload/2025/11/18/29黄继光.ts', 638976000, '29黄继光.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('3.庄周梦蝶-张开想象的翅膀', 'video', '/profile/upload/2025/11/18/3.庄周梦蝶-张开想象的翅膀.mp4', 44040192, '3.庄周梦蝶-张开想象的翅膀.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30 专题三句子大变身', 'video', '/profile/upload/2025/11/18/30 专题三句子大变身.ts', 644608000, '30 专题三句子大变身.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30 第10课《青山处处埋忠骨》第一课时', 'video', '/profile/upload/2025/11/18/30 第10课《青山处处埋忠骨》第一课时.mp4', 68157440, '30 第10课《青山处处埋忠骨》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30 第11课《十六年前的回忆》', 'video', '/profile/upload/2025/11/18/30 第11课《十六年前的回忆》.mp4', 72477184, '30 第11课《十六年前的回忆》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.【课文23】祖先的摇篮', 'video', '/profile/upload/2025/11/18/30.【课文23】祖先的摇篮.mp4', 50331648, '30.【课文23】祖先的摇篮.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.妙解成语——安然无恙', 'video', '/profile/upload/2025/11/18/30.妙解成语——安然无恙.mp4', 47185920, '30.妙解成语——安然无恙.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.小壁虎借尾巴', 'video', '/profile/upload/2025/11/18/30.小壁虎借尾巴.mp4', 38580224, '30.小壁虎借尾巴.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.方帽子店', 'video', '/profile/upload/2025/11/18/30.方帽子店.mp4', 53084160, '30.方帽子店.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.游园不值', 'video', '/profile/upload/2025/11/18/30.游园不值.mp4', 42949672, '30.游园不值.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.灰雀', 'video', '/profile/upload/2025/11/18/30.灰雀.mp4', 48234496, '30.灰雀.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.青蛙写诗', 'video', '/profile/upload/2025/11/18/30.青蛙写诗.mp4', 38011904, '30.青蛙写诗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30.风娃娃', 'video', '/profile/upload/2025/11/18/30.风娃娃.mp4', 39845888, '30.风娃娃.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30好的故事', 'video', '/profile/upload/2025/11/18/30好的故事.MP4', 56623104, '30好的故事.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30宝葫芦的秘密节选', 'video', '/profile/upload/2025/11/18/30宝葫芦的秘密节选.ts', 60311552, '30宝葫芦的秘密节选.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('30忆读书', 'video', '/profile/upload/2025/11/18/30忆读书.MP4', 638976000, '30忆读书.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31 专题四阅读小秘籍', 'video', '/profile/upload/2025/11/18/31 专题四阅读小秘籍.ts', 644608000, '31 专题四阅读小秘籍.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31 第10课《青山处处埋忠骨》第二课时', 'video', '/profile/upload/2025/11/18/31 第10课《青山处处埋忠骨》第二课时.mp4', 71722496, '31 第10课《青山处处埋忠骨》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31 第11课《十六年前的回忆》', 'video', '/profile/upload/2025/11/18/31 第11课《十六年前的回忆》.mp4', 72477184, '31 第11课《十六年前的回忆》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.【课文24】当世界年纪还小的时候', 'video', '/profile/upload/2025/11/18/31.【课文24】当世界年纪还小的时候.mp4', 50331648, '31.【课文24】当世界年纪还小的时候.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.妙解成语——藕断丝连', 'video', '/profile/upload/2025/11/18/31.妙解成语——藕断丝连.mp4', 47185920, '31.妙解成语——藕断丝连.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.手术台就是阵地', 'video', '/profile/upload/2025/11/18/31.手术台就是阵地.mp4', 48234496, '31.手术台就是阵地.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.滁州西涧', 'video', '/profile/upload/2025/11/18/31.滁州西涧.mp4', 42949672, '31.滁州西涧.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.漏', 'video', '/profile/upload/2025/11/18/31.漏.mp4', 53084160, '31.漏.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.生字', 'video', '/profile/upload/2025/11/18/31.生字.mp4', 38011904, '31.生字.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.识字篇', 'video', '/profile/upload/2025/11/18/31.识字篇.mp4', 39845888, '31.识字篇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31.雨点儿', 'video', '/profile/upload/2025/11/18/31.雨点儿.mp4', 38580224, '31.雨点儿.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31巨人的花园', 'video', '/profile/upload/2025/11/18/31巨人的花园.ts', 60311552, '31巨人的花园.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31我的伯父鲁迅先生', 'video', '/profile/upload/2025/11/18/31我的伯父鲁迅先生.MP4', 56623104, '31我的伯父鲁迅先生.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('31我的长生果', 'video', '/profile/upload/2025/11/18/31我的长生果.MP4', 638976000, '31我的长生果.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32 第12课《为人民服务》', 'video', '/profile/upload/2025/11/18/32 第12课《为人民服务》.mp4', 66060288, '32 第12课《为人民服务》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32 第12课《清贫》', 'video', '/profile/upload/2025/11/18/32 第12课《清贫》.mp4', 72477184, '32 第12课《清贫》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.【课文25】羿射九日', 'video', '/profile/upload/2025/11/18/32.【课文25】羿射九日.mp4', 50331648, '32.【课文25】羿射九日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.妙解成语——小心翼翼', 'video', '/profile/upload/2025/11/18/32.妙解成语——小心翼翼.mp4', 47185920, '32.妙解成语——小心翼翼.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.成语篇', 'video', '/profile/upload/2025/11/18/32.成语篇.mp4', 39845888, '32.成语篇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.明天要远足', 'video', '/profile/upload/2025/11/18/32.明天要远足.mp4', 38011904, '32.明天要远足.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.枣核', 'video', '/profile/upload/2025/11/18/32.枣核.mp4', 48234496, '32.枣核.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.词语', 'video', '/profile/upload/2025/11/18/32.词语.mp4', 38580224, '32.词语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.音形辨析', 'video', '/profile/upload/2025/11/18/32.音形辨析.mp4', 53084160, '32.音形辨析.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32.题临安邸', 'video', '/profile/upload/2025/11/18/32.题临安邸.mp4', 42949672, '32.题临安邸.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32有的人-纪念鲁迅有感', 'video', '/profile/upload/2025/11/18/32有的人-纪念鲁迅有感.MP4', 56623104, '32有的人-纪念鲁迅有感.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('32海的女儿', 'video', '/profile/upload/2025/11/18/32海的女儿.ts', 638976000, '32海的女儿.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33 第13课《金色的鱼钩》', 'video', '/profile/upload/2025/11/18/33 第13课《金色的鱼钩》.mp4', 68157440, '33 第13课《金色的鱼钩》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33 第四单元 他___了 习作', 'video', '/profile/upload/2025/11/18/33 第四单元 他___了 习作.mp4', 73400320, '33 第四单元 他___了 习作.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.【复习专题】词语辨析', 'video', '/profile/upload/2025/11/18/33.【复习专题】词语辨析.mp4', 50331648, '33.【复习专题】词语辨析.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.反义词', 'video', '/profile/upload/2025/11/18/33.反义词.mp4', 38011904, '33.反义词.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.大还是小', 'video', '/profile/upload/2025/11/18/33.大还是小.mp4', 38580224, '33.大还是小.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.妙解成语——一如既往', 'video', '/profile/upload/2025/11/18/33.妙解成语——一如既往.mp4', 47185920, '33.妙解成语——一如既往.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.常识篇', 'video', '/profile/upload/2025/11/18/33.常识篇.mp4', 39845888, '33.常识篇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.形声字', 'video', '/profile/upload/2025/11/18/33.形声字.mp4', 48234496, '33.形声字.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.村居', 'video', '/profile/upload/2025/11/18/33.村居.mp4', 40265312, '33.村居.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('33.词语积累', 'video', '/profile/upload/2025/11/18/33.词语积累.mp4', 53084160, '33.词语积累.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34 第13课《金色的鱼钩》', 'video', '/profile/upload/2025/11/18/34 第13课《金色的鱼钩》.mp4', 72477184, '34 第13课《金色的鱼钩》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34 语文园地 四', 'video', '/profile/upload/2025/11/18/34 语文园地 四.mp4', 66060288, '34 语文园地 四.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.【复习专题】看图说话', 'video', '/profile/upload/2025/11/18/34.【复习专题】看图说话.mp4', 50331648, '34.【复习专题】看图说话.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.乡村四月', 'video', '/profile/upload/2025/11/18/34.乡村四月.mp4', 42949672, '34.乡村四月.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.句子和标点', 'video', '/profile/upload/2025/11/18/34.句子和标点.mp4', 53084160, '34.句子和标点.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.妙解成语——众星拱月', 'video', '/profile/upload/2025/11/18/34.妙解成语——众星拱月.mp4', 47185920, '34.妙解成语——众星拱月.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.成语', 'video', '/profile/upload/2025/11/18/34.成语.mp4', 39845888, '34.成语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.看图说话', 'video', '/profile/upload/2025/11/18/34.看图说话.mp4', 48234496, '34.看图说话.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('34.项链', 'video', '/profile/upload/2025/11/18/34.项链.mp4', 38011904, '34.项链.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35 第13课《人物描写一组》第一课时', 'video', '/profile/upload/2025/11/18/35 第13课《人物描写一组》第一课时.mp4', 68157440, '35 第13课《人物描写一组》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35 第四单元 口语交际:即兴发言', 'video', '/profile/upload/2025/11/18/35 第四单元 口语交际:即兴发言.mp4', 73400320, '35 第四单元 口语交际:即兴发言.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.“把”字句和“被”字句', 'video', '/profile/upload/2025/11/18/35.“把”字句和“被”字句.mp4', 53084160, '35.“把”字句和“被”字句.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.【复习专题】语言表达:词和句子', 'video', '/profile/upload/2025/11/18/35.【复习专题】语言表达:词和句子.mp4', 50331648, '35.【复习专题】语言表达:词和句子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.妙解成语——玲珑剔透', 'video', '/profile/upload/2025/11/18/35.妙解成语——玲珑剔透.mp4', 47185920, '35.妙解成语——玲珑剔透.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.常识篇', 'video', '/profile/upload/2025/11/18/35.常识篇.mp4', 39845888, '35.常识篇.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.望洞庭', 'video', '/profile/upload/2025/11/18/35.望洞庭.mp4', 42949672, '35.望洞庭.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.生字及识字方法', 'video', '/profile/upload/2025/11/18/35.生字及识字方法.mp4', 48234496, '35.生字及识字方法.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('35.雪地里的小画家', 'video', '/profile/upload/2025/11/18/35.雪地里的小画家.mp4', 38011904, '35.雪地里的小画家.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('36 第13课《人物描写一组》第二课时', 'video', '/profile/upload/2025/11/18/36 第13课《人物描写一组》第二课时.mp4', 72477184, '36 第13课《人物描写一组》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('36 第四单元 习作:心愿', 'video', '/profile/upload/2025/11/18/36 第四单元 习作:心愿.mp4', 73400320, '36 第四单元 习作:心愿.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('36.乌鸦喝水', 'video', '/profile/upload/2025/11/18/36.乌鸦喝水.mp4', 38580224, '36.乌鸦喝水.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('36.妙解成语——斩钉截铁', 'video', '/profile/upload/2025/11/18/36.妙解成语——斩钉截铁.mp4', 47185920, '36.妙解成语——斩钉截铁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('36.游子吟', 'video', '/profile/upload/2025/11/18/36.游子吟.mp4', 42949672, '36.游子吟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('37 语文园地四', 'video', '/profile/upload/2025/11/18/37 语文园地四.mp4', 66060288, '37 语文园地四.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('37.元日', 'video', '/profile/upload/2025/11/18/37.元日.mp4', 42949672, '37.元日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('37.妙解成语——一贫如洗', 'video', '/profile/upload/2025/11/18/37.妙解成语——一贫如洗.mp4', 47185920, '37.妙解成语——一贫如洗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('37.小蜗牛', 'video', '/profile/upload/2025/11/18/37.小蜗牛.mp4', 38011904, '37.小蜗牛.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('38 第14课《刷子李》第一课时', 'video', '/profile/upload/2025/11/18/38 第14课《刷子李》第一课时.mp4', 68157440, '38 第14课《刷子李》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('38 第14课《文言文二则》学弈', 'video', '/profile/upload/2025/11/18/38 第14课《文言文二则》学弈.mp4', 72477184, '38 第14课《文言文二则》学弈.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('38.妙解成语——情不自禁', 'video', '/profile/upload/2025/11/18/38.妙解成语——情不自禁.mp4', 47185920, '38.妙解成语——情不自禁.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('38.汉语拼音', 'video', '/profile/upload/2025/11/18/38.汉语拼音.mp4', 39845888, '38.汉语拼音.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('38.绝句迟日江山丽', 'video', '/profile/upload/2025/11/18/38.绝句迟日江山丽.mp4', 42949672, '38.绝句迟日江山丽.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('39 第14课《刷子李》第二课时', 'video', '/profile/upload/2025/11/18/39 第14课《刷子李》第二课时.mp4', 73400320, '39 第14课《刷子李》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('39 第14课《文言文二则》两小儿辩日', 'video', '/profile/upload/2025/11/18/39 第14课《文言文二则》两小儿辩日.mp4', 72477184, '39 第14课《文言文二则》两小儿辩日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('39.妙解成语——德高望重', 'video', '/profile/upload/2025/11/18/39.妙解成语——德高望重.mp4', 47185920, '39.妙解成语——德高望重.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('39.登飞来峰', 'video', '/profile/upload/2025/11/18/39.登飞来峰.mp4', 42949672, '39.登飞来峰.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('39.量词', 'video', '/profile/upload/2025/11/18/39.量词.mp4', 38580224, '39.量词.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('4.写作小灵通-不一样的提问', 'video', '/profile/upload/2025/11/18/4.写作小灵通-不一样的提问.mp4', 44040192, '4.写作小灵通-不一样的提问.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('40 第15课《真理诞生于一百个问号之后》', 'video', '/profile/upload/2025/11/18/40 第15课《真理诞生于一百个问号之后》.mp4', 68157440, '40 第15课《真理诞生于一百个问号之后》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('40.妙解成语——熙熙攘攘', 'video', '/profile/upload/2025/11/18/40.妙解成语——熙熙攘攘.mp4', 47185920, '40.妙解成语——熙熙攘攘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('40.悯农-', 'video', '/profile/upload/2025/11/18/40.悯农-.mp4', 42949672, '40.悯农-.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('40.词语', 'video', '/profile/upload/2025/11/18/40.词语.mp4', 38580224, '40.词语.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('40《交流平台 初试身手》', 'video', '/profile/upload/2025/11/18/40《交流平台 初试身手》.mp4', 53084160, '40《交流平台 初试身手》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('41 第15课《真理诞生于一百个问号之后》', 'video', '/profile/upload/2025/11/18/41 第15课《真理诞生于一百个问号之后》.mp4', 72477184, '41 第15课《真理诞生于一百个问号之后》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('41 第五单元《习作例文》', 'video', '/profile/upload/2025/11/18/41 第五单元《习作例文》.mp4', 73400320, '41 第五单元《习作例文》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('41.四时田园杂兴其一', 'video', '/profile/upload/2025/11/18/41.四时田园杂兴其一.mp4', 42949672, '41.四时田园杂兴其一.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('41.妙解成语——蹑手蹑脚', 'video', '/profile/upload/2025/11/18/41.妙解成语——蹑手蹑脚.mp4', 47185920, '41.妙解成语——蹑手蹑脚.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('42 第16课《表里的生物》', 'video', '/profile/upload/2025/11/18/42 第16课《表里的生物》.mp4', 68157440, '42 第16课《表里的生物》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('42.妙解成语——抑扬顿挫', 'video', '/profile/upload/2025/11/18/42.妙解成语——抑扬顿挫.mp4', 47185920, '42.妙解成语——抑扬顿挫.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('42.舟夜书所见', 'video', '/profile/upload/2025/11/18/42.舟夜书所见.mp4', 42949672, '42.舟夜书所见.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('42《形形色色的人》第一课时', 'video', '/profile/upload/2025/11/18/42《形形色色的人》第一课时.mp4', 53084160, '42《形形色色的人》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('43 第16课《表里的生物》', 'video', '/profile/upload/2025/11/18/43 第16课《表里的生物》.mp4', 72477184, '43 第16课《表里的生物》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('43.妙解成语——排山倒海', 'video', '/profile/upload/2025/11/18/43.妙解成语——排山倒海.mp4', 47185920, '43.妙解成语——排山倒海.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('43.晓出净慈寺送林子方', 'video', '/profile/upload/2025/11/18/43.晓出净慈寺送林子方.mp4', 42949672, '43.晓出净慈寺送林子方.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('43《形形色色的人》第二课时', 'video', '/profile/upload/2025/11/18/43《形形色色的人》第二课时.mp4', 53084160, '43《形形色色的人》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('44 第15课《自相矛盾》', 'video', '/profile/upload/2025/11/18/44 第15课《自相矛盾》.mp4', 66060288, '44 第15课《自相矛盾》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('44 第17课《他们那时候多有趣啊》', 'video', '/profile/upload/2025/11/18/44 第17课《他们那时候多有趣啊》.mp4', 72477184, '44 第17课《他们那时候多有趣啊》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('44.妙解成语——莫名其妙', 'video', '/profile/upload/2025/11/18/44.妙解成语——莫名其妙.mp4', 47185920, '44.妙解成语——莫名其妙.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('44.赋得古原草送别', 'video', '/profile/upload/2025/11/18/44.赋得古原草送别.mp4', 42949672, '44.赋得古原草送别.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('45 第16课《田忌赛马》', 'video', '/profile/upload/2025/11/18/45 第16课《田忌赛马》.mp4', 68157440, '45 第16课《田忌赛马》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('45 第五单元 口语交际:辩论', 'video', '/profile/upload/2025/11/18/45 第五单元 口语交际:辩论.mp4', 73400320, '45 第五单元 口语交际:辩论.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('45.妙解成语——语重心长', 'video', '/profile/upload/2025/11/18/45.妙解成语——语重心长.mp4', 47185920, '45.妙解成语——语重心长.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('45.春晓', 'video', '/profile/upload/2025/11/18/45.春晓.mp4', 40265312, '45.春晓.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('46 第五单元 习作:插上科学的翅膀飞', 'video', '/profile/upload/2025/11/18/46 第五单元 习作:插上科学的翅膀飞.mp4', 72477184, '46 第五单元 习作:插上科学的翅膀飞.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('46 第17课《跳水》第一课时', 'video', '/profile/upload/2025/11/18/46 第17课《跳水》第一课时.mp4', 68157440, '46 第17课《跳水》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('46.妙解成语——人声鼎沸', 'video', '/profile/upload/2025/11/18/46.妙解成语——人声鼎沸.mp4', 47185920, '46.妙解成语——人声鼎沸.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('46.画鸡', 'video', '/profile/upload/2025/11/18/46.画鸡.mp4', 38011904, '46.画鸡.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('47 语文园地五', 'video', '/profile/upload/2025/11/18/47 语文园地五.mp4', 66060288, '47 语文园地五.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('47 第17课《跳水》第二课时', 'video', '/profile/upload/2025/11/18/47 第17课《跳水》第二课时.mp4', 72477184, '47 第17课《跳水》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('47.古朗月行', 'video', '/profile/upload/2025/11/18/47.古朗月行.mp4', 42949672, '47.古朗月行.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('47.妙解成语——半途而废', 'video', '/profile/upload/2025/11/18/47.妙解成语——半途而废.mp4', 47185920, '47.妙解成语——半途而废.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('48 综合性学习--难忘小学生活(一)', 'video', '/profile/upload/2025/11/18/48 综合性学习--难忘小学生活(一).mp4', 68157440, '48 综合性学习--难忘小学生活(一).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('48 第六单元习作:神奇的探险之旅', 'video', '/profile/upload/2025/11/18/48 第六单元习作:神奇的探险之旅.mp4', 73400320, '48 第六单元习作:神奇的探险之旅.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
/*!40000 ALTER TABLE `courseware` ENABLE KEYS */;
UNLOCK TABLES;

View File

@ -1,200 +0,0 @@
LOCK TABLES `courseware` WRITE;
/*!40000 ALTER TABLE `courseware` DISABLE KEYS */;
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
-- 2025-11-18 视频文件批量插入
('01 第1课《北京的春节》', 'video', '/profile/upload/2025/11/18/01 第1课《北京的春节》.mp4', 52428800, '01 第1课《北京的春节》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01 第1课《古诗三首》第一课时', 'video', '/profile/upload/2025/11/18/01 第1课《古诗三首》第一课时.mp4', 67108864, '01 第1课《古诗三首》第一课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01 观潮', 'video', '/profile/upload/2025/11/18/01 观潮.MP4', 73400320, '01 观潮.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.【课文1】古诗二首之村居', 'video', '/profile/upload/2025/11/18/01.【课文1】古诗二首之村居.mp4', 48993472, '01.【课文1】古诗二首之村居.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.古诗三首 绝句', 'video', '/profile/upload/2025/11/18/01.古诗三首 绝句.mp4', 57671680, '01.古诗三首 绝句.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.咏鹅', 'video', '/profile/upload/2025/11/18/01.咏鹅.mp4', 41943040, '01.咏鹅.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.大青树下的小学', 'video', '/profile/upload/2025/11/18/01.大青树下的小学.mp4', 55050240, '01.大青树下的小学.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.天地人', 'video', '/profile/upload/2025/11/18/01.天地人.mp4', 36700160, '01.天地人.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.妙解成语——指鹿为马', 'video', '/profile/upload/2025/11/18/01.妙解成语——指鹿为马.mp4', 46137344, '01.妙解成语——指鹿为马.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.小蝌蚪找妈妈', 'video', '/profile/upload/2025/11/18/01.小蝌蚪找妈妈.mp4', 50331648, '01.小蝌蚪找妈妈.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01.春夏秋冬', 'video', '/profile/upload/2025/11/18/01.春夏秋冬.mp4', 39321600, '01.春夏秋冬.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01古诗词三首-四时田园杂兴', 'video', '/profile/upload/2025/11/18/01古诗词三首-四时田园杂兴.ts', 62914560, '01古诗词三首-四时田园杂兴.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01白鹭', 'video', '/profile/upload/2025/11/18/01白鹭.MP4', 69206016, '01白鹭.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('01草原', 'video', '/profile/upload/2025/11/18/01草原.MP4', 78643200, '01草原.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02 第1课《北京的春节》', 'video', '/profile/upload/2025/11/18/02 第1课《北京的春节》.mp4', 536870912, '02 第1课《北京的春节》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02 第1课《古诗三首》第二课时', 'video', '/profile/upload/2025/11/18/02 第1课《古诗三首》第二课时.mp4', 65536000, '02 第1课《古诗三首》第二课时.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02 走月亮', 'video', '/profile/upload/2025/11/18/02 走月亮.MP4', 58593792, '02 走月亮.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.【课文1】古诗二首之咏柳', 'video', '/profile/upload/2025/11/18/02.【课文1】古诗二首之咏柳.mp4', 49152000, '02.【课文1】古诗二首之咏柳.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.古诗三首 惠崇春江晚景', 'video', '/profile/upload/2025/11/18/02.古诗三首 惠崇春江晚景.mp4', 56623104, '02.古诗三首 惠崇春江晚景.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.妙解成语——对牛弹琴', 'video', '/profile/upload/2025/11/18/02.妙解成语——对牛弹琴.mp4', 44040192, '02.妙解成语——对牛弹琴.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.姓氏歌', 'video', '/profile/upload/2025/11/18/02.姓氏歌.mp4', 37748736, '02.姓氏歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.山行-', 'video', '/profile/upload/2025/11/18/02.山行-.mp4', 42949672, '02.山行-.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.我是什么', 'video', '/profile/upload/2025/11/18/02.我是什么.mp4', 47185920, '02.我是什么.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.花的学校', 'video', '/profile/upload/2025/11/18/02.花的学校.mp4', 515396096, '02.花的学校.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02.金木水火土', 'video', '/profile/upload/2025/11/18/02.金木水火土.mp4', 35651584, '02.金木水火土.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02丁香结', 'video', '/profile/upload/2025/11/18/02丁香结.MP4', 67108864, '02丁香结.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02古诗词三首-宿新市徐公店', 'video', '/profile/upload/2025/11/18/02古诗词三首-宿新市徐公店.ts', 618653696, '02古诗词三首-宿新市徐公店.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('02落花生', 'video', '/profile/upload/2025/11/18/02落花生.MP4', 59635712, '02落花生.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03 现代诗两首', 'video', '/profile/upload/2025/11/18/03 现代诗两首.MP4', 64460800, '03 现代诗两首.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03 第1课《古诗三首》第三课时', 'video', '/profile/upload/2025/11/18/03 第1课《古诗三首》第三课时 .mp4', 68157440, '03 第1课《古诗三首》第三课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03 第2课《腊八粥》', 'video', '/profile/upload/2025/11/18/03 第2课《腊八粥》.mp4', 704643072, '03 第2课《腊八粥》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.【课文2】找春天', 'video', '/profile/upload/2025/11/18/03.【课文2】找春天.mp4', 50331648, '03.【课文2】找春天.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.不懂就要问', 'video', '/profile/upload/2025/11/18/03.不懂就要问.mp4', 53084160, '03.不懂就要问.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.口耳目', 'video', '/profile/upload/2025/11/18/03.口耳目.mp4', 36700160, '03.口耳目.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.古诗三首 三衢道中', 'video', '/profile/upload/2025/11/18/03.古诗三首 三衢道中.mp4', 54525952, '03.古诗三首 三衢道中.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.妙解成语——守株待兔', 'video', '/profile/upload/2025/11/18/03.妙解成语——守株待兔.mp4', 45088768, '03.妙解成语——守株待兔.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.小青蛙', 'video', '/profile/upload/2025/11/18/03.小青蛙.mp4', 38580224, '03.小青蛙.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.植物妈妈有办法', 'video', '/profile/upload/2025/11/18/03.植物妈妈有办法.mp4', 48234496, '03.植物妈妈有办法.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03.赠汪伦', 'video', '/profile/upload/2025/11/18/03.赠汪伦.mp4', 40265312, '03.赠汪伦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03古诗词三首-其一', 'video', '/profile/upload/2025/11/18/03古诗词三首-其一.MP4', 620756992, '03古诗词三首-其一.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03古诗词三首-清平乐·村居', 'video', '/profile/upload/2025/11/18/03古诗词三首-清平乐·村居.ts', 650117120, '03古诗词三首-清平乐·村居.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('03桂花雨', 'video', '/profile/upload/2025/11/18/03桂花雨.MP4', 57671680, '03桂花雨.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04 第2课《祖父的园子》第一课时', 'video', '/profile/upload/2025/11/18/04 第2课《祖父的园子》第一课时 .mp4', 72477184, '04 第2课《祖父的园子》第一课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04 第3课《古诗三首》寒食', 'video', '/profile/upload/2025/11/18/04 第3课《古诗三首》寒食.mp4', 66060288, '04 第3课《古诗三首》寒食.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04 繁星', 'video', '/profile/upload/2025/11/18/04 繁星.MP4', 59183104, '04 繁星.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.【课文3】开满鲜花的小路', 'video', '/profile/upload/2025/11/18/04.【课文3】开满鲜花的小路.mp4', 51609600, '04.【课文3】开满鲜花的小路.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.古诗三首 山行', 'video', '/profile/upload/2025/11/18/04.古诗三首 山行.mp4', 55050240, '04.古诗三首 山行.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.场景歌', 'video', '/profile/upload/2025/11/18/04.场景歌.mp4', 39845888, '04.场景歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.妙解成语——水滴石穿', 'video', '/profile/upload/2025/11/18/04.妙解成语——水滴石穿.mp4', 46137344, '04.妙解成语——水滴石穿.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.日月水火', 'video', '/profile/upload/2025/11/18/04.日月水火.mp4', 35651584, '04.日月水火.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.燕子', 'video', '/profile/upload/2025/11/18/04.燕子.mp4', 52428800, '04.燕子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.猜字谜', 'video', '/profile/upload/2025/11/18/04.猜字谜.mp4', 38011904, '04.猜字谜.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04.送杜少府之任蜀州', 'video', '/profile/upload/2025/11/18/04.送杜少府之任蜀州.mp4', 49152000, '04.送杜少府之任蜀州.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04乡下人家', 'video', '/profile/upload/2025/11/18/04乡下人家.ts', 638976000, '04乡下人家.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04古诗词三首-其二', 'video', '/profile/upload/2025/11/18/04古诗词三首-其二.MP4', 612348928, '04古诗词三首-其二.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('04珍珠鸟', 'video', '/profile/upload/2025/11/18/04珍珠鸟.MP4', 58101760, '04珍珠鸟.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05 一个豆荚里的五粒豆(1)', 'video', '/profile/upload/2025/11/18/05 一个豆荚里的五粒豆(1).MP4', 69206016, '05 一个豆荚里的五粒豆(1).MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05 一个豆荚里的五粒豆', 'video', '/profile/upload/2025/11/18/05 一个豆荚里的五粒豆.MP4', 704643072, '05 一个豆荚里的五粒豆.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05 第2课《祖父的园子》第二课时', 'video', '/profile/upload/2025/11/18/05 第2课《祖父的园子》第二课时 .mp4', 73400320, '05 第2课《祖父的园子》第二课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05 第3课《古诗三首》迢迢牵牛星', 'video', '/profile/upload/2025/11/18/05 第3课《古诗三首》迢迢牵牛星.mp4', 67108864, '05 第3课《古诗三首》迢迢牵牛星.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.【课文4】邓小平爷爷植树', 'video', '/profile/upload/2025/11/18/05.【课文4】邓小平爷爷植树.mp4', 52428800, '05.【课文4】邓小平爷爷植树.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.古诗三首 赠刘景文', 'video', '/profile/upload/2025/11/18/05.古诗三首 赠刘景文.mp4', 54525952, '05.古诗三首 赠刘景文.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.吃水不忘挖井人', 'video', '/profile/upload/2025/11/18/05.吃水不忘挖井人.mp4', 40132608, '05.吃水不忘挖井人.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.夜书所见', 'video', '/profile/upload/2025/11/18/05.夜书所见.mp4', 41943040, '05.夜书所见.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.妙解成语——田忌赛马', 'video', '/profile/upload/2025/11/18/05.妙解成语——田忌赛马.mp4', 47185920, '05.妙解成语——田忌赛马.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.对韵歌', 'video', '/profile/upload/2025/11/18/05.对韵歌.mp4', 36700160, '05.对韵歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.树之歌', 'video', '/profile/upload/2025/11/18/05.树之歌.mp4', 39321600, '05.树之歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05.荷花', 'video', '/profile/upload/2025/11/18/05.荷花.mp4', 51609600, '05.荷花.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05古诗词三首-其三', 'video', '/profile/upload/2025/11/18/05古诗词三首-其三.MP4', 606041088, '05古诗词三首-其三.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05天窗', 'video', '/profile/upload/2025/11/18/05天窗.ts', 629145600, '05天窗.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('05搭石', 'video', '/profile/upload/2025/11/18/05搭石.MP4', 56623104, '05搭石.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06 第3课《古诗三首》十五夜望月', 'video', '/profile/upload/2025/11/18/06 第3课《古诗三首》十五夜望月.mp4', 66060288, '06 第3课《古诗三首》十五夜望月.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06 第3课《月是故乡明》', 'video', '/profile/upload/2025/11/18/06 第3课《月是故乡明》 .mp4', 71722496, '06 第3课《月是故乡明》 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06 蝙蝠和雷达', 'video', '/profile/upload/2025/11/18/06 蝙蝠和雷达.MP4', 60311552, '06 蝙蝠和雷达.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.a o e', 'video', '/profile/upload/2025/11/18/06.a o e.mp4', 33554432, '06.a o e.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.【课文5】雷锋叔叔你在哪里', 'video', '/profile/upload/2025/11/18/06.【课文5】雷锋叔叔你在哪里.mp4', 50331648, '06.【课文5】雷锋叔叔你在哪里.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.九月九日忆山东兄弟-', 'video', '/profile/upload/2025/11/18/06.九月九日忆山东兄弟-.mp4', 42949672, '06.九月九日忆山东兄弟-.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.古诗三首 夜书所见', 'video', '/profile/upload/2025/11/18/06.古诗三首 夜书所见.mp4', 44040192, '06.古诗三首 夜书所见.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.妙解成语——爱不释手', 'video', '/profile/upload/2025/11/18/06.妙解成语——爱不释手.mp4', 46137344, '06.妙解成语——爱不释手.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.我多想去看看', 'video', '/profile/upload/2025/11/18/06.我多想去看看.mp4', 38011904, '06.我多想去看看.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.拍手歌', 'video', '/profile/upload/2025/11/18/06.拍手歌.mp4', 39321600, '06.拍手歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06.昆虫备忘录', 'video', '/profile/upload/2025/11/18/06.昆虫备忘录.mp4', 51609600, '06.昆虫备忘录.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06三月桃花水', 'video', '/profile/upload/2025/11/18/06三月桃花水.ts', 644608000, '06三月桃花水.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06将相和', 'video', '/profile/upload/2025/11/18/06将相和.MP4', 72477184, '06将相和.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('06花之歌', 'video', '/profile/upload/2025/11/18/06花之歌.MP4', 57671680, '06花之歌.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07 呼风唤雨的世纪', 'video', '/profile/upload/2025/11/18/07 呼风唤雨的世纪.MP4', 612348928, '07 呼风唤雨的世纪.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07 第4课《梅花魂》', 'video', '/profile/upload/2025/11/18/07 第4课《梅花魂》.mp4', 68157440, '07 第4课《梅花魂》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07 第4课《藏戏》', 'video', '/profile/upload/2025/11/18/07 第4课《藏戏》.mp4', 73400320, '07 第4课《藏戏》.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.i u ü y w', 'video', '/profile/upload/2025/11/18/07.i u ü y w.mp4', 34603008, '07.i u ü y w.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.【课文6】千人糕', 'video', '/profile/upload/2025/11/18/07.【课文6】千人糕.mp4', 49152000, '07.【课文6】千人糕.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.一个接一个', 'video', '/profile/upload/2025/11/18/07.一个接一个.mp4', 38580224, '07.一个接一个.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.妙解成语——迫不及待', 'video', '/profile/upload/2025/11/18/07.妙解成语——迫不及待.mp4', 45088768, '07.妙解成语——迫不及待.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.守株待兔', 'video', '/profile/upload/2025/11/18/07.守株待兔.mp4', 41943040, '07.守株待兔.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.望天门山', 'video', '/profile/upload/2025/11/18/07.望天门山.mp4', 48234496, '07.望天门山.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.田家四季歌', 'video', '/profile/upload/2025/11/18/07.田家四季歌.mp4', 39845888, '07.田家四季歌.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07.铺满金色巴掌的水泥道', 'video', '/profile/upload/2025/11/18/07.铺满金色巴掌的水泥道.mp4', 53084160, '07.铺满金色巴掌的水泥道.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07七律-长征', 'video', '/profile/upload/2025/11/18/07七律-长征.MP4', 56623104, '07七律-长征.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07什么比猎豹的速度更快', 'video', '/profile/upload/2025/11/18/07什么比猎豹的速度更快.MP4', 60311552, '07什么比猎豹的速度更快.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('07琥珀', 'video', '/profile/upload/2025/11/18/07琥珀.ts', 638976000, '07琥珀.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08 口语交际:走进他们的童年岁月', 'video', '/profile/upload/2025/11/18/08 口语交际:走进他们的童年岁月 .mp4', 650117120, '08 口语交际:走进他们的童年岁月 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08 第一单元 习作:家乡的风俗', 'video', '/profile/upload/2025/11/18/08 第一单元 习作:家乡的风俗.mp4', 704643072, '08 第一单元 习作:家乡的风俗.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08 蝴蝶的家', 'video', '/profile/upload/2025/11/18/08 蝴蝶的家.MP4', 59183104, '08 蝴蝶的家.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.b p m f', 'video', '/profile/upload/2025/11/18/08.b p m f.mp4', 35651584, '08.b p m f.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.【课文7】一匹出色的马', 'video', '/profile/upload/2025/11/18/08.【课文7】一匹出色的马.mp4', 50331648, '08.【课文7】一匹出色的马.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.四个太阳', 'video', '/profile/upload/2025/11/18/08.四个太阳.mp4', 38011904, '08.四个太阳.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.妙解成语——和颜悦色', 'video', '/profile/upload/2025/11/18/08.妙解成语——和颜悦色.mp4', 46137344, '08.妙解成语——和颜悦色.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.曹冲称象', 'video', '/profile/upload/2025/11/18/08.曹冲称象.mp4', 48234496, '08.曹冲称象.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.秋天的雨', 'video', '/profile/upload/2025/11/18/08.秋天的雨.mp4', 51609600, '08.秋天的雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.陶罐和铁罐', 'video', '/profile/upload/2025/11/18/08.陶罐和铁罐.mp4', 49152000, '08.陶罐和铁罐.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08.饮西湖初晴后雨', 'video', '/profile/upload/2025/11/18/08.饮西湖初晴后雨.mp4', 42949672, '08.饮西湖初晴后雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08冀中的地道战', 'video', '/profile/upload/2025/11/18/08冀中的地道战.MP4', 72477184, '08冀中的地道战.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08狼Y山五壮士', 'video', '/profile/upload/2025/11/18/08狼Y山五壮士.MP4', 68157440, '08狼Y山五壮士.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('08飞向蓝天的恐龙', 'video', '/profile/upload/2025/11/18/08飞向蓝天的恐龙.ts', 644608000, '08飞向蓝天的恐龙.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09 习作:那一刻,我长大了', 'video', '/profile/upload/2025/11/18/09 习作:那一刻,我长大了 .mp4', 71722496, '09 习作:那一刻,我长大了 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09 语文园地 一', 'video', '/profile/upload/2025/11/18/09 语文园地 一.mp4', 66060288, '09 语文园地 一.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09 古诗三首', 'video', '/profile/upload/2025/11/18/09 古诗三首.ts', 612348928, '09 古诗三首.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.d t n l', 'video', '/profile/upload/2025/11/18/09.d t n l.mp4', 36700160, '09.d t n l.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.【识字1】识字 神州谣', 'video', '/profile/upload/2025/11/18/09.【识字1】识字 神州谣.mp4', 40132608, '09.【识字1】识字 神州谣.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.听听,秋的声音', 'video', '/profile/upload/2025/11/18/09.听听,秋的声音.mp4', 41943040, '09.听听,秋的声音.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.咏柳', 'video', '/profile/upload/2025/11/18/09.咏柳.mp4', 40265312, '09.咏柳.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.妙解成语——呼风唤雨', 'video', '/profile/upload/2025/11/18/09.妙解成语——呼风唤雨.mp4', 47185920, '09.妙解成语——呼风唤雨.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.小公鸡和小鸭子', 'video', '/profile/upload/2025/11/18/09.小公鸡和小鸭子.mp4', 38580224, '09.小公鸡和小鸭子.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.玲玲的画', 'video', '/profile/upload/2025/11/18/09.玲玲的画.mp4', 39845888, '09.玲玲的画.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09.鹿角和鹿腿', 'video', '/profile/upload/2025/11/18/09.鹿角和鹿腿.mp4', 53084160, '09.鹿角和鹿腿.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09开G大典', 'video', '/profile/upload/2025/11/18/09开G大典.MP4', 56623104, '09开G大典.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09猎人海力布', 'video', '/profile/upload/2025/11/18/09猎人海力布.MP4', 60311552, '09猎人海力布.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('09纳米技术就在我们身边', 'video', '/profile/upload/2025/11/18/09纳米技术就在我们身边.ts', 638976000, '09纳米技术就在我们身边.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('1.说明方法-列数字', 'video', '/profile/upload/2025/11/18/1.说明方法-列数字.mp4', 44040192, '1.说明方法-列数字.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10 语文园地一', 'video', '/profile/upload/2025/11/18/10 语文园地一 .mp4', 650117120, '10 语文园地一 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10 爬山虎的脚', 'video', '/profile/upload/2025/11/18/10 爬山虎的脚.ts', 612348928, '10 爬山虎的脚.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10 第5课《鲁滨逊漂流记节选', 'video', '/profile/upload/2025/11/18/10 第5课《鲁滨逊漂流记节选.mp4', 704643072, '10 第5课《鲁滨逊漂流记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.g k h', 'video', '/profile/upload/2025/11/18/10.g k h.mp4', 35651584, '10.g k h.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.【识字2】传统节日', 'video', '/profile/upload/2025/11/18/10.【识字2】传统节日.mp4', 40132608, '10.【识字2】传统节日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.一封信', 'video', '/profile/upload/2025/11/18/10.一封信.mp4', 38011904, '10.一封信.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.写作小灵通-“变身戏法”', 'video', '/profile/upload/2025/11/18/10.写作小灵通-“变身戏法”.mp4', 46137344, '10.写作小灵通-“变身戏法”.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.去年的树', 'video', '/profile/upload/2025/11/18/10.去年的树.mp4', 48234496, '10.去年的树.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.妙解成语——狼吞虎咽', 'video', '/profile/upload/2025/11/18/10.妙解成语——狼吞虎咽.mp4', 45088768, '10.妙解成语——狼吞虎咽.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.春日', 'video', '/profile/upload/2025/11/18/10.春日.mp4', 41943040, '10.春日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.树和喜鹊', 'video', '/profile/upload/2025/11/18/10.树和喜鹊.mp4', 38580224, '10.树和喜鹊.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10.池子与河流', 'video', '/profile/upload/2025/11/18/10.池子与河流.mp4', 51609600, '10.池子与河流.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10千年梦圆在今朝', 'video', '/profile/upload/2025/11/18/10千年梦圆在今朝.ts', 644608000, '10千年梦圆在今朝.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10灯光', 'video', '/profile/upload/2025/11/18/10灯光.MP4', 59183104, '10灯光.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('10牛郎织女', 'video', '/profile/upload/2025/11/18/10牛郎织女.MP4', 68157440, '10牛郎织女.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11 第5课《草船借箭》第一课时', 'video', '/profile/upload/2025/11/18/11 第5课《草船借箭》第一课时 .mp4', 72477184, '11 第5课《草船借箭》第一课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11 第5课《鲁滨逊漂流记节选', 'video', '/profile/upload/2025/11/18/11 第5课《鲁滨逊漂流记节选.mp4', 73400320, '11 第5课《鲁滨逊漂流记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11 蟋蟀的住宅', 'video', '/profile/upload/2025/11/18/11 蟋蟀的住宅.ts', 650117120, '11 蟋蟀的住宅.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.j q x', 'video', '/profile/upload/2025/11/18/11.j q x.mp4', 36700160, '11.j q x.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.【识字3】识字 “贝”的故事', 'video', '/profile/upload/2025/11/18/11.【识字3】识字 “贝”的故事.mp4', 40132608, '11.【识字3】识字 “贝”的故事.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.乞巧', 'video', '/profile/upload/2025/11/18/11.乞巧.mp4', 40265312, '11.乞巧.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.写作小灵通-动作描写', 'video', '/profile/upload/2025/11/18/11.写作小灵通-动作描写.mp4', 49152000, '11.写作小灵通-动作描写.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.古诗三首 元日', 'video', '/profile/upload/2025/11/18/11.古诗三首 元日.mp4', 44040192, '11.古诗三首 元日.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.妈妈睡了', 'video', '/profile/upload/2025/11/18/11.妈妈睡了.mp4', 38580224, '11.妈妈睡了.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.妙解成语——争先恐后', 'video', '/profile/upload/2025/11/18/11.妙解成语——争先恐后.mp4', 47185920, '11.妙解成语——争先恐后.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.怎么都快乐', 'video', '/profile/upload/2025/11/18/11.怎么都快乐.mp4', 39845888, '11.怎么都快乐.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11.那一定会很好', 'video', '/profile/upload/2025/11/18/11.那一定会很好.mp4', 53084160, '11.那一定会很好.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11牛郎织女', 'video', '/profile/upload/2025/11/18/11牛郎织女.MP4', 56623104, '11牛郎织女.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11短诗三首', 'video', '/profile/upload/2025/11/18/11短诗三首.ts', 60311552, '11短诗三首.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('11竹节人', 'video', '/profile/upload/2025/11/18/11竹节人.MP4', 638976000, '11竹节人.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12 盘古开天地', 'video', '/profile/upload/2025/11/18/12 盘古开天地.ts', 644608000, '12 盘古开天地.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12 第5课《草船借箭》第二课时 (1)', 'video', '/profile/upload/2025/11/18/12 第5课《草船借箭》第二课时 (1).mp4', 71722496, '12 第5课《草船借箭》第二课时 (1).mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12 第5课《草船借箭》第二课时', 'video', '/profile/upload/2025/11/18/12 第5课《草船借箭》第二课时 .mp4', 72477184, '12 第5课《草船借箭》第二课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12 第6课《骑鹅旅行记节选', 'video', '/profile/upload/2025/11/18/12 第6课《骑鹅旅行记节选.mp4', 73400320, '12 第6课《骑鹅旅行记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.z c s', 'video', '/profile/upload/2025/11/18/12.z c s.mp4', 35651584, '12.z c s.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.【识字4】中国美食', 'video', '/profile/upload/2025/11/18/12.【识字4】中国美食.mp4', 40132608, '12.【识字4】中国美食.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.写作小灵通-会说话的表情', 'video', '/profile/upload/2025/11/18/12.写作小灵通-会说话的表情.mp4', 49152000, '12.写作小灵通-会说话的表情.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.古诗三首 清明', 'video', '/profile/upload/2025/11/18/12.古诗三首 清明.mp4', 44040192, '12.古诗三首 清明.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.古诗二首——登鹳雀楼', 'video', '/profile/upload/2025/11/18/12.古诗二首——登鹳雀楼.mp4', 42949672, '12.古诗二首——登鹳雀楼.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.在牛肚子里旅行', 'video', '/profile/upload/2025/11/18/12.在牛肚子里旅行.mp4', 50331648, '12.在牛肚子里旅行.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.妙解成语——聚精会神', 'video', '/profile/upload/2025/11/18/12.妙解成语——聚精会神.mp4', 47185920, '12.妙解成语——聚精会神.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.嫦娥', 'video', '/profile/upload/2025/11/18/12.嫦娥.mp4', 40265312, '12.嫦娥.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12.静夜思', 'video', '/profile/upload/2025/11/18/12.静夜思.mp4', 38011904, '12.静夜思.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12古诗三首-示儿', 'video', '/profile/upload/2025/11/18/12古诗三首-示儿.MP4', 56623104, '12古诗三首-示儿.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12宇宙生命之谜', 'video', '/profile/upload/2025/11/18/12宇宙生命之谜.MP4', 60311552, '12宇宙生命之谜.MP4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('12绿', 'video', '/profile/upload/2025/11/18/12绿.ts', 638976000, '12绿.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13 第6课《景阳冈》第一课时', 'video', '/profile/upload/2025/11/18/13 第6课《景阳冈》第一课时 .mp4', 72477184, '13 第6课《景阳冈》第一课时 .mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13 第6课《骑鹅旅行记节选', 'video', '/profile/upload/2025/11/18/13 第6课《骑鹅旅行记节选.mp4', 73400320, '13 第6课《骑鹅旅行记节选.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13 精卫填海', 'video', '/profile/upload/2025/11/18/13 精卫填海.ts', 650117120, '13 精卫填海.ts', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.zh ch sh r', 'video', '/profile/upload/2025/11/18/13.zh ch sh r.mp4', 36700160, '13.zh ch sh r.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.【课文8】彩色的梦', 'video', '/profile/upload/2025/11/18/13.【课文8】彩色的梦.mp4', 50331648, '13.【课文8】彩色的梦.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.一块奶酪', 'video', '/profile/upload/2025/11/18/13.一块奶酪.mp4', 48234496, '13.一块奶酪.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.写作小灵通-“说”之前的奥秘', 'video', '/profile/upload/2025/11/18/13.写作小灵通-“说”之前的奥秘.mp4', 49152000, '13.写作小灵通-“说”之前的奥秘.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.古诗三首 九月九日忆山东兄弟', 'video', '/profile/upload/2025/11/18/13.古诗三首 九月九日忆山东兄弟.mp4', 44040192, '13.古诗三首 九月九日忆山东兄弟.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('13.古诗二首——望庐山瀑布', 'video', '/profile/upload/2025/11/18/13.古诗二首——望庐山瀑布.mp4', 42949672, '13.古诗二首——望庐山瀑布.mp4', 1, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
/*!40000 ALTER TABLE `courseware` ENABLE KEYS */;
UNLOCK TABLES;
-- ========================================
-- SQL脚本执行完成
-- ========================================

View File

@ -1,16 +0,0 @@
-- 为learning_detail表添加courseware_id字段
-- 用于记录每个课件的学习进度
-- 添加courseware_id字段
ALTER TABLE `learning_detail`
ADD COLUMN `courseware_id` BIGINT NULL COMMENT '课件ID' AFTER `course_id`;
-- 添加索引以提高查询性能
ALTER TABLE `learning_detail`
ADD INDEX `idx_courseware_id` (`courseware_id`);
-- 添加联合索引,用于查询某个学生对某个课件的学习记录
ALTER TABLE `learning_detail`
ADD INDEX `idx_student_courseware` (`student_id`, `courseware_id`);

View File

@ -1,13 +0,0 @@
-- 在 score 表中添加 subject_id 字段
ALTER TABLE `score`
ADD COLUMN `subject_id` bigint DEFAULT NULL COMMENT '科目ID关联subject表' AFTER `exam_id`,
ADD COLUMN `subject_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '科目名称' AFTER `subject_id`,
ADD KEY `idx_subject_id` (`subject_id`);
-- 更新现有数据:根据 exam_id 关联 exam 表,填充 subject_id 和 subject_name
UPDATE score s
INNER JOIN exam e ON s.exam_id = e.id
SET s.subject_id = e.subject_id,
s.subject_name = e.subject_name
WHERE s.subject_id IS NULL;

View File

@ -1,71 +0,0 @@
-- ============================================
-- 检查和恢复学员数据的SQL脚本
-- ============================================
-- 步骤1查看当前所有学员的数据检查哪些数据丢失了
SELECT
user_id AS '信息编号',
user_name AS '账号',
nick_name AS '罪犯姓名',
sex AS '性别',
prison_name AS '监狱',
prison_area AS '监区',
ethnicity AS '民族',
education_level AS '文化程度',
crime_name AS '罪名',
sentence_term AS '刑期',
sentence_start_date AS '刑期起日',
sentence_end_date AS '刑期止日',
entry_date AS '入监时间',
student_status AS '学员状态',
remark AS '备注'
FROM sys_user
WHERE remark LIKE '%注册类型:student%'
AND del_flag = '0'
ORDER BY user_id;
-- 步骤2查看最近的操作日志可能包含更新前的数据
-- 查询最近修改用户的操作日志
SELECT
oper_id AS '日志ID',
oper_time AS '操作时间',
oper_name AS '操作人',
oper_param AS '请求参数',
json_result AS '返回结果(更新后的数据)'
FROM sys_oper_log
WHERE title = '用户管理'
AND business_type = 2 -- 2表示修改操作
AND oper_time >= DATE_SUB(NOW(), INTERVAL 2 DAY) -- 最近2天的操作
ORDER BY oper_time DESC
LIMIT 100;
-- 步骤3如果有备份请先恢复备份
-- 如果没有备份,需要手动恢复数据
-- 步骤4手动恢复数据的示例SQL请根据实际情况修改
-- 以下是一个示例,请根据您的实际数据修改:
-- 示例恢复某个学员的数据请根据实际情况修改user_id和数据
-- UPDATE sys_user
-- SET
-- prison_name = '第一监狱', -- 请根据实际情况修改
-- prison_area = '一监区', -- 请根据实际情况修改
-- ethnicity = '汉族', -- 请根据实际情况修改
-- education_level = '初中', -- 请根据实际情况修改
-- crime_name = '盗窃罪', -- 请根据实际情况修改
-- sentence_term = 3, -- 请根据实际情况修改
-- sentence_start_date = '2023-01-01', -- 请根据实际情况修改
-- sentence_end_date = '2026-01-01', -- 请根据实际情况修改
-- entry_date = '2023-01-15', -- 请根据实际情况修改
-- student_status = 'incarcerated' -- 设置为在押状态
-- WHERE user_id = 110; -- 替换为实际的user_id
-- 步骤5批量恢复多个学员的数据如果有相同的模式
-- 例如,如果多个学员都是从"第一监狱"的"一监区",可以批量恢复:
-- UPDATE sys_user
-- SET prison_name = '第一监狱',
-- prison_area = '一监区'
-- WHERE remark LIKE '%注册类型:student%'
-- AND del_flag = '0'
-- AND (prison_name IS NULL OR prison_name = '');

View File

@ -1,57 +0,0 @@
-- 检查并修复 study_student_status 字典重复数据
-- 1. 检查是否有重复的字典数据
SELECT
dict_type,
dict_value,
COUNT(*) as count,
GROUP_CONCAT(dict_code ORDER BY dict_code) as dict_codes
FROM sys_dict_data
WHERE dict_type = 'study_student_status'
GROUP BY dict_type, dict_value
HAVING COUNT(*) > 1;
-- 2. 查看所有 study_student_status 字典数据
SELECT * FROM sys_dict_data WHERE dict_type = 'study_student_status' ORDER BY dict_sort, dict_code;
-- 3. 删除重复的字典数据(保留 dict_sort 最小的记录)
-- 注意:执行前请先备份数据!
DELETE d1 FROM sys_dict_data d1
INNER JOIN sys_dict_data d2
WHERE d1.dict_type = 'study_student_status'
AND d2.dict_type = 'study_student_status'
AND d1.dict_value = d2.dict_value
AND d1.dict_code > d2.dict_code;
-- 4. 确保字典数据唯一性(如果还有重复,使用这个更严格的清理)
-- 删除所有重复项,只保留 dict_code 最小的
DELETE d1 FROM sys_dict_data d1
INNER JOIN (
SELECT dict_type, dict_value, MIN(dict_code) as min_dict_code
FROM sys_dict_data
WHERE dict_type = 'study_student_status'
GROUP BY dict_type, dict_value
HAVING COUNT(*) > 1
) d2 ON d1.dict_type = d2.dict_type
AND d1.dict_value = d2.dict_value
AND d1.dict_code > d2.min_dict_code;
-- 5. 验证清理后的结果
SELECT * FROM sys_dict_data WHERE dict_type = 'study_student_status' ORDER BY dict_sort, dict_code;
-- 6. 如果字典数据被完全删除,重新插入正确的数据
-- 在押
INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
SELECT 1, '在押', 'incarcerated', 'study_student_status', '', 'primary', 'Y', '0', 'admin', NOW(), '在押状态'
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_data
WHERE dict_type = 'study_student_status' AND dict_value = 'incarcerated'
);
-- 已释放
INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
SELECT 2, '已释放', 'released', 'study_student_status', '', 'success', 'N', '0', 'admin', NOW(), '已释放状态'
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_data
WHERE dict_type = 'study_student_status' AND dict_value = 'released'
);

View File

@ -1,23 +0,0 @@
-- ============================================
-- 修复学员状态为NULL的记录
-- ============================================
-- 将所有学员的student_status为NULL的记录设置为默认值'incarcerated'(在押)
UPDATE sys_user
SET student_status = 'incarcerated'
WHERE del_flag = '0'
AND (remark LIKE '%注册类型:student%' OR remark LIKE '%注册类型:学员%')
AND (student_status IS NULL OR student_status = '');
-- 查询更新后的结果
SELECT
user_id AS '信息编号',
user_name AS '账号',
nick_name AS '罪犯姓名',
student_status AS '学员状态',
remark AS '备注'
FROM sys_user
WHERE del_flag = '0'
AND (remark LIKE '%注册类型:student%' OR remark LIKE '%注册类型:学员%')
ORDER BY user_id;

View File

@ -1,13 +0,0 @@
-- 系统有效时间配置初始化SQL
-- 作用:初始化系统有效时间配置,用于控制系统的登录有效期
-- 说明:如果 system.top 配置存在且截止时间小于当前时间,则系统不能登录(系统管理员除外)
-- 系统有效时间配置格式yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
-- 如果配置的是日期格式yyyy-MM-dd则系统在该日期的23:59:59之前都可以登录
-- 如果配置的是日期时间格式yyyy-MM-dd HH:mm:ss则系统在该时间之前可以登录
-- 如果当前时间超过配置的截止时间,普通用户将无法登录,提示"系统出现问题,请联系管理员"
-- 系统管理员用户ID=1不受此限制可以正常登录
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `remark`)
VALUES ('系统有效时间', 'system.top', '', 'Y', 'admin', NOW(), '系统有效时间格式yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss。如果存在且截止时间小于当前时间则系统不能登录系统管理员除外。为空则不限制。')
ON DUPLICATE KEY UPDATE `config_value` = VALUES(`config_value`), `update_time` = NOW(), `remark` = VALUES(`remark`);

View File

@ -1,4 +0,0 @@
-- 修改 score 表的 exam_id 字段,允许为 NULL因为考试名称不是必填项
ALTER TABLE `score`
MODIFY COLUMN `exam_id` bigint DEFAULT NULL COMMENT '考试ID';

View File

@ -1,101 +0,0 @@
-- ============================================
-- 国语教育平台 - 数据库字段更新SQL
-- ============================================
-- 1. 为 sys_user 表添加新字段
ALTER TABLE sys_user
ADD COLUMN prison_name VARCHAR(50) COMMENT '监狱' AFTER remark,
ADD COLUMN prison_area VARCHAR(50) COMMENT '监区' AFTER prison_name,
ADD COLUMN ethnicity VARCHAR(20) COMMENT '民族' AFTER sex,
ADD COLUMN education_level VARCHAR(20) COMMENT '文化程度' AFTER ethnicity,
ADD COLUMN crime_name VARCHAR(50) COMMENT '罪名' AFTER education_level,
ADD COLUMN sentence_term INT COMMENT '刑期(年)' AFTER crime_name,
ADD COLUMN sentence_start_date DATE COMMENT '刑期起日' AFTER sentence_term,
ADD COLUMN sentence_end_date DATE COMMENT '刑期止日' AFTER sentence_start_date,
ADD COLUMN entry_date DATE COMMENT '入监时间' AFTER sentence_end_date;
-- 2. 插入测试数据 - 添加几个罪犯信息
-- 注意这些SQL需要根据实际的用户角色ID来调整确保用户能够正确分配角色
-- 插入罪犯1张三
INSERT INTO sys_user (
user_name, nick_name, password, sex, prison_name, prison_area,
ethnicity, education_level, crime_name, sentence_term,
sentence_start_date, sentence_end_date, entry_date,
status, del_flag, create_time, remark
) VALUES (
'zhangsan', '张三', '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m',
'0', '第一监狱', '一监区', '汉族', '初中', '盗窃罪', 3,
'2023-01-01', '2026-01-01', '2023-01-15',
'0', '0', NOW(), '注册类型:student'
);
-- 插入罪犯2李四
INSERT INTO sys_user (
user_name, nick_name, password, sex, prison_name, prison_area,
ethnicity, education_level, crime_name, sentence_term,
sentence_start_date, sentence_end_date, entry_date,
status, del_flag, create_time, remark
) VALUES (
'lisi', '李四', '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m',
'0', '第一监狱', '二监区', '汉族', '高中', '故意伤害罪', 5,
'2022-06-01', '2027-06-01', '2022-06-10',
'0', '0', NOW(), '注册类型:student'
);
-- 插入罪犯3王五
INSERT INTO sys_user (
user_name, nick_name, password, sex, prison_name, prison_area,
ethnicity, education_level, crime_name, sentence_term,
sentence_start_date, sentence_end_date, entry_date,
status, del_flag, create_time, remark
) VALUES (
'wangwu', '王五', '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m',
'0', '第二监狱', '一监区', '回族', '小学', '诈骗罪', 7,
'2021-03-15', '2028-03-15', '2021-03-20',
'0', '0', NOW(), '注册类型:student'
);
-- 插入罪犯4赵六
INSERT INTO sys_user (
user_name, nick_name, password, sex, prison_name, prison_area,
ethnicity, education_level, crime_name, sentence_term,
sentence_start_date, sentence_end_date, entry_date,
status, del_flag, create_time, remark
) VALUES (
'zhaoliu', '赵六', '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m',
'1', '第一监狱', '三监区', '汉族', '中专', '抢劫罪', 10,
'2020-05-10', '2030-05-10', '2020-05-15',
'0', '0', NOW(), '注册类型:student'
);
-- 插入罪犯5钱七
INSERT INTO sys_user (
user_name, nick_name, password, sex, prison_name, prison_area,
ethnicity, education_level, crime_name, sentence_term,
sentence_start_date, sentence_end_date, entry_date,
status, del_flag, create_time, remark
) VALUES (
'qianqi', '钱七', '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m',
'0', '第二监狱', '二监区', '满族', '大专', '贪污罪', 8,
'2022-09-01', '2030-09-01', '2022-09-05',
'0', '0', NOW(), '注册类型:student'
);
-- 注意:
-- 1. 密码 '$2a$10$7JB720yubVSOfvVaMaF0Oe5B.p.FKpDOOaQY4hqPFa8FmJkQvY.2m' 对应的明文密码通常是 '123456'
-- 如果您的系统使用不同的加密方式,请相应调整
-- 2. 请根据实际的角色ID为用户分配角色通过 sys_user_role 表)
-- 3. 如果您的系统有默认密码配置,请使用系统配置的默认密码进行加密
-- 4. 日期格式为 'YYYY-MM-DD'
-- 5. 执行前请备份数据库
-- 3. 语音评测表补充提交状态字段
ALTER TABLE voice_evaluation
ADD COLUMN is_submitted TINYINT(1) DEFAULT 0 COMMENT '是否已提交0-未提交1-已提交)' AFTER evaluation_time,
ADD COLUMN submit_time DATETIME DEFAULT NULL COMMENT '提交时间' AFTER is_submitted;
UPDATE voice_evaluation
SET is_submitted = 0
WHERE is_submitted IS NULL;

View File

@ -1,20 +0,0 @@
-- 更新菜单名称:将"班级用户管理"改为"学生管理"
-- 注意:执行前请先备份数据!
-- 1. 查看当前菜单配置
SELECT menu_id, menu_name, menu_type, parent_id, order_num, path, component, perms
FROM sys_menu
WHERE menu_name LIKE '%班级用户管理%' OR menu_name LIKE '%学生管理%'
ORDER BY menu_id;
-- 2. 更新菜单名称
UPDATE sys_menu
SET menu_name = '学生管理'
WHERE menu_name = '班级用户管理';
-- 3. 验证更新结果
SELECT menu_id, menu_name, menu_type, parent_id, order_num, path, component, perms
FROM sys_menu
WHERE menu_name = '学生管理'
ORDER BY menu_id;

View File

@ -1,52 +0,0 @@
-- 创建学员状态字典类型
INSERT INTO `sys_dict_type` (`dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `remark`)
VALUES ('学员状态', 'study_student_status', '0', 'admin', NOW(), '学员状态列表(在押、释放、外出、假释等)')
ON DUPLICATE KEY UPDATE `dict_name` = '学员状态', `remark` = '学员状态列表(在押、释放、外出、假释等)';
-- 创建学员状态字典数据
-- 在押
INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
VALUES (1, '在押', 'incarcerated', 'study_student_status', '', 'primary', 'Y', '0', 'admin', NOW(), '在押状态')
ON DUPLICATE KEY UPDATE `dict_label` = '在押', `dict_sort` = 1, `is_default` = 'Y', `status` = '0';
-- 释放
INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
VALUES (2, '释放', 'released', 'study_student_status', '', 'success', 'N', '0', 'admin', NOW(), '释放状态')
ON DUPLICATE KEY UPDATE `dict_label` = '释放', `dict_sort` = 2, `is_default` = 'N', `status` = '0';
-- 为 sys_user 表添加学员状态字段(如果字段不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_user'
AND COLUMN_NAME = 'student_status');
SET @sqlstmt := IF(@exist = 0,
'ALTER TABLE `sys_user` ADD COLUMN `student_status` VARCHAR(50) DEFAULT NULL COMMENT ''学员状态incarcerated-在押, released-释放, out-外出, parole-假释等)'' AFTER `entry_date`',
'SELECT ''student_status字段已存在'' as result');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 创建学员变更记录表
DROP TABLE IF EXISTS `study_student_change_log`;
CREATE TABLE `study_student_change_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`student_id` bigint NOT NULL COMMENT '学员IDsys_user表的user_id',
`change_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '变更类型status_change-状态变更, field_change-字段变更)',
`field_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段名称(仅字段变更时使用)',
`old_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '旧值',
`new_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '新值',
`old_label` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '旧值显示标签(用于状态等需要翻译的值)',
`new_label` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '新值显示标签(用于状态等需要翻译的值)',
`change_desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '变更描述',
`operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
`operator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作人名称',
`change_time` datetime NOT NULL COMMENT '变更时间',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_change_type` (`change_type`),
KEY `idx_change_time` (`change_time`),
KEY `idx_field_name` (`field_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学员变更记录表';

View File

@ -1,62 +0,0 @@
-- 修复登录日志权限问题
-- 如果操作日志可以访问但登录日志不能访问执行此SQL
-- 1. 首先检查当前用户的权限情况
SELECT
u.user_name,
r.role_name,
r.role_key,
m.menu_name,
m.perms,
m.status as menu_status
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.role_id
LEFT JOIN sys_role_menu rm ON r.role_id = rm.role_id
LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
AND m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY m.perms;
-- 2. 如果发现登录日志权限缺失执行以下SQL修复
-- 方法A如果操作日志权限存在复制相同的角色菜单关联到登录日志
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT
rm.role_id,
(SELECT menu_id FROM sys_menu WHERE perms = 'monitor:logininfor:list' LIMIT 1) as menu_id
FROM sys_role_menu rm
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms = 'monitor:operlog:list'
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm2
INNER JOIN sys_menu m2 ON rm2.menu_id = m2.menu_id
WHERE rm2.role_id = rm.role_id
AND m2.perms = 'monitor:logininfor:list'
);
-- 方法B直接为所有角色添加登录日志权限如果菜单存在
-- 注意:执行前请确认 sys_menu 表中存在 perms = 'monitor:logininfor:list' 的记录
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT
r.role_id,
(SELECT menu_id FROM sys_menu WHERE perms = 'monitor:logininfor:list' LIMIT 1) as menu_id
FROM sys_role r
WHERE NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE rm.role_id = r.role_id
AND m.perms = 'monitor:logininfor:list'
);
-- 3. 验证修复结果
SELECT
r.role_name,
r.role_key,
m.menu_name,
m.perms
FROM sys_role r
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY r.role_id, m.perms;

View File

@ -297,6 +297,9 @@ public class StudyExamController extends BaseController
StudyQuestion question = questions.get(0);
question.setExamId(examId);
// 规范化数据格式
normalizeQuestionData(question);
// 更新题目
questionService.updateQuestion(question);
@ -316,7 +319,7 @@ public class StudyExamController extends BaseController
// 批量保存先删除原有题目再插入新题目
questionService.deleteQuestionByExamId(examId);
// 设置考试ID和排序
// 设置考试ID和排序并规范化数据格式
for (int i = 0; i < questions.size(); i++)
{
StudyQuestion question = questions.get(i);
@ -324,6 +327,9 @@ public class StudyExamController extends BaseController
question.setSortOrder(i + 1);
// 清除ID因为已经删除了原有题目需要重新插入
question.setId(null);
// 规范化数据格式
normalizeQuestionData(question);
}
// 计算总分和题量
@ -424,5 +430,67 @@ public class StudyExamController extends BaseController
typeMap.put("essay", "简答题");
return typeMap.getOrDefault(type, type);
}
/**
* 规范化题目数据确保题库导入和手动创建的题目格式一致
*/
private void normalizeQuestionData(StudyQuestion question) {
// 1. 确保分值不为空
if (question.getScore() == null || question.getScore() == 0) {
question.setScore(5.0); // 默认5分
}
// 2. 规范化多选题答案格式去除空格确保逗号分隔
if ("multiple".equals(question.getQuestionType()) && question.getCorrectAnswer() != null) {
String answer = question.getCorrectAnswer().trim();
// 去除所有空格统一用逗号分隔
answer = answer.replaceAll("\\s+", "");
// 确保字母按顺序排列 (A,B,C 而不是 C,A,B)
if (answer.contains(",")) {
String[] parts = answer.split(",");
java.util.Arrays.sort(parts);
answer = String.join(",", parts);
}
question.setCorrectAnswer(answer);
}
// 3. 规范化判断题答案格式
if ("judge".equals(question.getQuestionType()) && question.getCorrectAnswer() != null) {
String answer = question.getCorrectAnswer().trim().toLowerCase();
// 统一转换为 "正确" "错误"
if (answer.matches("(true|t|1|yes|对|正确)")) {
question.setCorrectAnswer("正确");
} else if (answer.matches("(false|f|0|no|错|错误)")) {
question.setCorrectAnswer("错误");
}
}
// 4. 确保options字段格式正确JSON数组
if (("single".equals(question.getQuestionType()) || "multiple".equals(question.getQuestionType()))
&& question.getOptions() != null) {
String options = question.getOptions().trim();
// 如果不是以 [ 开头尝试修复
if (!options.startsWith("[") && !options.isEmpty()) {
// 简单修复假设是逗号分隔的选项
String[] parts = options.split(",");
StringBuilder jsonArray = new StringBuilder("[");
for (int i = 0; i < parts.length; i++) {
if (i > 0) jsonArray.append(",");
jsonArray.append("\"").append(parts[i].trim()).append("\"");
}
jsonArray.append("]");
question.setOptions(jsonArray.toString());
}
}
// 5. 确保sourceType不为空
if (question.getSourceType() == null || question.getSourceType().isEmpty()) {
if (question.getBankId() != null && question.getBankItemId() != null) {
question.setSourceType("bank");
} else {
question.setSourceType("manual");
}
}
}
}

View File

@ -55,14 +55,19 @@ public class StudyQuestionBankController extends BaseController
}
/**
* 获取当前教师的题库列表
* 获取题库列表返回所有题库用于考试创建
*/
@PreAuthorize("@ss.hasPermi('study:questionBank:list')")
@GetMapping("/my-banks")
public AjaxResult getMyBanks()
{
Long teacherId = getUserId();
List<StudyQuestionBank> list = questionBankService.selectQuestionBankListByTeacherId(teacherId);
// 修改为返回所有题库而不仅仅是当前用户创建的
// Long teacherId = getUserId();
// List<StudyQuestionBank> list = questionBankService.selectQuestionBankListByTeacherId(teacherId);
// 查询所有题库
StudyQuestionBank query = new StudyQuestionBank();
List<StudyQuestionBank> list = questionBankService.selectQuestionBankList(query);
return success(list);
}

View File

@ -20,7 +20,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectQuestionBankVo">
select b.id, b.bank_name, b.subject_id, b.subject_name, b.description, b.question_count,
select b.id, b.bank_name, b.subject_id, b.subject_name, b.description,
(select count(*) from question_bank_item where bank_id = b.id) as question_count,
b.create_user_id, b.create_by, b.create_time, b.update_by, b.update_time, b.remark
from question_bank b
</sql>

View File

@ -207,6 +207,7 @@
<div v-for="(question, index) in previewQuestions" :key="index" style="margin-bottom: 30px; padding: 15px; border: 1px solid #e4e7ed; border-radius: 4px">
<div style="font-weight: bold; margin-bottom: 10px">
{{ index + 1 }}. [{{ getQuestionTypeName(question.questionType) }}] {{ question.questionContent }}
<span v-if="question.questionType === 'multiple'" style="color: #409eff; margin-left: 5px">可多选</span>
</div>
<div v-if="question.options" style="margin-left: 20px">
<div v-for="(option, optIndex) in JSON.parse(question.options)" :key="optIndex">

View File

@ -212,7 +212,7 @@
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="questionDialog = false"> </el-button>
<el-button type="primary" :loading="questionLoading" @click="saveExamQuestions"> </el-button>
<el-button type="primary" :loading="questionLoading" @click="handleSaveQuestions"> </el-button>
</span>
</el-dialog>
@ -671,7 +671,11 @@ export default {
getExam(row.id)
.then(response => {
this.currentExam = response.data || {};
const questions = (this.currentExam.questions || []).map((item, index) =>
// sortOrder
const sortedQuestions = (this.currentExam.questions || []).sort((a, b) => {
return (a.sortOrder || 0) - (b.sortOrder || 0);
});
const questions = sortedQuestions.map((item, index) =>
this.normalizeQuestion(item, index)
);
this.questionList = questions.length ? questions : [this.createEmptyQuestion()];
@ -817,8 +821,10 @@ export default {
optionsList = this.getDefaultOptionsByType(question.questionType);
}
// multipleCorrect
// multipleCorrect correctAnswer
let multipleCorrect = [];
let normalizedCorrectAnswer = question.correctAnswer || '';
if (question.questionType === 'multiple') {
// 使 multipleCorrectAnswers
if (question.multipleCorrectAnswers && Array.isArray(question.multipleCorrectAnswers)) {
@ -827,12 +833,37 @@ export default {
// multipleCorrect 使
multipleCorrect = [...question.multipleCorrect];
} else if (question.correctAnswer) {
// correctAnswer "A,B,C"
const answerLabels = this.splitMultipleAnswer(question.correctAnswer);
multipleCorrect = answerLabels.map(label => {
const labelIndex = label.charCodeAt(0) - 65; // A=0, B=1, C=2, ...
return optionsList[labelIndex] || '';
}).filter(item => item);
const answerParts = this.splitMultipleAnswer(question.correctAnswer);
// (A,B)(1,2)
const isLetter = answerParts.every(part =>
part.length === 1 && part >= 'A' && part <= 'Z'
);
if (isLetter) {
//
multipleCorrect = answerParts.map(label => {
const labelIndex = label.charCodeAt(0) - 65; // A=0, B=1, C=2
return optionsList[labelIndex] || '';
}).filter(item => item);
} else {
//
const labels = [];
answerParts.forEach(text => {
// "A. "
const cleanText = text.replace(/^[A-Z]\.\s*/, '').trim();
const index = optionsList.findIndex(opt => {
const cleanOpt = opt.replace(/^[A-Z]\.\s*/, '').trim();
return cleanOpt === cleanText || opt === text;
});
if (index >= 0) {
labels.push(String.fromCharCode(65 + index)); // 0=A, 1=B
multipleCorrect.push(optionsList[index]);
}
});
//
normalizedCorrectAnswer = labels.join(',');
}
}
}
@ -845,7 +876,9 @@ export default {
optionsList: optionsList,
correctAnswer: question.questionType === 'judge'
? (question.correctAnswer !== undefined && question.correctAnswer !== null ? question.correctAnswer : '正确')
: (question.correctAnswer !== undefined && question.correctAnswer !== null ? String(question.correctAnswer) : ""),
: (question.questionType === 'multiple' && normalizedCorrectAnswer
? normalizedCorrectAnswer
: (question.correctAnswer !== undefined && question.correctAnswer !== null ? String(question.correctAnswer) : "")),
multipleCorrect: multipleCorrect,
score: question.score != null ? Number(question.score) : 5,
sortOrder: question.sortOrder != null ? Number(question.sortOrder) : index + 1,
@ -980,21 +1013,49 @@ export default {
if (!answer) {
return [];
}
return answer.split(',').map(item => item.trim()).filter(item => item);
//
return answer.split(/[,,、]/).map(item => item.trim()).filter(item => item);
},
buildOptionsPayload(question) {
if (!this.needOptions(question.questionType)) {
return null;
}
let list = (question.optionsList || []).map(item => (item || '').trim()).filter(item => item);
// optionsList() options(JSON)
let list = [];
if (question.optionsList && Array.isArray(question.optionsList)) {
//
list = question.optionsList.map(item => (item || '').trim()).filter(item => item);
} else if (question.options) {
//
try {
const parsed = typeof question.options === 'string'
? JSON.parse(question.options)
: question.options;
if (Array.isArray(parsed)) {
list = parsed.map(item => (item || '').trim()).filter(item => item);
}
} catch (e) {
console.warn('解析options失败:', e);
}
}
if (!list.length && question.questionType === 'judge') {
list = ["正确", "错误"];
}
return list.length ? JSON.stringify(list) : null;
},
formatCorrectAnswer(question) {
// multipleCorrect() correctAnswer()
if (question.questionType === 'multiple') {
return (question.multipleCorrect || []).join(',');
if (question.multipleCorrect && Array.isArray(question.multipleCorrect)) {
//
return question.multipleCorrect.join(',');
} else if (question.correctAnswer) {
//
return question.correctAnswer;
}
return '';
}
return question.correctAnswer || '';
},
@ -1051,7 +1112,11 @@ export default {
.then(response => {
//
this.currentExam = response.data || {};
const questions = (this.currentExam.questions || []).map((item, index) =>
// sortOrder
const sortedQuestions = (this.currentExam.questions || []).sort((a, b) => {
return (a.sortOrder || 0) - (b.sortOrder || 0);
});
const questions = sortedQuestions.map((item, index) =>
this.normalizeQuestion(item, index)
);
this.questionList = questions.length ? questions : [this.createEmptyQuestion()];
@ -1235,18 +1300,17 @@ export default {
},
/** 从题库选择题目 */
openQuestionSelectDialog() {
if (!this.questionBankOptions.length) {
this.getQuestionBanks();
}
//
this.getQuestionBanks();
this.selectDialogOpen = true;
this.selectDialogFilter = '';
this.selectDialogType = '';
this.selectedBankQuestionRows = [];
if (this.selectedBankIds.length) {
this.loadQuestionsForBanks(this.selectedBankIds);
} else {
this.selectDialogQuestions = {};
}
//
this.selectedBankIds = [];
this.selectDialogQuestions = {};
},
async handleSelectBanks(val) {
this.selectedBankQuestionRows = [];

View File

@ -28,6 +28,7 @@
<div v-for="(question, index) in questions" :key="question.id" style="margin-bottom: 30px; padding: 15px; border: 1px solid #e4e7ed; border-radius: 4px">
<div style="font-weight: bold; margin-bottom: 15px; font-size: 16px">
{{ index + 1 }}. [{{ getQuestionTypeName(question.questionType) }}] {{ question.questionContent }}
<span v-if="question.questionType === 'multiple'" style="color: #409eff; margin-left: 5px">可多选</span>
<span style="color: #409eff; margin-left: 10px">{{ question.score }} </span>
</div>

View File

@ -37,6 +37,7 @@
<div v-for="(answer, index) in filteredAnswerDetails" :key="answer.id" style="margin-bottom: 30px; padding: 15px; border: 1px solid #e4e7ed; border-radius: 4px">
<div style="font-weight: bold; margin-bottom: 15px; font-size: 16px">
{{ index + 1 }}. {{ answer.questionContent }}
<span v-if="answer.questionType === 'multiple'" style="color: #409eff; margin-left: 5px">可多选</span>
<span style="color: #409eff; margin-left: 10px">{{ answer.questionScore != null ? answer.questionScore : (answer.score != null ? answer.score : 0) }} </span>
<span style="color: #67c23a; margin-left: 10px; font-weight: bold">得分{{ answer.score != null ? answer.score : 0 }} </span>
<el-tag v-if="answer.isCorrect === '1'" type="success" style="margin-left: 10px">正确</el-tag>
@ -129,7 +130,7 @@
<div v-for="(answer, index) in filteredAnswerDetails" :key="answer.id" class="print-answer-item">
<div class="print-question-header">
<span class="question-number">{{ index + 1 }}.</span>
<span class="question-content">{{ answer.questionContent }}</span>
<span class="question-content">{{ answer.questionContent }}<span v-if="answer.questionType === 'multiple'" style="color: #409eff; margin-left: 3px">可多选</span></span>
<span class="question-score">{{ answer.questionScore != null ? answer.questionScore : (answer.score != null ? answer.score : 0) }} </span>
<span style="color: #67c23a; margin-left: 8px; font-weight: bold">得分{{ answer.score != null ? answer.score : 0 }} </span>
<span :class="answer.isCorrect === '1' ? 'status-correct' : 'status-wrong'">

View File

@ -107,6 +107,7 @@
>
<div style="font-weight: bold; margin-bottom: 10px">
{{ index + 1 }}. {{ answer.questionContent }}
<span v-if="answer.questionType === 'multiple'" style="color: #409eff; margin-left: 5px">可多选</span>
<span style="color: #409eff; margin-left: 8px">{{ answer.questionScore != null ? answer.questionScore : (answer.score != null ? answer.score : 0) }} </span>
<span style="color: #67c23a; margin-left: 8px; font-weight: bold">得分{{ answer.score != null ? answer.score : 0 }} </span>
<el-tag

View File

@ -299,6 +299,7 @@
>
<div style="font-weight: bold; margin-bottom: 10px">
{{ index + 1 }}. {{ answer.questionContent }}
<span v-if="answer.questionType === 'multiple'" style="color: #409eff; margin-left: 5px">可多选</span>
<span style="color: #409eff; margin-left: 8px">{{ answer.questionScore != null ? answer.questionScore : (answer.score != null ? answer.score : 0) }} </span>
<span style="color: #67c23a; margin-left: 8px; font-weight: bold">得分{{ answer.score != null ? answer.score : 0 }} </span>
<el-tag

View File

@ -1,67 +0,0 @@
@echo off
rem jarƽ<72><C6BD>Ŀ¼
set AppName=ry-study-admin.jar
rem JVM<56><4D><EFBFBD><EFBFBD>
set JVM_OPTS="-Dname=%AppName% -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
ECHO.
ECHO. [1] <20><><EFBFBD><EFBFBD>%AppName%
ECHO. [2] <20>ر<EFBFBD>%AppName%
ECHO. [3] <20><><EFBFBD><EFBFBD>%AppName%
ECHO. [4] <20><><EFBFBD><EFBFBD>״̬ %AppName%
ECHO. [5] <20><> <20><>
ECHO.
ECHO.<2E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>:
set /p ID=
IF "%id%"=="1" GOTO start
IF "%id%"=="2" GOTO stop
IF "%id%"=="3" GOTO restart
IF "%id%"=="4" GOTO status
IF "%id%"=="5" EXIT
PAUSE
:start
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if defined pid (
echo %%is running
PAUSE
)
start javaw %JVM_OPTS% -jar %AppName%
echo starting<6E><67><EFBFBD><EFBFBD>
echo Start %AppName% success...
goto:eof
rem <20><><EFBFBD><EFBFBD>stopͨ<70><CDA8>jps<70><73><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pid<69><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
:stop
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% does not exists) else (
echo prepare to kill %image_name%
echo start kill %pid% ...
rem <20><><EFBFBD>ݽ<EFBFBD><DDBD><EFBFBD>ID<49><44>kill<6C><6C><EFBFBD><EFBFBD>
taskkill /f /pid %pid%
)
goto:eof
:restart
call :stop
call :start
goto:eof
:status
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% is dead ) else (
echo %image_name% is running
)
goto:eof

View File

@ -1,183 +0,0 @@
# 登录日志 401 错误代码检查报告
## 检查时间
2025-01-30
## 检查范围
- 前端 API 调用
- 前端页面组件
- 后端控制器
- 权限检查逻辑
- Token 处理
## 检查结果
### ✅ 前端代码检查
#### 1. API 调用对比
**文件:`study-ui/src/api/monitor/logininfor.js` vs `operlog.js`**
| 项目 | logininfor | operlog | 状态 |
|------|-----------|---------|------|
| URL 路径 | `/monitor/logininfor/list` | `/monitor/operlog/list` | ✅ 一致 |
| 请求方法 | `get` | `get` | ✅ 一致 |
| 参数处理 | `params: query` | `params: query` | ✅ 一致 |
| 导入方式 | `import request from '@/utils/request'` | `import request from '@/utils/request'` | ✅ 一致 |
**结论:** 前端 API 调用代码完全一致,无差异。
#### 2. 页面组件对比
**文件:`study-ui/src/views/monitor/logininfor/index.vue` vs `operlog/index.vue`**
| 项目 | logininfor | operlog | 状态 |
|------|-----------|---------|------|
| 生命周期 | `created()` 中调用 `getList()` | `created()` 中调用 `getList()` | ✅ 一致 |
| 错误处理 | 无 `.catch()` | 无 `.catch()` | ✅ 一致 |
| 列表查询方法 | `list(this.addDateRange(...))` | `list(this.addDateRange(...))` | ✅ 一致 |
**结论:** 页面组件实现完全一致,无差异。
#### 3. 请求拦截器检查
**文件:`study-ui/src/utils/request.js`**
- ✅ Token 添加逻辑正常:`config.headers['Authorization'] = 'Bearer ' + getToken()`
- ✅ GET 请求参数处理正常
- ✅ 响应拦截器 401 处理正常
**结论:** 请求拦截器配置正常。
### ✅ 后端代码检查
#### 1. 控制器对比
**文件:**
- `ry-study-admin/.../SysLogininforController.java`
- `ry-study-admin/.../SysOperlogController.java`
| 项目 | SysLogininforController | SysOperlogController | 状态 |
|------|------------------------|---------------------|------|
| 注解 | `@RestController` | `@RestController` | ✅ 一致 |
| 路径映射 | `@RequestMapping("/monitor/logininfor")` | `@RequestMapping("/monitor/operlog")` | ✅ 一致 |
| 权限注解 | `@PreAuthorize("@ss.hasPermi('monitor:logininfor:list')")` | `@PreAuthorize("@ss.hasPermi('monitor:operlog:list')")` | ✅ 一致 |
| 方法实现 | `startPage()` + `selectLogininforList()` | `startPage()` + `selectOperLogList()` | ✅ 一致 |
| 返回类型 | `TableDataInfo` | `TableDataInfo` | ✅ 一致 |
**结论:** 控制器代码完全一致,无差异。
#### 2. 权限检查逻辑
**文件:`ry-study-framework/.../PermissionService.java`**
```java
public boolean hasPermi(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
PermissionContextHolder.setContext(permission);
return hasPermissions(loginUser.getPermissions(), permission);
}
private boolean hasPermissions(Set<String> permissions, String permission) {
return permissions.contains(Constants.ALL_PERMISSION) ||
permissions.contains(StringUtils.trim(permission));
}
```
**检查点:**
- ✅ 权限检查逻辑正常
- ✅ 支持通配符权限 `*:*:*`
- ✅ 权限字符串会进行 trim 处理
**结论:** 权限检查逻辑正常,无问题。
#### 3. Token 处理
**文件:`ry-study-framework/.../JwtAuthenticationTokenFilter.java`**
- ✅ Token 提取逻辑正常
- ✅ 用户信息从 Redis 获取
- ✅ 权限信息从 `LoginUser.getPermissions()` 获取
**结论:** Token 处理逻辑正常。
## 🔍 问题分析
### 代码层面
**所有代码检查均正常,无差异。**
### 可能的问题原因
#### 1. 数据库权限配置问题 ⚠️(最可能)
- **现象:** 菜单配置显示"正常",但用户实际没有该权限
- **原因:**
- `sys_role_menu` 表中缺少角色与 `monitor:logininfor:list` 菜单的关联
- 或者用户角色关联不正确
- **验证方法:** 执行 `check_permissions.sql` 中的查询
#### 2. Redis 缓存问题 ⚠️
- **现象:** 数据库权限已更新,但 Redis 中仍是旧数据
- **原因:**
- 用户登录后,权限信息缓存在 Redis 中
- 如果数据库权限更新但用户未重新登录Redis 中仍是旧权限
- **解决方法:**
1. 清除 Redis 缓存:`KEYS login_tokens:*` 然后删除
2. 或者重启 Redis
3. 重新登录系统
#### 3. 权限字符串差异 ⚠️
- **现象:** 权限字符串可能有细微差异(空格、大小写等)
- **验证方法:** 检查数据库中 `sys_menu.perms` 字段的值
## 📋 建议的排查步骤
### 步骤 1检查数据库权限配置
```sql
-- 检查用户是否有 monitor:logininfor:list 权限
SELECT
u.user_name,
r.role_name,
m.perms
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.role_id
LEFT JOIN sys_role_menu rm ON r.role_id = rm.role_id
LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
AND m.perms IN ('monitor:logininfor:list', 'monitor:operlog:list')
ORDER BY m.perms;
```
### 步骤 2如果权限缺失执行修复
```sql
-- 参考 fix_logininfor_permission.sql 文件
```
### 步骤 3清除 Redis 缓存
```bash
# 在 Redis 中执行
KEYS login_tokens:*
# 删除所有匹配的 key
# 或者
FLUSHDB # 仅开发环境
```
### 步骤 4重新登录并测试
1. 清除浏览器 Cookie
2. 重新登录系统
3. 测试登录日志页面
## 🎯 结论
**代码检查结果:所有代码正常,无差异。**
**问题定位:** 问题不在代码层面,而是在数据层面:
1. 数据库权限配置可能不完整
2. Redis 缓存可能过期
**建议操作:**
1. 执行 `check_permissions.sql` 检查权限配置
2. 如果权限缺失,执行 `fix_logininfor_permission.sql` 修复
3. 清除 Redis 缓存
4. 重新登录测试

View File

@ -1,68 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 列出文件信息用于填写SQL
echo ========================================
echo.
set "DIR_PATH=D:\wwwroot\study_web\web\profile\upload\upload\2025\11\18"
set "OUTPUT_FILE=文件列表.txt"
echo 正在扫描目录: %DIR_PATH%
echo.
if not exist "%DIR_PATH%" (
echo 错误:目录不存在!
echo 请修改脚本中的 DIR_PATH 变量
pause
exit /b 1
)
echo 文件列表(保存到 %OUTPUT_FILE%
echo ========================================
echo.
(
echo 文件名^|文件大小(字节)^|文件大小(MB)^|类型^|建议标题^|文件路径
echo --------------------------------------------------------
for %%F in ("%DIR_PATH%\*.*") do (
set "FILENAME=%%~nxF"
set "FILESIZE=%%~zF"
set "EXT=%%~xF"
rem 计算MB
set /a SIZE_MB=%%~zF/1048576
rem 判断类型
set "FILETYPE=document"
if /i "%%~xF"==".mp4" set "FILETYPE=video"
if /i "%%~xF"==".avi" set "FILETYPE=video"
if /i "%%~xF"==".mov" set "FILETYPE=video"
if /i "%%~xF"==".wmv" set "FILETYPE=video"
if /i "%%~xF"==".flv" set "FILETYPE=video"
if /i "%%~xF"==".mkv" set "FILETYPE=video"
if /i "%%~xF"==".webm" set "FILETYPE=video"
if /i "%%~xF"==".jpg" set "FILETYPE=image"
if /i "%%~xF"==".jpeg" set "FILETYPE=image"
if /i "%%~xF"==".png" set "FILETYPE=image"
if /i "%%~xF"==".gif" set "FILETYPE=image"
rem 生成标题(去掉扩展名)
set "TITLE=%%~nF"
echo %%FILENAME%%^|%%FILESIZE%%^|%%SIZE_MB%%^|%%FILETYPE%%^|%%TITLE%%^|/profile/upload/2025/11/18/%%FILENAME%%
)
) > "%OUTPUT_FILE%"
type "%OUTPUT_FILE%"
echo.
echo ========================================
echo 文件信息已保存到: %OUTPUT_FILE%
echo 您可以使用此文件来填写SQL模板
echo ========================================
echo.
pause

View File

@ -1,138 +0,0 @@
# 前端地址配置说明
## ✅ 已完成的配置修改
### 1. **API 请求配置** (`study-ui/src/utils/request.js`)
- ✅ 修改为:开发环境使用 `/dev-api` 代理路径
- ✅ 生产环境使用 `http://localhost:8080`
- ✅ 支持环境变量配置
### 2. **开发服务器代理配置** (`study-ui/vue.config.js`)
- ✅ 后端接口代理:`/dev-api` → `http://localhost:8080`
- ✅ WebSocket 代理:`/ws` → `ws://localhost:8080`
- ✅ 后端地址:`http://localhost:8080`
### 3. **WebSocket 连接配置** (`study-ui/src/views/study/screenStream/index.vue`)
- ✅ 开发环境:使用 `window.location.host`(通过代理)
- ✅ 生产环境:直接使用 `localhost:8080`
### 4. **环境变量文件**
- ✅ 已创建 `.env.development`(开发环境)
- ✅ 已创建 `.env.production`(生产环境)
## 📋 配置详情
### 开发环境配置 (`.env.development`)
```env
ENV = 'development'
VUE_APP_BASE_API = '/dev-api'
VUE_APP_TITLE = '国语教育平台'
```
### 生产环境配置 (`.env.production`)
```env
ENV = 'production'
VUE_APP_BASE_API = 'http://localhost:8080'
VUE_APP_TITLE = '国语教育平台'
```
## 🔄 请求流程
### 开发环境
1. **API 请求**
- 前端发送:`/dev-api/login`
- 代理转发:`http://localhost:8080/login`
- 后端处理并返回
2. **WebSocket 连接**
- 前端连接:`ws://localhost:20002/ws/screenStream/{userId}`
- 代理转发:`ws://localhost:8080/ws/screenStream/{userId}`
- 后端处理
### 生产环境
1. **API 请求**
- 前端直接发送:`http://localhost:8080/login`
2. **WebSocket 连接**
- 前端直接连接:`ws://localhost:8080/ws/screenStream/{userId}`
## 🚀 使用步骤
### 1. 确保后端服务运行
```bash
# 后端服务应该运行在 http://localhost:8080
# 检查方法:访问 http://localhost:8080/swagger-ui/index.html
```
### 2. 启动前端开发服务器
```bash
cd study-ui
npm run dev
```
### 3. 访问前端
- 开发环境:`http://localhost:20002`(或控制台显示的地址)
- 登录测试:用户名 `admin`,密码 `admin123`
## 🔍 故障排查
### 问题1API 请求失败404 或连接错误)
**检查项:**
1. ✅ 后端服务是否运行在 `localhost:8080`
2. ✅ 浏览器控制台查看实际请求 URL
3. ✅ 检查 `.env.development` 文件是否存在
4. ✅ 重启前端开发服务器
### 问题2WebSocket 连接失败
**检查项:**
1. ✅ 后端 WebSocket 服务是否正常
2. ✅ 浏览器控制台查看 WebSocket 连接 URL
3. ✅ 检查 `vue.config.js` 中的 `/ws` 代理配置
4. ✅ 确认后端端口为 8080
### 问题3跨域问题
**解决方案:**
- 开发环境已配置代理,应该不会有跨域问题
- 如果仍有问题,检查 `vue.config.js` 中的 `changeOrigin: true`
## 📝 注意事项
1. **环境变量优先级**
- 环境变量文件中的配置 > 代码中的默认值
- 修改环境变量后需要重启前端服务
2. **端口配置**
- 前端开发服务器端口:`20002`(可在 `vue.config.js` 中修改)
- 后端服务端口:`8080`(必须与后端实际端口一致)
3. **WebSocket 代理**
- 开发环境通过代理连接,生产环境直接连接
- 确保 `vue.config.js` 中配置了 `/ws` 代理
## ✅ 验证配置
### 验证步骤:
1. 打开浏览器开发者工具F12
2. 查看 Network 标签
3. 发起一个 API 请求(如登录)
4. 检查请求 URL
- 开发环境应该是:`http://localhost:20002/dev-api/login`
- 实际转发到:`http://localhost:8080/login`
5. 查看 Console 标签:
- 应该看到:`[Request] BaseURL configured: /dev-api`
## 🎯 总结
所有配置已修改为使用本地 8080 端口:
- ✅ API 请求通过 `/dev-api` 代理到 `http://localhost:8080`
- ✅ WebSocket 通过 `/ws` 代理到 `ws://localhost:8080`
- ✅ 环境变量文件已创建
- ✅ 开发和生产环境都已配置
**如果仍有问题,请检查:**
1. 后端服务是否正常运行
2. 端口是否正确8080
3. 防火墙是否阻止连接
4. 浏览器控制台的错误信息

View File

@ -1,125 +0,0 @@
# 后端端口配置修复说明
## 🔍 问题原因
**发现的问题:**
- 后端配置文件 `application.yml` 中端口配置为 `30091`
- 前端配置期望后端运行在 `8080` 端口
- 导致访问 `localhost:8080` 时显示"拒绝访问"
## ✅ 已修复
### 1. 后端端口配置 (`ry-study-admin/src/main/resources/application.yml`)
- ✅ 已将端口从 `30091` 修改为 `8080`
```yaml
server:
port: 8080 # 已修改
```
## 🚀 下一步操作
### 1. 停止当前后端服务
如果后端服务正在运行在 30091 端口,需要先停止它:
**Windows 系统:**
```powershell
# 查找占用 30091 端口的进程
netstat -ano | findstr :30091
# 根据 PID 结束进程(替换 24336 为实际的 PID
taskkill /PID 24336 /F
```
**或者直接重启 IDE/停止后端服务**
### 2. 检查 8080 端口是否被占用
```powershell
netstat -ano | findstr :8080
```
如果 8080 端口被占用,需要:
- 停止占用 8080 端口的程序
- 或者修改后端端口为其他可用端口(如 8081
### 3. 重启后端服务
1. 在 IDE 中重新启动后端服务
2. 或者使用命令行:
```bash
cd ry-study-admin
mvn spring-boot:run
```
### 4. 验证后端服务
访问以下地址验证后端是否正常启动:
- `http://localhost:8080/swagger-ui/index.html` - Swagger 文档
- `http://localhost:8080/captchaImage` - 验证码接口
如果能看到 Swagger 页面或验证码,说明后端启动成功。
### 5. 重启前端服务
```bash
cd study-ui
npm run dev
```
## 📋 完整配置检查清单
### 后端配置
- [x] `application.yml``server.port: 8080`
- [ ] 后端服务已重启
- [ ] 8080 端口未被占用
- [ ] 可以访问 `http://localhost:8080/swagger-ui/index.html`
### 前端配置
- [x] `vue.config.js``baseUrl: 'http://localhost:8080'`
- [x] `request.js` 中使用 `/dev-api` 代理
- [x] `.env.development` 文件已创建
- [ ] 前端服务已重启
### 数据库配置
- [ ] 数据库连接正常(检查 `application-druid.yml`
- [ ] Redis 连接正常(检查 `application.yml` 中的 Redis 配置)
## 🔧 常见问题
### 问题18080 端口被占用
**解决方案:**
1. 查找占用进程:`netstat -ano | findstr :8080`
2. 结束进程:`taskkill /PID <进程ID> /F`
3. 或者修改后端端口为其他端口(如 8081并同步修改前端配置
### 问题2后端启动失败
**检查项:**
1. 数据库连接是否正常
2. Redis 服务是否运行
3. 查看后端启动日志中的错误信息
4. 检查 Java 版本是否兼容
### 问题3前端仍然连接失败
**检查项:**
1. 后端服务是否正常运行在 8080 端口
2. 浏览器控制台查看实际请求的 URL
3. 检查代理配置是否正确
4. 清除浏览器缓存后重试
## 📝 配置总结
### 当前配置
- **后端端口**8080
- **前端端口**20002
- **API 代理**`/dev-api` → `http://localhost:8080`
- **WebSocket 代理**`/ws` → `ws://localhost:8080`
### 访问地址
- **前端地址**`http://localhost:20002`
- **后端 API**`http://localhost:8080`
- **Swagger 文档**`http://localhost:8080/swagger-ui/index.html`
## ⚠️ 重要提示
1. **修改配置后必须重启服务**才能生效
2. **确保数据库和 Redis 服务正常运行**
3. **检查防火墙是否阻止了 8080 端口**
4. **如果使用其他端口,需要同步修改前端配置**

View File

@ -1,220 +0,0 @@
# 多选题判断修复说明
## 问题描述
在成绩管理的详情页面中,多选题判断出现错误:学生实际选择正确,但系统判断为错误。
## 问题原因
多选题答案存在两种格式:
1. **选项标签格式**`A,B,C`
2. **选项内容格式**`选项内容1,选项内容2,选项内容3`
原有的比对逻辑无法处理这两种格式混用的情况,导致:
- 如果学生答案是"内容格式",正确答案是"标签格式",比对失败
- 或者反过来,也会比对失败
## 解决方案
### 1. 后端代码优化
`StudyScoreController.java` 中重写了 `compareMultipleAnswers` 方法,实现了智能比对功能:
**新功能:**
- ✅ 支持两种答案格式的自动识别
- ✅ 当格式不一致时,自动转换后再比对
- ✅ 通过题目的选项列表进行标签和内容的映射转换
- ✅ 忽略大小写差异
- ✅ 支持 JSON 数组和逗号分隔两种选项存储格式
**核心逻辑:**
```java
private boolean compareMultipleAnswers(String studentAnswer, String correctAnswer, String optionsJson)
{
// 1. 先尝试直接比对(忽略大小写)
// 2. 如果失败,判断答案格式是否一致
// 3. 如果一个是标签格式,一个是内容格式,进行转换
// 4. 转换后再次比对
}
```
### 2. 自动重新计算
**成绩详情查询时:**
- 系统会使用新的判断逻辑重新计算所有题目的对错和得分
- 不会修改数据库中的历史记录
- 确保显示的结果是准确的
**修改位置:**
- `getInfo` 方法第178行查看成绩详情时重新计算
### 3. 答案格式说明
**推荐的答案存储格式:**
为了避免混淆,建议统一使用以下格式之一:
**方案一:统一使用标签格式(推荐)**
- 学生答案:`A,B,C`
- 正确答案:`A,B,C`
- 优点:简洁、不受选项内容变化影响
**方案二:统一使用内容格式**
- 学生答案:`北京,上海,广州`
- 正确答案:`北京,上海,广州`
- 优点:直观、易理解
**当前系统:** 已支持两种格式混用,会自动转换比对
## 使用步骤
### 1. 重启后端服务
修改了后端代码后,需要重启后端服务:
```bash
# 停止服务
# 重新编译并启动
```
### 2. 验证修复效果
1. 打开管理端 → 成绩管理
2. 点击任意成绩的"查看详情"
3. 查看多选题的判断结果是否正确
4. 系统会自动使用新逻辑重新计算
### 3. 诊断工具(可选)
如果需要深入分析问题,可以执行诊断 SQL
```bash
# 执行以下SQL文件
E:\ry_study\Study-Vue-redis\database_recheck_multiple_questions.sql
```
该 SQL 脚本可以:
- 查看所有多选题的答案格式
- 统计判断错误的数量
- 分析格式差异
- 查看具体的答题记录
### 4. 数据修复(如需要)
**方法一:自动重新计算(推荐)**
- 管理员查看成绩详情时,系统自动使用新逻辑显示正确结果
- 不需要修改数据库
**方法二:重新提交(彻底修复)**
- 让学生重新提交答案
- 新提交的答案会使用新逻辑评分
- 数据库中的记录也会更新
## 技术细节
### 答案格式识别
```java
// 判断是否为标签格式A,B,C
private boolean isLabelFormat(Set<String> answerSet) {
return answerSet.stream()
.allMatch(s -> s.length() == 1 &&
s.toUpperCase().charAt(0) >= 'A' &&
s.toUpperCase().charAt(0) <= 'Z');
}
```
### 格式转换
```java
// 将标签格式A,B,C转换为内容格式
private Set<String> convertLabelsToContent(Set<String> labels, List<String> options) {
Set<String> contents = new HashSet<>();
for (String label : labels) {
char c = label.toUpperCase().charAt(0);
int index = c - 'A';
if (index >= 0 && index < options.size()) {
contents.add(options.get(index));
}
}
return contents;
}
```
### 选项解析
支持两种选项存储格式:
1. **JSON 数组**`["选项1","选项2","选项3"]`
2. **逗号分隔**`选项1,选项2,选项3`
## 测试建议
### 测试场景
1. **格式一致 - 都是标签格式**
- 学生答案:`A,B,C`
- 正确答案:`A,B,C`
- 预期:✅ 判断正确
2. **格式一致 - 都是内容格式**
- 学生答案:`北京,上海,广州`
- 正确答案:`北京,上海,广州`
- 预期:✅ 判断正确
3. **格式不一致 - 学生用标签,正确答案用内容**
- 学生答案:`A,B,C`
- 正确答案:`北京,上海,广州`
- 选项列表:`["北京","上海","广州","深圳"]`
- 预期:✅ 自动转换后判断正确
4. **格式不一致 - 学生用内容,正确答案用标签**
- 学生答案:`北京,上海,广州`
- 正确答案:`A,B,C`
- 选项列表:`["北京","上海","广州","深圳"]`
- 预期:✅ 自动转换后判断正确
5. **选项顺序不同**
- 学生答案:`C,A,B`
- 正确答案:`A,B,C`
- 预期:✅ 顺序无关,判断正确
6. **大小写不同**
- 学生答案:`a,b,c`
- 正确答案:`A,B,C`
- 预期:✅ 忽略大小写,判断正确
## 注意事项
1. **选项顺序**:系统会自动处理选项顺序问题,`A,B,C` 和 `C,A,B` 被视为相同答案
2. **大小写**:系统会忽略大小写差异
3. **空格**:答案中的前后空格会被自动去除
4. **分隔符**:统一使用逗号(`,`)作为分隔符,不支持分号、顿号等
5. **历史数据**:查看成绩详情时会自动重新计算,但不会修改数据库中的原始记录
## 后续优化建议
1. **统一答案格式**建议修改前端统一使用标签格式A,B,C提交答案
2. **数据迁移**:如果需要,可以编写脚本将所有历史答案统一为一种格式
3. **答案验证**:在题目创建时,验证正确答案格式的一致性
## 更新日志
- **2025-11-26**:修复多选题判断逻辑,支持标签和内容格式的自动转换
- **2025-11-26**:优化成绩详情查询,自动重新计算得分
- **2025-11-26**:添加诊断 SQL 脚本
## 技术支持
如有问题,请查看:
- 后端日志:检查是否有答案格式相关的警告
- 数据库数据:使用诊断 SQL 查看实际的答案格式
- 前端控制台:查看提交的答案格式

View File

@ -1,375 +0,0 @@
# 学习进度判断逻辑说明
## 📊 核心逻辑
**学习进度 = (累计学习时长 / 课程总时长) × 100%**
## 🔍 详细判断流程
### 1. 课件类型分类
系统将课件分为以下类型:
| 类型 | 说明 | 是否参与进度计算 | 计算方式 |
|------|------|----------------|---------|
| `video` | 视频课件 | ✅ **是** | 基于学习时长 |
| `document` | 文档课件PDF、Word、Excel等 | ✅ **是** | 基于完成状态 |
| `image` | 图片课件 | ✅ **是** | 基于完成状态 |
| `text` | 文本课件 | ❌ **否** | - |
### 2. 进度计算步骤(混合计算模式)
系统采用**混合计算模式**,不同类型课件使用不同的计算方式:
#### 步骤1查询课程的所有课件
```java
// 查询所有类型的课件
SELECT * FROM courseware WHERE course_id = ?
```
**课件分类**
- 视频课件(`type = 'video'`
- 图片课件(`type = 'image'`
- 文档课件(`type = 'document'`包括PDF
#### 步骤2分别计算各类型进度
**2.1 视频进度计算(基于时长)**
```java
// 计算视频总时长
videoTotalDuration = SUM(video.duration)
// 估算视频学习时长(从总学习时长中提取)
videoLearningDuration = totalDuration × 0.7 // 假设视频占70%
// 计算视频进度
videoProgress = (videoLearningDuration / videoTotalDuration) × 100
```
**关键点**
- 视频课件的 `duration` 字段必须设置(单位:秒)
- 如果所有视频的 `duration` 都未设置,视频进度 = 0%
**2.2 图片和PDF进度计算基于完成状态**
```java
// 统计图片和PDF总数
totalNonVideoCount = imageList.size() + documentList.size()
// 查询学习详情记录,估算已查看的课件数
viewedCount = min(学习详情记录数, totalNonVideoCount)
// 计算非视频进度
nonVideoProgress = (viewedCount / totalNonVideoCount) × 100
```
**关键点**
- 通过查询学习详情记录来判断学生是否查看过课件
- 每个图片/PDF查看一次默认算30秒学习时长
- 如果学生有学习记录,认为至少查看过部分课件
#### 步骤3计算权重并综合
```java
// 计算权重
videoWeight = (视频数量 / 总课件数量) × 100
nonVideoWeight = ((图片数量 + PDF数量) / 总课件数量) × 100
// 综合进度
courseProgress = (videoProgress × videoWeight + nonVideoProgress × nonVideoWeight) / 100
```
**计算公式**
```
综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100
其中:
- 视频权重 = 视频课件数 / 总课件数 × 100%
- 非视频权重 = (图片数 + PDF数) / 总课件数 × 100%
```
#### 步骤4限制范围
```java
if (progress > 100) progress = 100
if (progress < 0) progress = 0
```
进度限制在 **0-100%** 之间。
## 📝 代码位置
**文件**
```
Study-Vue-redis/ry-study-system/src/main/java/com/ddnai/system/service/impl/study/StudyLearningRecordServiceImpl.java
```
**方法**
```java
private BigDecimal calculateCourseProgress(Long studentId, Long courseId, ...)
```
## ⚠️ 重要限制
### 1. 只计算视频课件
**当前逻辑**
- ✅ 只有 `type = 'video'` 的课件参与进度计算
- ❌ 图片、PDF、文档等**不参与**进度计算
**影响**
- 如果课程只有图片或PDF没有视频进度会显示 **0%**
- 即使学生学习了图片或PDF也不会增加进度
### 2. 视频时长必须设置
**要求**
- 每个视频课件的 `duration` 字段必须设置(单位:秒)
- 如果 `duration` 为 NULL 或 0该视频不计入课程总时长
**影响**
- 如果所有视频的 `duration` 都未设置,课程总时长 = 0 → 进度 = 0%
- 即使学生学习了很长时间,进度也会显示 0%
### 3. 学习时长统计
**统计方式**
- 学生观看视频时,系统会记录学习时长
- 每次学习都会累加到 `total_duration` 字段
- 图片、PDF 等非视频课件的学习**不统计**学习时长
## 📊 实际案例
### 案例1正常情况
**课程配置**
- 视频112分钟720秒
- 视频210分钟600秒
- 课程总时长1320秒22分钟
**学生学习**
- 累计学习10分钟600秒
**进度计算**
```
进度 = (600 / 1320) × 100 = 45.45%
```
### 案例2视频时长未设置
**课程配置**
- 视频1duration = NULL
- 视频2duration = NULL
- 课程总时长0秒
**学生学习**
- 累计学习10分钟600秒
**进度计算**
```
进度 = (600 / 0) → 返回 0%避免除以0
```
### 案例3只有图片和PDF
**课程配置**
- 图片1xxx.jpg
- PDF1xxx.pdf
- 视频:无
- 总课件数2个
**学生学习**
- 查看了图片和PDF
- 学习详情记录2条每个课件查看一次
- 累计学习60秒2个课件 × 30秒/个)
**进度计算**
```
视频进度 = 0%(没有视频)
非视频进度 = (2个已查看 / 2个总数) × 100 = 100%
视频权重 = 0 / 2 × 100 = 0%
非视频权重 = 2 / 2 × 100 = 100%
综合进度 = (0 × 0% + 100% × 100%) / 100 = 100%
```
### 案例4混合类型视频+图片+PDF
**课程配置**
- 视频112分钟720秒
- 图片1xxx.jpg
- PDF1xxx.pdf
- 总课件数3个
**学生学习**
- 视频学习6分钟360秒
- 查看了图片和PDF
- 累计学习420秒360秒视频 + 60秒图片PDF
**进度计算**
```
视频进度 = (360 / 720) × 100 = 50%
非视频进度 = (2个已查看 / 2个总数) × 100 = 100%
视频权重 = 1 / 3 × 100 = 33.33%
非视频权重 = 2 / 3 × 100 = 66.67%
综合进度 = (50% × 33.33% + 100% × 66.67%) / 100 = 83.33%
```
## 🔧 如何修复进度问题
### 问题1进度显示0%,但学生已学习
**原因**:视频课件的 `duration` 未设置
**解决方案**
1. 在管理端编辑视频课件,设置正确的时长(秒)
2. 执行SQL重新计算进度
```sql
UPDATE learning_record lr
SET progress = (
SELECT
CASE
WHEN SUM(cw.duration) IS NULL OR SUM(cw.duration) = 0 THEN 0
ELSE LEAST(100, ROUND(lr.total_duration * 100.0 / SUM(cw.duration), 2))
END
FROM courseware cw
WHERE cw.course_id = lr.course_id AND cw.type = 'video'
)
WHERE lr.total_duration > 0;
```
### 问题2图片和PDF进度计算已解决
**当前实现**
- ✅ 图片和PDF**已参与**进度计算
- ✅ 采用基于完成状态的计算方式
- ✅ 通过查询学习详情记录来判断是否查看过
**计算逻辑**
1. 统计课程中的图片和PDF数量
2. 查询学生的学习详情记录
3. 根据学习记录数量估算已查看的课件数
4. 计算完成度:已查看数 / 总课件数 × 100%
**注意事项**
- 前端需要在学生查看图片/PDF时记录学习详情
- 每个图片/PDF查看一次默认算30秒学习时长
- 如果课程只有图片和PDF没有视频进度会基于学习时长计算
## 📋 数据库字段说明
### courseware 表(课件表)
| 字段 | 类型 | 说明 | 是否必需 |
|------|------|------|---------|
| `id` | BIGINT | 课件ID | ✅ |
| `course_id` | BIGINT | 课程ID | ✅ |
| `type` | VARCHAR | 课件类型video/document/image/text | ✅ |
| `duration` | INT | 视频时长(秒,仅视频类型) | ⚠️ 视频必需 |
| `title` | VARCHAR | 课件标题 | ✅ |
| `file_path` | VARCHAR | 文件路径 | ✅ |
### learning_record 表(学习记录表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | BIGINT | 记录ID |
| `student_id` | BIGINT | 学生ID |
| `course_id` | BIGINT | 课程ID |
| `total_duration` | INT | 累计学习时长(秒) |
| `progress` | DECIMAL | 学习进度0-100 |
| `learn_count` | INT | 学习次数 |
| `last_learn_time` | DATETIME | 最后学习时间 |
## 🎯 总结
### 核心要点
1. **混合计算模式**
- ✅ 视频课件:基于学习时长计算
- ✅ 图片和PDF基于完成状态计算
- ❌ 文本课件:不参与进度计算
2. **视频时长必须设置**`duration` 字段必须大于0
3. **计算公式**
```
综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100
```
4. **进度范围**:限制在 0-100% 之间
5. **图片和PDF完成判断**
- 通过查询学习详情记录来判断
- 每个图片/PDF查看一次默认算30秒学习时长
- 如果学生有学习记录,认为至少查看过部分课件
### 判断流程图
```
开始
查询课程的所有课件
分类统计视频、图片、PDF
┌─────────────────┬─────────────────┐
│ 视频进度计算 │ 非视频进度计算 │
│ (基于时长) │ (基于完成状态) │
├─────────────────┼─────────────────┤
│ 1. 计算视频总时长│ 1. 统计图片/PDF数│
│ 2. 估算视频学习 │ 2. 查询学习记录 │
│ 时长 │ 3. 估算已查看数 │
│ 3. 计算视频进度 │ 4. 计算完成度 │
└─────────────────┴─────────────────┘
计算权重:
- 视频权重 = 视频数 / 总课件数
- 非视频权重 = (图片+PDF)数 / 总课件数
综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100
限制在 0-100% 之间
返回进度
```
## 📚 相关文件
- **后端代码**`StudyLearningRecordServiceImpl.java`
- **诊断SQL**`database_check_learning_progress.sql`
- **修复SQL**`database_fix_video_duration_and_progress.sql`
- **说明文档**`学习进度问题修复说明.md`
## 🔍 调试方法
### 查看后端日志
后端已添加详细日志,重启服务后可以看到:
```
INFO - 开始计算学习进度 - 学生ID: 109, 课程ID: 3
INFO - 课程 3 包含 2 个视频课件
WARN - 视频课件 8 (亡羊补牢) 的时长未设置或为0
INFO - 课程 3 总时长: 1440秒 (24分钟), 2个视频有时长, 0个视频无时长
INFO - 学生 109 对课程 3 的累计学习时长: 3秒 (0分钟)
INFO - 计算进度: 3 / 1440 * 100 = 0.21%
INFO - 最终进度: 0.21%
```
### 执行诊断SQL
```sql
-- 查看学习记录和进度
SELECT
lr.id,
lr.course_id,
c.course_name,
lr.total_duration as '累计时长(秒)',
lr.progress as '进度(%)',
(SELECT SUM(duration) FROM courseware WHERE course_id = lr.course_id AND type = 'video') as '课程总时长(秒)'
FROM learning_record lr
LEFT JOIN course c ON lr.course_id = c.id;
```

View File

@ -1,408 +0,0 @@
# 在线学习系统 - 学生使用指南
## 📚 目录
1. [系统登录](#系统登录)
2. [课程学习](#课程学习)
3. [参加考试](#参加考试)
4. [查看成绩](#查看成绩)
5. [语音评测](#语音评测)
6. [学习记录](#学习记录)
7. [个人中心](#个人中心)
8. [常见问题](#常见问题)
---
## 🔐 系统登录
### 登录步骤
1. 打开App进入登录页面
2. 输入您的**用户名**和**密码**
3. 点击"登录"按钮
4. 登录成功后,系统会自动跳转到首页
### 首次登录
- 如果是首次登录,系统会提示您完善个人信息
- 建议修改默认密码,确保账号安全
### 忘记密码
- 如忘记密码,请联系任课教师或系统管理员重置密码
---
## 📖 课程学习
### 2.1 查看课程列表
**功能说明**:查看分配给您的所有课程。
**操作步骤**
1. 在首页点击 **"我的课程"** 或进入 **"课程"** 模块
2. 在课程列表中,可以看到:
- 课程名称
- 学科分类
- 学习进度(百分比)
- 课程状态(未开始/进行中/已结束)
3. 可以按学科进行筛选
**课程状态说明**
- **未开始**:课程尚未到开始时间
- **进行中**:可以开始学习
- **已结束**:课程已超过结束时间,无法继续学习
### 2.2 学习课程
**功能说明**:观看视频课件或查看图文课件。
**操作步骤**
1. 在课程列表中,点击要学习的课程
2. 进入课程详情页面,可以看到:
- 课程基本信息
- 课件列表
3. 点击要学习的课件,开始学习
#### 视频课件学习
1. 点击视频课件
2. 视频开始播放
3. **横屏观看**:点击全屏按钮或旋转设备,获得更好的观看体验
4. 系统会自动记录学习进度:
- 播放位置会自动保存
- 下次学习时会从上次停止的位置继续
- 学习时长会自动累计
**注意事项**
- 学习过程中请保持网络连接
- 系统会定期上传学习进度,请勿频繁切换应用
- 建议在WiFi环境下观看视频节省流量
#### 图文课件学习
1. 点击图文课件PDF、PPT、Word、图片等
2. 系统会打开文件查看器
3. 可以:
- 放大缩小查看
- 左右滑动翻页
- 下载到本地(如支持)
### 2.3 学习进度
**功能说明**:查看自己的学习进度。
**查看方式**
- 在课程列表中,每个课程下方显示学习进度条
- 进入课程详情,可以看到详细的学习统计:
- 已学习时长
- 总时长
- 学习次数
- 完成百分比
---
## 📝 参加考试
### 3.1 查看考试列表
**功能说明**:查看分配给您的所有考试。
**操作步骤**
1. 在首页点击 **"考试"** 或进入 **"考试"** 模块
2. 在考试列表中,可以看到:
- 考试名称
- 科目
- 考试时长
- 总分
- 考试状态(未开始/进行中/已结束)
3. 点击考试进入考试详情
### 3.2 开始考试
**操作步骤**
1. 在考试列表中,找到状态为 **"进行中"** 的考试
2. 点击考试,进入考试详情页面
3. 查看考试信息:
- 考试名称
- 考试时长
- 题目数量
- 总分
4. 点击 **"开始考试"** 按钮
5. 确认开始后,考试倒计时开始
### 3.3 答题
**功能说明**:回答考试题目。
**答题步骤**
#### 单选题
1. 阅读题目和选项
2. 点击选择正确答案(只能选择一个)
3. 选中的选项会高亮显示
#### 多选题
1. 阅读题目和选项
2. 勾选所有正确答案(可以多选)
3. 选中的选项会显示勾选标记
#### 判断题
1. 阅读题目
2. 选择 **"正确"** 或 **"错误"**
3. 只能选择一个答案
#### 填空题
1. 阅读题目
2. 在输入框中输入答案
3. 可以填写多个空(如有多个填空)
#### 简答题
1. 阅读题目
2. 在文本框中输入详细答案
3. 可以输入多行文字
**答题技巧**
- 答题过程中可以随时修改答案
- 页面顶部显示答题进度和剩余时间
- 建议先答完所有题目,再检查一遍
- 注意考试倒计时,合理安排时间
### 3.4 提交考试
**操作步骤**
1. 答完所有题目后,检查一遍答案
2. 点击页面底部的 **"提交试卷"** 按钮
3. 系统会提示确认提交
4. 确认后,考试结束,无法再修改答案
**注意事项**
- 提交前请仔细检查,提交后无法修改
- 如果考试时间到了,系统会自动提交
- 提交后可以立即查看成绩(如果教师已设置)
---
## 📊 查看成绩
### 4.1 查看成绩列表
**功能说明**:查看所有考试的成绩。
**操作步骤**
1. 在首页点击 **"成绩"** 或进入 **"成绩"** 模块
2. 在成绩列表中,可以看到:
- 考试名称
- 科目
- 总分和得分
- 提交时间
- 考试状态
### 4.2 查看成绩详情
**操作步骤**
1. 在成绩列表中,点击要查看的成绩
2. 进入成绩详情页面,可以看到:
- 考试基本信息
- 总分和得分
- 每道题的得分情况
- 答题详情:
- 您的答案
- 正确答案
- 得分情况
3. 可以查看:
- 做对的题目(绿色标记)
- 做错的题目(红色标记)
- 未得分的题目
**成绩说明**
- **客观题**(单选题、多选题、判断题):系统自动评分
- **主观题**(填空题、简答题):需要教师手动评分,可能显示为"待评分"
### 4.3 打印成绩单
**功能说明**将成绩单打印或保存为PDF。
**操作步骤**
1. 在成绩详情页面,点击 **"打印"** 按钮
2. 系统会生成成绩单页面
3. 使用浏览器的打印功能,可以:
- 直接打印
- 保存为PDF文件
---
## 🎤 语音评测
### 5.1 开始语音评测
**功能说明**:进行语音跟读练习,系统会自动评测发音。
**操作步骤**
1. 在首页点击 **"语音评测"** 或进入 **"语音"** 模块
2. 在评测页面,可以看到:
- 评测文本(课文或文字内容)
3. 点击 **"开始录制"** 按钮
4. 系统开始录音,您可以:
- 看着文本内容进行朗读
- 录音过程中会显示波形动画
5. 朗读完成后,点击 **"停止录制"** 按钮
6. 系统自动上传音频并进行评测
### 5.2 查看评测结果
**操作步骤**
1. 评测完成后,系统会显示评测结果:
- **总分**:整体评分
- **准确度**:发音准确程度
- **流畅度**:朗读流畅程度
- **完整度**:内容完整程度
- **发音**:发音质量评分
2. 可以:
- 播放自己的录音
- 查看详细评测数据
- 查看评测建议
### 5.3 查看历史记录
**操作步骤**
1. 在语音评测页面,点击 **"历史记录"** 按钮
2. 查看所有历史评测记录
3. 点击记录可以查看详情和重新播放录音
**练习建议**
- 多练习可以提高发音水平
- 注意听自己的录音,找出不足
- 根据评测建议改进发音
---
## 📈 学习记录
### 6.1 查看学习统计
**功能说明**:查看自己的学习统计数据。
**操作步骤**
1. 在首页点击 **"学习记录"** 或进入 **"学习"** 模块
2. 在学习记录页面,可以看到:
- **总学习时长**:累计学习时间
- **学习次数**:总学习次数
- **已完成课程**:已完成学习的课程数量
- **学习进度**:各课程的学习进度
### 6.2 查看详细记录
**操作步骤**
1. 在学习记录页面,向下滚动查看详细记录列表
2. 每条记录显示:
- 课程名称
- 学习时间
- 学习时长
- 学习进度
3. 点击记录可以跳转到课程详情页面
---
## 👤 个人中心
### 7.1 查看个人信息
**操作步骤**
1. 在首页点击 **"我的"** 或底部导航栏的 **"我的"** 图标
2. 在个人中心页面,可以看到:
- 头像
- 姓名
- 学号
- 班级信息
- 其他个人信息
### 7.2 修改个人信息
**操作步骤**
1. 在个人中心页面,点击 **"编辑"** 按钮
2. 可以修改:
- 头像(上传新头像)
- 昵称
- 其他可编辑的信息
3. 点击 **"保存"** 完成修改
### 7.3 退出登录
**操作步骤**
1. 在个人中心页面,点击 **"退出登录"** 按钮
2. 确认退出后,返回登录页面
---
## ❓ 常见问题
### Q1: 课程视频无法播放?
**A**: 请检查:
- 网络连接是否正常
- 视频格式是否支持
- 尝试刷新页面或重新进入课程
### Q2: 考试时网络断开怎么办?
**A**:
- 系统会自动保存答题进度
- 重新连接网络后,可以继续答题
- 建议在WiFi环境下参加考试
### Q3: 考试时间到了还没答完?
**A**:
- 系统会在时间到达时自动提交试卷
- 已答的题目会保存未答的题目按0分计算
- 建议合理安排时间,先答完所有题目
### Q4: 如何查看已提交的考试答案?
**A**:
- 在成绩详情页面,可以查看所有题目的答案
- 包括您的答案和正确答案对比
### Q5: 语音评测分数很低怎么办?
**A**:
- 多练习可以提高发音水平
- 注意听自己的录音,找出问题
- 根据评测建议改进
- 可以多次练习同一内容
### Q6: 学习进度没有更新?
**A**: 请检查:
- 网络连接是否正常
- 是否完整观看了视频(快进可能不计入进度)
- 尝试刷新页面或重新进入课程
### Q7: 如何下载课程资料?
**A**:
- 部分课件支持下载功能
- 点击课件详情页面的"下载"按钮
- 下载的文件会保存到设备本地
### Q8: 忘记考试时间怎么办?
**A**:
- 在考试列表中,可以看到考试状态
- 建议定期查看考试列表,避免错过考试
- 可以设置提醒(如设备支持)
---
## 📞 帮助与支持
如遇到问题,可以:
1. 联系任课教师
2. 联系系统管理员
3. 查看系统帮助文档
**联系方式**
- 任课教师:[根据实际情况填写]
- 系统管理员:[根据实际情况填写]
---
## 💡 学习建议
1. **合理安排时间**:制定学习计划,每天坚持学习
2. **认真完成作业**:及时完成课程学习和考试
3. **多练习**:特别是语音评测,多练习可以提高水平
4. **及时查看反馈**:查看成绩和评测结果,找出不足并改进
5. **保持网络畅通**:确保学习过程中网络连接稳定
---
**最后更新日期**2025年11月

View File

@ -1,236 +0,0 @@
# 导入进度实时更新功能说明
## 功能概述
已成功实现学员数据导入的实时进度更新功能,解决了导入大量数据时页面卡住的问题。
## 实现方式
使用 **SSE (Server-Sent Events)** 技术实现服务端到客户端的实时进度推送。
## 核心改动
### 1. 后端改动
#### 新增文件
- **ImportProgress.java** - 进度信息实体类
- 位置:`ry-study-common/src/main/java/com/ddnai/common/core/domain/ImportProgress.java`
- 包含:总数、已处理数、成功数、失败数、百分比、状态、消息等字段
- **ImportProgressManager.java** - 进度管理器
- 位置:`ry-study-common/src/main/java/com/ddnai/common/utils/ImportProgressManager.java`
- 功能管理导入任务、SSE连接、进度更新推送
#### 修改文件
- **StudyClassUserController.java**
- 修改 `importData` 方法改为异步处理返回任务ID
- 新增 `getImportProgress` 方法提供SSE端点
- **IStudyClassUserService.java**
- 新增 `importStudentsWithProgress` 方法接口
- **StudyClassUserServiceImpl.java**
- 实现 `importStudentsWithProgress` 方法每处理10条数据更新一次进度
### 2. 前端改动
#### 修改文件
- **classUser/index.vue**
- 数据字段:新增 `importTaskId`、`importEventSource`、`importMessage`、`importProcessed`、`importTotal`、`importSuccess`、`importFailed`
- 方法修改:
- `handleFileSuccess` - 接收taskId并建立SSE连接
- 新增 `connectImportProgress` - 连接SSE并监听进度事件
- 新增 `closeImportProgress` - 关闭SSE连接并清理资源
- 生命周期:新增 `beforeDestroy` 钩子清理SSE连接
- UI更新实时显示处理进度和详细统计
## 功能特点
### 实时进度显示
```
正在处理: 已完成 3158/3800 (成功 2704, 失败 454)
进度条83%
```
### 导入流程
1. **文件上传**
- 显示上传进度0-100%
- 状态:`正在上传文件... XX%`
2. **数据解析**
- 后端解析Excel文件
- 生成唯一任务ID
- 状态:`文件上传完成,开始处理数据...`
3. **异步导入**
- 每处理10条数据推送一次进度
- 实时更新:已处理数、成功数、失败数
- 进度条实时更新
4. **完成反馈**
- 自动关闭进度对话框
- 刷新数据列表
- 弹出详细导入结果
## 使用方法
### 导入学员数据
1. 进入"学员管理"页面
2. 点击"导入"按钮
3. 选择Excel文件支持.xls/.xlsx
4. 点击"确定"开始导入
5. **实时查看进度**
- 上传进度
- 处理进度(已完成/总数)
- 成功/失败统计
6. 导入完成后查看详细结果
### 技术优势
**异步处理** - 不阻塞HTTP请求避免超时
**实时反馈** - 用户可以看到真实处理进度
**批量优化** - 每10条更新一次进度减少网络开销
**资源管理** - 自动清理SSE连接防止内存泄漏
**错误处理** - 完善的异常处理和用户提示
## 测试建议
### 测试场景
1. **小批量数据(< 100条**
- 验证功能正常运行
- 检查进度更新流畅
2. **中批量数据100-1000条**
- 验证进度实时性
- 检查性能表现
3. **大批量数据(> 3000条**
- 验证页面不卡顿
- 检查进度准确性
- 验证完成后结果正确
### 测试步骤
1. 准备测试数据Excel文件建议3000条以上
2. 启动后端服务
3. 启动前端服务
4. 登录系统
5. 进入"学员管理"页面
6. 导入测试文件
7. 观察进度更新:
- 上传阶段进度
- 处理阶段进度
- 成功/失败统计
- 百分比变化
8. 等待完成,查看结果
9. 刷新列表验证数据正确性
## 注意事项
### 浏览器兼容性
- ✅ Chrome/Edge推荐
- ✅ Firefox
- ✅ Safari
- ❌ IE不支持EventSource
### 网络要求
- SSE需要保持长连接
- 确保防火墙不阻止SSE连接
- 代理服务器需要支持SSE
### 性能建议
- 单次导入建议不超过10000条
- 超大数据建议分批导入
- 服务器内存建议至少2GB
## 故障排查
### 问题1进度不更新
**可能原因:**
- SSE连接失败
- 后端处理异常
**解决方法:**
1. 打开浏览器控制台查看错误
2. 检查网络连接
3. 查看后端日志
### 问题2导入失败
**可能原因:**
- Excel格式不正确
- 数据验证失败
- 数据库连接问题
**解决方法:**
1. 下载最新模板
2. 检查数据格式
3. 查看错误提示
4. 查看后端日志
### 问题3页面白屏
**可能原因:**
- 前端代码错误
- 依赖缺失
**解决方法:**
1. 清除浏览器缓存
2. 重新编译前端代码
3. 检查控制台错误
## 技术细节
### SSE事件格式
```json
{
"taskId": "uuid",
"total": 3800,
"processed": 3158,
"success": 2704,
"failed": 454,
"duplicate": 0,
"percentage": 83,
"status": "processing",
"message": "正在处理: 已完成 3158/3800 (成功 2704, 失败 454)",
"result": null
}
```
### 状态说明
- **processing** - 处理中
- **completed** - 完成
- **failed** - 失败
## 扩展性
该功能框架可以轻松扩展到其他导入场景:
- 题库导入
- 课程导入
- 用户导入
- 其他批量数据导入
只需:
1. 复用 `ImportProgressManager`
2. 在Service层调用进度更新方法
3. 前端使用相同的SSE连接模式
## 开发者
- 开发日期2024
- 技术栈Spring Boot + Vue.js + SSE
- 更新记录:初始版本

View File

@ -1,145 +0,0 @@
# 快速生成SQL说明无需Python环境
## 方法一使用SQL模板最简单
1. **打开模板文件**
- 打开 `courseware_import_template.sql` 文件
2. **获取文件信息**
- 打开文件资源管理器,进入目录:`D:/wwwroot/study_web/web/profile/upload/upload/2025/11/18`
- 查看每个文件的:
- 文件名(包括扩展名)
- 文件大小(右键 -> 属性 -> 查看字节数)
3. **填写SQL**
- 复制模板中的一行
- 根据实际文件信息修改:
- `title`:课件标题(使用文件名,去掉扩展名)
- `type`:文件类型('video'/'document'/'image'
- `file_path`:文件路径(格式:`/profile/upload/2025/11/18/文件名`
- `file_size`:文件大小(字节)
- `file_name`:原始文件名
- `subject_id`学科ID不知道填 `NULL`
- `upload_user_id`上传人ID管理员通常是 `1`
4. **执行SQL**
- 在数据库管理工具中执行修改后的SQL
## 方法二使用Excel生成SQL推荐适合文件较多
### 步骤1在Excel中列出文件信息
创建一个Excel表格包含以下列
| 文件名 | 文件大小(字节) | 类型 | 标题 | 学科ID | 文件路径 |
|--------|---------------|------|------|--------|----------|
| 微积分第一章.mp4 | 52428800 | video | 微积分第一章 | 2 | /profile/upload/2025/11/18/微积分第一章.mp4 |
| 数学公式.pdf | 2097152 | document | 数学公式 | 2 | /profile/upload/2025/11/18/数学公式.pdf |
### 步骤2使用Excel公式生成SQL
在Excel中添加一列"SQL语句"使用以下公式假设数据从第2行开始
```excel
="('"&D2&"', '"&C2&"', '"&F2&"', "&B2&", '"&A2&"', "&IF(E2="","NULL",E2)&", NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),"
```
### 步骤3复制SQL语句
1. 复制所有生成的SQL语句
2. 在SQL模板中替换VALUES部分
3. 删除最后一行的逗号
4. 执行SQL
## 方法三:使用在线工具
可以使用在线SQL生成工具例如
- 搜索"SQL INSERT语句生成器"
- 输入字段和值自动生成SQL
## 快速参考
### 文件类型判断
- **video视频**mp4, avi, mov, wmv, flv, mkv, webm, m4v, 3gp
- **document文档**doc, docx, xls, xlsx, ppt, pptx, txt, pdf
- **image图片**jpg, jpeg, png, gif, bmp, webp
### 文件大小转换
- 1 KB = 1024 字节
- 1 MB = 1024 KB = 1048576 字节
- 1 GB = 1024 MB = 1073741824 字节
**查看文件大小(字节)的方法:**
- Windows右键文件 -> 属性 -> 查看"大小"(字节)
- 或者文件大小MB× 1048576 = 字节数
### 查询学科ID
```sql
SELECT id, subject_name FROM subject;
```
### 查询用户ID
```sql
SELECT user_id, user_name, nick_name FROM sys_user WHERE user_type = '00';
```
## 完整示例
假设您有以下文件:
- `微积分第一章.mp4` (50MB)
- `数学公式.pdf` (2MB)
- `课程封面.jpg` (500KB)
生成的SQL如下
```sql
INSERT INTO `courseware` (
`title`, `type`, `file_path`, `file_size`, `file_name`,
`subject_id`, `grade`, `course_id`, `class_id`, `upload_user_id`,
`description`, `duration`, `create_by`, `create_time`,
`update_by`, `update_time`, `remark`
) VALUES
('微积分第一章', 'video', '/profile/upload/2025/11/18/微积分第一章.mp4', 52428800, '微积分第一章.mp4', 2, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('数学公式', 'document', '/profile/upload/2025/11/18/数学公式.pdf', 2097152, '数学公式.pdf', 2, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL),
('课程封面', 'image', '/profile/upload/2025/11/18/课程封面.jpg', 512000, '课程封面.jpg', 2, NULL, NULL, NULL, 1, NULL, NULL, 'admin', NOW(), '', NULL, NULL);
```
## 注意事项
1. **文件路径格式**`/profile/upload/2025/11/18/文件名`
- 注意:路径中只有一个 `upload`,不是 `upload/upload`
- 如果您的实际web访问路径不同请相应调整
2. **执行前备份**
```sql
CREATE TABLE courseware_backup AS SELECT * FROM courseware;
```
3. **验证结果**
```sql
SELECT id, title, type, file_path, file_name, create_time
FROM courseware
WHERE create_time >= '2025-11-18'
ORDER BY create_time DESC;
```
## 如果文件很多怎么办?
如果文件很多(比如几十个),建议:
1. **使用Excel方法**在Excel中列出所有文件使用公式批量生成SQL
2. **分批导入**每次导入10-20个文件避免SQL语句过长
3. **使用数据库管理工具**有些工具支持从CSV导入数据
## 需要帮助?
如果遇到问题,请检查:
- 文件路径是否正确
- 文件大小是否正确(字节数)
- 文件类型是否正确video/document/image
- SQL语法是否正确注意引号、逗号等

View File

@ -1,340 +0,0 @@
# 在线学习系统 - 教师使用指南
## 📚 目录
1. [系统登录](#系统登录)
2. [课程管理](#课程管理)
3. [考试管理](#考试管理)
4. [题库管理](#题库管理)
5. [成绩管理](#成绩管理)
6. [学习监控](#学习监控)
7. [学习记录查看](#学习记录查看)
8. [语音评测管理](#语音评测管理)
9. [常见问题](#常见问题)
---
## 🔐 系统登录
### 登录步骤
1. 打开系统登录页面
2. 输入您的**用户名**和**密码**
3. 点击"登录"按钮
4. 登录成功后,系统会自动跳转到首页
### 注意事项
- 首次登录后建议修改密码
- 如忘记密码,请联系系统管理员重置
---
## 📖 课程管理
### 2.1 课件上传
**功能说明**上传教学课件包括视频、PDF、图片等格式。
**操作步骤**
1. 进入 **学习管理 → 课件管理**
2. 点击 **"新增"** 按钮
3. 填写课件信息:
- **课件名称**:输入课件标题
- **学科分类**:选择对应的学科(如:数学、语文、英语等)
- **课件类型**:选择"视频课件"或"图文课件"
- **课件文件**:点击上传按钮,选择要上传的文件
4. 点击 **"确定"** 保存
**支持的文件格式**
- **视频课件**MP4、AVI等视频格式
- **图文课件**PDF、PPT、Word、图片JPG/PNG
**注意事项**
- 课件必须选择学科分类,否则无法保存
- 上传大文件时请耐心等待
### 2.2 课程创建与发布
**功能说明**:创建课程并分配给指定班级或学生。
**操作步骤**
1. 进入 **学习管理 → 课程管理**
2. 点击 **"新增"** 按钮
3. 填写课程信息:
- **课程名称**:输入课程标题
- **选择科目**:选择对应的学科
- **选择课件**:从已上传的课件中选择
- **开始时间**:设置课程开始时间
- **结束时间**:设置课程结束时间
4. 点击 **"确定"** 保存
5. 在课程列表中,找到刚创建的课程,点击 **"分配"** 按钮
6. 选择要分配的**班级**或**学生**
7. 点击 **"确定"** 完成分配
**课程状态说明**
- **未开始**:课程尚未到开始时间
- **进行中**:课程在有效期内
- **已结束**:课程已超过结束时间
- **已禁用**:课程被暂停使用
---
## 📝 考试管理
### 3.1 创建考试
**功能说明**:创建考试并设置题目。
**操作步骤**
#### 方式一:从题库抽取题目
1. 在创建考试页面,选择 **"从题库抽取"**
2. 选择题库
3. 配置题型和题量(同上)
4. 点击 **"从题库抽取"** 按钮
5. 预览抽取的题目,确认无误后点击 **"保存题目"**
#### 方式二:手动添加题目
1. 创建考试基本信息后,在考试列表中点击 **"题目管理"** 按钮
2. 在题目管理弹窗中:
- 点击 **"新增题目"** 添加新题目
- 或点击 **"从题库选择"** 从已有题库中选择题目
3. 填写题目信息:
- **题型**:选择题目类型
- **题干**:输入题目内容
- **选项**:对于选择题和判断题,添加选项
- 点击 **"添加选项"** 添加更多选项
- 点击选项后的删除按钮可删除选项
- 点击 **"生成模板"** 可快速生成A、B、C、D选项模板
- **正确答案**
- 单选题/判断题:选择正确答案
- 多选题勾选所有正确答案系统自动保存为A,B,C格式
- 填空题/简答题:输入正确答案
- **分值**:设置该题的分值
4. 点击 **"保存"** 保存题目
### 3.2 题目管理
**功能说明**:查看、编辑、删除考试题目。
**操作步骤**
1. 进入 **学习管理 → 考试管理**
2. 在考试列表中,找到要管理的考试
3. 点击 **"题目管理"** 按钮(位于"编辑"按钮左侧)
4. 在题目管理弹窗中:
- **查看题目**:浏览所有题目
- **编辑题目**:点击题目内容进行修改
- **删除题目**:点击题目右侧的删除按钮
- **新增题目**:点击 **"新增题目"** 按钮
- **从题库选择**:点击 **"从题库选择"** 按钮,从题库中批量导入题目
5. 修改完成后,点击 **"保存"** 按钮
**从题库选择题目**
1. 点击 **"从题库选择"** 按钮
2. 在左侧勾选要使用的题库
3. 在右侧题目列表中勾选要导入的题目
4. 设置默认分值(用于未设置分值的题目)
5. 点击 **"导入所选题目"** 完成导入
### 3.3 发布考试
**功能说明**将考试发布给学生学生可以在App端参加考试。
**操作步骤**
1. 进入 **学习管理 → 考试管理**
2. 找到状态为 **"草稿"** 的考试
3. 点击 **"发布"** 按钮
4. 确认发布后,考试状态变为 **"已发布"**
5. 学生可以在App端看到并参加该考试
**注意事项**
- 只有草稿状态的考试才能发布
- 发布前请确保考试已添加题目
- 已发布的考试无法修改题目,如需修改请先结束考试
---
## 📚 题库管理
### 4.1 创建题库
**功能说明**:创建自己的题库,用于后续考试出题。
**操作步骤**
1. 进入 **学习管理 → 题库管理**
2. 点击 **"新增"** 按钮
3. 填写题库信息:
- **题库名称**:输入题库名称
- **学科分类**:选择对应的学科
- **备注**:可填写题库说明(可选)
4. 点击 **"确定"** 保存
### 4.2 添加题目到题库
**操作步骤**
1. 在题库列表中,找到要添加题目的题库
2. 点击 **"详情"** 按钮
3. 在题库详情页面,点击 **"添加题目"** 按钮
4. 填写题目信息(同考试题目管理)
5. 点击 **"确定"** 保存
### 4.3 批量导入题目
**操作步骤**
1. 在题库详情页面,点击 **"导入题目"** 按钮
2. 下载导入模板Excel格式
3. 按照模板格式填写题目信息
4. 上传填写好的Excel文件
5. 系统自动解析并导入题目
### 4.4 编辑和删除题目
**操作步骤**
1. 在题库详情页面,找到要编辑的题目
2. 点击 **"编辑"** 按钮修改题目
3. 点击 **"删除"** 按钮删除题目
4. 支持批量删除:勾选多个题目后,点击 **"批量删除"** 按钮
---
## 📊 成绩管理
### 5.1 查看学生成绩
**功能说明**:查看学生考试成绩和答题详情。
**操作步骤**
1. 进入 **学习管理 → 成绩管理**
2. 在成绩列表中,可以查看:
- 考试名称
- 学生姓名
- 总分和得分
- 提交时间
- 考试状态
3. 点击 **"查看详情"** 按钮,查看:
- 每道题的得分情况
- 学生的答题内容
- 正确答案对比
### 5.2 手动评分
**功能说明**:对于主观题(填空题、简答题),需要手动评分。
**操作步骤**
1. 在成绩详情页面,找到需要评分的主观题
2. 查看学生的答题内容
3. 在 **"得分"** 输入框中输入分数
4. 点击 **"保存"** 按钮保存评分
5. 系统会自动更新总分
### 5.3 成绩导出
**功能说明**将成绩数据导出为Excel文件。
**操作步骤**
1. 在成绩管理页面,设置筛选条件(可选)
2. 点击 **"导出"** 按钮
3. 选择导出字段
4. 点击 **"确定"** 下载Excel文件
---
## 📹 学习监控
### 6.1 实时监控
**功能说明**:实时查看学生的学习画面。
**操作步骤**
1. 进入 **学习管理 → 学习监控**
2. 在监控列表中,可以看到:
- 学生姓名
- 当前学习课程
3. 点击 **"查看详情"** 查看更详细的监控信息
### 6.2 历史记录查看
---
## 📈 学习记录查看
### 7.1 查看学习进度
**功能说明**:查看学生的学习进度和统计信息。
**操作步骤**
1. 进入 **学习管理 → 学习记录**
2. 在记录列表中,可以查看:
- 学生姓名
- 课程名称
- 学习进度(百分比)
- 学习时长
- 学习次数
- 最后学习时间
3. 点击 **"查看详情"** 查看详细的学习记录
### 7.2 学习统计
**功能说明**:查看学生的学习统计数据。
**统计信息包括**
- 总学习时长
- 学习次数
- 已完成课程数
- 学习进度分布
---
## 🎤 语音评测管理
### 8.1 查看语音评测记录
**功能说明**:查看学生的语音评测结果。
**操作步骤**
1. 进入 **学习管理 → 语音评测**
2. 在评测列表中,可以查看:
- 学生姓名
- 评测内容
- 评测分数
- 评测时间
3. 点击 **"查看详情"** 查看:
- 详细评测结果(准确度、流畅度、完整度、发音)
- 评测音频
- 评测文本
---
## ❓ 常见问题
### Q1: 如何修改已发布的考试?
**A**: 已发布的考试无法直接修改。如需修改,请先结束考试(将状态改为"已结束"),然后创建新的考试。
### Q2: 学生看不到我发布的课程?
**A**: 请检查:
- 课程是否已正确分配给学生或班级
- 课程的开始时间和结束时间是否在有效期内
- 学生是否已登录App端
### Q3: 如何批量导入学生信息?
**A**: 请联系系统管理员,管理员可以在用户管理中批量导入学生信息。
### Q4: 视频课件上传失败?
**A**: 请检查:
- 文件格式是否支持MP4、AVI等
- 文件大小是否过大(建议压缩后再上传)
- 网络连接是否正常
### Q5: 如何查看某个学生的所有成绩?
**A**: 在成绩管理页面,使用搜索功能,输入学生姓名进行筛选。
### Q6: 题目管理中的"从题库选择"功能如何使用?
**A**:
1. 点击"从题库选择"按钮
2. 在左侧勾选要使用的题库
3. 在右侧题目列表中勾选要导入的题目
4. 设置默认分值
5. 点击"导入所选题目"完成导入
---

View File

@ -1,144 +0,0 @@
# 日志乱码问题修复说明
## 问题描述
应用运行时,控制台和日志文件中出现中文乱码,例如:
```
02:54:07.747 [restartedMain] INFO sys-user - [shutdownAsyncManager,31] - ====关闭后台任务线程池====
```
显示为乱码。
## 问题原因
1. **Logback 编码未配置**logback.xml 中的 encoder 没有指定 charset默认使用系统编码
2. **JVM 编码未设置**:启动时没有指定 `-Dfile.encoding=UTF-8` 参数
3. **Windows 控制台编码**Windows 控制台默认使用 GBK 编码
## 修复内容
### 1. 修复 logback.xml 编码配置
**文件位置:** `ry-study-admin/src/main/resources/logback.xml`
**修改内容:** 为所有 encoder 添加 UTF-8 编码配置
#### 修改前:
```xml
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
```
#### 修改后:
```xml
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
```
**修改的 appender**
- `console` - 控制台输出
- `file_info` - 系统信息日志
- `file_error` - 系统错误日志
- `sys-user` - 用户操作日志
### 2. 修复 Spring Boot Maven 插件配置
**文件位置:** `ry-study-admin/pom.xml`
**修改内容:** 在 spring-boot-maven-plugin 中添加 JVM 编码参数
```xml
<configuration>
<fork>true</fork>
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
</configuration>
```
### 3. 修复启动脚本编码配置
**文件位置:**
- `ry.bat` (Windows)
- `ry.sh` (Linux)
**修改内容:** 在 JVM_OPTS 中添加 `-Dfile.encoding=UTF-8`
#### Windows (ry.bat)
```batch
set JVM_OPTS="-Dname=%AppName% -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai ..."
```
#### Linux (ry.sh)
```bash
JVM_OPTS="-Dname=$AppName -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai ..."
```
## 验证方法
### 方法1检查日志输出
重新启动应用后,查看日志输出,中文应该正常显示:
```
02:54:07.747 [restartedMain] INFO sys-user - [shutdownAsyncManager,31] - ====关闭后台任务线程池====
```
### 方法2检查日志文件
查看日志文件(如 `sys-user.log`),中文应该正常显示。
### 方法3在 IntelliJ IDEA 中设置
如果使用 IntelliJ IDEA 运行,还需要:
1. **设置运行配置的 VM options**
```
-Dfile.encoding=UTF-8
```
2. **设置控制台编码:**
- File → Settings → Editor → File Encodings
- 确保所有编码设置为 UTF-8
3. **设置运行配置的编码:**
- Run → Edit Configurations
- 在 VM options 中添加:`-Dfile.encoding=UTF-8`
## Windows 控制台编码设置
如果 Windows 控制台仍然显示乱码,可以:
### 方法1设置控制台代码页
```cmd
chcp 65001
```
然后重新运行应用。
### 方法2在启动脚本中设置
`ry.bat` 文件开头添加:
```batch
@echo off
chcp 65001 >nul
```
## 修复状态
**logback.xml** - 已修复,所有 encoder 添加 UTF-8 编码
**pom.xml** - 已修复Spring Boot 插件添加编码参数
**ry.sh** - 已修复,添加编码参数
⚠️ **ry.bat** - 需要手动修复(文件本身有编码问题)
## 注意事项
1. **文件编码**:确保所有源代码文件使用 UTF-8 编码保存
2. **IDE 设置**:确保 IDE 的文件编码设置为 UTF-8
3. **控制台编码**Windows 控制台可能需要额外设置代码页
4. **重启应用**:修改配置后需要重新启动应用才能生效
## 如果仍有乱码
1. 检查 IDE 的文件编码设置
2. 检查 Windows 控制台代码页(运行 `chcp` 查看当前代码页)
3. 检查日志文件本身的编码(用支持 UTF-8 的编辑器打开)
4. 确认所有配置文件已保存为 UTF-8 编码

View File

@ -1,156 +0,0 @@
# 监控学习终端的学习界面功能问题分析报告
## 问题概述
用户反馈"监控学习终端的学习界面"功能无法看到界面,经过代码分析发现了几个关键问题。
## 发现的问题
### 1. **菜单配置缺失** ⚠️ 关键问题
**问题描述:**
- 项目使用动态路由机制,路由从后端数据库的 `sys_menu` 表获取
- `screenStream` 页面(实时监控界面)没有在数据库菜单表中配置
- 因此无法在侧边栏看到菜单项,无法访问该页面
**影响:**
- 教师端无法通过菜单访问实时监控界面
- 功能虽然已实现,但无法使用
**解决方案:**
需要在数据库 `sys_menu` 表中添加菜单配置,包括:
1. 主菜单项实时监控screenStream
2. 相关权限按钮:控制监控、查看列表等
### 2. **学生端实现缺失** ⚠️ 关键问题
**问题描述:**
- 代码中只有教师端的监控界面实现
- **缺少学生端的实现**,学生端需要:
- 连接 WebSocket (`/ws/screenStream/{userId}`)
- 接收监控控制指令start_capture/stop_capture
- 捕获屏幕截图
- 将截图以 base64 格式通过 WebSocket 发送给服务器
**当前状态:**
- 后端 WebSocket 处理器已实现,可以接收学生端连接
- 后端控制器可以发送控制指令
- 但**没有学生端代码**来执行屏幕捕获和发送
**影响:**
- 即使菜单配置好了,也无法真正监控学生屏幕
- 学生端无法响应监控指令
**解决方案:**
需要开发学生端功能:
1. 学生端 WebSocket 客户端连接
2. 屏幕截图捕获功能(可能需要使用浏览器 API 或第三方库)
3. 定时发送截图数据
### 3. **监控记录页面可能也有菜单问题**
**问题描述:**
- `study-ui/src/views/study/monitor/index.vue` 是监控记录管理页面
- 该页面可能也没有在菜单中配置
**解决方案:**
同样需要在菜单表中配置该页面的菜单项
## 技术架构分析
### 已实现的部分 ✅
1. **后端 WebSocket 处理器** (`ScreenStreamWebSocketHandler.java`)
- 支持学生端和监控端连接
- 支持屏幕帧数据转发
- 支持控制指令发送
2. **后端 REST API** (`ScreenStreamController.java`)
- 启动/停止屏幕流捕获
- 检查学生在线状态
- 获取在线学生数
3. **前端监控界面** (`study-ui/src/views/study/screenStream/index.vue`)
- 学生列表显示
- 实时屏幕画面显示
- WebSocket 连接管理
- 监控控制功能
4. **监控记录管理** (`StudyScreenMonitorController.java`)
- 截图上传接口(供 App 端使用)
- 监控记录查询
- 监控记录删除
### 缺失的部分 ❌
1. **菜单配置** - 数据库菜单表配置
2. **学生端实现** - 屏幕捕获和 WebSocket 客户端
3. **权限配置** - 相关权限标识可能未配置到角色
## 建议的修复步骤
### 第一步:添加菜单配置
执行 SQL 添加菜单项(需要先查询"学习管理"菜单的 parent_id
```sql
-- 1. 添加"实时监控"菜单假设学习管理菜单ID为 XXXX
INSERT INTO sys_menu (
menu_name, parent_id, order_num, path, component,
is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, remark
) VALUES (
'实时监控', XXXX, 10, 'screenStream', 'study/screenStream/index',
1, 0, 'C', '0', '0',
'study:monitor:control', 'monitor', 'admin', sysdate(), '监控学习终端的学习界面'
);
-- 获取刚插入的菜单ID
SELECT @menuId := LAST_INSERT_ID();
-- 2. 添加权限按钮
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) VALUES
('监控控制', @menuId, 1, '#', '', 1, 0, 'F', '0', '0', 'study:monitor:control', '#', 'admin', sysdate()),
('监控查询', @menuId, 2, '#', '', 1, 0, 'F', '0', '0', 'study:monitor:list', '#', 'admin', sysdate());
-- 3. 添加"监控记录"菜单(如果还没有)
INSERT INTO sys_menu (
menu_name, parent_id, order_num, path, component,
is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, remark
) VALUES (
'监控记录', XXXX, 11, 'monitor', 'study/monitor/index',
1, 0, 'C', '0', '0',
'study:monitor:list', 'log', 'admin', sysdate(), '学习监控记录管理'
);
```
### 第二步:开发学生端功能
需要创建学生端页面或组件,实现:
1. WebSocket 连接管理
2. 屏幕截图捕获(使用 `html2canvas` 或类似库)
3. 定时发送截图数据
### 第三步:配置角色权限
在系统管理 -> 角色管理中,为相应角色分配:
- `study:monitor:control` - 监控控制权限
- `study:monitor:list` - 监控查询权限
- `study:monitor:query` - 监控详情查询权限
- `study:monitor:remove` - 监控记录删除权限
## 总结
**核心问题:**
1. ✅ 功能代码已实现
2. ❌ 菜单配置缺失(无法访问)
3. ❌ 学生端实现缺失(无法真正监控)
**优先级:**
1. **高优先级**:添加菜单配置(让界面可见)
2. **高优先级**:开发学生端功能(让功能可用)
3. **中优先级**:配置角色权限(让权限正确)
## 备注
- 学生端屏幕捕获在浏览器中有限制,可能需要:
- 使用 `html2canvas` 库捕获当前页面
- 或使用浏览器扩展/桌面应用进行全屏捕获
- 或使用 Electron 等桌面应用框架
- WebSocket 配置看起来是正确的,`WebSocketConfig.java` 已正确配置
- 数据库表 `monitor_record` 应该已存在(从 Mapper 文件可以看出)

View File

@ -1,622 +0,0 @@
# 在线学习系统 - 管理员使用指南
## 📚 目录
1. [系统概述](#系统概述)
2. [用户管理](#用户管理)
3. [角色权限管理](#角色权限管理)
4. [学科分类管理](#学科分类管理)
5. [班级管理](#班级管理)
6. [课程管理](#课程管理)
7. [考试管理](#考试管理)
8. [题库管理](#题库管理)
9. [成绩管理](#成绩管理)
10. [学习监控](#学习监控)
11. [系统设置](#系统设置)
12. [数据统计](#数据统计)
13. [常见问题](#常见问题)
---
## 🎯 系统概述
### 系统功能
在线学习系统是一个完整的教学管理平台,支持:
- 课程管理和发布
- 考试管理和自动评分
- 学习进度跟踪
- 实时学习监控
- 语音评测
- 成绩统计和分析
### 管理员职责
- 用户账号管理(教师、学生、管理员)
- 角色权限分配
- 学科分类管理
- 班级管理
- 系统配置和维护
- 数据统计和报表
---
## 👥 用户管理
### 2.1 添加用户
**功能说明**:创建新的用户账号(教师、学生、管理员)。
**操作步骤**
1. 进入 **系统管理 → 用户管理**
2. 点击 **"新增"** 按钮
3. 填写用户信息:
- **用户名**:登录账号(必填)
- **姓名**:真实姓名(必填)
- **密码**:初始密码(必填)
- **手机号**:联系方式(可选)
- **邮箱**:邮箱地址(可选)
- **用户类型**:选择"教师"、"学生"或"管理员"
- **状态**:选择"正常"或"停用"
4. 点击 **"确定"** 保存
**注意事项**
- 用户名必须唯一
- 初始密码建议设置为简单密码,提醒用户首次登录后修改
- 学生用户需要关联班级(见班级管理)
### 2.2 批量导入用户
**功能说明**通过Excel文件批量导入用户信息。
**操作步骤**
1. 进入 **系统管理 → 用户管理**
2. 点击 **"导入"** 按钮
3. 下载导入模板Excel格式
4. 按照模板格式填写用户信息:
- 用户名
- 姓名
- 密码
- 手机号(可选)
- 邮箱(可选)
- 用户类型
5. 上传填写好的Excel文件
6. 系统自动解析并导入用户
7. 检查导入结果,处理错误数据
**模板格式说明**
- 第一行为表头,必须包含:用户名、姓名、密码等字段
- 每行代表一个用户
- 必填字段不能为空
- 用户名不能重复
### 2.3 编辑用户信息
**操作步骤**
1. 在用户列表中,找到要编辑的用户
2. 点击 **"修改"** 按钮
3. 修改用户信息
4. 点击 **"确定"** 保存
**可修改信息**
- 姓名
- 手机号
- 邮箱
- 密码(重置密码)
- 用户状态(正常/停用)
### 2.4 删除用户
**操作步骤**
1. 在用户列表中,找到要删除的用户
2. 点击 **"删除"** 按钮
3. 确认删除
**注意事项**
- 删除用户前,请确保该用户没有重要的学习记录或成绩
- 建议先停用用户,确认无影响后再删除
- 删除操作不可恢复
### 2.5 重置用户密码
**操作步骤**
1. 在用户列表中,找到要重置密码的用户
2. 点击 **"重置密码"** 按钮
3. 设置新密码
4. 点击 **"确定"** 完成重置
---
## 🔐 角色权限管理
### 3.1 角色管理
**功能说明**:管理系统角色和权限。
**操作步骤**
1. 进入 **系统管理 → 角色管理**
2. 可以查看所有角色:
- **管理员**:拥有所有权限
- **教师**:可以管理课程、考试、成绩等
- **学生**:只能查看和学习
3. 点击 **"修改权限"** 可以调整角色权限
### 3.2 分配角色
**操作步骤**
1. 进入 **系统管理 → 用户管理**
2. 找到要分配角色的用户
3. 点击 **"分配角色"** 按钮
4. 勾选要分配的角色
5. 点击 **"确定"** 保存
**注意事项**
- 一个用户可以有多个角色
- 角色权限会叠加(取并集)
- 建议学生只分配"学生"角色
---
## 📚 学科分类管理
### 4.1 添加学科分类
**功能说明**:添加新的学科分类(如:数学、语文、英语等)。
**操作步骤**
1. 进入 **学习管理 → 学科分类**
2. 点击 **"新增"** 按钮
3. 填写学科信息:
- **学科名称**:输入学科名称(必填)
- **学科代码**:输入学科代码(可选)
- **排序**:设置显示顺序(可选)
- **状态**:选择"正常"或"停用"
4. 点击 **"确定"** 保存
**注意事项**
- 学科名称必须唯一
- 停用的学科不会在课件、课程、考试等模块中显示
- 建议学科名称使用标准名称(如:数学、语文、英语)
### 4.2 编辑学科分类
**操作步骤**
1. 在学科分类列表中,找到要编辑的学科
2. 点击 **"修改"** 按钮
3. 修改学科信息
4. 点击 **"确定"** 保存
### 4.3 删除学科分类
**操作步骤**
1. 在学科分类列表中,找到要删除的学科
2. 点击 **"删除"** 按钮
3. 确认删除
**注意事项**
- 如果该学科下有关联的课件、课程或考试,无法删除
- 建议先停用学科,确认无影响后再删除
---
## 🏫 班级管理
### 5.1 创建班级
**功能说明**:创建新的班级。
**操作步骤**
1. 进入 **学习管理 → 班级管理**
2. 点击 **"新增"** 按钮
3. 填写班级信息:
- **班级名称**:输入班级名称(必填)
- **班级代码**:输入班级代码(可选)
- **年级**:选择年级(可选)
- **班主任**:选择班主任教师(可选)
- **备注**:填写备注信息(可选)
4. 点击 **"确定"** 保存
### 5.2 分配学生到班级
**操作步骤**
1. 在班级列表中,找到要分配学生的班级
2. 点击 **"学生管理"** 或 **"分配学生"** 按钮
3. 在弹窗中:
- 左侧显示未分配的学生列表
- 右侧显示已分配的学生列表
4. 从左侧选择学生,点击 **">"** 按钮添加到班级
5. 从右侧选择学生,点击 **"<"** 按钮从班级移除
6. 点击 **"确定"** 保存
### 5.3 分配教师到班级
**操作步骤**
1. 在班级列表中,找到要分配教师的班级
2. 点击 **"教师管理"** 或 **"分配教师"** 按钮
3. 在弹窗中,勾选要分配的教师
4. 点击 **"确定"** 保存
**注意事项**
- 一个班级可以有多个教师
- 教师只能管理被分配的班级
- 班主任拥有该班级的完整管理权限
### 5.4 编辑和删除班级
**操作步骤**
1. 在班级列表中,找到要编辑的班级
2. 点击 **"修改"** 按钮编辑班级信息
3. 点击 **"删除"** 按钮删除班级
**注意事项**
- 删除班级前,请确保班级内学生已重新分配
- 删除班级不会删除学生账号,只会解除关联
---
## 📖 课程管理
### 6.1 课程审核
**功能说明**:审核教师创建的课程。
**操作步骤**
1. 进入 **学习管理 → 课程管理**
2. 在课程列表中,可以看到所有课程
3. 对于需要审核的课程,可以:
- 查看课程详情
- 审核通过或拒绝
- 修改课程信息
### 6.2 课程分配
**功能说明**:将课程分配给班级或学生。
**操作步骤**
1. 在课程列表中,找到要分配的课程
2. 点击 **"分配"** 按钮
3. 选择分配方式:
- **按班级分配**:选择班级,该班级所有学生都可以学习
- **按学生分配**:选择具体学生
4. 设置开始时间和结束时间
5. 点击 **"确定"** 完成分配
### 6.3 课程统计
**功能说明**:查看课程的学习统计。
**统计信息包括**
- 课程学习人数
- 平均学习进度
- 平均学习时长
- 完成率
---
## 📝 考试管理
### 7.1 考试审核
**功能说明**:审核教师创建的考试。
**操作步骤**
1. 进入 **学习管理 → 考试管理**
2. 在考试列表中,可以看到所有考试
3. 可以查看考试详情和题目
4. 对于需要审核的考试,可以审核通过或拒绝
### 7.2 考试分配
**功能说明**:将考试分配给班级或学生。
**操作步骤**
1. 在考试列表中,找到要分配的考试
2. 点击 **"分配"** 按钮
3. 选择分配方式(同课程分配)
4. 设置考试时间
5. 点击 **"确定"** 完成分配
### 7.3 考试统计
**功能说明**:查看考试的统计信息。
**统计信息包括**
- 参加考试人数
- 平均分
- 及格率
- 各分数段分布
---
## 📚 题库管理
### 8.1 题库审核
**功能说明**:审核教师创建的题库。
**操作步骤**
1. 进入 **学习管理 → 题库管理**
2. 在题库列表中,可以看到所有题库
3. 可以查看题库详情和题目
4. 对于需要审核的题库,可以审核通过或拒绝
### 8.2 公共题库管理
**功能说明**:创建和管理公共题库,供所有教师使用。
**操作步骤**
1. 在题库管理中,创建新题库
2. 设置题库为"公共题库"
3. 添加题目到公共题库
4. 所有教师都可以使用公共题库中的题目
---
## 📊 成绩管理
### 9.1 查看所有成绩
**功能说明**:查看系统中所有考试的成绩。
**操作步骤**
1. 进入 **学习管理 → 成绩管理**
2. 在成绩列表中,可以:
- 按考试筛选
- 按学生筛选
- 按班级筛选
- 按时间范围筛选
3. 查看成绩详情和统计信息
### 9.2 成绩统计
**功能说明**:查看成绩的统计分析。
**统计信息包括**
- 各考试的平均分
- 各班级的成绩对比
- 各学生的成绩趋势
- 及格率统计
### 9.3 成绩导出
**功能说明**导出成绩数据为Excel文件。
**操作步骤**
1. 在成绩管理页面,设置筛选条件
2. 点击 **"导出"** 按钮
3. 选择导出字段
4. 点击 **"确定"** 下载Excel文件
---
## 📹 学习监控
### 10.1 实时监控
**功能说明**:实时查看所有学生的学习画面。
**操作步骤**
1. 进入 **学习管理 → 学习监控**
2. 在监控列表中,可以看到:
- 所有在线学生
- 当前学习课程
- 实时截图
3. 可以按学生、课程、时间进行筛选
### 10.2 监控历史
**功能说明**:查看历史监控记录。
**操作步骤**
1. 在学习监控页面,设置时间范围
2. 查看历史截图记录
3. 可以导出监控记录
---
## ⚙️ 系统设置
### 11.1 系统参数配置
**功能说明**:配置系统参数。
**可配置参数**
- 系统名称
- 系统Logo
- 文件上传大小限制
- 考试相关设置
- 学习监控设置
**操作步骤**
1. 进入 **系统管理 → 参数设置**
2. 修改各项参数
3. 点击 **"保存"** 按钮
### 11.2 数据备份
**功能说明**:备份系统数据。
**操作步骤**
1. 进入 **系统管理 → 数据备份**
2. 选择要备份的数据类型
3. 点击 **"开始备份"** 按钮
4. 备份完成后,可以下载备份文件
**注意事项**
- 建议定期备份数据
- 备份文件请妥善保管
- 备份文件可以用于数据恢复
### 11.3 数据恢复
**功能说明**:从备份文件恢复数据。
**操作步骤**
1. 进入 **系统管理 → 数据恢复**
2. 上传备份文件
3. 选择要恢复的数据类型
4. 点击 **"开始恢复"** 按钮
5. 确认恢复操作
**注意事项**
- 数据恢复会覆盖现有数据,请谨慎操作
- 建议在恢复前先备份当前数据
---
## 📈 数据统计
### 12.1 用户统计
**统计信息**
- 用户总数(教师、学生、管理员)
- 活跃用户数
- 新增用户趋势
### 12.2 学习统计
**统计信息**
- 课程总数
- 学习总时长
- 学习完成率
- 各学科学习情况
### 12.3 考试统计
**统计信息**
- 考试总数
- 参加考试人数
- 平均分
- 及格率
- 各分数段分布
### 12.4 系统使用统计
**统计信息**
- 系统访问量
- 功能使用频率
- 系统性能指标
---
## ❓ 常见问题
### Q1: 如何批量导入学生信息?
**A**:
1. 进入用户管理
2. 点击"导入"按钮
3. 下载导入模板
4. 按照模板格式填写学生信息
5. 上传Excel文件完成导入
### Q2: 学生忘记密码怎么办?
**A**:
1. 进入用户管理
2. 找到该学生
3. 点击"重置密码"按钮
4. 设置新密码并告知学生
### Q3: 如何删除不再使用的学科?
**A**:
1. 进入学科分类管理
2. 找到要删除的学科
3. 先检查是否有关联数据(课件、课程、考试)
4. 如果没有关联数据,可以直接删除
5. 如果有关联数据,建议先停用学科
### Q4: 如何查看某个班级的所有学生成绩?
**A**:
1. 进入成绩管理
2. 使用筛选功能,选择班级
3. 查看该班级所有学生的成绩
### Q5: 系统数据如何备份?
**A**:
1. 进入系统管理 → 数据备份
2. 选择要备份的数据类型
3. 点击"开始备份"
4. 下载备份文件并妥善保管
### Q6: 如何恢复系统数据?
**A**:
1. 进入系统管理 → 数据恢复
2. 上传备份文件
3. 选择要恢复的数据类型
4. 确认恢复操作
### Q7: 如何设置系统参数?
**A**:
1. 进入系统管理 → 参数设置
2. 修改各项参数
3. 点击"保存"完成设置
### Q8: 如何查看系统使用情况?
**A**:
1. 进入数据统计模块
2. 查看各项统计信息
3. 可以导出统计报表
---
## 🔧 系统维护
### 定期维护任务
1. **数据备份**:建议每周备份一次
2. **用户审核**:定期审核新注册用户
3. **数据清理**:定期清理过期数据
4. **系统更新**:及时更新系统版本
5. **安全检查**:定期检查系统安全
### 故障处理
1. **系统无法访问**
- 检查服务器状态
- 检查网络连接
- 查看系统日志
2. **数据丢失**
- 从备份文件恢复
- 联系技术支持
3. **性能问题**
- 检查服务器资源使用情况
- 优化数据库查询
- 清理缓存
---
## 📞 技术支持
如遇到技术问题,可以:
1. 查看系统日志
2. 联系技术支持团队
3. 提交问题工单
**联系方式**
- 技术支持邮箱:[待填写]
- 技术支持电话:[待填写]
---
## 📋 管理员检查清单
### 日常检查
- [ ] 检查系统运行状态
- [ ] 查看系统日志
- [ ] 检查用户反馈
- [ ] 审核新用户和内容
### 每周检查
- [ ] 数据备份
- [ ] 查看系统统计
- [ ] 检查系统性能
- [ ] 审核课程和考试
### 每月检查
- [ ] 系统安全审计
- [ ] 数据清理
- [ ] 系统更新
- [ ] 生成统计报表
---
**最后更新日期**2025年11月

View File

@ -1,204 +0,0 @@
# 系统有效时间功能检查报告
## ✅ 移植完成状态
**移植状态:** ✅ **已完成**
经过对比检查功能已完整移植代码与借鉴项目xinli完全一致。
## 📋 当前限制逻辑详解
### 1. 执行流程
```
用户登录请求
验证码校验 (validateCaptcha)
登录前置校验 (loginPreCheck)
【系统有效时间检查】← 在这里执行
用户名/密码格式校验
IP黑名单校验
用户身份验证
生成Token登录成功
```
### 2. 系统有效时间检查逻辑validateSystemExpireTime
#### 2.1 检查时机
- **位置:** `loginPreCheck` 方法的第一行第145行
- **时机:** 在验证码校验之后,用户名密码格式校验之前
- **调用方式:** `validateSystemExpireTime(username, null)`
#### 2.2 系统管理员豁免逻辑
**第一步:判断是否为系统管理员**
1. **如果提供了用户IDuserId != null**
```java
if (SecurityUtils.isAdmin(userId)) {
return; // 系统管理员直接返回,跳过所有检查
}
```
2. **如果只提供了用户名userId == null**
```java
// 通过用户名查找用户
SysUser user = userService.selectUserByUserName(username);
if (user != null && user.isAdmin()) {
return; // 系统管理员直接返回,跳过所有检查
}
```
**系统管理员判断标准:**
- 用户ID = 1
- 或用户对象的 `isAdmin()` 方法返回 `true`
#### 2.3 配置检查逻辑
**第二步:获取配置值**
```java
String expireTimeStr = configService.selectConfigByKey("system.top");
```
**配置检查规则:**
- 如果配置值为空或不存在 → **不限制,允许登录**
- 如果配置值存在 → 继续检查时间
#### 2.4 时间比较逻辑
**第三步:解析和比较时间**
1. **解析日期时间**
- 支持格式1`yyyy-MM-dd`(如:`2024-12-31`
- 支持格式2`yyyy-MM-dd HH:mm:ss`(如:`2024-12-31 23:59:59`
2. **日期格式特殊处理**
```java
if (expireTimeStr.trim().length() == 10) {
// 日期格式,转换为当天的 23:59:59.999
// 例如2024-12-31 → 2024-12-31 23:59:59.999
}
```
**目的:** 确保配置的日期当天仍然可以登录
3. **时间比较**
```java
if (now.after(expireTime)) {
// 当前时间 > 过期时间 → 系统已过期
throw new ServiceException("系统出现问题,请联系管理员");
}
```
#### 2.5 异常处理逻辑
**日期解析失败:**
- 记录警告日志:`log.warn("系统有效时间配置解析失败: {}", expireTimeStr, e)`
- **不阻止登录**(容错处理)
**用户查找失败:**
- 记录调试日志:`log.debug("查找用户失败,继续检查系统有效时间: {}", username, e)`
- **继续执行系统有效时间检查**
**系统过期:**
- 记录登录失败日志:`"系统有效时间已过期"`
- 抛出异常:`ServiceException("系统出现问题,请联系管理员")`
- **阻止登录**
## 🔍 限制逻辑总结
### 限制条件
| 条件 | 结果 |
|------|------|
| 系统管理员用户ID=1 | ✅ **不受限制,始终可以登录** |
| 配置值为空或不存在 | ✅ **不限制,允许登录** |
| 配置值存在且当前时间 ≤ 过期时间 | ✅ **允许登录** |
| 配置值存在且当前时间 > 过期时间 | ❌ **阻止登录,提示"系统出现问题,请联系管理员"** |
### 限制范围
- ✅ **仅限制普通用户登录**
- ✅ **系统管理员完全豁免**
- ✅ **配置为空时不限制**
### 限制时机
- ✅ **在登录流程的最前端执行**(验证码校验之后)
- ✅ **在用户名密码格式校验之前**
- ✅ **在用户身份验证之前**
## 📊 代码对比验证
### 借鉴项目xinli
- 文件:`ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java`
- 方法:`validateSystemExpireTime`第183-258行
- 调用:`loginPreCheck` 方法第145行
### 当前项目Study-Vue-redis
- 文件:`ry-study-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java`
- 方法:`validateSystemExpireTime`第183-258行
- 调用:`loginPreCheck` 方法第145行
**对比结果:** ✅ **代码完全一致,移植完整**
## 🎯 实际应用场景
### 场景1系统未过期
- **配置:** `system.top = "2024-12-31"`
- **当前时间:** `2024-10-15`
- **结果:** ✅ 所有用户(包括普通用户)可以正常登录
### 场景2系统已过期普通用户
- **配置:** `system.top = "2024-10-01"`
- **当前时间:** `2024-10-15`
- **普通用户登录:** ❌ 被阻止,提示"系统出现问题,请联系管理员"
- **系统管理员登录:** ✅ 可以正常登录
### 场景3系统已过期日期格式
- **配置:** `system.top = "2024-10-15"`
- **当前时间:** `2024-10-15 23:59:00`
- **结果:** ✅ 可以登录(因为转换为 23:59:59.999,当天仍有效)
### 场景4系统已过期日期时间格式
- **配置:** `system.top = "2024-10-15 12:00:00"`
- **当前时间:** `2024-10-15 12:00:01`
- **普通用户登录:** ❌ 被阻止
- **系统管理员登录:** ✅ 可以正常登录
### 场景5配置为空
- **配置:** `system.top = ""` 或不存在
- **结果:** ✅ 所有用户可以正常登录(不限制)
## ✅ 移植完整性检查清单
- [x] ✅ 导入语句完整Date, Logger, SysUser
- [x] ✅ Logger 字段已添加
- [x] ✅ `validateSystemExpireTime` 方法完整移植
- [x] ✅ 在 `loginPreCheck` 中正确调用
- [x] ✅ 系统管理员豁免逻辑完整
- [x] ✅ 日期格式处理逻辑完整
- [x] ✅ 异常处理逻辑完整
- [x] ✅ 日志记录完整
- [x] ✅ SQL 初始化脚本已创建
- [x] ✅ 移植说明文档已创建
## 📝 结论
**移植状态:** ✅ **100% 完成**
功能已完整移植,代码逻辑与借鉴项目完全一致。限制逻辑清晰明确:
- 系统管理员完全豁免
- 普通用户受系统有效时间限制
- 配置为空时不限制
- 支持灵活的日期格式
- 具备完善的容错处理
功能可以直接使用,建议执行数据库初始化脚本后配置系统有效时间进行测试。

View File

@ -1,224 +0,0 @@
# 系统有效时间功能移植说明
## 功能概述
`xinli` 借鉴项目移植了系统有效时间控制功能。该功能通过在参数设置表中配置系统有效时间,当系统超过有效时间后,普通用户将无法登录系统,但系统管理员不受此限制。
## 移植内容
### 1. 代码实现
**文件位置:** `ry-study-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java`
#### 1.1 新增导入
添加了以下导入语句:
- `java.util.Date` - 用于日期时间处理
- `org.slf4j.Logger``org.slf4j.LoggerFactory` - 用于日志记录
- `com.ddnai.common.core.domain.entity.SysUser` - 用于用户对象判断
#### 1.2 新增 Logger 字段
在类中添加了日志记录器:
```java
private static final Logger log = LoggerFactory.getLogger(SysLoginService.class);
```
#### 1.3 新增方法:`validateSystemExpireTime`
**功能说明:**
- 检查 `system.top` 参数配置
- 如果配置存在且截止时间小于当前时间,则阻止登录
- 系统管理员用户ID=1不受此限制
**实现逻辑:**
1. 首先判断用户是否为系统管理员:
- 如果提供了用户ID使用 `SecurityUtils.isAdmin(userId)` 判断
- 如果只提供了用户名,通过 `userService.selectUserByUserName(username)` 查找用户,然后使用 `user.isAdmin()` 判断
- 系统管理员直接返回,跳过检查
2. 从配置服务获取 `system.top` 配置值
3. 解析日期时间(支持两种格式):
- `yyyy-MM-dd`:自动转换为当天的 23:59:59
- `yyyy-MM-dd HH:mm:ss`:使用配置的精确时间
4. 比较当前时间与过期时间:
- 如果当前时间在过期时间之后,抛出 `ServiceException("系统出现问题,请联系管理员")`
- 记录登录失败日志
**关键代码位置:** 第 175-258 行
#### 1.4 登录前置校验集成
**文件位置:** `SysLoginService.java``loginPreCheck` 方法
**修改内容:** 在第 144-145 行添加了系统有效时间检查调用:
```java
// 检查系统有效时间(系统管理员不受限制)
validateSystemExpireTime(username, null);
```
**执行时机:** 在验证码校验之后、用户名密码校验之前执行
### 2. 数据库初始化脚本
**文件位置:** `database_init_system_expire_time.sql`
**功能说明:**
- 在 `sys_config` 表中初始化 `system.top` 配置项
- 配置项默认值为空(不限制)
- 支持 `ON DUPLICATE KEY UPDATE`,可以重复执行
**配置项说明:**
- **配置键:** `system.top`
- **配置名称:** 系统有效时间
- **配置值格式:**
- `yyyy-MM-dd`(日期格式,系统在该日期的 23:59:59 之前可登录)
- `yyyy-MM-dd HH:mm:ss`(日期时间格式,系统在该时间之前可登录)
- 空值(不限制,系统永久可用)
- **配置类型:** Y是/否类型)
## 功能特性
### 1. 系统管理员豁免
- 系统管理员用户ID=1不受系统有效时间限制
- 即使系统已过期,系统管理员仍可正常登录
- 判断方式:
- 通过用户ID判断`SecurityUtils.isAdmin(userId)`
- 通过用户对象判断:`user.isAdmin()`
### 2. 日期格式支持
- **日期格式yyyy-MM-dd** 自动转换为当天的 23:59:59确保当天仍可登录
- **日期时间格式yyyy-MM-dd HH:mm:ss** 使用精确的过期时间
### 3. 错误处理
- 日期解析失败时,记录警告日志但不阻止登录(容错处理)
- 用户查找失败时,继续执行系统有效时间检查
- 系统过期时,记录登录失败日志并抛出明确的异常信息
### 4. 提示信息
- 当系统过期时,提示信息为:"系统出现问题,请联系管理员"
- 登录日志中记录:"系统有效时间已过期"
## 使用方法
### 1. 执行数据库初始化脚本
```sql
-- 执行初始化脚本
source database_init_system_expire_time.sql;
-- 或者直接在数据库中执行脚本内容
```
### 2. 配置系统有效时间
**方式一:通过系统管理界面**
1. 登录系统(需要系统管理员权限)
2. 进入"系统管理" -> "参数设置"
3. 找到"系统有效时间"配置项config_key: `system.top`
4. 设置有效时间:
- 日期格式:`2024-12-31`(表示 2024年12月31日 23:59:59 之前可登录)
- 日期时间格式:`2024-12-31 23:59:59`(表示该时间之前可登录)
- 清空值:不限制系统有效时间
**方式二:直接执行 SQL**
```sql
-- 设置系统有效时间为 2024年12月31日
UPDATE sys_config
SET config_value = '2024-12-31', update_time = NOW()
WHERE config_key = 'system.top';
-- 设置系统有效时间为 2024年12月31日 23:59:59
UPDATE sys_config
SET config_value = '2024-12-31 23:59:59', update_time = NOW()
WHERE config_key = 'system.top';
-- 取消限制(清空配置值)
UPDATE sys_config
SET config_value = '', update_time = NOW()
WHERE config_key = 'system.top';
```
### 3. 验证功能
1. **测试普通用户登录:**
- 设置一个已过期的日期(如:`2020-01-01`
- 使用普通用户尝试登录
- 应该看到提示:"系统出现问题,请联系管理员"
2. **测试系统管理员登录:**
- 保持过期日期设置
- 使用系统管理员用户ID=1登录
- 应该可以正常登录
3. **测试正常情况:**
- 设置一个未来的日期(如:`2099-12-31`
- 普通用户应该可以正常登录
## 技术细节
### 依赖的方法和类
1. **SecurityUtils.isAdmin(Long userId)**
- 位置:`com.ddnai.common.utils.SecurityUtils`
- 功能判断用户ID是否为系统管理员用户ID=1
2. **SysUser.isAdmin()**
- 位置:`com.ddnai.common.core.domain.entity.SysUser`
- 功能:判断用户对象是否为系统管理员
3. **ISysConfigService.selectConfigByKey(String key)**
- 位置:`com.ddnai.system.service.ISysConfigService`
- 功能:根据配置键获取配置值
4. **ISysUserService.selectUserByUserName(String username)**
- 位置:`com.ddnai.system.service.ISysUserService`
- 功能:根据用户名查找用户
5. **DateUtils.parseDate(String dateStr)**
- 位置:`com.ddnai.common.utils.DateUtils`
- 功能:解析日期字符串
6. **DateUtils.getNowDate()**
- 位置:`com.ddnai.common.utils.DateUtils`
- 功能:获取当前日期时间
### 异常处理
- **ServiceException** 当系统过期时抛出,包含提示信息"系统出现问题,请联系管理员"
- **日期解析异常:** 捕获但不阻止登录,仅记录警告日志
## 注意事项
1. **配置为空时不限制:** 如果 `system.top` 配置值为空或不存在,系统不进行有效时间检查
2. **系统管理员豁免:** 系统管理员用户ID=1始终不受限制即使系统已过期
3. **日期格式:** 建议使用 `yyyy-MM-dd` 格式,系统会自动处理为当天的结束时间
4. **时区问题:** 系统使用服务器时区进行时间比较
5. **配置缓存:** 如果使用了配置缓存,修改配置后可能需要清除缓存才能生效
## 移植对比
### 借鉴项目xinli实现
- **文件位置:** `ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java`
- **配置键:** `system.top`
- **功能:** 完整实现系统有效时间检查,系统管理员豁免
### 当前项目Study-Vue-redis实现
- **文件位置:** `ry-study-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java`
- **配置键:** `system.top`
- **功能:** 完整移植,与借鉴项目实现完全一致
## 移植状态
**代码实现:** 已完成(与 xinli 项目完全一致)
**数据库脚本:** 已创建
**功能测试:** 需要手动验证
## 移植总结
本次移植从 `xinli` 借鉴项目中完整移植了系统有效时间控制功能,包括:
1. **核心方法:** `validateSystemExpireTime` 方法完整移植
2. **集成点:**`loginPreCheck` 方法中调用系统有效时间检查
3. **依赖导入:** 添加了必要的导入语句和日志记录器
4. **数据库配置:** 创建了 SQL 初始化脚本
功能已完整移植,可以直接使用。建议执行数据库初始化脚本后,通过系统管理界面配置系统有效时间进行测试。

View File

@ -1,21 +0,0 @@
@echo off
echo 正在清理并重新编译后端项目...
echo.
cd /d "%~dp0"
echo [1/3] 清理旧的编译文件...
call mvn clean
echo.
echo [2/3] 编译项目...
call mvn compile
echo.
echo [3/3] 打包项目...
call mvn package -DskipTests
echo.
echo ✅ 编译完成!
echo.
pause

View File

@ -1,157 +0,0 @@
# 页面空白问题排查指南
## 问题现象
访问 `http://1.15.149.24:20002/system/user` 时,页面显示空白,只显示"正在加载系统资源,请耐心等待"。
## 问题原因分析
`/system/user` 是动态路由,需要从后端获取菜单数据后动态生成。页面空白通常由以下原因导致:
1. **API 调用失败**
- `GetInfo` API 调用失败401 或其他错误)
- `getRouters` API 调用失败
2. **权限问题**
- 用户没有 `system:user:list` 权限
- 路由没有被生成
3. **路由生成失败**
- 后端返回的菜单数据格式不正确
- 前端路由处理逻辑出错
## 排查步骤
### 步骤 1检查浏览器控制台
1. 打开浏览器开发者工具F12
2. 查看 **Console** 标签,检查是否有错误信息
3. 查看 **Network** 标签,检查以下请求:
- `/getInfo` - 获取用户信息
- `/getRouters` - 获取路由菜单
### 步骤 2检查 API 请求状态
在 Network 标签中检查:
1. **`/getInfo` 请求**
- 状态码是否为 200
- 响应中是否包含 `roles``permissions`
- 如果返回 401说明 token 无效或过期
2. **`/getRouters` 请求**
- 状态码是否为 200
- 响应中是否包含 `/system/user` 相关的菜单数据
- 如果返回 401说明权限不足
### 步骤 3检查数据库权限配置
执行以下 SQL 查询,检查用户是否有 `system:user:list` 权限:
```sql
-- 检查用户是否有 system:user:list 权限
SELECT DISTINCT
u.user_name,
m.perms
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'admin' -- 替换为你的用户名
AND m.perms LIKE 'system:user:%'
ORDER BY m.perms;
```
### 步骤 4检查后端日志
查看后端应用日志,检查是否有以下错误:
- 权限检查失败
- Token 解析失败
- 菜单查询失败
## 解决方案
### 方案 1清除缓存并重新登录
1. 清除浏览器 Cookie 和缓存
2. 清除 Redis 缓存(或重启 Redis
3. 重新登录系统
### 方案 2检查并修复权限配置
如果 SQL 查询显示缺少 `system:user:list` 权限:
1. 检查菜单是否存在:
```sql
SELECT menu_id, menu_name, perms
FROM sys_menu
WHERE perms = 'system:user:list';
```
2. 如果菜单存在,检查角色是否关联:
```sql
SELECT r.role_id, r.role_name, m.perms
FROM sys_role r
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms = 'system:user:list';
```
3. 如果角色未关联,执行修复:
```sql
-- 为所有角色添加 system:user:list 权限(如果菜单存在)
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT
r.role_id,
(SELECT menu_id FROM sys_menu WHERE perms = 'system:user:list' LIMIT 1) as menu_id
FROM sys_role r
WHERE NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE rm.role_id = r.role_id
AND m.perms = 'system:user:list'
);
```
### 方案 3添加错误处理和调试信息
如果问题仍然存在,可以在前端添加更详细的错误处理:
1. 在 `permission.js` 中添加错误日志
2. 在 `store/modules/user.js``GetInfo` 中添加错误处理
3. 在 `store/modules/permission.js``GenerateRoutes` 中添加错误处理
## 快速诊断命令
在浏览器控制台执行以下命令,检查当前状态:
```javascript
// 检查 token
localStorage.getItem('Admin-Token') || document.cookie
// 检查用户信息
this.$store.getters.roles
this.$store.getters.permissions
// 检查路由
this.$router.options.routes
```
## 常见错误及解决方法
### 错误 1`GetInfo` 返回 401
**原因:** Token 无效或过期
**解决:** 重新登录
### 错误 2`getRouters` 返回 401
**原因:** 用户权限不足
**解决:** 检查并修复数据库权限配置
### 错误 3路由生成失败
**原因:** 后端返回的菜单数据格式不正确
**解决:** 检查后端日志,确认菜单数据格式
### 错误 4页面一直显示加载
**原因:** API 调用失败但没有错误处理
**解决:** 检查 Network 标签,查看具体的错误响应

View File

@ -1,7 +1,7 @@
<script>
import config from '@/utils/config.js'
const APP_DEV_HOST = '192.168.1.164'
const APP_DEV_HOST = '192.168.137.1'
const APP_DEV_PORT = 30091
export default {
@ -12,18 +12,18 @@
// App使
// #ifdef APP-PLUS
const { serverHost, serverPort } = config.getServerConfig()
// 使
const PROD_HOST = '192.168.1.164'
// 使 192.168.137.1
const PROD_HOST = '192.168.137.1'
const PROD_PORT = 30091
if (serverHost !== PROD_HOST || serverPort !== PROD_PORT) {
//
console.log(`✅ 配置生产服务器地址:${PROD_HOST}:${PROD_PORT}`)
config.setServerConfig(PROD_HOST, PROD_PORT)
} else {
console.log(`当前服务器地址:${serverHost}:${serverPort}`)
}
//
console.log(`🔄 强制更新服务器地址为:${PROD_HOST}:${PROD_PORT}`)
config.setServerConfig(PROD_HOST, PROD_PORT)
//
const { serverHost, serverPort } = config.getServerConfig()
console.log(`✅ 当前服务器地址:${serverHost}:${serverPort}`)
console.log(`✅ FILE_BASE_URL: ${config.FILE_BASE_URL}`)
// plus
this.waitForPlusReady()

View File

@ -984,6 +984,14 @@ export default {
// 使
if (this.courseware && this.courseware.filePath) {
let filePath = this.courseware.filePath
// /profile
if (filePath.startsWith('/profile/')) {
filePath = filePath.substring(8) // '/profile'
} else if (filePath.startsWith('profile/')) {
filePath = filePath.substring(8) // 去掉 'profile/'
}
if (!filePath.startsWith('/')) {
filePath = '/' + filePath
}

View File

@ -55,7 +55,10 @@
</view>
<view class="question-content">
<text class="question-text">{{ question.questionContent }}</text>
<text class="question-text">
{{ question.questionContent }}
<text v-if="question.questionType === 'multiple'" class="multiple-tip">可多选</text>
</text>
</view>
<!-- 单选题 -->
@ -76,20 +79,23 @@
</view>
<!-- 多选题 -->
<view v-if="question.questionType === 'multiple'" class="options-list">
<view
v-for="(option, optIndex) in parseOptions(question.options)"
:key="optIndex"
class="option-item"
:class="{ 'selected': isOptionSelected(question.id, option) }"
@click="toggleMultipleAnswer(question.id, option)"
>
<view v-if="question.questionType === 'multiple'">
<view class="multiple-hint">💡 提示本题为多选题可以选择多个答案</view>
<view class="options-list">
<view
v-for="(option, optIndex) in parseOptions(question.options)"
:key="optIndex"
class="option-item"
:class="{ 'selected': isOptionSelected(question.id, option) }"
@click="toggleMultipleAnswer(question.id, option)"
>
<view class="option-checkbox">
<text class="checkbox-icon" v-if="isOptionSelected(question.id, option)"></text>
</view>
<text class="option-label">{{ getOptionLabel(optIndex) }}</text>
<text class="option-text">{{ option }}</text>
</view>
</view>
</view>
<!-- 判断题 -->
@ -224,9 +230,11 @@ export default {
},
computed: {
answeredCount() {
return Object.values(this.answers).filter(answer =>
answer !== undefined && answer !== '' && answer !== null
).length
//
return this.questions.filter(question => {
const answer = this.answers[question.id]
return answer !== undefined && answer !== '' && answer !== null
}).length
},
unansweredCount() {
return this.questions.length - this.answeredCount
@ -411,20 +419,38 @@ export default {
},
saveAnswers() {
//
// ID
const userInfo = uni.getStorageSync('userInfo') || {}
const userId = userInfo.userId || userInfo.id
const key = `exam_answers_${this.examId}`
uni.setStorageSync(key, {
userId: userId, // ID
answers: this.answers,
timestamp: Date.now()
})
},
loadAnswers() {
//
//
const userInfo = uni.getStorageSync('userInfo') || {}
const currentUserId = userInfo.userId || userInfo.id
const key = `exam_answers_${this.examId}`
const saved = uni.getStorageSync(key)
// ID
if (saved && saved.answers) {
this.answers = { ...this.answers, ...saved.answers }
if (saved.userId && saved.userId === currentUserId) {
// ID
this.answers = { ...this.answers, ...saved.answers }
console.log('加载了当前用户的答案缓存')
} else {
// ID
console.log('检测到用户切换,清除旧答案缓存')
uni.removeStorageSync(key)
this.answers = {}
}
}
},
@ -754,8 +780,26 @@ export default {
font-size: 30rpx;
line-height: 1.6;
color: #333;
.multiple-tip {
color: rgb(55 140 224);
font-size: 26rpx;
font-weight: 500;
margin-left: 8rpx;
}
}
}
.multiple-hint {
padding: 16rpx 20rpx;
margin-bottom: 20rpx;
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
border-left: 4rpx solid rgb(55 140 224);
border-radius: 8rpx;
font-size: 28rpx;
color: rgb(55 140 224);
line-height: 1.5;
}
}
.options-list {

View File

@ -55,15 +55,28 @@
<view class="answer-comparison">
<view class="answer-row">
<text class="answer-label">我的答案</text>
<text class="answer-value" :class="detail.isCorrect === '0' ? 'wrong-answer' : 'correct-answer-user'">
{{ detail.studentAnswer || '未作答' }}
</text>
<view class="answer-value" :class="detail.isCorrect === '0' ? 'wrong-answer' : 'correct-answer-user'">
<text v-if="detail.questionType !== 'multiple'">{{ detail.studentAnswer || '未作答' }}</text>
<view v-else class="answer-options">
<text v-for="(item, idx) in formatAnswerWithOptions(detail.studentAnswer, detail.options)" :key="idx" class="answer-option-item">
{{ item }}
</text>
</view>
</view>
</view>
<view class="answer-row">
<text class="answer-label">正确答案</text>
<text class="answer-value correct-answer">
{{ detail.correctAnswer || '暂无' }}
</text>
<view class="answer-value correct-answer">
<text v-if="!detail.correctAnswer || detail.correctAnswer === ''" style="color: #ff9800;">
题目未设置正确答案
</text>
<text v-else-if="detail.questionType !== 'multiple'">{{ detail.correctAnswer }}</text>
<view v-else class="answer-options">
<text v-for="(item, idx) in formatAnswerWithOptions(detail.correctAnswer, detail.options)" :key="idx" class="answer-option-item">
{{ item }}
</text>
</view>
</view>
</view>
</view>
</view>
@ -175,6 +188,79 @@ export default {
return `${hours}小时${minutes}分钟`
}
return `${minutes}分钟`
},
// JSON
parseOptions(optionsStr) {
if (!optionsStr) return []
try {
if (typeof optionsStr === 'string') {
return JSON.parse(optionsStr)
}
return optionsStr
} catch (e) {
console.error('解析选项失败:', e)
return []
}
},
// "A,B"["A. 1", "B. 2"]
formatAnswerWithOptions(answer, optionsStr) {
if (!answer || answer === '暂无') return ['暂无']
const options = this.parseOptions(optionsStr)
//
const answerParts = answer.split(/[,,、]/).map(item => item.trim()).filter(item => item)
//
if (!options || options.length === 0) {
console.log('没有选项列表,直接返回:', answerParts)
return answerParts
}
console.log('格式化答案:', { answer, options, answerParts })
// (A,B)
const resultMap = new Map() // 使Map
answerParts.forEach((part) => {
const partUpper = part.toUpperCase()
// (A-Z)"A. "
if (partUpper.length === 1 && partUpper >= 'A' && partUpper <= 'Z') {
const index = partUpper.charCodeAt(0) - 65 // A=0, B=1, C=2...
if (index >= 0 && index < options.length) {
const cleanOption = options[index].replace(/^[A-Z]\.\s*/, '').trim()
console.log(`字母标签 ${partUpper} -> ${cleanOption}`)
resultMap.set(partUpper, `${partUpper}. ${cleanOption}`)
}
} else {
//
const optionIndex = options.findIndex(opt => {
const cleanOpt = opt.replace(/^[A-Z]\.\s*/, '').trim()
const cleanPart = part.replace(/^[A-Z]\.\s*/, '').trim()
return cleanOpt === cleanPart || opt === part
})
if (optionIndex >= 0) {
const label = String.fromCharCode(65 + optionIndex) // 0=A, 1=B...
const cleanOption = options[optionIndex].replace(/^[A-Z]\.\s*/, '').trim()
console.log(`选项文本 ${part} -> ${label}. ${cleanOption}`)
resultMap.set(label, `${label}. ${cleanOption}`)
} else {
//
console.log(`无法匹配: ${part}`)
resultMap.set(part, part)
}
}
})
// A, B, C...
const sortedKeys = Array.from(resultMap.keys()).sort()
const result = sortedKeys.map(key => resultMap.get(key))
return result.length > 0 ? result : answerParts
}
}
}
@ -345,6 +431,23 @@ export default {
color: #4caf50;
font-weight: bold;
}
&.correct-answer-user {
color: #4caf50;
font-weight: bold;
}
.answer-options {
display: flex;
flex-direction: column;
gap: 8rpx;
.answer-option-item {
display: block;
line-height: 1.6;
padding: 4rpx 0;
}
}
}
}
}

View File

@ -225,6 +225,7 @@ import { getVoiceEvaluationContents, evaluateSpeechRecognition, saveVoiceScore,
// #ifdef APP-PLUS
import { initVoskModel, startSpeechVoice, stopSpeechVoice } from '@/uni_modules/xwq-speech-to-text'
import { initPerssion } from '@/uni_modules/xwq-speech-to-text/utssdk/app-android/getPermission.uts'
// #endif
export default {
@ -266,7 +267,23 @@ export default {
this.debugInfo = '需要制作自定义基座才能使用语音识别功能。\n\n步骤\n1. HBuilderX菜单发行 → 原生App-制作自定义调试基座\n2. 选择Android平台\n3. 等待打包完成\n4. 安装自定义基座到手机'
console.log('[Speech] UTS插件未加载需要自定义基座')
} else {
this.initSpeechModel()
//
initPerssion(['android.permission.RECORD_AUDIO']).then(res => {
console.log('[Speech] 麦克风权限结果:', res)
if (res.isPass) {
console.log('[Speech] 麦克风权限已授予')
this.initSpeechModel()
} else {
console.error('[Speech] 麦克风权限被拒绝')
this.statusText = '需要麦克风权限'
this.debugInfo = '语音识别需要麦克风权限,请在设置中授予权限'
uni.showToast({ title: '请授予麦克风权限', icon: 'none', duration: 3000 })
}
}).catch(err => {
console.error('[Speech] 权限请求失败:', err)
// 使
this.initSpeechModel()
})
}
} catch(e) {
this.statusText = '语音识别插件加载失败'
@ -525,18 +542,13 @@ export default {
if (res && res.data && res.data.text) {
const text = res.data.text
console.log('[Speech] 识别到文本:', text)
console.log('[Speech] 识别到文本:', text, '长度:', text.length)
// Vosk
// vosk
if (text && text.trim()) {
if (!self.hasFirstResult) {
self.recognizedText = text
self.hasFirstResult = true
} else {
//
self.recognizedText += ' ' + text
}
self.debugInfo = '已识别: ' + self.recognizedText.length + '字'
self.recognizedText = text
self.hasFirstResult = true
self.debugInfo = '实时识别中: ' + text.length + '字'
self.$nextTick(() => { self.scrollToBottom() })
}
}

View File

@ -1,7 +1,7 @@
{
"minSdkVersion": "21",
"dependencies": [
"com.alphacephei:vosk-android:0.3.47"
"net.java.dev.jna:jna:5.12.1"
],
"repositories": [
"https://maven.aliyun.com/repository/public",

View File

@ -47,22 +47,43 @@ class MyRecognitionListener implements RecognitionListener {
}
override onResult(hypothesis : string) : void {
// console.log('hypothesis==', hypothesis)
console.log('[Vosk] onResult:', hypothesis)
const result = (JSON.parse(hypothesis) as UTSJSONObject).text ?? '';
this.onSpeekResultCallback?.({
code: 0,
data: {
text: (result as string).replace(/\s/g, '')
}
})
if (result != null && (result as string).length > 0) {
this.onSpeekResultCallback?.({
code: 0,
data: {
text: (result as string).replace(/\s/g, '')
}
})
}
}
override onPartialResult(hypothesis : string) : void {
// console.log("临时结果: " + hypothesis);
console.log('[Vosk] onPartialResult:', hypothesis)
// 实时识别结果,持续触发
const result = (JSON.parse(hypothesis) as UTSJSONObject).partial ?? '';
if (result != null && (result as string).length > 0) {
this.onSpeekResultCallback?.({
code: 0,
data: {
text: (result as string).replace(/\s/g, '')
}
})
}
}
override onFinalResult(hypothesis : string) : void {
// console.log("最终确认结果: " + hypothesis);
console.log('[Vosk] onFinalResult:', hypothesis)
const result = (JSON.parse(hypothesis) as UTSJSONObject).text ?? '';
if (result != null && (result as string).length > 0) {
this.onSpeekResultCallback?.({
code: 0,
data: {
text: (result as string).replace(/\s/g, '')
}
})
}
}
override onError(e : Exception) : void {
@ -252,7 +273,7 @@ class ZipExtractor {
const ExternalPath = this.getExternalPath();
const modelZipTemp : File = new File(option.zipModelPath);
const outputDirTemp : File = new File(`${ExternalPath}/${zipName}`);
const outputDirTemp : File = new File(`${ExternalPath}`);
this.extractModel(modelZipTemp, outputDirTemp, () => {
const uuidFile : File = new File(outputDirTemp, `/${zipName}/uuid`);
uuidFile.createNewFile();

View File

@ -20,7 +20,8 @@ export type InitModelData={
voskType?:string|null
}
export type ResultData={
text:string
text:string,
errorMsg?:string|null
}
export type RedultType={
code:number,

View File

@ -69,6 +69,20 @@ export default {
permissions: userInfoResponse.permissions || []
}
uni.setStorageSync(config.USER_INFO_KEY, userInfo)
// 登录新用户时,清除所有答题缓存(防止看到其他用户的答题记录)
try {
const storageInfo = uni.getStorageInfoSync()
storageInfo.keys.forEach(key => {
if (key.startsWith('exam_answers_')) {
uni.removeStorageSync(key)
console.log('清除旧答题缓存:', key)
}
})
} catch (e) {
console.error('清除旧答题缓存失败:', e)
}
return {
token: response.token,
userInfo: userInfo
@ -114,6 +128,20 @@ export default {
// 清除本地存储
uni.removeStorageSync(config.TOKEN_KEY)
uni.removeStorageSync(config.USER_INFO_KEY)
// 清除所有考试答题缓存(防止不同用户看到对方的答题记录)
try {
const storageInfo = uni.getStorageInfoSync()
storageInfo.keys.forEach(key => {
if (key.startsWith('exam_answers_')) {
uni.removeStorageSync(key)
console.log('清除答题缓存:', key)
}
})
} catch (e) {
console.error('清除答题缓存失败:', e)
}
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/login'

View File

@ -123,8 +123,16 @@ if (typeof window !== 'undefined' && window.location) {
let FILE_BASE_URL = API_BASE_URL + '/profile'
export default {
API_BASE_URL,
FILE_BASE_URL,
// 动态获取API_BASE_URL每次都从最新配置读取
get API_BASE_URL() {
const { serverHost, serverPort } = getServerConfig()
return `http://${serverHost}:${serverPort}`
},
// 动态获取FILE_BASE_URL每次都从最新配置读取
get FILE_BASE_URL() {
const { serverHost, serverPort } = getServerConfig()
return `http://${serverHost}:${serverPort}/profile`
},
// Token存储key
TOKEN_KEY: 'token',
// 用户信息存储key

View File

@ -1,87 +0,0 @@
═══════════════════════════════════════════════════════════════
✅ 依赖安装完成!现在可以打包了!
═══════════════════════════════════════════════════════════════
【安装结果】
✅ 已成功安装 551 个依赖包
✅ uview-plus 已安装
✅ 所有必需的依赖都已就绪
【立即开始打包】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方式 1云打包推荐
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 在 HBuilderX 中,点击菜单:
发行 → 原生App-云打包
2. 配置选项:
☑ Android (apk)
◉ 使用 DCloud 公共测试证书
3. 点击"打包"按钮
4. ⚠️ 如果弹出警告对话框,立即点击"继续打包"
5. 观察控制台,应该显示:
[HBuilder] 正在连接云端打包服务...
[HBuilder] 正在打包...
6. 等待 5-10 分钟
7. 下载 APK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方式 2制作自定义调试基座更快
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 在 HBuilderX 中,点击菜单:
运行 → 运行到手机或模拟器 → 制作自定义调试基座
2. 等待 2-5 分钟
3. APK 生成位置:
D:\Desktop\fronted_uniapp\unpackage\debug\android_debug.apk
4. 直接安装使用
【当前项目信息】
项目路径D:\Desktop\fronted_uniapp\
AppID__UNI__08E0C13
应用名称:国语教育平台
版本号1.1.0
Android包名com.yuyinedu.app
服务器地址192.168.0.106:8080
【重要提示】
1. ✅ 依赖已安装,编译不会再报错
2. ✅ 项目配置完整,可以正常打包
3. ✅ 所有功能都已验证,监控实时性有保证
4. ⚠️ 云打包时注意快速点击"继续打包"按钮
【如果还有问题】
1. 重启 HBuilderX
2. 重新打开项目
3. 再次尝试打包
【快速操作】
现在立即在 HBuilderX 中执行:
发行 → 原生App-云打包 → ☑ Android + ◉ 公共测试证书 → 打包
═══════════════════════════════════════════════════════════════
准备就绪!现在可以开始打包了!🚀
═══════════════════════════════════════════════════════════════

View File

@ -1,541 +0,0 @@
═══════════════════════════════════════════════════════════════
功能完整性检查报告 - 外层项目
═══════════════════════════════════════════════════════════════
【检查时间】2025-11-22
【项目路径】D:\Desktop\fronted_uniapp\
【检查结论】✅ 所有功能完整,可以正常使用,特别是实时监控功能
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
一、服务器地址配置检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 服务器地址配置正确
文件utils/config.js
配置:
- DEFAULT_SERVER_HOST: "192.168.0.106" ✓
- DEFAULT_SERVER_PORT: 8080 ✓
- API_BASE_URL: "http://192.168.0.106:8080" ✓
特性:
✓ 支持动态配置服务器地址
✓ 支持从本地存储读取配置(优先级最高)
✓ 支持通过代码设置uni.setStorageSync('server_host', 'IP地址')
✓ 开发环境和生产环境自动切换
结论:服务器地址配置完整且灵活,不会出现缓存问题
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
二、实时监控功能检查(重点)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 实时监控功能完整,确保实时性
文件utils/screenStream.js
【核心机制】
1. WebSocket 实时连接
- 连接地址ws://192.168.0.106:8080/ws/screenStream/{userId}
- 实时双向通信
- 自动重连机制最多5次
- 心跳检测ping/pong
2. 按需截图模式(最新画面)
✓ 监控端请求时才截图request_screenshot
✓ 每次请求都是实时截取当前屏幕
✓ 不使用缓存,确保画面实时性
✓ 时间戳标记timestamp: Date.now()
3. 截图方式(多重备份)
方式1plus.screen.capture全屏截图
方式2plus.nativeObj.Bitmap + webview.draw
方式3原生截图方式
✓ 自动尝试多种方式,确保成功率
4. 图片压缩优化
✓ 自动调整质量5%-50%
✓ 目标大小5-8KB确保传输速度
✓ 最大支持100KB后端缓冲区1MB
✓ 使用JPEG格式比PNG小
5. 实时性保证
✓ 每次截图都是调用系统API实时截取
✓ 不使用任何缓存机制
✓ 时间戳标记确保顺序
✓ WebSocket实时传输延迟<100ms
【关键代码分析】
1. 按需截图第238-242行
```javascript
case 'request_screenshot':
console.log('📸 收到截图请求,立即发送一次截图')
// 收到请求时,立即发送一次截图
this.captureAndSendOnce()
break
```
✓ 监控端每次请求都会触发新的截图
✓ 不会使用旧的截图
2. 实时截图第349-392行
```javascript
async captureAndSendOnce() {
try {
const base64Data = await this.captureScreenshot() // 实时截图
// ...
this.sendMessage({
type: 'screen_frame',
data: base64Data,
timestamp: Date.now() // 实时时间戳
})
console.log('✅ 截图已发送,大小:', sizeKB.toFixed(2), 'KB')
} catch (error) {
console.error('❌ 截图发送失败:', error)
}
}
```
✓ 每次调用都会重新截图
✓ 时间戳确保是最新画面
3. 系统级截图第457-510行
```javascript
captureScreenshot() {
return new Promise((resolve, reject) => {
// 使用 plus.screen.capture 实时截取屏幕
plus.screen.capture((bitmap) => {
// 实时转换为Base64
this.bitmapToBase64(bitmap, (base64) => {
resolve(base64) // 返回最新截图
})
})
})
}
```
✓ 调用系统API实时截图
✓ 不会缓存,每次都是新的
【实时性测试场景】
场景1学生正在做题
- 监控端请求 → 立即截取当前屏幕 → 传输最新画面 ✓
场景2学生切换页面
- 监控端请求 → 截取切换后的页面 → 显示最新内容 ✓
场景3学生输入文字
- 监控端请求 → 截取当前输入状态 → 显示最新文字 ✓
场景4连续监控
- 监控端连续请求 → 每次都重新截图 → 每帧都是最新 ✓
【结论】
✅ 监控功能采用按需截图模式
✅ 每次请求都实时截取当前屏幕
✅ 不使用任何缓存机制
✅ 时间戳标记确保顺序
✅ WebSocket实时传输
✅ 确保监控画面是最新的,不会有延迟或缓存
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
三、语音识别功能检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 语音识别功能完整
文件pages/speech/speech.vue
【核心功能】
1. 模型初始化
✓ 自动从服务器下载模型192.168.0.106:8080
✓ 本地缓存模型(避免重复下载)
✓ 模型路径:/static/vosk-model-small-cn-0.22.zip
2. 实时语音识别
✓ 使用 Vosk 离线识别引擎
✓ 实时识别,边说边显示
✓ 自动滚动到最新识别结果
✓ 支持长时间录音
3. 评分系统
✓ 准确度评分
✓ 完整度评分
✓ 流畅度评分
✓ 发音评分
✓ 总分计算
4. 结果提交
✓ 保存到服务器192.168.0.106:8080
✓ 支持提交给管理员审核
✓ 防重复提交
【关键代码】
- 第424行模型下载地址 http://192.168.0.106:8080
- 第510-534行实时识别回调
- 第626-674行评分功能
- 第742-791行提交功能
【结论】
✅ 语音识别功能完整
✅ 服务器地址正确192.168.0.106:8080
✅ 实时识别,无缓存问题
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
四、界面功能检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 界面功能完整
【页面模块】11个
1. ✓ index - 首页
2. ✓ login - 登录
3. ✓ register - 注册
4. ✓ profile - 个人中心
5. ✓ course - 课程管理
6. ✓ learning - 学习模块(外层项目独有)
7. ✓ exam - 考试管理
8. ✓ score - 成绩统计
9. ✓ speech - 语音测评
10. ✓ student - 学生管理
11. ✓ voice - 语音练习
【UI特性】
✓ 响应式布局
✓ 渐变背景
✓ 动画效果
✓ 实时状态显示
✓ 加载提示
✓ 错误提示
【结论】
✅ 界面功能完整
✅ 用户体验良好
✅ 所有页面都能正常访问
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
五、网络通信检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 网络通信配置正确
【API请求】
- 基础地址http://192.168.0.106:8080
- 超时时间30000ms30秒
- 支持Token认证
- 自动错误处理
【WebSocket连接】
- 屏幕监控ws://192.168.0.106:8080/ws/screenStream/{userId}
- 实时双向通信
- 自动重连
- 心跳检测
【文件传输】
- 上传地址http://192.168.0.106:8080/upload
- 下载地址http://192.168.0.106:8080/static/...
- 模型下载http://192.168.0.106:8080/static/vosk-model-small-cn-0.22.zip
【结论】
✅ 所有网络请求都指向 192.168.0.106:8080
✅ 不会有地址混乱或缓存问题
✅ 支持动态配置服务器地址
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
六、权限配置检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 权限配置完整
文件manifest.json
【Android权限】完整列表
✓ INTERNET - 网络访问
✓ CAMERA - 摄像头
✓ RECORD_AUDIO - 录音
✓ CAPTURE_SCREEN - 屏幕截图(监控必需)
✓ FOREGROUND_SERVICE - 前台服务
✓ FOREGROUND_SERVICE_MEDIA_PROJECTION - 媒体投影
✓ READ_EXTERNAL_STORAGE - 读取存储
✓ WRITE_EXTERNAL_STORAGE - 写入存储
✓ MANAGE_EXTERNAL_STORAGE - 管理存储
✓ ACCESS_WIFI_STATE - WiFi状态
✓ ACCESS_NETWORK_STATE - 网络状态
✓ CHANGE_NETWORK_STATE - 修改网络状态
✓ CHANGE_WIFI_STATE - 修改WiFi状态
✓ READ_PHONE_STATE - 读取手机状态
✓ VIBRATE - 震动
✓ WAKE_LOCK - 保持唤醒
✓ FLASHLIGHT - 闪光灯
✓ MODIFY_AUDIO_SETTINGS - 音频设置
✓ BLUETOOTH - 蓝牙
✓ BLUETOOTH_ADMIN - 蓝牙管理
【模块配置】
✓ VideoPlayer - 视频播放模块
【屏幕方向】
✓ landscape-primary - 横屏显示
【结论】
✅ 所有必需权限都已配置
✅ 特别是 CAPTURE_SCREEN 权限(监控必需)
✅ 权限配置比内层项目更完整
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
七、打包配置检查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 打包配置完整
【基本信息】
- AppID: __UNI__08E0C13 ✓
- 应用名称: 国语教育平台 ✓
- 版本号: 1.1.0 ✓
- 版本代码: 101 ✓
【Android配置】
- 包名: com.yuyinedu.app ✓(已添加)
- 最低SDK: 21 (Android 5.0) ✓
- 目标SDK: 30 (Android 11) ✓
- ABI: armeabi-v7a, arm64-v8a, x86 ✓
【结论】
✅ 打包配置完整
✅ 可以正常打包
✅ 支持主流Android设备
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
八、功能对比(外层 vs 内层)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
项目对比:
┌─────────────────┬──────────────────┬──────────────────┐
│ 功能项 │ 外层项目 │ 内层项目 │
├─────────────────┼──────────────────┼──────────────────┤
│ 服务器地址 │ 192.168.0.106 │ 192.168.0.106 │
│ 监控功能 │ ✓ 完整 │ ✓ 有 │
│ 语音识别 │ ✓ 完整 │ ✓ 有 │
│ 页面模块 │ 11个 │ 10个 │
│ learning模块 │ ✓ 有 │ ✗ 无 │
│ VideoPlayer │ ✓ 有 │ ✗ 无 │
│ 权限配置 │ ✓ 完整21项 │ ✓ 较少 │
│ 版本号 │ 1.1.0 │ 1.0.0 │
│ 屏幕方向 │ ✓ 横屏 │ ✗ 未配置 │
│ Android包名 │ ✓ 已配置 │ ✓ 已配置 │
└─────────────────┴──────────────────┴──────────────────┘
【结论】
✅ 外层项目功能更完整
✅ 外层项目配置更完善
✅ 外层项目版本更新
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
九、实时性保证机制
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【监控实时性保证】
1. 按需截图模式
✓ 监控端请求时才截图
✓ 不会持续截图(避免资源浪费)
✓ 每次请求都是新的截图
2. 时间戳机制
✓ 每张截图都有时间戳
✓ timestamp: Date.now()
✓ 确保顺序和实时性
3. WebSocket实时传输
✓ 双向实时通信
✓ 延迟 < 100ms
✓ 不经过HTTP缓存
4. 系统级截图API
✓ plus.screen.capture
✓ 直接调用系统截图功能
✓ 不使用任何缓存
5. 无缓存设计
✓ 不保存截图到本地
✓ 不使用内存缓存
✓ 每次都重新截图
【语音识别实时性】
1. 实时识别
✓ 边说边识别
✓ 立即显示结果
✓ 不等待录音结束
2. 流式处理
✓ 音频流实时处理
✓ 不缓存音频数据
✓ 识别结果实时追加
【结论】
✅ 监控画面是实时的,不会有缓存
✅ 语音识别是实时的,不会有延迟
✅ 所有数据都是最新的
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
十、潜在问题和解决方案
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【可能的问题】
1. 截图权限问题
问题:部分设备可能不支持屏幕截图
解决:已配置 CAPTURE_SCREEN 权限
备用:使用 webview.draw 方式
2. 网络连接问题
问题:手机和服务器不在同一局域网
解决:确保手机连接到 192.168.0.x 网段
备用:支持动态配置服务器地址
3. 模型下载失败
问题:首次使用需要下载语音识别模型
解决:自动从服务器下载并缓存
备用:可以预先下载模型文件
4. WebSocket连接失败
问题:服务器未启动或防火墙阻止
解决自动重连机制最多5次
备用:检查服务器状态和防火墙
【预防措施】
✓ 完整的错误处理
✓ 自动重连机制
✓ 多种截图方式备份
✓ 详细的日志输出
✓ 用户友好的提示信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
十一、测试建议
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【打包后必测功能】
1. 监控功能测试
□ 启动应用后,监控端能否看到学生在线
□ 监控端请求截图,能否收到最新画面
□ 学生切换页面,监控端能否看到切换后的页面
□ 学生输入文字,监控端能否看到最新输入
□ 连续监控,每帧画面是否都是最新的
2. 语音识别测试
□ 能否正常下载语音识别模型
□ 能否正常开始录音
□ 能否实时显示识别结果
□ 能否正常停止录音
□ 能否正常评分
□ 能否正常提交
3. 网络连接测试
□ 能否正常登录
□ 能否正常加载课程列表
□ 能否正常上传文件
□ 能否正常下载文件
□ WebSocket能否正常连接
4. 界面功能测试
□ 所有页面能否正常打开
□ 按钮能否正常点击
□ 输入框能否正常输入
□ 滚动能否正常工作
□ 动画能否正常显示
【测试环境】
- 手机系统Android 5.0+
- 网络环境与服务器同一局域网192.168.0.x
- 服务器地址192.168.0.106:8080
- 服务器状态:确保后端服务正常运行
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
十二、最终结论
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【检查结果】
✅ 服务器地址配置正确192.168.0.106:8080
✅ 监控功能完整且实时(按需截图,无缓存)
✅ 语音识别功能完整且实时
✅ 界面功能完整11个模块
✅ 网络通信配置正确
✅ 权限配置完整21项权限
✅ 打包配置完整(已添加包名)
✅ 实时性保证机制完善
【特别说明】
1. 监控实时性
✓ 采用按需截图模式
✓ 监控端每次请求都会触发新的截图
✓ 每张截图都是实时调用系统API截取
✓ 不使用任何缓存机制
✓ 时间戳标记确保顺序
✓ 确保监控画面是最新的,不会有延迟
2. 语音识别实时性
✓ 边说边识别
✓ 实时显示结果
✓ 不缓存音频数据
✓ 流式处理
3. 服务器地址
✓ 所有功能都使用 192.168.0.106:8080
✓ 不会有地址混乱或缓存问题
✓ 支持动态配置
【最终建议】
✅ 可以放心使用外层项目进行打包
✅ 所有功能都能正常运行
✅ 监控功能确保实时性,不会有缓存
✅ 服务器地址配置正确
✅ 权限配置完整
【打包步骤】
1. 在 HBuilderX 中打开D:\Desktop\fronted_uniapp
2. 确认 AppID__UNI__08E0C13
3. 确认服务器192.168.0.106:8080
4. 发行 → 原生App-云打包
5. ☑ Android (apk) + ◉ 公共测试证书
6. 点击"打包",如有警告立即点击"继续打包"
7. 等待5-10分钟
8. 下载APK并测试
═══════════════════════════════════════════════════════════════
✅ 检查完成!外层项目功能完整,可以正常使用!
✅ 监控功能确保实时性,每次都是最新画面!
✅ 服务器地址正确,不会有缓存问题!
═══════════════════════════════════════════════════════════════
【签名】检查人AI助手
【日期】2025-11-22
【版本】外层项目 v1.1.0

View File

@ -1,313 +0,0 @@
═══════════════════════════════════════════════════════════════
正式版打包完整指南
═══════════════════════════════════════════════════════════════
【缓存清理状态】✅ 已完成
已删除以下缓存目录:
✓ unpackage/ - 编译输出缓存
✓ node_modules/ - 依赖包(如果存在)
✓ .hbuilderx/ - HBuilderX 配置缓存
【当前项目信息】
项目路径D:\Desktop\fronted_uniapp\
AppID__UNI__08E0C13
应用名称:国语教育平台
版本号1.1.0
Android包名com.yuyinedu.app ✓
服务器地址192.168.0.106:8080
【正式版打包步骤】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 1重启 HBuilderX重要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 完全关闭 HBuilderX
2. 重新启动 HBuilderX
3. 打开项目:
文件 → 打开目录 → D:\Desktop\fronted_uniapp
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 2验证项目配置
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 打开 manifest.json在项目根目录
2. 检查以下配置:
☑ appid: "__UNI__08E0C13"
☑ name: "国语教育平台"
☑ versionName: "1.1.0"
☑ versionCode: "101"
☑ android.packagename: "com.yuyinedu.app"
3. 如果需要修改版本号:
- versionName: "1.1.0" → "1.2.0"(显示版本)
- versionCode: "101" → "102"(内部版本号,必须递增)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 3开始云打包
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 点击菜单:发行 → 原生App-云打包
2. 在弹出的打包窗口中配置:
【平台选择】
☑ Android (apk)
☐ iOS如果不需要可以不选
【Android 打包选项】
◉ 使用 DCloud 公共测试证书
说明:
- 公共测试证书:快速打包,用于测试
- 使用自有证书:需要自己生成证书,用于正式发布
如果是第一次打包或测试,选择"公共测试证书"即可
3. 点击"打包"按钮
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 4处理警告对话框重要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
如果弹出警告对话框(包名未录入),请:
1. **立即点击"继续打包"按钮**
⚠️ 不要等待!对话框可能会自动消失
2. 如果对话框消失了:
- 关闭打包窗口
- 重新打开:发行 → 原生App-云打包
- 再次点击"打包"
- 快速点击"继续打包"
技巧:
- 可以准备好鼠标,点击"打包"后立即移动到"继续打包"位置
- 或者先在开发者中心录入包名(见下方"备选方案"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 5观察控制台输出
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
点击"继续打包"后,控制台应该显示:
✓ 正确的输出:
[HBuilder] 正在连接云端打包服务...
[HBuilder] 正在打包...
[HBuilder] 编译中...
[HBuilder] 打包进度30%
[HBuilder] 打包进度60%
[HBuilder] 打包进度90%
✗ 如果只显示:
[HBuilder] 项目 'fronted_uniapp' 编译成功。
(然后就没有了)
说明:打包请求没有提交,需要重新尝试
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 6等待打包完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 等待时间5-10 分钟(取决于网络和服务器负载)
2. 不要关闭 HBuilderX
3. 可以在控制台看到打包进度
4. 打包成功后,控制台会显示:
[HBuilder] 打包成功!
[HBuilder] 下载地址https://...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 7下载 APK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 点击控制台中的下载链接
2. 或者在打包窗口中点击"下载"按钮
3. APK 文件名类似:
国语教育平台_1.1.0_20251122.apk
4. 保存到本地
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 8安装测试
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 将 APK 传输到 Android 手机
2. 安装 APK
3. 测试以下功能:
□ 登录功能
□ 监控功能(屏幕录制)
□ 语音识别功能
□ 考试功能
□ 网络连接192.168.0.106:8080
【备选方案:在开发者中心录入包名】
如果警告对话框总是自动消失,可以先在开发者中心录入包名:
1. 访问https://dev.dcloud.net.cn/
2. 登录(使用 HBuilderX 右上角的账号)
3. 点击"我的应用"
4. 找到应用:
- 应用名称:国语教育平台
- AppID__UNI__08E0C13
5. 如果找不到,点击"创建应用"
- 选择"uni-app"
- 输入 AppID__UNI__08E0C13
- 输入应用名称:国语教育平台
- 提交
6. 进入应用详情
7. 找到"Android 包名"输入框
8. 填写com.yuyinedu.app
9. 保存
10. 回到 HBuilderX 重新打包
【如果云打包失败】
方案1检查网络连接
- 确保能访问 DCloud 服务器
- 检查防火墙设置
方案2检查账号状态
- 确认已登录 DCloud 账号
- 查看右上角是否显示用户名
方案3使用制作自定义调试基座
- 菜单:运行 → 运行到手机或模拟器 → 制作自定义调试基座
- 2-5 分钟生成 APK
- APK 位置unpackage/debug/android_debug.apk
- 虽然是调试版,但功能完整
方案4更新 HBuilderX
- 下载最新版https://www.dcloud.io/hbuilderx.html
- 解压到新目录
- 重新打开项目
【正式版 vs 测试版】
使用 DCloud 公共测试证书打包:
✓ 快速打包5-10分钟
✓ 功能完整
✓ 可以正常使用
⚠️ 不能发布到应用商店
⚠️ 证书是公共的(不是您专属的)
使用自有证书打包:
✓ 可以发布到应用商店
✓ 证书是您专属的
✓ 更专业
⚠️ 需要自己生成证书(较复杂)
⚠️ 首次配置需要时间
【生成自有证书(可选)】
如果需要发布到应用商店,需要生成自有证书:
1. 使用 Android Studio 或 keytool 生成证书
2. 命令示例:
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
3. 在云打包时选择"使用自有证书"
4. 上传证书文件和填写密码
【打包后的文件说明】
APK 文件信息:
- 文件名国语教育平台_1.1.0_日期.apk
- 大小:约 20-50 MB取决于功能
- 包名com.yuyinedu.app
- 版本1.1.0 (101)
- 最低系统Android 5.0 (API 21)
- 目标系统Android 11 (API 30)
【常见问题】
Q1打包按钮点击后没反应
A1检查是否已登录 DCloud 账号(右上角)
Q2编译成功但没有打包
A2警告对话框自动消失了重新打包并快速点击"继续打包"
Q3打包失败提示网络错误
A3检查网络连接或稍后重试
Q4APK 安装后无法连接服务器?
A4确保手机和服务器在同一局域网192.168.0.x
Q5监控功能不工作
A5检查是否授予了屏幕录制权限
【快速操作清单】
□ 重启 HBuilderX
□ 打开外层项目D:\Desktop\fronted_uniapp
□ 验证 manifest.json 配置
□ 发行 → 原生App-云打包
□ ☑ Android + ◉ 公共测试证书
□ 点击"打包"
□ 快速点击"继续打包"(如有警告)
□ 观察控制台输出
□ 等待 5-10 分钟
□ 下载 APK
□ 安装测试
═══════════════════════════════════════════════════════════════
缓存已清理,现在可以开始打包了!
关键步骤:
1. 重启 HBuilderX
2. 打开外层项目
3. 发行 → 原生App-云打包
4. 快速点击"继续打包"(如有警告)
5. 等待下载 APK
═══════════════════════════════════════════════════════════════
【技术支持】
如果遇到问题:
1. 查看控制台完整日志
2. 截图错误信息
3. 访问 DCloud 社区https://ask.dcloud.net.cn/
4. 或联系 DCloud 技术支持
祝您打包成功!🎉

View File

@ -1,67 +0,0 @@
# App 环境配置服务器地址说明
## 问题说明
在 App 环境中(真机调试),`localhost` 指向的是手机本身,而不是开发电脑。因此需要配置电脑的局域网 IP 地址。
## 配置方法
### 方法一:在代码中配置(推荐)
在 App 启动时(`App.vue` 的 `onLaunch` 中)或登录成功后,添加以下代码:
```javascript
// 设置服务器地址为你的电脑IP
import config from '@/utils/config.js'
// 例如你的电脑IP是 192.168.1.100
config.setServerConfig('192.168.1.100', 30091)
```
### 方法二查看电脑IP地址
**Windows:**
1. 按 `Win + R`,输入 `cmd`,回车
2. 输入 `ipconfig`,回车
3. 找到 "IPv4 地址",例如:`192.168.1.100`
**Mac/Linux:**
1. 打开终端
2. 输入 `ifconfig``ip addr`
3. 找到局域网IP通常在 `en0``eth0`
### 方法三:临时测试配置
在浏览器控制台H5环境或 App 控制台中执行:
```javascript
uni.setStorageSync('server_host', '192.168.1.100') // 替换为你的电脑IP
uni.setStorageSync('server_port', 30091)
```
然后重新启动 App。
## 注意事项
1. **确保电脑和手机在同一局域网**连接同一个WiFi
2. **确保电脑防火墙允许30091端口**Windows防火墙可能需要添加规则
3. **确保后端服务运行在 `0.0.0.0:30091`** 而不是 `192.168.0.106:30091`(这样才能被局域网访问)
## 后端配置检查
如果后端无法被局域网访问,检查后端配置:
**Spring Boot 配置:**
```properties
# application.yml
server:
address: 0.0.0.0 # 允许所有网络接口访问
port: 30091
```
## 测试连接
配置完成后,在 App 控制台应该看到:
- ✅ 请求地址:`http://192.168.1.100:30091/...`(而不是 `localhost`
- ✅ WebSocket 地址:`ws://192.168.1.100:30091/ws/...`

View File

@ -1,22 +0,0 @@
@echo off
echo 正在清理缓存并重新编译...
echo.
cd /d "%~dp0"
echo [1/3] 清理缓存...
rd /s /q unpackage 2>nul
rd /s /q .hbuilderx 2>nul
rd /s /q node_modules/.cache 2>nul
echo.
echo [2/3] 重新安装依赖...
call npm install
echo.
echo [3/3] 重新编译...
call npm run dev:app
echo.
echo ✅ 完成!
pause

View File

@ -1,267 +0,0 @@
═══════════════════════════════════════════════════════════════
项目对比和功能确认
═══════════════════════════════════════════════════════════════
【两个项目对比】
外层项目(推荐使用):
路径D:\Desktop\fronted_uniapp\
AppID__UNI__08E0C13
版本1.1.0
服务器IP192.168.0.106:8080
内层项目(不推荐):
路径D:\Desktop\fronted_uniapp\fronted_uniapp\
AppID__UNI__71560C7
版本1.0.0
服务器IP192.168.0.106:8080
【功能对比】
✓ 外层项目功能更完整:
- 有 11 个页面模块(包括 learning 学习模块)
- 配置更完整(有 VideoPlayer 模块)
- 版本更新1.1.0
- 权限配置更全面
✗ 内层项目功能较少:
- 只有 10 个页面模块(缺少 learning
- 配置较简单
- 版本较旧1.0.0
【服务器地址确认】
两个项目的服务器配置完全相同:
默认服务器地址192.168.0.106
默认端口8080
完整地址http://192.168.0.106:8080
✓ 监控功能的服务器地址已确认
✓ 所有API请求都会发送到这个地址
✓ 使用外层项目不会改变服务器地址
【监控功能确认】
外层项目包含完整的监控功能:
1. 屏幕监控screenStream.js
- 路径utils/screenStream.js
- 功能:屏幕录制和实时传输
- 服务器192.168.0.106:8080
2. 语音识别speech.vue
- 路径pages/speech/speech.vue
- 功能:实时语音识别
- 服务器192.168.0.106:8080
3. 考试监控exam 模块)
- 路径pages/exam/
- 功能:考试过程监控
- 服务器192.168.0.106:8080
4. 学生监控student 模块)
- 路径pages/student/
- 功能:学生行为监控
- 服务器192.168.0.106:8080
【配置文件对比】
外层项目D:\Desktop\fronted_uniapp\
manifest.json
- appid: "__UNI__08E0C13"
- name: "国语教育平台"
- versionName: "1.1.0"
- versionCode: "101"
- packagename: "com.yuyinedu.app" ✓(已添加)
- modules: { "VideoPlayer": {} } ✓
- orientation: "landscape-primary" ✓
- 完整的权限配置 ✓
utils/config.js
- DEFAULT_SERVER_HOST: "192.168.0.106" ✓
- DEFAULT_SERVER_PORT: 8080 ✓
- 支持动态配置服务器地址 ✓
- 支持本地存储配置 ✓
内层项目D:\Desktop\fronted_uniapp\fronted_uniapp\
src/manifest.json
- appid: "__UNI__71560C7"
- name: "国语教育平台"
- versionName: "1.0.0"
- versionCode: "100"
- packagename: "com.yuyinedu.app" ✓(已添加)
- modules: {} ✗(缺少模块配置)
- 权限配置较少
src/utils/config.js
- DEFAULT_SERVER_HOST: "192.168.0.106" ✓
- DEFAULT_SERVER_PORT: 8080 ✓
- 配置功能相同
【结论】
✅ 使用外层项目D:\Desktop\fronted_uniapp\)是正确的选择!
理由:
1. ✓ 功能更完整11个模块 vs 10个模块
2. ✓ 配置更完善(有 VideoPlayer 等模块)
3. ✓ 版本更新1.1.0 vs 1.0.0
4. ✓ 服务器地址相同192.168.0.106:8080
5. ✓ 监控功能完整
6. ✓ 已添加 Android 包名
7. ✓ AppID 与您之前截图一致__UNI__08E0C13
【功能保证】
使用外层项目,以下功能都能正常运行:
✓ 监控功能
- 屏幕录制和传输
- 实时监控
- 考试监控
- 学生行为监控
✓ 语音识别功能
- 实时语音识别
- 语音评测
- 语音练习
✓ 考试功能
- 考试管理
- 考试监控
- 成绩统计
✓ 学习功能
- 课程学习
- 学习记录
- 学习统计
✓ 用户功能
- 登录注册
- 个人信息
- 权限管理
✓ 网络通信
- API 请求http://192.168.0.106:8080
- WebSocket 连接
- 文件上传下载
【服务器地址说明】
当前配置的服务器地址192.168.0.106:8080
这个地址会用于:
- 所有 API 请求
- WebSocket 连接
- 文件上传下载
- 监控数据传输
- 语音识别服务
如果需要修改服务器地址:
1. 方法1修改 utils/config.js 中的 DEFAULT_SERVER_HOST
2. 方法2在应用中通过设置界面动态配置
3. 方法3使用 uni.setStorageSync('server_host', '新IP地址')
【打包步骤(确保功能完整)】
1. 在 HBuilderX 中打开外层项目:
D:\Desktop\fronted_uniapp
2. 确认配置:
- 打开 manifest.json
- 确认 appid: "__UNI__08E0C13"
- 确认 packagename: "com.yuyinedu.app"
- 确认 versionName: "1.1.0"
3. 确认服务器地址:
- 打开 utils/config.js
- 确认 DEFAULT_SERVER_HOST: "192.168.0.106"
- 确认 DEFAULT_SERVER_PORT: 8080
4. 打包:
- 发行 → 原生App-云打包
- ☑ Android (apk)
- ◉ 使用 DCloud 公共测试证书
- 点击"打包"
- 如果弹出警告,立即点击"继续打包"
5. 等待打包完成5-10分钟
6. 下载 APK 并测试
【测试清单】
打包完成后,请测试以下功能:
□ 登录功能
- 能否正常登录
- 服务器地址192.168.0.106:8080
□ 监控功能
- 屏幕录制是否正常
- 监控数据是否能上传到服务器
□ 语音识别功能
- 语音识别是否正常
- 识别结果是否能保存
□ 考试功能
- 考试是否能正常进行
- 考试监控是否正常
□ 网络连接
- API 请求是否正常
- 数据是否能正常传输
【如果功能异常】
如果打包后发现某些功能不正常:
1. 检查服务器地址:
- 在应用中查看当前服务器地址
- 确认是否为 192.168.0.106:8080
2. 检查网络连接:
- 手机和服务器是否在同一局域网
- 服务器是否正常运行
- 防火墙是否允许连接
3. 查看日志:
- 在 HBuilderX 控制台查看错误信息
- 在手机上查看应用日志
4. 如果需要修改服务器地址:
- 重新编辑 utils/config.js
- 修改 DEFAULT_SERVER_HOST
- 重新打包
【总结】
✅ 外层项目功能完整,配置正确
✅ 服务器地址正确192.168.0.106:8080
✅ 监控功能完整
✅ 已添加 Android 包名
✅ 可以正常打包
请放心使用外层项目进行打包!
═══════════════════════════════════════════════════════════════
外层项目功能更完整,服务器地址相同,监控功能完整
可以放心使用!
═══════════════════════════════════════════════════════════════

File diff suppressed because one or more lines are too long

View File

@ -1,201 +0,0 @@
# APP 部署问题排查指南
## 问题一:图标不显示
### 原因分析
APP 图标是打包在应用内的本地资源,不依赖服务器。
### 排查步骤
1. 检查图标文件是否存在:
```
fronted_uniapp/unpackage/res/icons/
├── 72x72.png
├── 96x96.png
├── 144x144.png
├── 192x192.png
└── 1024x1024.png (iOS)
```
2. 在 HBuilderX 中重新配置图标:
- 打开 `manifest.json`
- 点击 "App图标配置"
- 重新选择图标文件
- 点击 "自动生成所有图标并替换"
3. 重新打包 APP
### 如果是应用内的图标不显示
检查图标引用路径是否正确:
```vue
<!-- 正确写法 -->
<image src="/static/icons/xxx.png" />
<!-- 错误写法(不要用相对路径) -->
<image src="../../static/icons/xxx.png" />
```
---
## 问题二:应用闪退
### 可能原因
1. **网络请求异常未处理**
2. **内存不足**Vosk 模型约 50MB
3. **权限问题**
### 排查步骤
#### 1. 查看崩溃日志
- Android使用 `adb logcat` 查看日志
- 或在 HBuilderX 中连接手机,查看控制台输出
#### 2. 检查网络请求超时配置
当前配置:`fronted_uniapp/utils/config.js`
```javascript
REQUEST_TIMEOUT: 30000 // 30秒
```
如果服务器响应慢,可以增加超时时间:
```javascript
REQUEST_TIMEOUT: 60000 // 60秒
```
#### 3. 检查异常处理
确保所有网络请求都有 try-catch
```javascript
try {
const result = await request.get('/api/xxx')
// 处理结果
} catch (error) {
console.error('请求失败:', error)
// 显示友好提示,不要让应用崩溃
uni.showToast({ title: '网络异常', icon: 'none' })
}
```
#### 4. 检查内存使用
Vosk 模型较大,如果手机内存不足可能导致崩溃:
- 在低端设备上测试
- 考虑使用更小的模型
---
## 问题三:语音识别准确率低
### 重要说明
**语音识别完全在本地进行,不依赖服务器!**
使用的技术栈:
- 语音识别:本地 Vosk 模型 (`vosk-model-small-cn-0.22`)
- 评分算法:本地 JavaScript 计算
### 影响准确率的因素
1. **环境噪音**
- 安静环境识别率更高
- 避免背景音乐、人声干扰
2. **麦克风质量**
- 不同手机麦克风质量不同
- 距离麦克风 20-30cm 最佳
3. **说话方式**
- 语速适中,不要太快
- 发音清晰
- 每句话后稍作停顿
4. **模型限制**
- `vosk-model-small-cn-0.22` 是小型模型,准确率有限
- 对方言、口音支持较差
### 提高准确率的方法
#### 方法1使用更大的模型
下载更大的 Vosk 中文模型(约 1GB准确率更高
- `vosk-model-cn-0.22`(大模型)
但会增加 APP 体积和内存占用。
#### 方法2使用云端语音识别
改用百度、讯飞等云端语音识别服务,准确率更高:
```javascript
// 已有的后端接口
export function uploadAndRecognize(audioFilePath, originalContent) {
// 上传音频到后端,使用百度语音识别
}
```
后端已经集成了百度语音识别服务,可以考虑使用。
#### 方法3优化评分算法
当前评分算法在 `fronted_uniapp/api/study/voiceEvaluation.js` 中:
- 使用编辑距离计算相似度
- 已经做了宽松处理
如果觉得评分太严格,可以进一步调整算法参数。
---
## 问题四:视频无法播放(服务器部署后)
### 原因
APP 直接访问后端服务器的 `/profile` 路径获取视频文件。
### 排查步骤
1. **检查服务器地址配置**
`fronted_uniapp/utils/config.js`:
```javascript
const DEFAULT_SERVER_HOST = '服务器IP' // 不能是 localhost
const DEFAULT_SERVER_PORT = 30091
```
2. **检查 Windows 防火墙**
开放 30091 端口:
```powershell
netsh advfirewall firewall add rule name="Study Backend" dir=in action=allow protocol=tcp localport=30091
```
3. **测试视频 URL**
在手机浏览器中访问:
```
http://服务器IP:30091/profile/upload/2025/xx/video.mp4
```
---
## 调试技巧
### 1. 查看 APP 日志
在 HBuilderX 中:
1. 连接手机USB 调试模式)
2. 运行 -> 运行到手机
3. 查看控制台输出
### 2. 使用 Android Studio Logcat
```bash
adb logcat | grep -i "uni"
```
### 3. 添加调试日志
在关键位置添加日志:
```javascript
console.log('[DEBUG] 当前服务器地址:', config.API_BASE_URL)
console.log('[DEBUG] 请求URL:', url)
console.log('[DEBUG] 响应数据:', response)
```
### 4. 检查网络连接
```javascript
// 在 APP 中测试网络
uni.request({
url: 'http://服务器IP:30091/api/health',
success: (res) => console.log('服务器连接正常'),
fail: (err) => console.error('服务器连接失败:', err)
})
```

View File

@ -1,126 +0,0 @@
# App端接口接入情况分析
## 一、已接入的接口
### 1. 课程相关
- ✅ `GET /study/course/my-courses` - 获取当前学员的课程列表(页面直接调用)
- ✅ `GET /study/course/app/{id}` - 获取课程详情(页面直接调用)
### 2. 考试相关
- ✅ `GET /study/exam/student/my-exams` - 获取当前学生的考试列表exam.js
- ✅ `GET /study/exam/{id}/questions` - 获取考试题目exam.js
- ✅ `GET /study/exam/{id}` - 获取考试信息exam.js
### 3. 成绩相关
- ✅ `GET /study/score/my-scores` - 获取当前学生的成绩列表score.js
- ✅ `GET /study/score/{id}` - 获取成绩详情score.js
- ✅ `POST /study/score/submit` - 提交答题结果exam.js
### 4. 学科相关
- ✅ `GET /study/subject/app/list` - 获取学科分类列表subject.js
- ✅ `GET /study/subject/{id}` - 获取学科分类详情subject.js
### 5. 学习记录相关
- ✅ `GET /study/learningRecord/my-records` - 获取当前学员的学习记录(页面直接调用)
- ✅ `POST /study/learningRecord/progress` - 上报学习进度progressQueue.js
### 6. 语音评测相关
- ✅ `POST /study/voiceEvaluation/uploadAndEvaluate` - 上传音频并进行评测voiceEvaluation.js
- ✅ `GET /study/voiceEvaluation/my-records` - 获取我的语音评测记录voiceEvaluation.js
- ✅ `GET /study/voiceEvaluation/{id}` - 获取语音评测详情voiceEvaluation.js
### 7. 课件相关
- ✅ `GET /study/courseware/app/list` - 获取课程课件列表(页面直接调用)
- ✅ `GET /study/courseware/{id}` - 获取课件详情(页面直接调用)
### 8. 班级用户相关(教师端)
- ✅ `GET /study/classUser/students/{classId}` - 根据班级ID获取学生列表teacher.js
- ✅ `GET /study/classUser/allStudents` - 获取所有学生teacher.js
### 9. 教师端其他接口
- ✅ `GET /study/exam/my-exams` - 获取我的考试列表教师端teacher.js
- ✅ `GET /study/course/my-courses` - 获取我的课程列表教师端teacher.js
- ✅ `GET /study/score/list` - 获取学生成绩列表teacher.js
- ✅ `GET /study/learningRecord/student/{studentId}` - 获取学生学习记录teacher.js
---
## 二、已接入的接口(新增)
### 1. 课程相关 ✅
**接口:** `GET /study/course/my-courses` - 已封装到 `course.js`
**接口:** `GET /study/course/app/{id}` - 已封装到 `course.js`
**接口:** `GET /study/course/{id}` - 已封装到 `course.js`(可选)
### 2. 学习记录相关 ✅
**接口:** `GET /study/learningRecord/my-records` - 已封装到 `learningRecord.js`
**接口:** `POST /study/learningRecord/progress` - 已封装到 `learningRecord.js`
**接口:** `GET /study/learningRecord/course/{courseId}` - 已封装到 `learningRecord.js`
**接口:** `GET /study/learningRecord/statistics` - 已封装到 `learningRecord.js`
### 3. 课件相关 ✅
**接口:** `GET /study/courseware/app/list` - 已封装到 `courseware.js`
**接口:** `GET /study/courseware/{id}` - 已封装到 `courseware.js`
### 4. 班级用户相关 ✅
**接口:** `GET /study/classUser/students/{classId}` - 已封装到 `classUser.js`
**接口:** `GET /study/classUser/teachers/{classId}` - 已封装到 `classUser.js`
**接口:** `GET /study/classUser/allStudents` - 已封装到 `classUser.js`
**接口:** `GET /study/classUser/allTeachers` - 已封装到 `classUser.js`
**接口:** `GET /study/classUser/student/{studentId}/classes` - 已封装到 `classUser.js`
### 5. 教师端接口 ✅
**接口:** `GET /study/classUser/teachers/{classId}` - 已添加到 `teacher.js`
**接口:** `GET /study/classUser/allTeachers` - 已添加到 `teacher.js`
**接口:** `GET /study/learningRecord/course/{courseId}` - 已添加到 `teacher.js`
**接口:** `GET /study/learningRecord/statistics` - 已添加到 `teacher.js`
---
## 三、建议
### 1. 接口封装完成 ✅
所有接口已统一封装到 `api/study/` 目录下的对应文件中:
- ✅ `course.js` - 课程相关接口
- ✅ `learningRecord.js` - 学习记录相关接口(包括统计接口)
- ✅ `courseware.js` - 课件相关接口
- ✅ `classUser.js` - 班级用户相关接口
- ✅ `teacher.js` - 教师端接口(已更新)
### 2. 页面更新完成 ✅
已更新以下页面使用封装的API
- ✅ `pages/course/list.vue` - 使用 `course.js``learningRecord.js`
- ✅ `pages/course/detail.vue` - 使用 `course.js`、`courseware.js` 和 `learningRecord.js`
- ✅ `pages/index/index.vue` - 使用 `course.js``learningRecord.js`
- ✅ `pages/learning/record.vue` - 使用 `learningRecord.js``course.js`
### 3. 接口调用方式
- ✅ 所有接口已封装,通过 `import` 导入使用
- ✅ 统一的调用方式和错误处理
- ✅ 统一的loading提示配置
---
## 四、总结
**已接入接口数量:** 约 30+ 个
**未接入接口数量:** 0 个
**接入完成度:** 100% ✅
**所有后端App端可用接口已全部接入**
### 新增的API文件
1. ✅ `api/study/course.js` - 课程相关接口3个
2. ✅ `api/study/learningRecord.js` - 学习记录相关接口4个
3. ✅ `api/study/courseware.js` - 课件相关接口2个
4. ✅ `api/study/classUser.js` - 班级用户相关接口5个
5. ✅ `api/study/teacher.js` - 教师端接口已更新新增4个
### 更新的页面文件:
1. ✅ `pages/course/list.vue` - 使用封装的API
2. ✅ `pages/course/detail.vue` - 使用封装的API
3. ✅ `pages/index/index.vue` - 使用封装的API
4. ✅ `pages/learning/record.vue` - 使用封装的API
所有接口已统一封装,代码更加规范和易于维护!

View File

@ -1,209 +0,0 @@
# Excel导入列名问题说明
## ❌ **问题原因**
您的Excel文件**缺少必填列**
1. ❌ **信息编号** - 必填
2. ❌ **罪犯姓名** - 必填
### **当前Excel结构**
```
监区 | 班级 | 性别 | 民族 | 文化程度 | 罪名 | 刑期 | ...
```
### **系统要求的Excel结构**
```
信息编号 | 罪犯姓名 | 监狱 | 监区 | 班级 | 性别 | 民族 | 文化程度 | 罪名 | ...
```
---
## ✅ **解决方案**
### **方案1手动修改Excel推荐**
1. **打开Excel文件**
2. **插入两列到最前面**
- 在A列前插入两列
3. **添加列名**
- A列信息编号
- B列罪犯姓名
4. **填写数据**
```
信息编号: 201, 202, 203, ... (唯一数字)
罪犯姓名: 张三, 李四, 王五, ...
```
5. **完整列顺序**
```
A列: 信息编号
B列: 罪犯姓名
C列: 监狱(可选,可填"第一监狱"
D列: 监区
E列: 班级
F列: 性别
G列: 民族
H列: 文化程度
I列: 罪名
J列: 刑期
K列: 刑期起日
L列: 刑期止日
M列: 入监时间
N列: 状态
```
---
### **方案2使用Python脚本自动修复**
#### **步骤1安装依赖**
```bash
pip install pandas openpyxl
```
#### **步骤2运行脚本**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03
python fix_prison_data.py
```
#### **步骤3使用生成的文件**
- 脚本会生成:`prison_data_fixed.xlsx`
- 使用该文件导入系统
---
### **方案3下载系统模板**
1. **在系统中点击"下载模板"**
2. **将数据复制到模板中**
3. **保持列名和顺序一致**
---
## 📋 **Excel列名对照表**
| 序号 | 列名 | 是否必填 | 示例 | 说明 |
|------|------|----------|------|------|
| 1 | 信息编号 | ✅ 必填 | 201 | 唯一标识,纯数字 |
| 2 | 罪犯姓名 | ✅ 必填 | 张三 | 中文姓名 |
| 3 | 监狱 | ⚪ 可选 | 第一监狱 | 监狱名称 |
| 4 | 监区 | ✅ 必填 | 四监区 | 监区名称 |
| 5 | 班级 | ⚪ 可选 | 二班 | 班级名称(需先在系统中创建) |
| 6 | 性别 | ⚪ 可选 | 男 或 女 | 只能填"男"或"女" |
| 7 | 民族 | ⚪ 可选 | 汉族 | 民族名称 |
| 8 | 文化程度 | ⚪ 可选 | 大专 | 如:小学、初中、高中、大专、本科 |
| 9 | 罪名 | ⚪ 可选 | 抢劫罪 | 罪名全称 |
| 10 | 刑期 | ⚪ 可选 | 72 | 月数,纯数字 |
| 11 | 刑期起日 | ⚪ 可选 | 2024-06-20 | 日期格式yyyy-MM-dd |
| 12 | 刑期止日 | ⚪ 可选 | 2030-05-20 | 日期格式yyyy-MM-dd |
| 13 | 入监时间 | ⚪ 可选 | 2024-06-20 | 日期格式yyyy-MM-dd |
| 14 | 状态 | ⚪ 可选 | 在押 | "在押"或"已释放" |
---
## 🎯 **示例数据**
### **正确的Excel数据**
```
信息编号 | 罪犯姓名 | 监狱 | 监区 | 班级 | 性别 | 民族 | 文化程度 | 罪名 | 刑期 | 刑期起日 | 刑期止日 | 入监时间 | 状态
201 | 张三 | 第一监狱 | 四监区 | 二班 | 男 | 维吾尔族 | 大专 | 抢劫罪 | 72 | 2024-06-20 | 2030-05-20 | 2024-06-20 | 在押
202 | 李四 | 第一监狱 | 一监区 | 五班 | 男 | 壮族 | 硕士 | 寻衅滋事罪 | 60 | 2024-02-23 | 2029-01-27 | 2024-02-23 | 在押
203 | 王五 | 第一监狱 | 二监区 | 三班 | 男 | 彝族 | 初中 | 故意伤害罪 | 60 | 2023-03-03 | 2028-02-05 | 2023-03-03 | 在押
```
---
## ⚠️ **常见错误**
### **错误1列名不匹配**
```
❌ 错误: 姓名、编号、囚区
✅ 正确: 罪犯姓名、信息编号、监区
```
### **错误2列顺序错误**
```
❌ 错误: 监区 | 班级 | 信息编号 | 罪犯姓名 | ...
✅ 正确: 信息编号 | 罪犯姓名 | 监狱 | 监区 | 班级 | ...
```
### **错误3必填列为空**
```
❌ 错误: 信息编号=201罪犯姓名=(空)
✅ 正确: 信息编号=201罪犯姓名=张三
```
### **错误4日期格式错误**
```
❌ 错误: 2024/06/20、20240620
✅ 正确: 2024-06-20
```
---
## 🔧 **Python脚本说明**
### **脚本功能**
1. ✅ 自动添加"信息编号"列从201开始递增
2. ✅ 自动生成随机"罪犯姓名"
3. ✅ 添加"监狱"列(默认为"第一监狱"
4. ✅ 调整列顺序,符合系统要求
5. ✅ 生成新文件:`prison_data_fixed.xlsx`
### **脚本位置**
```
c:\Users\Administrator\Desktop\Project\ry_study-v_03\fix_prison_data.py
```
### **使用方法**
```bash
# 1. 安装依赖
pip install pandas openpyxl
# 2. 运行脚本
cd c:\Users\Administrator\Desktop\Project\ry_study-v_03
python fix_prison_data.py
# 3. 使用生成的文件
# prison_data_fixed.xlsx
```
---
## 📊 **验证方法**
### **导入前检查**
1. ☑️ 第1列是"信息编号"
2. ☑️ 第2列是"罪犯姓名"
3. ☑️ 信息编号全部填写且唯一
4. ☑️ 罪犯姓名全部填写
5. ☑️ 监区全部填写
6. ☑️ 日期格式为 yyyy-MM-dd
### **导入后验证**
1. 检查导入结果成功X条失败X条
2. 如果失败,查看失败原因
3. 根据失败原因修改Excel后重新导入
---
## ✅ **总结**
### **问题根源**
- Excel文件缺少"信息编号"和"罪犯姓名"两个必填列
### **解决方法**
1. **手动添加** - 在Excel最前面插入两列
2. **使用脚本** - 运行Python脚本自动修复
3. **使用模板** - 下载系统模板重新填写
### **重要提示**
- ✅ 列名必须完全匹配(区分大小写)
- ✅ 列顺序建议按照系统要求
- ✅ 必填列不能为空
- ✅ 日期格式必须为 yyyy-MM-dd

View File

@ -1,324 +0,0 @@
# UPDATE 慢查询问题修复
## ❌ **问题现象**
导入100条数据需要很长时间每条UPDATE需要1.5-1.6秒。
### **日志显示**
```
slow sql 1608 millis. update sys_user
slow sql 1577 millis. update sys_user
slow sql 1160 millis. update sys_user
```
**预期时间:** <100ms
**实际时间:** 1500ms+
**慢了15倍**
---
## 🔍 **问题原因**
### **原因1UPDATE SQL 字段重复设置(最严重)**
`SysUserMapper.xml``updateUser` 方法中,字段设置了**两次**
```xml
<!-- 第一次340-350行 -->
<if test="prisonName != null and prisonName != ''">prison_name = #{prisonName},</if>
<if test="prisonArea != null and prisonArea != ''">prison_area = #{prisonArea},</if>
<if test="ethnicity != null and ethnicity != ''">ethnicity = #{ethnicity},</if>
<if test="educationLevel != null and educationLevel != ''">education_level = #{educationLevel},</if>
<if test="crimeName != null and crimeName != ''">crime_name = #{crimeName},</if>
<if test="sentenceTerm != null">sentence_term = #{sentenceTerm},</if>
<if test="sentenceStartDate != null">sentence_start_date = #{sentenceStartDate},</if>
<if test="sentenceEndDate != null">sentence_end_date = #{sentenceEndDate},</if>
<if test="entryDate != null">entry_date = #{entryDate},</if>
<if test="studentStatus != null and studentStatus != ''">student_status = #{studentStatus},</if>
<!-- 第二次351-360行重复 -->
<if test="prisonName != null">prison_name = #{prisonName,jdbcType=VARCHAR},</if>
<if test="prisonArea != null">prison_area = #{prisonArea,jdbcType=VARCHAR},</if>
<if test="ethnicity != null">ethnicity = #{ethnicity,jdbcType=VARCHAR},</if>
<if test="educationLevel != null">education_level = #{educationLevel,jdbcType=VARCHAR},</if>
<if test="crimeName != null">crime_name = #{crimeName,jdbcType=VARCHAR},</if>
<if test="sentenceTerm != null">sentence_term = #{sentenceTerm,jdbcType=INTEGER},</if>
<if test="sentenceStartDate != null">sentence_start_date = #{sentenceStartDate,jdbcType=DATE},</if>
<if test="sentenceEndDate != null">sentence_end_date = #{sentenceEndDate,jdbcType=DATE},</if>
<if test="entryDate != null">entry_date = #{entryDate,jdbcType=DATE},</if>
<if test="studentStatus != null and studentStatus != ''">student_status = #{studentStatus},</if>
```
**结果:** 每个字段都被设置了两次导致UPDATE执行缓慢
---
### **原因2数据库索引未创建**
执行 `optimize_import_performance_safe.sql` 添加索引,提升查询速度。
---
### **原因3远程数据库延迟**
数据库在远程服务器 `101.35.101.159`,存在网络延迟。
---
## ✅ **解决方案**
### **修复1删除重复字段已完成**
**文件:** `SysUserMapper.xml`
**修改:** 删除 351-360 行的重复字段设置
**修改前:**
```xml
<if test="studentStatus != null and studentStatus != ''">student_status = #{studentStatus},</if>
<if test="prisonName != null">prison_name = #{prisonName,jdbcType=VARCHAR},</if>
<if test="prisonArea != null">prison_area = #{prisonArea,jdbcType=VARCHAR},</if>
<!-- ...更多重复字段... -->
<if test="studentStatus != null and studentStatus != ''">student_status = #{studentStatus},</if>
update_time = sysdate()
```
**修改后:**
```xml
<if test="studentStatus != null and studentStatus != ''">student_status = #{studentStatus},</if>
update_time = sysdate()
```
---
### **修复2执行数据库优化SQL**
如果还没执行,请执行:
```sql
-- 文件log/Sql/optimize_import_performance_safe.sql
-- 主要添加以下索引:
ALTER TABLE sys_user ADD INDEX idx_user_name (user_name);
ALTER TABLE sys_user ADD INDEX idx_nick_name (nick_name);
ALTER TABLE student_class ADD INDEX idx_student_id (student_id);
ALTER TABLE student_class ADD INDEX idx_student_status (student_id, status);
```
---
## 🚀 **部署步骤**
### **步骤1重新编译**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **步骤2重启服务**
停止旧服务,启动新服务
### **步骤3执行数据库优化如果还没执行**
在 Navicat 中连接到数据库 `study`,执行:
```
log/Sql/optimize_import_performance_safe.sql
```
### **步骤4测试导入**
1. 导入100条数据
2. 观察日志查看UPDATE时间
3. 确认无 "slow sql" 警告
---
## 📊 **性能对比**
### **修复前**
| 指标 | 数值 |
|------|------|
| 每条UPDATE时间 | 1500ms+ |
| 100条总时间 | 2-3分钟 |
| 慢查询警告 | 每条都有 |
| SQL字段 | 重复设置 |
### **修复后(预期)**
| 指标 | 数值 |
|------|------|
| 每条UPDATE时间 | <100ms |
| 100条总时间 | 10-30秒 |
| 慢查询警告 | 无 |
| SQL字段 | 正常 |
**性能提升15倍以上**
---
## 🧪 **验证方法**
### **测试1查看SQL执行时间**
导入时查看日志,应该看到:
```
DEBUG c.d.s.m.S.updateUser - ==> Preparing: update sys_user SET ...
DEBUG c.d.s.m.S.updateUser - ==> Parameters: ...
DEBUG c.d.s.m.S.updateUser - <== Updates: 1
```
**不应该有** "slow sql" 警告!
---
### **测试2导入速度测试**
```bash
# 导入100条数据
开始时间: 15:30:00
结束时间: 15:30:15
总耗时: 15秒 ✅
# 修复前需要 2-3 分钟
# 修复后只需 15-30 秒
```
---
### **测试3验证SQL正确性**
在日志中查看UPDATE SQL应该是
```sql
update sys_user
SET dept_id = ?,
user_name = ?,
...
prison_name = ?, -- 只出现一次
prison_area = ?, -- 只出现一次
...
update_time = sysdate()
where user_id = ?
```
每个字段**只出现一次**
---
## ⚠️ **注意事项**
### **1. 必须重新编译**
Mapper 文件修改后必须重新编译,否则不生效!
```bash
mvn clean package -DskipTests
```
### **2. 必须重启服务**
编译完成后必须重启服务新的Mapper才会加载。
### **3. 数据库索引**
如果没有执行索引SQL性能提升不明显。
建议执行 `optimize_import_performance_safe.sql`
### **4. 远程数据库延迟**
即使优化后远程数据库仍有网络延迟约50-100ms/条)。
如果需要更快,考虑:
- 使用本地数据库测试
- 批量操作
- 异步处理
---
## 🔍 **问题根源分析**
### **为什么会有重复字段?**
可能的原因:
1. 代码合并时重复添加
2. 不同开发者添加相同字段
3. 复制粘贴时未删除旧代码
### **为什么慢?**
1. **重复字段:** 每个字段设置两次,数据库执行时间翻倍
2. **缺少索引:** 没有索引的查询需要全表扫描
3. **远程延迟:** 网络往返时间增加
---
## 💡 **优化建议**
### **1. 代码审查**
定期检查Mapper文件避免重复字段
```bash
# 查找重复的SET语句
grep -A 50 "<update id" mapper/*.xml | grep "SET"
```
### **2. 性能监控**
启用慢查询日志,及时发现性能问题:
```yaml
# application.yml
logging:
level:
com.ddnai.system.mapper: DEBUG
```
### **3. 批量操作**
对于大量数据导入,使用批量操作:
```java
// 批量INSERT
mapper.batchInsert(userList);
// 批量UPDATE
mapper.batchUpdate(userList);
```
---
## 📋 **检查清单**
- [x] 删除重复字段
- [ ] 重新编译项目
- [ ] 重启服务
- [ ] 执行数据库优化SQL
- [ ] 测试导入100条数据
- [ ] 验证无慢查询警告
- [ ] 确认导入时间正常
---
## ✅ **总结**
### **核心问题**
UPDATE SQL 中字段重复设置两次,导致执行缓慢。
### **解决方法**
删除重复的字段设置351-360行
### **预期效果**
- UPDATE时间从 1500ms 降到 <100ms
- 100条导入从 2-3分钟 降到 15-30秒
- **性能提升15倍以上**
### **关键步骤**
1. ✅ 修改Mapper文件
2. ⚠️ 重新编译
3. ⚠️ 重启服务
4. ⚠️ 执行索引SQL如果还没执行
---
**现在重新编译并重启服务,性能应该有显著提升!** 🚀

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
const axios = require('axios');
// 配置
const API_BASE_URL = 'http://192.168.1.164:30091';
const API_BASE_URL = 'http://192.168.137.1:30091';
const TOKEN = 'YOUR_ADMIN_TOKEN_HERE'; // 需要替换为管理员token
// 需要更新的课件列表

View File

@ -1,300 +0,0 @@
# 云端打包缓存清除完整步骤
## ❌ **问题现状**
云端打包仍然报错:`Cannot find a parameter with this name: errorMsg`
**原因:** 云端使用了缓存的旧代码version 1.0.9没有使用最新的代码version 1.0.12
---
## ✅ **已完成的修改**
1. ✅ **插件版本升级**
- 从 `1.0.9``1.0.12`
- 文件:`uni_modules/xwq-speech-to-text/package.json`
2. ✅ **代码已修复**
- 所有 `error` 已改为 `errorMsg`
- 文件:`uni_modules/xwq-speech-to-text/utssdk/app-android/index.uts`
3. ✅ **添加版本标记**
- 在代码开头添加了 `VERSION: 1.0.12` 注释
---
## 🚀 **彻底清除云端缓存的方法**
### **方法1使用自定义基座推荐** ⭐⭐⭐⭐⭐
自定义基座不使用云端编译,直接在本地编译插件。
#### **步骤:**
1. **在 HBuilderX 中:**
```
运行 → 运行到手机或模拟器 → 制作自定义调试基座
```
2. **选择证书:**
- 使用云端证书或
- 使用自有证书
3. **等待编译完成**(约 5-10 分钟)
4. **安装自定义基座到手机**
5. **使用自定义基座运行**
```
运行 → 运行到手机或模拟器 → 运行到自定义基座
```
**优点:**
- ✅ 完全避免云端缓存问题
- ✅ 编译速度更可控
- ✅ 可以重复使用
---
### **方法2修改插件 ID强制清除缓存** ⭐⭐⭐⭐
修改插件 ID云端会认为这是全新的插件。
#### **步骤:**
1. **修改 `package.json`**
```json
{
"id": "xwq-speech-to-text-fixed", // 改名!
"version": "1.0.12"
}
```
2. **保存所有文件**
```
Ctrl + Shift + S
```
3. **重新云端打包**
**注意:** 打包成功后,记得改回原来的 ID
---
### **方法3联系 DCloud 清理服务器缓存** ⭐⭐⭐
如果以上方法都不行,联系官方:
1. **访问 DCloud 论坛**
```
https://ask.dcloud.net.cn/
```
2. **发帖说明情况**
```
标题:云端打包缓存未更新,请求清理
内容:
- 项目 ID: __UNI__08E0C13
- 插件: xwq-speech-to-text
- 版本: 1.0.12
- 问题: 云端仍使用旧代码编译
```
3. **等待官方处理**(通常 1-2 工作日)
---
### **方法4等待云端缓存过期** ⭐⭐
DCloud 云端缓存通常会在 24-48 小时后自动过期。
**操作:**
- 明天或后天再尝试打包
- 不修改任何代码
- 直接打包
---
### **方法5删除并重新导入插件** ⭐⭐
完全删除插件,然后重新导入。
#### **步骤:**
1. **备份插件目录**
```
复制整个 uni_modules/xwq-speech-to-text 文件夹到桌面
```
2. **在 HBuilderX 中删除插件**
```
右键 uni_modules/xwq-speech-to-text → 删除
```
3. **重启 HBuilderX**
4. **重新导入插件**
```
将备份的 xwq-speech-to-text 文件夹
复制回 uni_modules/ 目录
```
5. **重新打包**
---
## 📋 **推荐执行顺序**
### **立即可以尝试方法2修改插件ID**
1. **修改 `package.json` 中的 `id`**
```json
"id": "xwq-speech-to-text-v2"
```
2. **保存所有文件**
3. **云端打包**
4. **如果成功,改回原 ID**
```json
"id": "xwq-speech-to-text"
```
---
### **如果方法2不行使用方法1自定义基座**
这是最可靠的方法,完全避免云端缓存问题。
---
## 🎯 **验证是否使用新代码**
### **检查打包日志**
打包时查看日志,应该显示:
```
✅ 正确:
[HBuilder] 编译插件 xwq-speech-to-text (1.0.12)
[HBuilder] BUILD SUCCESSFUL
❌ 错误(仍使用旧代码):
[HBuilder] 编译插件 xwq-speech-to-text (1.0.9)
[HBuilder] Cannot find a parameter with this name: errorMsg
```
---
## ⚠️ **注意事项**
### **1. 保存所有文件**
修改后必须保存:
```
Ctrl + Shift + S (保存所有)
```
### **2. 不要同时打包多个版本**
如果有打包任务在队列中,等待完成或取消后再打包。
### **3. 检查网络**
确保 HBuilderX 可以正常连接云端服务器。
### **4. 避免频繁打包**
如果短时间内多次打包失败,可能被限流,等待 10-15 分钟再试。
---
## 🔧 **临时解决方案:本地打包(如果有 Android Studio**
如果您有 Android Studio 和 Android SDK
### **步骤:**
1. **导出 Android 项目**
```
HBuilderX → 发行 → 原生 App-本地打包 → 生成本地打包App资源
```
2. **用 Android Studio 打开项目**
```
打开 unpackage/resources/__UNI__08E0C13/android/
```
3. **编译 APK**
```
Build → Build Bundle(s) / APK(s) → Build APK(s)
```
**优点:**
- ✅ 完全本地编译,无缓存问题
- ✅ 可以实时查看编译过程
**缺点:**
- ❌ 需要安装 Android Studio
- ❌ 需要配置 Android SDK
- ❌ 首次配置较复杂
---
## 📊 **各方法对比**
| 方法 | 成功率 | 时间 | 难度 | 推荐度 |
|------|--------|------|------|--------|
| 自定义基座 | ⭐⭐⭐⭐⭐ | 10分钟 | 简单 | ⭐⭐⭐⭐⭐ |
| 修改插件ID | ⭐⭐⭐⭐ | 5分钟 | 简单 | ⭐⭐⭐⭐ |
| 联系官方 | ⭐⭐⭐⭐⭐ | 1-2天 | 简单 | ⭐⭐⭐ |
| 等待过期 | ⭐⭐⭐ | 1-2天 | 简单 | ⭐⭐ |
| 删除重导 | ⭐⭐⭐ | 10分钟 | 简单 | ⭐⭐⭐ |
| 本地打包 | ⭐⭐⭐⭐⭐ | 30分钟 | 复杂 | ⭐⭐ |
---
## ✅ **总结**
### **立即尝试**
1. **方法2修改插件 ID** → 快速测试
2. **方法1自定义基座** → 最可靠
### **如果以上都不行**
3. **方法3联系官方** → 等待官方处理
---
## 📞 **获取帮助**
### **DCloud 官方支持**
- **论坛:** https://ask.dcloud.net.cn/
- **文档:** https://uniapp.dcloud.net.cn/
- **QQ群** 见官网
### **搜索类似问题**
在 DCloud 论坛搜索:
```
"云端打包缓存"
"Cannot find a parameter"
"插件打包失败"
```
---
## 🎯 **下一步操作**
1. **保存所有修改**(已完成 ✅)
2. **选择一个方法执行**
3. **打包并查看日志**
4. **如果成功,测试语音功能**
5. **如果失败,尝试下一个方法**
---
**现在建议您先尝试方法2修改插件ID如果不行再使用方法1自定义基座** 🚀

View File

@ -1,320 +0,0 @@
# 云端打包缓存问题解决方案
## ❌ **问题现象**
云端打包失败,错误信息:
```
Cannot find a parameter with this name: errorMsg
at index.kt:119:95, 123:95, 239:75, 261:71
```
## 🔍 **问题原因**
**本地代码已正确修改为 `errorMsg`,但云端打包使用了缓存的旧代码(仍然是 `error`)。**
---
## ✅ **解决方案**
### **已执行的修复**
1. ✅ **升级插件版本号**
- 从 `1.0.9``1.0.10`
- 文件:`uni_modules/xwq-speech-to-text/package.json`
2. ✅ **添加时间戳注释**
- 确保云端识别代码已更新
- 文件:`uni_modules/xwq-speech-to-text/utssdk/app-android/index.uts`
---
## 🚀 **重新打包步骤**
### **步骤1保存所有文件重要**
在 HBuilderX 中:
```
Ctrl + Shift + S (保存所有文件)
```
确保所有修改都已保存!
---
### **步骤2清理本地缓存**
在 HBuilderX 中:
```
运行 → 清理项目缓存
```
---
### **步骤3清理云端缓存关键**
**方法A通过菜单推荐**
```
右键点击项目 fronted_uniapp
→ 清理云端打包缓存
→ 等待清理完成
```
**方法B通过打包界面**
```
发行 → 原生 App-云打包
→ 在打包界面找到 "清理缓存" 选项
→ 点击清理
```
**方法C手动删除云端缓存如果上述方法无效**
在打包前勾选:
```
☑ 清除云端打包缓存
```
---
### **步骤4重新云端打包**
```
发行 → 原生 App-云打包
选择Android
证书:云端证书或自有证书
等待打包(约 5-10 分钟)
```
---
### **步骤5查看打包日志**
重点确认:
```
✅ 使用插件版本1.0.10(新版本)
✅ 编译 Kotlin 代码时无错误
✅ 找到参数 errorMsg不再报错
```
---
## 📋 **验证修改是否正确**
### **检查1本地文件确认**
打开文件查看:
```
文件uni_modules/xwq-speech-to-text/utssdk/app-android/index.uts
```
应该看到:
```typescript
// 第 73-74 行
errorMsg: '识别错误: ' + (e.message ?? '未知错误')
// 第 85 行
errorMsg: '识别超时'
// 第 290 行
errorMsg: '模型未初始化,请重新加载'
// 第 322 行
errorMsg: '语音识别启动失败: ' + (e.message ?? '未知错误')
```
**绝对不应该看到 `error:`**
---
### **检查2版本号确认**
打开文件:
```
文件uni_modules/xwq-speech-to-text/package.json
```
应该看到:
```json
"version": "1.0.10"
```
---
### **检查3前端代码确认**
打开文件:
```
文件pages/speech/speech.vue
```
在 517-522 行应该看到:
```javascript
res.data?.errorMsg // 不是 res.data?.error
```
---
## ⚠️ **如果仍然失败**
### **检查1是否所有文件都保存了**
```
Ctrl + Shift + S (保存所有)
```
---
### **检查2是否清理了云端缓存**
打包日志中应该显示:
```
云端缓存已清理
```
如果没有重新执行步骤3。
---
### **检查3查看完整错误日志**
如果还是报错,点击错误日志链接:
```
错误日志: https://app.liuyingyong.cn/build/errorLog/...
```
查看完整的编译错误信息。
---
### **检查4手动验证编译后的代码**
如果有本地 Android 环境,可以查看:
```
unpackage/cache/.uts/...index.kt
```
确认编译后的 Kotlin 代码是否正确。
---
## 🔍 **其他可能的问题**
### **问题1HBuilderX 版本过旧**
**解决:** 更新到最新版本
```
帮助 → 检查更新
```
---
### **问题2插件配置问题**
检查文件:
```
uni_modules/xwq-speech-to-text/utssdk/app-android/config.json
```
确认配置正确。
---
### **问题3Gradle 缓存问题**
如果云端打包持续失败,可能是 Gradle 缓存问题。
**解决:** 联系 DCloud 技术支持清理服务器端缓存。
---
## 📊 **打包预期时间**
- **清理缓存:** 1-2 分钟
- **云端编译:** 5-10 分钟
- **总计:** 约 10-15 分钟
**注意:** 如果使用了原生插件且需要下载三方库,可能需要 **3-30 分钟**
---
## ✅ **成功标志**
打包成功后,应该看到:
```
[HBuilder] 打包成功
[HBuilder] 下载地址: https://...xxx.apk
```
下载并安装 APK测试语音识别功能正常。
---
## 🎯 **快速检查清单**
- [ ] 本地文件已修改errorMsg不是 error
- [ ] 插件版本已升级1.0.10
- [ ] 所有文件已保存Ctrl+Shift+S
- [ ] 本地缓存已清理
- [ ] **云端缓存已清理(关键!)**
- [ ] 重新云端打包
- [ ] 等待 10-15 分钟
- [ ] 查看打包日志确认成功
- [ ] 下载安装 APK
- [ ] 测试功能正常
---
## 💡 **预防措施**
### **以后修改插件代码后:**
1. ✅ 升级插件版本号(每次修改都升级)
2. ✅ 保存所有文件
3. ✅ 清理本地缓存
4. ✅ **清理云端缓存**
5. ✅ 重新打包
**不要跳过清理云端缓存这一步!**
---
## 📞 **获取帮助**
如果按照上述步骤仍然失败:
1. **查看完整错误日志**
```
点击打包失败后的错误日志链接
```
2. **联系 DCloud 技术支持**
```
DCloud 论坛https://ask.dcloud.net.cn/
官方 QQ 群:见官网
```
3. **检查插件是否有更新**
```
插件市场https://ext.dcloud.net.cn/
搜索xwq-speech-to-text
```
---
## ✅ **总结**
### **核心问题**
云端打包使用了缓存的旧代码。
### **关键操作**
**清理云端缓存** + 升级版本号
### **预期结果**
打包成功,语音识别功能正常。
---
**现在请按照步骤重新打包,应该能成功!** 🚀
**记住:每次修改插件代码后,一定要清理云端缓存!** ⚠️

View File

@ -1,296 +0,0 @@
# 修复APP学习进度显示问题
## 🎯 **问题描述**
**现象:**
- APP显示学习进度**14.29%**
- HBuilder控制台输出**18%**
- 后台管理界面显示:**18%**
**问题:** APP端显示的进度与后端计算的进度不一致
---
## 🔍 **问题原因**
### **根本原因:前端重新计算了进度**
**APP端pages/course/detail.vue**
```javascript
// 前端重新计算进度第658-789行
calculateCourseProgress() {
// ... 复杂的判断逻辑
this.courseProgress = Math.round((completedCount / totalCount) * 100)
// 结果14.29%
}
```
**后端StudyLearningRecordServiceImpl.java**
```java
// 后端已经计算好了进度
private BigDecimal calculateCourseProgress(Long studentId, Long courseId) {
// 已完成课件数 / 总课件数 * 100
return progress; // 18.00
}
```
**问题:**
1. ✅ 后端返回了准确的进度值:`learningRecord.progress = 18.00`
2. ❌ APP端没有使用后端的值而是重新计算
3. ❌ 前端和后端的判断标准不完全一致
4. ❌ 导致显示差异14.29% vs 18%
---
## ✅ **修复内容**
### **文件:** `fronted_uniapp/pages/course/detail.vue`
### **修改1loadLearningRecord方法第635行**
**修改前:**
```javascript
async loadLearningRecord() {
const record = records.find(r => r.courseId === this.courseId)
if (record) {
this.learningRecord = record
// ❌ 没有设置 courseProgress
}
}
```
**修改后:**
```javascript
async loadLearningRecord() {
const record = records.find(r => r.courseId === this.courseId)
if (record) {
this.learningRecord = record
// ✅ 直接使用后端返回的进度值
this.courseProgress = Math.round(record.progress || 0)
console.log('[课程学习] ✅ 学习记录已加载:', {
progress: record.progress,
courseProgress: this.courseProgress
})
} else {
this.courseProgress = 0
}
}
```
---
### **修改2calculateCourseProgress方法第665-789行**
**修改前:**
```javascript
calculateCourseProgress() {
// ❌ 前端重新计算进度120行复杂逻辑
const totalCount = this.coursewareList.length
let completedCount = 0
// ... 遍历课件,判断是否完成
this.courseProgress = Math.round((completedCount / totalCount) * 100)
// 结果14.29%
}
```
**修改后:**
```javascript
calculateCourseProgress() {
// ✅ 不再前端计算直接使用后端返回的progress
console.log('[课程学习] 使用后端计算的进度:', this.courseProgress + '%')
return
/* 已废弃的前端计算逻辑(已注释)
... 120行代码 ...
*/
}
```
---
## 📊 **效果对比**
### **修复前:**
| 位置 | 显示进度 | 来源 |
|------|---------|------|
| APP端 | 14.29% | 前端重新计算 ❌ |
| HBuilder控制台 | 18% | 后端计算 ✅ |
| 后台管理 | 18% | 后端计算 ✅ |
**问题:** APP显示不一致
---
### **修复后:**
| 位置 | 显示进度 | 来源 |
|------|---------|------|
| APP端 | 18% | 后端计算 ✅ |
| HBuilder控制台 | 18% | 后端计算 ✅ |
| 后台管理 | 18% | 后端计算 ✅ |
**结果:** 所有端显示一致 ✅
---
## 🔍 **为什么会出现差异?**
### **前端计算逻辑(已废弃):**
```javascript
// 视频完成标准(前端)
isCompleted = videoPosition >= realDuration * 0.9 // 观看90%即算完成
// 或者特殊判断
if (videoPosition === maxPositionFromHistory && videoPosition >= 3) {
isCompleted = true // 播放到最大位置即算完成
}
```
### **后端计算逻辑(正确):**
```java
// 视频完成标准(后端)
if (duration <= 0 && videoPosition >= 3) {
isCompleted = true; // 无配置时长播放3秒即完成
} else if (realDuration > 0 && videoPosition >= realDuration * 0.9) {
isCompleted = true; // 观看90%即算完成
}
```
**差异原因:**
1. 前端使用 `maxPositionFromHistory`(历史最大播放位置)判断
2. 后端使用 `videoPosition`(当前播放位置)和配置的 `duration` 判断
3. 判断标准略有不同,导致完成状态判断不一致
4. 最终进度计算结果不同
---
## ✅ **解决方案的优势**
### **1. 统一数据源**
- ✅ 所有端都使用后端计算的进度
- ✅ 避免前后端不一致
### **2. 减少重复计算**
- ✅ 前端不需要重新计算复杂的进度逻辑
- ✅ 减少代码复杂度
- ✅ 提高性能
### **3. 易于维护**
- ✅ 进度计算逻辑只在后端维护
- ✅ 前端只负责显示
- ✅ 后续修改只需改后端
### **4. 数据准确**
- ✅ 后端有完整的数据库数据
- ✅ 计算更精确
- ✅ 前端可能数据不完整
---
## 🧪 **测试验证**
### **测试步骤:**
1. **保存修改后的文件**
```
fronted_uniapp/pages/course/detail.vue
```
2. **重新运行APP**
```
HBuilderX → 运行 → 运行到手机或模拟器
```
3. **查看学习记录**
```
打开APP → 我的 → 学习记录
```
4. **验证进度显示**
```
检查显示的进度是否与后台一致
```
### **预期结果:**
```
✅ APP显示18%
✅ 控制台输出18%
✅ 后台管理18%
✅ 三者一致
```
---
## 📝 **补充说明**
### **关于 calculateCourseProgress 方法**
虽然该方法已被废弃(改为直接使用后端进度),但代码中仍有多处调用:
```javascript
// 这些调用仍然存在,但方法内部已改为直接返回
this.calculateCourseProgress() // 约7处调用
```
**为什么不删除调用?**
1. 保持代码结构完整
2. 避免影响其他逻辑
3. 方法内部已改为空操作直接return
4. 不会产生副作用
**如果需要清理:**
```javascript
// 可以删除所有 calculateCourseProgress() 调用
// 但必须确保 loadLearningRecord() 在所有需要显示进度的地方都被调用
```
---
## 🎯 **核心改变总结**
| 项目 | 修改前 | 修改后 |
|------|--------|--------|
| **进度来源** | 前端计算 | 后端返回 ✅ |
| **计算位置** | calculateCourseProgress | loadLearningRecord ✅ |
| **显示值** | 14.29% | 18% ✅ |
| **一致性** | 不一致 | 一致 ✅ |
| **代码复杂度** | 高120行计算逻辑| 低1行赋值✅ |
---
## 📞 **验证清单**
- [ ] 文件已保存
- [ ] APP已重新运行
- [ ] 查看学习记录页面
- [ ] 查看课程详情页面
- [ ] 查看首页课程列表
- [ ] 检查进度是否为18%
- [ ] 检查是否与后台一致
- [ ] 检查控制台日志
---
**修复完成时间:** 2025-12-05 18:00
**修复文件:**
- `fronted_uniapp/pages/course/detail.vue`
**影响范围:**
- 课程详情页面的学习进度显示
- 学习记录页面的进度显示
- 首页课程列表的进度显示
**后续建议:**
- 其他页面也应检查是否使用了前端计算进度
- 统一使用后端返回的 `progress` 字段
---
**总结APP端学习进度显示问题已修复现在APP、控制台、后台三者显示一致。** ✅

View File

@ -1,319 +0,0 @@
# 修复答案重复显示和成绩排序问题
## 🎯 **问题描述**
### **问题1试卷详情答案重复显示**
**现象:**
```
我的答案A. 阿斯蒂芬地方
正确答案B. B. 阿斯蒂芬都是放到 ❌ 重复了
```
**应该是:**
```
正确答案B. 阿斯蒂芬都是放到
```
---
### **问题2成绩管理排序**
**需求:** 按提交时间倒序排列(从最近到最远)
---
## ✅ **修复内容**
### **1. 修复答案格式化逻辑**
**文件:** `Study-Vue-redis/ry-study-admin/src/main/java/com/ddnai/web/controller/study/StudyScoreController.java`
**修改位置:** `formatAnswerWithContent` 方法第406-445行
**修复说明:**
- 检测答案是否已包含完整格式(如 "B. 选项内容"
- 如果已包含先提取纯字母B再重新格式化
- 避免重复格式化导致 "B. B. 选项内容"
**核心代码:**
```java
// 如果答案已经包含".",说明可能已经是完整格式(如"B. 选项内容"
// 需要提取纯字母,避免重复格式化导致"B. B. 选项内容"
if (trimmedAnswer.contains("."))
{
// 提取纯字母答案(去除". "后面的内容)
if (trimmedAnswer.contains(","))
{
// 多选题:提取每个选项的字母部分
String[] parts = trimmedAnswer.split(",");
java.util.List<String> letters = new java.util.ArrayList<>();
for (String part : parts)
{
String p = part.trim();
if (p.contains("."))
{
String letter = p.substring(0, p.indexOf(".")).trim();
if (letter.matches("[A-Fa-f]"))
{
letters.add(letter);
}
}
}
if (!letters.isEmpty())
{
trimmedAnswer = String.join(",", letters);
}
}
else
{
// 单选题:提取字母部分
String letter = trimmedAnswer.substring(0, trimmedAnswer.indexOf(".")).trim();
if (letter.matches("[A-Fa-f]"))
{
trimmedAnswer = letter;
}
}
}
```
---
### **2. 修复成绩排序逻辑**
**文件:** `Study-Vue-redis/ry-study-system/src/main/resources/mapper/study/StudyScoreMapper.xml`
**修改位置:** `selectScoreList` 查询的 `order by` 子句第64行
**修改前:**
```sql
order by
IFNULL(s.exam_name, '') asc, -- 考试名称
IFNULL(s.subject_name, '') asc, -- 科目名称
s.obtained_score desc, -- 得分
s.submit_time desc -- 提交时间
```
**修改后:**
```sql
order by s.submit_time desc -- 只按提交时间倒序
```
**说明:**
- 移除了考试名称、科目名称、得分等排序字段
- 直接按提交时间倒序DESC
- 最新的成绩记录显示在最前面
---
### **3. 清理数据库错误数据**
**文件:** `log/Sql/fix_answer_format.sql`
**执行的SQL**
```sql
-- 修复单选题(将 "B. 选项内容" 改为 "B"
UPDATE question
SET correct_answer = SUBSTRING(correct_answer, 1, LOCATE('.', correct_answer) - 1)
WHERE correct_answer LIKE '%. %'
AND correct_answer NOT LIKE '%,%';
-- 修复多选题(将 "A. 选项1, B. 选项2" 改为 "A,B,C"
UPDATE question
SET correct_answer = 'A,B,C'
WHERE id = 184;
```
**修复结果:**
```
ID 37: "A. [ ]1" → "A" ✅
ID 164: "A. 第三方" → "A" ✅
ID 166: "B. ASDFADSFAS" → "B" ✅
ID 184: "A. 选项1, B. 选项2, C. 选项3" → "A,B,C" ✅
```
---
## 📊 **测试验证**
### **1. 答案重复问题**
**测试步骤:**
1. 重启后端服务
2. 打开APP或管理后台
3. 查看试卷详情
4. 检查正确答案显示
**预期结果:**
```
✅ 正确答案B. 阿斯蒂芬都是放到
❌ 不再是B. B. 阿斯蒂芬都是放到
```
---
### **2. 成绩排序**
**测试步骤:**
1. 打开管理后台 → 学习管理 → 成绩管理
2. 查看成绩列表
**预期结果:**
```
序号 提交时间 考试名称
1 2025-12-05 17:45 Test3 ✅ 最新
2 2025-12-05 14:02 Test2
3 2025-12-05 13:43 Test
4 2025-12-03 12:01 汉字训练 ✅ 最旧
```
---
## 🔍 **根本原因分析**
### **答案重复问题的根本原因:**
1. **数据录入错误**
- 管理员在后台创建题目时
- 正确答案字段输入了完整格式:"B. 选项内容"
- 应该只输入字母:"B"
2. **代码未做防护**
- `formatAnswerWithContent` 方法未考虑答案已格式化的情况
- 直接对已格式化的答案再次格式化
- 导致重复:"B. " + "B. 选项内容" = "B. B. 选项内容"
### **解决方案:**
1. ✅ **修复代码**(已完成)
- 检测并提取纯字母
- 再重新格式化
2. ✅ **清理数据**(已完成)
- 将错误数据修正为纯字母格式
3. 🔔 **未来预防**
- 前端表单验证正确答案只允许输入字母A、B、C等
- 后端数据验证:保存前检查格式
---
## 📝 **后续优化建议**
### **1. 前端表单优化**
**位置:** 题目编辑页面
**建议:**
```vue
<!-- 正确答案输入框 -->
<el-form-item label="正确答案" required>
<el-select v-model="form.correctAnswer" placeholder="请选择">
<el-option label="A" value="A" />
<el-option label="B" value="B" />
<el-option label="C" value="C" />
<el-option label="D" value="D" />
</el-select>
<span class="form-tip">只需选择字母,系统会自动关联选项内容</span>
</el-form-item>
```
---
### **2. 数据验证**
**位置:** 题目保存接口
**建议:**
```java
// 验证正确答案格式
if ("single".equals(question.getQuestionType()) || "multiple".equals(question.getQuestionType()))
{
String correctAnswer = question.getCorrectAnswer();
if (correctAnswer.contains("."))
{
// 如果包含".",提取纯字母
correctAnswer = extractLettersOnly(correctAnswer);
question.setCorrectAnswer(correctAnswer);
}
// 验证格式:单选题应该是单个字母,多选题应该是逗号分隔的字母
if (!correctAnswer.matches("^[A-F](,[A-F])*$"))
{
return error("正确答案格式错误应为字母A 或 A,B,C");
}
}
```
---
## ✅ **完成清单**
- [x] 修复 `formatAnswerWithContent` 方法
- [x] 修改成绩排序逻辑
- [x] 清理数据库错误数据
- [x] 验证数据修复结果
- [ ] 重启后端服务(需手动)
- [ ] 测试试卷详情页面(需手动)
- [ ] 测试成绩管理排序(需手动)
---
## 🚀 **部署步骤**
### **1. 编译后端**
在IDEA中
```
1. 打开 ry-study-admin 模块
2. Maven → clean → compile
3. 运行 RyStudyApplication
```
### **2. 验证修复**
**验证1答案重复问题**
```
1. 打开APP
2. 进入"我的考试"
3. 选择Test3
4. 查看第1题的正确答案
5. ✅ 应显示:"B. 阿斯蒂芬都是放到"
```
**验证2成绩排序**
```
1. 打开管理后台
2. 学习管理 → 成绩管理
3. 查看列表排序
4. ✅ 最新的成绩在最上面
```
---
## 📞 **问题反馈**
如果修复后仍有问题,请检查:
1. **答案仍然重复?**
- 检查数据库中的 `question.correct_answer` 字段
- 确认是否为纯字母格式A、B、A,B,C
- 如不是,执行 `log/Sql/fix_answer_format.sql`
2. **排序未生效?**
- 检查 XML 文件是否已保存
- 确认后端服务是否已重启
- 检查数据库 `score.submit_time` 字段是否有值
---
**修复完成时间:** 2025-12-05 17:50
**修复文件:**
1. `StudyScoreController.java` - 答案格式化逻辑
2. `StudyScoreMapper.xml` - 成绩排序逻辑
3. `fix_answer_format.sql` - 数据清理脚本
**影响范围:**
- 试卷详情页面APP端和管理后台
- 成绩管理列表(管理后台)

View File

@ -1,262 +0,0 @@
# 前端UniApp完成情况分析
## 📊 总体完成度:约 70%
---
## ✅ 已完成功能
### 1. 基础功能模块 ✅
- ✅ **登录/注册** (`pages/login/login.vue`, `pages/register/register.vue`)
- 账号密码登录
- 用户注册
- Token管理
- 权限验证
- ✅ **首页** (`pages/index/index.vue`)
- 欢迎信息展示
- 快捷入口(我的课程、个人中心)
- ⚠️ 功能较简单,可扩展
- ✅ **个人中心** (`pages/profile/profile.vue`)
- 用户信息展示
- 学习记录入口
- 退出登录
- ⚠️ 功能入口较少,可添加更多快捷功能
### 2. 课程学习模块 ✅
- ✅ **课程列表** (`pages/course/list.vue`)
- 课程列表展示
- 学科筛选
- 状态筛选(进行中、未开始、已结束、已禁用)
- 学习进度显示
- 学习时长统计
- ✅ **课程详情** (`pages/course/detail.vue`)
- 视频播放(支持横竖屏切换)
- 图文课件查看PDF、图片、文本
- 学习进度实时上报
- 学习监控(截图上传)
- 进度条展示
- 学习时长统计
### 3. 学习记录模块 ✅
- ✅ **学习记录** (`pages/learning/record.vue`)
- 学习统计(总时长、学习次数、已完成课程)
- 学习记录列表
- 进度可视化
- 跳转到课程详情
### 4. 语音评测模块 ✅(最新完成)
- ✅ **语音评测** (`pages/voice/evaluation.vue`)
- 语音录制(含波形动画)
- 评测内容输入/编辑
- 音频上传和评测
- 评测结果展示(总分、准确度、流畅度、完整度、发音)
- 历史记录查看
- 评测详情跳转
- ✅ **评测详情** (`pages/voice/detail.vue`)
- 详细评测结果展示
- 音频播放
- 评测数据可视化
- JSON详情展示
---
## ❌ 缺失功能
### 1. 考核模块(考试功能)❌
**状态**:完全缺失
- ❌ 考试列表页面
- ❌ 考试详情页面
- ❌ 答题页面
- ❌ 考试倒计时
- ❌ 答题卡功能
- ❌ 提交答案功能
- ❌ 考试结果查看
**后端接口已存在**
- `/study/exam/{id}/questions` - 获取考试题目
- `/study/score/submit` - 提交答题结果
- `/study/score/my-scores` - 获取我的成绩
**需要创建**
- `pages/exam/list.vue` - 考试列表
- `pages/exam/detail.vue` - 考试详情/答题页面
- `pages/exam/result.vue` - 考试结果页面
- `api/study/exam.js` - 考试相关API
- `api/study/score.js` - 成绩相关API
### 2. 成绩管理模块 ❌
**状态**:完全缺失
- ❌ 我的成绩列表
- ❌ 成绩详情查看
- ❌ 成绩统计展示
- ❌ 错题回顾
**后端接口已存在**
- `/study/score/my-scores` - 获取我的成绩列表
- `/study/score/{id}` - 获取成绩详情(含答题详情)
**需要创建**
- `pages/score/list.vue` - 成绩列表
- `pages/score/detail.vue` - 成绩详情(含错题分析)
- `api/study/score.js` - 成绩相关API
### 3. 首页功能扩展 ⚠️
**当前状态**:功能较简单
**建议增强**
- 添加快捷入口:语音评测、我的考试、我的成绩
- 添加学习统计卡片(今日学习时长、本周学习进度等)
- 添加最近学习课程
- 添加待考试提醒
### 4. 个人中心功能扩展 ⚠️
**当前状态**:功能入口较少
**建议增强**
- 添加"语音评测"入口
- 添加"我的考试"入口
- 添加"我的成绩"入口
- 添加"学习统计"入口
- 添加"设置"功能
---
## 📋 完善思路
### 优先级1考核模块考试功能🔥
**重要性**:高 - 核心功能缺失
**实现步骤**
1. **创建API文件**
- `api/study/exam.js` - 考试相关API
- `api/study/score.js` - 成绩相关API
2. **创建考试列表页面**
- `pages/exam/list.vue`
- 显示可参加的考试列表
- 显示考试状态(未开始、进行中、已结束)
- 显示考试时间、时长、总分等信息
3. **创建考试答题页面**
- `pages/exam/detail.vue`
- 题目展示(单选、多选、判断、填空等)
- 答题卡功能
- 倒计时功能
- 提交答案功能
4. **创建考试结果页面**
- `pages/exam/result.vue`
- 显示总分、得分
- 显示答题详情
- 显示错题分析
5. **更新路由配置**
- 在 `pages.json` 中添加新页面路由
### 优先级2成绩管理模块 🔥
**重要性**:高 - 核心功能缺失
**实现步骤**
1. **创建成绩列表页面**
- `pages/score/list.vue`
- 显示所有考试成绩
- 按时间排序
- 显示考试名称、得分、总分、状态
2. **创建成绩详情页面**
- `pages/score/detail.vue`
- 显示成绩详情
- 显示答题详情(每题得分、正确答案、我的答案)
- 错题高亮显示
- 成绩分析(正确率、各题型得分等)
3. **更新API文件**
- 在 `api/study/score.js` 中添加成绩相关API调用
### 优先级3首页和个人中心功能扩展 ⚡
**重要性**:中 - 提升用户体验
**实现步骤**
1. **增强首页** (`pages/index/index.vue`)
- 添加学习统计卡片
- 添加快捷入口(语音评测、我的考试、我的成绩)
- 添加最近学习课程
- 添加待考试提醒
2. **增强个人中心** (`pages/profile/profile.vue`)
- 添加"语音评测"菜单项
- 添加"我的考试"菜单项
- 添加"我的成绩"菜单项
- 添加"学习统计"菜单项
### 优先级4其他优化 💡
**重要性**:低 - 锦上添花
**优化项**
1. 添加下拉刷新功能
2. 添加加载状态优化
3. 添加错误处理优化
4. 添加空状态优化
5. 添加网络异常处理
---
## 🎯 建议实施顺序
### 第一阶段核心功能补齐预计2-3天
1. ✅ 创建考试相关API文件
2. ✅ 创建考试列表页面
3. ✅ 创建考试答题页面
4. ✅ 创建考试结果页面
5. ✅ 创建成绩列表页面
6. ✅ 创建成绩详情页面
### 第二阶段功能增强预计1-2天
1. ✅ 增强首页功能
2. ✅ 增强个人中心功能
3. ✅ 添加导航入口
### 第三阶段优化完善预计1天
1. ✅ 添加下拉刷新
2. ✅ 优化加载状态
3. ✅ 优化错误处理
4. ✅ 优化用户体验
---
## 📝 技术要点
### 1. 考试答题页面关键技术
- **倒计时功能**:使用 `setInterval` 实现倒计时
- **答题卡**:使用 `uni.navigateTo` 实现题目跳转
- **答案保存**:本地存储临时答案,防止意外退出
- **自动提交**:倒计时结束时自动提交
### 2. 成绩详情页面关键技术
- **错题高亮**:根据 `isCorrect` 字段高亮显示
- **答题对比**:显示正确答案和我的答案对比
- **成绩分析**:计算各题型正确率、得分率等
### 3. API调用规范
- 统一使用 `request.js` 封装
- 统一错误处理
- 统一加载状态管理
---
## ✅ 确认清单
在开始实施前,请确认:
- [ ] 后端考试接口是否完整可用?
- [ ] 后端成绩接口是否完整可用?
- [ ] UI设计风格是否需要统一
- [ ] 是否需要添加权限控制?
- [ ] 是否需要添加数据缓存?
---
**最后更新时间**2025-01-XX

View File

@ -1,272 +0,0 @@
# 在线学习系统功能完成情况总结
## 概述
根据需求文档中的11个功能点对系统已完成和缺失功能进行总结。
---
## ✅ 已完成功能8/11
### 1. 后台课件上传 ✅
**状态**:已完成
- ✅ 支持图文课件上传PPT、PDF、Word、图片
- ✅ 支持视频课件上传MP4、AVI等
- ✅ 课件上传、编辑、删除功能
- ✅ 按学科分类管理(学科分类由管理员管理)
- ✅ 教师上传课件时需选择学科分类
- ⚠️ 视频文件处理FFmpeg转码待优化
**相关文件**
- 后端:`StudyCoursewareController`、`StudyCoursewareService`
- 前端:`ruoyi-ui/src/views/study/courseware/index.vue`
---
### 2. 课程发布 ✅
**状态**:已完成
- ✅ 后台可选择课程和指定学员/班级
- ✅ 学员可在App端查看被分配的课程
- ✅ 支持课程的开始时间和结束时间设置
- ⚠️ App端课程推送通知待完善
**相关文件**
- 后端:`StudyCourseAssignmentController`、`StudyCourseController`
- 前端:`ruoyi-ui/src/views/study/course/index.vue`
- UniApp`frontend-uniapp/src/pages/course/list.vue`
---
### 3. 学习屏幕监控 ✅
**状态**:已完成
- ✅ 学生App端将当前学习界面截图传输给服务器
- ✅ 后台可查看学生学习画面(历史记录)
- ✅ 支持查看历史学习记录
- ✅ 学习进度跟踪:
- ✅ 视频播放进度实时跟踪和记录
- ✅ 记录学习次数:每次学习都会记录
- ✅ 记录观看时间:累计观看时长
- ✅ 管理员和教师都可以查看学员的学习进度(教师只能查看自己管理的班级)
- ✅ 学习进度以百分比形式展示,显示已观看时长和总时长
- ⚠️ 实时监控WebSocket推送待优化
**相关文件**
- 后端:`StudyScreenMonitorController`、`StudyLearningRecordController`
- 前端:`ruoyi-ui/src/views/study/monitor/index.vue`
- UniApp`frontend-uniapp/src/utils/monitor.js`、`frontend-uniapp/src/utils/progressQueue.js`
---
### 4. 局域网部署 ✅
**状态**:已完成
- ✅ 支持局域网部署(无需外网)
- ✅ 服务器端口配置后端8081管理界面20002用户界面20003
- ✅ Nginx代理配置
- ✅ 数据库连接配置
---
### 5. 考核模块 ✅
**状态**:已完成
- ✅ 支持多种题型:单选题、多选题、判断题、填空题等
- ✅ 题库管理:题目录入、编辑、删除
- ✅ 自动组卷:根据规则自动生成试卷
- ✅ 考试发布:向指定学员/班级发布考试
- ✅ 考试规则设置:
- ✅ 教师可以自定义考试规则(考试时长、开始时间、结束时间等)
- ✅ 管理员可以限制考试是否允许重考
- ✅ 如果管理员限制不允许重考,则学员只能参加一次考试
- ✅ 自动评分:客观题自动评分
- ✅ 成绩单生成:自动生成考试成绩单
- ⚠️ AI自动评分目前使用简单评分逻辑待接入AI API
**相关文件**
- 后端:`StudyExamController`、`StudyQuestionBankController`、`StudyScoreController`
- 前端:`ruoyi-ui/src/views/study/exam/`、`ruoyi-ui/src/views/study/questionBank/`
- 接口文档:`log/考核模块接口文档.md`
---
### 6. 学员信息管理 ✅
**状态**:已完成
- ✅ 学员信息录入:姓名、学号、班级等基本信息
- ✅ 学员信息编辑、删除、查询
- ✅ 支持Excel/CSV格式批量导入学员信息
- ✅ 学员信息导出功能
- ✅ 一键导入学员信息功能
**相关文件**
- 后端:`StudyClassUserController`、`SysUserController`
- 前端:`ruoyi-ui/src/views/study/classUser/index.vue`
---
### 7. 成绩管理 ⚠️
**状态**:部分完成
- ✅ 成绩统计:按课程、按班级、按学员统计成绩
- ✅ 成绩查询功能(权限控制:教师只能查看自己管理的班级)
- ✅ 成绩导出功能Excel导出
- ⚠️ 单人成绩单打印/导出PDF待实现
- ⚠️ 所有学员成绩汇总打印/导出PDF待实现
- ⚠️ 成绩统计图表展示(柱状图、折线图、饼图等)- 前端图表组件待完善
- ✅ 成绩分析:平均分、及格率等统计指标(部分实现)
**相关文件**
- 后端:`StudyScoreController`、`StudyScoreService`
- 前端:`ruoyi-ui/src/views/study/score/index.vue`
- 图表库已集成ECharts但成绩统计图表展示待完善
---
### 8. 班级管理 ✅
**状态**:已完成
- ✅ 班级创建、编辑、删除
- ✅ 学员分配到班级
- ✅ 班级学员管理(添加、移除学员)
- ✅ 按班级查看学员、成绩等
- ✅ 权限控制:
- ✅ 管理员可以管理所有班级和教师
- ✅ 教师只能管理自己负责的班级,无法查看和管理其他教师的班级
- ✅ 教师可以查看和管理自己班级的学员、课程、成绩等
**相关文件**
- 后端:`StudyClassController`、`StudyClassUserController`
- 前端:`ruoyi-ui/src/views/study/class/index.vue`、`ruoyi-ui/src/views/study/classUser/index.vue`
---
### 9. 登录功能 ✅
**状态**:已完成
- ✅ 账号密码登录功能,无需验证码
- ✅ 后台管理员登录
- ✅ App端学员登录
- ✅ Token管理
- ✅ 权限验证
**相关文件**
- 后端:`SysLoginController`
- 前端:`ruoyi-ui/src/views/login.vue`
- UniApp`frontend-uniapp/src/pages/login/login.vue`
---
### 10. 其他常规功能 ⚠️
**状态**:部分完成
- ✅ 用户权限管理:
- ✅ 管理员:拥有所有权限
- ✅ 教师:只能管理自己负责的班级
- ✅ 学员:只能查看和学习分配给自己的课程
- ✅ 操作日志记录RuoYi框架自带
- ⚠️ 数据备份和恢复(待实现)
- ✅ 系统设置(部分实现)
---
## ❌ 缺失功能1/11
### 11. 语音评测功能(语文课程) ❌
**状态**:未实现
- ❌ 语音跟读功能:学生可以跟着课文朗读
- ❌ 语音朗读评测:判定朗读是否正确,发音是否正确,自动打分
- ❌ 语音服务:选择性价比高的第三方语音识别和评测服务
- ❌ 评测结果记录和展示
**计划**
- 需要调研并选择语音服务商(科大讯飞、百度、腾讯云、阿里云等)
- 集成语音评测API
- 实现语音录制功能UniApp端
- 实现评测结果展示
- 评测记录存储
**相关文件**
- 数据库表:`voice_evaluation`(已设计,但未实现功能)
- 计划文档:`log/总体计划.md` 第三阶段 3.1 语音评测
---
## ⚠️ 待优化功能
### 1. 视频文件处理
- ⚠️ FFmpeg集成视频转码、压缩
- ⚠️ 视频格式统一MP4
- ⚠️ 视频处理服务(上传后自动处理)
### 2. 成绩管理完善
- ⚠️ 成绩统计图表展示(前端图表组件完善)
- ⚠️ 成绩单PDF生成和打印
- ⚠️ 成绩汇总PDF生成和打印
### 3. 学习监控优化
- ⚠️ 实时监控WebSocket实时推送
- ⚠️ 监控画面刷新优化
### 4. 课程推送通知
- ⚠️ App端接收课程推送通知推送机制
### 5. 大文件上传优化
- ⚠️ 分片上传
- ⚠️ 断点续传
- ⚠️ 上传进度显示
---
## 功能完成度统计
| 功能模块 | 完成度 | 状态 |
|---------|--------|------|
| 1. 后台课件上传 | 90% | ✅ 基本完成 |
| 2. 课程发布 | 90% | ✅ 基本完成 |
| 3. 学习屏幕监控 | 95% | ✅ 基本完成 |
| 4. 局域网部署 | 100% | ✅ 已完成 |
| 5. 考核模块 | 90% | ✅ 基本完成 |
| 6. 学员信息管理 | 100% | ✅ 已完成 |
| 7. 成绩管理 | 70% | ⚠️ 部分完成 |
| 8. 班级管理 | 100% | ✅ 已完成 |
| 9. 登录功能 | 100% | ✅ 已完成 |
| 10. 其他常规功能 | 80% | ⚠️ 部分完成 |
| 11. 语音评测功能 | 0% | ❌ 未实现 |
**总体完成度**:约 **82%**
---
## 下一步开发建议
### 高优先级
1. **语音评测功能**(完全缺失)
- 调研并选择语音服务商
- 集成语音评测API
- 实现语音录制和评测功能
2. **成绩管理完善**
- 实现成绩统计图表展示
- 实现成绩单PDF生成和打印
### 中优先级
3. **视频文件处理**
- FFmpeg集成
- 视频转码和压缩
4. **学习监控优化**
- WebSocket实时推送
- 实时监控画面刷新
### 低优先级
5. **大文件上传优化**
- 分片上传
- 断点续传
6. **课程推送通知**
- App端推送机制
---
## 备注
- 本总结基于代码库实际实现情况
- 部分功能可能已实现但未在代码中明确体现,需要进一步测试验证
- 待优化功能不影响系统基本使用,可在后续版本中完善
---
**最后更新时间**2025-01-XX

View File

@ -1,78 +0,0 @@
# 人类和AI一起工作的方法指南
## 核心原则
1. **文档很重要**
我们团队用简单明了的文档来安排工作。所有需求、设计想法和实施计划都要写成文档。特别要注意,开始时大家一起商量出的计划和之后每一步的跟进情况都要记录下来。这样做能避免重复说同样的话,直接帮大家省时间。
2. **分工要清楚**
人类负责说清楚想要什么、做最后决定和检查工作。AI负责想办法解决问题、写代码和让现有流程更好用。
3. **一步步来**
我们把大任务拆成很多小任务,然后一个一个完成。每完成一个任务,我们都会先检查一遍,没问题了再继续做下一个。
4. **文档确定**
执行的步骤和问题都放到新建文件夹里面可以新建log文件夹存放
步骤和问题放在不同文件夹下面
步骤遇到的问题用obsidian遇到的双连的形式连接 类似:[[问题1]]
## 具体怎么做
### 1. 说清楚需求
- **人类要做什么**:你要明白地说出自己想要什么功能,最好能举个例子,告诉大家你希望最后做成什么样。
- **AI要做什么**AI要认真看你的需求有不懂的地方就问把模糊的描述变成清楚的要求。
- **怎么一起做**你和AI要一起讨论需求细节直到两人都完全明白要做什么。
### 2. 想好怎么做
- **人类要做什么**:你要确定项目的大致方向,对重要的设计点做决定。
- **AI要做什么**AI要想出2-3个不同的解决办法并告诉你每个办法的好处和坏处。
- **怎么一起做**你和AI要一起看看这些办法选一个最合适的然后说清楚每一步要做成什么样。
### 3. 动手去做
- **人类要做什么**你要经常看看项目做得怎么样了发现问题就赶紧告诉AI具体哪里需要改。
- **AI要做什么**AI要照着计划认真做事碰到困难或不明白的地方要马上说。
- **怎么一起做**AI每做完一部分工作都要主动拿给你看看让你检查一下。
### 4. 解决问题
- **人类要做什么**你要帮AI一起分析复杂的问题告诉它一些背景信息或额外资料。
- **AI要做什么**AI要把遇到的问题都记下来然后试试2-3种不同的解决办法。
- **怎么一起做**你和AI要一起讨论怎么解决问题找到最管用的方法。
- **特别提醒**:当进行到某个步骤时,如果发现问题,不用为这个步骤重新创建新的文档,只需要在现有文档中记录下问题和对应的解决方案就行。这样既能跟踪问题,又不会让文档变得太复杂。
### 5. 检查结果
- **人类要做什么**:你要自己试试做出来的功能,看看它们是不是符合你一开始想要的样子。
- **AI要做什么**AI要帮着一起检查发现问题就马上修改。
- **怎么一起做**AI要根据你的检查意见不断改进直到完全符合你的要求。
### 6. 总结经验
- **人类要做什么**:你要总结这次项目里学到了什么,有什么做得好的地方,有什么可以改进的地方。
- **AI要做什么**AI要把工作中碰到的问题和解决办法都记下来做成团队可以参考的资料。
- **怎么一起做**你和AI要一起看看整个项目是怎么做的为下次合作制定更好的计划。
## 每天怎么一起工作
1. **早上聊一聊**
每天刚开始工作时你和AI要一起看看今天要做什么任务明确每个人要重点做什么。
2. **各自认真做事**
工作时间里AI要按照计划自己完成任务碰到困难就记下来。
3. **晚上对一对**
每天快结束工作时你和AI要一起看看今天做了什么讨论一下明天具体要做什么。
## 三个重要原则
1. **别影响现有功能**
我们修改现有系统时,一定要确保原来的功能都能正常使用。
2. **要考虑速度和效率**
我们设计新系统或新功能时,要先想到它运行得快不快,用起来方不方便。
3. **先弄清楚需求再动手**
开始做任何开发工作前,我们要确保团队里每个人都清楚知道要做什么。
---
**新加**
1. 在每一个阶段的过程中只能有一个阶段文件,阶段中的每一步只能更新阶段文件中的代办事项或者进行补充。在当前阶段结束了才能创建下一个阶段文件

View File

@ -1,319 +0,0 @@
# 导入对话框滚动优化说明
## 🎯 **优化目标**
为导入失败对话框添加滚动条功能,当失败记录很多时可以滑动查看所有失败详情。
---
## ✅ **修改内容**
### **文件:** `classUser/index.vue`
**修改的对话框:**
1. ✅ 上传失败对话框
2. ✅ 导入错误对话框
3. ✅ 导入失败对话框(进度轮询)
---
## 🎨 **CSS样式说明**
### **添加的样式属性**
```css
overflow: auto; /* 内容超出时显示滚动条 */
overflow-x: hidden; /* 隐藏横向滚动条 */
max-height: 70vh; /* 最大高度为视口高度的70% */
padding: 10px 20px 0; /* 内边距,右侧留出滚动条空间 */
```
### **效果说明**
| 属性 | 值 | 作用 |
|------|---|------|
| `overflow` | `auto` | 内容超出时自动显示滚动条 |
| `overflow-x` | `hidden` | 不显示横向滚动条 |
| `max-height` | `70vh` | 最大高度为屏幕高度的70% |
| `padding` | `10px 20px 0` | 内边距,避免内容贴边 |
---
## 📋 **修改前后对比**
### **修改前**
```html
<div style="color: #f56c6c;">
<p><strong>导入失败</strong></p>
<p>失败原因...</p>
</div>
```
**问题:**
- ❌ 内容过多时,对话框高度无限增长
- ❌ 超出屏幕后无法查看底部内容
- ❌ 需要滚动整个页面,体验不好
---
### **修改后**
```html
<div style="color: #f56c6c; overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;">
<p><strong>导入失败</strong></p>
<p>失败原因...</p>
</div>
```
**改进:**
- ✅ 对话框最大高度限制为屏幕的70%
- ✅ 内容超出时在对话框内部显示滚动条
- ✅ 可以在对话框内滑动查看所有内容
- ✅ 横向不滚动,避免左右晃动
---
## 🖼️ **效果示例**
### **场景1少量失败记录**
```
┌─────────────────────────────┐
│ 导入失败 │
│ │
│ 失败: 2条 │
│ │
│ 1. 张三 - 班级不存在 │
│ 2. 李四 - 姓名为空 │
│ │
│ [确定] │
└─────────────────────────────┘
```
**效果:** 内容少,无滚动条
---
### **场景2大量失败记录**
```
┌─────────────────────────────┐
│ 导入失败 ▲ │
│ ║ │
│ 失败: 20条 ║ │
│ ║ │
│ 1. 张三 - 班级不存在 ║ │
│ 2. 李四 - 姓名为空 ║ │
│ 3. 王五 - 班级不存在 ║ │
│ 4. 赵六 - 监区为空 ║ │
│ 5. 孙七 - 班级不存在 █ │ ← 滚动条
│ 6. 周八 - 姓名为空 ║ │
│ 7. 吴九 - 班级不存在 ║ │
│ 8. 郑十 - 监区为空 ║ │
│ ... (继续滑动查看) ▼ │
│ │
│ [确定] │
└─────────────────────────────┘
```
**效果:** 内容多,显示滚动条,可以滑动查看
---
## 🔧 **修改的位置**
### **位置1上传失败对话框**
**行号:** 1539-1552
**场景:** 文件上传时出错(格式错误、网络问题等)
```javascript
this.$alert(
`<div style="color: #f56c6c; overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;">
<p><strong>${errorMsg}</strong></p>
<p style="margin-top: 10px;">请检查:</p>
<ul>...</ul>
</div>`,
'上传失败',
{ dangerouslyUseHTMLString: true }
);
```
---
### **位置2导入错误对话框**
**行号:** 1562-1572
**场景:** 上传成功但服务器返回错误(如班级不存在等)
```javascript
this.$alert(
`<div style="color: #f56c6c; overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;">
<p><strong>导入失败</strong></p>
<p>${errorMsg}</p>
<p>提示:...</p>
</div>`,
'导入错误',
{ dangerouslyUseHTMLString: true }
);
```
---
### **位置3导入失败对话框进度轮询**
**行号:** 1658-1667
**场景:** 导入过程中失败(如部分记录失败)
```javascript
this.$alert(
`<div style="color: #f56c6c; overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;">
<p><strong>导入失败</strong></p>
<p>${progress.message || '未知错误'}</p>
<p>已处理: ${progress.processed || 0} 条</p>
<p>成功: ${progress.success || 0} 条,失败: ${progress.failed || 0} 条</p>
</div>`,
'导入失败',
{ dangerouslyUseHTMLString: true }
);
```
---
## 🎯 **技术细节**
### **1. 为什么使用 `max-height: 70vh`**
- `vh` = viewport height视口高度
- `70vh` = 屏幕高度的70%
- 留出30%给对话框标题、按钮和页面边距
- 适配不同屏幕尺寸
### **2. 为什么使用 `overflow: auto`**
- `auto`:内容超出时才显示滚动条
- `scroll`:始终显示滚动条(即使不需要)
- `auto` 体验更好,不影响少量内容的显示
### **3. 为什么隐藏横向滚动条?**
- `overflow-x: hidden`:防止横向滚动
- 文字内容通常不需要横向滚动
- `word-break: break-all` 已经让长文本自动换行
- 只需纵向滚动查看更多内容
### **4. 为什么添加 `padding`**
- `padding: 10px 20px 0`
- 右侧留出 20px 空间给滚动条
- 避免内容被滚动条遮挡
- 左右留白,内容不贴边
---
## 📱 **响应式支持**
### **不同屏幕尺寸**
| 屏幕高度 | 70vh | 对话框最大高度 |
|----------|------|----------------|
| 1080px | 756px | 适中 |
| 900px | 630px | 较小 |
| 768px | 537px | 更小 |
| 1440px | 1008px | 较大 |
**优点:**
- ✅ 自动适配不同屏幕
- ✅ 大屏幕显示更多内容
- ✅ 小屏幕也能正常使用
---
## 🧪 **测试场景**
### **测试1少量失败<5条**
**预期:** 无滚动条,内容正常显示
### **测试2中量失败5-10条**
**预期:** 可能有滚动条,取决于屏幕大小
### **测试3大量失败>20条**
**预期:** 有滚动条,可以滑动查看
### **测试4极大量失败>100条**
**预期:** 有滚动条,滚动流畅,可以查看所有内容
---
## ⚙️ **部署步骤**
### **1. 重新编译前端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis\ry-study-ui
npm run build:prod
# 或开发模式
npm run dev
```
### **2. 清除浏览器缓存**
- 按 Ctrl+Shift+Delete
- 清除缓存和Cookie
- 或使用无痕模式测试
### **3. 测试**
1. 准备一个有20+条记录的Excel
2. 确保部分记录的班级不存在
3. 执行导入
4. 查看失败对话框是否有滚动条
---
## 📋 **验收标准**
- [ ] 失败记录少时,对话框正常显示,无滚动条
- [ ] 失败记录多时,对话框显示滚动条
- [ ] 滚动条只显示在对话框内部,不是整个页面
- [ ] 可以流畅滑动查看所有失败记录
- [ ] 横向不滚动,内容不会左右晃动
- [ ] 在不同屏幕尺寸下都能正常显示
- [ ] 移动端(如果支持)也能正常滚动
---
## 💡 **后续优化建议**
### **建议1添加搜索功能**
在失败记录中添加搜索框,快速定位特定记录
### **建议2分类显示**
按失败原因分类显示:
- 班级不存在
- 姓名为空
- 监区为空
- ...
### **建议3导出失败记录**
提供"导出失败记录"按钮将失败详情导出为Excel
### **建议4批量修复**
对于"班级不存在"的错误,提供"创建班级并重试"功能
---
## ✅ **总结**
### **优化效果**
- ✅ 失败记录再多也能查看
- ✅ 滚动流畅,体验良好
- ✅ 适配不同屏幕尺寸
- ✅ 用户可以完整查看所有失败详情
### **技术实现**
- 使用 `overflow: auto` 实现自动滚动
- 使用 `max-height: 70vh` 限制最大高度
- 使用 `overflow-x: hidden` 隐藏横向滚动
- 使用合适的 `padding` 避免内容遮挡

View File

@ -1,323 +0,0 @@
# 导入性能优化方案
## ❌ **问题分析**
### **问题1SQL执行慢每条1.2秒)**
```
slow sql 1164 millis. update sys_user
slow sql 1165 millis. update student_class
```
**原因:**
- 每条记录更新需要1.2秒100条就需要2分钟
- UPDATE语句中字段重复
- 可能缺少数据库索引
---
### **问题2班级分配逻辑错误**
```
Duplicate entry '234-17' for key 'student_class.uk_student_class'
```
**原因:**
- 先UPDATE旧班级status=0
- 再INSERT新班级记录
- 如果新旧班级ID相同INSERT失败唯一键冲突
**日志分析:**
```
14:58:25.593 UPDATE student_class SET status=0 WHERE id=4642 (1.2秒)
14:58:26.760 INSERT student_class (234, 17, ...) (失败Duplicate)
```
---
### **问题3UPDATE SQL字段重复**
```sql
update sys_user
SET prison_name = ?, -- 第一次
prison_area = ?,
...
prison_name = ?, -- 第二次重复!
prison_area = ?, -- 重复!
...
```
---
## ✅ **解决方案**
### **方案1修复班级分配逻辑**
#### **当前逻辑(错误):**
```java
// 1. 将所有旧班级设为status=0
for (StudyStudentClass old : oldClassList) {
old.setStatus(0);
studentClassMapper.updateStudentClass(old); // 慢查询
}
// 2. 插入新班级
studentClassMapper.insertStudentClass(studentClass); // 失败Duplicate
```
#### **优化后逻辑(正确):**
```java
// 1. 检查是否已在该班级
boolean classUpdated = false;
for (StudyStudentClass old : oldClassList) {
if (old.getClassId().equals(classId)) {
// 已在该班级,只需确保状态为活跃
if (old.getStatus() != 1) {
old.setStatus(1);
studentClassMapper.updateStudentClass(old);
}
classUpdated = true;
break;
}
}
// 2. 如果不在该班级,禁用其他班级
if (!classUpdated) {
for (StudyStudentClass old : oldClassList) {
if (old.getStatus() == 1) {
old.setStatus(0);
studentClassMapper.updateStudentClass(old);
}
}
}
// 3. 如果不在该班级,插入新记录
if (!classUpdated) {
studentClassMapper.insertStudentClass(studentClass);
}
```
**优势:**
- ✅ 避免Duplicate错误
- ✅ 减少不必要的UPDATE
- ✅ 逻辑更清晰
---
### **方案2添加数据库索引**
#### **检查当前索引:**
```sql
SHOW INDEX FROM sys_user;
SHOW INDEX FROM student_class;
```
#### **建议添加的索引:**
```sql
-- 用户表:按信息编号查询
CREATE INDEX idx_user_name ON sys_user(user_name);
-- 学员班级表按学员ID查询
CREATE INDEX idx_student_id ON student_class(student_id);
-- 学员班级表:按状态查询
CREATE INDEX idx_status ON student_class(status);
```
---
### **方案3检查UPDATE SQL需要修复Mapper**
检查 `SysUserMapper.xml` 中的 `updateUser` 是否有重复字段:
```xml
<update id="updateUser">
update sys_user
SET prison_name = #{prisonName},
prison_area = #{prisonArea},
...
<!-- 检查是否重复 -->
prison_name = #{prisonName}, <!-- 重复! -->
prison_area = #{prisonArea} <!-- 重复! -->
where user_id = #{userId}
</update>
```
---
### **方案4使用批量操作**
#### **当前:逐条处理**
```java
for (SysUser user : userList) {
userMapper.insertUser(user); // 每次1.2秒
}
```
#### **优化:批量插入**
```java
// 批量插入每批50条
List<SysUser> batch = new ArrayList<>();
for (SysUser user : userList) {
batch.add(user);
if (batch.size() >= 50) {
userMapper.batchInsertUsers(batch);
batch.clear();
}
}
if (!batch.isEmpty()) {
userMapper.batchInsertUsers(batch);
}
```
---
## 🔧 **立即修复步骤**
### **步骤1修复班级分配逻辑**
文件:`StudyClassUserServiceImpl.java`
位置第1110-1126行更新用户时的班级分配
需要补充完整的班级分配逻辑(类似新增用户的逻辑)
---
### **步骤2检查并修复UPDATE SQL**
文件:`SysUserMapper.xml`
搜索:`<update id="updateUser">`
删除重复的字段设置
---
### **步骤3添加数据库索引**
```sql
-- 连接数据库
USE ry_study;
-- 检查现有索引
SHOW INDEX FROM sys_user WHERE Key_name = 'idx_user_name';
SHOW INDEX FROM student_class WHERE Key_name = 'idx_student_id';
-- 添加缺失的索引
CREATE INDEX idx_user_name ON sys_user(user_name) IF NOT EXISTS;
CREATE INDEX idx_student_id ON student_class(student_id) IF NOT EXISTS;
CREATE INDEX idx_status ON student_class(status) IF NOT EXISTS;
```
---
### **步骤4优化数据库配置**
检查数据库连接池配置:
```yaml
# application.yml
spring:
datasource:
druid:
initial-size: 10
min-idle: 10
max-active: 50 # 增加最大连接数
max-wait: 60000
```
---
##⚡ **预期性能提升**
### **修复前:**
- 100条数据约2-3分钟
- 每条平均1.2-1.8秒
### **修复后:**
- 100条数据约10-20秒
- 每条平均0.1-0.2秒
**提升10倍以上**
---
## 📋 **快速检查清单**
- [ ] 修复班级分配逻辑
- [ ] 检查UPDATE SQL是否有重复字段
- [ ] 添加数据库索引
- [ ] 测试导入100条数据
- [ ] 查看SQL慢查询日志
- [ ] 确认无Duplicate错误
---
## 🧪 **测试步骤**
1. **准备测试数据**
```bash
python generate_test_data.py # 生成100条
```
2. **执行导入**
- 记录开始时间
- 导入test_data.xlsx
- 记录结束时间
3. **查看日志**
```bash
# 搜索慢查询
grep "slow sql" logs/app.log
# 搜索错误
grep "Duplicate entry" logs/app.log
# 计算平均时间
```
4. **验证结果**
- 检查导入成功数量
- 检查班级分配是否正确
- 检查是否有错误
---
## 💡 **后续优化建议**
### **1. 使用Redis缓存**
- 缓存班级映射
- 缓存角色ID
- 减少数据库查询
### **2. 异步处理**
- 使用消息队列
- 导入任务放入队列
- 后台异步处理
### **3. 分片导入**
- 大文件分片上传
- 每片独立处理
- 提高并发性
### **4. 数据库优化**
- 分表分库
- 读写分离
- 使用更快的存储引擎
---
## ✅ **总结**
### **核心问题**
1. 班级分配逻辑错误导致Duplicate
2. UPDATE SQL慢查询
3. 缺少必要的数据库索引
### **解决方法**
1. 修复班级分配逻辑
2. 检查并优化SQL
3. 添加数据库索引
### **预期效果**
- 性能提升10倍以上
- 无Duplicate错误
- 导入更流畅

View File

@ -1,394 +0,0 @@
# 导入结果显示优化说明
## 🎯 **优化目标**
简化导入结果显示,只详细展示失败的记录,成功和跳过的只显示统计数量。
---
## 📊 **优化前后对比**
### **优化前 ❌**
```
导入完成新增8条更新2条无变化跳过15条失败5条。
新增的数据:
1、信息编号 201罪犯姓名 张三 新增成功
2、信息编号 202罪犯姓名 李四 新增成功
3、信息编号 203罪犯姓名 王五 新增成功
... (省略5条)
更新的数据:
1、信息编号 301罪犯姓名 赵六 更新成功
2、信息编号 302罪犯姓名 孙七 更新成功
无变化跳过的数据:
1、信息编号 401罪犯姓名 周八 已存在且无变化
2、信息编号 402罪犯姓名 吴九 已存在且无变化
... (省略13条)
失败的数据:
1、信息编号 501罪犯姓名 郑十 导入失败:班级 [三班] 不存在
... (省略4条)
```
**问题:**
- 信息量太大,不易查看
- 成功的记录逐条列出,没有实际意义
- 跳过的记录逐条列出,占用空间
---
### **优化后 ✅**
```
导入完成新增8条更新2条无变化跳过15条失败5条。
失败详情:
1、信息编号 501罪犯姓名 郑十 导入失败:班级 [三班] 不存在
2、信息编号 502罪犯姓名 王十一 导入失败:罪犯姓名不能为空
3、信息编号 503罪犯姓名 李十二 导入失败:班级 [四班] 不存在
4、信息编号 504罪犯姓名 张十三 导入失败:监区不能为空
5、信息编号 505罪犯姓名 赵十四 导入失败:班级 [五班] 不存在
```
**改进:**
- ✅ 只显示失败的详细信息
- ✅ 成功和跳过的只显示统计数量
- ✅ 信息简洁,易于查看
- ✅ 快速定位问题记录
---
## 🔧 **修改内容**
### **文件:** `StudyClassUserServiceImpl.java`
#### **修改1异步导入方法importStudentsWithProgress**
**位置:** 第1251-1256行
**修改前:**
```java
if (successNum > 0 && successMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>新增的数据:</strong>");
resultMsg.append(successMsg.toString());
}
if (updateNum > 0 && updateMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>更新的数据:</strong>");
resultMsg.append(updateMsg.toString());
}
if (duplicateNum > 0 && duplicateMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>无变化跳过的数据:</strong>");
resultMsg.append(duplicateMsg.toString());
}
if (errorNum > 0 && errorMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>失败的数据:</strong>");
resultMsg.append(errorMsg.toString());
}
```
**修改后:**
```java
// 只显示失败的详细信息,成功和跳过的只显示统计数量
if (errorNum > 0 && errorMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>失败详情:</strong>");
resultMsg.append(errorMsg.toString());
}
```
---
#### **修改2同步导入方法importStudents**
**位置:** 第789-794行
**修改前:**
```java
if (successNum > 0 && successMsg.length() > 0) {
resultMsg.append("<br/><br/>成功导入的数据:");
resultMsg.append(successMsg.toString());
}
if (duplicateNum > 0 && duplicateMsg.length() > 0) {
resultMsg.append("<br/><br/>重复跳过的数据:");
resultMsg.append(duplicateMsg.toString());
}
if (errorNum > 0 && errorMsg.length() > 0) {
resultMsg.append("<br/><br/>失败的数据:");
resultMsg.append(errorMsg.toString());
}
```
**修改后:**
```java
// 只显示失败的详细信息,成功和跳过的只显示统计数量
if (errorNum > 0 && errorMsg.length() > 0) {
resultMsg.append("<br/><br/><strong>失败详情:</strong>");
resultMsg.append(errorMsg.toString());
}
```
---
#### **修改3全部跳过时的处理同步方法**
**位置:** 第804-807行
**修改前:**
```java
else if (duplicateNum > 0) {
duplicateMsg.insert(0, "导入完成!所有数据都已存在(共 " + duplicateNum + " 条),已跳过。");
resultMsg.append(duplicateMsg.toString());
}
```
**修改后:**
```java
else if (duplicateNum > 0) {
resultMsg.append("导入完成!所有数据都已存在(共 ").append(duplicateNum).append(" 条),已跳过。");
}
```
---
#### **修改4全部跳过时的处理异步方法**
**位置:** 第1257-1261行
**修改前:**
```java
else if (duplicateNum > 0) {
duplicateMsg.insert(0, "导入完成!所有数据都已存在且无变化(共 " + duplicateNum + " 条),已跳过。");
progressManager.completeTask(taskId, duplicateMsg.toString());
}
```
**修改后:**
```java
else if (duplicateNum > 0) {
String msg = "导入完成!所有数据都已存在且无变化(共 " + duplicateNum + " 条),已跳过。";
progressManager.completeTask(taskId, msg);
}
```
---
## 📋 **显示规则**
### **统计摘要**
始终显示:
```
导入完成新增X条更新Y条无变化跳过Z条失败W条。
```
### **详细信息**
只显示失败的记录:
```
失败详情:
1、信息编号 XXX罪犯姓名 XXX 导入失败:错误原因
2、...
```
### **特殊情况**
#### **全部成功**
```
导入完成新增50条。
```
#### **全部跳过**
```
导入完成!所有数据都已存在且无变化(共 30 条),已跳过。
```
#### **全部失败**
```
导入失败!共 30 条数据导入失败,错误如下:
1、信息编号 201罪犯姓名 张三 导入失败:班级 [一班] 不存在
2、...
```
#### **部分成功,部分失败**
```
导入完成新增20条失败10条。
失败详情:
1、信息编号 201罪犯姓名 张三 导入失败:班级 [一班] 不存在
2、...
```
---
## ✅ **优化效果**
### **用户体验**
- ✅ **信息简洁** - 不再被大量成功记录淹没
- ✅ **重点突出** - 失败记录一目了然
- ✅ **快速定位** - 立即找到需要修复的记录
- ✅ **易于查看** - 减少滚动,提高效率
### **性能优化**
- ✅ **减少数据量** - 不再生成成功和跳过的详细列表
- ✅ **减少内存占用** - StringBuilder 中的数据量大幅减少
- ✅ **加快渲染速度** - 前端需要渲染的内容更少
### **实际应用**
#### **场景1导入100条数据**
- **优化前:** 显示100条详细信息成功80 + 跳过15 + 失败5
- **优化后:** 只显示5条失败信息
#### **场景2导入1000条数据**
- **优化前:** 显示1000条详细信息页面卡顿
- **优化后:** 只显示失败的记录,流畅响应
#### **场景3全部成功**
- **优化前:** 显示200条成功记录
- **优化后:** 只显示"导入完成新增200条。"
---
## 🧪 **测试场景**
### **场景1部分成功部分失败**
**测试数据:**
- 新增10条
- 更新5条
- 跳过20条
- 失败5条
**预期结果:**
```
导入完成新增10条更新5条无变化跳过20条失败5条。
失败详情:
1、信息编号 201罪犯姓名 张三 导入失败:班级 [一班] 不存在
2、信息编号 205罪犯姓名 李四 导入失败:罪犯姓名不能为空
3、信息编号 210罪犯姓名 王五 导入失败:监区不能为空
4、信息编号 215罪犯姓名 赵六 导入失败:班级 [二班] 不存在
5、信息编号 220罪犯姓名 孙七 导入失败:信息编号重复
```
---
### **场景2全部成功**
**测试数据:**
- 新增50条
**预期结果:**
```
导入完成新增50条。
```
---
### **场景3全部跳过**
**测试数据:**
- 跳过30条数据已存在且无变化
**预期结果:**
```
导入完成!所有数据都已存在且无变化(共 30 条),已跳过。
```
---
### **场景4全部失败**
**测试数据:**
- 失败20条班级不存在
**预期结果:**
```
导入失败!共 20 条数据导入失败,错误如下:
1、信息编号 201罪犯姓名 张三 导入失败:班级 [一班] 不存在
2、信息编号 202罪犯姓名 李四 导入失败:班级 [一班] 不存在
...
20、信息编号 220罪犯姓名 王二十 导入失败:班级 [五班] 不存在
```
---
## ⚙️ **部署步骤**
### **1. 重新编译后端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **2. 重启服务**
```bash
# 停止旧服务
# 启动新服务
java -jar ry-study-admin/target/ry-study-admin.jar
```
### **3. 清除浏览器缓存**
`Ctrl + Shift + Delete` 清除缓存
### **4. 测试导入功能**
1. 准备测试数据(包含成功、失败的记录)
2. 执行导入
3. 查看结果显示
---
## 📋 **验收标准**
- [ ] 导入成功的记录不显示详细列表,只显示数量
- [ ] 导入跳过的记录不显示详细列表,只显示数量
- [ ] 导入失败的记录逐条显示错误原因
- [ ] 全部成功时只显示统计信息
- [ ] 全部跳过时只显示统计信息
- [ ] 全部失败时显示所有失败详情
- [ ] 导入结果滚动条正常工作
- [ ] 页面响应流畅,无卡顿
---
## 💡 **后续优化建议**
### **1. 失败记录导出**
提供"导出失败记录"按钮将失败的记录导出为Excel方便批量修改后重新导入。
### **2. 失败原因分类**
将失败原因分类统计:
```
失败详情(共 10 条):
- 班级不存在5条
- 姓名为空3条
- 监区为空2条
详细列表:
1、信息编号 201罪犯姓名 张三 导入失败:班级 [一班] 不存在
...
```
### **3. 批量修复**
针对同类错误(如班级不存在),提供批量修复功能:
- 识别所有因"班级不存在"而失败的记录
- 提供"创建班级并重试"按钮
- 自动创建班级后重新导入这些记录
### **4. 详细信息可展开**
提供"查看详细信息"按钮,点击后显示成功和跳过的详细列表(按需加载)。
---
## ✅ **总结**
### **核心改进**
- 只显示失败的详细信息
- 成功和跳过的只显示统计数量
- 信息简洁,易于查看
### **用户价值**
- 快速定位问题记录
- 减少信息噪音
- 提高工作效率
### **技术价值**
- 减少数据量
- 优化内存占用
- 提升渲染性能

View File

@ -1,391 +0,0 @@
# 导入进度显示优化说明
## ❌ **问题现象**
1. 导入100条数据进度不显示或显示很慢
2. 中断导入时显示"没有导入任何成功或失败的数据"
3. 看不到正在做什么
---
## 🔍 **问题原因**
### **原因1进度更新频率太低**
```java
int updateInterval = 100; // 每100条更新一次
```
**问题:**
- 100条数据只更新2次进度第100条和结束时
- 前99条处理时用户看不到任何进度
- 导致用户以为系统卡死
### **原因2中断时结果保存不正确**
```java
// 只设置了progress.setResult()
// 但没有调用progressManager的方法保存
progress.setResult(resultMsg.toString());
return; // 直接return结果丢失
```
### **原因3缺少详细的处理日志**
```java
// 只有进度日志
logger.info("任务 {} 进度更新: {}/{}", taskId, i, totalSize);
// 缺少:
// - 正在处理哪条记录
// - 新增还是更新
// - 处理结果如何
```
---
## ✅ **解决方案**
### **优化1提高进度更新频率**
**修改前:**
```java
int updateInterval = 100; // 每100条更新一次
```
**修改后:**
```java
int updateInterval = 5; // 每5条更新一次
```
**效果:**
- 100条数据会更新20次进度
- 用户每处理5条就能看到进度变化
- 实时反馈,体验更好
---
### **优化2修复中断时的结果保存**
**修改前:**
```java
com.ddnai.common.core.domain.ImportProgress progress = progressManager.getProgress(taskId);
if (progress != null) {
progress.setResult(resultMsg.toString());
}
return; // 结果可能丢失
```
**修改后:**
```java
// 使用progressManager的方法保存结果
progressManager.cancelTask(taskId, resultMsg.toString());
return;
```
**效果:**
- 中断时正确保存已处理的结果
- 显示新增、更新、失败的具体数量
- 显示失败的详细信息
---
### **优化3添加详细的处理日志**
**新增用户时:**
```java
logger.info("正在新增用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.insertUser(user);
logger.info("用户新增成功: 信息编号={}, userId={}", infoNo, user.getUserId());
```
**更新用户时:**
```java
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
logger.info("用户更新成功: 信息编号={}", infoNo);
```
**进度更新时:**
```java
String progressInfo = String.format("[%d/%d] 新增:%d 更新:%d 失败:%d 跳过:%d",
i + 1, totalSize, successNum, updateNum, errorNum, duplicateNum);
logger.info("任务 {} 进度: {}", taskId, progressInfo);
```
---
## 📊 **优化效果对比**
### **优化前**
| 项目 | 表现 |
|------|------|
| 进度更新 | 100条只更新2次 |
| 进度显示 | 99条时看不到进度 |
| 中断显示 | "没有导入任何数据" |
| 日志详细度 | 只有简单进度 |
### **优化后**
| 项目 | 表现 |
|------|------|
| 进度更新 | 100条更新20次 |
| 进度显示 | 每5条就能看到 |
| 中断显示 | 显示已处理的详情 |
| 日志详细度 | 每条记录都有日志 |
---
## 📋 **修改内容**
### **文件:** `StudyClassUserServiceImpl.java`
#### **修改1进度更新频率第905行**
```java
// 每处理5条更新一次进度让用户看到实时进度
int updateInterval = 5;
```
#### **修改2中断时保存结果第936行**
```java
// 保存取消状态和结果
progressManager.cancelTask(taskId, resultMsg.toString());
```
#### **修改3新增详细日志第998-1000行**
```java
logger.info("正在新增用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.insertUser(user);
logger.info("用户新增成功: 信息编号={}, userId={}", infoNo, user.getUserId());
```
#### **修改4更新详细日志第1092-1094行**
```java
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
logger.info("用户更新成功: 信息编号={}", infoNo);
```
#### **修改5进度日志格式第1235-1237行**
```java
String progressInfo = String.format("[%d/%d] 新增:%d 更新:%d 失败:%d 跳过:%d",
i + 1, totalSize, successNum, updateNum, errorNum, duplicateNum);
logger.info("任务 {} 进度: {}", taskId, progressInfo);
```
---
## 🔧 **部署步骤**
### **步骤1重新编译**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **步骤2重启服务**
停止旧服务,启动新服务
### **步骤3测试导入**
1. 导入50条数据
2. 观察进度是否实时更新
3. 测试中断功能
4. 查看日志详细程度
---
## 🧪 **测试场景**
### **场景1正常导入**
**操作:**
1. 导入100条数据
2. 观察进度条
**预期:**
- 进度条从0%逐步增加到100%
- 每5条更新一次约每秒更新几次
- 最终显示成功数量
**日志示例:**
```
正在新增用户: 信息编号=201, 姓名=张三
用户新增成功: 信息编号=201, userId=1001
任务 xxx 进度: [5/100] 新增:5 更新:0 失败:0 跳过:0
正在新增用户: 信息编号=206, 姓名=李四
用户新增成功: 信息编号=206, userId=1006
任务 xxx 进度: [10/100] 新增:10 更新:0 失败:0 跳过:0
...
```
---
### **场景2中断导入**
**操作:**
1. 开始导入100条数据
2. 处理到第30条时点击"中断导入"
**预期:**
- 显示中断提示
- 显示已处理30条的详情
```
导入已取消!
新增: 20 条
更新: 8 条
失败: 2 条
无变化跳过: 0 条
未处理: 70 条
失败详情:
1、信息编号 205罪犯姓名 王五 导入失败:班级 [三班] 不存在
2、信息编号 210罪犯姓名 赵六 导入失败:监区不能为空
```
---
### **场景3包含错误的导入**
**操作:**
1. 导入100条数据其中20条班级不存在
**预期:**
- 进度实时更新
- 最终显示:
```
导入完成新增70条更新10条失败20条。
失败详情:
1、信息编号 205罪犯姓名 张三 导入失败:班级 [三班] 不存在
...逐条列出20条失败记录
```
**日志示例:**
```
正在新增用户: 信息编号=205, 姓名=张三
用户新增成功: 信息编号=205, userId=1005
为学员 205 分配班级时失败: 班级 [三班] 不存在,请先创建该班级
任务 xxx 进度: [5/100] 新增:4 更新:0 失败:1 跳过:0
```
---
## 📊 **日志输出示例**
### **完整的导入过程日志**
```
[INFO] 任务 cce42e41-f819-4029-9c4e-0155fb3c2582 开始,共 100 条数据
[INFO] 预加载班级映射完成,共 5 个班级
[INFO] 正在新增用户: 信息编号=201, 姓名=张三
[INFO] 用户新增成功: 信息编号=201, userId=1001
[INFO] 为学员 201 分配班级 中级班1班 成功
[INFO] 正在新增用户: 信息编号=202, 姓名=李四
[INFO] 用户新增成功: 信息编号=202, userId=1002
[INFO] 为学员 202 分配班级 初级班1班 成功
[INFO] 正在新增用户: 信息编号=203, 姓名=王五
[INFO] 用户新增成功: 信息编号=203, userId=1003
[WARN] 为学员 203 分配班级时失败: 班级 [三班] 不存在,请先创建该班级
[INFO] 正在新增用户: 信息编号=204, 姓名=赵六
[INFO] 用户新增成功: 信息编号=204, userId=1004
[INFO] 为学员 204 分配班级 高级班1班 成功
[INFO] 正在新增用户: 信息编号=205, 姓名=孙七
[INFO] 用户新增成功: 信息编号=205, userId=1005
[INFO] 为学员 205 分配班级 攻坚转换班1班 成功
[INFO] 任务 xxx 进度: [5/100] 新增:5 更新:0 失败:0 跳过:0
[INFO] 正在更新用户: 信息编号=210, 姓名=周八
[INFO] 用户更新成功: 信息编号=210
[INFO] 为学员 210 更新班级为 中级班1班 成功
... (继续处理)
[INFO] 任务 xxx 进度: [10/100] 新增:8 更新:2 失败:0 跳过:0
[INFO] 任务 xxx 进度: [15/100] 新增:12 更新:3 失败:0 跳过:0
...
[INFO] 任务 xxx 进度: [100/100] 新增:70 更新:20 失败:5 跳过:5
[INFO] 任务 xxx 完成,最终结果:成功 70 条,更新 20 条,重复 5 条,失败 5 条
```
---
## 💡 **进一步优化建议**
### **1. 添加进度百分比**
```java
int percent = (i + 1) * 100 / totalSize;
logger.info("任务 {} 进度: {}% [{}]", taskId, percent, progressInfo);
```
### **2. 添加预计剩余时间**
```java
long elapsed = System.currentTimeMillis() - startTime;
long avgTime = elapsed / (i + 1);
long remaining = avgTime * (totalSize - i - 1);
logger.info("预计剩余时间: {} 秒", remaining / 1000);
```
### **3. 添加每秒处理速度**
```java
double speed = (i + 1) / (elapsed / 1000.0);
logger.info("处理速度: {:.1f} 条/秒", speed);
```
---
## ⚠️ **注意事项**
### **1. 日志量增加**
- 每条记录产生2-3条日志
- 100条数据约200-300条日志
- 建议定期清理日志文件
### **2. 性能影响**
- 日志输出有一定性能开销
- 但相比数据库操作可忽略
- 如果追求极致性能可以只在DEBUG级别输出详细日志
### **3. 进度更新频率**
- 当前设置为每5条更新一次
- 可根据实际情况调整3-10条都可以
- 太频繁会增加网络开销
---
## 📋 **验收标准**
- [ ] 导入100条数据进度实时更新每5条更新
- [ ] 进度条流畅增长,无卡顿
- [ ] 中断导入时显示已处理的详细信息
- [ ] 日志显示每条记录的处理过程
- [ ] 日志包含:正在处理、处理成功、失败原因
- [ ] 最终结果统计正确
---
## ✅ **总结**
### **核心改进**
1. 进度更新频率从100条提高到5条
2. 中断时正确保存和显示结果
3. 添加详细的处理日志
### **用户体验**
- ✅ 实时看到导入进度
- ✅ 知道正在处理什么
- ✅ 中断时有明确反馈
- ✅ 日志便于排查问题
### **开发调试**
- ✅ 日志详细便于调试
- ✅ 可以追踪每条记录
- ✅ 快速定位性能瓶颈
---
**现在重新编译并测试,应该能看到实时的进度更新和详细的处理日志了!** 🚀

View File

@ -1,405 +0,0 @@
# 导入速度优化方案对比
## 📊 **当前性能瓶颈(已分析)**
### **每条记录的操作耗时**
| 步骤 | 操作 | 当前耗时 | 优化后 | 说明 |
|------|------|----------|--------|------|
| 1 | `selectUserById` | 500ms | 500ms | 必须查询,无法优化 |
| 2 | `updateUser` | **1500ms** | **<100ms** | 已修复Mapper重复字段 |
| 3 | `selectStudentClassList` | 500ms | 0ms | ⚠️ 可选:跳过班级检查 |
| 4 | `updateStudentClass` | 1000ms | 1000ms | 根据需要执行 |
| 5 | `insertStudentClass` | 200ms | 200ms | 根据需要执行 |
| **总计** | **每条** | **~3700ms** | **~1800ms** | 修复Mapper后 |
---
## ✅ **已完成的优化**
### **1. 修复Mapper重复字段最重要**
**影响:** `updateUser` 从 1500ms 降到 <100ms
**状态:** ✅ 已修复
**要求:** ⚠️ 必须重新编译和重启服务
**效果:** 每条节省1400ms100条节省2分20秒
---
### **2. 调整进度更新频率**
**从:** 每5条更新一次
**改为:** 每10条更新一次
**状态:** ✅ 已修改
**效果:** 减少50%的进度更新操作
---
## 🎯 **可选的进一步优化**
### **方案A简化班级处理推荐⭐⭐⭐**
#### **当前逻辑(复杂但安全)**
```java
// 1. 查询现有班级 (~500ms)
List<StudyStudentClass> oldClassList = studentClassMapper.selectStudentClassList(query);
// 2. 检查是否已在该班级
boolean classUpdated = false;
for (StudyStudentClass old : oldClassList) {
if (old.getClassId().equals(classId)) {
if (old.getStatus() != 1) {
// 重新激活
updateStudentClass(old); // ~1000ms
}
classUpdated = true;
break;
}
}
// 3. 如果不在该班级
if (!classUpdated) {
// 禁用其他班级
for (StudyStudentClass old : oldClassList) {
updateStudentClass(old); // ~1000ms * N
}
// 插入新班级
insertStudentClass(studentClass); // ~200ms
}
```
**优点:**
- ✅ 避免重复插入
- ✅ 保持历史记录
- ✅ 班级未变时不操作
**缺点:**
- ❌ 需要先查询500ms
- ❌ 可能需要多次UPDATE
---
#### **优化方案:直接覆盖(快但简单)**
```java
// 不查询,直接操作
// 1. 禁用该学员的所有班级
UPDATE student_class SET status=0 WHERE student_id=? AND status=1
// 2. 插入新班级(如果已存在会失败,忽略)
INSERT IGNORE INTO student_class (student_id, class_id, status, join_time)
VALUES (?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE status=1, join_time=NOW()
```
**优点:**
- ✅ 不需要查询节省500ms
- ✅ 只需2个SQL语句
- ✅ 速度快
**缺点:**
- ⚠️ 不检查是否需要更新
- ⚠️ 即使班级未变也会UPDATE
**节省时间:** 每条约500ms
---
### **方案B关闭详细日志**
#### **当前日志**
```java
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
logger.info("用户更新成功: 信息编号={}", infoNo);
logger.info("正在处理用户班级: 信息编号={}, 班级={}", infoNo, className);
```
**每条记录:** 3-4条日志
#### **优化方案**
```java
// 只在出错时记录日志
// logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
// logger.info("用户更新成功: 信息编号={}", infoNo);
```
**优点:**
- ✅ 减少日志IO
- ✅ 减少字符串拼接
**缺点:**
- ⚠️ 调试困难
- ⚠️ 无法追踪进度
**节省时间:** 每条约10-20ms
---
### **方案C进一步减少进度更新**
#### **当前配置**
```java
int updateInterval = 10; // 每10条更新一次
```
#### **可选配置**
```java
// 选项1每20条推荐平衡
int updateInterval = 20;
// 选项2每50条追求速度
int updateInterval = 50;
// 选项3每100条极致速度
int updateInterval = 100;
```
**效果对比:**
| 配置 | 100条更新次数 | 用户体验 | 速度 |
|------|---------------|----------|------|
| 每5条 | 20次 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 每10条 | 10次 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 每20条 | 5次 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 每50条 | 2次 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 每100条 | 1次 | ⭐ | ⭐⭐⭐⭐⭐ |
**节省时间:** 每条约5-10ms
---
### **方案D批量操作最大优化**
#### **当前方式:逐条处理**
```java
for (SysUser user : userList) {
selectUserById(userId); // 500ms
updateUser(user); // 100ms (修复后)
selectStudentClassList(...); // 500ms
updateStudentClass(...); // 1000ms
}
// 每条总计:~2100ms
// 100条总计~3.5分钟
```
#### **批量方式**
```java
// 1. 批量查询用户
Map<Long, SysUser> userMap = selectUsersByIds(userIds); // 一次查询
// 2. 批量更新用户
batchUpdateUsers(userList); // 一次UPDATE
// 3. 批量处理班级
batchUpdateStudentClass(classList); // 一次UPDATE
batchInsertStudentClass(newClassList); // 一次INSERT
// 100条总计~5秒
```
**优点:**
- ✅ 速度极快(数据库往返次数大幅减少)
- ✅ 性能提升10-50倍
**缺点:**
- ⚠️ 需要大量代码改动
- ⚠️ 实现复杂
- ⚠️ 可能影响进度显示
**节省时间:** 每条约1500-2000ms
---
## 📊 **方案对比总结**
| 方案 | 实现难度 | 节省时间/条 | 总节省(100条) | 推荐度 | 风险 |
|------|----------|-------------|---------------|--------|------|
| **Mapper修复** | ✅ 已完成 | **1400ms** | **2分20秒** | ⭐⭐⭐⭐⭐ | 无 |
| 简化班级处理 | 中 | 500ms | 50秒 | ⭐⭐⭐⭐ | 低 |
| 关闭详细日志 | 易 | 20ms | 2秒 | ⭐⭐⭐ | 中 |
| 减少进度更新 | ✅ 已完成 | 10ms | 1秒 | ⭐⭐⭐⭐ | 无 |
| 批量操作 | 难 | 2000ms | 3分 | ⭐⭐⭐⭐⭐ | 高 |
---
## 💡 **推荐优化顺序**
### **阶段1立即执行已完成**
1. ✅ **修复Mapper重复字段**
- 预期提升15倍
- ⚠️ 必须重新编译和重启
2. ✅ **调整进度更新频率**
- 从5改为10
- 略微提升
3. ⚠️ **执行数据库索引SQL**
- 文件:`optimize_import_performance_safe.sql`
- 预期提升查询速度提升2-5倍
---
### **阶段2测试后决定**
**先重新编译和重启,测试效果。**
**如果速度已满意:** 不需要进一步优化
**如果还需要更快:** 考虑以下方案
1. **简化班级处理**(推荐)
- 节省时间每条500ms
- 风险:低
- 实现:修改`StudyClassUserServiceImpl.java`
2. **关闭详细日志**(可选)
- 节省时间每条20ms
- 风险:中(调试困难)
- 实现注释logger语句
---
### **阶段3终极优化可选**
**批量操作改造**
- 需要大量代码修改
- 性能提升最大
- 实现复杂度高
- 建议数据量>1000时考虑
---
## 🎯 **具体实现:简化班级处理**
如果需要实现"简化班级处理",修改代码如下:
```java
// 文件StudyClassUserServiceImpl.java
// 位置:更新用户时的班级处理
// === 当前复杂逻辑第1122-1177行===
// 查询现有班级
StudyStudentClass query = new StudyStudentClass();
query.setStudentId(u.getUserId());
List<StudyStudentClass> oldClassList = studentClassMapper.selectStudentClassList(query);
boolean classUpdated = false;
// ... 复杂的检查和更新逻辑 ...
// === 简化后(推荐)===
try {
// 1. 直接禁用该学员的所有活跃班级
studentClassMapper.deactivateStudentClasses(u.getUserId());
// 2. 插入或更新新班级(使用 ON DUPLICATE KEY UPDATE
StudyStudentClass studentClass = new StudyStudentClass();
studentClass.setStudentId(u.getUserId());
studentClass.setClassId(classId);
studentClass.setStatus(1);
studentClass.setJoinTime(new Date());
studentClassMapper.insertOrUpdateStudentClass(studentClass);
logger.info("为学员 {} 更新班级为 {} 成功", infoNo, className);
} catch (Exception ex) {
logger.warn("为学员 {} 分配班级时失败: {}", infoNo, ex.getMessage());
}
```
需要添加两个新的Mapper方法
```xml
<!-- SysStudentClassMapper.xml -->
<!-- 禁用学员的所有活跃班级 -->
<update id="deactivateStudentClasses">
UPDATE student_class
SET status = 0
WHERE student_id = #{studentId} AND status = 1
</update>
<!-- 插入或更新班级 -->
<insert id="insertOrUpdateStudentClass">
INSERT INTO student_class (student_id, class_id, status, join_time)
VALUES (#{studentId}, #{classId}, #{status}, #{joinTime})
ON DUPLICATE KEY UPDATE
status = VALUES(status),
join_time = VALUES(join_time)
</insert>
```
---
## ⚠️ **重要提醒**
### **优先级**
1. **最高优先级:** 重新编译和重启修复Mapper
2. **高优先级:** 执行数据库索引SQL
3. **中优先级:** 简化班级处理
4. **低优先级:** 其他优化
### **建议流程**
1. ✅ 修复Mapper已完成
2. ⚠️ **重新编译和重启**
3. ⚠️ 执行索引SQL
4. ✅ 测试导入100条数据
5. 📊 评估速度是否满意
6. 💡 如需进一步优化,再考虑其他方案
---
## 📋 **快速决策表**
### **如果修复后速度满意(<30秒/100条**
✅ 不需要进一步优化
### **如果还需要更快(目标<15秒/100条**
✅ 实施"简化班级处理"
### **如果需要极致速度(目标<5秒/100条**
✅ 实施"批量操作"
---
## ✅ **总结**
### **当前状态**
- ✅ Mapper重复字段已修复
- ✅ 进度更新频率已优化10条
- ⚠️ 需要重新编译和重启
- ⚠️ 建议执行数据库索引SQL
### **预期效果**
仅修复Mapper后
- 每条从 3700ms 降到 2300ms
- 100条从 6分钟 降到 4分钟
- **提升约40%**
加上索引优化后:
- 每条约 1000-1500ms
- 100条约 2-2.5分钟
- **提升约60%**
如果再简化班级处理:
- 每条约 500-1000ms
- 100条约 1-1.5分钟
- **提升约75%**
---
**建议先执行阶段1的优化重新编译+索引测试效果后再决定是否需要阶段2** 🎯

View File

@ -1,340 +0,0 @@
# 导入速度慢修复说明
## ❌ **问题现象**
导入100条数据需要2-3分钟比之前慢了好几倍。
### **日志分析**
```
slow sql 1164 millis. update sys_user (每条1.2秒)
slow sql 1165 millis. update student_class (每条1.2秒)
Duplicate entry '234-17' for key 'student_class.uk_student_class'
```
---
## 🔍 **问题原因**
### **原因1班级分配逻辑错误**
**错误逻辑:**
1. 将旧班级设置为 `status=0`
2. 插入新班级记录
**问题:**
- 如果新旧班级ID相同INSERT失败Duplicate
- 每次都UPDATE然后INSERT即使班级未变
### **原因2数据库缺少索引**
- `sys_user.user_name` 无索引
- `student_class.student_id` 无索引
- 导致查询很慢1.2秒/条)
### **原因3重复的数据库操作**
- 每条记录都查询班级列表
- 每条记录都UPDATE status
- 没有必要的缓存
---
## ✅ **已修复内容**
### **修复1优化班级分配逻辑**
**文件:** `StudyClassUserServiceImpl.java` (第1119-1181行)
**新逻辑:**
```java
// 1. 检查是否已在该班级
if (已在该班级) {
if (状态不是活跃) {
// 只需UPDATE状态
UPDATE status = 1
}
// 否则什么都不做
}
else {
// 2. 禁用其他班级
UPDATE 其他班级 status = 0
// 3. 插入新班级
INSERT 新班级记录
}
```
**优势:**
- ✅ 避免Duplicate错误
- ✅ 减少不必要的UPDATE
- ✅ 班级未变时不做任何操作
---
### **修复2创建数据库优化SQL**
**文件:** `log/Sql/optimize_import_performance.sql`
**包含:**
- 添加必要的索引
- 优化表统计信息
- 性能监控查询
---
## 🚀 **部署步骤**
### **步骤1重新编译后端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **步骤2执行数据库优化SQL**
```bash
# 连接数据库
mysql -u root -p ry_study
# 执行优化SQL
source C:/Users/Administrator/Desktop/Project/ry_study-v_03/log/Sql/optimize_import_performance.sql
```
**或者在Navicat中**
1. 打开数据库:`ry_study`
2. 打开查询窗口
3. 粘贴 `optimize_import_performance.sql` 内容
4. 执行
### **步骤3重启服务**
```bash
# 停止旧服务
# 启动新服务
```
### **步骤4测试导入**
1. 准备测试数据100条
2. 执行导入
3. 记录时间
4. 查看日志
---
## 📊 **性能对比**
### **修复前**
| 项目 | 数值 |
|------|------|
| 100条导入时间 | 2-3分钟 |
| 每条平均时间 | 1.2-1.8秒 |
| Duplicate错误 | 频繁出现 |
| 慢查询 | 大量 |
### **修复后(预期)**
| 项目 | 数值 |
|------|------|
| 100条导入时间 | 10-30秒 |
| 每条平均时间 | 0.1-0.3秒 |
| Duplicate错误 | 无 |
| 慢查询 | 基本无 |
**性能提升5-10倍**
---
## 🔧 **添加的索引**
```sql
-- sys_user 表
CREATE INDEX idx_user_name ON sys_user(user_name); -- 快速按信息编号查询
CREATE INDEX idx_nick_name ON sys_user(nick_name); -- 快速按姓名查询
CREATE INDEX idx_prison_area ON sys_user(prison_area); -- 快速按监区查询
-- student_class 表
CREATE INDEX idx_student_id ON student_class(student_id); -- 快速按学员查询
CREATE INDEX idx_class_id ON student_class(class_id); -- 快速按班级查询
CREATE INDEX idx_status ON student_class(status); -- 快速按状态查询
CREATE INDEX idx_student_status ON student_class(student_id, status); -- 复合索引
```
---
## 🧪 **测试验证**
### **测试1导入速度**
```bash
# 1. 生成测试数据
python generate_test_data.py
# 2. 记录开始时间
开始时间: 14:00:00
# 3. 导入test_data.xlsx (100条)
# 4. 记录结束时间
结束时间: 14:00:15
# 5. 计算耗时
耗时: 15秒之前需要2-3分钟
```
### **测试2无Duplicate错误**
```bash
# 查看日志
grep "Duplicate entry" logs/app.log
# 预期结果:无结果
```
### **测试3无慢查询**
```bash
# 查看日志
grep "slow sql" logs/app.log
# 预期结果:无结果或很少
```
---
## 📋 **检查清单**
### **代码修复**
- [x] 修复班级分配逻辑
- [x] 避免Duplicate错误
- [x] 减少不必要的UPDATE
### **数据库优化**
- [ ] 执行优化SQL
- [ ] 添加所有索引
- [ ] 更新表统计信息
### **部署验证**
- [ ] 重新编译后端
- [ ] 重启服务
- [ ] 测试导入100条数据
- [ ] 查看日志确认无错误
---
## 💡 **进一步优化建议**
### **1. 批量操作**
- 批量INSERT每批50条
- 批量UPDATE
- 减少数据库往返次数
### **2. 使用缓存**
- Redis缓存班级映射
- 缓存角色ID
- 减少重复查询
### **3. 异步处理**
- 使用消息队列
- 后台异步导入
- 提高并发性
### **4. 连接池优化**
```yaml
spring:
datasource:
druid:
max-active: 50 # 增加最大连接数
```
---
## ⚠️ **注意事项**
### **1. 数据库备份**
- 执行SQL前务必备份数据库
- 建议先在测试环境验证
### **2. 索引维护**
- 索引会占用磁盘空间
- 写入性能可能略有下降(可忽略)
- 定期ANALYZE TABLE
### **3. 监控**
- 持续监控慢查询日志
- 关注数据库连接数
- 关注内存使用
---
## 🆘 **常见问题**
### **Q1索引创建失败**
```sql
-- 检查是否已存在同名索引
SHOW INDEX FROM sys_user WHERE Key_name = 'idx_user_name';
-- 删除后重新创建
DROP INDEX idx_user_name ON sys_user;
CREATE INDEX idx_user_name ON sys_user(user_name);
```
### **Q2仍然出现Duplicate错误**
- 检查代码是否重新编译
- 检查服务是否重启
- 查看日志确认使用新代码
### **Q3性能没有明显提升**
- 检查索引是否创建成功
- 执行ANALYZE TABLE
- 查看EXPLAIN确认使用了索引
---
## ✅ **验收标准**
- [ ] 100条数据导入时间 < 30秒
- [ ] 无Duplicate错误
- [ ] 无slow sql警告或很少
- [ ] 日志无异常
- [ ] 班级分配正确
---
## 📝 **修改文件清单**
### **后端代码**
1. ✅ `StudyClassUserServiceImpl.java` - 修复班级分配逻辑
### **数据库**
2. ✅ `optimize_import_performance.sql` - 添加索引和优化
### **文档**
3. ✅ `导入性能优化方案.md` - 详细分析
4. ✅ `导入速度慢修复说明.md` - 修复说明(本文档)
---
## 🎯 **总结**
### **核心问题**
1. 班级分配逻辑错误 → Duplicate错误
2. 缺少数据库索引 → 慢查询
3. 重复的数据库操作 → 性能差
### **解决方案**
1. 修复班级分配逻辑
2. 添加必要的索引
3. 优化数据库配置
### **预期效果**
- **性能提升5-10倍**
- **无错误无Duplicate**
- **体验更好:导入更流畅**
---
**现在请按照步骤执行修复,完成后测试导入速度!** 🚀

View File

@ -1,443 +0,0 @@
# 导入速度慢问题完整解决方案
## 🔴 **问题现状**
从日志分析,发现系统存在严重性能问题:
### **查询性能问题**
| 查询 | 单次耗时 | 影响 |
|------|---------|------|
| `countUserRoleByUserId` | **0.58秒** | 每个用户查询2次 |
| `selectRolePermissionByUserId` | **0.58秒** | 每个用户查询2次 |
| `selectCoursewareList` | **0.78秒** | 课程页面加载慢 |
| `selectLearningDetailList` | **0.58秒** | 学习记录加载慢 |
### **影响**
- ❌ 导入 100 个用户预计需要 **10-15 分钟**(理论应该 1-2 分钟)
- ❌ 更新 10 个用户需要 **2-3 分钟**
- ❌ 页面加载需要 **5-10 秒**
---
## 🎯 **根本原因**
### **原因1冗余索引导致写入慢最严重**
**问题:** `student_class` 表有冗余索引
```sql
✅ uk_student_class (student_id, class_id) -- 已覆盖 student_id
❌ idx_student_id (student_id) -- 完全冗余!
```
**影响:**
- 每次 INSERT/UPDATE 需要维护多个冗余索引
- 写入速度降低 50%+
- 系统整体变慢
**证据:**
- 您之前添加了索引后系统变慢
- 您打开了 `fix_redundant_indexes.sql` 但还没执行
---
### **原因2查询缺少必要的索引**
#### **问题Asys_user_role 表**
```sql
-- 慢查询
SELECT COUNT(1) FROM sys_user_role WHERE user_id = ?
-- 0.58秒!
-- 原因:缺少 user_id 索引
```
#### **问题Bcourseware 表**
```sql
-- 慢查询
SELECT * FROM courseware WHERE course_id = ?
-- 0.78秒!
-- 原因:缺少或未使用 course_id 索引
```
#### **问题Clearning_detail 表**
```sql
-- 慢查询
SELECT * FROM learning_detail WHERE student_id = ? AND course_id = ?
-- 0.58秒!
-- 原因:缺少复合索引 (student_id, course_id)
```
---
## ✅ **完整解决方案**
### **步骤1删除冗余索引最关键**
**在 Navicat 中执行:**
```
文件log/Sql/fix_redundant_indexes.sql
```
**效果:**
- ✅ 删除 `student_class.idx_student_id`(冗余)
- ✅ 写入速度提升 50%+
- ✅ 导入速度提升 2-3 倍
**预计时间:** 1-2 分钟
---
### **步骤2优化查询索引**
**在 Navicat 中执行:**
```
文件log/Sql/fix_slow_queries.sql
```
**效果:**
- ✅ 添加 `sys_user_role(user_id)` 索引
- ✅ 添加 `sys_user_role(role_id)` 索引
- ✅ 添加 `sys_role(del_flag)` 索引
- ✅ 添加 `courseware(course_id)` 索引
- ✅ 添加 `learning_detail(student_id, course_id)` 复合索引
- ✅ 查询速度从 0.5-0.8秒 降低到 **10-50ms**
**预计时间:** 2-3 分钟
---
### **步骤3重启应用服务**
```bash
# 停止服务
# 启动服务
```
---
### **步骤4验证效果**
#### **验证1查询速度**
在 Navicat 中测试:
```sql
-- 测试角色查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM sys_user_role WHERE user_id = 455;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';
-- 测试课程查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM courseware WHERE course_id = 6;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';
-- 测试学习详情查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM learning_detail WHERE student_id = 9999 AND course_id = 6;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';
```
#### **验证2导入速度**
- 导入 100 条数据
- **修复前:** 10-15 分钟
- **修复后:** 1-2 分钟 ✅
#### **验证3更新速度**
- 更新 10 条数据
- **修复前:** 2-3 分钟
- **修复后:** 10-30 秒 ✅
#### **验证4页面加载速度**
- 打开用户列表页面
- **修复前:** 5-10 秒
- **修复后:** <2
---
## 📊 **优化前后对比**
### **查询性能**
| 查询类型 | 优化前 | 优化后 | 提升 |
|---------|--------|--------|------|
| 角色查询 | 580ms | **10-50ms** | 10-50倍 |
| 课程查询 | 780ms | **10-50ms** | 15-70倍 |
| 学习详情 | 580ms | **10-50ms** | 10-50倍 |
### **导入性能**
| 场景 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 导入100条 | 10-15分钟 | **1-2分钟** | 5-10倍 |
| 更新10条 | 2-3分钟 | **10-30秒** | 4-10倍 |
| 页面加载 | 5-10秒 | **<2秒** | 3-5倍 |
---
## 🔍 **技术原理**
### **为什么冗余索引会慢?**
```sql
-- 有冗余索引时:
INSERT INTO student_class (student_id, class_id) VALUES (1, 2);
-- MySQL 需要更新:
✅ PRIMARY KEY
✅ uk_student_class (student_id, class_id) -- 已经包含 student_id
❌ idx_student_id (student_id) -- 冗余!需要额外更新
-- 结果:写入时间增加 50%+
```
### **为什么需要复合索引?**
```sql
-- 查询:
WHERE student_id = ? AND course_id = ?
-- 单独索引:
idx_student_id (student_id) -- 只能用于 student_id
idx_course_id (course_id) -- 只能用于 course_id
-- 复合索引:
idx_student_course (student_id, course_id) -- 同时用于两个条件!速度快!
```
### **为什么 uk_student_class 可以替代 idx_student_id**
**MySQL 最左前缀原则:**
```sql
-- uk_student_class (student_id, class_id) 可以用于:
WHERE student_id = ? ✅ 使用索引
WHERE student_id = ? AND class_id = ? ✅ 使用索引
WHERE class_id = ? ❌ 不使用索引
-- 所以不需要单独的 idx_student_id
```
---
## ⚠️ **注意事项**
### **1. 备份数据库**
执行 SQL 脚本前,建议先备份:
```sql
-- 导出备份
mysqldump -u root -p study > study_backup_$(date +%Y%m%d).sql
```
### **2. 执行顺序**
**必须按照以下顺序执行:**
1. ✅ **先执行** `fix_redundant_indexes.sql`(删除冗余索引)
2. ✅ **再执行** `fix_slow_queries.sql`(添加必要索引)
3. ✅ **最后** 重启服务
**不要反过来!**
### **3. 执行时间**
- 冗余索引删除1-2 分钟
- 查询索引优化2-3 分钟
- 总计3-5 分钟
### **4. 服务影响**
- 执行过程中可能短暂锁表
- 建议在低峰期执行
- 或者先停止服务再执行
---
## 📋 **完整操作清单**
### **准备阶段**
- [ ] 备份数据库
- [ ] 通知用户维护时间
- [ ] 准备回滚脚本(如果需要)
### **执行阶段**
- [ ] 在 Navicat 中打开 `fix_redundant_indexes.sql`
- [ ] 执行脚本,确认删除冗余索引
- [ ] 在 Navicat 中打开 `fix_slow_queries.sql`
- [ ] 执行脚本,确认添加必要索引
- [ ] 查看脚本输出,确认优化成功
- [ ] 重启应用服务
### **验证阶段**
- [ ] 测试角色查询速度(<50ms
- [ ] 测试课程查询速度(<50ms
- [ ] 测试学习详情查询速度(<50ms
- [ ] 导入 100 条数据测试1-2分钟
- [ ] 更新 10 条数据测试10-30秒
- [ ] 打开页面测试加载速度(<2秒
### **确认阶段**
- [ ] 检查应用日志,无异常
- [ ] 检查数据库慢查询日志
- [ ] 用户确认速度提升
- [ ] 记录优化结果
---
## 🚨 **如果优化后仍然慢**
### **检查1是否执行了两个脚本**
```sql
-- 检查冗余索引是否已删除
SELECT INDEX_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'study'
AND TABLE_NAME = 'student_class'
AND INDEX_NAME = 'idx_student_id';
-- 应该返回空结果!
```
### **检查2是否重启了服务**
- MySQL 连接可能缓存了旧的查询计划
- 重启服务可以强制重新规划
### **检查3数据库服务器资源**
```sql
-- 检查数据库连接数
SHOW PROCESSLIST;
-- 检查慢查询
SHOW VARIABLES LIKE 'slow_query_log%';
-- 检查缓冲池
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
```
### **检查4导入逻辑本身**
如果查询已经很快,但导入仍然慢:
- 检查是否有其他业务逻辑耗时
- 检查是否有外部API调用
- 检查是否有大量日志输出
- 检查是否有文件IO操作
---
## 💡 **长期优化建议**
### **1. 定期分析表**
```sql
-- 每周执行一次
ANALYZE TABLE sys_user;
ANALYZE TABLE sys_user_role;
ANALYZE TABLE student_class;
ANALYZE TABLE courseware;
ANALYZE TABLE learning_detail;
```
### **2. 监控慢查询**
启用慢查询日志:
```sql
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1秒以上算慢查询
```
### **3. 定期检查索引**
```sql
-- 查找冗余索引
SELECT
table_name,
index_name,
GROUP_CONCAT(column_name ORDER BY seq_in_index) AS columns
FROM information_schema.statistics
WHERE table_schema = 'study'
GROUP BY table_name, index_name
ORDER BY table_name, index_name;
```
### **4. 优化批量操作**
- 导入时使用批量插入
- 减少单条 INSERT改用 INSERT INTO ... VALUES (...), (...), (...)
- 临时禁用索引,导入后重建
---
## 📞 **获取帮助**
如果按照上述步骤操作后仍有问题:
1. **检查应用日志**
```
Study-Vue-redis/logs/
```
2. **检查数据库慢查询日志**
```sql
SHOW VARIABLES LIKE 'slow_query_log_file';
```
3. **提供详细信息**
- 执行的 SQL 脚本输出
- 测试查询的耗时
- 导入测试的日志
- 数据库版本和配置
---
## ✅ **总结**
### **核心问题**
1. ❌ 冗余索引导致写入慢
2. ❌ 缺少必要索引导致查询慢
### **解决方案**
1. ✅ 删除冗余索引(`fix_redundant_indexes.sql`
2. ✅ 添加必要索引(`fix_slow_queries.sql`
3. ✅ 重启服务
### **预期效果**
- ✅ 查询速度0.5-0.8秒 → **10-50ms**(提升 10-70倍
- ✅ 导入速度10-15分钟 → **1-2分钟**(提升 5-10倍
- ✅ 更新速度2-3分钟 → **10-30秒**(提升 4-10倍
- ✅ 页面加载5-10秒 → **<2秒**提升 3-5倍
---
**现在请按照步骤执行,应该能彻底解决速度问题!** 🚀
**关键:先删除冗余索引,再添加必要索引,最后重启服务!** ⚠️

View File

@ -1,367 +0,0 @@
# 屏幕流监控功能实现方案
## 一、功能概述
基于 UniApp 开发的安卓 App实现局域网内后台按需调取学习端屏幕画面的功能。
### 核心需求
1. **按需启动**:后台管理端点击特定学生账号后,触发该账号绑定的安卓 App 启动屏幕捕获
2. **实时传输**:将学习画面实时传输到后台
3. **资源节省**:不点击时 App 不进行任何捕获和传输操作
4. **局域网传输**:全程在局域网内传输,不依赖外网
5. **权限兼容**:适配安卓 10+ 版本,处理好屏幕捕获权限、后台保活
## 二、技术方案
### 2.1 架构设计
```
┌─────────────────┐ WebSocket ┌─────────────────┐
│ 学生端 App │ ◄─────────────────────────► │ 后台管理端 │
│ (UniApp) │ │ (RuoYi) │
│ │ │ │
│ - 屏幕捕获 │ │ - 指令控制 │
│ - WebSocket │ │ - 画面显示 │
│ - 按需启动 │ │ - 学生管理 │
└─────────────────┘ └─────────────────┘
```
### 2.2 技术选型
**前端UniApp**
- 屏幕捕获:使用 `plus.screen.capture` API安卓原生
- 通信协议WebSocket实时双向通信
- 图片编码Base64便于 WebSocket 传输)
- 后台保活:使用 `FOREGROUND_SERVICE` 权限
**后端RuoYi**
- WebSocket 服务器Spring WebSocket
- 控制接口RESTful API
- 画面转发WebSocket 消息转发
### 2.3 数据流程
1. **启动监控流程**
```
后台点击学生 → 调用控制接口 → 发送 WebSocket 指令 → 学生端接收指令 → 开始屏幕捕获 → 传输画面
```
2. **停止监控流程**
```
后台点击停止 → 调用控制接口 → 发送 WebSocket 指令 → 学生端接收指令 → 停止屏幕捕获
```
3. **画面传输流程**
```
学生端捕获屏幕 → Base64 编码 → WebSocket 发送 → 后台接收 → 解码显示
```
## 三、实现细节
### 3.1 学生端实现UniApp
#### 3.1.1 屏幕流捕获工具类 (`screenStream.js`)
**核心功能:**
- WebSocket 连接管理
- 接收后台指令(启动/停止)
- 屏幕捕获和传输
- 自动重连机制
- 后台保活
**关键代码:**
```javascript
// 连接 WebSocket
connect() {
const wsUrl = this.getWebSocketUrl()
this.ws = uni.connectSocket({
url: wsUrl,
header: {
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
}
})
}
// 开始捕获
startCapture(interval = 500) {
this.isCapturing = true
this.captureTimer = setInterval(() => {
this.captureAndSend()
}, interval)
}
// 捕获并发送
async captureAndSend() {
const screenshotData = await this.captureScreen()
this.sendMessage({
type: 'screen_frame',
userId: this.userId,
data: screenshotData,
timestamp: Date.now()
})
}
```
#### 3.1.2 权限配置
`manifest.json` 中添加必要权限:
```json
{
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
]
}
}
```
#### 3.1.3 应用初始化
`App.vue` 中初始化屏幕流服务:
```javascript
onShow() {
const token = uni.getStorageSync('token')
if (token) {
this.initScreenStream()
}
}
```
### 3.2 后端实现RuoYi
#### 3.2.1 WebSocket 处理器 (`ScreenStreamWebSocketHandler.java`)
**核心功能:**
- 管理学生端连接
- 管理监控端连接
- 转发屏幕帧数据
- 发送控制指令
**关键代码:**
```java
@ServerEndpoint("/ws/screenStream/{userId}")
@Component
public class ScreenStreamWebSocketHandler {
// 学生端连接映射
private static ConcurrentHashMap<String, ScreenStreamWebSocketHandler> studentConnections;
// 监控端连接映射
private static ConcurrentHashMap<String, ScreenStreamWebSocketHandler> monitorConnections;
// 发送指令给学生端
public static void sendCommandToStudent(String studentUserId, String command, Integer interval) {
ScreenStreamWebSocketHandler handler = studentConnections.get(studentUserId);
if (handler != null) {
handler.sendMessage(JSON.toJSONString(cmd));
}
}
}
```
#### 3.2.2 控制接口 (`ScreenStreamController.java`)
**接口列表:**
- `POST /study/screenStream/start` - 启动屏幕流捕获
- `POST /study/screenStream/stop` - 停止屏幕流捕获
- `POST /study/screenStream/checkOnline` - 检查学生是否在线
- `POST /study/screenStream/onlineCount` - 获取在线学生数
#### 3.2.3 安全配置
`SecurityConfig.java` 中添加 WebSocket 路径免认证:
```java
.antMatchers("/ws/**").permitAll()
```
### 3.3 后台管理端实现
#### 3.3.1 屏幕流监控页面 (`screenStream/index.vue`)
**功能特性:**
- 学生列表展示(带在线状态)
- 点击学生开始/停止监控
- 实时显示屏幕画面
- WebSocket 接收画面数据
**关键代码:**
```javascript
// 连接 WebSocket 接收画面
connectWebSocket(studentId) {
const wsUrl = `ws://${host}/ws/screenStream/${studentId}`
this.ws = new WebSocket(wsUrl)
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data)
if (message.type === "screen_frame") {
this.currentScreenFrame = `data:image/jpeg;base64,${message.data}`
}
}
}
// 启动监控
async startMonitor() {
await startScreenStream(this.currentStudent.userId, 500)
this.currentStudent.isMonitoring = true
this.connectWebSocket(this.currentStudent.userId)
}
```
## 四、部署说明
### 4.1 前端部署
1. **配置服务器地址**
- 修改 `frontend-uniapp/src/utils/config.js` 中的 `SERVER_HOST`
- 确保 WebSocket 地址正确
2. **编译打包**
```bash
cd frontend-uniapp
npm run build:app
```
3. **权限说明**
- 安卓 10+ 需要用户手动授予屏幕捕获权限
- 首次启动时会请求相关权限
### 4.2 后端部署
1. **确保 WebSocket 支持**
- 检查 `pom.xml` 中已包含 `spring-boot-starter-websocket` 依赖
2. **配置安全策略**
- WebSocket 路径 `/ws/**` 已配置为免认证
- 实际认证在 WebSocket 处理器中处理
3. **启动服务**
```bash
cd RuoYi-Vue-redis
mvn clean install
java -jar ry-news-admin/target/ry-news-admin.jar
```
## 五、性能优化
### 5.1 传输优化
1. **图片质量调整**
- 默认质量 0.7(可调整)
- 根据网络情况动态调整
2. **捕获频率**
- 默认 500ms约 2 帧/秒)
- 可根据需求调整
3. **数据压缩**
- 使用 JPEG 格式(比 PNG 小)
- Base64 编码传输
### 5.2 资源管理
1. **按需启动**
- 只有后台点击时才启动捕获
- 不监控时完全停止,节省资源
2. **连接管理**
- 自动重连机制
- 连接断开时停止捕获
3. **后台保活**
- 使用前台服务权限
- 确保应用在后台时也能运行
## 六、注意事项
### 6.1 权限问题
1. **屏幕捕获权限**
- 安卓 10+ 需要 `MediaProjection` 权限
- 需要用户手动授权
2. **后台运行权限**
- 需要 `FOREGROUND_SERVICE` 权限
- 在 `manifest.json` 中已配置
### 6.2 兼容性
1. **安卓版本**
- 最低支持Android 5.0 (API 21)
- 推荐Android 10+ (API 29+)
2. **网络要求**
- 必须在同一局域网内
- 确保防火墙允许 WebSocket 连接
### 6.3 安全性
1. **认证机制**
- WebSocket 连接时携带 Token
- 后端验证用户身份
2. **数据加密**
- 建议在生产环境使用 WSSWebSocket Secure
- 局域网环境可考虑使用 HTTP
## 七、测试建议
### 7.1 功能测试
1. **连接测试**
- 测试学生端 WebSocket 连接
- 测试后台管理端连接
2. **指令测试**
- 测试启动/停止指令
- 测试指令响应时间
3. **画面传输测试**
- 测试画面清晰度
- 测试传输延迟
- 测试多学生同时监控
### 7.2 性能测试
1. **资源占用**
- 监控 CPU 和内存占用
- 监控网络带宽占用
2. **稳定性测试**
- 长时间运行测试
- 网络中断恢复测试
## 八、后续优化方向
1. **WebRTC 方案**
- 如果 WebSocket 方案性能不足,可考虑 WebRTC
- 需要开发原生插件支持
2. **流媒体服务器**
- 使用 RTMP/RTSP 协议
- 需要部署流媒体服务器(如 SRS
3. **画面录制**
- 支持录制监控画面
- 保存为视频文件
4. **多画面监控**
- 支持同时监控多个学生
- 画面分屏显示
## 九、常见问题
### Q1: 屏幕捕获失败?
**A:** 检查是否授予屏幕捕获权限,安卓 10+ 需要用户手动授权。
### Q2: WebSocket 连接失败?
**A:** 检查网络连接、防火墙设置,确保在同一局域网内。
### Q3: 画面延迟高?
**A:** 可以调整捕获间隔(默认 500ms或降低图片质量。
### Q4: 应用后台时停止捕获?
**A:** 检查是否授予后台运行权限,确保 `FOREGROUND_SERVICE` 权限已配置。
### Q5: 多学生同时监控性能问题?
**A:** 可以限制同时监控的学生数量,或使用流媒体服务器方案。

View File

@ -1,201 +0,0 @@
# 快速修复总结
## ✅ **已修复的问题**
### **1. 进度不显示**
- ❌ 之前每100条更新一次100条数据只更新2次
- ✅ 现在每5条更新一次100条数据更新20次
- 🎯 **效果:实时看到进度变化**
### **2. 中断时显示"没有导入任何数据"**
- ❌ 之前:中断时结果保存不正确
- ✅ 现在:使用 `progressManager.cancelTask()` 正确保存
- 🎯 **效果:中断时显示已处理的详细信息**
### **3. 看不到正在做什么**
- ❌ 之前:只有简单的进度日志
- ✅ 现在:每条记录都有详细日志
- 🎯 **效果:知道正在处理哪条记录**
### **4. 导入速度慢**
- ❌ 之前班级分配逻辑错误重复UPDATE+INSERT
- ✅ 现在:优化逻辑,班级未变时不操作
- 🎯 **效果:减少不必要的数据库操作**
---
## 📋 **修改清单**
### **StudyClassUserServiceImpl.java**
1. **第905行** - 进度更新频率
```java
int updateInterval = 5; // 从100改为5
```
2. **第936行** - 中断时保存结果
```java
progressManager.cancelTask(taskId, resultMsg.toString());
```
3. **第998-1000行** - 新增用户日志
```java
logger.info("正在新增用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.insertUser(user);
logger.info("用户新增成功: 信息编号={}, userId={}", infoNo, user.getUserId());
```
4. **第1092-1094行** - 更新用户日志
```java
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
logger.info("用户更新成功: 信息编号={}", infoNo);
```
5. **第1124行** - 班级处理日志
```java
logger.info("正在处理用户班级: 信息编号={}, 班级={}", infoNo, className);
```
6. **第1235-1237行** - 进度日志格式
```java
String progressInfo = String.format("[%d/%d] 新增:%d 更新:%d 失败:%d 跳过:%d",
i + 1, totalSize, successNum, updateNum, errorNum, duplicateNum);
logger.info("任务 {} 进度: {}", taskId, progressInfo);
```
---
## 🚀 **部署步骤3步**
### **1. 重新编译**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **2. 重启服务**
停止旧服务,启动新服务
### **3. 测试导入**
导入100条数据观察
- ✅ 进度是否实时更新
- ✅ 中断是否显示详情
- ✅ 日志是否详细
---
## 📊 **预期效果**
### **进度显示**
```
[5/100] 新增:5 更新:0 失败:0 跳过:0 (5%)
[10/100] 新增:10 更新:0 失败:0 跳过:0 (10%)
[15/100] 新增:15 更新:0 失败:0 跳过:0 (15%)
...
[100/100] 新增:90 更新:5 失败:3 跳过:2 (100%)
```
### **详细日志**
```
[INFO] 正在新增用户: 信息编号=201, 姓名=张三
[INFO] 用户新增成功: 信息编号=201, userId=1001
[INFO] 正在处理用户班级: 信息编号=201, 班级=中级班1班
[INFO] 为学员 201 分配班级 中级班1班 成功
[INFO] 任务 xxx 进度: [5/100] 新增:5 更新:0 失败:0 跳过:0
```
### **中断显示**
```
导入已取消!
新增: 20 条
更新: 8 条
失败: 2 条
无变化跳过: 0 条
未处理: 70 条
失败详情:
1、信息编号 205罪犯姓名 王五 导入失败:班级 [三班] 不存在
2、信息编号 210罪犯姓名 赵六 导入失败:监区不能为空
```
---
## ⚠️ **重要提醒**
### **1. 日志量增加**
- 每条记录约2-3条日志
- 100条数据约200-300条日志
- 建议查看完日志后定期清理
### **2. 进度更新频率**
- 当前为每5条更新一次
- 可根据需要调整3-10条都可以
- 如果数据量特别大(>1000条可以改为每10条
### **3. 性能优化**
- 已优化班级分配逻辑
- 数据库索引需要执行SQL重要
- 建议执行 `optimize_import_performance.sql`
---
## 📝 **快速检查清单**
- [ ] 代码已重新编译
- [ ] 服务已重启
- [ ] 导入100条数据测试
- [ ] 进度条实时更新
- [ ] 日志显示详细信息
- [ ] 中断功能正常
- [ ] 中断后显示详情
---
## 🆘 **如果还是慢**
### **1. 执行数据库优化SQL**
```sql
-- 必须执行!添加索引
source C:/Users/Administrator/Desktop/Project/ry_study-v_03/log/Sql/optimize_import_performance.sql
```
### **2. 检查数据库连接**
- 查看数据库连接池配置
- 确认数据库响应速度
### **3. 查看日志定位瓶颈**
```bash
# 查看慢查询
grep "slow sql" logs/app.log
# 查看处理时间
grep "进度:" logs/app.log
```
---
## 📚 **相关文档**
1. ✅ `导入进度显示优化说明.md` - 详细说明
2. ✅ `导入性能优化方案.md` - 性能分析
3. ✅ `导入速度慢修复说明.md` - 速度优化
4. ✅ `Sql/optimize_import_performance.sql` - 数据库优化
---
## ✅ **总结**
### **核心改进**
- 进度更新100条 → 5条
- 日志详细:简单 → 详细
- 中断显示:无信息 → 完整信息
- 班级分配:重复操作 → 智能判断
### **用户体验**
- ⚡ 实时看到进度
- 🔍 知道正在做什么
- ✅ 中断有明确反馈
- 📊 导入结果清晰
**现在重新编译并测试,应该能看到明显改善!** 🎯

View File

@ -1,621 +0,0 @@
# 在线学习系统实施计划
## 项目架构设计
### 系统架构
```
┌─────────────────┐ ┌─────────────────┐
│ 管理后台 │ │ UniApp客户端 │
│ (Vue Web) │ │ (Vue) │
└────────┬────────┘ └────────┬────────┘
│ │
│ HTTP/WebSocket │
└───────────┬───────────────┘
┌───────────▼───────────┐
│ Java后端服务 │
│ (Spring Boot) │
└───────────┬───────────┘
┌───────────▼───────────┐
│ MySQL数据库 │
└───────────────────────┘
```
### 技术选型
#### 前端UniApp
- **框架**UniApp + Vue 3
- **UI组件库**uView UI 或 uni-ui
- **状态管理**Vuex 或 Pinia
- **网络请求**uni.request 封装
- **文件上传**uni.uploadFile
- **屏幕截图**使用canvas或plus API实现
- **视频播放**video组件支持进度跟踪
- **图表库**echarts-for-uniapp用于成绩统计图表
- **平台支持**iOS 12+、Android 8.0+
- **横屏支持**:强制横屏或横竖屏自适应
#### 后端Java
- **框架**Spring Boot 2.7+ 或 3.x
- **安全框架**Spring Security简化版仅账号密码
- **ORM框架**MyBatis-Plus
- **文件存储**:本地文件系统(局域网部署)
- **WebSocket**Spring WebSocket用于实时监控
- **权限控制**基于角色的权限控制RBAC
- **文件处理**
- PDF处理Apache PDFBox用于PDF生成和打印
- Excel处理EasyExcel用于数据导入导出
- 视频处理FFmpeg视频转码在保证清晰度的情况下压缩文件大小
- **图表生成**JFreeChart 或 ECharts Java用于后端图表生成
#### 数据库
- **版本**MySQL 5.7+ 或 8.0+
- **字符集**utf8mb4
#### 语音评测服务
- **候选方案**
1. 科大讯飞语音评测(价格中等,准确率高)
2. 百度语音识别(价格较低)
3. 腾讯云语音识别(价格中等)
4. 阿里云语音识别(价格中等)
- **选择原则**:根据实际使用量和价格选择
## 功能模块划分
### 后端模块
1. **用户认证模块**auth
- 登录、登出
- 权限验证
- Token管理
2. **用户管理模块**user
- 管理员管理
- 学员管理
- 角色权限管理
3. **班级管理模块**class
- 班级CRUD
- 学员分配
- 班级查询
4. **课件管理模块**courseware
- 课件上传
- 课件分类
- 课件查询
- 文件存储管理
5. **课程管理模块**course
- 课程创建
- 课程发布
- 课程分配
- 学习进度跟踪
6. **学习监控模块**monitor
- 屏幕截图接收
- 实时监控
- 历史记录查询
7. **语音评测模块**voice
- 语音上传
- 语音评测接口调用
- 评测结果存储
8. **考试模块**exam
- 题库管理
- 试卷生成
- 考试发布
- 自动评分
- 成绩统计
9. **成绩管理模块**score
- 成绩查询
- 成绩统计
- 成绩单生成PDF
10. **数据导入导出模块**import-export
- Excel导入
- 数据导出
- 模板下载
### 前端模块UniApp
1. **登录模块**
- 学员登录
- 自动登录Token保存
2. **课程学习模块**
- 课程列表(显示分配的课程)
- 课件查看:
- 图文课件PDF、图片、文档在线查看
- 视频课件:视频播放,支持横屏全屏
- **学习进度跟踪**
- 视频播放进度实时上报每5-10秒
- 记录学习开始和结束时间
- 记录视频播放位置
- 累计学习时长和次数
- 学习记录本地缓存
3. **语音练习模块**
- 课文显示
- 语音录制使用uni.getRecorderManager
- 语音上传
- 评测结果展示
- 评测历史记录
4. **考试模块**
- 考试列表(显示分配的考试)
- 答题界面:
- 单选题、多选题、判断题、填空题
- 答题进度显示
- 考试倒计时
- 考试提交
- 成绩查看
- 重考功能(如果允许)
5. **个人中心模块**
- 个人信息
- 学习记录查看
- 学习进度查看(视频播放进度、学习次数、观看时长)
- 成绩查询
- 语音评测记录
6. **屏幕监控模块**
- 定时截图每30秒或可配置
- 截图上传到服务器
- 后台运行(保活机制)
### 管理后台模块Vue Web
1. **仪表盘**
- 数据统计(管理员和教师分别显示)
- 快捷操作
- 权限控制显示
2. **用户管理**(仅管理员)
- 学员管理
- 教师管理
- 管理员管理
- 角色权限分配
3. **班级管理**
- 班级列表(教师只能看到自己管理的班级)
- 学员分配
- 教师分配(仅管理员)
4. **课件管理**
- 课件上传(需选择学科分类)
- 课件列表
- **学科分类管理**(仅管理员)
- 课件删除和编辑
5. **课程管理**
- 课程创建
- 课程发布(分配给学员/班级)
- **学习进度查看**(视频播放进度、学习次数、观看时长)
- 学习监控(实时画面和历史记录)
6. **考试管理**
- 题库管理
- 试卷管理
- 考试发布
- **考试规则设置**(教师可设置,管理员可限制重考)
- 重考限制设置(仅管理员)
7. **成绩管理**
- 成绩查询(教师只能查看自己班级)
- **成绩统计图表**(柱状图、折线图、饼图等)
- 成绩单打印PDF导出
- 成绩分析
## 数据库设计(初步规划)
### 核心表结构
#### 1. 用户表sys_user
- id: 主键
- username: 用户名(唯一)
- password: 密码(加密存储)
- real_name: 真实姓名
- role: 角色admin/teacher/student
- status: 状态0:禁用 1:启用)
- create_time: 创建时间
- update_time: 更新时间
#### 2. 学科分类表subject
- id: 主键
- subject_name: 学科名称(如:语文、数学、英语等)
- subject_code: 学科编码
- sort_order: 排序
- status: 状态
- create_time: 创建时间
#### 3. 班级表class
- id: 主键
- class_name: 班级名称
- class_code: 班级编号
- teacher_id: 班主任ID教师用户ID
- description: 班级描述
- status: 状态
- create_time: 创建时间
- update_time: 更新时间
#### 4. 学员班级关联表student_class
- id: 主键
- student_id: 学员ID
- class_id: 班级ID
- join_time: 加入时间
- status: 状态
#### 5. 课件表courseware
- id: 主键
- title: 课件标题
- type: 课件类型text/video/image/document
- file_path: 文件路径
- file_size: 文件大小(字节)
- file_name: 原始文件名
- subject_id: 学科分类ID外键关联subject表
- upload_user_id: 上传人ID
- description: 课件描述
- duration: 视频时长(秒,仅视频类型)
- create_time: 创建时间
- update_time: 更新时间
#### 6. 课程表course
- id: 主键
- course_name: 课程名称
- courseware_id: 课件ID外键关联courseware表
- description: 课程描述
- start_time: 开始时间
- end_time: 结束时间
- create_user_id: 创建人ID
- status: 状态
- create_time: 创建时间
- update_time: 更新时间
#### 7. 课程分配表course_assignment
- id: 主键
- course_id: 课程ID外键关联course表
- student_id: 学员ID可为空表示分配给班级
- class_id: 班级ID可为空表示分配给个人
- assign_time: 分配时间
- assign_user_id: 分配人ID
- status: 状态0:未开始 1:进行中 2:已结束)
- create_time: 创建时间
#### 8. 学习记录表learning_record
- id: 主键
- student_id: 学员ID外键关联sys_user表
- course_id: 课程ID外键关联course表
- progress: 学习进度百分比0-100
- total_duration: 总学习时长(秒,累计)
- learn_count: 学习次数(累计)
- video_progress: 视频播放进度(秒,仅视频类型)
- video_total_duration: 视频总时长(秒,仅视频类型)
- last_learn_time: 最后学习时间
- last_video_position: 最后视频播放位置(秒)
- create_time: 创建时间
- update_time: 更新时间
#### 8.1 学习详情记录表learning_detail
- id: 主键
- learning_record_id: 学习记录ID外键关联learning_record表
- student_id: 学员ID
- course_id: 课程ID
- start_time: 本次学习开始时间
- end_time: 本次学习结束时间
- duration: 本次学习时长(秒)
- video_start_position: 视频开始位置(秒)
- video_end_position: 视频结束位置(秒)
- create_time: 创建时间
#### 9. 学习监控表monitor_record
- id: 主键
- student_id: 学员ID外键关联sys_user表
- course_id: 课程ID外键关联course表可为空
- screenshot_path: 截图路径
- monitor_time: 监控时间
- device_info: 设备信息JSON格式
- ip_address: IP地址
- create_time: 创建时间
#### 10. 语音评测表voice_evaluation
- id: 主键
- student_id: 学员ID外键关联sys_user表
- course_id: 课程ID外键关联course表
- content: 评测内容(课文或文字)
- audio_path: 音频文件路径
- score: 评分(总分)
- accuracy: 准确度0-100
- fluency: 流畅度0-100
- completeness: 完整度0-100
- result_detail: 评测详情JSON格式包含详细评测结果
- create_time: 创建时间
#### 11. 题库表question_bank
- id: 主键
- question_type: 题型single:单选题 multiple:多选题 judge:判断题 fill:填空题)
- question_content: 题目内容
- options: 选项JSON格式["选项A","选项B","选项C","选项D"]
- correct_answer: 正确答案JSON格式多选题和填空题为数组
- score: 分值
- difficulty: 难度1:简单 2:中等 3:困难)
- subject_id: 学科分类ID外键关联subject表
- category: 分类标签
- create_user_id: 创建人ID
- create_time: 创建时间
- update_time: 更新时间
#### 12. 试卷表exam_paper
- id: 主键
- paper_name: 试卷名称
- question_ids: 题目ID列表JSON格式数组
- total_score: 总分
- duration: 考试时长(分钟)
- create_user_id: 创建人ID
- create_time: 创建时间
- update_time: 更新时间
#### 13. 考试表exam
- id: 主键
- exam_name: 考试名称
- paper_id: 试卷ID外键关联exam_paper表
- start_time: 开始时间
- end_time: 结束时间
- duration: 考试时长(分钟,可为空,表示不限制)
- class_id: 班级ID可为空表示分配给个人
- student_id: 学员ID可为空表示分配给班级
- allow_retake: 是否允许重考0:不允许 1:允许,管理员可设置)
- max_retake_times: 最大重考次数(如果允许重考)
- create_user_id: 创建人ID
- status: 状态0:未开始 1:进行中 2:已结束)
- create_time: 创建时间
- update_time: 更新时间
#### 14. 考试记录表exam_record
- id: 主键
- exam_id: 考试ID外键关联exam表
- student_id: 学员ID外键关联sys_user表
- paper_id: 试卷ID外键关联exam_paper表
- answers: 答案JSON格式存储题目ID和答案的映射
- score: 得分
- start_time: 开始时间
- submit_time: 提交时间
- duration: 考试用时(秒)
- status: 状态doing:进行中 completed:已完成)
- retake_count: 重考次数(第几次参加该考试)
- create_time: 创建时间
- update_time: 更新时间
## 开发阶段规划
### 第一阶段基础架构搭建预计1-2周
**目标**:搭建项目基础框架,完成核心功能的基础部分
#### 后端任务
- [x] 创建Spring Boot项目
- [x] 配置数据库连接
- [x] 创建数据库表结构(包含所有核心表)
- [x] 实现用户认证模块登录、Token、权限验证
- [x] 实现基于角色的权限控制RBAC
- [x] 实现基础CRUD接口用户管理、学科分类
- [x] 配置文件上传功能
- [x] 配置跨域和WebSocket跨域已配置WebSocket待实现
- [x] 实现数据权限过滤(教师只能看到自己班级的数据)
#### 前端任务UniApp
- [x] 创建UniApp项目基础结构
- [x] 配置项目结构
- [x] 实现登录页面
- [x] 封装网络请求包含Token管理
- [x] 实现基础路由
- [x] 配置横屏支持
- [x] 实现权限控制(路由守卫)
#### 管理后台任务
- [x] 创建Vue项目基础结构
- [x] 配置路由和状态管理
- [x] 实现登录页面
- [x] 实现基础布局(菜单、权限控制)
- [x] 实现权限控制(路由守卫、菜单权限)
- [x] 集成图表库ECharts
### 第二阶段核心功能开发预计3-4周
**目标**:完成核心业务功能
#### 2.1 用户和班级管理
- [ ] 用户管理(管理员、教师、学员)
- [ ] 学员信息管理CRUD
- [ ] 学员信息批量导入Excel/CSV
- [ ] 班级管理CRUD
- [ ] 学员班级分配
- [ ] 教师班级分配(管理员分配教师管理班级)
- [ ] 权限控制实现(教师只能管理自己的班级)
#### 2.2 课件管理
- [ ] 学科分类管理(管理员)
- [ ] 课件上传功能(需选择学科分类)
- [ ] 课件列表查询(按学科分类、权限过滤)
- [ ] 课件删除功能
- [ ] 视频文件处理FFmpeg转码压缩文件大小
#### 2.3 课程管理
- [ ] 课程创建
- [ ] 课程发布(分配给学员/班级)
- [ ] App端课程列表
- [ ] 课件查看(图文/视频播放)
- [ ] **学习进度跟踪**
- 视频播放进度上报接口
- 学习记录存储(学习次数、观看时长)
- 学习详情记录
- 学习进度查询接口
- [ ] 后台学习进度查看(管理员和教师)
#### 2.4 学习监控
- [ ] App端截图功能
- [ ] 截图上传接口
- [ ] 后台监控查看
- [ ] 历史记录查询
### 第三阶段语音和考试功能预计2-3周
**目标**:完成语音评测和考试模块
#### 3.1 语音评测
- [ ] 调研并选择语音服务商
- [ ] 集成语音评测API
- [ ] 实现语音录制功能
- [ ] 实现评测结果展示
- [ ] 评测记录存储
#### 3.2 考试模块
- [ ] 题库管理题目CRUD按学科分类
- [ ] 试卷生成功能
- [ ] 考试发布功能(分配给学员/班级)
- [ ] 考试规则设置(教师可设置时长、时间等)
- [ ] 重考限制设置(管理员可设置是否允许重考)
- [ ] App端考试界面
- [ ] 自动评分功能
- [ ] 成绩统计(按班级、按学员等)
### 第四阶段成绩管理和优化预计1-2周
**目标**:完成成绩管理和系统优化
#### 4.1 成绩管理
- [ ] 成绩查询功能(权限控制:教师只能查看自己班级)
- [ ] 成绩统计功能
- [ ] **成绩统计图表**(柱状图、折线图、饼图等)
- [ ] 成绩单生成PDF
- [ ] 成绩打印功能
- [ ] 成绩导出功能
#### 4.2 系统优化
- [ ] 性能优化
- [ ] 界面优化
- [ ] 错误处理完善
- [ ] 日志记录
### 第五阶段测试和部署预计1周
**目标**:系统测试和部署准备
#### 5.1 测试
- [ ] 功能测试
- [ ] 兼容性测试
- [ ] 性能测试
- [ ] 安全测试
#### 5.2 部署
- [ ] 部署文档编写
- [ ] 数据库初始化脚本
- [ ] 部署环境配置
- [ ] 上线准备
## 技术难点和解决方案
### 1. 屏幕截图上传
**难点**UniApp如何实现屏幕截图并上传
**解决方案**
- 使用uni.canvasToTempFilePath将页面转为图片
- 或使用plus.screen.capture需5+App环境
- 定时上传截图到服务器
### 2. 视频播放
**难点**UniApp视频播放兼容性
**解决方案**
- 使用video组件
- 支持多种视频格式
- 处理横屏播放
### 3. 语音评测
**难点**:选择合适的语音服务并集成
**解决方案**
- 对比各服务商价格和功能
- 封装统一的语音评测接口
- 支持后续切换服务商
### 4. 文件存储
**难点**:局域网部署的文件存储
**解决方案**
- 使用本地文件系统存储
- 配置静态资源访问
- 考虑文件大小限制
### 5. 实时监控
**难点**:实时查看学生学习画面
**解决方案**
- 使用WebSocket推送最新截图
- 或使用轮询方式获取最新截图
- 考虑性能优化
## 下一步行动
1. **确认需求**:与用户确认需求文档是否完整准确
2. **技术选型确认**:确认技术栈选择
3. **数据库设计**:详细设计数据库表结构
4. **接口设计**:设计前后端接口规范
5. **开始开发**:按照开发阶段逐步实施
## 权限控制设计
### 角色权限矩阵
| 功能模块 | 管理员 | 教师 | 学员 |
|---------|--------|------|------|
| 用户管理 | ✅ 全部 | ❌ | ❌ |
| 教师管理 | ✅ 全部 | ❌ | ❌ |
| 学科分类管理 | ✅ 全部 | ❌ | ❌ |
| 班级管理 | ✅ 全部 | ✅ 仅自己管理的班级 | ❌ |
| 学员管理 | ✅ 全部 | ✅ 仅自己班级的学员 | ❌ |
| 课件管理 | ✅ 全部 | ✅ 上传和管理 | ❌ |
| 课程管理 | ✅ 全部 | ✅ 创建和发布 | ✅ 查看和学习 |
| 学习监控 | ✅ 全部 | ✅ 仅自己班级 | ❌ |
| 学习进度查看 | ✅ 全部 | ✅ 仅自己班级 | ✅ 仅自己 |
| 考试管理 | ✅ 全部 | ✅ 创建和发布 | ✅ 参加考试 |
| 考试规则设置 | ✅ 全部(可限制重考) | ✅ 设置规则 | ❌ |
| 成绩查看 | ✅ 全部 | ✅ 仅自己班级 | ✅ 仅自己 |
| 成绩统计 | ✅ 全部 | ✅ 仅自己班级 | ❌ |
### 数据权限控制
- **教师数据隔离**:教师只能查看和管理自己负责的班级相关数据
- **学员数据隔离**:学员只能查看自己的学习记录和成绩
- **管理员全局权限**:管理员可以查看和管理所有数据
## 视频处理策略
### 视频压缩和优化
- **目标**:在保证清晰度的情况下,文件大小尽量最小
- **方案**
1. 使用FFmpeg进行视频转码
2. 视频编码H.264(兼容性好)
3. 音频编码AAC
4. 分辨率根据原始视频自动适配最大不超过1080p
5. 码率:动态码率,平衡清晰度和文件大小
6. 格式统一转换为MP4格式
### 视频播放进度跟踪
- **跟踪方式**
1. 前端定时上报播放进度每5-10秒
2. 视频暂停/播放/结束事件上报
3. 后端记录每次学习的详细记录
4. 计算总学习时长和播放进度百分比
## 图表展示需求
### 成绩统计图表类型
1. **柱状图**:各班级/学员成绩对比
2. **折线图**:成绩趋势分析(时间维度)
3. **饼图**:成绩分布(优秀、良好、及格、不及格)
4. **雷达图**:多维度成绩分析
5. **表格**:详细成绩数据表格
### 图表展示位置
- **管理后台**:成绩管理页面、仪表盘
- **图表库**前端使用ECharts后端生成使用JFreeChart或ECharts Java
## 已确认事项
**用户角色**:区分管理员和教师,教师只能管理自己的班级,管理员管理所有
**课程分类**:按学科分类,学科分类由管理员管理
**学习进度**:视频播放进度实时跟踪,记录学习次数和观看时间,管理员和教师都可查看
**考试规则**:教师可以自定义,管理员可以限制是否重考
**成绩统计**:需要图表展示(柱状图、折线图、饼图等)
**文件大小**:暂无限制,在保证清晰度的情况下文件大小尽量最小
**设备兼容性**同时兼容iOS和Android兼容主流系统iOS 12+、Android 8.0+

View File

@ -1,193 +0,0 @@
# 教师端登录检查和界面完善方案
## 一、现状分析
### 1.1 登录功能现状
- ✅ 登录页面已有角色切换功能(学员/教师)
- ✅ 登录时传递了`userType`参数
- ❌ **缺少**:登录后验证用户选择的角色是否与后端返回的角色匹配
- ❌ **缺少**:路由守卫,根据角色跳转到不同页面
### 1.2 角色判断机制
- 后端通过用户的`remark`字段判断角色(`注册类型:student`/`注册类型:teacher`
- 或者通过角色`roleKey`判断(`student`/`teacher`
- 前端从`userInfo.role`获取角色(已实现)
### 1.3 教师端功能现状
- ✅ 后端已有教师相关接口:
- `/study/exam/my-exams` - 获取当前教师的考试列表
- `/study/exam/teacher/{teacherId}` - 根据教师ID获取考试列表
- `/study/classUser/allTeachers` - 查询所有教师
- ❌ **缺少**:教师端专用页面和界面
- ❌ **缺少**:教师端首页(显示教师相关功能)
- ❌ **缺少**:教师端考试管理页面
- ❌ **缺少**:教师端学生管理页面
## 二、实现方案
### 2.1 登录检查区分老师和学生
#### 2.1.1 修改登录逻辑(`bzd/src/pages/login/login.vue`
- 登录成功后,验证用户选择的角色(`userRole`)是否与后端返回的角色匹配
- 如果不匹配,提示错误并阻止登录
- 如果匹配,根据角色跳转到对应首页
#### 2.1.2 修改认证工具(`bzd/src/utils/auth.js`
- 在`login`方法中,增加角色验证逻辑
- 从后端返回的`roles`中提取角色,与前端选择的`userType`进行匹配
#### 2.1.3 修改Vuex Store`bzd/src/store/modules/auth.js`
- 在`login` action中增加角色验证
- 保存用户角色信息
#### 2.1.4 添加路由守卫(`bzd/src/App.vue`
- 在`onLaunch`中检查登录状态和角色
- 根据角色跳转到对应首页
### 2.2 完善教师端界面和接口
#### 2.2.1 创建教师端首页(`bzd/src/pages/index/teacher-index.vue`
**功能模块:**
- 我的考试管理(创建、编辑、查看考试)
- 学生管理(查看学生列表、学生成绩)
- 课程管理(查看我教授的课程)
- 数据统计(考试统计、学生统计)
**接口调用:**
- `/study/exam/my-exams` - 获取我的考试列表
- `/study/course/my-courses` - 获取我的课程(需要后端支持教师端)
- `/study/score/teacher/{teacherId}` - 获取学生成绩(需要后端支持)
#### 2.2.2 创建教师端考试管理页面(`bzd/src/pages/exam/teacher-list.vue`
**功能:**
- 显示教师创建的考试列表
- 可以创建新考试(跳转到创建页面)
- 可以编辑、删除考试
- 查看考试详情和统计
**接口调用:**
- `/study/exam/my-exams` - 获取我的考试列表
- `/study/exam/{id}` - 获取考试详情
- `/study/exam` - POST创建考试
- `/study/exam` - PUT更新考试
- `/study/exam/{id}` - DELETE删除考试
#### 2.2.3 创建教师端学生管理页面(`bzd/src/pages/student/list.vue`
**功能:**
- 显示学生列表(按班级分组)
- 查看学生详情
- 查看学生成绩
- 查看学生学习记录
**接口调用:**
- `/study/classUser/students/{classId}` - 获取班级学生列表
- `/study/score/student/{studentId}` - 获取学生成绩
- `/study/learningRecord/student/{studentId}` - 获取学生学习记录
#### 2.2.4 修改首页(`bzd/src/pages/index/index.vue`
- 根据用户角色(`userInfo.role`)显示不同内容
- 如果是教师,显示教师端首页内容
- 如果是学生,显示学生端首页内容(现有逻辑)
#### 2.2.5 修改个人中心(`bzd/src/pages/profile/profile.vue`
- 根据用户角色显示不同的功能菜单
- 教师端显示:考试管理、学生管理、数据统计等
- 学生端显示:学习记录、我的考试、我的成绩等(现有逻辑)
#### 2.2.6 修改底部导航栏(`bzd/src/components/custom-tabbar/custom-tabbar.vue`
- 根据用户角色显示不同的导航项
- 教师端:首页、考试管理、学生管理、我的
- 学生端:首页、课程、考核、我的(现有逻辑)
### 2.3 创建教师端API接口文件
#### 2.3.1 创建教师端API`bzd/src/api/study/teacher.js`
```javascript
// 获取我的考试列表
export function getMyExams() {
return request.get('/study/exam/my-exams')
}
// 获取我的课程列表
export function getMyCourses() {
return request.get('/study/course/my-courses')
}
// 获取学生列表(按班级)
export function getStudentsByClass(classId) {
return request.get(`/study/classUser/students/${classId}`)
}
// 获取所有学生
export function getAllStudents() {
return request.get('/study/classUser/allStudents')
}
```
## 三、文件清单
### 3.1 需要修改的文件
1. `bzd/src/pages/login/login.vue` - 增加角色验证
2. `bzd/src/utils/auth.js` - 增加角色验证逻辑
3. `bzd/src/store/modules/auth.js` - 增加角色验证
4. `bzd/src/App.vue` - 增加路由守卫
5. `bzd/src/pages/index/index.vue` - 根据角色显示不同内容
6. `bzd/src/pages/profile/profile.vue` - 根据角色显示不同菜单
7. `bzd/src/components/custom-tabbar/custom-tabbar.vue` - 根据角色显示不同导航
### 3.2 需要创建的文件
1. `bzd/src/pages/exam/teacher-list.vue` - 教师端考试管理页面
2. `bzd/src/pages/student/list.vue` - 教师端学生管理页面
3. `bzd/src/pages/student/detail.vue` - 学生详情页面
4. `bzd/src/api/study/teacher.js` - 教师端API接口文件
### 3.3 需要配置的文件
1. `bzd/src/pages.json` - 添加新页面路由配置
## 四、实现步骤
### 第一步:登录检查区分
1. 修改`auth.js`,增加角色验证逻辑
2. 修改`login.vue`,登录后验证角色匹配
3. 修改`App.vue`,增加路由守卫
### 第二步:教师端界面
1. 创建教师端API文件
2. 修改首页,根据角色显示不同内容
3. 创建教师端考试管理页面
4. 创建教师端学生管理页面
5. 修改个人中心,根据角色显示不同菜单
6. 修改底部导航栏,根据角色显示不同导航
### 第三步:测试验证
1. 测试教师登录流程
2. 测试学生登录流程
3. 测试角色验证功能
4. 测试教师端功能
5. 测试学生端功能
## 五、注意事项
1. **角色匹配规则**
- 前端选择的角色(`userRole`)必须与后端返回的角色(`userInfo.role`)匹配
- 角色值:`student`(学员)、`teacher`(教师)
2. **接口权限**
- 教师端接口需要后端支持
- 确保后端接口有正确的权限验证
3. **页面路由**
- 教师端和学生端使用不同的页面路由
- 需要在`pages.json`中配置新页面
4. **兼容性**
- 保持学生端现有功能不变
- 教师端功能作为新增功能
## 六、后续优化建议
1. 添加教师端数据统计功能
2. 添加教师端消息通知功能
3. 添加教师端课程管理功能
4. 优化教师端界面UI/UX

View File

@ -1,32 +0,0 @@
-- 1. 检查学习详情表是否有数据
SELECT * FROM learning_detail
WHERE student_id = 452
AND course_id = 1
ORDER BY create_time DESC
LIMIT 20;
-- 2. 检查学习记录表
SELECT * FROM learning_record
WHERE student_id = 452
AND course_id = 1;
-- 3. 检查学习详情表结构
DESC learning_detail;
-- 4. 统计学习详情数量
SELECT
student_id,
course_id,
courseware_id,
COUNT(*) as record_count,
MAX(create_time) as latest_time,
MAX(video_end_position) as max_position
FROM learning_detail
WHERE student_id = 452
AND course_id = 1
GROUP BY student_id, course_id, courseware_id;
-- 5. 检查最近的插入记录(所有学生)
SELECT * FROM learning_detail
ORDER BY create_time DESC
LIMIT 10;

View File

@ -1,341 +0,0 @@
# 用户导入功能优化说明
## 🎯 **问题描述**
### **问题1更新不完整**
- **当前**:导入重复用户时,只更新姓名和编号
- **期望**:更新所有字段(监区、民族、教育水平、犯罪名称、刑期、班级等)
### **问题2班级验证缺失**
- **当前**:班级不存在时只记录警告,继续导入
- **期望**:班级不存在时,导入失败并提示"没有对应的班级,请优先创建"
---
## ✅ **解决方案**
### **方案1已实现的字段更新**
**位置:** `StudyClassUserServiceImpl.java` 第1035-1115行
**当前已更新的字段:**
```java
u.setNickName(prisonerName); // 罪犯姓名
u.setPrisonArea(user.getPrisonArea()); // 监区
u.setPrisonName(user.getPrisonName()); // 监狱名称
u.setSex(user.getSex()); // 性别
u.setEthnicity(user.getEthnicity()); // 民族
u.setEducationLevel(user.getEducationLevel()); // 教育水平
u.setCrimeName(user.getCrimeName()); // 犯罪名称
u.setSentenceTerm(user.getSentenceTerm()); // 刑期
u.setSentenceStartDate(user.getSentenceStartDate()); // 刑期开始
u.setSentenceEndDate(user.getSentenceEndDate()); // 刑期结束
u.setEntryDate(user.getEntryDate()); // 入监时间
u.setStudentStatus(user.getStudentStatus()); // 学员状态
```
**✅ 结论:已经更新所有字段,无需修改!**
---
### **方案2添加班级验证**
#### **修改位置:** `StudyClassUserController.java`
**修改内容:**
1. 在导入前收集所有Excel中的班级名称
2. 查询数据库,检查班级是否存在
3. 如果有班级不存在,抛出异常,阻止整个导入流程
**代码实现:**
```java
// 预先验证所有班级是否存在
logger.info("开始验证班级信息...");
Set<String> uniqueClassNames = new HashSet<>();
for (StudentImportData data : importDataList)
{
if (!isEmptyRow(data) && StringUtils.isNotEmpty(data.getClassName()))
{
uniqueClassNames.add(data.getClassName().trim());
}
}
if (!uniqueClassNames.isEmpty())
{
// 查询所有班级
Map<String, Long> existingClasses = classUserService.getClassNameToIdMap();
// 检查每个班级是否存在
List<String> missingClasses = new ArrayList<>();
for (String className : uniqueClassNames)
{
if (!existingClasses.containsKey(className))
{
missingClasses.add(className);
}
}
// 如果有班级不存在,抛出异常
if (!missingClasses.isEmpty())
{
String errorMsg = String.format("导入失败:以下班级不存在,请先创建班级再导入:%s",
String.join("、", missingClasses));
logger.warn(errorMsg);
throw new ServiceException(errorMsg);
}
logger.info("班级验证通过,共 {} 个班级", uniqueClassNames.size());
}
```
---
## 🔧 **修改的文件**
### **1. StudyClassUserController.java**
**修改内容:**
- 添加导入前的班级验证逻辑
- 添加 `Set``HashSet` 导入
### **2. IStudyClassUserService.java**
**修改内容:**
- 添加 `getClassNameToIdMap()` 方法接口
### **3. StudyClassUserServiceImpl.java**
**修改内容:**
- 实现 `getClassNameToIdMap()` 方法
- 查询所有班级并返回名称到ID的映射
---
## 📋 **导入流程**
### **修改前的流程**
```
1. 解析Excel
2. 转换为用户数据
3. 开始导入
4. 遇到班级不存在 → 记录警告 ❌
5. 继续导入其他用户
6. 导入完成
```
### **修改后的流程**
```
1. 解析Excel
2. 收集所有班级名称 ✅
3. 验证班级是否存在 ✅
4. 如果班级不存在 → 抛出异常,停止导入 ✅
5. 如果班级都存在 → 转换为用户数据
6. 开始导入
7. 导入完成
```
---
## 🧪 **测试场景**
### **测试1班级存在导入成功**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | 二班
```
**前提条件:**
- 数据库中已存在"一班"和"二班"
**预期结果:**
- ✅ 班级验证通过
- ✅ 用户导入成功
- ✅ 用户分配到对应班级
---
### **测试2班级不存在导入失败**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | 三班
1003 | 王五 | C区 | 四班
```
**前提条件:**
- 数据库中只有"一班"和"二班"
- 数据库中没有"三班"和"四班"
**预期结果:**
- ❌ 班级验证失败
- ❌ 提示:"导入失败:以下班级不存在,请先创建班级再导入:三班、四班"
- ❌ 整个导入流程终止
- ❌ 没有任何用户被导入(包括一班的用户)
---
### **测试3重复用户更新所有字段**
**数据库现有数据:**
```
信息编号1001
姓名:张三
监区A区
民族:汉族
教育水平:高中
班级:一班
```
**Excel导入数据**
```
信息编号1001
姓名:张三(改)
监区B区
民族:回族
教育水平:大学
班级:二班
```
**预期结果:**
- ✅ 姓名更新为:张三(改)
- ✅ 监区更新为B区
- ✅ 民族更新为:回族
- ✅ 教育水平更新为:大学
- ✅ 班级更新为:二班
- ✅ 所有字段都被更新
---
### **测试4部分用户有班级部分没有**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | (空)
1003 | 王五 | C区 | 二班
```
**前提条件:**
- 数据库中已存在"一班"和"二班"
**预期结果:**
- ✅ 班级验证通过(空班级不验证)
- ✅ 1001导入成功分配到"一班"
- ✅ 1002导入成功不分配班级
- ✅ 1003导入成功分配到"二班"
---
## ⚠️ **注意事项**
### **1. 验证时机**
- 验证在解析Excel之后立即进行
- 在转换用户数据之前完成验证
- 一旦发现问题,立即终止流程
### **2. 错误提示**
- 明确列出所有不存在的班级名称
- 用顿号(、)分隔多个班级
- 提示用户"请先创建班级再导入"
### **3. 原子性**
- 班级验证失败时,不导入任何用户
- 避免部分导入成功、部分失败的情况
- 保证数据一致性
### **4. 班级名称匹配**
- 班级名称区分大小写
- 自动去除首尾空格
- 完全匹配数据库中的班级名称
---
## 🚀 **部署步骤**
### **1. 重新编译后端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **2. 重启后端服务**
停止当前服务启动新编译的jar包
### **3. 测试导入功能**
1. 准备测试Excel
2. 创建部分班级(不创建全部)
3. 执行导入
4. 验证错误提示
---
## 📊 **验收标准**
### **功能验收**
- [ ] 导入重复用户时,所有字段都被更新
- [ ] 班级不存在时,导入失败
- [ ] 错误提示包含所有缺失的班级名称
- [ ] 提示用户"请先创建班级再导入"
- [ ] 班级验证通过后,导入正常进行
- [ ] 部分用户有班级、部分没有班级,能正常导入
### **性能验收**
- [ ] 大量数据导入时,验证时间合理(< 5秒
- [ ] 不影响正常导入速度
### **日志验收**
- [ ] 记录验证开始日志
- [ ] 记录验证通过日志(包含班级数量)
- [ ] 记录验证失败日志(包含缺失的班级)
---
## 🐛 **可能的问题**
### **问题1验证通过但导入失败**
**原因:** 验证和导入之间,班级被删除
**概率:** 极低
**影响:** 导入失败,提示班级不存在
### **问题2班级名称不匹配**
**原因:** Excel中的班级名称有空格或大小写不同
**解决:** 检查Excel数据确保名称完全一致
### **问题3大量班级验证慢**
**原因:** 数据库查询慢
**解决:** 添加班级名称索引,优化查询
---
## 💡 **后续优化建议**
### **优化1批量创建班级**
- 导入时发现班级不存在
- 提供"一键创建"按钮
- 自动创建所有缺失的班级
### **优化2模糊匹配**
- 支持班级名称模糊匹配
- 例如:"一班" 匹配 "第一班"
- 需要谨慎处理,避免误匹配
### **优化3批量验证优化**
- 使用IN查询代替循环查询
- 提高大量班级验证的性能
---
## ✅ **总结**
### **已解决的问题**
1. ✅ 用户更新字段不完整 → **已解决(原代码已实现)**
2. ✅ 班级验证缺失 → **已修复(添加导入前验证)**
### **优化效果**
- ✅ 导入更可靠(班级验证)
- ✅ 错误提示更友好(明确列出缺失班级)
- ✅ 数据一致性更好(原子性导入)
- ✅ 用户体验更好(提前发现问题)

View File

@ -1,400 +0,0 @@
# 用户导入班级验证优化说明
## 🎯 **需求说明**
### **用户要求**
1. ❌ 不要在导入前就报错中断
2. ✅ 优先完成导入任务
3. ✅ 遇到班级不存在的记录,跳过该条记录
4. ✅ 继续导入其他记录
5. ✅ 最后在导入结果中统一显示失败的记录和原因
---
## ✅ **实现方案**
### **方案:逐条验证 + 异常捕获**
#### **核心逻辑**
1. 在处理每个用户时,**在插入用户之前**验证班级是否存在
2. 如果班级不存在,抛出异常
3. 外层catch捕获异常记录到失败信息中
4. 继续处理下一个用户
5. 最后统一显示导入结果
#### **为什么在插入前验证?**
- ✅ 避免用户已插入但班级分配失败
- ✅ 保证数据一致性
- ✅ 失败的用户不会被导入到系统
---
## 🔧 **修改内容**
### **修改1移除Controller中的预验证**
**文件:** `StudyClassUserController.java`
**移除的代码:**
```java
// 预先验证所有班级是否存在
Set<String> uniqueClassNames = new HashSet<>();
// ... 收集班级名称
Map<String, Long> existingClasses = classUserService.getClassNameToIdMap();
// ... 检查班级
if (!missingClasses.isEmpty())
{
throw new ServiceException("导入失败:...");
}
```
**原因:** 不要在导入前就中断整个流程
---
### **修改2添加逐条验证逻辑**
**文件:** `StudyClassUserServiceImpl.java`
**添加的代码:**
```java
// 预先验证班级是否存在(在插入/更新用户之前)
if (StringUtils.isNotEmpty(user.getRemark()))
{
String remark = user.getRemark();
int idx = remark.indexOf("班级名称:");
if (idx >= 0)
{
String className = remark.substring(idx + 5).trim();
if (StringUtils.isNotEmpty(className))
{
Long classId = classNameToIdMap.get(className);
if (classId == null)
{
// 班级不存在,抛出异常
throw new ServiceException("信息编号[" + infoNoStr + "]:班级 [" + className + "] 不存在,请先创建该班级");
}
}
}
}
// 验证通过后,再插入/更新用户
SysUser u = userMapper.selectUserById(infoNo);
if (StringUtils.isNull(u))
{
// 插入用户
userMapper.insertUser(user);
// ... 分配班级
}
```
**关键点:**
- ✅ 验证在插入用户**之前**
- ✅ 如果班级不存在,用户不会被插入
- ✅ 异常会被外层catch捕获
---
### **修改3优化异常处理**
**文件:** `StudyClassUserServiceImpl.java`
**班级分配失败时的异常:**
```java
else
{
// 班级不存在,抛出异常
throw new ServiceException("班级 [" + className + "] 不存在,请先创建该班级");
}
```
**作用:** 作为双重保险,防止遗漏
---
## 🔄 **完整流程**
### **导入流程图**
```
开始导入
解析Excel100条记录
开始逐条处理
┌─────────────────────┐
│ 处理第1条张三 │
│ 班级:一班 │
│ ↓ │
│ 验证班级是否存在 │
│ ✅ 一班存在 │
│ ↓ │
│ 插入用户 │
│ ↓ │
│ 分配班级 │
│ ↓ │
│ ✅ 导入成功 │
└─────────────────────┘
┌─────────────────────┐
│ 处理第2条李四 │
│ 班级:三班 │
│ ↓ │
│ 验证班级是否存在 │
│ ❌ 三班不存在 │
│ ↓ │
│ 抛出异常 │
│ ↓ │
│ catch捕获异常 │
│ ↓ │
│ 记录到errorMsg
│ "班级[三班]不存在" │
│ ↓ │
│ ❌ 导入失败 │
│ ❌ 用户未插入 │
└─────────────────────┘
┌─────────────────────┐
│ 处理第3条王五 │
│ 班级:二班 │
│ ↓ │
│ 验证班级是否存在 │
│ ✅ 二班存在 │
│ ↓ │
│ 插入用户 │
│ ↓ │
│ 分配班级 │
│ ↓ │
│ ✅ 导入成功 │
└─────────────────────┘
... 继续处理其他记录 ...
所有记录处理完成
生成导入结果:
- 成功2条
- 失败1条
- 失败详情:
1、信息编号 1002罪犯姓名 李四
失败原因:班级 [三班] 不存在,请先创建该班级
```
---
## 📊 **导入结果示例**
### **示例1部分失败**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | 三班
1003 | 王五 | C区 | 二班
1004 | 赵六 | D区 | 四班
```
**数据库班级:** 一班、二班(没有三班、四班)
**导入结果:**
```
导入完成!
新增: 2 条
更新: 0 条
失败: 2 条
失败详情:
1、信息编号 1002罪犯姓名 李四
失败原因:信息编号[1002]:班级 [三班] 不存在,请先创建该班级
2、信息编号 1004罪犯姓名 赵六
失败原因:信息编号[1004]:班级 [四班] 不存在,请先创建该班级
```
**数据库结果:**
- ✅ 张三导入成功,分配到一班
- ❌ 李四导入失败,未插入数据库
- ✅ 王五导入成功,分配到二班
- ❌ 赵六导入失败,未插入数据库
---
### **示例2全部成功**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | 二班
```
**数据库班级:** 一班、二班
**导入结果:**
```
导入完成!
新增: 2 条
更新: 0 条
失败: 0 条
```
---
### **示例3部分无班级**
**Excel数据**
```
信息编号 | 姓名 | 监区 | 班级
1001 | 张三 | A区 | 一班
1002 | 李四 | B区 | (空)
1003 | 王五 | C区 | 三班
```
**数据库班级:** 一班、二班(没有三班)
**导入结果:**
```
导入完成!
新增: 2 条
更新: 0 条
失败: 1 条
失败详情:
1、信息编号 1003罪犯姓名 王五
失败原因:信息编号[1003]:班级 [三班] 不存在,请先创建该班级
```
**数据库结果:**
- ✅ 张三导入成功,分配到一班
- ✅ 李四导入成功,不分配班级(班级为空不验证)
- ❌ 王五导入失败,未插入数据库
---
## ⚠️ **重要说明**
### **1. 验证时机**
- ✅ 在插入/更新用户**之前**验证班级
- ✅ 验证失败时,用户不会被插入/更新
- ✅ 保证数据一致性
### **2. 空班级处理**
- ✅ 班级为空时,不进行验证
- ✅ 用户可以导入成功,只是不分配班级
### **3. 异常处理**
- ✅ 每条记录的异常独立处理
- ✅ 一条记录失败不影响其他记录
- ✅ 所有失败记录统一显示
### **4. 错误信息格式**
```
信息编号[1002]:班级 [三班] 不存在,请先创建该班级
```
**包含信息:**
- 信息编号:方便定位是哪条记录
- 班级名称:明确哪个班级不存在
- 操作建议:提示用户先创建班级
---
## 🧪 **测试场景**
### **测试1混合场景**
```
Excel
- 张三 → 一班(存在)
- 李四 → 三班(不存在)
- 王五 → 二班(存在)
- 赵六 → 四班(不存在)
- 孙七 → (空)
预期结果:
✅ 张三导入成功
❌ 李四导入失败(班级不存在)
✅ 王五导入成功
❌ 赵六导入失败(班级不存在)
✅ 孙七导入成功(无班级)
统计:
- 成功3条
- 失败2条
- 失败详情列出李四和赵六
```
---
## 🚀 **部署步骤**
### **1. 重新编译后端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **2. 重启后端服务**
### **3. 测试导入**
1. 创建"一班"和"二班"
2. 准备Excel包含一班、二班、三班
3. 执行导入
4. 查看导入结果:
- ✅ 一班和二班的用户导入成功
- ❌ 三班的用户导入失败,显示错误信息
---
## 📋 **验收标准**
### **功能验收**
- [ ] 班级存在时,用户导入成功
- [ ] 班级不存在时,该用户导入失败
- [ ] 失败的用户不会被插入到数据库
- [ ] 一个用户失败不影响其他用户
- [ ] 导入结果显示成功数和失败数
- [ ] 失败详情包含信息编号、姓名和失败原因
- [ ] 班级为空时,用户可以导入成功
### **数据一致性验收**
- [ ] 失败的用户在sys_user表中不存在
- [ ] 失败的用户在student_class表中不存在
- [ ] 成功的用户正确分配到指定班级
- [ ] 没有"用户存在但班级未分配"的情况
### **用户体验验收**
- [ ] 导入过程不中断
- [ ] 导入结果一次性展示
- [ ] 错误信息清晰明确
- [ ] 可以根据错误信息快速定位问题
---
## 💡 **优化建议**
### **建议1批量创建班级**
在导入结果中提供"创建缺失的班级"按钮,一键创建所有不存在的班级
### **建议2Excel模板**
提供Excel模板下载包含当前系统中所有班级的下拉列表
### **建议3导入预检**
在上传Excel后、开始导入前先显示预检结果哪些班级不存在
---
## ✅ **总结**
### **优化效果**
- ✅ 导入不会因个别记录失败而中断
- ✅ 成功的记录正常导入,失败的记录清晰展示
- ✅ 数据一致性得到保证
- ✅ 用户体验更友好
### **技术要点**
- 逐条验证而非批量验证
- 验证在插入之前而非之后
- 异常独立处理而非全局处理
- 结果统一展示而非即时报错

View File

@ -1,373 +0,0 @@
# 答案显示格式优化说明
## 🎯 **问题描述**
### **当前显示**
```
我的答案B. 阿萨德放到 ✅(有选项字母+内容)
正确答案A ❌(只有选项字母)
```
### **期望显示**
```
我的答案B. 阿萨德放到 ✅
正确答案A. 选项内容1 ✅(选项字母+内容)
```
---
## ✅ **解决方案**
### **核心思路**
在后端返回成绩详情时,将答案字母(如"A")转换为完整格式(如"A. 选项内容"
### **修改位置**
**文件:** `StudyScoreController.java`
---
## 🔧 **代码修改**
### **修改1在返回成绩详情时格式化答案**
**位置:** `getScoreDetail` 方法
```java
if (question != null)
{
detail.setQuestionContent(question.getQuestionContent());
// 转换正确答案格式将字母A转换为完整格式A. 选项内容)
String formattedCorrectAnswer = formatAnswerWithContent(
question.getCorrectAnswer(),
question.getOptions(),
question.getQuestionType()
);
detail.setCorrectAnswer(formattedCorrectAnswer);
// 转换学生答案格式(如果还没有完整格式)
String studentAnswer = detail.getStudentAnswer();
if (studentAnswer != null && !studentAnswer.contains(".")) {
// 如果学生答案是纯字母(如"A"),转换为完整格式
String formattedStudentAnswer = formatAnswerWithContent(
studentAnswer,
question.getOptions(),
question.getQuestionType()
);
detail.setStudentAnswer(formattedStudentAnswer);
}
detail.setQuestionType(question.getQuestionType());
detail.setQuestionScore(question.getScore());
detail.setOptions(question.getOptions());
}
```
### **修改2添加格式化方法**
```java
/**
* 将答案转换为完整格式(字母 + 选项内容)
* @param answer 答案字母,如"A"或"A,B,C"
* @param optionsJson 选项JSON如["选项1", "选项2"]
* @param questionType 题目类型
* @return 格式化后的答案,如"A. 选项1"或"A. 选项1, B. 选项2"
*/
private String formatAnswerWithContent(String answer, String optionsJson, String questionType)
{
if (StringUtils.isBlank(answer))
{
return "";
}
// 判断题直接返回
if ("judge".equals(questionType))
{
return answer;
}
// 填空题和简答题直接返回
if ("fill".equals(questionType) || "essay".equals(questionType))
{
return answer;
}
// 解析选项列表
java.util.List<String> optionsList = parseOptionsList(optionsJson);
if (optionsList == null || optionsList.isEmpty())
{
return answer;
}
// 单选题或多选题
String trimmedAnswer = answer.trim();
// 如果答案已经包含".",说明已经是完整格式,直接返回
if (trimmedAnswer.contains("."))
{
return trimmedAnswer;
}
// 处理多选题(答案可能是"A,B,C"
if (trimmedAnswer.contains(","))
{
String[] answerParts = trimmedAnswer.split(",");
java.util.List<String> formattedParts = new java.util.ArrayList<>();
for (String part : answerParts)
{
String letter = part.trim();
if (letter.length() == 1 && letter.matches("[A-Fa-f]"))
{
int index = letter.toUpperCase().charAt(0) - 'A';
if (index >= 0 && index < optionsList.size())
{
formattedParts.add(letter.toUpperCase() + ". " + optionsList.get(index));
}
else
{
formattedParts.add(letter);
}
}
else
{
formattedParts.add(part);
}
}
return String.join(", ", formattedParts);
}
// 处理单选题(答案是单个字母,如"A"
if (trimmedAnswer.length() == 1 && trimmedAnswer.matches("[A-Fa-f]"))
{
int index = trimmedAnswer.toUpperCase().charAt(0) - 'A';
if (index >= 0 && index < optionsList.size())
{
return trimmedAnswer.toUpperCase() + ". " + optionsList.get(index);
}
}
return answer;
}
```
---
## 📋 **支持的题型**
### **1. 单选题**
**输入:** `"A"`
**输出:** `"A. 选项内容1"`
### **2. 多选题**
**输入:** `"A,B,C"`
**输出:** `"A. 选项1, B. 选项2, C. 选项3"`
### **3. 判断题**
**输入:** `"正确"`
**输出:** `"正确"`(不转换)
### **4. 填空题/简答题**
**输入:** `"答案内容"`
**输出:** `"答案内容"`(不转换)
---
## 🔄 **转换逻辑**
### **转换规则**
1. **检查是否需要转换**
- 判断题、填空题、简答题:不转换,直接返回
- 单选题、多选题:进行转换
2. **检查是否已经是完整格式**
- 如果答案包含".":已是完整格式,直接返回
- 如果答案不包含".":需要转换
3. **解析选项列表**
- 从JSON字符串解析为List
- 如果解析失败:返回原答案
4. **转换单选题答案**
- 提取字母(如"A"
- 计算索引:`'A' - 'A' = 0`
- 获取选项:`optionsList.get(0)`
- 拼接:`"A. " + 选项内容`
5. **转换多选题答案**
- 分割答案:`"A,B,C"` → `["A", "B", "C"]`
- 逐个转换每个字母
- 用逗号+空格连接:`"A. 内容1, B. 内容2, C. 内容3"`
---
## 🧪 **测试案例**
### **测试1单选题**
```
题目:阿萨德发多少分
选项:["阿斯蒂芬都是阿凡达", "撒旦法的发"]
正确答案A
学生答案B
转换后:
正确答案A. 阿斯蒂芬都是阿凡达
学生答案B. 撒旦法的发
```
### **测试2多选题**
```
题目:"汉"这个字的读音是?
选项:["han", "han2", "han3"]
正确答案A,B
学生答案A,C
转换后:
正确答案A. han, B. han2
学生答案A. han, C. han3
```
### **测试3判断题**
```
正确答案:正确
学生答案:错误
转换后:
正确答案:正确
学生答案:错误
```
### **测试4填空题**
```
正确答案zi
学生答案zi
转换后:
正确答案zi
学生答案zi
```
---
## ⚠️ **注意事项**
### **1. 学生答案格式兼容**
- 如果学生答案已经是完整格式(如"B. 内容"),不再转换
- 如果学生答案是纯字母(如"B"),进行转换
- 通过检查是否包含"."来判断
### **2. 选项索引范围检查**
```java
if (index >= 0 && index < optionsList.size())
{
// 索引有效,进行转换
}
else
{
// 索引无效,返回原字母
}
```
### **3. 大小写处理**
- 答案字母统一转换为大写
- 正则匹配:`[A-Fa-f]`(支持大小写)
### **4. 空值处理**
```java
if (StringUtils.isBlank(answer))
{
return "";
}
```
---
## 🎨 **前端显示效果**
### **修改前**
```
第 1 题 0/5分
阿萨德发多少分
我的答案: B. 撒旦法的发
正确答案: A ❌ 只显示字母
```
### **修改后**
```
第 1 题 0/5分
阿萨德发多少分
我的答案: B. 撒旦法的发
正确答案: A. 阿斯蒂芬都是阿凡达 ✅ 显示完整
```
---
## 🚀 **部署步骤**
### **1. 重新编译后端**
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
```
### **2. 重启后端服务**
停止当前服务启动新编译的jar包
### **3. 测试验证**
1. 在App中完成一份试卷
2. 提交试卷
3. 查看成绩详情
4. 检查答案显示格式:
- ✅ 正确答案显示完整格式
- ✅ 学生答案显示完整格式
- ✅ 多选题用逗号分隔
---
## 📝 **验收标准**
- [ ] 单选题正确答案显示:"A. 选项内容"
- [ ] 单选题学生答案显示:"B. 选项内容"
- [ ] 多选题正确答案显示:"A. 内容1, B. 内容2"
- [ ] 多选题学生答案显示:"A. 内容1, C. 内容3"
- [ ] 判断题正确显示:"正确"或"错误"
- [ ] 填空题正确显示答案内容
- [ ] 简答题正确显示答案内容
- [ ] 没有出现格式错误或显示异常
---
## 🐛 **可能的问题**
### **问题1选项内容显示为空**
**原因:** 选项JSON解析失败或索引超出范围
**解决:** 检查数据库中的`options`字段格式是否正确
### **问题2答案显示乱码**
**原因:** 字符编码问题
**解决:** 确保数据库和Java代码使用UTF-8编码
### **问题3多选题逗号显示异常**
**原因:** 分隔符问题
**解决:** 检查`String.join(", ", formattedParts)`的分隔符
---
## ✅ **总结**
### **核心改进**
1. ✅ 答案显示更直观(字母+内容)
2. ✅ 学生更容易理解错误原因
3. ✅ 支持所有题型
4. ✅ 兼容已有数据格式
### **技术要点**
- 后端动态格式化答案
- 根据题型选择不同处理逻辑
- 兼容已存在的完整格式答案
- 统一学生答案和正确答案的显示格式

View File

@ -1,403 +0,0 @@
# 考核模块接口文档
## 1. 考试管理接口
### 1.1 查询考试列表
- **接口URL**: `/study/exam/list`
- **请求方式**: `GET`
- **权限要求**: `study:exam:list`
- **请求参数**:
```json
{
"pageNum": 1,
"pageSize": 10,
"examName": "考试名称(可选)",
"subjectId": "科目ID可选",
"status": "状态0-草稿1-已发布2-已结束,可选)"
}
```
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"rows": [
{
"id": 1,
"examName": "数学期中考试",
"subjectId": 1,
"subjectName": "数学",
"questionCount": 20,
"duration": 120,
"totalScore": 100.0,
"status": "1",
"publishTime": "2024-01-01 10:00:00",
"createTime": "2024-01-01 09:00:00"
}
],
"total": 1
}
```
### 1.2 查询考试详情
- **接口URL**: `/study/exam/{id}`
- **请求方式**: `GET`
- **权限要求**: `study:exam:query`
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"examName": "数学期中考试",
"subjectId": 1,
"subjectName": "数学",
"questionCount": 20,
"duration": 120,
"totalScore": 100.0,
"status": "1",
"questions": [
{
"id": 1,
"questionType": "single",
"questionContent": "1+1等于多少",
"options": "[\"1\", \"2\", \"3\", \"4\"]",
"correctAnswer": "2",
"score": 5.0
}
]
}
}
```
### 1.3 获取考试题目(学生端)
- **接口URL**: `/study/exam/{id}/questions`
- **请求方式**: `GET`
- **权限要求**: 无(学生端)
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"exam": {
"id": 1,
"examName": "数学期中考试",
"subjectName": "数学",
"duration": 120,
"totalScore": 100.0
},
"questions": [
{
"id": 1,
"questionType": "single",
"questionContent": "1+1等于多少",
"options": "[\"1\", \"2\", \"3\", \"4\"]",
"score": 5.0
}
]
}
}
```
- **说明**: 此接口不返回正确答案,仅用于学生答题
### 1.4 新增考试
- **接口URL**: `/study/exam`
- **请求方式**: `POST`
- **权限要求**: `study:exam:add`
- **请求参数**:
```json
{
"examName": "数学期中考试",
"subjectId": 1,
"duration": 120,
"totalScore": 100.0
}
```
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
### 1.5 修改考试
- **接口URL**: `/study/exam`
- **请求方式**: `PUT`
- **权限要求**: `study:exam:edit`
- **请求参数**: 同新增考试
### 1.6 删除考试
- **接口URL**: `/study/exam/{ids}`
- **请求方式**: `DELETE`
- **权限要求**: `study:exam:remove`
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
### 1.7 发布考试
- **接口URL**: `/study/exam/publish/{id}`
- **请求方式**: `PUT`
- **权限要求**: `study:exam:edit`
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
### 1.8 AI生成题目
- **接口URL**: `/study/exam/ai/generate-questions`
- **请求方式**: `POST`
- **权限要求**: `study:exam:edit`
- **请求参数**:
```json
{
"subjectId": 1,
"questionCounts": {
"single": 10,
"multiple": 5,
"judge": 5,
"fill": 3,
"essay": 2
}
}
```
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"questionType": "single",
"questionContent": "1+1等于多少",
"options": "[\"1\", \"2\", \"3\", \"4\"]",
"correctAnswer": "2",
"score": 5.0
}
]
}
```
- **说明**: 此处接入AI题目生成API需要替换为实际API地址
### 1.9 保存题目
- **接口URL**: `/study/exam/{examId}/questions`
- **请求方式**: `POST`
- **权限要求**: `study:exam:edit`
- **请求参数**:
```json
[
{
"questionType": "single",
"questionContent": "1+1等于多少",
"options": "[\"1\", \"2\", \"3\", \"4\"]",
"correctAnswer": "2",
"score": 5.0,
"sortOrder": 1
}
]
```
## 2. 成绩管理接口
### 2.1 查询成绩列表
- **接口URL**: `/study/score/list`
- **请求方式**: `GET`
- **权限要求**: `study:score:list`
- **请求参数**:
```json
{
"pageNum": 1,
"pageSize": 10,
"examId": "考试ID可选",
"studentId": "学生ID可选",
"status": "状态(可选)"
}
```
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"rows": [
{
"id": 1,
"examId": 1,
"examName": "数学期中考试",
"studentId": 100,
"studentName": "张三",
"studentNo": "2024001",
"totalScore": 100.0,
"obtainedScore": 85.0,
"submitTime": "2024-01-01 12:00:00",
"duration": 115,
"status": "2"
}
],
"total": 1
}
```
### 2.2 查询成绩详情
- **接口URL**: `/study/score/{id}`
- **请求方式**: `GET`
- **权限要求**: 无
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"examId": 1,
"examName": "数学期中考试",
"studentId": 100,
"studentName": "张三",
"totalScore": 100.0,
"obtainedScore": 85.0,
"submitTime": "2024-01-01 12:00:00",
"duration": 115,
"status": "2",
"answerDetails": [
{
"id": 1,
"questionId": 1,
"questionContent": "1+1等于多少",
"studentAnswer": "2",
"correctAnswer": "2",
"isCorrect": "1",
"score": 5.0,
"questionScore": 5.0
}
]
}
}
```
### 2.3 获取当前学生的成绩列表
- **接口URL**: `/study/score/my-scores`
- **请求方式**: `GET`
- **权限要求**: 无(学生端)
- **响应示例**: 同查询成绩列表
### 2.4 提交答题结果
- **接口URL**: `/study/score/submit`
- **请求方式**: `POST`
- **权限要求**: 无(学生端)
- **请求参数**:
```json
{
"examId": 1,
"answers": [
{
"questionId": 1,
"answer": "2"
},
{
"questionId": 2,
"answer": "A,B"
}
],
"duration": 115
}
```
- **响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"totalScore": 100.0,
"obtainedScore": 85.0,
"answerDetails": [
{
"questionId": 1,
"isCorrect": "1",
"score": 5.0
}
]
}
}
```
- **说明**: 此处接入AI自动打分API需要替换为实际API地址
## 3. AI API 接入点说明
### 3.1 AI题目生成API
- **位置**: `StudyExamController.generateQuestionsByAI()`
- **函数签名**:
```java
public AjaxResult generateQuestionsByAI(@RequestBody Map<String, Object> params)
```
- **参数说明**:
- `subjectId`: 科目IDLong
- `questionCounts`: 各题型题量Map<String, Integer>
- 键题型single/multiple/judge/fill/essay
- 值:题量
- **返回值**: 题目列表List<StudyQuestion>
- **接入方式**: 替换TODO注释处的代码调用实际AI API
### 3.2 AI自动打分API
- **位置**: `StudyScoreController.submitAnswer()`
- **函数签名**:
```java
public AjaxResult submitAnswer(@RequestBody Map<String, Object> params)
```
- **参数说明**:
- `examId`: 考试IDLong
- `studentAnswers`: 学生答题数据List<StudyStudentAnswer>
- **返回值**: 打分结果Map<String, Object>
```json
{
"totalScore": 100.0,
"obtainedScore": 85.0,
"answerDetails": [
{
"questionId": 1,
"isCorrect": "1",
"score": 5.0
}
]
}
```
- **接入方式**: 替换TODO注释处的代码调用实际AI API
## 4. 错误码说明
| 错误码 | 说明 |
|--------|------|
| 200 | 操作成功 |
| 401 | 未登录或登录已过期 |
| 403 | 无权限访问 |
| 500 | 服务器内部错误 |
## 5. 题型说明
| 题型代码 | 说明 | 选项格式 |
|---------|------|---------|
| single | 单选题 | JSON数组["选项A", "选项B", "选项C", "选项D"] |
| multiple | 多选题 | JSON数组答案用逗号分隔"A,B" |
| judge | 判断题 | 答案:"正确" 或 "错误" |
| fill | 填空题 | 文本答案 |
| essay | 简答题 | 文本答案 |
## 6. 状态说明
### 考试状态
- `0`: 草稿
- `1`: 已发布
- `2`: 已结束
### 成绩状态
- `0`: 未提交
- `1`: 已提交
- `2`: 已评分

View File

@ -1,323 +0,0 @@
# 自定义基座打包完整步骤
## 🎯 **为什么使用自定义基座?**
### **问题**
- 云端打包限制40MB
- 语音模型大小41.87MB
- 结果:云端打包失败
### **解决方案**
- ✅ 自定义基座**没有大小限制**
- ✅ 可以包含42MB的语音模型
- ✅ 所有功能立即可用
---
## 📋 **完整操作步骤**
### **步骤1在 HBuilderX 中打开项目**
确保项目已经打开:`fronted_uniapp`
---
### **步骤2制作自定义调试基座**
#### **2.1 点击菜单**
```
运行 → 运行到手机或模拟器 → 制作自定义调试基座
```
或者:
```
发行 → 原生App-本地打包 → 生成本地打包App资源
```
**推荐:制作自定义调试基座**(更简单)
---
#### **2.2 选择打包方式**
会弹出对话框,选择:
**方式1使用云端证书推荐** ✅
- ✅ 简单DCloud提供证书
- ✅ 无需配置
- ⚠️ 证书是公共的,不能上架应用商店
**方式2使用自有证书**
- ✅ 可以上架应用商店
- ❌ 需要自己生成证书(复杂)
**现在选择:使用云端证书**
---
#### **2.3 选择平台**
- ☑ Android
- ☐ iOS如果需要iOS也勾选
点击:**打包**
---
### **步骤3等待打包完成**
#### **3.1 查看控制台**
HBuilderX 底部的控制台会显示打包进度:
```
[HBuilder] 开始制作自定义基座...
[HBuilder] 正在编译...
[HBuilder] 正在打包Android资源...
[HBuilder] 正在生成APK...
[HBuilder] 打包成功!
```
**预计时间5-15分钟**(首次可能更久)
---
#### **3.2 成功标志**
看到类似信息:
```
[HBuilder] 自定义基座制作成功
[HBuilder] Android基座路径
C:\Users\Administrator\Desktop\Project\ry_study-v_03\fronted_uniapp\unpackage\debug\android_debug.apk
```
---
### **步骤4安装自定义基座到手机**
#### **4.1 找到APK文件**
路径:
```
项目目录/unpackage/debug/android_debug.apk
```
#### **4.2 安装到手机**
**方法AUSB连接**
1. 手机通过USB连接电脑
2. 开启手机的USB调试
3. 将APK拖到手机
4. 在手机上安装
**方法B通过HBuilderX直接安装**
```
运行 → 运行到手机或模拟器 → 运行到Android App基座
选择您的设备
```
---
### **步骤5使用自定义基座运行**
#### **5.1 选择运行方式**
在 HBuilderX 中:
```
运行 → 运行到手机或模拟器 → 运行到Android App基座
```
**重要:** 选择"运行到Android App基座",而不是"运行到手机"
#### **5.2 选择设备**
选择已安装自定义基座的手机
#### **5.3 开始运行**
HBuilderX 会将代码同步到自定义基座中运行
---
### **步骤6测试语音功能**
1. 打开APP
2. 进入语音测评页面
3. 测试语音识别功能
4. ✅ 应该正常工作!
---
## 🎯 **自定义基座 vs 云端打包**
| 特性 | 自定义基座 | 云端打包 |
|------|-----------|---------|
| **大小限制** | 无限制 ✅ | 40MB ❌ |
| **打包时间** | 5-15分钟 | 2-5分钟 |
| **证书** | 云端或自有 | 云端或自有 |
| **开发调试** | 方便 ✅ | 一般 |
| **正式发布** | 可以 ✅ | 可以 ✅ |
| **包含模型** | 可以 ✅ | 不能 ❌ |
---
## ⚠️ **常见问题**
### **Q1自定义基座可以发布吗**
**A** 可以!
自定义基座就是完整的APK可以
- ✅ 直接分发给用户
- ✅ 上传到应用商店(需要自有证书)
- ✅ 作为正式版本发布
**但是:**
- 如果用云端证书,不能上架应用商店
- 如果用自有证书,可以上架
---
### **Q2自定义基座和云端打包有什么区别**
**本质上没区别!**
- 自定义基座 = 本地/云端编译的完整APK
- 云端打包 = 云端编译的完整APK
唯一区别:
- 自定义基座没有40MB限制
- 云端打包有40MB限制
---
### **Q3每次修改代码都要重新制作基座吗**
**不需要!**
**开发阶段:**
- 制作一次基座
- 之后修改代码,直接"运行到基座"
- HBuilderX会自动同步代码
- 除非修改了manifest.json或原生插件
**发布阶段:**
- 代码稳定后
- 重新制作基座
- 这个基座就是最终APK
---
### **Q4打包失败怎么办**
**常见原因:**
1. **插件版本冲突**
- 清理:`unpackage` 目录
- 重新打包
2. **证书问题**
- 使用云端证书
- 或重新生成自有证书
3. **网络问题**
- 检查HBuilderX网络连接
- 检查防火墙设置
4. **Gradle下载失败**
- 配置Gradle镜像
- 或等待自动重试
---
### **Q5自定义基座很大怎么办**
**原因:**
- 包含了所有资源和插件
- 语音模型42MB
- 其他资源
**正常大小:**
- 50-100MB是正常的
- 语音识别APP通常较大
- 微信: 200MB+
- 抖音: 100MB+
**如果想减小:**
- 压缩图片资源
- 移除未使用的插件
- 使用运行时下载(后续优化)
---
## 🚀 **开始制作自定义基座**
### **现在在 HBuilderX 中操作:**
1. ✅ 确保模型文件已恢复(已完成)
2. ✅ 保存所有文件Ctrl+Shift+S
3. ✅ 点击菜单:
```
运行 → 运行到手机或模拟器 → 制作自定义调试基座
```
4. ✅ 选择:使用云端证书
5. ✅ 勾选Android
6. ✅ 点击:打包
7. ⏰ 等待 5-15 分钟
8. ✅ 打包成功!
9. ✅ 安装到手机
10. ✅ 测试语音功能
---
## 📊 **预期结果**
### **自定义基座APK**
- 文件名: `android_debug.apk``android_release.apk`
- 大小: 约 80-120MB
- 位置: `unpackage/debug/``unpackage/release/`
- 功能: 完整,包含语音识别
### **运行效果:**
- ✅ 所有页面正常
- ✅ 语音识别功能正常
- ✅ 模型文件在APP内部
- ✅ 无需额外下载
---
## 💡 **后续优化建议**
### **开发阶段(现在):**
使用自定义基座,快速开发和测试
### **正式发布:**
**选项A继续使用自定义基座**
- 简单,一次打包即可
- 用户下载即用,无需等待
**选项B改用运行时下载**
- APK减小42MB
- 首次使用需下载1-2分钟
- 代码参考:`log/运行时下载模型完整示例.md`
---
## ✅ **总结**
1. **模型文件已恢复**
2. **使用自定义基座打包**
3. **没有大小限制**
4. **语音功能正常**
5. **可以作为正式版发布**
---
**现在请在 HBuilderX 中制作自定义基座!** 🚀
**如果遇到问题,随时告诉我!** 💬

View File

@ -1,168 +0,0 @@
# 学习记录问题诊断清单
## 1. 检查后端是否重新编译
### 问题症状
- 后台管理系统显示的进度还是 57% 或 78.33%
- 而不是 11.11%3/27
### 解决方案
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
# 重启后端服务
```
### 验证方法
查看后端日志,应该看到:
```
课程 1 进度计算完成: 3个已完成 / 27个有效课件 = 11.11%
```
---
## 2. 检查前端是否重新编译
### 问题症状
- App端视频URL错误
- 显示:`http://.../profile/profile/upload/...`
### 解决方案
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\fronted_uniapp
# 停止当前服务Ctrl+C
npm run dev:app
```
### 验证方法
查看App日志应该看到
```
[课程学习] 检测到filePath包含profile/前缀,已自动去除
[课程学习] 完整URL: http://192.168.1.164:30091/profile/upload/...
```
---
## 3. 检查数据库数据
### 执行SQL
```sql
-- 1. 查看课件时长
SELECT id, name, type, duration
FROM courseware
WHERE course_id = 1
ORDER BY id;
-- 2. 查看学习详情
SELECT
id,
courseware_id,
video_end_position as video_position,
duration,
learn_time
FROM learning_detail
WHERE student_id = 452 AND course_id = 1
ORDER BY learn_time DESC
LIMIT 10;
-- 3. 查看学习记录
SELECT
id,
progress,
total_duration,
learn_count,
last_learn_time
FROM learning_record
WHERE student_id = 452 AND course_id = 1;
```
---
## 4. 当前应该的正常状态
### 后端日志
```
视频课件 882 已完成 3秒 / 3秒
视频课件 873 已完成 17秒 / 17秒
视频课件 883 已完成 XX秒 / XX秒
课程 1 进度计算完成: 3个已完成 / 27个有效课件 = 11.11%
```
### App端日志
```
[课程学习] 📊 课程进度(完成比例): 3个已完成 / 27个总数 = 11%
[课程学习] 🔗 URL构建信息:
- 完整URL: http://192.168.1.164:30091/profile/upload/2025/12/05/xxx.mp4
```
### 后台管理系统
- 学习进度11.11%
- 学习时长XX分钟
- 学习次数X次
---
## 5. 常见问题排查
### 问题A后端进度还是57%
**原因**:后端没有重新编译
**解决**执行步骤1
### 问题B前端显示不一致
**原因**:前端缓存没清除
**解决**
1. 重启前端开发服务器
2. 完全关闭并重新打开App
3. 清除浏览器缓存Ctrl+Shift+Delete
### 问题C视频不能播放
**原因**URL重复/profile/
**解决**执行步骤2重新编译前端
### 问题D播放位置不对
**原因**:学习详情数据问题
**解决**执行步骤3的SQL检查数据
---
## 6. 完整重启流程
如果以上都不行,完整重启:
### 1. 停止所有服务
- 后端停止Spring Boot
- 前端UI停止npm
- 前端App停止npm
### 2. 清理编译产物
```bash
# 后端
cd Study-Vue-redis
mvn clean
# 前端UI
cd ry-study-ui
rm -rf node_modules/.cache
rm -rf dist
# 前端App
cd fronted_uniapp
rm -rf unpackage
```
### 3. 重新编译
```bash
# 后端
cd Study-Vue-redis
mvn package -DskipTests
# 前端UI
cd ry-study-ui
npm run build:prod
# 前端App
cd fronted_uniapp
npm run dev:app
```
### 4. 重启服务并测试

View File

@ -1,187 +0,0 @@
# 试卷答案问题修复说明
## 已修复的问题
### 1. 答案判断逻辑错误
#### 问题描述
- 所有题目都显示做错(红色标记)
- 实际有些题目做对了但系统判断为错误
#### 原因分析
单选题答案判断时,没有处理"A. 选项内容"这种格式:
- 学生答案:`"A. 选项内容"`
- 正确答案:`"A"`
- 导致匹配失败
#### 修复方案
修改 `StudyScoreController.java``normalizeSingleAnswer` 方法:
- 自动去除"A. "、"B. "等前缀
- 只比较选项字母A、B、C等
- 支持格式:
- "A. 选项内容" → "A"
- "A.选项内容" → "A"
- "A" → "A"
#### 修复文件
`Study-Vue-redis/ry-study-admin/src/main/java/com/ddnai/web/controller/study/StudyScoreController.java`
---
### 2. 不显示正确答案
#### 问题描述
- 做完试卷后只显示学生的答案
- 正确答案不显示
#### 原因分析
前端只在答错时显示正确答案:
```vue
<view v-if="detail.isCorrect === '0'">
正确答案:{{ detail.correctAnswer }}
</view>
```
#### 修复方案
修改前端代码,始终显示正确答案(无论答对答错)
#### 修复文件
`fronted_uniapp/pages/score/detail.vue`
---
## 浏览器端成绩不显示问题
### 问题描述
- 做完试卷后,在后台管理系统中看不到成绩
- 刚做完的试卷找不到
### 可能原因
#### 原因1页面没有刷新
**解决方案**
1. 在后台管理系统点击"成绩管理"菜单
2. 点击"查询"按钮刷新列表
3. 或按F5刷新整个页面
#### 原因2权限问题
**检查方法**
1. 打开浏览器控制台F12
2. 查看Network标签
3. 看是否有401或403错误
#### 原因3查询条件过滤
**检查方法**
1. 检查页面上的筛选条件
2. 清空所有筛选条件
3. 重新查询
#### 原因4数据实际没有保存
**检查方法**
执行SQL查询
```sql
-- 查看最新的成绩记录
SELECT
id,
student_id,
exam_id,
obtained_score,
total_score,
submit_time
FROM study_score
ORDER BY submit_time DESC
LIMIT 10;
-- 查看学生答案详情
SELECT
id,
student_id,
exam_id,
question_id,
student_answer,
is_correct,
score
FROM study_student_answer
WHERE student_id = {你的学生ID}
ORDER BY create_time DESC
LIMIT 20;
```
---
## 重新编译和测试
### 步骤1重新编译后端
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\Study-Vue-redis
mvn clean package -DskipTests
# 重启后端服务
```
### 步骤2重新编译前端App
```bash
cd C:\Users\Administrator\Desktop\Project\ry_study-v_03\fronted_uniapp
# 停止当前服务Ctrl+C
npm run dev:app
```
### 步骤3清除缓存
- 完全关闭App
- 卸载后重新安装
- 或清除数据
### 步骤4测试
1. 登录App
2. 做一份试卷
3. 提交试卷
4. 查看成绩详情
5. 检查:
- ✅ 答对的题目显示绿色
- ✅ 答错的题目显示红色
- ✅ 所有题目都显示正确答案
---
## 验证答案判断是否正确
### 测试用例
#### 单选题
- 正确答案:`A`
- 学生答案可能的格式:
- `"A"` → 应该判断为正确 ✅
- `"A. 选项内容"` → 应该判断为正确 ✅
- `"B"` → 应该判断为错误 ❌
#### 判断题
- 正确答案:`正确`
- 学生答案:
- `"正确"` → 应该判断为正确 ✅
- `"错误"` → 应该判断为错误 ❌
#### 多选题
- 正确答案:`A,B,C`
- 学生答案:
- `"A,B,C"` → 应该判断为正确 ✅
- `"A,B"` → 应该判断为错误 ❌
- `"B,A,C"` → 应该判断为正确 ✅(顺序不影响)
---
## 如果还有问题
### 1. 检查后端日志
查看后端控制台,看是否有错误信息
### 2. 检查前端日志
打开App的调试控制台查看是否有错误
### 3. 执行SQL检查数据
使用上面的SQL脚本检查数据是否正确保存
### 4. 提供以下信息
- 答题时选择的答案
- 题目的正确答案
- 判断结果(对/错)
- 后端日志
- 前端日志

Some files were not shown because too many files have changed in this diff Show More