1. 项目背景与核心需求
郑州工商学院作为一所应用型本科院校,日常教学中会产生大量课件、习题集、复习笔记等学习资料。传统通过QQ群、网盘分享的方式存在资料分散、版本混乱、检索困难等问题。这个基于SpringBoot+Vue的全栈项目,正是为了解决以下痛点:
- 资料孤岛现象:各班级、专业间缺乏统一的分享渠道,优质资源难以跨年级流通
- 版本管理缺失:同一门课程可能存在多个教师版本,学生难以获取最新权威资料
- 移动端适配不足:现有分享方式在手机端操作体验差,无法满足碎片化学习需求
- 权限控制薄弱:无法区分公开资料与内部教学资源,存在版权风险
实际开发中发现,学生用户最迫切的需求是能按"课程-章节-资料类型"三级结构组织内容,并支持手机端PDF预览和关键词搜索。
2. 技术选型与架构设计
2.1 前后端分离架构
采用经典的SpringBoot+Vue组合,主要基于以下考虑:
-
SpringBoot优势:
- 内嵌Tomcat简化部署,适合学院IT运维能力有限的现状
- Starter机制快速集成MyBatis-Plus、Redis等教学系统常用组件
- Actuator端点便于监控服务健康状态
-
Vue优势:
- 组件化开发适合构建资料分类展示的复用界面
- Vue Router实现无刷新页面跳转,提升资料浏览体验
- 配合Element UI可快速搭建管理后台界面
2.2 数据库设计要点
核心表结构设计考虑了教学场景的特殊性:
sql复制CREATE TABLE `course_material` (
`id` BIGINT PRIMARY KEY COMMENT '雪花算法ID',
`course_code` VARCHAR(20) NOT NULL COMMENT '课程编码(如COMP2021)',
`chapter_no` INT COMMENT '章节序号',
`material_type` ENUM('课件','习题','笔记','视频') NOT NULL,
`title` VARCHAR(100) NOT NULL,
`file_url` VARCHAR(255) NOT NULL COMMENT 'OSS存储路径',
`download_count` INT DEFAULT 0,
`uploader_id` BIGINT NOT NULL COMMENT '关联用户表',
`is_approved` TINYINT DEFAULT 0 COMMENT '审核状态',
`version` INT DEFAULT 1 COMMENT '乐观锁版本号'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别添加version字段处理并发下载场景,避免计数不准确。
3. 核心功能实现细节
3.1 文件上传与预览
采用阿里云OSS作为存储后端,关键实现逻辑:
java复制// SpringBoot服务端签名生成
@GetMapping("/oss/policy")
public Result getOssPolicy(@RequestParam String fileType) {
String dir = "material/" + DateUtil.today() + "/";
String host = "https://" + bucketName + "." + endpoint;
OssPolicyResult result = ossService.policy(dir, fileType);
return Result.success(result.setHost(host));
}
// Vue前端上传组件
<template>
<el-upload
:action="policy.host"
:data="policy"
:before-upload="checkFile"
@success="handleSuccess">
<el-button type="primary">点击上传</el-button>
</el-upload>
</template>
PDF预览使用PDF.js方案,解决移动端兼容性问题:
javascript复制// 在Vue中集成PDF.js
initPDFViewer(url) {
const loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(pdf => {
this.pageCount = pdf.numPages;
pdf.getPage(1).then(page => {
const viewport = page.getViewport({ scale: 1.5 });
const canvas = this.$refs.pdfCanvas;
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({
canvasContext: canvas.getContext('2d'),
viewport: viewport
});
});
});
}
3.2 智能搜索功能
结合Elasticsearch实现多维度搜索:
- 索引映射设计:
json复制{
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"courseName": { "type": "keyword" },
"content": { "type": "text", "analyzer": "ik_smart" },
"uploadTime": { "type": "date" }
}
}
- 混合搜索策略:
java复制public Page<MaterialDoc> search(String keyword, String courseCode) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
if (StringUtils.hasText(courseCode)) {
builder.withFilter(QueryBuilders.termQuery("courseCode", courseCode));
}
builder.withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content")
.minimumShouldMatch("70%"));
builder.withSort(SortBuilders.fieldSort("downloadCount").order(DESC));
return elasticsearchRestTemplate.search(builder.build(), MaterialDoc.class);
}
4. 权限控制与审核机制
4.1 基于RBAC的权限模型
mermaid复制graph TD
A[游客] -->|浏览公开资料| B(普通用户)
B -->|上传资料| C[待审核状态]
C -->|管理员审核| D[正式发布]
E[教师角色] -->|直接发布| D
F[管理员] -->|管理所有内容| D
实际开发中使用Spring Security + JWT实现:
java复制@PreAuthorize("hasRole('TEACHER') or hasRole('ADMIN')")
@PostMapping("/approve/{id}")
public Result approveMaterial(@PathVariable Long id) {
materialService.approveMaterial(id);
return Result.success();
}
4.2 敏感内容过滤方案
- 文件内容检查:
python复制# 使用Python脚本检测PDF文本内容(通过Java调用)
def check_pdf_content(pdf_path):
with open(pdf_path, 'rb') as f:
text = extract_text(f)
for word in sensitive_words:
if word in text:
return False
return True
- 图片OCR识别:
集成Tesseract进行图片文字识别,防止敏感信息通过图片形式传播。
5. 性能优化实践
5.1 缓存策略设计
采用多级缓存架构:
- 热点资料缓存:
java复制@Cacheable(value = "hotMaterials", key = "#courseCode")
public List<MaterialVO> getHotMaterials(String courseCode) {
return baseMapper.selectHotMaterials(courseCode);
}
- 下载限流保护:
java复制@RateLimiter(value = 10, key = "#materialId")
@GetMapping("/download/{materialId}")
public void downloadMaterial(@PathVariable Long materialId) {
// 下载逻辑
}
5.2 前端性能优化
- 懒加载与分页:
vue复制<template>
<div v-infinite-scroll="loadMore" :infinite-scroll-disabled="busy">
<material-card v-for="item in materials" :key="item.id"/>
</div>
</template>
<script>
export default {
methods: {
loadMore() {
if (this.page * this.size >= this.total) return;
this.busy = true;
fetchMaterials(++this.page).then(data => {
this.materials.push(...data.list);
this.busy = false;
});
}
}
}
</script>
- WebP图片转换:
使用Nginx动态转换图片格式:
nginx复制location ~* ^/uploads/.+\.(jpg|png)$ {
if ($http_accept ~* "webp") {
add_header Vary Accept;
rewrite ^(.+)\.(jpg|png)$ $1.$2.webp break;
}
}
6. 部署与运维方案
6.1 容器化部署
Docker Compose编排方案:
yaml复制version: '3'
services:
backend:
image: registry.cn-hangzhou.aliyuncs.com/zzgsxy/material-backend:${TAG}
ports:
- "8080:8080"
depends_on:
- redis
- mysql
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
6.2 监控与日志
- Prometheus监控配置:
yaml复制spring:
application:
name: material-service
metrics:
export:
prometheus:
enabled: true
tags:
service: ${spring.application.name}
- 日志收集方案:
使用Filebeat + ELK Stack收集日志,关键Nginx日志格式:
nginx复制log_format json_analytics escape=json
'{'
'"timestamp":"$time_iso8601",'
'"host":"$host",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"request_time":$request_time'
'}';
7. 项目演进方向
- 智能推荐系统:
python复制# 基于协同过滤的推荐算法原型
def recommend_materials(user_id):
user_courses = get_user_courses(user_id)
similar_users = find_similar_users(user_courses)
return aggregate_materials(similar_users)
- 在线协作功能:
集成OnlyOffice实现文档协同编辑:
javascript复制const docEditor = new DocsAPI.DocEditor("editor", {
document: {
fileType: "docx",
key: "material_123",
title: "小组笔记.docx",
url: "/api/material/123/content"
},
editorConfig: {
callbackUrl: "/api/save/123"
}
});
- 移动端优化:
使用Capacitor打包为原生应用:
javascript复制// 在Vue项目中添加原生功能
import { App } from '@capacitor/app';
App.addListener('backButton', ({ canGoBack }) => {
if (!canGoBack) {
App.exitApp();
} else {
window.history.back();
}
});
8. 典型问题解决方案
8.1 大文件上传中断续传
前端分片上传实现:
javascript复制async function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkNumber', i + 1);
formData.append('totalChunks', chunks);
await axios.post('/api/upload/chunk', formData, {
headers: { 'Content-Range': `bytes ${i*chunkSize}-${(i+1)*chunkSize-1}/${file.size}` }
});
}
}
服务端合并逻辑:
java复制public void mergeChunks(String fileKey, int totalChunks) throws IOException {
try (FileOutputStream fos = new FileOutputStream(finalFile)) {
for (int i = 1; i <= totalChunks; i++) {
File chunkFile = new File(tempDir, fileKey + ".part" + i);
Files.copy(chunkFile.toPath(), fos);
chunkFile.delete();
}
}
}
8.2 高并发下载优化
使用Nginx的X-Accel-Redirect实现高效文件下载:
nginx复制location /protected/ {
internal;
alias /data/uploads/;
}
Java控制器:
java复制@GetMapping("/download/{id}")
public void download(@PathVariable Long id, HttpServletResponse response) {
Material material = materialService.getById(id);
response.setHeader("X-Accel-Redirect", "/protected/" + material.getFilePath());
response.setHeader("Content-Disposition", "attachment; filename=" + material.getFileName());
}
9. 安全防护措施
9.1 防爬虫策略
- 动态渲染保护:
vue复制<template>
<div v-if="humanVerify">
<!-- 真实内容 -->
</div>
<div v-else>
<!-- 诱饵内容 -->
</div>
</template>
<script>
export default {
data() {
return {
humanVerify: false
}
},
mounted() {
this.checkHuman();
},
methods: {
checkHuman() {
// 通过鼠标移动轨迹、点击行为等判断
this.humanVerify = true;
}
}
}
</script>
- API限流配置:
java复制@Configuration
public class RateLimitConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RateLimitInterceptor())
.addPathPatterns("/api/material/search");
}
}
9.2 内容安全策略
- CSP头部设置:
nginx复制add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: oss.example.com";
- XSS防护:
前端使用DOMPurify过滤:
javascript复制import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href', 'title']
});
10. 项目成果与反思
系统上线后关键指标:
- 日均活跃用户:1200+
- 资料总量:8500+份
- 平均下载延迟:<500ms
- 服务器负载:<30%
遇到的典型问题及解决方案:
-
PDF预览兼容性问题:
- 现象:iOS Safari无法正常渲染某些PDF
- 解决方案:强制转换为PDF/A-1a标准格式
-
数据库连接泄漏:
- 现象:高峰时段出现连接池耗尽
- 解决方案:引入Druid连接池监控,优化事务边界
-
移动端上传失败:
- 现象:部分Android机型上传中断
- 解决方案:将分片大小从5MB调整为2MB
项目后续改进方向:
- 引入AI自动分类(使用NLP技术识别资料内容)
- 增加学习进度跟踪功能
- 开发微信小程序版本扩大覆盖范围
