作为一名经历过多次毕业设计指导的开发者,我深知一个完整的校园作业管理系统对于计算机专业学生的重要性。这个基于SpringBoot+Vue的作业管理平台,正是为了解决传统纸质作业管理效率低下、信息不准确等问题而设计的全栈解决方案。
系统采用前后端分离架构,后端使用SpringBoot+MyBatis技术栈,前端基于Vue.js+ElementUI,实现了管理员、教师和学生三个角色的完整作业管理流程。相比市面上简单的CRUD系统,本项目特别注重实际教学场景中的需求细节,比如作业批改的痕迹保留、多条件组合查询、批量操作等实用功能。
SpringBoot 2.7.x作为基础框架,其自动配置特性大幅减少了XML配置工作量。这里特别说明几个关键配置:
java复制@SpringBootApplication
@MapperScan("com.edu.mapper")
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
数据库连接池选用阿里Druid,相比HikariCP,它提供了更完善的监控功能。在application.yml中配置示例:
yaml复制spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
注意:生产环境务必修改默认监控账号密码,并限制访问IP
权限控制采用SpringSecurity+JWT组合方案,解决了传统Session方式在分布式环境下的问题。核心过滤器配置:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
Vue 3.x组合式API大幅提升了代码组织效率。项目采用典型的前端分层结构:
code复制src/
├── api/ # 接口定义
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
└── views/ # 页面组件
Element Plus组件库提供了丰富的UI控件,特别优化了表单验证逻辑:
vue复制<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item label="作业标题" prop="title">
<el-input v-model="form.title"></el-input>
</el-form-item>
</el-form>
<script>
const rules = {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' },
{ min: 5, max: 50, message: '长度在5到50个字符', trigger: 'blur' }
]
}
</script>
作业管理核心的ER关系如下图所示(需替换为实际ER图)。几个需要特别注意的设计:
sql复制ALTER TABLE homework ADD COLUMN version INT DEFAULT 0;
sql复制CREATE TABLE homework_submit (
id BIGINT PRIMARY KEY,
homework_id BIGINT NOT NULL,
student_id BIGINT NOT NULL,
content TEXT NOT NULL,
attachments VARCHAR(512),
submit_time DATETIME NOT NULL,
status TINYINT DEFAULT 0 COMMENT '0-待批改,1-已批改'
) ENGINE=InnoDB;
sql复制CREATE TABLE homework_review (
id BIGINT PRIMARY KEY,
submit_id BIGINT NOT NULL,
teacher_id BIGINT NOT NULL,
score DECIMAL(5,2),
comments TEXT,
corrections JSON COMMENT '批注位置及内容',
review_time DATETIME NOT NULL
);
教师端作业发布采用多步骤表单设计,关键实现代码:
java复制@PostMapping("/homeworks")
public Result publishHomework(@Valid @RequestBody HomeworkDTO dto) {
// 1. 基础信息校验
if(dto.getDeadline().before(new Date())) {
throw new BusinessException("截止时间不能早于当前时间");
}
// 2. 构建实体
Homework homework = new Homework();
BeanUtils.copyProperties(dto, homework);
homework.setPublishTime(new Date());
homework.setStatus(HomeworkStatus.PUBLISHED);
// 3. 保存作业与班级关联关系
homeworkMapper.insert(homework);
for(Long classId : dto.getClassIds()) {
homeworkClassMapper.insert(new HomeworkClass(homework.getId(), classId));
}
// 4. 异步通知学生
notifyStudents(homework.getId());
return Result.success();
}
前端采用Steps组件引导教师完成发布流程:
vue复制<el-steps :active="activeStep" finish-status="success">
<el-step title="基本信息"></el-step>
<el-step title="选择班级"></el-step>
<el-step title="设置附件"></el-step>
</el-steps>
批改界面实现的核心难点在于实现类似Word的批注功能。我们采用Canvas+自定义选区方案:
javascript复制function generateAnchor(text) {
return text.split('').map((char, index) =>
`<span class="text-segment" data-pos="${index}">${char}</span>`
).join('');
}
javascript复制document.addEventListener('mouseup', () => {
const selection = window.getSelection();
if (selection.toString().trim()) {
const start = selection.anchorNode.parentNode.dataset.pos;
const end = selection.focusNode.parentNode.dataset.pos;
this.selectedRange = { start, end };
}
});
java复制public class CorrectionDTO {
private Long submitId;
private Integer startPos;
private Integer endPos;
private String comment;
private String color;
}
使用ECharts实现多维度的作业统计,后端提供聚合查询接口:
java复制@GetMapping("/homeworks/statistics")
public Result getStatistics(@RequestParam Long teacherId) {
Map<String, Object> result = new HashMap<>();
// 作业发布量趋势
result.put("publishTrend", homeworkMapper.selectPublishTrend(teacherId));
// 班级平均分对比
result.put("scoreCompare", submitMapper.selectClassScoreCompare(teacherId));
// 作业完成率
result.put("completionRate", submitMapper.selectCompletionRate(teacherId));
return Result.success(result);
}
前端使用v-charts封装常用图表:
vue复制<ve-histogram :data="chartData" :settings="chartSettings"></ve-histogram>
<script>
export default {
data() {
return {
chartSettings: {
metrics: ['平均分'],
dimension: ['班级']
}
}
}
}
</script>
推荐使用Docker Compose进行容器化部署,示例docker-compose.yml:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/homework?useSSL=false
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
frontend:
build: ./frontend
ports:
- "80:80"
重要提示:实际部署时应使用外部配置文件管理敏感信息,不要将密码硬编码在文件中
xml复制<cache eviction="LRU" flushInterval="60000" size="512"/>
java复制@Cacheable(value = "homework", key = "#id")
public Homework getById(Long id) {
return homeworkMapper.selectById(id);
}
javascript复制const HomeworkList = () => import('./views/homework/List.vue')
vue复制<template>
<Suspense>
<AsyncComponent />
<template #fallback>Loading...</template>
</Suspense>
</template>
<script setup>
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
</script>
开发阶段常见跨域问题,推荐以下两种解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
}
}
javascript复制// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
yaml复制# 后端配置
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
vue复制<!-- 前端限制 -->
<el-upload
:before-upload="beforeUpload"
></el-upload>
<script>
methods: {
beforeUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('文件大小不能超过10MB');
}
return isLt10M;
}
}
</script>
java复制public String uploadToOss(MultipartFile file) {
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
String fileName = UUID.randomUUID() + getFileExtension(file.getOriginalFilename());
ossClient.putObject(bucketName, fileName, file.getInputStream());
return "https://" + bucketName + "." + endpoint + "/" + fileName;
} finally {
ossClient.shutdown();
}
}
sql复制ALTER TABLE homework_submit ADD INDEX idx_homework_student (homework_id, student_id);
ALTER TABLE homework_review ADD INDEX idx_submit (submit_id);
java复制public enum HomeworkStatus {
DRAFT(0), PUBLISHED(1), CLOSED(2);
private final int code;
// ...
}
sql复制CREATE TABLE homework_content (
homework_id BIGINT PRIMARY KEY,
content LONGTEXT,
FOREIGN KEY (homework_id) REFERENCES homework(id)
);
css复制@media screen and (max-width: 768px) {
.form-item {
width: 100%;
}
}
javascript复制// 小程序端API适配
const request = (url, data) => {
if (process.env.VUE_APP_PLATFORM === 'mp-weixin') {
return uni.request({ url: baseURL + url, data });
}
return axios.get(url, { params: data });
}
java复制public class SimHash {
public static int compute(String text) {
// 实现SimHash算法
}
public static int hammingDistance(int hash1, int hash2) {
return Integer.bitCount(hash1 ^ hash2);
}
}
python复制# 伪代码示例
def auto_grade(submission_code, test_cases):
scores = []
for case in test_cases:
try:
output = execute_code(submission_code, case['input'])
scores.append(1 if output == case['expect'] else 0)
except:
scores.append(0)
return sum(scores) / len(scores) * 100
随着系统规模扩大,可考虑拆分为微服务架构:
yaml复制# application.yml示例
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: homework-service
uri: lb://homework-service
predicates:
- Path=/api/homework/**
在开发这个系统的过程中,我深刻体会到教学管理系统的设计必须紧密贴合实际使用场景。比如在批改功能中,最初设计的简单打分功能在实际测试中被教师反馈不够用,后来才增加了批注和痕迹保留功能。这也提醒我,在开发教育类系统时,一定要多与一线教师沟通,了解他们的真实工作流程和痛点。