毕业设计实战:SpringBoot教学资料管理系统,从0到1完整开发指南
当初做教学资料管理系统时,我在“多格式文件上传与在线预览”功能上卡了整整一周——一开始只支持PDF,结果老师传了个Word课件,学生打不开,导师说“这系统实用性太差”😫 后来踩了无数坑,终于总结出这套完整开发流程。今天就把教学资料管理系统的实战经验全部分享出来,宝子们跟着做,毕设稳过!
一、需求分析别想当然!先搞懂“谁上传,谁下载”
最开始我以为做个简单的文件上传下载就行,结果导师说“要考虑教学场景,要有课程管理、班级管理、权限控制”。后来才明白,教学资料系统的核心是 “老师上传-学生下载-管理员管理” 的三级体系,必须抓住这三个角色的核心需求。
1. 核心用户 & 核心功能(踩坑后总结版)
教学资料管理系统有三类核心用户:学生、老师、管理员。千万别把“辅导员”、“院系领导”都加进去!我当初加了,权限体系混乱,最后简化成三级才顺畅。
-
学生端(核心用户,必须做的功能):
- 个人信息管理:查看/修改个人信息,绑定班级。
- 课程查看:查看本学期课程列表,按院系/时间筛选。
- 资料下载:这是核心中的核心!
- 按课程查看教学资料。
- 支持多种格式下载(PDF、Word、PPT、视频)。
- 资料预览(在线查看,不用下载)。
- 我的收藏:收藏重要资料,方便快速查找。
- 学习统计:查看自己下载/学习的数据统计。
-
老师端(上传者,重要功能):
- 我的课程:管理自己教授的课程。
- 资料上传:
- 为指定课程上传资料。
- 支持多格式(PDF、Word、PPT、Excel、视频)。
- 设置资料是否共享。
- 资料管理:管理已上传的资料(修改、删除、查看下载量)。
- 学生管理:查看选修自己课程的学生列表。
-
管理员端(管理者,核心功能):
- 用户管理:管理所有学生/老师账号。
- 班级管理:管理院系、班级信息。
- 课程管理:审核/管理所有课程。
- 资料审核:审核老师上传的资料(防违规内容)。
- 系统统计:统计资料数量、下载量等数据。
2. 需求分析避坑指南(血泪教训!)
- 一定要考虑实际教学场景:
- 一个老师可能教多个班,一个班可能有多个老师。
- 资料要有版本管理,老师更新资料后学生能知道。
- 大文件上传要有进度条和断点续传。
- 敏感资料要有权限控制(如“仅本班可见”)。
- 文件格式兼容性要重视:
- 至少支持PDF、Word、PPT、Excel、图片、视频。
- 在线预览功能很重要(学生不想下载就能看)。
- 文件大小限制要合理(比如单个文件最大500MB)。
- 写清楚约束条件:
- “一个学生只能加入一个班级”
- “一门课程可以有多个老师”
- “资料更新要保留历史版本”
- “敏感资料需要管理员审核”
3. 可行性分析(三句话说清楚)
- 技术可行性:SpringBoot + MySQL + Vue + 文件存储服务,技术成熟。
- 经济可行性:云存储用OSS(学生优惠很便宜),其他工具都免费。
- 操作可行性:老师学生都会用,界面简洁直观。
二、技术选型:SpringBoot是真香!
当初我看别人用PHP,后来选择了 SpringBoot 2.7 + MyBatis-Plus + Vue 2 + Element UI + MinIO(自建文件存储),灵活又省钱!
技术栈详解与避坑
| 技术 | 选择理由 | 避坑提醒 |
|---|---|---|
| SpringBoot 2.7.x | 快速开发,配置简单,生态丰富。 | 版本别太新,2.7.x最稳定。 |
| MyBatis-Plus | CRUD神器,代码生成器好用。 | 学会用它的@TableField注解。 |
| Vue 2 + Element UI | 组件丰富,做后台管理很快。 | 文件上传组件要自己封装。 |
| MySQL 8.0 | JSON字段支持,存资料标签方便。 | 字符集一定用utf8mb4。 |
| MinIO | 开源对象存储,替代OSS,免费! | 配置访问权限要仔细。 |
| PDF.js + Office Online | 在线预览多种格式文件。 | Office Online要自己部署或买服务。 |
开发环境一步到位
# application.yml核心配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/teaching_material?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4
username: root
password: 123456
servlet:
multipart:
max-file-size: 500MB # 视频文件可能很大
max-request-size: 500MB
enabled: true
# MinIO配置(自建文件存储)
minio:
endpoint: http://localhost:9000
access-key: minioadmin
secret-key: minioadmin
bucket-name: teaching-files
三、数据库设计:文件存储是关键!
我当初的坑:把文件存数据库BLOB字段,查询慢得要死。后来用MinIO存文件,数据库只存元数据。
核心表结构设计(重点!)
-- 学生表
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT,
`student_no` varchar(20) NOT NULL COMMENT '学号',
`student_name` varchar(50) NOT NULL COMMENT '姓名',
`password` varchar(100) NOT NULL COMMENT '密码(MD5)',
`class_id` int NOT NULL COMMENT '班级ID',
`phone` varchar(20) COMMENT '手机号',
`email` varchar(100) COMMENT '邮箱',
`avatar_url` varchar(500) COMMENT '头像URL',
`status` tinyint DEFAULT 1 COMMENT '1正常,2禁用',
`last_login_time` datetime COMMENT '最后登录时间',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_no` (`student_no`),
KEY `idx_class` (`class_id`),
CONSTRAINT `fk_student_class` FOREIGN KEY (`class_id`) REFERENCES `class` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';
-- 老师表
CREATE TABLE `teacher` (
`id` int NOT NULL AUTO_INCREMENT,
`teacher_no` varchar(20) NOT NULL COMMENT '工号',
`teacher_name` varchar(50) NOT NULL COMMENT '姓名',
`password` varchar(100) NOT NULL COMMENT '密码',
`department` varchar(100) COMMENT '院系',
`title` varchar(50) COMMENT '职称',
`phone` varchar(20),
`email` varchar(100),
`avatar_url` varchar(500),
`status` tinyint DEFAULT 1,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_teacher_no` (`teacher_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='老师表';
-- 班级表
CREATE TABLE `class` (
`id` int NOT NULL AUTO_INCREMENT,
`class_no` varchar(20) NOT NULL COMMENT '班级编号',
`class_name` varchar(100) NOT NULL COMMENT '班级名称',
`department` varchar(100) NOT NULL COMMENT '院系',
`grade` varchar(20) COMMENT '年级',
`major` varchar(100) COMMENT '专业',
`head_teacher_id` int COMMENT '班主任ID',
`student_count` int DEFAULT 0 COMMENT '学生人数',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_class_no` (`class_no`),
KEY `idx_department` (`department`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级表';
-- 课程表(核心表)
CREATE TABLE `course` (
`id` int NOT NULL AUTO_INCREMENT,
`course_no` varchar(20) NOT NULL COMMENT '课程编号',
`course_name` varchar(100) NOT NULL COMMENT '课程名称',
`course_type` tinyint DEFAULT 1 COMMENT '1必修,2选修,3公选',
`credit` decimal(3,1) DEFAULT 1.0 COMMENT '学分',
`hours` int DEFAULT 36 COMMENT '学时',
`department` varchar(100) COMMENT '开课院系',
`teacher_ids` json COMMENT '授课老师ID列表',
`class_ids` json COMMENT '上课班级ID列表',
`semester` varchar(20) COMMENT '学期(如2024春)',
`status` tinyint DEFAULT 1 COMMENT '1正常,2停开',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_course_no_semester` (`course_no`, `semester`),
KEY `idx_semester` (`semester`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';
-- 教学资料表(核心表!)
CREATE TABLE `teaching_material` (
`id` int NOT NULL AUTO_INCREMENT,
`material_no` varchar(30) NOT NULL COMMENT '资料编号:TM+年月日+随机',
`course_id` int NOT NULL COMMENT '所属课程',
`teacher_id` int NOT NULL COMMENT '上传老师',
`title` varchar(200) NOT NULL COMMENT '资料标题',
`description` text COMMENT '资料描述',
`file_type` varchar(20) NOT NULL COMMENT '文件类型:pdf/doc/ppt/xls/video',
`file_name` varchar(200) NOT NULL COMMENT '原始文件名',
`file_size` bigint DEFAULT 0 COMMENT '文件大小(字节)',
`file_url` varchar(500) NOT NULL COMMENT '文件存储URL',
`preview_url` varchar(500) COMMENT '在线预览URL',
`version` varchar(20) DEFAULT '1.0' COMMENT '版本号',
`is_shared` tinyint DEFAULT 1 COMMENT '1共享,0不共享',
`need_review` tinyint DEFAULT 0 COMMENT '0无需审核,1待审核,2已审核',
`review_status` tinyint DEFAULT 1 COMMENT '1通过,2驳回',
`review_remark` varchar(500) COMMENT '审核意见',
`download_count` int DEFAULT 0 COMMENT '下载次数',
`view_count` int DEFAULT 0 COMMENT '查看次数',
`tags` json COMMENT '标签数组',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_material_no` (`material_no`),
KEY `idx_course` (`course_id`),
KEY `idx_teacher` (`teacher_id`),
KEY `idx_file_type` (`file_type`),
FULLTEXT KEY `ft_title_desc` (`title`, `description`) COMMENT '全文索引,方便搜索',
CONSTRAINT `fk_material_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`),
CONSTRAINT `fk_material_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教学资料表';
-- 学生-课程关联表(选课关系)
CREATE TABLE `student_course` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL,
`course_id` int NOT NULL,
`semester` varchar(20) COMMENT '学期',
`score` decimal(5,2) COMMENT '成绩',
`status` tinyint DEFAULT 1 COMMENT '1在修,2已完成',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_course` (`student_id`, `course_id`),
KEY `idx_course` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生选课表';
设计亮点:
- JSON字段存储多对多关系:
teacher_ids、class_ids、tags用JSON存储,查询方便。 - 文件元数据与文件分离:文件存MinIO,数据库只存URL和元数据。
- 全文索引:资料标题和描述加全文索引,搜索快。
- 版本管理:资料有版本号,老师更新时自动生成新版本。
四、功能实现:抓住教学场景核心需求
2. 学生端:资料搜索与下载(智能搜索)
关键逻辑:全文搜索、按课程筛选、智能推荐。
@Service
public class MaterialSearchServiceImpl implements MaterialSearchService {
@Autowired
private TeachingMaterialMapper materialMapper;
@Override
public PageResult<MaterialVO> searchMaterials(MaterialSearchRequest request) {
// 1. 构建查询条件
LambdaQueryWrapper<TeachingMaterial> wrapper = new LambdaQueryWrapper<>();
// 按课程筛选
if (request.getCourseId() != null) {
wrapper.eq(TeachingMaterial::getCourseId, request.getCourseId());
}
// 按老师筛选
if (request.getTeacherId() != null) {
wrapper.eq(TeachingMaterial::getTeacherId, request.getTeacherId());
}
// 按文件类型筛选
if (StringUtils.isNotBlank(request.getFileType())) {
wrapper.eq(TeachingMaterial::getFileType, request.getFileType());
}
// 按标签筛选
if (CollectionUtils.isNotEmpty(request.getTags())) {
for (String tag : request.getTags()) {
wrapper.like(TeachingMaterial::getTags, tag);
}
}
// 按共享状态筛选
wrapper.eq(TeachingMaterial::getIsShared, 1);
// 2. 全文搜索(标题和描述)
if (StringUtils.isNotBlank(request.getKeyword())) {
wrapper.and(w ->
w.like(TeachingMaterial::getTitle, request.getKeyword())
.or()
.like(TeachingMaterial::getDescription, request.getKeyword())
);
}
// 3. 排序
if (StringUtils.isNotBlank(request.getSortField())) {
String sortField = request.getSortField();
boolean isAsc = "asc".equalsIgnoreCase(request.getSortOrder());
if ("downloadCount".equals(sortField)) {
wrapper.orderBy(true, isAsc, TeachingMaterial::getDownloadCount);
} else if ("createTime".equals(sortField)) {
wrapper.orderBy(true, isAsc, TeachingMaterial::getCreateTime);
} else {
wrapper.orderByDesc(TeachingMaterial::getCreateTime); // 默认按时间倒序
}
} else {
wrapper.orderByDesc(TeachingMaterial::getCreateTime);
}
// 4. 分页查询
Page<TeachingMaterial> page = new Page<>(request.getPageNum(), request.getPageSize());
Page<TeachingMaterial> resultPage = materialMapper.selectPage(page, wrapper);
// 5. 转换为VO
List<MaterialVO> voList = resultPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(
voList,
resultPage.getTotal(),
resultPage.getCurrent(),
resultPage.getSize()
);
}
@Override
public List<MaterialVO> getRecommendMaterials(Integer studentId) {
// 1. 获取学生的课程
List<Integer> courseIds = studentCourseService.getStudentCourseIds(studentId);
if (CollectionUtils.isEmpty(courseIds)) {
return Collections.emptyList();
}
// 2. 获取这些课程的热门资料(按下载量)
LambdaQueryWrapper<TeachingMaterial> wrapper = new LambdaQueryWrapper<>();
wrapper.in(TeachingMaterial::getCourseId, courseIds)
.eq(TeachingMaterial::getIsShared, 1)
.orderByDesc(TeachingMaterial::getDownloadCount)
.last("LIMIT 10");
List<TeachingMaterial> materials = materialMapper.selectList(wrapper);
return materials.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
}
@Override
public List<MaterialVO> getLatestMaterials(Integer studentId) {
// 获取学生课程的最新资料(最近7天)
List<Integer> courseIds = studentCourseService.getStudentCourseIds(studentId);
LocalDateTime weekAgo = LocalDateTime.now().minusDays(7);
LambdaQueryWrapper<TeachingMaterial> wrapper = new LambdaQueryWrapper<>();
wrapper.in(TeachingMaterial::getCourseId, courseIds)
.eq(TeachingMaterial::getIsShared, 1)
.ge(TeachingMaterial::getCreateTime, weekAgo)
.orderByDesc(TeachingMaterial::getCreateTime);
List<TeachingMaterial> materials = materialMapper.selectList(wrapper);
return materials.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
}
private MaterialVO convertToVO(TeachingMaterial material) {
MaterialVO vo = new MaterialVO();
BeanUtils.copyProperties(material, vo);
// 获取课程信息
Course course = courseService.getById(material.getCourseId());
if (course != null) {
vo.setCourseName(course.getCourseName());
vo.setCourseNo(course.getCourseNo());
}
// 获取老师信息
Teacher teacher = teacherService.getById(material.getTeacherId());
if (teacher != null) {
vo.setTeacherName(teacher.getTeacherName());
}
// 格式化文件大小
vo.setFileSizeFormatted(formatFileSize(material.getFileSize()));
// 判断是否可以下载/预览
vo.setCanDownload(true);
vo.setCanPreview(material.getPreviewUrl() != null);
return vo;
}
private String formatFileSize(long bytes) {
if (bytes < 1024) {
return bytes + " B";
} else if (bytes < 1024 * 1024) {
return String.format("%.1f KB", bytes / 1024.0);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
} else {
return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
}
}
}
五、在线预览功能实现(核心体验)
1. 多种格式预览方案
@Component
public class FilePreviewService {
@Autowired
private MinioService minioService;
/**
* 生成文件预览URL
*/
public String generatePreviewUrl(String filename, String fileType) {
switch (fileType.toLowerCase()) {
case "pdf":
return generatePdfPreviewUrl(filename);
case "doc":
case "docx":
case "ppt":
case "pptx":
case "xls":
case "xlsx":
return generateOfficePreviewUrl(filename);
case "jpg":
case "jpeg":
case "png":
case "gif":
return generateImagePreviewUrl(filename);
case "mp4":
case "avi":
case "mov":
return generateVideoPreviewUrl(filename);
default:
return null; // 不支持预览
}
}
/**
* PDF预览(使用PDF.js)
*/
private String generatePdfPreviewUrl(String filename) {
// 获取文件URL
String fileUrl = minioService.getFileUrl("teaching-files", filename);
// PDF.js预览URL格式
return "/pdfjs/web/viewer.html?file=" + URLEncoder.encode(fileUrl, StandardCharsets.UTF_8);
}
/**
* Office文档预览(使用OnlyOffice)
*/
private String generateOfficePreviewUrl(String filename) {
// OnlyOffice配置
String onlyOfficeUrl = "http://localhost:8080/onlyoffice";
// 获取文件信息
String fileUrl = minioService.getFileUrl("teaching-files", filename);
String fileExt = filename.substring(filename.lastIndexOf(".") + 1);
// 生成文档密钥
String docKey = DigestUtils.md5DigestAsHex(filename.getBytes());
// OnlyOffice配置参数
Map<String, Object> config = new HashMap<>();
config.put("documentType", getDocumentType(fileExt));
config.put("document", Map.of(
"title", filename,
"url", fileUrl,
"fileType", fileExt,
"key", docKey
));
config.put("editorConfig", Map.of(
"callbackUrl", "/api/preview/callback",
"lang", "zh-CN"
));
return onlyOfficeUrl + "?config=" + URLEncoder.encode(JSON.toJSONString(config), StandardCharsets.UTF_8);
}
/**
* 图片预览(直接返回图片URL)
*/
private String generateImagePreviewUrl(String filename) {
return minioService.getFileUrl("teaching-files", filename);
}
/**
* 视频预览(使用HTML5 video)
*/
private String generateVideoPreviewUrl(String filename) {
String fileUrl = minioService.getFileUrl("teaching-files", filename);
return "/preview/video?url=" + URLEncoder.encode(fileUrl, StandardCharsets.UTF_8);
}
private String getDocumentType(String fileExt) {
switch (fileExt.toLowerCase()) {
case "doc":
case "docx":
return "text";
case "ppt":
case "pptx":
return "presentation";
case "xls":
case "xlsx":
return "spreadsheet";
default:
return "text";
}
}
}
2. 视频预览页面
<!DOCTYPE html>
<html>
<head>
<title>视频预览</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f0f0f0;
}
.video-container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
video {
width: 100%;
border-radius: 8px;
}
.video-info {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.video-actions {
margin-top: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="video-container">
<video id="videoPlayer" controls controlsList="nodownload">
<source src="${videoUrl}" type="video/mp4">
您的浏览器不支持视频播放
</video>
<div class="video-info">
<h3>视频信息</h3>
<p><strong>文件名:</strong>${fileName}</p>
<p><strong>文件大小:</strong>${fileSize}</p>
<p><strong>上传时间:</strong>${uploadTime}</p>
<p><strong>播放次数:</strong>${viewCount}</p>
</div>
<div class="video-actions">
<button onclick="downloadVideo()" class="btn-download">
下载视频
</button>
<button onclick="shareVideo()" class="btn-share">
分享链接
</button>
</div>
</div>
<script>
// 记录播放时间
const video = document.getElementById('videoPlayer');
let watchStartTime = null;
video.addEventListener('play', function() {
watchStartTime = Date.now();
});
video.addEventListener('pause', function() {
if (watchStartTime) {
const watchDuration = Date.now() - watchStartTime;
recordWatchTime(watchDuration);
watchStartTime = null;
}
});
video.addEventListener('ended', function() {
if (watchStartTime) {
const watchDuration = Date.now() - watchStartTime;
recordWatchTime(watchDuration);
watchStartTime = null;
}
});
function recordWatchTime(duration) {
// 发送AJAX请求记录观看时间
fetch('/api/material/record-watch', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
materialId: ${materialId},
duration: duration,
timestamp: Date.now()
})
});
}
function downloadVideo() {
window.location.href = '/api/material/download/${materialId}';
}
function shareVideo() {
const shareUrl = window.location.href;
if (navigator.share) {
navigator.share({
title: '教学视频分享',
text: '分享一个教学视频给你',
url: shareUrl
});
} else {
// 复制到剪贴板
navigator.clipboard.writeText(shareUrl).then(() => {
alert('链接已复制到剪贴板');
});
}
}
</script>
</body>
</html>
六、系统测试:重点测试场景
核心测试用例
| 测试场景 | 测试步骤 | 预期结果 | 重要性 |
|---|---|---|---|
| 大文件上传 | 上传400MB的视频文件 | 上传成功,有进度条显示 | 高 |
| 多格式支持 | 上传PDF、Word、PPT、视频 | 都能上传成功 | 高 |
| 在线预览 | 点击各种格式文件的预览按钮 | 能在线查看文件内容 | 高 |
| 权限控制 | 学生A尝试下载学生B课程的资料 | 提示无权下载 | 高 |
| 搜索功能 | 输入关键词搜索资料 | 能搜到相关文件 | 中 |
| 版本管理 | 老师更新已上传的文件 | 生成新版本,保留历史记录 | 中 |
压力测试
用JMeter模拟100个学生同时下载文件,测试服务器并发处理能力。
七、部署方案
1. 技术架构
前端(Nginx)
↓
后端(SpringBoot)
↓
数据库(MySQL)
↓
文件存储(MinIO)
↓
预览服务(PDF.js + OnlyOffice)
2. 一键部署脚本
#!/bin/bash
# deploy-teaching-system.sh
echo "开始部署教学资料管理系统..."
# 1. 安装Docker(如果没安装)
if ! command -v docker &> /dev/null; then
echo "安装Docker..."
curl -fsSL https://get.docker.com | bash
fi
# 2. 启动MySQL
docker run -d \
--name mysql-teaching \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=teaching_material \
-v /data/mysql:/var/lib/mysql \
mysql:8.0
# 3. 启动MinIO(文件存储)
docker run -d \
--name minio-teaching \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-v /data/minio:/data \
minio/minio server /data --console-address ":9001"
# 4. 启动OnlyOffice(文档预览)
docker run -d \
--name onlyoffice-teaching \
-p 8080:80 \
onlyoffice/documentserver
# 5. 部署后端应用
mkdir -p /app/teaching-system
cp teaching-system.jar /app/teaching-system/
cp application-prod.yml /app/teaching-system/
cd /app/teaching-system
nohup java -jar teaching-system.jar --spring.profiles.active=prod > app.log 2>&1 &
# 6. 部署前端
mkdir -p /var/www/teaching
cp -r dist/* /var/www/teaching/
# 7. 配置Nginx
cat > /etc/nginx/conf.d/teaching.conf <<EOF
server {
listen 80;
server_name teaching.yourschool.com;
# 前端
location / {
root /var/www/teaching;
try_files \$uri \$uri/ /index.html;
}
# 后端API
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
# 文件下载
location /files/ {
proxy_pass http://localhost:9000;
}
# PDF预览
location /pdfjs/ {
alias /var/www/pdfjs/;
}
# 视频预览
location /preview/video {
proxy_pass http://localhost:8080;
}
}
EOF
nginx -s reload
echo "部署完成!"
echo "访问地址:http://teaching.yourschool.com"
echo "MinIO管理:http://your-server:9001"
八、答辩准备:突出教学特色
-
演示流程要完整: “大家好,我演示教学资料系统的核心流程。首先,张老师登录系统,为《Java程序设计》课程上传课件(展示多格式上传)。然后,学生李雷登录,在课程资料列表中看到这个课件(展示智能推荐),他可以直接在线预览(展示PDF/Word预览功能),也可以下载到本地。管理员可以查看所有资料的上传记录和下载统计。”
-
重点讲“教学特色功能”:
- “我实现了多格式文件支持,老师可以上传各种教学资料。”
- “在线预览功能让学生不用下载就能查看文件,节省流量和时间。”
- “智能推荐根据学生的学习记录推荐相关资料。”
- “版本管理让老师更新资料时不会覆盖旧版本。”
-
准备好问答:
- Q:大文件上传中断怎么办? A:实现了断点续传功能,上传中断后可以继续上传,不会重新开始。
- Q:如何防止资料被非法下载? A:严格的权限控制,学生只能下载自己课程的资料,所有下载都有日志记录。
- Q:系统能支持多少用户? A:采用MinIO分布式存储,支持水平扩展,理论上可以支持上千用户同时使用。
最后:一点真心话
教学资料管理系统要真正好用,必须站在老师和学生的角度思考。上传要方便、下载要快捷、查找要智能,这三点比任何花哨功能都重要。
需要完整源码、多格式预览方案、部署文档的宝子,可以在评论区留言。
觉得这篇干货有帮助,记得点赞收藏!祝大家毕设顺利,都能做出实用的教学系统!📚