资讯详情

资讯详情

考研互助平台全栈开发:SpringBoot+Vue+MyBatis从设计到部署

1. 项目整体设计与技术选型做考研互助交流平台这个项目我最初的想法其实很简单考研人群的需求非常集中无非是找资料、找学长学姐答疑、看经验分享、找研友一起打卡。但这些需求目前散落在各类论坛、QQ群、公众号里信息非常割裂。QQ群里翻聊天记录找资料效率低到令人抓狂公众号里的经验贴经常被删论坛上问答又半天没人回复。把这些核心场景集中到一个平台上来就是一个考研互助交流平台最朴素的出发点。从标题上就能看出这套系统用了SpringBootVue作为前后端主框架数据层是MySQLMyBatis。这不是随便搭配的。SpringBoot负责后端服务的快速构建开发效率高一套标准的RESTful API下来非常省事Vue负责前端的页面渲染和交互配合Element Plus这类组件库后台管理页面和数据展示页面的开发速度能提升不少MySQL作为最普及的关系型数据库处理用户、帖子、资料这些结构化数据绰绰有余MyBatis则负责数据库访问层它的灵活SQL控制力和与Java实体类的直接映射比那些自动生成SQL的框架更适合这种业务逻辑多变的项目。这个项目的定位是“设计与实现”所以从毕业设计或者全栈练手的角度看这四件套的组合可以说覆盖了现阶段国内高校和初级开发者最常接触的技术栈要求。整套系统需要实现用户登录注册、资料上传下载、社区发帖与评论、问答互动、管理员后台等模块属于典型的“中小规模全栈应用”。1.1 从“互助交流”这个需求倒推系统功能考研互助交流平台的核心不是做个论坛而是把“互助”和“交流”落到实际使用场景里。我在设计功能时先把用户分成了两类普通学生用户和管理员。学生用户的行为路径无非是注册登录、浏览资料、下载资料、发帖求助、回帖答疑、收藏感兴趣的帖子管理员负责用户管理、内容审核、分类维护、数据统计。这也是这类系统最常见的角色权限划分。围绕用户行为路径我拆出了几个核心模块用户模块注册、登录、个人信息修改、密码重置、头像上传资料模块考研资料的分类上传、详情展示、下载记录、后台审核帖子模块经验分享帖的发布、编辑、删除多条件检索评论与点赞模块对帖子进行评论回复帖子点赞问答模块提问、回答、采纳最佳答案后台管理模块用户状态管理、帖子审核、资料审核、数据看板模块拆到这个粒度前后端的开发量刚好控制在一个合理范围内又能完整跑通一条业务链路。这里需要特别说一句很多初学者在确定功能时容易犯一个毛病——想要的功能特别多结果开发到后面连核心链路都没完成。好的做法是先保证主线功能的完整流畅比如用户发帖到审核到评论展示这条路能走通再去扩展边角功能。1.2 技术栈组合为什么是SpringBootVueMySQLMyBatis这四件套的组合我做了好几轮的选型对比最后敲定下来是因为它们正好覆盖了全栈开发的完整链路而且每一环都有足够的社区资源支持。SpringBoot的好处不用多说它省掉了Spring MVC时代大量的XML配置。不过这里要提醒一句SpringBoot 2.7.x和3.x之间的取舍很关键。如果学生党还在用JDK8老老实实选2.7.x版本别追新。SpringBoot 3.0强制要求JDK17而且部分第三方starter对3.x的适配还不完善万一后续集成某一个组件出现兼容性问题排查起来非常消耗精力。我实测下来2.7.x配JDK8几乎不会踩版本坑。Vue这边我选择的是Vue 3配合Vite构建。Vite的冷启动速度和热更新体验比webpack时代的Vue CLI强很多对开发调试是实打实的效率提升。组件库用Element Plus它提供了一整套现成的表格、表单、弹窗组件后台管理页面基本不需要自己写CSS布局这对前端基础比较薄弱的开发者非常友好。但要注意Vue 3的响应式原理和Options API写法都跟Vue 2不完全一样如果你之前学的是Vue 2建议先把setup语法和组合式API快速过一遍再动手。MySQL选择了8.0版本这也是当前数据库的主流分支。MyBatis虽然没有MyBatis-Plus那么“懒人”但优点恰恰在于SQL完全自己掌控写出什么样的SQL就执行什么样的SQL排查慢查询时看得明明白白。实际项目里我还在MyBatis工具类配置上重点加了驼峰映射和SQL日志打印这两个配置对于调试阶段影响很大。另外热点词里提到TypeHandler、缓存这些点在这个项目里虽然没有复杂到用自定义TypeHandler但理解MyBatis是怎么把Java对象和数据库字段做转换的对理解整个数据层的工作流程很有帮助。版本参考建议如下组件推荐版本说明JDK1.8匹配SpringBoot 2.7.x兼容性最好SpringBoot2.7.x避开3.x的JDK17门槛MySQL8.0主流稳定Navicat连接记得配置SSL关闭Node.js16.x或18.x适配Vite构建要求Vue3.x配合Vite Element PlusMyBatis3.5.xSpringBoot 2.7.x自动管理版本这个组合还有一个隐性优势它和主流招聘JD里“熟悉SpringBoot/MyBatis/Vue”正好对上。做完这个项目你对Mapper接口开发、动态SQL、Vue组件通信、axios请求封装这些面试高频考点的理解会比简历上写“了解”要扎实得多。2. 数据库设计与核心业务表数据库是一套信息管理系统的地基。业务纠缠不清往往第一步就是表设计出了问题。考研互助交流平台涉及的数据对象比较清晰用户、帖子、评论、资料、点赞关系。但在表设计阶段还是有几个容易忽略的点我详细展开一下。2.1 数据库设计思路与ER关系梳理建表之前我建议先在纸上把业务关系捋一遍这一遍省下来后面重构表的成本就高了。这个项目的核心数据关系大致是这样用户在平台里有两重身份关系普通用户和管理员。用户与帖子是发布关系一个用户可以发多个帖子一个帖子属于一个用户。用户与评论是发布与被评论关系。帖子与评论是一对多关系。用户与资料的关联表现在上传资料和下载资料。点赞关系比较特殊它本质上是用户与帖子之间的多对多关系需要一个中间表来解耦。这里要特别提醒多对多关系的处理。如果直接在主表里用一个字段去存点赞用户ID列表比如用逗号分隔的字符串当时写起来确实省事但是一旦需要统计某个用户都给哪些帖子点过赞、或者做“取消点赞”这类逆操作时SQL写起来很别扭正确姿势是单独建一张中间表用复合唯一索引控制重复点赞。2.2 关键表结构设计与字段说明用户表是系统的基础我设计了以下核心字段CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名/学号, password varchar(100) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, email varchar(100) DEFAULT NULL COMMENT 邮箱, role tinyint(4) NOT NULL DEFAULT 0 COMMENT 角色0学生1管理员, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1正常0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码字段长度建议设置为100因为你大概率会用BCrypt加密BCrypt生成的结果是60个字符左右的定长字符串留点余量避免某次加密策略升级后字段存不下。用户名加唯一索引这是登录的检索条件命中唯一索引的效率比全表扫描高一个数量级。帖子表的结构需要覆盖“经验分享”和“问答求助”两种场景。我用了type字段做区分考研经验帖可以设置category字段分类到政治、英语、数学、专业课等标签下CREATE TABLE post ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 发帖人ID, title varchar(200) NOT NULL COMMENT 帖子标题, content text COMMENT 帖子正文, type tinyint(4) NOT NULL DEFAULT 0 COMMENT 帖子类型0分享帖1求助帖, category varchar(50) DEFAULT NULL COMMENT 分类政治/英语/数学/专业课等, view_count int(11) NOT NULL DEFAULT 0 COMMENT 浏览量, like_count int(11) NOT NULL DEFAULT 0 COMMENT 点赞数, comment_count int(11) NOT NULL DEFAULT 0 COMMENT 评论数, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态0待审核1已发布2已屏蔽, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;content字段用text类型而不是varchar原因很简单经验帖或者求助帖的正文长度可能超过varchar的默认上限。另外几乎所有统计类操作都离不开创建时间主表必须有一律的create_time字段。这也是我复盘很多学生项目时发现最普遍的问题——列设计少、字段类型抠抠搜搜结果跑起来才发现不够用。评论表相对简单但一个关键点在于需要用parent_id支持评论的回复嵌套。如果只做一级评论处理起来轻松得多但用户很容易在前端界面上看到某楼层的“回复”操作如果表结构没有预留parent_id后面要加就非常痛苦。资料表用于承载考研资料上传、审核、下载字段设计时要为“文件类型”做区分比如PDF、Word、视频链接。视频播放这个场景有必要多说一句很多网课资料是以m3u8格式分片的如果平台往后要支持在线播放建议在资料表预留一个video_url字段前端用vue插件只做兜底目前比较通用的方案是hls.js或者flv.js做播放这些都是免费开源库不需要特殊处理往后扩展时很方便。完整表结构可以根据上面的思路在Navicat里一次性建好表再逐步调整。2.3 为什么按这个顺序建索引索引设计不是建得越多越好。我给所有表的create_time都建了索引因为后台管理页面基本都要按时间倒序展示这个字段是一个天然的排序值。帖子表的category字段建索引的原因是前台首页会有分类筛选操作这个字段的选择性算不上特别高但加上索引后分类搜索的响应速度有明显改善。反过来像status这种只有两三个取值的字段就不适合单独建索引了选择性太低走了索引反而不如全表扫描快。这一点是最容易在MySQL面试题里翻车的细节——面试官很喜欢问“索引失效场景”和“低选择性索引值不值得建”实操中踩过一次就知道为什么拿status做索引列是下策。3. 后端实现要点与关键代码后端架构我采用的是经典的三层结构Controller接收请求、Service处理业务逻辑、Mapper操作数据库。项目包名按技术分层来组织controller、service、mapper、entity、dto、config、common各司其职。这种结构看着中规中矩但非常稳定适合中小团队协作和后续维护。3.1 认证授权方案为什么用JWT而不是Session登录认证是管理系统的第一道门。我见过不少初学者直接用Session存储用户状态SpringBoot里引入Servlet依赖后往session里set一个user对象拦截器里getSession来判断登录状态。这个方案在单体项目里能跑但有一个很现实的问题前后端分离场景下Session的跨域处理和集群共享非常麻烦而且对移动端调用API的场景天然不友好。我最终选择的是JWT自包含令牌方案。JWT的本质是把用户身份信息经过签名加密后生成一段字符串服务端不保存会话状态后端只需要在拦截器里校验令牌的签名和过期时间就能确认当前请求的用户身份。这样做的好处至少有三个服务端无状态部署时可以随意横向扩展前后端彻底解耦同一个后端可以同时服务Web端和后续可能的App端令牌本身可以携带少量非敏感信息减少数据库查询频率。JWT的工具类核心代码如下public class JwtUtil { // 密钥实际项目中建议放在配置文件中不要硬编码 private static final String SECRET_KEY your-secret-key; // 过期时间24小时 private static final long EXPIRE_TIME 24 * 60 * 60 * 1000; public static String generateToken(Long userId, String username, Integer role) { Date now new Date(); Date expireDate new Date(now.getTime() EXPIRE_TIME); return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); } }拦截器里对白名单路径放行比如登录注册接口、首页的帖子列表接口其余接口一律校验token。令牌从请求头的Authorization字段中取格式为“Bearer xxxxx”取出后去掉“Bearer ”前缀再解析。如果解析失败或者过期返回401状态码和一个统一的JSON结构前端收到401后自动跳转到登录页。整体链路非常简单但足够支撑系统前期的安全需求。这里给出一个关键提示将密钥硬编码在代码里只能用于演示项目。如果是想真正部署上线的产品密钥要放到应用配置文件里并通过环境变量动态注入否则代码一泄露别人的恶意用户自己给自己签发token瞬间就能拿到管理员权限。这个风险极其真实不要心存侥幸。3.2 统一返回结果与全局异常处理前后端分离开发时要约定一套统一的数据响应格式。我定义的返回结构包含四个字段code状态码、message提示信息、data数据主体、timestamp时间戳。成功时code为200业务异常时code分别为400、401、403、500等。这层设计看起来占不了几行代码但能省去前端大量繁琐的异常判断逻辑。全局异常处理我用了RestControllerAdvice把所有可能抛出的异常统一拦截避免异常堆栈直接返回给前端。这里有个细节不要把所有异常都笼统地返回“服务器错误”最好细分出业务异常类。我在项目中自定义了一个BusinessException比如资料不存在、帖子已删除、参数校验失败等场景主动抛出由全局处理器捕获后返回对应的错误提示。这样前端能直接拿到具体原因并展示给用户而不是看到一个莫名其妙的500。RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error(系统异常, e); return Result.error(500, 系统异常请稍后重试); } }在实际项目中这个全局异常处理的能见度很高。前端拿到“系统异常”这类通用提示时至少不会让用户体验崩塌。3.3 MyBatis动态SQL与核心SQL优化MyBatis的看家本领是动态SQL这个项目里最典型的应用场景是帖子列表的筛选条件。前端传过来可能带分类、带关键词、带时间范围甚至可能什么都不传后端需要根据条件动态拼接SQL。用MyBatis的where标签和if判断写起来非常顺畅完全不需要手动拼接字符串。select idselectPostList resultTypecom.example.entity.Post SELECT * FROM post where if testcategory ! null and category ! AND category #{category} /if if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR content LIKE CONCAT(%, #{keyword}, %)) /if if testtype ! null AND type #{type} /if AND status 1 /where ORDER BY create_time DESC /select注意几个细节。status 1是硬性条件因为前台只能看到审核通过的帖子所以放到where里而不是if里。模糊查询用CONCAT拼接百分号这是一个很常见的MySQL面试题考点——用#{keyword}而不是%${keyword}%可以有效避免SQL注入。ORDER BY create_time DESC直接写在SQL里让数据库层完成排序不要在前端通过for循环排序。POST列表接口用了PageHelper分页插件并明确每页最多20条防止一次拉取全表数据拖垮数据库。关于MyBatis的resultType与resultMap选择字段名和Java属性名完全一致用resultType就够了SpringBoot配置里开启下划线转驼峰数据库字段create_time自动映射成createTime属性。但如果遇到多表关联查询比如帖子列表要关联查出发布人的昵称和头像resultMap更合适可以明确每个字段的映射关系避免出现“列名找不到”这种低级报错。3.4 文件上传与下载的实现细节资料模块必然涉及到文件上传。我的实现思路是上传接口接收MultipartFile先将文件写入服务器本地磁盘目录再将文件访问路径存入资料表。文件存储路径不要直接用用户上传的原始文件名保存到数据库因为存在重名覆盖风险。处理方案很简单用UUID重新生成文件名扩展名保留。public String uploadFile(MultipartFile file) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; String filePath uploadPath / newFileName; file.transferTo(new File(filePath)); return /files/ newFileName; }这里有一个我在真实项目中踩过的坑file.transferTo()方法在文件较大时如果目标路径不存在或者服务器的/tmp目录空间被占满会抛异常。解决的方案是非常简单的——在保存前先判断目录是否存在不存在则先创建。生产环境上传大文件时服务器静态文件目录根路径最好大到足够避开系统盘空间不足的问题另外用定时任务定期清理无主文件也是一个值得考虑的后续扩展点。4. 前端实现要点与前后端联调前端工程我用Vite从零搭建的Vue 3项目配合Element Plus做UI组件Pinia做用户状态管理。项目标题里只写了Vue但实际开发中如果连状态管理工具都没有复杂度一上来组件间通信就会变得混乱。4.1 前端工程结构与页面规划前端目录结构大致如下views路由对应的页面组件比如login、register、home、postDetail、postEdit、admin、userManage等router路由配置文件和权限守卫storePinia的store定义目前只需要一个userStore管理登录态apiaxios实例封装和各模块的接口调用函数components可复用组件比如帖子卡片、评论列表、分页组件路由配置是前端的骨架。我定义了两种路由不登录也能访问的公共路由和必须携带合法token才能访问的受保护路由。这是一层非常关键的前端权限控制和后端的JWT校验共同形成纵深防御体系不能再走回“前端隐藏按钮就当没人看得到”的老路。const routes [ { path: /login, component: Login }, { path: /register, component: Register }, { path: /, component: Home }, { path: /post/:id, component: PostDetail }, { path: /admin, component: AdminLayout, meta: { requiresAuth: true, requiresAdmin: true } } ]Element Plus最大的价值在于后台管理模块数据表格用el-table配一个el-pagination分页组件再套一个el-dialog做新增编辑弹窗三个组件的组合就能覆盖后台管理80%的功能页面。做这类管理系统定位就是“快速搭建、功能规范”不要花大量时间去手写复杂样式。4.2 路由守卫与用户状态管理路由守卫在Vue Router 4中是通过beforeEach实现的。每次路由跳转前先检查目标路由是否标记了requiresAuth如果标记了且当前没有登录态就跳转到登录页并带上redirect参数。管理员路由多一个角色判断非管理员直接跳首页。用户状态管理我放在Pinia的userStore中登录成功后把token保存到localStorage同时把用户基本信息存一份到store中。刷新页面时路由守卫会重新触发此时从localStorage读取token再调用一次获取用户信息的接口恢复登录态。这里要注意刷新应用时header里已经有token了所以不要把token存到Pinia里当作唯一登录依据否则一刷新就丢了。axios请求拦截器是联调阶段的核心。我在axios实例的request拦截器里统一设置Authorization头response拦截器里统一处理错误码。401时清除本地token跳转到登录页。这个拦截器逻辑如果不在最初就搭好后面开发时每个请求都要重复写登录校验代码将会非常痛苦。service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(new Error(res.message)); } return res; }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(error); } );4.3 跨域问题与Vite代理配置前后端分离开发时跨域问题几乎必现。前端跑在localhost:5173后端跑在localhost:8080端口不同跨域就出现了。这里要用一个很顺手的方案就是在Vite devServer里配置代理把所有/api开头的请求转发到后端地址server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里的请求地址全部写/api/user/login不会出现http://localhost:8080/api/user/login这种带完整域名的写法。代理解决了开发阶段的跨域生产环境部署时前后端通常放在同一个域下或者用Nginx配置反向代理跨域问题就不存在了。这个方案比在后端写CrossOrigin注解去放开某个Controller要稳得多。因为CrossOrigin只能解决局部接口的跨域而且一旦放开全部接口又等于给攻击者打开了一个可以提交跨域请求的合法口子。安全第一原则下能够用服务端代理的对开发才是舒服的。5. 常见问题与排查经验实录这是我从搭建到调通整个项目过程中踩过的最典型的坑。写下来的时候我对这些坑的印象已经很深刻了——如果你也在独立开发的过程中遇到了类似问题可以立刻对照定位。5.1 高频问题速查表问题现象根本原因解决方案前端请求接口报跨域错误前后端端口不一致且未配置代理Vite或Nginx配置代理转发去掉后端单个接口的CrossOrigin满开方案登录接口能通但登录后马上401token未存入localStorage或请求头拼接格式错误确认请求拦截器正确设置Authorization: Bearer xxx数据库中文全部变成问号连接串未配置useUnicodetruecharacterEncodingutf8JDBC URL增加utf8参数数据库表指定utf8mb4上传图片后网页显示404上传路径与静态资源映射路径不一致SpringBoot配置静态资源映射/files/**到物理路径MyBatis查询结果返回null实体类属性名与数据库列名在驼峰转换上不匹配开启map-underscore-to-camel-case: true或改用resultMap分页数据只有一页PageHelper依赖冲突或使用方式错误确认引入的pagehelper-spring-boot-starter版本页码从1开始MySQL连接报SSL错误MySQL 8默认使用SSL本地未配置JDBC URL追加useSSLfalseallowPublicKeyRetrievaltrue前端打包后访问后端404打包文件未放对位置或后端路由未配置将dist目录内容复制到SpringBoot的static目录下速查表里最实用一条的当属允许公钥检索参数allowPublicKeyRetrievaltrue因为MySQL 8在本地连接时确实有一个公钥检索环节如果不配这个参数直接报错。很多新手折腾半天以为是密码错了实际跟密码毫无关系。5.2 一次典型的“数据查不出来”排查记录有一次测试人员在后台管理页面反馈筛选“待审核”帖子时列表一直为空。我当时的第一反应是SQL条件写错了因为前台按状态1已发布查询是正常的。打开控制台发现请求返回的data确实是空数组说明后台接口执行成功了问题大概率出在SQL条件上。打开MyBatis的日志打印配置看到实际执行的SQL后立刻找到了原因MyBatis的where标签中我写了status #{status}但后台传参时status字段名写成了postStatus实体类属性名不匹配MyBatis传入了null导致拼出来的SQL是WHERE status null直接查不到记录。这里其实涉及一个MyBatis小知识点——if标签判断的是Java属性非空不会自动把属性名映射成数据库列名。前后端参数名不统一是联调问题的一大来源从头对一次参数名很多时候就能避免这种查不到数据的假故障。这个排查过程前后只花了七八分钟但如果没有日志打印纯靠肉眼审查代码可能得折腾半小时以上。5.3 避免后端SQL性能隐患的技巧项目是毕设或练手的规模不需要过度优化但有两点应该在开发过程中就养成习惯。第一列表查询接口不要直接用SELECT *。帖子内容字段用的是text类型如果把正文全部查询出来列表页只需要展示标题和摘要等于白查了一大堆数据。用SELECT id, title, category, view_count, like_count, comment_count, create_time这样只取必要字段MySQL能把加载到内存的数据量压缩到一个很可观的水平。第二必须创建必要索引。帖子表按user_id查询时没有索引就是全表扫描。测试数据量少时感觉不出来一旦生产环境的帖子量级上来慢查询日志里几乎每天都会出现这类语句。这也是MySQL面试中反复提到的“索引最左前缀”和“索引失效场景”真正的体会得在自己项目出过慢查询后才会深刻。6. 项目部署与扩展建议项目开发完成只是第一步能不能让同学或者测试人员真正访问到取决于部署环节顺不顺利。6.1 前端打包与后端本地启动流程前端执行npm run build之后dist目录就是编译产物。有两种部署上线的路径可选独立部署和合并部署。独立部署是前端dist的静态文件丢到Nginx的html目录Nginx配置一个location /api请求转发到Java后端合并部署更简单把dist目录下所有文件直接复制到SpringBoot项目的src/main/resources/static目录然后打包成一个Jar包运行。合并部署对毕设演示来说非常省事因为只需要启动一个进程不需要额外安装Nginx。前面提到的Vite代理是解决开发环境跨域到了合并部署阶段就没有跨域问题了因为静态资源和API接口在同源下。有一点一定要调整开发环境的前端请求地址写的是/api开头而后端接口的Controller路径如果不带这个前缀合并部署后就要通过Nginx做rewrite或者统一映射否则404。6.2 从毕设到上线需要考虑的增强如果想让这套系统更有竞争力我建议从下面三个方向中的任一个切入都会让思路完整度上一个台阶。智能推荐把帖子表和资料表的浏览记录、下载记录汇总用协同过滤算法给用户推荐可能感兴趣的考研内容算法不一定要复杂基于标签的统计推荐就能有不错的效果。数据看板后台管理加几个ECharts图表展示每日新增用户数、帖子发布趋势、热门分类占比技术含量不高但是产品完成度提升非常明显。团队协作与自动化部署用Git管理代码前后端仓库分开配置CI/CD流程提交代码后自动构建和部署到测试环境。这一套流程写进简历在求职面试时的加分效果非常明显。根据我做这类全栈项目的经验把基础链路跑通最要花心思的永远不是某一个单独的组件而是跨端问题的排查。前后端联调时如果某个接口的数据和预期不一致先打开浏览器F12看Network里的具体request和response再定位到后端日志这个方法能高效解决掉的联调问题占了整个项目的绝大部分。最后再分享一个我调试阶段的实际体会开发这种全栈项目时最好从“最小可用闭环”起步比如第一个迭代就做“用户注册→登录→发帖→帖子列表展示”把这四步跑通前后端的骨架就已经建立之后的模块都是在此基础上复制粘贴加修改。反过来如果一上来就去写完后台管理的所有页面再回过来调核心业务链路很容易在联调时发现后端接口和前端页面不适配推倒重来的代价会很高。这个顺序上的取舍说起来简单但真按这个思路执行的效率提升是很明显的。这套技术栈的组合方式无论未来是做毕业设计还是想往全栈开发的方向深入学习都可以作为第一步的完整参考后续再逐步加入Redis缓存、消息队列或者微服务相关内容扩展路径也足够清晰。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →