毕业设计实战:SpringBoot教学资料管理系统,从0到1完整开发指南

46 阅读12分钟

毕业设计实战: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-PlusCRUD神器,代码生成器好用。学会用它的@TableField注解。
Vue 2 + Element UI组件丰富,做后台管理很快。文件上传组件要自己封装。
MySQL 8.0JSON字段支持,存资料标签方便。字符集一定用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='学生选课表';

设计亮点

  1. JSON字段存储多对多关系teacher_idsclass_idstags用JSON存储,查询方便。
  2. 文件元数据与文件分离:文件存MinIO,数据库只存URL和元数据。
  3. 全文索引:资料标题和描述加全文索引,搜索快。
  4. 版本管理:资料有版本号,老师更新时自动生成新版本。

四、功能实现:抓住教学场景核心需求

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"

八、答辩准备:突出教学特色

  1. 演示流程要完整: “大家好,我演示教学资料系统的核心流程。首先,张老师登录系统,为《Java程序设计》课程上传课件(展示多格式上传)。然后,学生李雷登录,在课程资料列表中看到这个课件(展示智能推荐),他可以直接在线预览(展示PDF/Word预览功能),也可以下载到本地。管理员可以查看所有资料的上传记录和下载统计。”

  2. 重点讲“教学特色功能”

    • “我实现了多格式文件支持,老师可以上传各种教学资料。”
    • “在线预览功能让学生不用下载就能查看文件,节省流量和时间。”
    • “智能推荐根据学生的学习记录推荐相关资料。”
    • “版本管理让老师更新资料时不会覆盖旧版本。”
  3. 准备好问答

    • Q:大文件上传中断怎么办? A:实现了断点续传功能,上传中断后可以继续上传,不会重新开始。
    • Q:如何防止资料被非法下载? A:严格的权限控制,学生只能下载自己课程的资料,所有下载都有日志记录。
    • Q:系统能支持多少用户? A:采用MinIO分布式存储,支持水平扩展,理论上可以支持上千用户同时使用。

最后:一点真心话

教学资料管理系统要真正好用,必须站在老师和学生的角度思考。上传要方便、下载要快捷、查找要智能,这三点比任何花哨功能都重要。

需要完整源码多格式预览方案部署文档的宝子,可以在评论区留言。

觉得这篇干货有帮助,记得点赞收藏!祝大家毕设顺利,都能做出实用的教学系统!📚