ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot三角色作业协同系统:JWT+RBAC+全生命周期管理

2026/9/11 16:27:13 拓冰建站 浏览量
Spring Boot三角色作业协同系统:JWT+RBAC+全生命周期管理 简介本资源是一套完整的基于Spring Boot的Java作业管理系统毕业设计项目面向计算机专业本科生及Java Web开发初学者解决教学场景中作业发布、提交、批阅与反馈等核心管理需求。系统采用B/S架构涵盖管理员、教师、学生三角色权限体系实现班级管理、作业全生命周期管控、提交记录追踪及点评互动等功能适合作为课程设计、毕设参考或Web开发实战训练。压缩包含384个文件主体为90个Java后端业务逻辑文件、38个Vue前端组件、161个SVG图标资源辅以SQL建表脚本、YML配置、BAT启动脚本及多类静态资源整体8.12MB结构清晰模块边界明确便于理解MVC分层与前后端协作流程。已有390人学习下载提供可直接运行的完整工程含mvnw、数据库脚本及多环境启动批处理附带.bak备份文件与典型页面组件利于调试分析与二次开发。1. 这不是又一个“登录增删改查”的毕设模板而是一套可落地的三角色作业协同闭环你可能已经见过太多标着“Spring Boot作业管理系统”的毕业设计——首页写着“欢迎来到XX系统”后台只有管理员能进学生点提交按钮没反应教师看不到作业列表。但这个项目不同它真实跑通了从学生注册、教师发布、作业提交、多文件上传、教师点评、学生查反馈的全链路且所有角色均支持独立注册登录非硬编码账号密码修改走标准 JWT 流程前端.vue.bak文件保留了原始组件结构后端mvnw.cmd和3-build.bat表明它被实际构建过、部署过。它解决的不是“能不能跑”而是“怎么让教师不反复催交、学生不漏看截止时间、管理员不手动导出 Excel”的真实教学协作断点。适合两类人一是需要交付可演示、有业务逻辑深度的 Java 毕业设计的学生二是想快速复用角色权限模型、文件上传流程、多级点评状态机的中小型教务工具开发者。它不追求炫技的 AI 批改或实时协作但把 Spring Boot 的事务边界、MyBatis 动态 SQL 权限过滤、Vue 路由守卫与后端鉴权的对齐做得很扎实。2. 三角色权限模型与 Spring Security 实战从 URL 拦截到方法级注解的逐层加固2.1 为什么不用 ShiroSpring Security 在本系统中的选型依据本系统采用 Spring Security 而非 Shiro核心原因有三点第一Spring Boot 2.7 对 Security 的自动配置已高度成熟EnableWebSecurity配合SecurityFilterChain可在 50 行内完成 RBAC 基础框架而 Shiro 需额外维护ShiroFilterFactoryBean和IniSecurityManagerFactory第二系统需对接 JWT 无状态认证Security 的JwtAuthenticationFilter与BearerTokenResolver组合比 Shiro 的JwtToken自定义更贴近 OAuth2 规范第三教师点评作业需细粒度控制如“只能点评自己班级的学生作业”Security 的PreAuthorize(hasRole(TEACHER) and #studentId in T(com.example.util.ClassUtil).getStudentIdsByTeacher(#teacherId))支持 SpEL 表达式动态校验Shiro 的RequiresPermissions仅支持静态字符串。项目中SecurityConfig.java明确声明了/api/student/**仅允许 STUDENT 角色访问/api/teacher/**仅 TEACHER/api/admin/**仅 ADMIN且/api/login和/api/register全部放行——这是权限控制的第一道防线。2.2 角色-资源映射表与数据库权限设计系统权限不依赖硬编码而是通过四张表实现动态授权sys_user用户基础信息含role_type字段存 ADMIN/TEACHER/STUDENTsys_role角色定义主键role_idsys_permission权限项如student:submit、teacher:review、admin:user:deletesys_role_permission角色-权限关联表复合主键role_id permission_id关键设计在于教师发布作业时后端会校验该教师所属班级teacher_class表与作业绑定班级homework.class_id是否一致学生提交作业时homework_submit表插入前会查询homework表确认该作业未截止deadline now()且状态为PUBLISHED。这种校验不是靠前端 JS 控制而是HomeworkService.java中Transactional方法内调用homeworkMapper.selectById(homeworkId)后显式判断避免绕过前端直接发请求导致数据错乱。2.3 登录流程与 JWT Token 的生成、校验、刷新机制登录接口/api/login接收username和password经BCryptPasswordEncoder校验密码后生成 JWT Token// LoginController.java public Result login(RequestBody LoginRequest request) { User user userService.findByUsername(request.getUsername()); if (user null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { return Result.fail(用户名或密码错误); } String token jwtUtil.generateToken(user.getUserId(), user.getRoleType()); // 生成含 userId 和 role_type 的 JWT return Result.success(Map.of(token, token, role, user.getRoleType())); }jwtUtil.generateToken()使用io.jsonwebtoken:jjwt-api:0.11.5密钥存于application.yml的jwt.secret: your-secret-key-here有效期设为 2 小时jwt.expiration: 7200。前端将 Token 存入 localStorage并在每次请求头添加Authorization: Bearer token。后端JwtAuthenticationFilter拦截请求解析 Token 获取userId和roleType再调用UserDetailsService加载完整用户信息并设置SecurityContextHolder.getContext().setAuthentication(authentication)。注意Token 刷新未实现长连接保活而是采用“过期即重新登录”策略符合教育系统低频操作场景。2.4 方法级权限控制与动态数据过滤实战教师查看自己发布的作业列表接口/api/teacher/homeworks并非简单PreAuthorize(hasRole(TEACHER))而是// HomeworkController.java GetMapping(/homeworks) PreAuthorize(hasRole(TEACHER)) public Result getHomeworksByTeacher(RequestParam Long teacherId) { ListHomework homeworks homeworkService.findByTeacherId(teacherId); return Result.success(homeworks); }homeworkService.findByTeacherId()内部执行// HomeworkServiceImpl.java public ListHomework findByTeacherId(Long teacherId) { // 动态 SQL只查 teacher_id ? 且 status ! DELETED 的作业 return homeworkMapper.selectByTeacherId(teacherId); }对应 MyBatis XML!-- HomeworkMapper.xml -- select idselectByTeacherId resultTypeHomework SELECT * FROM homework WHERE teacher_id #{teacherId} AND status ! DELETED /select这种“接口层注解 服务层参数校验 Mapper 层 SQL 过滤”三层防护确保即使攻击者伪造teacherId参数也无法越权读取他人作业。学生查看作业列表同理homeworkService.findByClassId()会根据学生所属班级 ID 查询而非直接查student_id——因为一份作业面向整个班级而非单个学生。3. 作业全生命周期管理从发布、提交、多文件上传到教师点评的状态机实现3.1 作业状态流转与数据库字段设计系统用单一status字段VARCHAR管理作业全生命周期而非多个布尔字段避免状态爆炸状态值含义触发动作DRAFT草稿教师保存未发布教师点击“保存草稿”PUBLISHED已发布学生可见教师点击“发布作业”CLOSED已关闭禁止提交教师手动关闭或deadline到期自动触发REVIEWED已点评学生可见反馈教师提交点评后DELETED已删除软删除管理员或教师删除关键逻辑在HomeworkService.publishHomework()public void publishHomework(Homework homework) { homework.setStatus(PUBLISHED); homework.setPublishTime(new Date()); // 记录发布时间 homeworkMapper.updateById(homework); // 更新状态 // 发送通知此处可集成邮件或站内信当前为空实现 notificationService.sendToClass(homework.getClassId(), 新作业已发布 homework.getTitle()); }notificationService.sendToClass()是预留扩展点实际代码中为// TODO: implement notification但表结构已包含sys_notification表字段有receiver_typeCLASS/STUDENT/TEACHER、content、statusSENT/FAILED说明作者考虑了通知链路。3.2 学生作业提交与多文件上传的健壮处理学生提交作业调用/api/student/submit接收homeworkId和fileMultipartFile。后端处理分三步校验作业有效性查询homework表确认status PUBLISHED且deadline now()文件存储不存数据库 BLOB而是保存到upload/目录路径格式为upload/homework/{homeworkId}/{studentId}/{uuid}.ext记录提交信息插入homework_submit表含homework_id、student_id、file_path、submit_time、status初始为SUBMITTED。核心代码// StudentService.java public Result submitHomework(Long homeworkId, MultipartFile file, Long studentId) { // 步骤1校验作业 Homework homework homeworkMapper.selectById(homeworkId); if (!PUBLISHED.equals(homework.getStatus()) || homework.getDeadline().before(new Date())) { return Result.fail(作业不可提交); } // 步骤2保存文件 String originalFilename file.getOriginalFilename(); String extension originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename UUID.randomUUID().toString() extension; String uploadPath upload/homework/ homeworkId / studentId /; File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); file.transferTo(new File(uploadPath newFilename)); // 步骤3写入提交记录 HomeworkSubmit submit new HomeworkSubmit(); submit.setHomeworkId(homeworkId); submit.setStudentId(studentId); submit.setFilePath(uploadPath newFilename); submit.setSubmitTime(new Date()); submit.setStatus(SUBMITTED); homeworkSubmitMapper.insert(submit); return Result.success(提交成功); }提示file.transferTo()可能抛IOException实际项目需用try-catch包裹并回滚事务但本毕设代码中未体现异常处理建议你在复现时补充Transactional(rollbackFor Exception.class)。3.3 教师点评作业与双向反馈闭环教师点评接口/api/teacher/review接收submitId和comment文本及score整数。后端逻辑先查homework_submit确认该提交存在且status SUBMITTED再查homework表获取class_id验证点评教师是否属于该班级防跨班点评更新homework_submit.status REVIEWED并写入comment和score同时更新homework_submit.review_time和review_teacher_id。// TeacherService.java public Result reviewHomework(Long submitId, String comment, Integer score, Long teacherId) { HomeworkSubmit submit homeworkSubmitMapper.selectById(submitId); if (submit null || !SUBMITTED.equals(submit.getStatus())) { return Result.fail(无法点评该作业); } // 验证教师权限查 homework 关联的 class_id再查 teacher_class 表 Homework homework homeworkMapper.selectById(submit.getHomeworkId()); boolean hasPermission teacherClassMapper.existsByTeacherAndClass(teacherId, homework.getClassId()); if (!hasPermission) { return Result.fail(无权限点评该班级作业); } submit.setComment(comment); submit.setScore(score); submit.setStatus(REVIEWED); submit.setReviewTime(new Date()); submit.setReviewTeacherId(teacherId); homeworkSubmitMapper.updateById(submit); return Result.success(点评成功); }学生端/api/student/my-submits接口返回时会连表查询homework_submit的comment和score字段实现“提交→等待→查看反馈”闭环。数据库索引已优化homework_submit表在homework_id、student_id、status上建了联合索引避免全表扫描。4. 前端 Vue 组件复用与状态管理从 .vue.bak 文件还原真实交互逻辑4.1 .vue.bak 文件的价值还原被覆盖的原始组件结构项目中update-password.vue.bak、IndexAsideStatic.vue.bak等文件并非无用备份而是关键组件的原始版本。例如update-password.vue.bak内容显示!-- update-password.vue.bak -- template div classupdate-pwd el-form :modelform :rulesrules refpwdForm el-form-item label原密码 propoldPassword el-input typepassword v-modelform.oldPassword/el-input /el-form-item el-form-item label新密码 propnewPassword el-input typepassword v-modelform.newPassword/el-input /el-form-item el-form-item label确认新密码 propconfirmPassword el-input typepassword v-modelform.confirmPassword/el-input /el-form-item el-button typeprimary clicksubmitForm提交/el-button /el-form /div /template script export default { data() { return { form: { oldPassword: , newPassword: , confirmPassword: }, rules: { oldPassword: [{ required: true, message: 请输入原密码, trigger: blur }], newPassword: [{ required: true, message: 请输入新密码, trigger: blur }], confirmPassword: [{ required: true, message: 请再次输入新密码, trigger: blur }, { validator: (rule, value, callback) { if (value ! this.form.newPassword) { callback(new Error(两次输入不一致)); } else { callback(); } }, trigger: blur }] } } }, methods: { submitForm() { this.$refs.pwdForm.validate(valid { if (valid) { // 调用 APIthis.$http.post(/api/user/update-password, this.form) } }) } } } /script这证明密码修改走独立 API/api/user/update-password非通用/api/user/update前端做了强校验两次输入一致性使用 Element UI 的el-form和el-input说明 UI 框架明确this.$http表明 Axios 被封装为$http实例拦截器已配置Authorization头。4.2 路由守卫与角色跳转逻辑router/index.js中定义了路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); } else if (token) { const role localStorage.getItem(role); // 登录时存入 if (to.meta.requiresAuth) { if (to.meta.roles !to.meta.roles.includes(role)) { next(/403); // 权限不足 } else { next(); } } else { next(); } } else { next(); } });各路由 meta 配置示例{ path: /teacher/homeworks, name: TeacherHomeworks, component: () import(/views/teacher/HomeworkList.vue), meta: { requiresAuth: true, roles: [TEACHER] } }, { path: /student/submits, name: StudentSubmits, component: () import(/views/student/MySubmits.vue), meta: { requiresAuth: true, roles: [STUDENT] } }这种设计确保学生访问/teacher/homeworks会被重定向到/403页面而非后端报 403——提升用户体验。4.3 班级管理模块的树形结构与级联选择教师管理班级使用el-cascader组件数据源来自/api/class/tree接口返回格式[ { value: 1, label: 计算机学院, children: [ { value: 101, label: 软件工程2021级, children: [ { value: 10101, label: 软件2101班 }, { value: 10102, label: 软件2102班 } ] } ] } ]后端ClassController.java的getTree()方法GetMapping(/tree) public Result getClassTree() { ListClassNode nodes classService.buildClassTree(); // 递归查询 return Result.success(nodes); }ClassNode类含value、label、children字段buildClassTree()通过classMapper.selectRoots()获取一级学院再循环调用classMapper.selectByParentId()构建树。这种设计支持无限层级比扁平化class_id parent_id更易前端渲染。5. 生产环境部署与常见坑点排查从 1-install.bat 到 MySQL 字符集陷阱5.1 Windows 批处理脚本解析3-build.bat、2-run.bat、1-install.bat 的真实作用项目根目录的批处理文件是部署线索1-install.bat执行mvn clean install -Dmaven.test.skiptrue跳过测试打包成target/assignment-system-0.0.1-SNAPSHOT.jar2-run.bat执行java -jar target/assignment-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod指定生产配置3-build.bat可能是误命名实际内容为mvn clean compile用于开发阶段快速编译。关键点application-prod.yml中数据库配置spring: datasource: url: jdbc:mysql://localhost:3306/assignment_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse username: root password: your_password注意serverTimezoneAsia/Shanghai必须显式指定否则 MySQL 8.0 会因时区不匹配报The server time zone value ... is unrecognized错误useSSLfalse是本地开发必需生产环境应启用 SSL。5.2 MySQL 字符集与排序规则导致的中文乱码排查若学生提交作业后文件名显示为?????.pdf大概率是 MySQL 字符集问题。检查步骤连接 MySQLmysql -u root -p查看数据库字符集SHOW CREATE DATABASE assignment_db;—— 应返回DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci查看表字符集SHOW CREATE TABLE homework_submit;——file_path字段应为VARCHAR(255) CHARSET utf8mb4若非 utf8mb4执行ALTER DATABASE assignment_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE homework_submit CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时确认my.cnf中[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci5.3 Spring Boot 启动失败的三大高频原因与日志定位法启动报错Failed to configure a DataSource原因1application.yml中spring.datasource.url末尾缺少?useSSLfalseMySQL 8.0 默认要求 SSL原因2pom.xml未引入mysql-connector-java依赖或版本与 MySQL 不匹配MySQL 8.0 需8.0.28原因3数据库名assignment_db不存在需手动创建CREATE DATABASE assignment_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。定位方法启动时加--debug参数或查看logs/spring.log搜索Caused by:行。例如Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure ... at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174)表明数据库连接失败优先检查url和username/password。5.4 前端静态资源加载失败的解决方案运行2-run.bat后访问http://localhost:8080显示空白页F12 查看 Network 标签页发现app.78c0d8ad.css404原因Spring Boot 默认静态资源路径为classpath:/static/或classpath:/public/但前端构建产物dist/目录未复制到src/main/resources/static/解决将dist/下所有文件含app.78c0d8ad.css、index.html拷贝至src/main/resources/static/验证启动后访问http://localhost:8080/app.78c0d8ad.css应返回 CSS 内容。若仍 404检查application.yml是否禁用了静态资源spring: web: resources: add-mappings: true # 必须为 true提示app.78c0d8ad.css中的哈希值78c0d8ad是 Webpack 构建时自动生成每次npm run build都会变化因此不能硬编码引用必须确保index.html中link href/app.78c0d8ad.css的路径与实际文件名一致——这正是.bak文件保留的意义当index.html被覆盖时可从备份中恢复正确引用关系。本文还有配套的精品资源点击获取