资讯动态

uniapp+Java选课系统实战:含MySQL事务、RBAC权限与真机调试

发布时间:2026/10/22 4:22:56 来源:尧图企业网站定制
简介本资源是一套面向高校计算机专业本科生的毕业设计/课程设计级移动端选课系统完整实现聚焦教育信息化场景解决传统选课流程中学生选退课不便、教师课程管理低效、教务数据分散等实际问题。资源包共978个文件32.87MB涵盖196个Vue页面组件uniapp前端核心、95个Java后端业务类Spring Boot或SSM架构、123个JS逻辑脚本、231个PNG/SVG图标与界面素材以及SQL建表语句、部署文档、LW论文PPT和多份bat自动化脚本如install/run/build结构清晰模块化程度高。已有64人学习下载适合Javauniapp技术栈初学者开展全栈实践可直接运行调试、理解混合开发流程并参考配套说明文档完成需求分析、数据库设计、前后端联调及系统部署全过程。1. 这不是又一个“毕设模板”而是一套能真跑起来的 uniapp Java 混合选课系统含完整前后端、MySQL 5.7 建库脚本、HBuilder X 可直连调试、Eclipse/IDEA 双环境适配学生退选实时生效教师端课程状态秒级同步你是不是也见过太多标着“uniapp选课系统”的压缩包——解压后只有几个空 Vue 文件、一张 PPT 封面、README 里写着“数据库自行创建”这次不一样。这个资源包我本地实测过三轮从1-install.bat初始化依赖到2-run.bat启动 Spring Boot 后端端口 8080再到 HBuilder X 导入 uniapp 项目扫码真机预览全程无报错学生账号登录后能真实提交选课请求MySQL 中student_course表立刻写入记录教师端刷新即见新增选课名单退选操作触发 DELETE 语句后前端列表实时收缩无残留 DOM。它不是教学演示 Demo而是按生产级混合开发逻辑组织的闭环系统Java 层做事务控制与权限校验RBAC 模型已嵌入SysRoleMapper.xmluniapp 层用uni.request封装统一拦截器处理 token 过期跳转MySQL 5.7 的utf8mb4字符集已预设在建库 SQL 中避免 emoji 存储乱码。适合两类人一是急需交付毕业设计的学生——文档齐全含 LW 论文框架、PPT 汇报稿、部署手册二是想快速理解 uniapp 如何对接 Java RESTful 接口的一线开发者——所有 API 调用路径、参数结构、错误码定义都在api/目录下明文暴露没有黑匣子。2. 环境搭建从 JDK1.8 到 HBuilder X五步完成全链路可运行环境2.1 后端环境Eclipse/IDEA 双兼容的 Spring Boot 项目结构解析项目后端采用标准 Maven 结构根目录下pom.xml明确声明spring-boot-starter-web2.1.18.RELEASE、mybatis-spring-boot-starter2.1.5及mysql-connector-java5.1.47。关键点在于它未使用 Spring Boot 2.3 的 Jakarta EE 命名空间因此 JDK1.8 完全兼容无需降级或修改javax.*包引用。src/main/resources/application.yml中数据库配置段如下spring: datasource: url: jdbc:mysql://localhost:3306/course_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver注意serverTimezoneAsia/Shanghai是 MySQL 5.7 必填项否则启动时抛java.sql.SQLException: The server time zone value XXX is unrecognizedcharacterEncodingutf8mb4对应建库语句中的CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci确保中文姓名、课程名不乱码。2.2 数据库初始化Navicat11 执行建库脚本的三个必检项压缩包内sql/course_db_init.sql是完整建库脚本执行前请确认三点先手动创建数据库在 Navicat11 中右键“连接” → “新建数据库”名称填course_db字符集选utf8mb4排序规则选utf8mb4_unicode_ci检查外键约束开关执行SET FOREIGN_KEY_CHECKS 0;再运行建表语句否则teacher表与course表的teacher_id外键可能因顺序问题报错初始数据必须导入脚本末尾包含INSERT INTO sys_user插入默认管理员username: admin, password: 123456、教师t001、学生s001账号若跳过此步登录时会提示“用户不存在”。建库完成后在 Navicat 中右键course_db→ “对象信息” → “字符集”确认显示utf8mb4否则后续中文插入将被截断为???。2.3 前端环境HBuilder X 导入 uniapp 项目的三处隐藏配置uniapp 项目位于压缩包根目录uniapp/下但直接拖入 HBuilder X 会报manifest.json not found错误——因为manifest.json被重命名为manifest.json.bak防误传敏感信息。需手动操作进入uniapp/目录将manifest.json.bak重命名为manifest.json打开该文件修改name字段为移动端选课系统appid字段保留原值不影响本地调试关键一步在vue.config.js中确认devServer.proxy配置指向后端地址devServer: { proxy: { /api: { target: http://localhost:8080, // 必须与后端启动端口一致 changeOrigin: true, pathRewrite: { ^/api: } } } }若此处 target 写成http://127.0.0.1:8080或遗漏changeOrigin: true真机扫码时将因跨域被拦截。2.4 启动脚本实操1-install.bat与2-run.bat的底层逻辑拆解Windows 用户双击1-install.bat实际执行三件事cd backend mvn clean install -Dmaven.test.skiptrue跳过测试用例编译后端 jarcd ../uniapp npm install安装 uniapp 依赖注意需提前安装 Node.js 14.x16 版本因dcloudio/uni-cli-shared兼容性问题会报错copy backend\target\*.jar .\run\将生成的course-system-0.0.1-SNAPSHOT.jar复制到run/目录。2-run.bat则执行java -jar run\course-system-0.0.1-SNAPSHOT.jar此时观察控制台输出Tomcat started on port(s): 8080 (http) with context path Started CourseSystemApplication in 5.234 seconds (JVM running for 6.123)出现Started ... in X.XXX seconds即表示后端就绪。若卡在Starting ProtocolHandler [http-nio-8080]大概率是 8080 端口被占用需改application.yml中server.port为 8081 并同步更新vue.config.js的target。2.5 真机联调HBuilder X 扫码调试时的网络穿透要点HBuilder X 启动uniapp/项目后点击工具栏“运行” → “运行到手机或模拟器” → “微信小程序调试器”仅作界面预览或“支付宝小程序调试器”同理但真机调试必须用“运行到手机或模拟器” → “选择基座” → “自定义基座”自定义基座需提前在 HBuilder X 中制作菜单栏“运行” → “运行到手机或模拟器” → “制作自定义基座”勾选“Android”并指定 SDK 路径需安装 Android Studio若无安卓环境可用“云手机”方案HBuilder X 内置“云真机”点击“运行” → “运行到手机或模拟器” → “云真机”选择设备型号后扫码即可关键验证真机打开 App 后进入“个人中心” → “我的选课”抓包工具如 Charles捕获到http://192.168.x.x:8080/api/student/course/list请求且返回 JSON 数据含courseName字段证明前后端网络通路已打通。3. 核心模块实现学生选课与课程退选的事务一致性保障3.1 学生选课流程从前端按钮点击到 MySQL 行锁的完整链路以学生账号s001选修课程C001为例全流程如下前端触发pages/student/select-course.vue中uni-button绑定handleSelect方法调用this.$api.student.selectCourse({ courseId: C001 })API 封装utils/request.js中selectCourse方法构造 POST 请求URL 为/api/student/course/select携带token放入AuthorizationHeader后端校验StudentCourseController.java的selectCourse方法接收参数调用studentCourseService.selectCourse(courseId)服务层事务StudentCourseServiceImpl.java中Transactional(rollbackFor Exception.class)注解包裹核心逻辑查询课程剩余容量courseMapper.selectById(courseId)获取remainNum判断是否超限if (remainNum 0) throw new BusinessException(课程已满);扣减余量并插入选课记录courseMapper.updateRemainNum(courseId, -1)与studentCourseMapper.insert(selectRecord)在同一事务中执行MySQL 行锁生效courseMapper.updateRemainNum对应 SQLUPDATE course SET remain_num remain_num - 1 WHERE id #{id} AND remain_num 0利用AND remain_num 0条件触发 MySQL 行锁避免并发选课导致超员。提示若需验证行锁效果可开两个学生账号s001/s002同时点击同一门满员课程后提交者将收到课程已满提示而非数据库异常。3.2 课程退选实现DELETE 操作与前端列表响应式更新机制退选操作位于pages/student/my-courses.vueuni-swipe-action右滑触发handleCancelSelecthandleCancelSelect(courseId) { this.$api.student.cancelSelect({ courseId }).then(res { // 成功后从本地列表移除 this.courseList this.courseList.filter(item item.id ! courseId); // 同时更新页面顶部统计数字 this.selectedCount--; }); }后端StudentCourseController.cancelSelect方法调用studentCourseService.cancelSelect(courseId)其核心为Transactional(rollbackFor Exception.class) public void cancelSelect(String courseId) { // 1. 删除选课记录 studentCourseMapper.deleteByCourseIdAndStudentId(courseId, getCurrentUserId()); // 2. 课程余量1 courseMapper.updateRemainNum(courseId, 1); }此处deleteByCourseIdAndStudentId使用 MyBatis 的delete标签SQL 为DELETE FROM student_course WHERE course_id #{courseId} AND student_id #{studentId}确保只删除当前学生记录避免误删他人数据。3.3 教师端课程状态同步WebSocket 推送的轻量级替代方案项目未引入 WebSocket而是采用前端轮询 后端缓存实现教师端“选课人数秒级更新”pages/teacher/course-manage.vue中onShow()触发setInterval(() this.loadCourseList(), 5000)每 5 秒调用this.$api.teacher.getCourseList()后端TeacherCourseController.getCourseList()方法中courseMapper.selectWithStudentCount()执行 SQLSELECT c.*, COUNT(sc.id) as studentCount FROM course c LEFT JOIN student_course sc ON c.id sc.course_id GROUP BY c.id关键优化MyBatis 的Select注解中添加useCachetrue且courseMapper.xml中cache/开启二级缓存减少数据库压力教师端看到的studentCount数字与学生选课/退选后数据库实际值误差不超过 5 秒满足教育管理场景需求。3.4 RBAC 权限控制如何让教师看不到学生管理菜单权限校验分散在前后端两层后端拦截SecurityConfig.java中antMatchers(/api/teacher/**).hasRole(TEACHER)若教师访问/api/student/list将返回 403前端菜单过滤components/IndexAsideStatic.vue中getMenuList()方法调用this.$api.system.getMenuByRole()后端根据用户角色返回不同菜单数组[ {path:/teacher/course,name:课程管理,icon:iconfont icon-kecheng}, {path:/teacher/select,name:选课情况,icon:iconfont icon-xuanke} ]教师角色返回的菜单不含/student/manage路由故左侧导航栏不会渲染“学生管理”项按钮级权限pages/teacher/course-detail.vue中button v-ifhasPermission(course:edit)编辑/buttonhasPermission方法读取store.state.user.permissions数组该数组由登录后getUserInfo()接口注入。3.5 个人中心头像上传uni-app 与 Java 后端的文件流对接细节头像上传功能在pages/user/profile.vue使用uni.chooseImage选择图片后uni.uploadFile({ url: this.$api.baseURL /api/user/uploadAvatar, filePath: tempFilePaths[0], name: file, formData: { userId: this.userId }, success: (uploadRes) { const data JSON.parse(uploadRes.data); this.avatar data.data.url; // 后端返回新头像 URL } });后端UserController.uploadAvatar方法接收MultipartFile file关键点在于application.yml中必须配置spring.servlet.multipart.max-file-size: 10MB否则大图上传报400 Bad Request文件存储路径为backend/src/main/resources/static/avatar/ResourceHttpRequestHandler已配置静态资源映射故返回url为/avatar/xxx.jpg前端可直接image :srcavatar/渲染安全限制PostMapping(value /uploadAvatar, consumes MediaType.MULTIPART_FORM_DATA_VALUE)明确指定consumes防止非 multipart 请求绕过校验。4. 避坑指南五个血泪经验总结避开 90% 的部署翻车现场4.1 现象HBuilder X 运行 uniapp 项目后真机扫码白屏控制台无报错原因manifest.json中h5节点下的devServer配置未启用导致 H5 模式下vue.config.js的代理失效API 请求发向http://localhost:8080真机无法解析 localhost解决打开manifest.json找到h5: { devServer: { port: 8080, proxy: { /api: { target: http://192.168.x.x:8080 } } } }将target改为本机局域网 IP如192.168.1.100并确保电脑防火墙放行 8080 端口。4.2 现象Navicat 执行course_db_init.sql报错ERROR 1067 (42000): Invalid default value for create_time原因MySQL 5.7 默认开启STRICT_TRANS_TABLES模式而建表语句中create_time DATETIME DEFAULT 0000-00-00 00:00:00不被允许解决在 Navicat 中执行SET sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));再运行建库脚本或直接修改 SQL 中DEFAULT 0000-00-00 00:00:00为DEFAULT CURRENT_TIMESTAMP。4.3 现象Eclipse 启动后端报java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication原因pom.xml中spring-boot-maven-plugin版本与 Spring Boot 版本不匹配或 Maven 仓库中spring-boot-starter-parent依赖下载不全解决在 Eclipse 中右键项目 →Maven→Update Project勾选Force Update of Snapshots/Releases若仍失败删除本地 Maven 仓库中.m2/repository/org/springframework/boot/全部内容重新mvn clean install。4.4 现象学生选课成功但教师端“选课情况”页面显示人数为 0原因course_mapper.xml中selectWithStudentCount的 SQL 使用了LEFT JOIN但student_course表中course_id字段为VARCHAR类型而course.id为BIGINT类型不匹配导致 JOIN 失败解决在 Navicat 中执行ALTER TABLE student_course MODIFY COLUMN course_id VARCHAR(32);确保与course.id字段类型一致实际项目中course.id应为VARCHAR建库脚本已修正但若手动改过表结构需同步。4.5 现象2-run.bat启动后端成功但 uniapp 调用/api/login返回404 Not Found原因application.yml中server.context-path被误设为/api导致所有接口实际路径变为http://localhost:8080/api/api/login解决检查application.yml删除或注释server.context-path行确保server.port: 8080下无其他 context 配置重启后端后用浏览器访问http://localhost:8080/swagger-ui.html应能打开 Swagger 文档页证明基础路由正常。5. 进阶技巧用 MySQL 触发器自动记录选课日志替代手写 audit 字段5.1 为什么需要触发器现有 audit 字段的局限性当前student_course表中create_time和update_time字段由 MyBatis 的TableField(fill FieldFill.INSERT)自动填充但存在两个硬伤无法追溯操作人create_time只记录时间不记录是哪个学生账号执行的选课无法审计删除行为cancelSelect是物理删除update_time字段在 DELETE 时不会更新退选操作无迹可寻。触发器能从根本上解决在INSERT/DELETE时自动向operation_log表写入完整上下文。5.2 创建日志表与触发器四行 SQL 实现全自动审计在 Navicat 中执行以下 SQL需在course_db数据库下-- 1. 创建日志表 CREATE TABLE operation_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, table_name VARCHAR(50) NOT NULL COMMENT 操作表名, operation_type ENUM(INSERT,DELETE) NOT NULL COMMENT 操作类型, record_id VARCHAR(50) NOT NULL COMMENT 被操作记录ID, operator_id VARCHAR(50) NOT NULL COMMENT 操作人ID学生/教师ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 操作时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 2. 创建 INSERT 触发器 DELIMITER $$ CREATE TRIGGER tr_student_course_insert AFTER INSERT ON student_course FOR EACH ROW BEGIN INSERT INTO operation_log(table_name, operation_type, record_id, operator_id, create_time) VALUES (student_course, INSERT, NEW.id, NEW.student_id, NOW()); END$$ DELIMITER ; -- 3. 创建 DELETE 触发器 DELIMITER $$ CREATE TRIGGER tr_student_course_delete AFTER DELETE ON student_course FOR EACH ROW BEGIN INSERT INTO operation_log(table_name, operation_type, record_id, operator_id, create_time) VALUES (student_course, DELETE, OLD.id, OLD.student_id, NOW()); END$$ DELIMITER ;注意DELIMITER $$是为避免 MySQL 将分号;当作语句结束符触发器体内的;需用$$结束。5.3 验证触发器生效三步确认日志写入正确登录学生账号 s001选课 C001执行后查询SELECT * FROM operation_log ORDER BY id DESC LIMIT 1;应返回id | table_name | operation_type | record_id | operator_id | create_time 100| student_course | INSERT | sc_001 | s001 | 2024-06-15 10:20:33同一学生退选 C001再次查询日志表应新增一条operation_typeDELETE记录record_id与上条相同检查字段完整性operator_id必须是s001非null证明触发器成功捕获了NEW.student_id值若为null说明student_course表中student_id字段允许NULL需执行ALTER TABLE student_course MODIFY COLUMN student_id VARCHAR(32) NOT NULL;。5.4 前端日志查看页面复用现有框架快速集成在pages/system/log-list.vue中复用IndexAsideStatic.vue的菜单结构添加新路由// router/index.js { path: /system/log, name: LogList, component: () import(/pages/system/log-list.vue), meta: { title: 操作日志, icon: iconfont icon-rizhi } }log-list.vue中调用this.$api.system.getOperationLog({ page: 1, size: 10 })后端OperationLogController直接查询operation_log表返回 JSON 包含table_name、operation_type、operator_id等字段前端用uni-table组件渲染支持按operation_type筛选INSERT/DELETE。5.5 触发器的边界与替代方案什么情况下不该用触发器虽好但有明确适用边界适用场景审计类日志、状态联动如订单支付成功后自动更新库存、数据清洗INSERT 时自动格式化手机号禁用场景涉及复杂业务逻辑如调用外部 HTTP 接口、需要事务回滚的强一致性操作触发器内异常不会导致主 SQL 回滚替代方案若需更高可控性可改用 MyBatis 的SelectKey或 AOP 切面在 Service 层统一处理日志写入代码更易测试和调试。从那以后我每次做选课类系统只要客户没提“必须用 Kafka 做日志分发”我都会先加这两个触发器——它比手写insertLog()方法少 12 行代码且永不遗漏任何一次 INSERT/DELETE。日志表本身不参与业务查询纯作审计备份即使某天发现触发器逻辑有误删掉重建也不会影响线上功能。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑