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

发布时间:2026/10/22 4:22:37

uniapp+Java选课系统实战:含MySQL事务、RBAC权限与真机调试 简介本资源是一套面向高校计算机专业本科生的毕业设计/课程设计级移动端选课系统完整实现聚焦教育信息化场景解决传统选课流程中学生选退课不便、教师课程管理低效、教务数据分散等实际问题。资源包共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。日志表本身不参与业务查询纯作审计备份即使某天发现触发器逻辑有误删掉重建也不会影响线上功能。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/22 4:17:36

从零手写AI艺术生成器:扩散模型DDPM原理与实现

朋友问我用什么AI工具画画,我一开始支支吾吾答不上来。不是不会用,而是我就会按几个按钮,底下的原理一问三不知。后来我较上劲了:直接用 Python 和深度学习,从零写一个 AI 艺术生成器——不调现成的图像生成接口&#…

2026/10/22 4:17:36

Seedance实战指南:从参数调优到AI视频风格化重制全流程

1. 先搞懂Seedance的定位和设计思路Seedance这个词在圈子里最近讨论热度不低。我最初以为它只是个普通的视频生成接口封装,仔细扒完文档和源码之后发现,这玩意儿的思路跟常见的“一键生成视频”类工具完全不是一回事。它的核心卖点不是“生成”&#xff…

2026/10/22 4:17:36

CrewAI实战:从最小闭环到生产级虚拟员工团队

这两年我最大的体会是:单打独斗写脚本的日子越来越不够用了。不是写不出来,是活儿多到没时间写。跑数据、写周报、整理竞品、回邮件,每件事单独要一天。后来我试着把几件事塞进同一个自动化流程,结果 prompt 越长,模型…

2026/10/22 5:22:43

智慧药房赛项资源包解析:ROS导航与YOLOX检测工程实践

简介:这份资源是第二十五届中国机器人及人工智能大赛智慧药房组的完整参赛作品包,面向备战同类机器人竞赛的高校学生与指导教师,尤其适合需要参考赛题实现思路、搭建仿真与实机调试环境的中高级参赛者。压缩包共150个文件,约38.19…

2026/10/22 5:22:43

LSTM自编码器实现智能电表计量异常检测与Python复现

干计量运维的人都有过这种感觉:台区线损突然偏高,心里第一反应就是“又有一块表不准了”。可一个台区挂了三四十块户表,线损的锅到底扣在哪一块头上?靠经验猜不靠谱,一块块现场校验又太慢。深度学习能不能干这个活&…

2026/10/22 5:22:43

PHP项目安全HTTP头配置实战:从CSP到HSTS的完整加固指南

我们平时给PHP项目做安全审计,最容易看到的一个结果就是“缺少安全响应头”。你说功能、权限、参数过滤都做得不错,但安全头一排看下来全是空白——攻击者只要用浏览器直接访问,就知道这个站点有没有做基础加固。其实设置安全HTTP头是投入产出…

2026/10/22 5:22:43

OPNET MANET仿真实战:从模型解析到参数调优与避坑指南

简介:这是一份基于OPNET平台的MANET无线自组织网络仿真实例压缩包,面向网络仿真初学者、高校学生及研究MANET路由协议的技术人员。资源围绕AODV、DSR、TORA、OLSR、GRP等多种主流路由协议,构建了50节点、100节点、移动场景等不同实验模型&…

2026/10/22 5:22:43

为什么我不建议你冲动进入网络安全行业?攻防实战真相

直接开始正文。我入行网络安全到现在差不多也有十个年头了,带过团队,做过产品,也面试过几百个想进这行的年轻人。每次有人跟我说“我想转行学网络安全”或者“我对黑客技术特别感兴趣”的时候,我内心其实是挺复杂的。一方面觉得这…

2026/10/22 5:17:43

AI反爬防护引擎的检测机制与接入配置解析

现在做商业反爬防护的,AI引擎基本成了标配。以前写个脚本、挂个代理池就能轻松拿数据的日子早就不在了,主流防护产品普遍上了机器学习模型,请求还没到业务服务器,边缘节点就已经完成了风险打分。这篇文章就以一款典型的商业化AI防…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/22 0:01:49

电商智能运营体系从0到1:架构设计、落地步骤与避坑指南

前两年我们团队接了一个电商平台的运营中台项目,目标很直白:把过去靠运营拍脑袋做的选品、定价、发券、推活动这些事,逐步用数据算法和自动化流程代替。项目名字内部就叫“电商智能运营体系”。最近有不少朋友问我当时是怎么设计的、踩过哪些…

2026/10/22 0:01:49

若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

最近一段时间,我一直想把后台管理系统里那些机械重复的增删改查代码交给AI去写。框架用的是若依,按理说这类业务代码模式非常固定,应该是AI最擅长的事情。可真试下来却不是那么回事——你让AI生成一个用户管理模块,它给出的Contro…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑