☰
SpringBoot+MyBatis Plus开发家庭食谱管理系统实战教程
2026/10/1 12:37:14 网站建设 项目流程

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.6+
Lombok简化实体类记得配注解处理器

有个细节要单说: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、role(1为管理员,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(浏览量)、status(0为草稿,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.example,Artifact填recipe-management,Java版本选8,依赖勾选Spring Web、Thymeleaf、MySQL Driver、Lombok。生成完成后,在pom.xml里额外加MyBatis Plus的依赖:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.20</version> </dependency>

Hutool是我个人很喜欢的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?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/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/

serverTimezone=Asia/Shanghai这个参数必须加,不加就会报时区错误或者日期差8小时。useUnicode=true&characterEncoding=utf8保证中文不乱码。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) { Page<RecipeVO> 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.port=8080 > app.log 2>&1 &

如果需要开机自启,可以写一个systemd服务文件,或者更简单点用crontab的@reboot命令。数据库在上线前要把application.yml里的localhost改成服务器内网或公网地址,file.upload-path改成Linux路径,比如/home/ubuntu/recipe-upload/,并且要提前建好目录,否则上传图片时会报“系统找不到指定路径”。

4. 常见问题与排错实录

4.1 数据库连接失败与账号权限问题

报错信息里出现Access denied for user 'root'@'localhost',肯定是用户名或密码错了,去MySQL里执行ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';。还有一种情况是MySQL 8.0默认用了caching_sha2_password认证插件,而旧版驱动不支持,你会在启动时看到Unable to load authentication plugin,解决方法是把驱动升级到mysql-connector-java8.0以上,或者执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';切换认证方式。

4.2 中文乱码与时间8小时偏移

页面中文乱码,优先检查application.yml里有没有characterEncoding=utf8,没有就加上重启。如果请求参数中文乱码,需要在配置里加一个CharacterEncodingFilter的Bean,或者在application.yml配置server.servlet.encoding.force: true。时间差8小时问题,十有八九是serverTimezone没设置,设置成Asia/Shanghai之后重启就好。

4.3 Maven依赖下载失败与jar包冲突

依赖下载失败基本都是网络问题。国内用户务必配置阿里云镜像,找到Maven的settings.xml,在mirrors节点下加:

<mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <name>Aliyun Maven Mirror</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

jar包冲突在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”按钮,后端返回ResponseEntity<byte[]>设置响应头即可。这个功能看起来不起眼,但很实用,也方便写进“项目亮点”。

6. 最后聊几句做这个项目的真实体会

我自己把这套流程从零到部署跑了不止一遍,踩坑最多的反而不在代码本身,而在于环境不一致和细节忽略。比如上传路径在Windows下开发时写的D:/xxx,部署到Linux忘记改,结果图片上传报错;比如改了数据库密码,忘了改application.yml,启动报权限异常。这些问题的共同特点就是:配置和代码要解耦,能放配置文件的属性就不要硬编码在Java代码里。

另外,做这种管理系统,我建议你拿到源码后不要只看完就了事,最好能手动跟着敲一遍核心模块。尤其是用户登录、食谱发布、分页查询这三个模块,几乎涵盖了SpringBoot后端开发的常用知识点:依赖注入、事务管理、参数校验、异常处理、ORM操作。敲完这三个模块,你对SpringBoot的理解会上一个台阶。

这个项目后续还可以继续扩展的地方很多,比如接入Redis做缓存、用Spring Security做更细粒度的权限控制、部署到Docker容器里。如果你想拿它当毕设,建议至少再加一个新功能点,不要和原始的“健体食谱管理”完全一样,比如追加“每日膳食计划”模块或者“社区分享”功能,工作量不大但一查重就显得原创度更高。

希望这篇分享能帮你把这个项目真正跑起来、改成你自己的东西。有问题欢迎在评论区交流,我看到都会回复。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询