尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
前后端分离项目实战:基于SpringBoot+Vue的厨艺交流平台开发部署
前后端分离厨艺交流平台系统做下来我觉得最值得分享的还不是那一堆CRUD代码而是这套从需求拆解到技术选型、再到前后端联调和最终部署的完整链路。先说清楚这个项目是什么它本质上是一个以菜谱分享和用户互动为核心的社区型Web应用用户可以注册登录、发布包含步骤和图片的菜谱浏览首页推荐、按照分类或关键词搜索其他用户的作品然后点赞、收藏、评论和关注。技术组合是SpringBoot提供RESTful接口Vue负责页面交互MyBatis作为持久层框架操作MySQL数据库。前后端完全分离意味着后端只输出JSON数据前端页面通过Axios去请求这些接口两者通过定义清晰的API契约各干各的活。我写这篇文章的受众包括三类人正在做毕业设计或者课程项目的在校学生想练手前后端分离项目的Java开发新人以及想把一套完整系统从零部署上线、验证工程化流程的人。文章不会停留在“看代码能跑”的层面我会把每个模块为什么要这么设计、表结构为什么这么建、部署时哪些坑必须避开都讲透保证你照着做完不只是跑起来还能搞清楚背后的门道。1. 项目概述与方案选型背后的考量先聊点项目心得。厨艺交流平台这类社区系统在技术层面其实属于“经典CRUD 少量复杂查询”的业务形态——用户、菜谱、评论、收藏、点赞本质都是数据的增删改查。但恰恰是这种看似简单的业务最能检验一个开发者组织代码的能力。很多新人第一次做前后端分离就是从这种社区系统入手的因为业务足够熟悉、数据结构也直观不需要搞懂复杂的业务规则就能开始写代码。1.1 为什么选择前后端分离架构前后端分离这个词现在几乎成了Web开发的默认选项但如果你自己动手做一个完整项目就能体会到它到底解决了我什么问题。过去服务端渲染的年代前端页面是嵌在后端模板里的改一个按钮样式可能要碰Java代码前端工程师和后端工程师必须强耦合在一起开发。改成前后端分离之后后端只需要保证接口稳定返回JSON前端工程师甚至可以和后端并行开工只要先协商好接口文档。具体到厨艺交流平台这个项目分离架构的优势体现在几个方面。第一是接口复用同一个菜谱接口既可以被Web端调用以后如果做小程序或者移动App还能继续复用不需要重新写一套逻辑。第二是部署灵活前端是纯静态资源只要有Nginx就能托管后端是独立的Java进程两者可以分别扩缩容。第三是开发效率明显高前端用Vue的热更新开发改代码马上生效不用像以前那样整个服务重启。但我要提醒一句前后端分离不是银弹。如果项目只有几个页面、几乎没有交互用分离架构反而是给自己找麻烦光跨域问题和接口联调就能折腾半天。厨艺交流平台这种功能丰富、页面多、交互复杂的系统才真正适合分离架构。1.2 技术选型为什么是SpringBoot Vue MyBatis MySQL这套技术栈在国内的Spring生态里属于当之无愧的主力组合选它不是因为“大家都在用”而是每种技术都有明确的定位。SpringBoot解决的是后端开发中大量重复的配置问题比如搭建一个Web服务要配置的DispatcherServlet、内嵌Tomcat、数据源这些SpringBoot通过自动配置和Starter机制一键搞定我可以把精力集中在业务代码上。Vue是渐进式前端框架对新手极其友好单文件组件、响应式数据绑定、路由和状态管理都有完善的官方配套学起来比很多大型框架平滑。MyBatis是半自动化持久层框架SQL由开发者自己编写、自己控制这在项目里其实是个优点——我可以在SQL层面针对菜谱搜索这种复杂动态查询做精细调优而不是被框架自动生成的低效SQL绑架。MySQL不用多说开源稳定、资料海量对于社区交流平台这种读多写少、事务要求适中的业务完全够用。选型时也考虑过一些替代方案但都被我否决了。有人说持久层可以用Spring Data JPAHibernate自动建表写起来更快但JPA在复杂查询时需要写JPQL或者Criteria API学习成本反而高而且自动生成的SQL有时候让人心里没底MyBatis直接写SQL反而是这种项目最可控的选择。还有人说后端干脆用Node.js或者Flask但那意味着整个技术栈脱离Java生态对大部分做毕业设计或者走Java开发路线的人来说不划算。这套组合能让人在毕业后直接对接企业开发的主流技术栈这也是实用性的体现。1.3 系统能力范围和功能清单在动手写代码之前我建议先像这样把所有功能拉成一张清单做什么不做什么清清楚楚否则做着做着就会失控。厨艺交流平台第一版我规划了以下能力范围模块功能点优先级用户系统注册、登录、退出、个人信息维护、修改头像P0菜谱模块发布图文菜谱、编辑删除、查看详情、搜索筛选P0互动模块点赞、收藏、评论、关注用户P0社区首页菜谱广场列表、热门菜谱推荐、分类浏览P1个人中心我发布的菜谱、我的收藏、我的粉丝列表P1管理后台用户管理、内容审核、分类管理P2P0是第一版必须完成的P2可以后续迭代这样项目周期可控不会为了追求功能数量把质量搭进去。功能都拆清楚之后再去设计数据库表和接口心里就有谱了。2. 系统架构设计与核心模块拆解技术方案定了之后下一步是画清楚系统的整体架构和代码组织方式。前后端分离项目的架构核心就是依赖边界要清晰我从浏览器刷新页面那一刻开始说把一条完整的请求链路讲明白。2.1 完整调用链路和项目目录规划用户在浏览器里访问Vue页面看到的是纯静态资源——HTML、CSS、JavaScript。当用户点击“登录”按钮Vue的JavaScript逻辑会通过Axios发起一个HTTP请求指向后端的接口地址可能是http://localhost:8080/api/user/login。后端SpringBoot收到这个请求之后先由拦截器做身份鉴权如果是无需登录的接口然后路由到对应的Controller方法Controller调用Service层处理业务逻辑Service调用Mapper接口Mapper通过MyBatis框架去执行对应的SQL语句最终把结果一层层返回到前端。前端拿到JSON数据通过Vue的响应式系统更新页面状态用户看到的就是动态渲染后的界面。这个架构决定了我在搭建项目时要分成两个独立的工程目录cook-platform-backend放后端代码cook-platform-frontend放前端代码。后端采用标准的分层结构——controller、service、mapper、entity另外加一个common包存放公共类像统一返回结果、异常处理、JWT工具类。前端则采用Vue的标准结构——views存放页面组件、components存放可复用的子组件、router配置路由、store管理全局状态、api集中封装所有接口请求方法。这种结构最大的好处是任何人接手项目都能按照约定俗成的规矩快速找到自己想改的代码。2.2 数据库表结构设计思路数据库设计是厨艺平台项目里最值得花时间的一步。设计得好后面写SQL就是拼积木设计得差等代码写完再改表就是灾难。我第一版规划了六张核心表user用户表、recipe菜谱表、category分类表、comment评论表、favorite收藏表、like_record点赞记录表。用户和菜谱之间是典型的一对多关系一个用户可以发布多篇菜谱菜谱和收藏、评论、点赞之间都是一对多关系关注关系我单独用了一张follow表存储user_id和follow_user_id两个外键表示谁关注了谁。说几个设计细节。菜谱表的核心字段包括title标题、cover_image封面图、ingredients食材、steps制作步骤、tips小贴士、status状态草稿或已发布、view_count浏览量。像steps这种包含多个步骤序号和描述的内容很多人会想着建一张子表去存但步骤数量和结构都比较固定、几乎不会做跨步骤关联查询所以我直接用TEXT类型存JSON字符串这一步用JSON.stringify序列化进去读取时再反序列化就行省掉一大堆关联查询。点赞表我特意跟收藏表分开设计。两者的业务语义完全不同——收藏是持久性的今天收藏了明天可能还在点赞则允许取消取消之后记录应该删掉。如果混在同一张表里统计接口和去重逻辑都会很别扭分开是最干净的做法。另外这些表都要设置好索引recipe表的user_id和category_id建普通索引comment表的recipe_id建索引like_record和favorite表则建user_id recipe_id联合唯一索引防止同一用户对同一菜谱重复点赞或者重复收藏。2.3 RESTful接口规范设计前后端协作最关键的是接口契约。我推荐统一遵循RESTful风格设计接口语义清晰且贴近业界标准。用户的接口路径直接表明资源对象和操作方式操作请求方式接口路径说明用户注册POST/api/user/register提交账号密码用户登录POST/api/user/login返回JWT令牌获取用户信息GET/api/user/info需要鉴权发布菜谱POST/api/recipe需要鉴权菜谱列表GET/api/recipe/list支持分页搜索点赞菜谱POST/api/recipe/like需要鉴权收藏菜谱POST/api/recipe/favorite需要鉴权发表评论POST/api/comment需要鉴权这里还有一个后端开发容易忽视的规范统一返回格式。我在common包下定义了Result类里面包含code、message和data三个字段成功时code为200失败时根据场景返回400参数错误、401未登录、500服务器错误。所有接口都返回这个统一格式前端在处理响应时就只需要写一套逻辑不用每个接口单独适配省掉大量重复的异常处理代码。3. 后端核心实现与关键细节后端是系统的数据中心也是我花时间最多的部分。这一节从项目初始化讲到核心功能的实现细节尽量把每一步为什么要这样做说清楚。3.1 SpringBoot项目初始化和Maven依赖管理我用Spring Initializr初始化后端项目本质上就是去start.spring.io拉一个模板工程。创建时选择Java 8还是Java 11取决于本机JDK版本不过SpringBoot 2.7.x对应的JDK范围是8到17选择JDK 8兼容性最好用户量再大的部署环境都不会出问题。pom.xml里的依赖管理是整个后端项目的地基。我列一下第一版用到的核心依赖以及引入原因dependencies !-- Web服务核心依赖包含内嵌Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis与SpringBoot的集成启动器 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- JWT工具库用于生成和校验登录令牌 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies这里必须提醒一个版本坑MyBatis的Spring Boot Starter版本和SpringBoot版本需要匹配。如果你用的是SpringBoot 3.x那么mybatis-spring-boot-starter建议用3.0以上的版本否则启动时会因为类路径冲突直接报错。我在本地开发时用的SpringBoot 2.7.18配套MyBatis Starter 2.3.2实测稳定。配置文件application.yml里除了数据源配置还建议打开MyBatis的驼峰映射开关和SQL日志打印spring: datasource: url: jdbc:mysql://localhost:3306/cook_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplserverTimezoneAsia/Shanghai这个参数建议一定要加MySQL 8.0默认时区设置通常会导致Connection时区错误不加的话启动就会报。开启map-underscore-to-camel-case之后数据库里user_name这样的下划线字段就能自动映射到Java类的userName属性不用每个字段都写resultMap映射。3.2 用户登录与JWT鉴权机制用户登录模块是整个系统的安全入口实现方式要考虑好。我用的方案是JWTJSON Web Token登录成功后后端签发一个令牌给前端前端后续请求都带上这个令牌后端拦截器检查令牌是否合法。这样做的最大好处是服务端不需要维护Session状态适合前后端分离和未来的水平扩展。用户注册的逻辑很简单注意两点密码要加密存储绝对不能明文入库先用SELECT COUNT(*)检查用户名是否已经被注册。密码加密我用的Spring Security Crypto工具类BCryptPasswordEncoder这个算法内置盐值相同的密码每次加密结果都不同就算数据库泄露也不能直接反推原始密码。注册的Service代码大致思路是public Result register(UserRegisterDTO dto) { // 检查用户名是否存在 User existing userMapper.selectByUsername(dto.getUsername()); if (existing ! null) { return Result.error(用户名已被占用); } // 密码加密后入库 User user new User(); user.setUsername(dto.getUsername()); user.setPassword(new BCryptPasswordEncoder().encode(dto.getPassword())); userMapper.insert(user); return Result.success(); }登录接口成功后用用户ID和用户名生成JWT令牌然后封装成Token对象返回给前端。签发令牌的工具类核心代码只有几十行重点注意JWT的expiration过期时间设置我设置为24小时过短会导致用户频繁重新登录过长又降低安全性24小时对社区平台是合理折中。后端鉴权我用了SpringBoot的HandlerInterceptor拦截器方案。写一个AuthInterceptor实现HandlerInterceptor接口在preHandle方法里从请求头Authorization字段取出Token调用工具类验证签名。这里要配置一个白名单放行注册、登录、菜谱列表、菜谱详情等不需要登录就能访问的接口然后用WebMvcConfigurer注册拦截器并指定拦截路径为/api/**。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\登录已过期,请重新登录\}); return false; } }拦截器里有几个细节值得注意前端传Token的主流方式有两种放在Authorization请求头或者自定义Header里我用的是Authorization: Bearer token这种标准写法前后端要约定一致拦截器校验失败时不能只返回状态码还要给前端能解析的JSON提示所以专门写了统一格式的返回体。3.3 MyBatis持久层与动态SQL实战MyBatis这个框架的定位是“半自动化”SQL需要开发者手写也因此写得好能极大提升查询效率。厨艺平台里最复杂的查询是菜谱列表接口它要支持分页支持按分类筛选支持按关键词模糊搜索标题还可能要按某个条件排序。最初版本我写了一个动态SQL这个功能正好是MyBatis的强项。我把映射文件放在src/main/resources/mapper/RecipeMapper.xml下核心查询是这样select idselectRecipeList resultTypecom.example.entity.Recipe SELECT * FROM recipe where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND title LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签是MyBatis动态SQL的精髓。它会智能处理如果没有任何条件不会生成多余的WHERE如果第一个条件前面有AND它会自动去掉那个多余的AND避免SQL语法错误。配合分页参数offset和pageSize在Java代码里计算好传进来一个列表查询就完成了。这里要讲清楚#{}和${}的区别这是MyBatis面试几乎必考、实际开发又容易踩坑的点。#{}是预编译参数占位符MyBatis会把它转换成?由数据库驱动做参数绑定能有效防止SQL注入凡是用户输入的内容必须用#{}。${}是字符串直接拼接比如ORDER BY后面的排序字段没法用占位符只能使用${}但这种场景千万不要把用户输入直接拼进去要用白名单校验比如在Java层判断排序字段只允许是create_time、view_count等固定值。我在项目里就是严格遵循这个原则排序字段走了白名单其他全部用#{}。MyBatis的缓存也是一个高频考点。默认情况下同一SqlSession内的一级缓存是开启的连续两次查询同一条数据会命中缓存不会重复查数据库。但Spring容器管理的Mapper通常不是同一个SqlSession所以一级缓存价值有限二级缓存可以跨SqlSession生效但只在单服务环境下有意义一旦部署多实例或者引入分库分表缓存数据就不一致了。社区平台这类实时数据要求较高的系统真不建议开二级缓存为了避免读到菜谱列表修改前的旧数据我直接把全局缓存配置保持默认关闭把并发性能优化留给数据库索引和前端静态缓存。3.4 菜谱发布与图片上传的实现菜谱发布是厨艺平台最核心的交互功能牵扯到两个问题图片文件怎么存储、菜谱内容如何持久化。图片上传我在前后端分离架构里用的是最朴实的方案前端通过multipart/form-data把图片文件POST到后端后端把文件保存到服务器的upload目录并把可访问的URL路径返回给前端。用户提交菜谱时表单里填写的是图片URL字符串而不是二进制文件。后端上传接口的逻辑大概是检查文件格式jpg、png、gif等限制大小不超过5MB使用UUID重新生成文件名防止重名然后写入本地指定目录。注意生成的文件名要保留扩展名否则图片无法正常显示。开发时要确认上传目录有写权限部署到服务器时更要注意。同时还要在更新菜谱时删除旧图片文件否则服务器会堆满垃圾文件。实际项目中这些图片通常会上传到对象存储服务比如阿里云OSS本地保存方案教学够用但生产环境不建议。菜谱内容本身我用了一个RecipeDTO接收前端传过来的完整数据包括标题、封面图、食材列表、步骤列表、分类ID。里面的步骤和食材我前面提到过用JSON字符串存TEXT字段。实现时前端Vue组件里把步骤数组序列化成JSON字符串后端用一个RequestParam或直接封装在请求体里接收插入数据库前再JSON.toJSONString一下查询出来后JSON.parseArray解析回对象。简化程度明显代码量少了三四张表的关联查询。4. 前端核心实现与关键细节后端接口就绪后前端的任务是做数据展示和用户交互。Vue这块我讲讲项目搭建、跨域处理、页面实现和路由守卫这几个核心环节。4.1 Vue项目初始化与环境配置前端工程我用Vue CLI创建命令是vue create cook-platform-frontend。这里注意一个常见的环境坑Vue CLI本身依赖Node.js环境建议安装Node 16或以上版本npm在项目根目录执行。如果你用手写包管理工具一定保证Node版本和Vue CLI版本匹配否则项目拉不起来。创建时我选择了Vue 3的默认预设包含Router和Vuex。Vue 3组合式API对于新项目来说更推荐setup语法让组件的逻辑组织更清晰。安装好项目依赖后建议先把src目录结构调整成约定的规范结构src/ ├── api/ # 封装所有后端接口请求 ├── assets/ # 静态资源 ├── components/ # 可复用组件 ├── router/ # 路由配置 ├── store/ # Vuex全局状态 ├── views/ # 页面级组件 └── utils/ # 工具函数目录结构看起来简单但能起到约束作用。像api目录我规定项目中所有对后端接口的调用都必须写在这里不允许组件里直接写axios.get这样接口地址变更时只需要改一个文件遇到联调问题也能快速定位。4.2 Axios封装与跨域问题实战前后端分离开发时最常遇到的问题就是跨域。开发环境前端跑在http://localhost:5173后端跑在http://localhost:8080端口不同浏览器会判定为跨域请求直接发起会报CORS错误。解决思路有两个开发环境配置Vite代理生产环境配置Nginx反向代理让前后端同源。我在开发阶段用的是Vite的代理配置// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })配置完之后前端请求/api/user/loginVite开发服务器会自动把请求转发到http://localhost:8080/api/user/login浏览器看到的只是请求了localhost:5173跨域问题直接消失。这个方案是首选不要去碰后端跨域配置因为生产环境交给Nginx反向代理才是干净的做法。Axios封装方面我写了一个request.js工具文件统一处理三件事请求时从Vuex里取出Token并添加到请求头响应时统一解析后端返回的Result结构业务码非200时弹出错误提示。响应拦截器还要处理一个特殊场景后端返回401表示Token过期这时应该自动跳转到登录页并清空用户状态。// utils/request.js import axios from axios; import store from /store; import router from /router; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token store.state.token; if (token) { config.headers.Authorization Bearer ${token}; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code 401) { store.commit(clearUserInfo); router.push(/login); return Promise.reject(new Error(未授权)); } return res; }, error Promise.reject(error) );这里有个容易踩的坑响应拦截器返回的res是整个业务对象所以后续接口方法里要用两层解构。比如const { data } await getRecipeList(params)如果data正好是Result里的data字段就对了但有时候封装不彻底会让代码混乱建议在拦截器里直接返回res.data组件里拿到的就是Result对象本身再取它的data属性更合理。4.3 菜谱广场与详情页面的实现菜谱广场是前端的门面实现上是一个典型的列表页加筛选条件。页面顶部放分类Tab、搜索框底部是卡片式菜谱列表。数据请求通过api/recipe.js封装的getRecipeList(params)完成params里包含分页页码、分类ID、关键字。组件拿到数据后使用v-for循环渲染。卡片组件RecipeCard.vue是典型的可复用组件它接收一个recipe对象作为prop展示封面图、作者头像昵称、标题和点赞收藏数。Vue 3的defineProps定义prop后子组件内部不能直接修改prop的值必须通过emit向父组件发送事件。这个约束很多人一开始不习惯但它保证了单向数据流页面出现数据变化时更好定位是在哪个环节出了问题。菜谱详情页展示了完整的菜谱内容包括食材列表、图文步骤、评论区和互动按钮。接口设计上详情接口GET /api/recipe/{id}返回菜谱基本数据评论接口GET /api/comment/list?recipeIdxxx单独返回评论列表。这样设计的好处是详情页可以分批加载数据先渲染主体内容评论区域异步加载用户感知上页面更快。详情页还要做浏览量统计。我的方案是不做实时统计用户在详情页停留超过一定时间或者页面mounted时延迟调用一次POST /api/recipe/view/{id}后端对浏览量加一。如果每个用户每次进详情页都实时加一一次点击可能就是两三次请求压力增大而且数据还会虚高。4.4 路由守卫与登录状态管理社区平台很多页面需要登录后才能访问比如发布菜谱、个人中心、修改资料。前端不能只依赖后端拦截器保护接口页面级别的跳转限制要在路由层处理否则用户体验会很差。我用Vue Router的全局前置守卫beforeEach实现router.beforeEach((to, from, next) { const token store.state.token; if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); } else { next(); } });利用每个路由的meta元信息标记是否需要登录需要登录但没Token的直接跳到登录页并把目标路径放进query参数里登录成功后再跳回来这个体验细节很加分。登录状态用Vuex统一管理包括token、userInfo两个核心状态。用户信息从登录接口返回后存入Vuex同时要持久化到localStorage否则刷新页面Vuex状态丢失用户会感觉登录了又没登录。在Vuex的初始化阶段直接读取localStorage恢复状态就能实现刷新后仍然保持登录。这里要注意安全性Token存在localStorage有被XSS窃取的风险更严谨的做法是使用HttpOnlyCookie配合CSRF防护但教学项目里前端存储合理够用只要生产环境前端部署启用了CSP和必要的内容安全策略就行。5. 完整部署流程实操项目开发完成后部署上线的过程本身就是一个大型避坑现场。我把完整流程走一遍从环境准备讲到Nginx配置照着做基本能一次跑通。5.1 部署前环境准备部署的目标环境我建议是Linux服务器或者本地虚拟机操作系统Ubuntu 20.04或CentOS 7都行。需要准备的环境包括JDK 8或11推荐用apt install openjdk-11-jdk一键安装、Maven 3.6以上用于后端打包、Node.js 16以上用于前端构建、MySQL 5.7或8.0、Nginx。安装MySQL时要注意这个项目最容易出问题。如果使用RPM方式安装MySQL 8.0官方源默认密码策略很严格长度至少8位且包含大小写数字特殊字符初始安装后会在日志里生成临时密码用grep temporary password /var/log/mysqld.log查看登录后先改成一个符合策略的复杂密码再执行SET GLOBAL validate_password.policy LOW;降低后续建库密码的策略要求。很多新手在这里卡半天其实都是密码策略没搞明白。后端打包依赖Maven我在项目根目录执行mvn clean package -DskipTests成功后会在target目录生成cook-platform-backend-1.0.0.jar可执行Jar包。这里要说一句Java项目部署不建议直接在IDE里点击运行生产环境应该用java -jar xxx.jar启动或者更规范的做法是写一个systemd服务文件用systemctl管理进程这样应用崩溃后可以自动重启。5.2 数据库初始化与后端启动在部署服务器上第一步是创建数据库并导入初始化SQL脚本。我把建表语句和初始数据放在项目根目录的sql/init.sql里执行命令mysql -u root -p /path/to/init.sql。初始化脚本里包含前面说的六张核心表的创建语句还要插入默认分类数据比如“家常菜”“烘焙”“汤羹”“凉菜”等。这块值得注意初始化脚本要保证可以重复执行而不报错建表语句加上DROP TABLE IF EXISTS前缀。后端启动前改一下application.yml里的数据库连接为服务器本机的地址密码改成MySQL实际密码。启动命令我推荐把日志输出到独立文件里nohup java -jar cook-platform-backend-1.0.0.jar logs/app.log 21 启动之后检查是否成功看日志文件里有没有Started Application in X seconds字样。这一步有个经验SpringBoot启动报错多半是数据源不对或者端口被占用数据源错了先确认MySQL服务是否开启、账号权限是否允许远程连接端口被占用了就改server.port或kill掉占用进程。测试接口可以直接用curlcurl -X POST http://localhost:8080/api/user/login -H Content-Type: application/json -d {username:test,password:123456}返回JSON里包含Token就说明后端部署成功。5.3 前端构建和Nginx配置前端构建很简单在cook-platform-frontend目录执行npm run build产物会生成在dist目录。这里要提前设置一个构建时的API地址坑如果是开发环境代理转发的写法/api构建后请求路径也是/api然后交给Nginx把/api反向代理到后端地址这才是干净的同源方案。所以前端代码里用相对路径/api而不是写死http://localhost:8080这点非常关键直接决定生产环境能不能正常联调。接下来配置Nginx。核心思路是Nginx监听80端口当请求路径是/api时把请求转发到后端的8080端口其他所有静态资源的请求直接指向前端dist目录下的文件。这是前后端分离部署最标准、也是踩坑最少的方案server { listen 80; server_name localhost; root /usr/share/nginx/html/dist; index index.html; # 前端路由的history模式所有非API请求都返回index.html location / { try_files $uri $uri/ /index.html; } # 后端API反向代理 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; } }try_files这行是必须的。Vue Router如果开启createWebHistory模式history路由前端路由路径比如/recipe/12在刷新时Nginx会去服务器找这个文件结果找不到返回404。try_files $uri $uri/ /index.html的作用就是当文件不存在时把请求重新指向index.html让前端路由接管这个路径。如果你不想处理这个配置也可以改用createWebHashHistory模式路径带#但URL不够美观作为完整项目还是建议用history模式并配好try_files。配置完成后执行nginx -t检查语法nginx -s reload重新加载配置。访问http://服务器IP能看到前端页面并且接口正常返回数据整条部署链路就打通了。根据我个人多次部署的经验第一次访问时如果出现白屏或者接口404优先检查Nginx配置里proxy_pass是否少了末尾的斜杠、try_files是否写对这两个位置最容易出错而且出错后现象还不太一样。6. 常见问题与排查技巧实录这部分我把实操过程中遇到的高频问题集中整理一下按模块归档成速查表每个问题都给出排查思路和解法。6.1 MySQL连接类问题错误现象原因分析解决办法Communications link failure数据库地址端口不通检查MySQL是否启动netstat -tlnp查看3306端口Access denied for user账号密码错误确认application.yml里的密码和实际一致测试用mysql -u root -p连接库The server time zone value XXX is unrecognized时区未指定URL上加上serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedMySQL 8.0连接驱动加密规则URL加allowPublicKeyRetrievaltrueuseSSLfalse其中allowPublicKeyRetrievaltrue这个参数最容易被忽略。MySQL 8.0默认使用的是caching_sha2_password认证方式Java连接时如果不允许自动获取公钥会直接连接失败。开发环境的JDBC URL建议写成jdbc:mysql://localhost:3306/cook_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue6.2 SpringBoot版本过高引发的问题热词里有人提到“SpringBoot版本太高”这个问题很典型。当你用SpringBoot 3.x时底层是Java 17起步而且javax.servlet包被改成了jakarta.servlet。如果你的代码、第三方库还是老写法启动时会报ClassNotFoundException: javax.servlet.Filter之类的错误。所以如果项目基于旧教程构建建议老老实实用SpringBoot 2.7.x加上JDK 8或11的组合不要去追新版本。如果你想用SpringBoot 3.x做新项目则要确认所有依赖都有适配版本并注意自定义拦截器、过滤器里引用的包路径要换成jakarta.servlet。6.3 前端跨域和接口联调问题跨域问题我在第4部分已经讲了开发环境用Vite代理。但有些项目没有用代理而是去后端加CrossOrigin注解或者配置CORS过滤器这种做法开发时能用上线后如果Nginx已经做了同源代理后端这些配置会变成多一层但无害的配置。真正的排查技巧是先打开浏览器F12开发者工具看Network面板的请求URL。如果请求是http://localhost:5173/api/user/login说明代理配置生效如果直接请求http://localhost:8080/api/user/login说明代码里baseURL写死了必须改成相对路径/api。检查请求头里的Origin字段也能判断跨域问题出在前端还是后端。6.4 MyBatis映射和动态SQL问题MyBatis最常见的问题是结果映射不到实体类上去。如果字段对不上多数是驼峰映射没有开启或者字段名拼错了。排查时打开MyBatis的SQL日志输出看实际执行的SQL长什么样再把SQL复制到数据库客户端手动执行一次立刻就能看出是SQL本身的问题还是映射问题。动态SQL里还有一个很隐蔽的错误if testkeyword ! null and keyword ! 条件里的and必须小写不能写AND否则部分MyBatis版本解析可能出问题。另外XML文件里的、符号在SQL中要转义成lt;和gt;比如create_time lt; #{time}不然XML解析会报错。6.5 Vue构建部署的白屏和路由问题前端部署常见的白屏现象我排查过很多次原因基本锁定在三个方面一是静态资源路径不对打包时base路径没配置好部署在子目录时资源404二是没配try_files访问子路由刷新就404三是后端接口没通页面请求失败导致渲染不出数据。如果Nginx做的域名根路径部署建议在Vite配置里设base: /资源加载都是根路径。如果还要部署到二级目录比如/app就需要把base设成/app/并且Nginx的location也要相应调整。6.6 我的部署排错心得最后分享一个我在这个项目上最有价值的排错习惯。不管是后端报错还是前端异常第一件事就是去看日志和Network面板而不是凭感觉改代码。后端启动报错先看完整的堆栈第一行绝大多数问题都是依赖缺失或配置错误前端接口报错打开Network面板看响应体的错误信息和HTTP状态码401优先检查Token有没有带上404优先检查请求路径和Nginx转发规则。一个项目跑通之后你会发现真正的能力提升不只是会写那几个接口和页面而是面对未知问题时能快速定位、冷静排除。这套思路拿到任何项目里都适用也是我做完厨艺交流平台系统之后最大的收获。
RELATED

相关推荐

INT与gRPC网络遥测:精细化运维实战指南

INT与gRPC网络遥测:精细化运维实战指南

简介:这份PDF文档面向HPC与下一代数据中心网络的运维工程师及架构设计人员,聚焦如何借助Network Telemetry技术打破“网络黑盒”,解决大规模复杂网络中流量精细可视、可控以及端到端秒级故障定位的难题。资源包共1个PDF文件,大小约…

📅 2026/10/5 4:28:46
论文写作工具怎么选?生成型、检索型与学术规范一次讲透

论文写作工具怎么选?生成型、检索型与学术规范一次讲透

这段时间又到了毕业季,后台收到不少私信,问的都是同一个问题:马上要交论文了,有没有能一键生成论文的工具?正好有人把千笔专业论文写作工具和学术猹这两个名字放在一起对比,我干脆把这事一次讲透。先说结论…

📅 2026/10/5 4:28:46
Java数组全解析:基础用法、扩容原理、内存模型与面试避坑

Java数组全解析:基础用法、扩容原理、内存模型与面试避坑

有人问过我一个问题:都这个年头了,写“Java数组常见用法”还有人看吗?我的回答是:正因为常见,才真正值得往深里刨一刨。数组是Java里最容易被“以为会了”的基础知识点。增删改查谁都能写两行,可真到面试桌…

📅 2026/10/5 4:23:46
MORE NEWS

更多资讯

📰

OpenCADStudio命令行与脚本自动化完整指南:如何用别名和命令脚本批量完成绘图任务

OpenCADStudio命令行与脚本自动化完整指南:如何用别名和命令脚本批量完成绘图任务 【免费下载链接】OpenCADStudio A CAD application built with Rust — 2D/3D drawing, DWG/DXF support, and GPU-accelerated rendering 项目地址: https://gitcode.com/gh_mirr…

📰

AI Agent 权限边界设计:从沙箱逃逸到最小权限实践

1. 从“AI Agent 逃出沙箱”说起:一个被低估的权限边界问题第一次看到“AI Agent 逃出沙箱”这个说法,我脑子里蹦出来的不是科幻电影里的天网,而是一个特别具体的画面:你给一个自动化脚本开了个容器,让它帮你整理文件、…

📰

上下文工程:AI Agent稳定落地的核心技术

1. 这不是“加长版Prompt”,而是AI Agent的呼吸系统你有没有试过给大模型喂一段超长的用户对话历史、三份PDF摘要、五条实时行情数据,再加一个“请综合判断是否下单”,结果模型要么直接截断、要么逻辑混乱、要么开始胡编乱造?这不…

📰

Roo Code 本地模型卡顿优化:从链路分析到参数配置的完整指南

如果你也试过在 Roo Code 里接本地模型,大概率会碰上这样一种体验:第一句话发出去,光标转圈转得人心慌;好不容易开始出字了,又是一个字一个字往外蹦,像在看慢镜头回放。说“卡顿”都算客气了,对…

📰

同一个Beacon为何只该计数一次:wifit3 WiFi审计工具wlan去重模块完全解析

同一个Beacon为何只该计数一次:wifit3 WiFi审计工具wlan去重模块完全解析 【免费下载链接】wifit3 Wifite but USB-only & cross-platform. 项目地址: https://gitcode.com/GitHub_Trending/wi/wifit3 wifit3 是一款跨平台(Linux / Windows /…

📰

RAG客服机器人实战:原理拆解与工程落地避坑指南

1. 为什么客服机器人总爱“一本正经地胡说八道”先说我自己的真实经历。之前团队做了一个客服机器人,接的是某产品的售后知识库,整理了几百篇 Word 和 PDF 文档,喂给大模型做微调。结果上线第一天就翻车了:用户问“保修期多久”&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬