这可能是园区类美食平台最值得参考的一套前后端分离实现
今年在整理一个校园周边美食探索及分享平台时,我把整套技术栈定在了SpringBoot+Vue3+MyBatis+MySQL上。市面上的校园外卖、二手交易项目很多,但专门针对“校园周边美食探索+用户分享”这个细分场景做的前后端分离系统其实不算多,这套源码从功能设计到落地的踩坑过程,我觉得很值得拿出来从头讲一遍。
这套系统解决的核心问题很聚焦:学校周边的美食店铺分散、评价信息零散,学生想找“附近有什么好吃的、哪家评价靠谱、有没有人晒过实拍图”完全靠口口相传。平台把这些需求线上化:店铺信息聚合、菜单展示、用户发布美食分享帖、评论互动、收藏点赞,再加上后台对店铺和内容的管理。如果你正在做毕业设计、课设,或者想找一套Java全栈项目练手并实际把它跑起来,这篇文章应该能帮你省掉大量的试错时间。
我尽量不写流水账,把标题里提到的技术栈怎么落地、关键表结构怎么设计、哪些api实现起来最绕、前台和后台联调时有哪些坑,全部拆开讲。
1. 项目要解决的核心问题与用户链路设计
1.1 校园美食场景下的真实需求梳理
做这类平台最容易犯的错误,是上来就堆功能:购物车、订单、支付、骑手配送全加上。但“校园周边美食探索及分享平台”的定位不是外卖点餐,它更像一个校园版的大众点评+内容社区。
我梳理需求时先列了几个核心问题:
- 学生怎么发现学校周边有哪些店?—— 需要店铺列表、分类筛选、关键词搜索。
- 怎么判断一家店值不值得去?—— 需要用户评分、评论、实拍图分享。
- 用户凭什么愿意来这个平台?—— 需要有“分享”的激励场景:发帖子、晒探店图、被点赞收藏。
- 内容怎么保证不失控?—— 需要后台管理端,能审核店铺和帖子。
所以功能边界很清晰:C端(用户端)负责浏览店铺、查看详情、发布分享动态、互动;B端(管理端)负责店铺管理、分类管理、用户管理、内容审核。不需要做订单支付,把“探索+分享”这两个核心动作做扎实就够了。
1.2 用户端与管理端功能边界
我把整个系统拆成两个端,对应两套Vue3应用和两套后端接口模块:
用户端(前台)核心功能:
- 注册、登录(手机号+密码,JWT鉴权)
- 首页店铺推荐列表(按评分、距离、销量维度)
- 店铺分类浏览(快餐、火锅、奶茶、小吃等)
- 店铺详情:菜单、评分、评论列表、位置信息
- 探店分享帖:发布、浏览、点赞、收藏、评论
- 个人中心:我发布的帖子、我的收藏、我的评论记录
管理端(后台)核心功能:
- 管理员登录(独立账号体系)
- 店铺管理:审核、上架下架、编辑店铺信息
- 分类管理:维护店铺分类
- 分享帖管理:删除违规帖子
- 用户管理:查看用户列表、禁用账号
这套权限模型非常简单直接:前端控制路由跳转,后端通过拦截器校验JWT并区分角色。对比那些动辄引入SpringSecurity+OAuth2的重量级方案,这种轻量实现更容易读懂,也更符合“平台系统源码”的教学和扩展场景。
1.3 核心链路:从“发现店铺”到“分享种草”的闭环
这个平台最有意思的部分是业务闭环,用户的行为不是单向的“搜-看-走”,而是可以形成循环:
- 新用户进入首页,看到评分高的店铺推荐列表。
- 点进店铺详情,看到菜单、评论和用户发的探店帖。
- 到店消费后,用户在平台发布一条带图的分享帖。
- 其他用户刷到帖子,点赞、收藏、评论,同时被种草这家店。
- 帖子热度影响店铺评分和推荐权重,形成新一轮曝光。
我在实现时,让“分享帖”成为连接用户和店铺的桥梁,而不是割裂的两个模块。帖子表里直接冗余了店铺ID,用户发帖时可以关联探店对应的店铺。这样一个简单的设计就让首页既能按店铺维度推荐,也能按内容维度刷动态,后面做Feed流也留了扩展空间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 后端落地:SpringBoot+MyBatis的选型理由与分层设计
2.1 为什么选SpringBoot 2.7而不是SpringBoot 3.x
写代码之前先卡版本。说实话,如果从零学SpringBoot,直接上3.x没有太大问题,但这个项目我坚持用了SpringBoot 2.7.x,原因是它刚好处于一个非常舒适的兼容区间:
- JDK 8 / 11 / 17 都能跑,不用被强制上17。
- MyBatis、MyBatis-Plus、Druid、JWT等生态组件兼容性非常成熟。
- 绝大多数网上资料和教程都是基于2.x,排查问题容易得多。
如果你用的是SpringBoot 3.x,需要注意它基于Jakarta EE,javax.servlet要改成jakarta.servlet,很多老版本第三方库会直接无法兼容。这项目里如果当时选3.x,光是适配这些依赖就得额外消耗一两天。
我推荐跟着我这边的配置走:JDK 8 + SpringBoot 2.7.14 + MyBatis-Plus 3.5.3.1 + MySQL 8.0。
2.2 Maven多模块还是单模块?我选择了更直观的单模块
项目结构这块我犹豫过。理论上前后端分离项目,后端可以拆成common、system、business多个Maven模块,但考虑到这个项目是要给别人看源码、用来学习或做毕设的,单模块SpringBoot工程反而更直观。读者打开项目后,一眼就能看完controller、service、mapper分层的全貌。
我的包结构是这样的:
code复制com.campus.food
├── config # 配置类:跨域、拦截器、MyBatisPlus分页、文件上传
├── controller # 接口层:用户端controller / admin端controller
├── entity # 实体类
├── mapper # MyBatis Mapper接口
├── service # 业务逻辑层
│ └── impl
├── dto # 接收参数的对象
├── vo # 返回给前端的数据对象
├── utils # JWT、MD5等工具类
├── common # 统一返回结果、异常处理
└── FoodApplication.java
实体类用驼峰命名,表字段用下划线,MyBatis-Plus开启驼峰映射后无感转换。Mapper层基于MyBatis-Plus的BaseMapper,单表CRUD基本不用写SQL,复杂查询用@Select注解配合XML,兼顾开发速度和SQL可控性。
2.3 MyBatis与MyBatis-Plus:不只是插件那么简单
标题里写的是MyBatis,实际上我引入了MyBatis-Plus增强包。这不是偷懒,而是基于一个非常实际的判断:这个业务场景中80%的数据库操作是单表CRUD,用原生MyBatis手写这些SQL完全是在浪费时间。
MyBatis-Plus让我省力的几个点:
BaseMapper<T>直接提供insert、deleteById、selectById、updateById,单表操作一行代码都不用写SQL。- 分页插件
PaginationInnerInterceptor,配合Page<T>对象几行代码搞定分页。 LambdaQueryWrapper用lambda表达式构造查询条件,字段名从代码级硬编码变成编译期可检查,改表字段名不会因为SQL字符串拼错而线上炸掉。
举一个店铺列表分页的实际例子:
java复制@Override
public Page<ShopVO> getShopPage(int pageNum, int pageSize, String categoryId, String keyword) {
Page<Shop> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Shop> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasText(categoryId), Shop::getCategoryId, categoryId)
.like(StringUtils.hasText(keyword), Shop::getName, keyword)
.eq(Shop::getStatus, 1)
.orderByDesc(Shop::getRating);
Page<Shop> shopPage = shopMapper.selectPage(page, wrapper);
// 转换为VO并填
