ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Spring Boot和Vue的大学生创业信息管理系统开发实战

基于Spring Boot和Vue的大学生创业信息管理系统开发实战 1. 为什么选大学生创业信息管理系统当毕设选题逻辑与需求拆解每年到了毕业季计算机专业的同学都会为选题头疼。系统做太简单答辩时老师一问就露馅做得太复杂又担心自己一个人扛不下来。我当初选基于Java Vue的大学生创业信息管理系统这个题目核心原因是它在复杂度、工作量、可展示性三者之间取得了很好的平衡。它不是一个纯粹的CRUD增删改查但又不会难到需要分布式架构、消息队列这种大杀器——正适合用一套成熟的全栈技术栈把它做扎实。1.1 选题逻辑为什么这个题目适合毕业设计先说点实际的。毕设选题要满足三个条件第一必须是老师认可的真实业务场景第二技术栈要主流不能是十年前的老古董第三工作量要可控一个人能在两三个月内完成。大学生创业信息管理系统恰好符合这三点。国家层面一直在推创新创业教育几乎所有高校都有创业项目申报、评审、路演、项目跟踪这些业务需求。你拿着这个题目跟老师沟通业务背景是站得住脚的——这不是凭空捏造的玩具项目而是真实存在且被广泛需要的信息化需求。从技术角度看它天然具备多角色权限管理员、学生、评审老师、核心业务流转项目申报、审批、评分、文件上传下载创业计划书附件、数据统计展示各学院申报数量图表这些典型功能能完整地展示一个全栈开发者的基本功。当然我的私心在于这个系统的每一个功能模块都是可以讲出故事的。答辩的时候你不需要吹嘘自己用了多牛的技术只需要把业务链路讲清楚——学生怎么提交申报、老师怎么审核、管理员怎么看统计——这一套下来答辩委员会自然能看出你是真的把系统做出来了而不是在GitHub上随便找了个demo。1.2 功能需求拆解学校到底需要一个什么样的系统在动手编码之前我先花了整整一周做需求分析。这里有个经验千万不要拿到题目就急着建工程需求分析做得越细后面写代码越顺。我梳理出的核心角色权限矩阵如下角色核心权限使用场景学生填写创业项目申报书、上传附件、查看审核状态、修改项目信息在校大学生申报创业项目评审老师查看待评审项目、在线评分、填写评审意见创业导师或专业教师评审申报项目管理员用户管理、项目审核、数据统计、系统配置学校创新创业学院管理人员业务流程可以概括为学生注册登录 → 填写创业项目申报书含项目名称、类别、成员、指导老师、计划书附件→ 提交后进入待审核状态 → 管理员初审通过后分配评审老师 → 评审老师线上评分 → 管理员汇总成绩并公示 → 学生查看最终结果。除了这条主线系统还需要以下支撑功能用户管理学生注册需管理员审核激活防止校外人员混入项目类别管理将创业项目分为科技创新、文化创意、电子商务、社会服务等类别便于分类统计公告通知管理员发布创业政策、申报截止时间等通知数据看板首页展示各学院申报数量、项目类别分布、审核状态统计图表个人中心修改密码、查看自己的申报记录这些需求听起来不复杂但它们共同构成了一个完整的信息化闭环。而且每个模块都有明确的验收标准也方便我后面按模块推进开发不用返工。2. 技术栈选型Java Vue组合的底气与替代方案对比技术选型是这个项目最关键的决策点。我最终确定的是后端Spring Boot 2.7 MyBatis-Plus前端Vue 2 Element UI数据库MySQL 8.0鉴权用JWT Spring Security。下面详细说说每项选型的理由以及踩过的一些弯路。2.1 后端为什么选Spring Boot而不是SSH或SSM很多学校教材还在教SSHStruts2 Spring Hibernate或者SSMSpring Spring MVC MyBatis但我强烈建议毕设别用这些老框架组合。原因很简单配置繁琐调试困难社区资源少。光是一个Spring的XML配置就能写几百行一旦报错网上能搜到的解决方案大多是七八年前的参考价值有限。Spring Boot最大的价值在于约定优于配置。内嵌Tomcat一键启动Application类Spring Security和MyBatis-Plus都有对应的Starter引入依赖后几乎零配置就能跑起来。我身边有同学用SSM写同样的系统光是环境搭建就折腾了两周而Spring Boot环境下我第一天就把工程骨架搭好并成功启动了。ORM层我选了MyBatis-Plus而不是原生MyBatis。MyBatis-Plus提供BaseMapper简单的单表增删改查直接继承接口就能用省去大量写Mapper XML的时间。对于联表查询这种复杂场景保留手写SQL的灵活性正好满足毕业设计的需求节奏。2.2 前端为什么选Vue渐进式框架对学生的友好度前端框架目前主流是Vue和React两个方向。对于做毕设的同学我更推荐Vue理由非常接地气学习曲线平缓Vue的模板语法接近原生HTMLComposition APIVue 3虽然更强但Options APIVue 2对初学者极其友好——data里写数据、methods里写方法逻辑直观。中文生态完善Element UI对应Vue 2和Element Plus对应Vue 3的中文文档、示例、社区问答非常丰富几乎任何组件用法都能搜到现成案例。脚手架成熟Vue CLI或Vite一条命令就能创建工程配套Vue Router和Vuex/Pinia都有标准模板。我选用的是Vue 2.6.14 Element UI 2.15。为什么不赶时髦用Vue 3因为当时的Element Plus还不太稳定而且网上关于Vue 2 Element UI的踩坑资料远远多于Vue 3对于毕设这种求稳不求新的场景Vue 2是目前最稳妥的选择。如果你的毕业设计时间充裕也可以直接上Vue 3 Element Plus但要有心理准备——个别组件API有差异遇到问题要耐心查文档。2.3 技术选型替代方案对比表写这节前专门花了点时间整理了一份选型对比方便正在纠结的同学参考层次我的选择常见替代方案替代方案的优劣势后端框架Spring Boot 2.7SSM / SSH / 若依框架若依代码生成效率高但定制化程度低答辩时容易被追问原理ORMMyBatis-PlusSpring Data JPA / MyBatisJPA自动建表省事但复杂查询不直观原生MyBatis需手写大量XML前端框架Vue 2 Element UIVue 3 Element Plus / React AntdReact生态更强但上手成本高毕设周期内可能驾驭不好鉴权方案Spring Security JWTShiro / Sa-TokenShiro配置相对简单Sa-Token更轻量适合不想深入了解安全框架的同学数据库MySQL 8.0PostgreSQL / SQL ServerPostgreSQL功能更强但校方普遍熟悉MySQL部署环境兼容性最好最后补充一条原则选型不追求最新追求的是你能讲清楚。答辩时老师大概率会问为什么选这个技术如果你能说清Spring Boot相比SSM省掉哪些配置、Vue相比JQuery有什么优势、JWT相比Session有什么特点就已经够了。别选一个你自己都说不明白的框架那是给自己挖坑。3. 数据库设计实战核心表结构、关系与初始化SQL数据库设计是整个系统的地基。地基没打好后面写业务代码的时候会不断回来改表——改表还要同步改实体类、Mapper、前端表单那酸爽谁经历谁知道。这一节把核心表设计和一些容易忽略的细节全部交代清楚。3.1 用户角色设计学生、老师、管理员三套权限怎么落表用户表是系统的核心。我采用了单表角色字段而不是用户表角色表用户角色关联表的简化方案。原因在于这个系统的角色是固定的三类不会出现一个用户同时拥有学生和管理员双重身份这种复杂情况用role字段即可满足需求省掉三张表的冗余设计。核心的sys_user表结构如下CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role tinyint(4) NOT NULL DEFAULT 2 COMMENT 角色0-管理员 1-评审老师 2-学生, college varchar(100) DEFAULT NULL COMMENT 所属学院, student_no varchar(50) DEFAULT NULL COMMENT 学号, phone varchar(20) DEFAULT NULL COMMENT 联系电话, email varchar(100) DEFAULT NULL COMMENT 邮箱, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待审核 1-正常 2-禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT系统用户表;有几个地方特别说明一下密码加密必须用BCrypt。不要用MD5更不要明文存储。Spring Security自带BCryptPasswordEncoder加密后每次校验时调用matches方法安全性有保障。这在答辩时是个加分项——说明你有安全意识。status字段用于学生注册审核。业务场景是学生自助注册后不能立即登录需要管理员在后台审核账号这样能有效防止校外用户注册使用系统。学号student_no不设唯一索引。为什么因为评审老师和管理员没有学号设置为可空只能加唯一索引在username上。3.2 创业项目申报模块的表设计让业务字段对得上答辩的提问创业项目信息表是整个系统的业务核心。以我做过完整设计的标准来看这张表至少要考虑项目基本信息、项目成员信息、申报状态流转、附件存储路径、评分结果关联。我先设计了project_info表CREATE TABLE project_info ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 项目ID, project_name varchar(200) NOT NULL COMMENT 项目名称, project_category varchar(50) NOT NULL COMMENT 项目类别科技创新/文化创意/电子商务/社会服务等, leader_id bigint(20) NOT NULL COMMENT 项目负责人ID关联sys_user.id, leader_name varchar(50) NOT NULL COMMENT 项目负责人姓名冗余字段, leader_phone varchar(20) DEFAULT NULL COMMENT 负责人电话, teacher_name varchar(50) DEFAULT NULL COMMENT 指导老师姓名, teacher_phone varchar(20) DEFAULT NULL COMMENT 指导老师电话, project_desc text COMMENT 项目简介, plan_file_path varchar(255) DEFAULT NULL COMMENT 创业计划书附件存储路径, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待初审 1-初审通过 2-待评审 3-已评分 4-已立项 5-已驳回, reject_reason varchar(500) DEFAULT NULL COMMENT 驳回原因, reviewer_id bigint(20) DEFAULT NULL COMMENT 评审老师ID, score decimal(5,2) DEFAULT NULL COMMENT 评审评分, comment varchar(1000) DEFAULT NULL COMMENT 评审意见, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 申报时间, submit_time datetime DEFAULT NULL COMMENT 提交初审时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_leader_id (leader_id), KEY idx_status (status), KEY idx_category (project_category) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT创业项目申报表;关于这张表三个设计心得值得展开讲讲。第一个是状态设计。我用一个数字字段status表示项目全生命周期从待初审到已立项共6个状态。为什么不用单独的流程表因为毕设的核心目的是展示业务逻辑一个状态机字段配合前端按钮的条件渲染已经足够表达流程流转用单独的表反而把简单问题复杂化了。这里要注意状态的枚举值必须在代码中定义成常量类不能散落在各个业务方法里写死数字否则后期改动状态时极易漏改出错。第二个是冗余字段。leader_name和leader_phone直接从用户表冗余过来看似违背数据库范式但实际操作中极大简化了列表查询——不需要每次查询项目都去关联用户表取姓名。在数据量不大的系统中适度冗余换取查询性能和代码简洁性是完全值得的。第三个是附件存储。plan_file_path字段存的是文件在服务器上的相对路径比如/upload/20240512/xxx.pdf。实际文件存储在本地磁盘目录数据库不存二进制内容BLOB这样备份数据库时不会被大文件拖慢速度。3.3 辅助表的设计公告通知与系统配置除了用户表和项目表还有公告表和系统参数表。公告表notice_info结构比较简单id、标题title、内容content、发布人publisher_id、创建时间create_time。这个表不需要太多花哨的设计注意给创建时间加索引即可因为列表页要按照时间倒序展示。系统参数表sys_config则可以存储可配置的业务开关例如是否允许学生修改已提交的项目、初创项目申报截止日期等。用一张键值表搞定CREATE TABLE sys_config ( id bigint(20) NOT NULL AUTO_INCREMENT, config_key varchar(100) NOT NULL COMMENT 参数键, config_value varchar(500) DEFAULT NULL COMMENT 参数值, config_desc varchar(200) DEFAULT NULL COMMENT 参数说明, PRIMARY KEY (id), UNIQUE KEY uk_config_key (config_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统参数配置表;这种设计让系统具备一定灵活性——有些业务规则管理员可以在页面上配置不需要改代码重新部署。4. 后端核心模块实现从登录鉴权到业务接口后端是整个系统的大脑。这一节我从工程结构、登录鉴权、项目申报接口三个角度把最核心的代码逻辑和实现思路讲透。由于篇幅关系不会把每个类的完整代码都贴出来而是挑最关键的片段进行解析。4.1 Spring Boot工程结构包划分决定了代码的可维护性我的后端工程采用标准的Maven结构包名按照com.example.cieCollege Innovation Entrepreneurship 的缩写组织。目录结构如下src/main/java/com/example/cie ├── common │ ├── exception # 统一异常处理 │ ├── result # 统一返回结果封装 │ └── utils # JWT工具类、文件上传工具类等 ├── config # Spring Security配置、跨域配置、文件上传配置 ├── controller # 接口层 ├── entity # 数据库实体类 ├── mapper # MyBatis-Plus Mapper接口 ├── service # 业务逻辑层 │ └── impl ├── dto # 前端请求参数封装对象 └── vo # 返回给前端的视图对象这样的分包逻辑清晰各层职责分明。特别说一下dto和vo的区分——很多新手习惯直接拿entity对象接收前端请求我建议养成用DTO接收参数、用VO返回数据的习惯。因为实体类的字段往往包含password、create_time这些不需要暴露给前端的字段直接用实体类容易造成敏感信息泄露。在用户登录接口我就是建了一个LoginVO只返回token、用户名、角色这些必要信息绝不会把密码字段带出去。4.2 JWT登录鉴权的完整链路从一次登录请求说起登录鉴权我用了Spring Security JWT的组合。Spring Security负责拦截请求和密码校验JWT负责无状态的Token生成与校验。整体链路如下第一步用户发起登录请求前端把username和password以JSON格式POST到/api/auth/login。后端AuthController.login()接收请求调用AuthService.login()。第二步密码校验在AuthService中我使用AuthenticationManager调用UserDetailsService加载用户信息再用BCryptPasswordEncoder.matches()比对密码。这段核心代码如下Service public class AuthServiceImpl implements AuthService { Autowired private AuthenticationManager authenticationManager; Autowired private UserDetailsService userDetailsService; Autowired private JwtUtils jwtUtils; Override public LoginVO login(LoginDTO loginDTO) { // 1. 校验用户名密码 Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginDTO.getUsername(), loginDTO.getPassword() ) ); // 2. 加载用户详情 LoginUser loginUser (LoginUser) userDetailsService .loadUserByUsername(loginDTO.getUsername()); // 3. 检查账号状态 if (loginUser.getUser().getStatus() ! 1) { throw new BusinessException(账号未激活或被禁用请联系管理员); } // 4. 生成JWT Token String token jwtUtils.generateToken(loginUser); LoginVO loginVO new LoginVO(); loginVO.setToken(token); loginVO.setUsername(loginUser.getUsername()); loginVO.setRole(loginUser.getUser().getRole()); return loginVO; } }第三步JWT Token的生成与解析JWT的结构由Header、Payload、Signature三部分组成。我用io.jsonwebtoken:jjwt库来生成和解析核心配置放在application.yml中jwt: secret: your-256-bit-secret-key-change-in-production expiration: 86400000 # 24小时单位毫秒generateToken方法把用户ID和用户名作为Payload中的自定义字段public String generateToken(LoginUser loginUser) { MapString, Object claims new HashMap(); claims.put(userId, loginUser.getUser().getId()); claims.put(username, loginUser.getUsername()); Date now new Date(); return Jwts.builder() .setClaims(claims) .setSubject(loginUser.getUsername()) .setIssuedAt(now) .setExpiration(new Date(now.getTime() expiration)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); }第四步Spring Security过滤器链校验请求每次前端请求会携带Authorization: Bearer token头。我在SecurityConfig中注册了JwtAuthenticationTokenFilter它继承OncePerRequestFilter在每个请求到达Controller之前执行token解析和用户认证public class JwtAuthenticationTokenFilter 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 )) { token token.substring(7); String username jwtUtils.getUsernameFromToken(token); if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { UserDetails userDetails userDetailsService.loadUserByUsername(username); if (jwtUtils.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authToken new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } } filterChain.doFilter(request, response); } }代码逻辑不复杂关键点是通过继承OncePerRequestFilter确保每个请求只执行一次过滤校验通过后把认证信息放入SecurityContextHolder这样Controller里通过AuthenticationPrincipal就能拿到当前登录用户。这里有一个新手很容易踩的坑Spring Security 5.7之后WebSecurityConfigurerAdapter被废弃了。如果你用的是新版Spring Boot 2.7应该采用SecurityFilterChain的Bean方式配置否则会启动报错。我使用的是基于组件注册的方式配置如下Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/login, /api/auth/register).permitAll() .antMatchers(/api/upload/**).permitAll() .antMatchers(HttpMethod.GET, /api/project/**).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint); http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }4.3 创业项目申报的核心接口状态机流转是业务重头戏项目申报模块的接口有学生创建项目、提交初审、修改项目仅限未提交状态、查看自己的项目列表、查看审核进度管理员的审核接口评审老师的评分接口。学生的核心操作是创建项目和提交审核。在ProjectServiceImpl中我定义了几个关键方法。首先是创建项目草稿Override public Long createDraft(ProjectDTO projectDTO, Long userId) { ProjectInfo project new ProjectInfo(); BeanUtils.copyProperties(projectDTO, project); project.setLeaderId(userId); project.setLeaderName(userService.getById(userId).getRealName()); project.setStatus(0); // 待初审 projectMapper.insert(project); return project.getId(); }然后是提交初审涉及状态校验只有状态为待初审的项目才能提交进入管理员审核队列。具体判断如下Override public void submitForReview(Long projectId, Long userId) { ProjectInfo project projectMapper.selectById(projectId); if (project null) { throw new BusinessException(项目不存在); } if (!project.getLeaderId().equals(userId)) { throw new BusinessException(只能操作自己的项目); } if (project.getStatus() ! 0) { throw new BusinessException(当前状态不允许提交审核); } // 简单校验必填项 if (!StringUtils.hasText(project.getProjectName()) || !StringUtils.hasText(project.getProjectDesc())) { throw new BusinessException(请完善项目名称和项目简介后再提交); } project.setStatus(1); // 初审通过 project.setSubmitTime(new Date()); projectMapper.updateById(project); }这里的核心思想是用状态常量前置校验确保流程不错乱。比如一个已经评审通过的立项项目学生不可能再随手删掉或者改内容一个还在初审中的项目评审老师也看不着。每个状态变更之前都要检查当前状态是否合法保证了数据的完整性和可追溯性。管理员的审核接口也类似管理员看到待初审项目列表点击通过/驳回。如果是驳回必须填写原因系统通过短信或站内信我这边是站内信通知学生。评审老师评分后总分实时更新到项目记录上。5. 前端Vue页面落地从路由到组件再到接口联调前端部分我采用Vue 2 Element UI实现。这一节把工程结构、路由设计、核心页面、Axios封装讲清楚这些都是实战中直接能抄的东西。5.1 前端工程目录组织views按模块划分布局清晰使用Vue CLI 5创建工程后我调整了目录结构src下面按功能模块划分src ├── api │ ├── auth.js # 登录注册相关接口 │ ├── project.js # 项目申报相关接口 │ ├── user.js # 用户管理相关接口 │ └── notice.js # 公告相关接口 ├── router │ └── index.js # 路由配置含动态路由 ├── store │ ├── index.js # Vuex入口 │ └── modules │ └── user.js # 用户状态管理 ├── views │ ├── login/index.vue # 登录页 │ ├── register/index.vue # 注册页 │ ├── student │ │ ├── project-list.vue # 我的申报项目列表 │ │ ├── project-edit.vue # 新建/编辑项目 │ │ └── project-detail.vue # 项目详情与审核进度 │ ├── teacher │ │ └── review-list.vue # 评审任务列表 │ ├── admin │ │ ├── project-audit.vue # 项目初审 │ │ ├── user-manage.vue # 用户管理 │ │ ├── notice-manage.vue # 公告管理 │ │ └── dashboard.vue # 数据看板 │ └── layout/index.vue # 主布局侧边栏顶栏内容区 ├── utils │ ├── request.js # Axios请求封装 │ └── auth.js # Token存储与读取 ├── App.vue └── main.js路由配置中前端通过Vue Router的全局前置守卫判断用户是否已登录router.beforeEach((to, from, next) { if (to.path /login || to.path /register) { if (getToken()) { next(/); } else { next(); } } else { if (!getToken()) { next(/login); } else { next(); } } });5.2 Axios封装与请求拦截统一的错误提示是省事神器跨域和接口调用是前端最容易出问题的环节。我统一封装了utils/request.js基于Axios实例。核心逻辑如下import axios from axios; import { Message } from element-ui; import { getToken, removeToken } from ./auth; const service axios.create({ baseURL: /api, timeout: 10000 }); // 请求拦截器附加Token service.interceptors.request.use(config { const token getToken(); if (token) { config.headers[Authorization] Bearer token; } return config; }, error { return Promise.reject(error); }); // 响应拦截器统一处理返回结果与异常 service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res; } else { Message.error(res.message || 请求失败); return Promise.reject(new Error(res.message || 请求失败)); } }, error { if (error.response error.response.status 401) { // 处理Token过期 removeToken(); router.push(/login); Message.error(登录状态已过期请重新登录); } else { Message.error(网络异常请稍后重试); } return Promise.reject(error); } );这里有个很重要的约定后端统一返回Result对象格式为{code: 200, message: success, data: {...}}。前端每个模块的API接口只需要写请求URL和参数不用关心各自的返回格式如何处理拦截器直接把data解出来。跨域问题我用两种方式双保险解决后端在WebConfig中配置了CorsRegistry允许跨域同时前端在vue.config.js中配置了devServer.proxy把/api代理到后端http://localhost:8080。开发环境用代理生产环境用Nginx反向代理这样线上部署也不会被跨域卡脖子。5.3 核心页面组件项目编辑表单和数据看板学生端的项目编辑表单是整个系统交互最复杂的页面。包含项目名称、项目类别下拉选择、项目简介、团队成员、指导老师信息、文件上传等字段。这里说三个容易踩坑的点第一个是文件上传组件的回显与提交。Element UI的el-upload组件默认是选择文件后立即调用action地址上传但我们的业务诉求是先随表单一起提交。我在提交按钮的handleSubmit方法中使用FormData拼接所有字段和文件对象const formData new FormData(); formData.append(projectName, this.form.projectName); formData.append(projectCategory, this.form.projectCategory); formData.append(projectDesc, this.form.projectDesc); // ... 其他字段 if (this.file) { formData.append(file, this.file.raw); }后端接口用RequestParam(file) MultipartFile file接收文件上传成功后返回文件路径相对路径写入数据库。第二个是状态的按钮条件渲染。列表页操作列要根据项目的状态决定显示哪些按钮。例如状态为待初审时显示编辑和提交审核状态为初审通过时显示查看进度状态为已驳回时显示修改并重新提交。用Element UI的el-table-column配合template插槽和v-if指令进行判断逻辑清楚且交互友好。第三个是数据看板的实现。统计功能我用后端返回JSON前端通过ECharts渲染图表。后端写一个统计SQL按照项目类别和学院分组统计数量。比如我用了MyBatis-Plus的selectMaps方法返回自定义统计结果Override public ListMapString, Object countByCategory() { QueryWrapperProjectInfo wrapper new QueryWrapper(); wrapper.select(project_category, COUNT(*) as total); wrapper.groupBy(project_category); return projectMapper.selectMaps(wrapper); }前端拿到数据后直接填充ECharts的Pie图3分钟搞定一个统计图表。这里额外提醒统计SQL必须经过真实数据测试不同MySQL版本的COUNT别名和返回字段大小写可能不同最好在控制台打印SQL确认无误后再对接前端。6. 打包部署与常见坑本地跑通到服务器上线的完整记录很多同学的毕设止步于本地能跑。但答辩老师极有可能现场问你这个系统部署到服务器了吗生产环境怎么配置所以把项目部署到云服务器最低配的轻量服务器即可绝对是加分项。这一节把打包配置、Nginx部署、踩坑记录完整呈现。6.1 前后端打包的关键配置别再为static目录资源缺失背锅后端打包相对简单在pom.xml配置好Maven打包插件后直接跑mvn clean package即可生成可执行jar包。需要留意的是Spring Boot Maven插件版本要和Spring Boot版本匹配且要确认mainClass指定正确。前端打包要用npm run build默认生成dist目录。这里最常见的坑是静态资源路径不对。Vue CLI默认的publicPath是/如果你打算把前端打包后的静态文件放在后端jar同级的目录下由后端统一提供服务就需要设置相对路径// vue.config.js module.exports { publicPath: process.env.NODE_ENV production ? ./ : /, outputDir: dist, devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };使用相对路径./可以保证打包后的静态资源js/css文件在子路径下也能正常加载避免把前端服务挂在Nginx根路径之外时出现白屏。6.2 部署环境准备从零到上线只差这几步我购买的是一台2核2G的轻量云服务器系统选择Ubuntu 22.04 LTS。部署步骤可以整理成如下清单安装JDK 8或以上版本apt install openjdk-11-jdk确认java -version正常安装MySQL 8.0apt install mysql-server初始化数据库并导入SQL脚本安装Nginxapt install nginx配置反向代理上传后端jar包用nohup java -jar cie-server.jar --spring.profiles.activeprod app.log 21 启动上传前端dist目录到/var/www/cieNginx配置指向该目录并代理API请求Nginx配置关键片段server { listen 80; server_name your-domain.com; root /var/www/cie; index index.html; location / { 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /upload/ { alias /opt/cie/upload/; } }这里try_files $uri $uri/ /index.html;是SPA路由的关键——前端路由跳转时刷新页面Nginx会尝试匹配静态文件找不到就回到index.html由前端路由接管。如果不加这行刷新页面会出现404。6.3 我踩过的坑部署阶段最折腾的五个问题第一个坑MySQL 8.0的时区问题。启动后端时报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。解决方法是连接字符串加serverTimezoneAsia/Shanghai或者在MySQL执行set global time_zone 8:00。第二个坑数据库字符集排序规则。如果建表时没有指定utf8mb4插入中文就会变乱码。我重新创建数据库时统一指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci彻底解决。第三个坑前端devServer代理与后端CORS重复配置。开发阶段因为代理已经生效后端如果再配置宽松的CORS会导致接口出现OPTIONS预检请求频繁报错。我最后把后端的CORS配置关闭只保留前端代理干净利落。第四个坑jar包内置Tomcat的Spring Boot版本与JDK版本不匹配。我用JDK 11编译Spring Boot 2.7在服务器上安装了JDK 17结果启动失败提示Class版本错误。检查后发现是JDK版本过高导致服务器上改为JDK 11后正常。第五个坑Nginx上传文件大小限制。上传创业计划书PDF时报413 Request Entity Too Large。需要在Nginx的http块中添加client_max_body_size 20m;然后重启Nginx。7. 写在最后一些经验之外的建议项目做完之后回看这段经历最深的体会是毕业设计真正考验的不是技术有多深而是你在一个明确的范围内把完整闭环做出来的能力。你可能也会遇到需求不明、环境崩溃、联调失败、组件报错等各种问题——这些都正常关键是记录下每个问题的解决过程因为你的毕业论文和答辩PPT本质上就是这些踩坑记录的升华版。有两个小建议送给正在做毕设的同学。第一代码提交一定要用Git最好托管到私有仓库。我在开发过程中经历了两次差点毁掉工程的改动全靠Git回滚才保住进展。用git log --oneline复盘每天的开发进度其实也是毕业论文里工作进度安排部分最可信的素材。第二提前准备演示数据不要用空数据库答辩。我在系统里录入了20个学生账号、5个评审老师账号、30条项目申报数据、10条通知公告覆盖了所有状态。这样无论老师想看哪个页面都能看到真实且有内容的数据。对于一个信息管理系统来说有数据和不有数据演示效果是两个级别的。如果你要照着类似的思路做一套系统最核心的学习路径是先把Spring Boot的登录鉴权链路彻底搞懂再把Vue的请求封装和路由守卫弄明白然后把状态流转这个业务逻辑吃透。剩下的无非是加法——多一个模块、多一张表、多一组接口而已。希望这篇记录能帮你把路走得更顺一些。
返回列表