我今年给某高校的大创项目管理工作做了一次彻底改造,把原来一堆Excel表格和纸质申报书换成了一套前后端分离的大学生创新创业训练项目管理系统,技术栈是SpringBoot+Vue+MyBatis+MySQL。整套系统做下来,从零搭建到部署上线用了大概一个多月,期间踩了不少坑,也沉淀了一些特别值得说的设计思路。如果你正准备做类似的课设、毕设,或者学习前后端分离架构下的项目开发,这篇文章应该能帮你省下不少弯路。
先说清楚这套系统解决的核心问题:大创项目从申报、审批、中期检查、结题到优秀项目推荐,涉及学生、指导老师、学院管理员、校级管理员四类角色,流程长、状态多、文件杂。如果用传统方式管理,光是项目状态同步就能把人逼疯。这套系统把整个流程搬到线上,所有角色在同一个平台上协作,项目状态实时流转,文件统一存储,权限按角色划分,彻底告别表格传来传去。
内容上我不会只丢一堆代码片段,而是把架构设计逻辑、核心模块的数据库建模思路、关键代码的落地方式、以及部署过程中的实际排错过程都串起来讲,确保你拿到源码能看懂、能改、能部署。
1. 从"纸质填表"到"全流程线上化":这套系统到底解决了什么
很多学生和开发者看管理系统,第一反应是"这不就是增删改查吗",但真正接触过大创项目管理业务之后会发现,它远不止 CRUD 那么简单。业务逻辑的复杂度不在接口数量,而在项目状态流转和角色权限划分。
1.1 项目管理场景的真实痛点
大学生创新创业训练计划一般分为创新训练、创业训练、创业实践三类项目,每个项目要经历申报、学院初审、校级评审、立项、中期检查、结题验收,甚至还有推优环节。在不同的时间节点,不同角色的操作权限完全不一样。
比如申报阶段,学生需要填写项目申报书,上传附件,选择指导老师;指导老师需要确认是否同意指导;学院管理员要组织评审、填报推荐意见;校级管理员要分配评审专家、汇总立项名单、发布立项通知。到了中期检查阶段,学生要提交进展报告,指导老师要填写指导意见,学院要审核是否通过。结题阶段就更复杂,涉及成果材料提交、结题报告、经费使用情况等等。
在没做系统之前,这些环节全靠人工传递。我调研的那所高校,学院教务老师每个月要处理几百份Excel汇总表,不同老师发来的文件命名格式五花八门,经常出现"最后版本""最最终版""改完不要再改版"这种命名。更麻烦的是状态混乱——一个项目到底走到哪一步了,往往需要老师手动翻邮件、翻聊天记录才能确认。
这套管理系统最大的价值就是把"流程"显性化了:每个项目的状态都是系统里的一个字段,所有操作留痕,所有文件统一管理,再也不会出现某个项目中途"消失在流程里"的情况。
1.2 四类角色与权限边界的设定
系统角色划分是大创项目管理的关键设计决策之一。我最终采用的是学生、指导老师、学院管理员、校级管理员四个角色,没有把系统管理员和校级管理员合并,原因是校级管理员关注业务流程本身,而系统管理员关注基础数据维护,两者操作面和关注点差别很大。
我用的权限模型是RBAC改良版,主体是Spring Security + JWT,角色之间用固定的角色编码区分。后端通过注解控制接口访问级别,前端通过路由守卫控制页面可见性,两层控制缺一不可。
这里有一个容易被初学者忽略的点:前端隐藏菜单不等于安全,真正的权限控制必须落到后端接口。所以前端路由守卫只是体验优化,后端每个接口都要校验角色和具体的数据归属权。比如学生只能查看自己名下的项目,指导老师只能查看自己是第一指导老师或第二指导老师的项目,学院管理员只能操作本院项目。
1.3 技术选型是怎么敲定的
这套系统最终选了SpringBoot作为后端框架、Vue作为前端框架、MyBatis作为ORM、MySQL作为数据库。选这套组合不是跟风,而是综合考虑了团队熟悉度、生态成熟度和部署便捷性。
SpringBoot胜在开箱即用,内嵌Tomcat,一个jar包就能跑,不用单独配置服务器环境。对毕设、课设这种场景来说,部署成本低是特别重要的优势,因为演示环境往往只有一台Windows笔记本。MyBatis比JPA更灵活,写复杂SQL没有心理负担,比如项目列表、统计报表这类多表关联查询,MyBatis的XML映射写起来非常直观。MySQL更不用说了,免费、稳定、资料多,遇到问题一搜就有答案。
前端用Vue而不是React,一个重要原因是Vue对新手更友好,模板语法直观,生态内组件库成熟,Element UI就能满足后台管理场景的绝大多数需求,不需要自己造轮子。
2. 架构拆解:前后端分离这套组合是怎么分工协作的
既然标题叫"前后端分离"管理系统,那这部分值得展开聊聊。有些学生做的所谓"前后端分离"只是把Vue页面和后端启动在同一个端口下,本质还是耦合的。真正的分离应该是:前端和后端是两套独立应用,通过HTTP接口通信,前端负责渲染和交互,后端负责数据处理和业务逻辑。
2.1 为什么必须用前后端分离而不是单体模板
大创项目管理系统的用户场景是典型的"多端访问、多人并发、多角色操作",前后端分离在这种场景下的优势特别明显。开发层面,前端工程师和后端工程师可以并行工作,只要提前定义好接口文档;部署层面,前端静态资源可以扔到Nginx上,后端jar包跑在服务器上,互不影响;扩展层面,以后如果要出小程序端或App端,后端接口可以直接复用,不用重写业务逻辑。
我做的这套系统,前端和后端是完全独立的目录结构。后端是一个标准的SpringBoot Maven项目,前端是一个Vue CLI项目,开发环境下通过代理转发解决跨域问题,生产环境下用Nginx做反向代理和静态资源托管。前后端只通过JSON格式的接口数据通信,所有业务逻辑都在后端完成,前端不做任何数据持久化操作。
2.2 后端项目结构与分层逻辑
后端项目结构我用了经典的分层架构:Controller层负责接收请求和返回响应,Service层承载业务逻辑,Mapper层通过MyBatis操作数据库,再加上一个独立的dto包用来封装入参出参、一个entity包对应数据库表结构、一个common包放通用工具和统一返回结果。
这套结构看着常规,但有几个细节是踩坑后才加上去的。第一是统一返回体,我定义了一个Result类,所有接口返回结构都是{code, message, data},前端统一处理,不用每个接口单独判断。第二是全局异常处理器,用@RestControllerAdvice拦截所有异常,避免异常堆栈直接暴露给前端,同时让前端能拿到规范化的错误信息。第三是分页参数统一封装,所有列表查询都接受current和size两个参数,配合MyBatis的分页插件使用,不用每个查询手写limit。
2.3 前端结构与路由设计
前端项目采用Vue CLI创建,配合Vue Router和Vuex。页面结构按业务模块划分,登录页、系统首页、项目管理模块、项目申报模块、审核管理模块、系统管理模块,每个模块一个文件夹,内部包含列表页、详情页、表单页等子页面。
路由设计上最核心的是动态路由。后端在用户登录时返回该用户的所有权限信息,前端拿到之后通过router.addRoute动态添加可访问的路由,配合Vue Router的beforeEach守卫实现登录跳转和权限控制。菜单栏也是根据路由配置自动生成的,这样不同角色登录之后看到的菜单完全不同,学生看到的侧边栏是"我的项目""项目申报""进度提交",管理员看到的侧边栏是"项目审核""评审管理""系统管理"。
3. 核心业务建模:状态机是这套系统的灵魂
如果只让我说一个这套系统最值得讲的模块,那就是项目状态流转。大创管理系统的所有复杂业务,本质上都是不同角色在不同状态下执行不同操作。状态建模做得清晰,整个系统就成功了一大半。
3.1 项目全生命周期状态定义
我把一个项目从被创建到最终结题,总共定义了8个状态:草稿、已申报、学院审核中、校级评审中、立项通过、中期检查中、结题申请中、已结题。另外还有两个终止态:申报驳回和评审不通过。
这里要特别强调,状态字段不要用字符串硬编码,而是在代码里定义枚举类。有些同行为了省事直接在数据库里存中文状态,时间长了到处是魔法值,一旦要改状态名就要改全表数据。我采用的是状态枚举+数据库存状态码的方案,前端通过字典接口转换状态显示,后端通过枚举校验状态流转合法性。
3.2 状态流转合法性的校验设计
状态机最核心的问题不是"能改成什么",而是"不能改成什么"。比如一个处于草稿状态的项目,绝对不允许跳过申报直接进入学院审核;一个已经被驳回的项目,学生修改后只能重新提交申报,不能直接进入中期检查阶段。
我实现状态流转校验的方式是在Service层写一个专门的方法,接收当前状态和目标状态,对照一个提前定义好的状态流转映射表来判断是否合法。
public enum ProjectStatus { DRAFT("草稿"), SUBMITTED("已申报"), COLLEGE_REVIEWING("学院审核中"), SCHOOL_REVIEWING("校级评审中"), APPROVED("立项通过"), MIDTERM_CHECKING("中期检查中"), FINAL_REVIEWING("结题申请中"), COMPLETED("已结题"), REJECTED("申报驳回"); private final String desc; }流转映射表形如:DRAFT可以转到SUBMITTED和REJECTED,SUBMITTED可以转到COLLEGE_REVIEWING和REJECTED,COLLEGE_REVIEWING可以转到SCHOOL_REVIEWING和REJECTED,SCHOOL_REVIEWING可以转到APPROVED和REJECTED,依此类推。这个方法放在一个统一的ProjectStateMachine类里,所有涉及状态变更的Service方法都调用它校验,保证不会出现非法状态跳转。
这块设计逻辑如果做得好,后面前端写按钮显示逻辑就非常轻松:每个按钮的显隐,直接用当前状态+用户角色两个条件判断,不需要在前端再维护一套复杂的状态判断。
3.3 数据库表结构设计
数据库一共设计了十几个表,核心的几个包括:用户表、角色表、菜单权限表、项目管理主表、项目成员表、项目附件表、审核记录表、中期检查表、结题申请表、消息通知表。
举个例子,项目管理主表字段包括:项目ID、项目名称、项目类型、项目类别、负责人ID、指导老师ID、学院ID、项目状态、申报时间、立项时间、结题时间、项目简介、预算金额等。项目成员表单独建一个表,因为每个项目除了负责人还有若干成员,而成员信息在评审时要展示,所以不能省。
项目审核记录表这块值得多讲两句。有些设计直接把审核意见存到项目表的某个字段里,但这样只能存最新的审核意见,历史审核记录全丢了。我单独建了审核记录表,每次审核新增一条记录,包含审核人ID、审核角色、审核时间、审核结果、审核意见,这样既能追踪完整审核链路,又能支持"查看历史审核意见"功能。
CREATE TABLE project_audit_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, project_id BIGINT NOT NULL, auditor_id BIGINT NOT NULL, audit_role VARCHAR(20) NOT NULL, audit_result TINYINT NOT NULL COMMENT '0-驳回 1-通过', audit_comment VARCHAR(500), create_time DATETIME NOT NULL );附件表的设计初期容易被人忽略。项目申报书、中期报告、结题报告往往都是Word或PDF文件,不能直接塞进数据库。我采用的方案是:文件存储在本地磁盘或OSS上,数据库表只存文件路径和文件名。上传接口返回文件的访问路径,业务表里关联存储文件的唯一标识。这样设计的好处是数据库表不会膨胀,文件操作也灵活。
中期检查和结题模块本质上复用了项目主表和审核记录表,只是在业务层做了不同处理。中期检查需要学生填写进展占比、阶段性成果、经费使用情况,结题需要学生勾选成果类型(论文、专利、软著等)并填写成果数量。这些扩展字段我单独建了中期检查表和结题申请表,与项目主表是一对一关系。
4. 双端关键代码落地:从接口设计到页面渲染
架构和表结构设计完毕之后,开发阶段的核心任务就是让数据在各种角色之间流动起来。这个项目里最值得参考的代码集中在认证授权、文件上传、以及前端动态路由这三个地方。
4.1 后端认证与授权的实现细节
登录认证用的是JWT方案。用户输入账号密码,后端校验通过后生成一个Token返回,前端把Token存到localStorage,每次请求都在请求头带上Authorization字段,后端过滤器拦截请求并解析Token获取用户身份。
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { Claims claims = JwtUtil.parseToken(token.substring(7)); if (claims != null) { // 解析用户ID和角色,放入SecurityContext } } filterChain.doFilter(request, response); } }这里有个坑要提醒一下:JWT过期时间不能设置得太长,不然安全隐患很大,但也不能太短,否则用户操作到一半要重新登录,很影响体验。我最终设置的是2小时过期,前端在拦截器里检测到401响应时自动跳转到登录页重新登入。
权限控制这块,除了Spring Security默认的基于URL的配置外,我更依赖方法级别的@PreAuthorize注解。比如项目删除接口只允许管理员调用,就在方法上直接标注@PreAuthorize("hasRole('ADMIN')"),这样每个接口的权限要求一目了然,比维护一大段路径匹配规则可读性强得多。
4.2 前端登录流程与路由守卫
前端部分最值得说的就是登录之后的"身份感知"。用户登录成功后,后端返回的JWT Payload里包含userId和roleCode,前端存进Vuex。然后通过路由守卫检查,如果访问的路径对应角色没有权限,要么滚动到顶部提示无权限,要么直接重定向到首页。
动态路由的核心代码是后端登录接口返回"当前用户可访问的路由表",前端routes配置里只保留了基础路由(登录页、404页),其余全部在登录后动态添加。
// router/index.js router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (!token && to.path !== '/login') { next('/login'); } else { next(); } });菜单栏生成逻辑是从Vuex中取出路由数据,递归渲染成侧边栏菜单。这里有一个体验细节被很多人忽略——页面刷新时Vuex状态会清空,如果不做持久化处理,一刷新用户就"失忆"了。我的方案是加了一个vuex-persistedstate插件,把用户信息和路由表同步到localStorage,刷新后重新拉取并恢复。
4.3 文件上传与进度管理的实现方案
文件上传功能我用的是前端Element UI的上传组件,后端MultipartFile接收,存储路径按照/upload/yyyy/MM/dd/格式组织,避免单目录文件过多。上传成功后返回两个字段:文件存储路径和文件显示名,前端把这两个字段塞进表单的附件列表里。
至于进度管理,项目详情页会展示一个时间线组件,把申报、学院审核、校级评审、中期检查、结题等节点按时间轴排列,已完成节点用高亮显示,当前进行中的节点用加载图标标识,未来节点置灰。时间线数据来源就是项目状态字段+审计日志表里的时间戳。这个设计上线后反响很好,老师不用再点进去看详情就能一眼掌握项目走到哪一步了。
5. 部署全流程实战:从源码到能演示的完整步骤
写代码只是开发的一半,能顺利部署起来才是真正能交付的标准。因为毕设演示和实际交付场景里,服务器分布五花八门,有的就一台Windows笔记本,有的是一台阿里云Linux服务器,所以我分两个场景讲部署步骤,重点说一下最容易出错的配置部分。
5.1 环境准备清单
部署之前需要准备以下环境:
- JDK 1.8或以上版本
- Maven 3.6+
- MySQL 5.7+或8.0+
- Node.js 14+(用于前端打包)
- Nginx(生产环境部署前端静态资源)
注意MySQL 8.0的驱动和8.0之前的驱动名称不一样。整合的时候必须确认mysql-connector-java版本和pom.xml里配置的依赖对应,否则连接数据库的时候会报Communications link failure错误,排查半天才发现是驱动不匹配。
5.2 后端打包与配置文件修改
SpringBoot的配置文件是application.yml,部署前修改数据库连接参数。我用的是多环境配置文件方式:application-dev.yml、application-prod.yml分别对应开发和生产环境,打包时通过参数指定激活哪个环境。
重点提醒:文件上传路径的配置要改成服务器上的绝对路径。本地开发时我用的是用户目录下的相对路径,但部署到服务器后,相对路径会导致文件散落在各个目录下,找不到文件也很难备份。后来改成绝对路径配置:
upload: path: /data/upload/ # Linux环境打jar包的命令是mvn clean package -Dmaven.test.skip=true,然后java -jar project-system.jar --spring.profiles.active=prod启动。生产环境建议用nohup方式后台运行,并把日志输出到指定文件,方便排查问题。
5.3 前端构建与Nginx配置
前端部署的关键是打生产包之前必须修改环境变量。Vue项目默认开发环境请求路径是localhost:8080,生产环境必须改成后端服务实际地址。我用的是.env.production文件,配置VUE_APP_BASE_URL指向服务器域名或后端接口地址。
npm install npm run build构建完成之后,项目目录下的dist文件夹就是静态资源包,把它整个拷到服务器的/usr/share/nginx/html目录下。Nginx配置的关键在于把/api路径反向代理到后端服务:
server { listen 80; server_name your_domain; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有一个前端路由产生的经典坑:Vue Router使用的history模式,刷新非首页路径时会404,因为Nginx找不到对应的静态文件。解决方案就是上面配置里的try_files $uri $uri/ /index.html,它会把所有路径请求都回退到index.html,再由前端路由接管页面渲染。这个坑我一开始就踩了,后来加了这一行才算解决。
5.4 部署阶段踩过的坑与排查过程
第一个坑是数据库字符集乱码。系统里大量涉及中文,尤其是项目名称,如果数据库表的字符集不是utf8mb4,前端录入中文后存入数据库是乱码,读出来乱码,审核意见也是乱码。解决方法是建库时明确指定字符集,同时连接字符串也要加上characterEncoding=utf8参数。
第二个坑是端口占用问题。开发环境后端默认8080端口,但如果服务器上已经跑了其他Java应用,端口冲突就会导致启动失败。排查方法是先netstat -tlnp查看端口占用,然后修改application-prod.yml里server.port配置,或者换一个不常用的端口。
第三个坑是前端跨域问题。开发环境下我通过vue.config.js的devServer代理解决跨域,生产环境下如果没有配置Nginx反向代理,前端和后端在不同域名或不同端口,浏览器会直接拦截跨域请求。排查思路是先看浏览器Network面板,确认请求是否发出、后端是否正确响应,然后对照Nginx配置检查proxy_pass是否生效。
6. 做完这套系统之后,我最有价值的几条经验
项目收尾之后回望整个开发过程,有一些经验和教训如果当初就明白,至少能省下三五天时间。这里挑几条最值得说的分享给大家。
第一条经验是数据库设计阶段一定要画清楚状态流转图,哪怕手画也行。大创项目管理系统这类业务,状态设计直接决定后端的接口复杂度和前端的交互设计。如果状态设计含混不清,后面改起来就是动一处牵全身,改表结构加字段都是小事,最怕的是改状态逻辑导致历史数据错乱。
第二条经验是前后端接口文档一定要提前定,尤其是字段命名和时间格式。我一开始在时间字段上吃过亏,前端传的时间是yyyy-MM-dd格式,后端实体类却按时间戳接收,解析直接报错。后来统一约定所有日期用字符串传递,前端格式化后提交,后端直接存字符串,反而省心。
第三条经验是关于安全意识。很多人觉得做管理系统不用太在意安全,但真实业务环境里,学生和老师的数据都是敏感数据。建议至少在初始阶段做好三件事:密码加密(强烈建议用BCrypt),登录接口做频控限流,页面操作全部走权限校验而非只隐藏按钮。
7. 源码之外的扩展建议:这套系统还能怎么改
如果你正在以这套系统为基础做毕设或者二次开发,这里有几个比较高性价比的扩展方向。
预算充足或者毕设想拔高深度的话,可以考虑加入的消息通知模块,目前系统内的消息是在站在线通知,可以扩展成邮件或短信通知,在项目状态变更时自动发送给相关人员,这会显著提升系统的主动服务能力,答辩时也是个很好的加分点。
另外可以引入导出功能。教务处老师特别喜欢把项目汇总表导出成Excel,每次到评审时间都要统计全院申报项目数、通过率、经费总额等指标。可以集成EasyExcel做数据导出,再扩展一个简单的统计报表页面,用柱状图展示各学院的申报数量,这些功能实现难度不高,但实际价值非常大。
如果你拿到的源码版本里还没有做权限细粒度控制,建议优先补上"数据范围"控制。目前的方案是区分了校级和学院级,但学院管理员只能维护本院项目这一点可能需要你在初始化数据时通过学院ID关系合理配置,确保数据隔离不出问题。这部分做好了,离一个真正能交付给某高校使用的生产级系统就更进一步了。
那这次项目的完整回顾就先写到这里。如果后续有时间,准备再单独写一篇关于某套代码里Excel导入导出模块的实现笔记,比这个项目里的简单导出要复杂得多,到时候再和大家详细分享。