
1. 项目整体设计与技术选型思路1.1 为什么选SpringBoot做食谱管理而不是SSH或SSM做家庭食谱管理系统很多人第一反应是“这不就是个CRUD吗”但真正动手做过课程设计或者毕业设计的同学都懂CRUD只是表象背后的工程化思维、表结构设计、部署流程才是拉开差距的地方。我之所以在这个项目里选SpringBoot核心原因有三个。第一SpringBoot自带内嵌Tomcat打成一个jar包直接java -jar就能跑省去了单独装Tomcat、配置web.xml这一堆历史包袱。第二SpringBoot的starter机制让依赖管理变得很省心比如接入MyBatis Plus、MySQL驱动、Lombok只需要在pom.xml里加依赖就行版本号还能交给parent统一管理这对新手太友好了。第三SpringBoot项目的结构天然就是按“控制器、服务、数据访问”分层的这种结构用来做毕设答辩或者给面试官讲项目逻辑非常清晰。也有同学会纠结“要不要用SSM老结构”我的建议是没必要。SSM那套XML配置放到今天来看除了让你熟悉历史项目实际开发中已经很少从零搭了。而你用SpringBoot做出来的项目写到简历上是“SpringBoot MyBatis Plus MySQL”面试官看着也舒服不至于觉得你技术栈停留在十年前。还有个选择题是前端怎么做。家庭食谱管理系统这种项目功能侧重点是“食谱的增删改查 分类展示 用户收藏评论”属于典型的管理系统形态。如果你想快速出活用Thymeleaf模板引擎就能搞定页面渲染后端返回数据模型模板直接渲染HTML学习成本低项目也能跑通。如果你想把项目档次拉开一截可以用Vue Element UI做前后端分离后端只出接口前端单独部署。两种我都试过后者的工作量大概会多出30%到50%但如果你的时间够我更推荐前后端分离因为现在企业里主流就是这么干。考虑到这篇博文面向的是想做课程设计和毕设的同学我以Thymeleaf版本为主线来讲结尾再补一句Vue分离的思路。1.2 技术栈清单与版本选择这里直接给出我当时用的技术清单照着配就行踩坑经验会写在后面。组件选型版本建议JDKJDK 1.8 或 JDK 171.8最稳17也行但要注意SpringBoot版本对应SpringBootspring-boot-starter-parent2.7.x系列别用3.x坑多ORM框架MyBatis Plus3.5.x数据库MySQL5.7或8.0模板引擎Thymeleaf由SpringBoot统一管理前端UIBootstrap 5 少量自定义CSS简单高效项目管理Maven3.6Lombok简化实体类记得配注解处理器有个细节要单说SpringBoot版本别一上来就选3.x。3.x要求JDK17起步而且jakarta命名空间变动很多教程和资料会套不上。作为一个以“稳定复现”为第一目标的项目SpringBoot 2.7.x JDK1.8是全球公认最稳的组合网上搜资料、问AI、照抄代码遇到问题都是最少的。等你把这套跑通了再去看3.x的差异也来得及。1.3 数据库表结构六张表一次设计到位食谱管理系统的表结构并不复杂但我见过很多同学表设计得一团乱后面写代码时反复改甚至直接卡在最基础的分类和食谱关联上。这里我把自己验证过的表结构放出来直接抄。第一张是用户表sys_user字段包括id、username、password、nickname、avatar、role1为管理员2为普通用户、create_time。密码一定要加密存储用MD5加盐或BCrypt都行至少不能明文入库。第二张是分类表category字段是id、name、sort排序值、create_time。第三张是食谱表recipe这是核心表字段比较长id、category_id关联分类、user_id发布者、title、cover封面图路径、ingredients食材清单我用JSON字符串存储比如[{name:鸡蛋,amount:2个}]、steps制作步骤纯文本或JSON数组均可、cooking_time烹饪时长、difficulty难度简单/一般/困难、views浏览量、status0为草稿1为已发布、create_time、update_time。第四张是评论表comment字段是id、recipe_id、user_id、content、create_time。第五张是收藏表favorite字段是id、recipe_id、user_id、create_time为了防止重复收藏可以给recipe_id user_id加唯一索引。第六张是食材表ingredient这个表不是必须的但它能让系统显得更完整字段是id、name、category荤素/粮油/调料等、unit单位如克、毫升、calories每100克热量为后面的营养统计做铺垫。设计这六张表的时候有一个重要的取舍食材到底要不要单独建表如果只是做简单的“展示菜谱”把食材直接存成text字段就够了。但如果想做“根据冰箱剩余食材找菜谱”或者“统计一顿饭的营养摄入”就必须把食材拆出来。我当时的做法是折中——食谱里的食材清单用JSON字段存同时保留ingredient主数据表用来做食材基础档案和数据字典这样复杂度不会爆炸扩展空间也有。2. 核心功能拆解与业务逻辑实现2.1 用户登录注册与会话管理用户模块是整个系统的入口没有登录态后面的收藏、评论都没法做。登录逻辑我建议用SpringBoot拦截器统一处理不要在每个Controller里手动判断Session。注册接口的逻辑是前端提交用户名、密码、确认密码后端先查sys_user表有没有相同的用户名有则返回“用户已存在”没有就用BCrypt加密密码后插入。这里有个细节——用户名不要只做非空校验还要限制长度和字符范围避免用户填乱七八糟的符号导致后续出问题。登录成功之后把用户对象放进Session同时用拦截器拦截需要登录才能访问的路径。我在项目里把路径分了三类放行的登录页、注册页、静态资源、首页列表、需要登录的个人中心、发布食谱、收藏、评论、需要管理员的分类管理、用户管理。拦截器本质就是一个HandlerInterceptor实现类preHandle方法里判断Session有没有用户没有就重定向到登录页有就根据角色再判断一次权限。2.2 食谱发布、编辑、删除与图片上传这是整个系统的重头戏。食谱发布页需要提交的信息包括标题、分类、封面图、食材清单、步骤、烹饪时长、难度。表单提交后后端做两件事处理封面图、保存正文数据。图片上传我建议本地存储不要一开始就接OSS。在application.yml里配置一个上传目录比如D:/upload/Windows或者/home/ubuntu/upload/Linux上传时把文件名用UUID重命名防止中文名和重复名带来的问题后缀保留.jpg、.png等格式。重命名完把文件写到上传目录数据库里存的路径用相对路径比如/upload/20250312/xxxx.jpg。这里有个大坑开发环境和部署环境的路径不一致所以建议在配置文件中用file.upload-path这个自定义配置项来管理部署时改配置文件即可代码完全不用动。编辑食谱和发布的逻辑差不多只需要在保存前判断一下当前用户是不是这篇文章的作者或者是不是管理员否则不允许修改和删除。删除食谱时还要联动删除它的评论和收藏记录不然会留一堆脏数据。我的做法是在Service层写一个事务方法先删子表数据再删主表数据加Transactional注解保证原子性。2.3 分类检索、关键词搜索与分页分类检索和搜索是用户使用频率最高的功能。我在首页和食谱列表页放了一个搜索框支持按菜名关键词模糊查询同时左侧放分类导航点击某个分类就只看该分类下的食谱。这个功能用MyBatis Plus的LambdaQueryWrapper非常好写。分页则用MyBatis Plus自带的分页插件只需要在配置类里注册一个MybatisPlusInterceptor添加PaginationInnerInterceptor然后在Service里调用page(new Page(current, size), wrapper)就能拿到分页数据。分页结果里records是当前页数据total是总条数pages是总页数前端用Thymeleaf渲染时直接循环records翻页链接带上pageNum参数即可。我说一个自己踩过的坑分页插件必须注册拦截器才生效否则调用page方法你会发现SQL里根本没有LIMIT数据全查出来了只是前端显示一页。这个问题排查了好久最后发现是MybatisPlusInterceptor没注入到Spring容器里。2.4 收藏、评论与浏览量的联动设计收藏功能要注意“不可重复收藏”。我当时在favorite表建了唯一索引插入前虽然也先查了一遍但为了防止并发下重复插入数据库唯一索引是最后防线。点赞/收藏按钮的交互逻辑是用户已收藏则显示“已收藏”点击取消收藏未收藏则显示“收藏”点击加入收藏。评论模块相对简单提交评论时带上recipe_id和user_id列表按时间倒序展示。展示评论时还需要把user_id关联到sys_user表查出昵称和头像这个用MyBatis Plus的关联查询或者循环查询都行数据量小的情况下循环查询也不会有性能问题。浏览量我做了个最简单的方案每次点击食谱详情update recipe set views views 1 where id ?。虽然高并发下会有性能问题但课程设计和毕设场景完全够用。如果你想让项目显得“高级”可以用Redis的incr命令维护浏览量定时同步到MySQL这个作为加分项写在论文里会很出彩。3. 完整实操过程与关键代码解析3.1 从零搭建工程骨架5分钟跑起来第一步是去Spring Initializr生成基础工程或者直接在IDEA里新建Spring Initializr项目。Group填com.exampleArtifact填recipe-managementJava版本选8依赖勾选Spring Web、Thymeleaf、MySQL Driver、Lombok。生成完成后在pom.xml里额外加MyBatis Plus的依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.20/version /dependencyHutool是我个人很喜欢的Java工具库项目里很多地方都用得到比如生成UUID、日期格式化、文件操作没必要自己造轮子。工程结构我按经典三层分包controller、service、mapper、entity、config、interceptor、common公共返回结果与异常处理。在这个项目里我强烈建议common包下写一个统一的返回类Result格式是code message data虽然Thymeleaf模板直接传ModelAndView不一定需要它但养成写统一返回类的习惯对你后面做前后端分离帮助很大。3.2 核心配置与application.yml详解配置文件是项目的命门我见过无数人配置出错。这里把核心配置完整贴出来每行都解释一下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/recipe_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted file: upload-path: D:/recipe-upload/serverTimezoneAsia/Shanghai这个参数必须加不加就会报时区错误或者日期差8小时。useUnicodetruecharacterEncodingutf8保证中文不乱码。thymeleaf.cache: false开发时改成false改完模板刷新页面就生效不用重启。file.upload-path是自定义配置项用Value(${file.upload-path})注入读取部署到服务器时只需改这一处。3.3 Controller层如何写接口路径怎么规划很多新手在写Controller的时候会把所有逻辑堆进去这是项目变烂的开始。我推荐的做法是Controller只做参数接收和结果返回业务逻辑全部下沉到Service层。以食谱模块为例Controller RequestMapping(/recipe) public class RecipeController { Resource private RecipeService recipeService; GetMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword, Model model) { PageRecipeVO page recipeService.getRecipePage(pageNum, 8, categoryId, keyword); model.addAttribute(page, page); model.addAttribute(categoryId, categoryId); model.addAttribute(keyword, keyword); return recipe/list; } GetMapping(/detail/{id}) public String detail(PathVariable Long id, HttpSession session, Model model) { RecipeVO recipe recipeService.getRecipeDetail(id); model.addAttribute(recipe, recipe); model.addAttribute(comments, commentService.listByRecipeId(id)); model.addAttribute(isFavorite, favoriteService.isFavorite(id, getLoginUserId(session))); return recipe/detail; } }路径规划上/recipe/list是列表页/recipe/detail/{id}是详情页/admin/category/manage是管理端页面。RecipeVO是我建议单独写的视图对象里面除了食谱基础字段还包括分类名、发布者昵称、评论数等关联信息避免前端模板里到处调方法拿关联数据。3.4 登录拦截器与用户角色权限控制拦截器的代码核心就一句话请求进来先判断Session里有没有loginUser。我写了一个LoginInterceptor实现HandlerInterceptor在preHandle里做判断然后在WebMvcConfigurer里注册并指定拦截路径Configuration public class WebConfig implements WebMvcConfigurer { Resource private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/, /login, /register, /recipe/list, /recipe/detail/**, /css/**, /js/**, /images/**, /upload/**); } }这里有个很容易踩的坑/upload/**必须放行否则你上传的封面图片会因为拦截器拦截而无法访问。我一开始没放行图片路径结果页面上所有食谱封面都是裂图查了半天才想起来拦截器也拦了静态资源。角色权限控制我是在拦截器里加了一层判断如果是/admin/**的请求再检查当前用户角色是否为管理员不是就返回403页面。这种写法在毕设场景里足够了不用上Spring Security那么重的框架。3.5 构建打包与两种部署方式本地调试没问题后把代码打包部署。Windows上打包非常简单IDEA右侧Maven面板双击package或者在项目根目录执行mvn clean package -DskipTests打包成功后target目录下会生成一个recipe-management-0.0.1-SNAPSHOT.jar。本地跑起来直接java -jar recipe-management-0.0.1-SNAPSHOT.jar部署到云服务器时我习惯用nohup方式启动退出SSH窗口服务也不会停nohup java -jar recipe-management-0.0.1-SNAPSHOT.jar --server.port8080 app.log 21 如果需要开机自启可以写一个systemd服务文件或者更简单点用crontab的reboot命令。数据库在上线前要把application.yml里的localhost改成服务器内网或公网地址file.upload-path改成Linux路径比如/home/ubuntu/recipe-upload/并且要提前建好目录否则上传图片时会报“系统找不到指定路径”。4. 常见问题与排错实录4.1 数据库连接失败与账号权限问题报错信息里出现Access denied for user rootlocalhost肯定是用户名或密码错了去MySQL里执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;。还有一种情况是MySQL 8.0默认用了caching_sha2_password认证插件而旧版驱动不支持你会在启动时看到Unable to load authentication plugin解决方法是把驱动升级到mysql-connector-java8.0以上或者执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;切换认证方式。4.2 中文乱码与时间8小时偏移页面中文乱码优先检查application.yml里有没有characterEncodingutf8没有就加上重启。如果请求参数中文乱码需要在配置里加一个CharacterEncodingFilter的Bean或者在application.yml配置server.servlet.encoding.force: true。时间差8小时问题十有八九是serverTimezone没设置设置成Asia/Shanghai之后重启就好。4.3 Maven依赖下载失败与jar包冲突依赖下载失败基本都是网络问题。国内用户务必配置阿里云镜像找到Maven的settings.xml在mirrors节点下加mirror idaliyun/id mirrorOfcentral/mirrorOf nameAliyun Maven Mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirrorjar包冲突在SpringBoot场景下最常见的就是slf4j和log4j如果启动时刷一堆红色日志说SLF4J: Class path contains multiple SLF4J bindings去pom.xml里用exclusions排除多余的依赖就行。这个可以直接用IDEA的依赖分析功能看哪一行变红就点哪儿照着排除。4.4 部署后页面能开但图片全裂图片裂了优先看浏览器F12里图片的请求路径是什么。如果请求路径是localhost:8080/upload/xxx.jpg说明你数据库里存的是localhost开头的路径这在本地没问题但部署到服务器后自然就访问本机了。我建议数据库存相对路径/upload/xxx.jpg然后写一个WebMvcConfigurer把/upload/**映射到本地磁盘目录Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath fileUploadPath /; registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); }这样无论部署在哪里只要配置文件里的file.upload-path是对的图片就能正常访问。为了更直观我把之前遇到的高频问题整理成一个速查表现象原因解决方法启动报端口占用8080被其他进程占用netstat -ano查占用进程改SpringBoot端口页面能开但登录后跳回登录页Session失效或拦截器放行配置错误检查拦截器放行列表检查登录逻辑是否真的写入Session分页数据不变没有注册分页拦截器添加PaginationInnerInterceptor上传图片报空指针上传目录不存在先把上传目录手动建好再启动项目MyBatis Plus实体字段查不到数据驼峰映射没开启配置map-underscore-to-camel-case: true时间字段显示为nullLocalDateTime和JDBC驱动不兼容升级MySQL驱动到8.0实体用LocalDateTime类型5. 项目还可以往哪些方向扩展食谱管理系统做完了你可能会觉得功能有点单薄想让它更有竞争力。我提几个亲测可行、又不至于破坏现有结构的扩展方向。第一个方向是营养统计。既然ingredient表里已经存了热量和单位就可以在食谱详情页加一个“营养分析”模块根据食材清单里的每项食材和用量估算这一道菜的总热量、蛋白质、脂肪含量用图表展示出来。图表可以用ECharts前端引入CDN即可数据接口就是遍历食谱的ingredientsJSON字段按食材主数据去关联查营养数据逻辑简单但展示效果很惊艳。第二个方向是菜谱推荐。不需要搞复杂的机器学习有两个小方案都能做一是基于分类偏好的推荐统计当前用户收藏最多的分类然后优先推荐该分类下未被该用户浏览过的食谱二是基于食材的推荐用户输入现有的食材相当于“冰箱里有鸡蛋、西红柿”系统筛选出食材匹配度最高的食谱。第二个方案用MySQL的LIKE查询就能做个粗糙版本比如WHERE ingredients LIKE %鸡蛋% AND ingredients LIKE %西红柿%再按匹配数量排序。如果能写成论文里的“算法模块”很加分。第三个方向是做移动端适配。Thymeleaf模板用Bootstrap的话天然就是响应式的手机浏览器直接访问就能用。但如果你想把项目包装成一个完整的作品可以额外做一个H5前端或者用Uniapp套壳成App。接口已经有了前端只需要调/api/recipe/list这类接口即可。我在做这个方向时会在项目里加一组RestController返回JSON接口路径用/api/**区分和页面接口分开互不干扰维护起来也清晰。第四个方向是导出功能。很多管理系统都要导出Excel食谱管理系统导出“本周新增食谱”或者“收藏排行榜”也很合理。用Hutool的ExcelWriter十几行代码就能把List数据导出成xlsx文件下载前端放一个“导出Excel”按钮后端返回ResponseEntitybyte[]设置响应头即可。这个功能看起来不起眼但很实用也方便写进“项目亮点”。6. 最后聊几句做这个项目的真实体会我自己把这套流程从零到部署跑了不止一遍踩坑最多的反而不在代码本身而在于环境不一致和细节忽略。比如上传路径在Windows下开发时写的D:/xxx部署到Linux忘记改结果图片上传报错比如改了数据库密码忘了改application.yml启动报权限异常。这些问题的共同特点就是配置和代码要解耦能放配置文件的属性就不要硬编码在Java代码里。另外做这种管理系统我建议你拿到源码后不要只看完就了事最好能手动跟着敲一遍核心模块。尤其是用户登录、食谱发布、分页查询这三个模块几乎涵盖了SpringBoot后端开发的常用知识点依赖注入、事务管理、参数校验、异常处理、ORM操作。敲完这三个模块你对SpringBoot的理解会上一个台阶。这个项目后续还可以继续扩展的地方很多比如接入Redis做缓存、用Spring Security做更细粒度的权限控制、部署到Docker容器里。如果你想拿它当毕设建议至少再加一个新功能点不要和原始的“健体食谱管理”完全一样比如追加“每日膳食计划”模块或者“社区分享”功能工作量不大但一查重就显得原创度更高。希望这篇分享能帮你把这个项目真正跑起来、改成你自己的东西。有问题欢迎在评论区交流我看到都会回复。