
1. 大创管理系统到底管什么业务全貌与需求拆解1.1 高校大创项目的管理痛点做高校里的大创管理系统我前后折腾了快两个月。一上来就闷头写代码是不行的得先弄明白这套系统到底在解决什么问题。大创指的是大学生创新创业训练计划每年学校都要组织一批项目涵盖创新训练、创业训练、创业实践等类型。学生组队找指导老师提交申报书经过院级评审、校级评审立项以后再跑一年左右中间有中期检查最后结题验收。没有系统之前这套流程全靠纸质材料加Excel第一个痛点是版本混乱申报书改了三四版传到后面根本分不清哪份是最新的第二个痛点是进度全靠催中期检查时间到了负责老师得一个个电话通知第三个痛点是评审数据不好统计评委打分分散在微信和表格里汇总的时候又慢又容易出错。所以这类管理系统的核心价值不是做个网站而是把完整业务流程线上化、状态化、可追溯化。拆开需求核心词就几个申报、评审、立项、中期、结题、权限、消息。这个系统就是把这几件事用Web方式落地面向学生、指导教师、学院管理员、校级管理员四类用户覆盖项目从出生到结题的全部生命周期。1.2 四类角色与权限边界系统里的用户一般分成四类权限边界必须一开始就定清楚不然后面写代码容易乱。学生发起项目申报、填报成员信息、查看评审结果与立项状态、提交中期材料和结题材料。指导教师审核学生提交的申报书、给出指导意见、查看自己指导项目的进展。学院管理员组织本学院的评审、分配评审专家、统筹本学院项目动态。校级管理员发布通知、组织校级评审、确认立项名单、维护项目状态、导出统计报表。权限设计是这个项目最容易出错的地方很多类似的源码项目把权限写得很粗暴controller层到处是if判断角色维护起来特别痛苦。这个项目我推荐用统一的拦截器加注解方案后端接口上打RequireRole注解前端路由同步控制菜单显隐权限逻辑集中在一处不容易漏。1.3 核心功能模块一览模块核心功能涉及角色用户认证登录、注册、修改密码、个人信息全部项目管理申报填写、提交、修改、退回、删除学生、指导老师评审管理专家分配、在线打分、评语填写、分数统计管理员、评审专家立项管理确认立项名单、生成项目编号校级管理员过程管理中期检查、结题验收、异常终止学生、管理员通知公告发布、编辑、上下线、置顶管理员数据统计各学院申报数、立项数、结题率校级管理员这些模块全部写完以后工作量最大的其实不是增删改查而是状态流转和权限校验这两块后面我会重点拆。2. 技术选型为什么是SpringBootVueMySQL2.1 这套组合务实在哪技术栈这块我和很多做校园项目的人聊过大家的共识是不追新、不折腾、够用就行。SpringBoot省去了大量Spring XML配置内置Tomcat一个Jar包就能跑学生机房和学校服务器都能轻松部署Vue做单页应用开发效率高配合Element Plus组件库后台管理界面几天就能拼出来MySQL稳定、免费、资料多几乎每个学校运维都会点。三者组合正好覆盖后端API、前端页面、数据存储的最小完整闭环。有人会纠结要不要上微服务、Redis、消息队列我的建议是不要过度设计。一个几百人同时在线的管理系统单机SpringBoot加MySQL完全扛得住。堆一堆中间件上去部署复杂了反而违背了可直接运行这个目标。2.2 前后端分离的收益这个项目采用前后端分离架构后端只提供RESTful接口返回JSON前端用axios调接口、渲页面。好处是职责清楚前后端各自开发、各自调试用Swagger导出接口文档后前端可以并行开工不用等后端页面套好。生产部署也灵活开发环境前端走Vite代理解决跨域生产环境把前端build后的静态文件放进SpringBoot的static目录一个进程全搞定。Vue侧我用的是Vue3组合式API、Vite、Vue Router、Pinia和Element Plus。Vue3的setup语法让状态逻辑可以集中在函数里比Options API写起来舒服很多。状态管理选Pinia而不是Vuex因为Pinia更轻、TypeScript支持更好、没有mutations概念写起来直观。2.3 项目目录结构怎么规划我接这类项目时的习惯是先把目录划清楚不然功能一多就乱套。后端按业务分层com.example.dachuang ├── config 配置类跨域、拦截器、文件上传 ├── controller 接口层 ├── service 业务层 ├── mapper 数据访问层 ├── entity 数据库实体 ├── dto 传输对象避免直接暴露实体 ├── common 返回封装、异常处理 └── utils JWT、文件处理工具前端按页面和组件划分src ├── api 接口请求封装 ├── assets 静态资源 ├── components 通用组件 ├── router 路由配置 ├── stores Pinia状态 ├── views 页面 └── utils 工具函数这样分包有两个好处一是找文件快按controller、service、mapper的路径一层层往下追就行二是改动边界清晰新加一个功能基本不会动到不相关的文件。3. 数据库设计核心表结构与状态流转3.1 核心数据表清单数据库是系统的地基表结构设计的质量直接决定后面写代码顺不顺利。我把这套系统的核心表整理一下表名说明关键字段sys_user用户表id、username、password、role、name、collegestudent_info学生扩展信息user_id、student_no、major、gradeteacher_info教师扩展信息user_id、title、departmentproject项目表id、project_code、name、type、category、student_id、teacher_id、college、budget、statusproject_member项目成员表id、project_id、member_name、member_noreview_record评审记录表id、project_id、reviewer_id、score、comment、stagemidterm_report中期检查表id、project_id、progress、problem、attachmentconclusion_report结题表id、project_id、summary、achievement、attachmentnotice通知公告表id、title、content、publisher_id、publish_timeattachment附件表id、biz_type、biz_id、file_name、file_path、file_size其中一张表要特别说明就是operate_log操作日志表我用它记录每个项目每次状态变更的操作人、操作时间、操作内容和变更前状态。评审答辩的时候评委经常会问这个项目什么时候退回的、谁改的有这张表就能直接回答非常实用。3.2 项目状态字段怎么设计project表里的status字段是整个系统的灵魂。我用一组固定状态值整数存储状态值含义说明0草稿学生创建后未提交1待指导教师审核已提交等老师审核2学院评审中通过老师审核等待院级专家打分3校级评审中通过学院评审等待校级专家打分4已立项校级确认立项生成项目编号5中期检查中项目运行中期提交进展报告6待结题验收中期通过准备结题7已结题结题验收通过8已退回某一环节被退回修改9已终止异常终止或放弃用整数而不是字符串的好处是存储省空间、比较大小方便、前端可以映射枚举中文名显示。状态流转的控制规则我放在service层统一校验比如只有status为0或8才能提交审核只有status为1才能流转到学院评审不让controller散写判断逻辑。3.3 外键和索引的取舍实际操作中我很少在数据库里定义物理外键。原因一是批量导入数据时会受到约束限制原因二是程序里通过事务和业务逻辑已经保证了数据一致性。但逻辑外键一定要建索引user表的主键、project表的student_id和teacher_id、review_record表的project_id都建了普通索引评审统计的时候查询性能提升明显。项目表上我额外加了college和status两个普通索引因为统计各学院申报数量和按状态筛选项目列表是最高频的操作。如果没索引数据量一旦过万统计接口会明显变慢。3.4 初始化数据怎么准备既然标题强调可直接运行数据库脚本就必须带好初始化数据。我的建议是三步预置四个账号校级管理员admin、学院管理员college_admin、指导教师teacher01、学生student01密码统一用BCrypt加密后写进SQL。插入几条示例项目数据状态覆盖已立项、评审中、草稿等前端页面一打开就有内容可看。基础数据如学院名称表、专业名称表、项目类型枚举值也一并导进去运行时不用再手动维护。首次启动用的是SpringBoot自带的data.sql机制spring.sql.init.modealways在首次建表插数据后续改成never避免重复执行。4. 后端核心实现完整业务闭环怎么写出来4.1 启动配置与基础依赖后端项目的pom.xml里核心依赖包括SpringBoot Web、MyBatis-Plus、MySQL驱动、Lombok、JWT相关库、Hutool工具包。MyBatis-Plus是个强推的选择BaseMapper提供通用CRUD条件构造器可以动态拼查询条件分页插件一个注解搞定大规模减少了单表的重复代码。这个系统里绝大多数查询都是单表或轻量关联用MP默认方法就够了。application.yml里的几个关键配置spring: datasource: url: jdbc:mysql://localhost:3306/dachuang?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080 spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0两个特别容易踩的坑提前说MySQL8的驱动类名必须带cjcom.mysql.cj.jdbc.Driver少写cj直接ClassNotFoundExceptionurl里的serverTimezoneAsia/Shanghai不能省不然时间字段会差8小时。4.2 登录认证与权限拦截认证方案用的是JWT加拦截器。为什么不引入Spring Security因为这个项目的权限复杂度远不需要一套完整安全框架JWT加一个HandlerInterceptor就可以实现同样的拦截效果代码量少一半也更适合学习者读懂。登录接口校验用户名和密码成功后生成token把userId和role放进去String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 12)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();拦截器里从请求头取Authorization去掉Bearer前缀解析出userId和role塞进ThreadLocal的UserContext后续service层直接UserContext.getUserId()拿当前登录人。路由权限用自定义注解RequireRole({ADMIN, COLLEGE_ADMIN})拦截器判断角色是否匹配。这样所有权限逻辑集中在一个类里接口上打注解就行清晰且安全。4.3 项目申报的完整链路这是整个系统最核心的业务链路我用文字描述一遍状态和接口的对应关系学生创建项目调POST /api/project生成一条status0的草稿数据。学生补全项目信息调POST /api/project/{id}/submit提交状态变为1。操作前校验当前用户必须是项目创建人且项目状态必须是0或8。指导教师在待审核列表看到项目调POST /api/project/{id}/teacher-audit传通过或退回意见。通过则状态变为2退回则变为8并记录退回原因。学院管理员分配评审专家专家调POST /api/review/{projectId}/score打分所有专家打完后管理员确认汇总均分状态变为3。校级管理员在立项页面勾选通过项目调POST /api/project/batch-approve传入项目id数组批量把状态改为4并按规则生成项目编号。每一步都调用一次operate_log记录方便日后追溯。4.4 文件上传与Excel导出申报书、中期报告、结题报告都要传文件用Spring的MultipartFile实现。文件保存在本地upload/目录文件名用UUID重命名避免中文乱码和重名冲突路径存到attachment表后通过SpringBoot静态资源映射访问。导出功能我用EasyExcel导出项目列表的代码非常简洁ListProjectVO list projectService.getProjectList(query); EasyExcel.write(response.getOutputStream(), ProjectVO.class) .sheet(项目列表) .doWrite(list);VO类里的字段加ExcelProperty注解Excel的表头就会按注解自动生成不用手动写样式。4.5 接口返回的统一封装所有接口统一返回ResultT结构包含code、message、data三个字段成功时code为200。前端axios拦截器统一判断非200的code弹出错误提示处理401跳转登录页。后端用RestControllerAdvice做全局异常捕获业务异常抛自定义的BizException返回对应的错误信息未预期异常记录日志后返回系统繁忙这样友好的提示不把堆栈直接暴露给前端。5. 前端实现Vue3页面与联调细节5.1 前端工程化配置前端用Vite创建项目依赖安装npm install开发启动npm run dev。入口的main.js里注册Element Plus、Pinia和路由组件。开发中最痛的问题是跨域我在vite.config.js里配置代理解决server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/project时Vite开发服务器会自动转发到8080浏览器侧的请求保持同源避免CORS报错。后端同时也在CorsConfig里配了允许跨域双保险部署时不容易出问题。5.2 路由与前端权限控制前端路由分为静态路由和动态路由。静态路由包括登录页、注册页、404页动态路由根据角色加载。Pinia里存userRole在路由守卫前判断目标路由的meta.roles是否包含当前角色不包含则重定向到403页面。菜单通过遍历路由表生成学生看不到评审管理菜单管理员看不到我的项目角色不同看到的界面自然不同。登录态的处理是另一个关键点。我用Pinia存token和userInfo刷新页面时从localStorage重新读token调GET /api/user/info恢复用户信息再动态加载对应路由。这样刷新后不会跳回登录页体验流畅。5.3 核心页面拆解项目申报页一个动态表单项目成员列表可以增删行内部用v-model绑定数组删除行时同步触发校验至少保留一名成员。评审打分页卡片式布局展示项目基本信息下方是评分滑条和评语输入框提交前做防重复提交处理按钮在请求期间禁用。项目列表页ElTable加分页组件查询条件有项目名称、类型、状态、学院。状态筛选用下拉框选项枚举与后端状态值一一对应。数据统计页用ECharts画柱状图和饼图展示各学院申报数、立项率、项目类型分布。接口直接返回聚合好的数据前端只负责渲染。5.4 前后端联调的实操心得联调阶段我习惯先打开后端的Swagger接口文档页面确认每个接口请求参数和返回结构没问题再对接前端。前端报错优先看Network面板的接口返回别看控制台红字就慌大部分问题都出在参数名不匹配或者字段拼写错误。还有一个细节是时间字段。后端返回的LocalDateTime默认带字母T的ISO格式前端需要格式化才能显示我封装了统一的formatDate工具函数页面上调用即可避免重复代码。6. 跑起来部署的完整步骤从零到能访问6.1 本地环境准备可直接运行不等于不用装环境基础环境还是得有。我列一个最低清单软件版本要求用途JDK1.8或以上运行后端Maven3.6或以上依赖管理、打包Node.js16或以上运行前端构建工具MySQL5.7或8.0存储数据开发工具后端用IDEA前端用VS Code。IDEA打开后端项目后等Maven下载依赖前端在VS Code终端执行npm install命令卡住的话换成npmmirror的镜像源。6.2 数据库初始化的两种方式第一种是从零初始化在MySQL里建库create database dachuang default charset utf8mb4;再执行项目提供的sql文件导入表结构和数据。第二种是依靠SpringBoot自动初始化配了schema.sql和data.sql后首次启动会自动执行。我更推荐第一种因为用Navicat导入直观、能及时看到报错且对新手友好。6.3 启动顺序和后端参数调整启动顺序固定是先后端、后前端修改application.yml里的数据库账号密码为本地配置。IDEA里运行DachuangApplication的main方法控制台出现Started表示后端启动成功。前端目录执行npm install后npm run dev看到端口输出即启动成功。浏览器访问http://localhost:5173用初始化账号admin/admin123登录。如果默认8080端口被占用改application.yml里的server.port记得同步改vite的proxy target。6.4 生产部署的两种思路正式环境部署时前端要先构建npm run build产物是dist目录。然后两种选择省钱方案dist里的文件复制到SpringBoot的src/main/resources/static目录重新打包成jarjava -jar直接运行。所有静态资源和API都在同一个进程不需要额外起服务我强烈推荐校园项目用这个。标准方案dist放到Nginx的html目录Nginx把/api开头的请求代理到后端8080端口。适合前后端分离规模较大或者未来要扩展多应用的场景。数据库迁移到服务器时用Navicat转储SQL再导入字符集选utf8mb4。国内服务器上注意一下MySQL的默认字符集否则导入后中文容易变成问号。7. 常见问题与排查技巧实录7.1 后端启动失败的三个高频原因现象原因解决办法ClassNotFoundException: driver驱动类名写错或依赖没引入pom里补mysql-connector-j依赖url配置驱动类带cjAccess denied for user数据库账号密码不对核对application.yml确保与本地MySQL一致Port 8080 was already in use端口占用改server.port或netstat找PID杀掉进程我遇到最坑的一次是本地MySQL8的默认认证插件是caching_sha2_password用老版驱动连不上换成新版驱动类就好了。还有一次是系统里装了好几个JDK启动时加载了旧版本在IDEA里检查Project SDK和Java Compiler版本保持一致很关键。7.2 前端页面白屏或接口404前端白屏先看浏览器控制台一般是路由报错或组件引入路径不对。接口404分两种情况后端路径确实和前端请求对不上或者代理配错了。联调前我习惯把所有接口路径集中在src/api目录的js文件统一导出页面上只做引用避免路径手写拼错。前端请求出现404时先在Network面板里确认请求的URL再对比后端controller的RequestMapping。7.3 中文乱码问题中文乱码有三个常见位置数据库连接URL缺characterEncodingutf8、前端页面没有UTF-8声明、请求或响应编码设置不对。数据库连接串是最普遍的坑另外建表时要注意默认字符集我在SQL脚本里统一加了DEFAULT CHARSETutf8mb4Navicat导入时也能带上。还有一点文件上传时中文文件名要处理我前面提过用UUID重命名从根上避免文件名乱码。7.4 评审流程的隐蔽并发问题这个坑特别值得分享。多个评委同时给一个项目打分时如果每次提交都在项目表的score字段上做累加或覆盖会互相丢数据。最典型的场景是A评委提交了85分B评委同时提交了90分后提交的人覆盖前一个人的分数平均分等于没算。我的处理方案是评审表存每个评委的单次打分记录项目表的平均分在管理员点击完成评审汇总时才统一计算。这样既保留了每个评委的原始评审数据也规避了并发写覆盖问题。7.5 其他容易忽略的细节Lombok在IDEA里不装插件编译报找不到getter和setter。装一下Lombok插件或者检查Annotation Processing有没有开启。数据库表字段deleted逻辑删除字段查询时要留意MP自动拼接逻辑删除条件别在SQL里再写一遍deleted0容易重复。前端页面切换后状态丢失问题是Pinia的state没做持久化。我用了pinia-plugin-persistedstate插件把登录信息自动持久化到localStorage刷新不掉线。8. 扩展方向与个人体会这套系统做完以后我最大的感受是类似的管理系统真正难的从来不是增删改查的技术而是把业务状态梳理清楚。拿到这分源码的人我建议先不要急着跑而是把状态流转图手动画一遍把每个状态对应的可操作角色和操作列出来再去读代码会顺畅非常多。状态理顺了代码基本就顺了。如果想在毕设或者比赛基础上往深做可以考虑这几个方向增加消息通知立项结果、评审意见、中期检查提醒用站内信或者WebSocket推送给对应用户这是校园场景里非常刚需的功能。统计报表可视化增强用ECharts再加词云展示所有项目的关键词分布、趋势折线图展示历年立项数量变化视觉效果好且工作量可控。状态流转配置化按项目类型配置不同的评审轮次比如创业实践类项目要求校审两轮、创新训练类一轮。这个可以做成策略模式比引入流程引擎轻量得多。对接统一身份认证学生和教师用校园账号直接登录这是实际落地时最容易被追问的需求但注意对接方案按学校现状来不涉及统一框架的选型。如果你拿到的这套源码目标是拿到就能跑那它已经做到了。数据库脚本、初始化账号、部署说明都齐了基础环境备齐后半天内跑起来是没问题的。我自己当时踩过最大的教训就是不要一上来把系统想得太复杂先把一条主流程跑通申报、审核、立项、中期、结题闭环通了其他功能都是往这个框架里添砖加瓦。这也是这套源码能够快速跑起来的原因所在。希望读到这里的你也能顺着这条思路把自己的项目做得顺手、做得出彩。