从去年开始,我一直在帮一所高职院校搭建智慧校园信息管理平台,Spring Boot+Vue这个组合从头写到尾,中间踩了不少坑,也攒了不少经验。这个项目看起来就是一个典型的“后台管理系统”,但真正做进去才发现,智慧校园这四个字意味着很多业务场景要去梳理,权限模型要设计得足够灵活,前后端的数据交互要做到既安全又高效。如果你正准备做类似的项目,或者正在纠结技术选型,这篇就把我的完整思路、关键实现、参数取舍和踩坑记录都摊开来讲。
先说结论:Spring Boot+Vue确实是这类平台最稳的搭配之一。Spring Boot负责把后端服务快速落地,MyBatis Plus处理持久层效率极高,Vue 3搭配Element Plus做后台界面非常顺手,再加上JWT做无状态认证和RBAC权限模型,整个平台的骨架清晰、扩展性也好。这套方案不仅适合毕业设计和课程项目,放到真实的校园场景里也完全扛得住。
1. 项目整体设计与需求拆解
1.1 智慧校园到底在管什么
智慧校园信息管理平台,核心不是“做一个系统”,而是把学校里面散落的业务流程统一收口。我做的这个项目,需求方最初列了十几个模块,稍微梳理一下,主要就四大块:学生管理、教务管理、校园生活服务和系统权限。学生管理包括基本信息、学籍异动、奖惩记录;教务管理包括课程安排、选课结果、成绩录入与查询;校园生活服务包括场地预约、设备报修、公告通知;系统权限则负责用户、角色、菜单和数据范围的控制。
如果这些东西全部揉在一个单体内,开发速度倒是快,但后期维护会很痛苦。所以我在设计阶段就把模块边界划清楚,后端按照业务域拆包,前端按照路由懒加载拆分页面。这样做的直接好处是,每个人负责一个模块的时候互不干扰,测试和上线也是按模块验收。
场景上还有一个容易被忽略的点:使用者分三类,管理员、教师、学生,他们对系统的诉求完全不同。学生要的是查成绩、选课、约场地够方便;教师关心的是成绩录入、课表查询、报修审批是否顺手;管理员在意的是全局数据看得清、权限分得明、日志留得住。所以设计一开始就要以角色视角去讲功能,而不是把菜单平铺出来让用户自己找。
1.2 技术选型:为什么是Spring Boot和Vue
后端选Spring Boot,主要看中的是生态成熟和上手效率。Java本身在大学教学里覆盖率很高,Spring Boot又解决了传统Spring配置繁琐的问题,内嵌Tomcat让部署变成“一个jar跑起来”。在整个项目里,我用的是Spring Boot 2.7.x,搭配MyBatis Plus 3.5.x,理由很简单:MyBatis Plus的通用Mapper能让我省去大量CRUD的重复代码,分页插件、条件构造器都是现成的,生成代码的时候也能直接对接。
前端这块,我选了Vue 3加Element Plus加Vite。Vue 3的组合式API写业务逻辑比选项式API更利索,逻辑关注点可以放在一起;Element Plus是现阶段Vue 3生态里最成熟的中后台组件库。Vite做开发服务器那叫一个快,热更新基本秒开。这套组合还有一个隐性优点:找社区资料容易,遇到问题搜一下就有答案,对团队新人比较友好。
我之前也对比过若依这样的快速开发框架,最好别直接用了就跑业务,因为框架帮你做的决定太多,数据权限、代码生成、定时任务这些都是框架自己的约定,一旦需求超出它的设计范围,定制成本会明显上升。从零搭一个轻量骨架,反而能让你对每一行代码都心里有数。
1.3 功能模块的前后端映射
需求层面梳理好以后,就要转成前后端各自的任务清单。直播式的说法是:后端提供API,前端提供页面,但真正做的时候,两者之间需要一份严格的接口文档来约束。
我习惯用RESTful风格设计接口,资源名用名词复数,动作交给HTTP方法。比如POST /api/students表示新增学生,PUT /api/students/{id}表示更新,DELETE /api/students/{id}表示删除。响应结构统一包装,前端拿到永远都是{ code, message, data }这个格式,这样处理异常和加载状态就非常统一。
| 模块 | 后端核心接口 | 前端主要页面 | 关键交互 |
|---|---|---|---|
| 认证模块 | POST /api/auth/login,GET /api/auth/info | 登录页、个人中心 | JWT令牌刷新、路由守卫 |
| 学生管理 | GET/POST/PUT/DELETE /api/students | 学生列表、学籍详情 | 批量导入、分页搜索 |
| 教务管理 | GET /api/courses,POST /api/scores | 课程管理、成绩录入 | 教师录入后学生只读 |
| 场地预约 | POST /api/venues/reserve,GET /api/venues/available | 场地查看、预约申请 | 冲突检测、审批流 |
| 数据统计 | GET /api/dashboard/overview | 首页看板 | ECharts图表展示 |
这份表格看起来简单,实际上就是整个项目的“施工图”。后端开发照着接口清单落Controller,前端照着页面清单写视图和API封装,两边并行不悖。
2. 后端核心架构与Spring Boot实现
2.1 后端工程的分层设计
后端我没有刻意追求DDD那一套,而是用经典的四层结构:Controller层接收请求、Service层写业务逻辑、Mapper层访问数据库、实体类对应表结构。另外加了一个公共模块,放统一返回结果、异常处理、JWT工具、全局配置。这种分层的核心逻辑是:每一层只做一件事,上层不直接碰数据库,下层不处理HTTP细节。
我在pom.xml里把依赖控制得很克制。spring-boot-starter-web提供MVC能力,spring-boot-starter-security负责认证授权,mybatis-plus-boot-starter做持久层,jjwt生成和解析JWT,hutool处理常见的工具类操作,easyexcel做学生数据的批量导入导出。依赖不是越多越好,每多一个依赖就多一份版本冲突和安全隐患。
配置文件的组织也有讲究。application.yml里放公共配置,application-dev.yml和application-prod.yml分别对应本地开发和服务器部署。数据库连接、Redis地址、日志级别都通过spring.profiles.active切换。这里有个值得说的细节:密码在配置里绝不能明文写死,要放到环境变量里读取,比如${DB_PASSWORD}。
2.2 用户认证与JWT令牌机制
智慧校园平台的用户量不算大,但角色多、权限细,认证和授权就必须做扎实。登录流程是典型的JWT模式:用户提交账号密码,后端校验通过后生成一个token返回,前端把token存到localStorage,每次请求通过Authorization: Bearer <token>带上,后端用过滤器校验。
生成token的时候,我在JWT的payload里放了三样东西:用户ID、用户名、角色标识。注意不要放密码和手机号这类敏感信息,JWT默认只是Base64编码,不是加密,谁拿到都能解出来。过期时间我设的是2小时,刷新token设7天,这样既保证了安全性又不会让用户频繁重新登录。
2.3 权限模型:RBAC落地与数据隔离
权限设计这块是整个项目最核心的部分,也是很多同类项目最容易糊弄过去的地方。我用的经典RBAC模型,五张表:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户归属角色,角色绑定菜单,菜单对应前后端路由和按钮权限。
菜单表里有个字段叫perms,存的是权限标识字符串,比如system:student:list。后端在接口上通过@PreAuthorize("hasAuthority('system:student:list')")做控制,前端路由守卫里也判断meta中的角色是否匹配。这样前后端双重校验,即使有人绕过前端直接调接口,后端也不会放行。
数据权限这块要特别提一下。智慧校园里,辅导员只能看到自己带的学生,系部管理员只能看到本系的数据,校级管理员才能看全校。单纯靠RBAC解决不了这种行级数据隔离。我的做法是在业务查询里注入数据范围条件:通过AOP拦截Service方法,解析当前登录用户的角色和数据归属字段,自动拼接查询条件。这个方法写起来有点绕,但效果非常明显,比在每一个查询里手动加判断要规范和稳妥得多。
2.4 后端核心代码示例
登录接口的代码基本长这样,可以作个参考:
@PostMapping("/login") public Result<LoginVO> login(@RequestBody @Valid LoginDTO dto) { // 1. 查询用户 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getUsername, dto.getUsername()); SysUser user = userMapper.selectOne(wrapper); // 2. 校验密码(BCrypt加密比对) if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { throw new BizException("账号或密码错误"); } // 3. 查询角色和权限 List<String> roles = userService.getRolesByUserId(user.getId()); List<String> perms = userService.getPermsByUserId(user.getId()); // 4. 生成JWT String token = JwtUtil.createToken(user.getId(), user.getUsername(), roles); return Result.success(new LoginVO(token, user.getUsername(), roles, perms)); }整个接口只做四件事:查人、验密、拿权限、发token。一个很容易犯的错是把业务校验逻辑全堆在Controller里,这样看起来“很快”,但后续单元测试和扩展就麻烦了。我的习惯是Controller只负责参数接收和结果封装,所有判断和业务流转都放Service层。
3. 前端Vue设计与核心交互实现
3.1 Vue 3工程搭建与目录结构
前端我使用Vite创建项目,命令很简单:npm create vite@latest campus-ui -- --template vue。装好以后再把Element Plus、Vue Router、Pinia、Axios、ECharts、Sass这些依赖补上。目录结构上,src/api放接口定义,src/router放路由配置,src/stores放Pinia状态,src/views按业务模块放页面,src/components放公共组件,src/utils放请求封装和工具函数。
一个容易被忽略的点是别把API地址写死在前端代码里。我用的是环境变量:.env.development里写VITE_API_BASE_URL=/api,并配Vite代理把请求转发到后端localhost:8080;.env.production里写正式服务器地址。这样开发环境和生产环境切换就只改一个文件。
3.2 路由守卫与动态菜单权限
前端权限控制的核心是“根据用户的角色动态生成左侧菜单”。登录成功后,后端会把该用户的路由信息返回给前端,前端把组件映射成真正的路由动态添加。
我给路由配置加了meta字段来标记公开和需要权限的页面:
{ path: '/students', name: 'StudentManage', component: () => import('@/views/student/index.vue'), meta: { title: '学生管理', roles: ['admin', 'teacher'], icon: 'User' } }路由守卫里做三件事:判断有没有token,没有就踢回登录页;有token就判断store里有没有用户信息,没有就拉取用户信息;拉取以后判断当前路由的roles字段是否包含当前用户的角色,不包含就跳到403页面。这样每一个菜单项背后的路由都只对指定角色开放。
这里有个我觉得特别重要的细节:动态路由如果只是在前端根据角色hardcode一份菜单列表,那么角色一旦新增,就需要改代码重新发布。我的做法是后端把菜单表和角色关联做成可配置的,管理员在系统里给新角色勾选菜单,前端刷新后动态路由自动更新,这才算真正把权限做成“数据驱动”,而不是“代码驱动”。
3.3 请求封装与Axios拦截器
前端所有请求都走一个封装好的Axios实例。我在拦截器里统一做了几件事:
- 请求拦截器里从localStorage拿token放到Headers的Authorization字段。
- 响应拦截器里判断HTTP状态码,401就清空登录状态并跳转登录页。
- 后端返回的
code如果非0,统一弹Message提示并reject。
这样做最大的好处是业务页面永远不需要关心token怎么带、错误怎么提示,只需要拿到数据渲染即可。代码里我一般取名为request.js,导出后每个API模块写起来非常清爽:
export function getStudentList(params) { return request({ url: '/students', method: 'get', params }) }3.4 核心页面开发
以学生管理页面为例,布局是三段式:顶部是搜索栏,中间是操作按钮,下面是表格。Element Plus的el-table支持自定义列模板,我一般会把操作列固定在最右侧,列里放编辑和删除按钮,删除前必须弹ElMessageBox.confirm二次确认,这个交互不能省,学生数据删错了恢复成本太高。
表单部分我用el-dialog嵌套el-form,校验规则用rules配置,比如学号必填、手机号格式校验、邮箱格式校验。这里有一个容易踩的坑:编辑弹窗打开时,el-form的模型对象必须深拷贝一份,否则会直接改了表格行的数据,点取消也会生效,实际体验很糟糕。
分页这块用的是el-pagination,每页10条,共四种布局:总览、上一页下一页、页码、跳转器。搜索字段我用关键字模糊匹配,学生姓名和学号是高频搜索项,所以在后端对应加了两列索引,否则数据量上来之后搜索很吃力。
3.5 数据可视化看板
管理员登录后看到的是数据统计首页,我用ECharts做了三个核心图表:在校生人数按院系分布柱状图、每学期选课人数趋势折线图、场地预约类型占比饼图。这个首页不仅是“好看”,更是整个平台的指挥中心。
我按照Vue的生命周期在onMounted里调用统计接口,拿到数据后setOption更新图表。需要注意的一个坑是:图表容器必须有明确的高度,否则ECharts默认宽度撑满但高度为0,图表就根本不显示。最好用nextTick确保DOM渲染完成后初始化图表,窗口大小变化时记得调用resize()方法。
4. 数据库设计与关键表结构
4.1 核心实体关系梳理
数据库设计决定了平台能不能扛住复杂的业务查询。我按业务域划分了核心表:sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu这五张是权限底座的;stu_student、stu_class、stu_department是学生信息的;te_course、te_score、te_schedule是教务模块的;life_venue、life_reservation、life_repair是校园生活服务的。
每个模块的表我都使用统一的命名规则:业务前缀_实体名,比如stu_student、te_course。统一命名的好处是后期写SQL和做代码生成的时候可以靠前缀快速判断表的归属模块,MyBatis Plus的代码生成器也能通过表前缀自动配置。
表结构里我强制要求带这几个公共字段:create_time、update_time、deleted、create_by、update_by。时间字段做排序和审计,deleted做逻辑删除。逻辑删除一定要配合MyBatis Plus的@TableLogic注解,并且设置全局逻辑删除配置,否则一旦做关联查询,很容易把已删除的数据也查出来,导致统计数字不准确。
4.2 学生表与课程表的表结构示例
学生表核心字段如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,雪花ID |
| student_no | varchar(20) | 学号,唯一索引 |
| name | varchar(30) | 姓名 |
| gender | tinyint | 性别,0未知 1男 2女 |
| department_id | bigint | 院系ID |
| class_id | bigint | 班级ID |
| phone | varchar(20) | 手机号 |
| varchar(50) | 邮箱 | |
| status | tinyint | 1在读 2休学 3毕业 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| deleted | tinyint | 逻辑删除标志 |
这张表的索引设计我花了一些心思。除了主键外,我为student_no建了唯一索引,为department_id和class_id各建了普通索引。这样按照院系统计学生、按照班级筛选学生,走索引效率都很高。status字段区分度相对较低,单独建索引意义不大,和department_id联合使用的时候可以建联合索引。
课程表需要把学分、学时、上课时间这些信息都留住。特别要注意上课时间这个字段,不能简单地存成一个字符串“周一3-4节”,因为排课冲突检测需要结构化解析。我的方案是用week_day、start_section、end_section三个字段分开存,查询和冲突碰撞都方便。
4.3 事务与并发控制
在成绩录入、场地预约这类场景里,事务和并发控制是绕不开的。成绩录入如果教师一次性导入几百条,必须保证要么全成功要么全回滚,我在Service方法上加@Transactional注解。
场地预约的场景稍微复杂一点。多个学生同时预约同一个场地,并发情况下可能出现“双提交”的问题。我使用了数据库行级锁的思路:预约前先SELECT ... FOR UPDATE锁定场地记录,检查是否已满再插入预约记录。虽然在学校这个量级下并发冲突概率不高,但锁上这一层有备无患。
数据导入这块我用EasyExcel做了批量导入。模板里预先定义好学号、姓名、性别、班级这些列,导入的时候逐行校验格式,校验不通过的行记录错误原因,全部校验完以后再统一落库。这里不建议边校验边插入,否则一条脏数据会让整个批次都处于一种“半成功半失败”的状态,很难给用户一个清晰的反馈。
5. 智慧校园特色模块的细节实现
5.1 场地预约模块:冲突检测算法
这个模块算是整个平台里最“智慧”的一个点。学生预约场地时,需要选择场地类型、日期、时间段,后端要做的是判断该时间段是否已经有人占用。
冲突检测的逻辑并不复杂:查出该场地在同一日期下的所有已预约记录,然后逐条判断“新时间段是否与已有时间段重叠”。重叠的判断条件是两个区间有交集,也就是newStart <= existingEnd && newEnd >= existingStart。这个判断条件面试经常出,写业务的时候很多人反而容易写反。
更好的方案是在SQL层面直接做冲突判断,查询该场地在目标时间段是否已有有效预约记录,存在就不允许再约。这样把判断下沉到数据库,避免把大量记录拉到内存里再做循环比对。同时给venue_id、reserve_date、start_time这三个字段建联合索引,查询效率非常稳定。
审批流程上我做了两级状态:学生提交预约后状态是待审批,管理员确认后变成已通过,实际使用完以后由现场人员确认变成已完成。如果超过预约开始时间一小时还没有确认,系统自动置为超时未使用,释放场地资源。状态机看起来简单,但把整个生命周期的边界界定清楚了,后续短信通知、统计报表都能顺着这个状态流转做。
5.2 教务模块:成绩录入与发布策略
成绩模块有一个业务痛点:教师录入成绩时不想让成绩立刻对学生可见,需要确认无误后才“发布”。所以成绩表里我加了一个publish_status字段,初始是草稿,教师点发布后变成已发布。学生端查询成绩的接口只返回已发布的数据,草稿数据学生永远看不到。
同时成绩录入的时候要做分数范围校验,0到100之外直接拦截。如果课程是等级制(优秀、良好、中等、及格、不及格),后端需要支持两种考核方式的转换。我的做法是成绩表里同时存数值分数和等级,等级由数值分数自动映射,这样统计平均分和是否及格就直接用数值分数,打印成绩单时用等级分数。
5.3 设备报修与流程追踪
设备报修模块虽然业务逻辑不复杂,但状态流转很考验细节。学生提交报修单,填写设备位置、故障描述、联系方式,系统生成报修单号,状态为待受理。维修人员接单后变成处理中,维修完成以后填维修结果并上传照片,状态变成已完成。报修单全程可以被学生追踪状态。
我在这个模块里用了消息通知机制。状态变化时,通过Spring的事件发布机制异步发送站内信。为什么用事件发布而不是直接在业务代码里调用消息服务?因为报修状态流转可能涉及多个节点,把消息通知和核心业务解耦以后,即使通知服务出了问题,也不影响报修单本身的保存。其实用Spring的ApplicationEventPublisher就够了,没必要为了这个量级引入消息队列。
6. 部署上线与性能优化
6.1 环境准备与项目构建
整个平台我用Docker Compose做部署。后端打jar包,前端构建成静态文件后由Nginx托管,再单独起一个MySQL容器和Redis容器。部署结构的好处是环境和版本都能固化,换一台服务器不用重复装环境。
后端打包命令是mvn clean package -DskipTests,得到的jar大约80MB左右。Dockerfile里我用的是openjdk:8-jre-alpine基础镜像,启动命令是java -Xms512m -Xmx512m -jar。学校项目这个量级,512M堆内存完全够用。
前端构建是npm run build,产物会生成在dist目录。Nginx配置里要注意两点:一是root指向dist文件夹并按try_files $uri $uri/ /index.html转给前端路由,因为Vue是history模式,如果不这样配置,刷新子页面就会404;二是接口代理到后端服务的upstream。
6.2 性能优化三板斧
我实际做完后梳理了三类性能优化手段。
第一是数据库层面。所有高频查询都要explain看执行计划,确认走索引而不是全表扫描。最典型的例子是登录时按用户名查询用户,用户名建立了唯一索引,查询效率是毫秒级的。批量插入学生数据时,用MyBatis Plus的批量插入而不是for循环单条插入,性能差别明显。
第二是Redis缓存。菜单权限这种变化低频的数据,每次请求都查数据库完全没有必要。登录后把用户的权限标识列表以login:perms:{userId}为key缓存到Redis,设置30分钟过期。后续请求先查缓存,缓存逻辑一定要处理穿透问题。我的做法是缓存空对象并设置短过期时间,避免恶意请求绕开权限接口反复打数据库。
第三是前端静态资源的按需加载。路由懒加载已经把每个页面的JS拆开了,Vite构建时又做了代码分割,chunk和公共依赖分开。Element Plus我使用了自动按需导入插件,效果是首屏JS体积从2.5MB降到了800KB左右,首屏加载时间显著改善。
6.3 常见问题与排查技巧实录
这个项目从开发到上线,我碰到过不少问题,挑几个有代表性的整理成速查表:
| 问题 | 现象 | 排查思路与解决 |
|---|---|---|
| CORS跨域 | 前端请求接口报跨域 | 开发环境用Vite代理,生产环境用Nginx反向代理,后端不轻易开全局CORS |
| 刷新404 | Vue刷新子页面白屏 | 检查Nginxtry_files配置,确保前端路由回退到index.html |
| JWT过期 | 用户操作弹登录过期 | 拦截器统一处理401,前端跳登录页并清空localStorage |
| 数据权限越权 | 教师看到非本班学生 | 检查AOP数据权限切面是否生效,确认查询条件拼接正确 |
| 上传文件失败 | 大文件上传超时 | Nginxclient_max_body_size和Springmultipart.max-file-size同时修改 |
| 中文乱码 | 导出的Excel中文乱码 | EasyExcel用UTF-8,检查模板列头编码,必要时设置Content-Type响应头 |
| 时区错乱 | 预约时间差8小时 | 数据库连接参数加serverTimezone=Asia/Shanghai,Jackson统一设置时区 |
| 缓存不一致 | 权限变更后仍然提示无权限 | 在角色菜单变更时删除相关用户的Redis缓存,不要等过期 |
排查的思路其实就一条:从前到后逐层定位。先看浏览器Network请求,再看Nginx日志,最后到后端日志和数据库SQL。大部分问题都是配置问题而不是代码问题,所以环境差异导致的“本地好了上线挂了”这种情况最多见。
6.4 安全管理不能省的配置
校园平台涉及大量师生个人信息,安全管理这部分必须写清楚。Spring Security里,我配置了密码使用BCrypt加密存储,这是业界公认的密码哈希方案,自带盐值,同一密码每次加密结果不同,有效对抗彩虹表攻击。
接口层面做了两类防护:一是登录接口加了简单的验证码校验,防止暴力破解和脚本批量登录;二是所有写操作都经过权限注解检查,未授权请求直接返回403。这里有个容易被忽视的点:验证码的校验要与登录重试策略配合,我设置了同一账号连续错误5次就锁定15分钟,防止撞库攻击。
XSS和SQL注入防护依赖项目里的两个习惯:前端Vue模板默认会转义用户输入;后端所有查询都用MyBatis Plus的条件构造器或#{}占位符,避免字符串拼接SQL。
7. 项目复盘与几点实操建议
7.1 踩过的最值钱的坑
如果只说一个我想提醒后来者的话题,那就是联调阶段一定要用真实数据量测试,不要用三条测试数据验证完就上线。我当初场地预约模块本地怎么测都正常,上线后第一天就被并发预约戳穿了冲突检测的漏洞,原因就是真实场景下两三个学生几乎同时提交,而我的业务代码里没有加数据库行锁。后来补上SELECT ... FOR UPDATE才真正解决。
还有一个关于代码生成器的建议。MyBatis Plus的代码生成器能根据表结构直接生成实体、Mapper、Service和Controller,开发速度快很多。但生成的Controller默认是简单CRUD,业务复杂的接口还是得手动写。我的经验是生成器只生成实体和Mapper,Service和Controller自己写,这样既保留效率,也不至于被生成的模板代码带偏。
7.2 后续功能可以怎么扩展
这套平台做完以后,我已经在规划二期。消息推送目前只有站内信,后续想接入企业微信或者邮件通知;数据分析目前只是静态报表,后续想引入简单的学生行为分析,比如通过图书借阅次数、一卡通消费记录给辅导员提供参考。这些扩展想做成插件形态,不影响到目前的业务。
选择Spring Boot和Vue还有一个很现实的考虑:招人、找人维护都不难。Java和Vue的开发者基数大,后续如果学校想要自己维护系统,不会遇到“技术栈太偏找不到人”的尴尬。我自己在选型的时候把这个因素纳入了考量,建议你选型时也考虑一下团队可持续性。
7.3 给新手的最后一句体会
我做这个项目的最大收获不是代码写得有多好,而是想明白了一件事:技术方案是手段,业务模型才是骨架。智慧校园信息管理平台表面上是一堆CRUD,真正有价值的地方在于把学校的业务流程抽象清楚,把权限边界和数据规则定义明白。Spring Boot和Vue帮你把开发效率拉满了,但决定项目上限的,永远是需求分析和架构设计。
如果你现在刚开始做这个类型的项目,先把用户的角色和典型场景梳理一遍,用自己的话说清楚“谁在什么情况下要做什么事”,然后对着这份流程去设计表结构和接口,开发起来会顺畅很多。希望这篇拆解能给你一点参考,哪怕只是帮你少走一个弯路,也值了。