原创

基于springboot2+vue2的美食推荐商城

温馨提示:
本文最后更新于 2026年05月28日,已超过 12 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

1. 项目简介

美食推荐商城是一个基于 Spring Boot + MyBatis Plus 的后端服务与 Vue.js(后台管理) + LayUI(前台展示) 的前端界面构成的 B2B2C(商家对用户) 型电商平台。系统面向三类角色:

  • 管理员:对平台进行整体管理,包括基础数据维护(公告类型、美食类型、会员等级)、公告管理、留言板管理、美食及订单管理、商家管理、用户管理等。
  • 商家:可以上架/下架自己的美食,查看订单,回复用户评价,管理商家基本信息。
  • 用户:浏览美食、收藏、加入购物车、下单(支持余额或积分支付)、评价订单、留言反馈、个人中心充值等。

系统核心业务流程:
用户注册登录 → 浏览美食列表/详情 → 收藏或加入购物车 → 结算下单 → 支付(余额/积分) → 商家确认 → 用户收货 → 评价 → 完成。


2. 技术栈

2.1 后端

技术 版本/说明
Spring Boot 2.2.2.RELEASE
MyBatis Plus 2.3 + 1.0.5 启动器
数据库 MySQL 5.7.32
数据库连接池 HikariCP(Spring Boot默认)
权限控制 基于 Token(自定义拦截器 + Redis?无Redis,使用数据库token表) + Shiro(依赖引入但未使用,实际使用自定义AuthorizationInterceptor
工具库 Hutool、Fastjson、Apache Commons Lang3、Poi(Excel导入导出))
定时任务 无,但提供线程基类 MyThreadMethod 用于后台常驻任务(如自动状态变更)

2.2 前端

2.2.1 后台管理(admin)

  • Vue.js 2.x
  • Element UI(组件库)
  • Vue Router(静态路由)
  • Axios(封装为 http.js
  • ECharts(图表统计)
  • Vuex(未使用,采用 localStorage 管理状态)

2.2.2 用户前台(front)

  • 原生 HTML / CSS / JavaScript
  • LayUI(轮播图、弹层、分页等)
  • jQuery
  • Swiper(部分轮播)
  • Vue.js(仅用于部分页面数据绑定,非单页应用)
  • Element UI(部分页面如留言板、订单弹窗)

2.3 其他

  • 构建工具:Maven
  • 项目打包:JAR(Spring Boot 内置 Tomcat)
  • 数据库初始化:提供 db.sql 文件

3. 详细介绍

3.1 数据库设计

表名 说明
yonghu 用户表(账户、密码、姓名、手机、身份证、头像、性别、邮箱、余额、积分、会员等级)
shangjia 商家表(账户、密码、商家名称、联系方式、邮箱、营业执照、星级、余额)
meishi 美食表(所属商家、名称、照片、类型、库存、购买获得积分、原价、现价、点击次数、上下架状态)
meishi_order 订单表(订单号、美食、用户、购买数量、实付价格、订单状态、支付类型、创建时间)
cart 购物车表(用户、美食、购买数量)
meishi_collection 收藏表(用户、美食、收藏类型)
meishi_commentback 评价表(美食、用户、评价内容、回复内容)
liuyan 留言板(用户、留言标题、内容、回复)
gonggao 公告表(标题、图片、类型、内容、发布时间)
dictionary 字典表(统一管理枚举值:性别、会员等级、商家星级、美食类型、订单状态等)
config 配置表(轮播图路径等)
token Token 管理表(用户id、用户名、表名、角色、token、过期时间)
users 管理员表(用户名、密码、角色)

3.2 核心功能模块

3.2.1 用户端(前台)

  • 注册 / 登录:支持用户名/密码登录,注册时验证手机号、身份证唯一性。
  • 首页:轮播图、美食推荐列表、公告展示、商家推荐。
  • 美食浏览:按类型筛选、关键词搜索、点击查看详情(浏览次数+1)。
  • 购物车:添加美食(限购数量不超过库存)、修改数量、删除、统一下单。
  • 订单流程
    • 确认订单(选择支付方式:余额或积分)
    • 支付(折扣根据会员等级自动计算)
    • 商家确认(后台操作,此处前端暂未实现商家确认按钮?订单类型4表示已确认)
    • 用户收货(状态变为5)
    • 评价(状态变为1)
  • 个人中心:修改资料、上传头像、充值余额、查看收藏、订单列表(可按状态筛选)。
  • 留言板:发表留言,管理员后台回复。
  • 公告:查看平台公告。

3.2.2 商家端(后台)

  • 美食管理:添加/修改/删除自己的美食,管理库存、价格、上下架。
  • 订单管理:查看购买自己美食的订单,可以确认订单(deliver 接口将订单状态改为4)。
  • 评价管理:查看用户对自己美食的评价,并可回复。
  • 基本信息:修改商家资料、营业执照、星级等。

3.2.3 管理员端(后台)

  • 基础数据管理:管理字典表(公告类型、会员等级、美食类型等)。
  • 公告管理:发布、编辑、删除公告。
  • 留言板管理:回复用户留言。
  • 美食管理:全平台美食的查看、删除(逻辑删除)。
  • 订单管理:查看所有订单、退款审核(退款接口可还原库存和金额)。
  • 商家管理:查看、添加、删除商家,重置商家密码。
  • 用户管理:查看、添加、删除用户,重置密码。
  • 轮播图管理:配置首页轮播图图片路径。

3.3 业务规则

  • 会员等级:根据总积分自动升级(青铜 < 10000、白银 < 100000、黄金 ≥ 100000),不同等级享受不同折扣(字典表 beizhu 字段存储折扣系数,如青铜0.98)。
  • 支付方式
    • 余额支付:扣除用户余额,增加商家余额,同时增加用户积分(购买美食时获得的积分)。
    • 积分支付:扣除用户当前积分(yonghu_new_jifen),不增加积分。
  • 退款:用户可对“已支付”的订单申请退款,恢复库存、退还用户金额(或积分)、扣减商家余额、扣回已获得的积分。
  • 库存扣减:下单时(订单创建即支付)立即扣减库存,退款时恢复。
  • 订单状态流转
    • 3-已支付 → 4-已确认(商家操作) → 5-已收到(用户操作) → 1-已评价(用户操作)。
    • 2-退款(用户申请退款后状态变为2)。

3.4 接口设计(RESTful)

后端Controller示例(部分):

  • /yonghu/login(登录)、/yonghu/register(注册)
  • /meishi/list(美食列表)、/meishi/detail/{id}(详情,自动+1点击量)
  • /cart/save/cart/update/cart/delete
  • /meishiOrder/order(批量下单)、/meishiOrder/refund(退款)、/meishiOrder/receiving(确认收货)、/meishiOrder/commentback(评价)
  • /shangjia/session(获取商家信息)

所有需要登录的接口均通过请求头 Token 校验(自定义拦截器 AuthorizationInterceptor)。

3.5 安全性

  • 登录生成随机32位Token,存入数据库并返回客户端,后续请求携带。
  • Token有效期为1小时,过期需重新登录。
  • 拦截器对所有请求进行Token验证(除 @IgnoreAuth 标记的方法外)。
  • 密码明文存储(示例中未加密,实际生产应加密)。
  • 逻辑删除:meishi_deleteshangjia_delete 字段标记删除,查询时过滤已删除数据。

4. 部分代码

4.1 后端 – 订单下单核心逻辑(MeishiOrderController.order

@RequestMapping("/order")
public R add(@RequestParam Map<String, Object> params, HttpServletRequest request){
    // 获取当前登录用户、支付方式、购物车商品列表
    Integer userId = (Integer) request.getSession().getAttribute("userId");
    Integer meishiOrderPaymentTypes = Integer.valueOf(String.valueOf(params.get("meishiOrderPaymentTypes")));
    String data = String.valueOf(params.get("meishis"));
    JSONArray jsonArray = JSON.parseArray(data);
    List<Map> meishis = JSON.parseObject(jsonArray.toString(), List.class);

    YonghuEntity yonghuEntity = yonghuService.selectById(userId);
    // 计算会员折扣
    BigDecimal zhekou = getDiscount(yonghuEntity.getHuiyuandengjiTypes());

    List<MeishiOrderEntity> meishiOrderList = new ArrayList<>();
    List<ShangjiaEntity> shangjiaList = new ArrayList<>();
    List<MeishiEntity> meishiList = new ArrayList<>();
    List<Integer> cartIds = new ArrayList<>();

    for (Map<String, Object> map : meishis) {
        Integer meishiId = Integer.valueOf(String.valueOf(map.get("meishiId")));
        Integer buyNumber = Integer.valueOf(String.valueOf(map.get("buyNumber")));
        MeishiEntity meishiEntity = meishiService.selectById(meishiId);
        // 库存校验
        if(meishiEntity.getMeishiKucunNumber() < buyNumber){
            return R.error(meishiEntity.getMeishiName()+"的库存不足");
        }
        meishiEntity.setMeishiKucunNumber(meishiEntity.getMeishiKucunNumber() - buyNumber);
        // 订单对象组装
        MeishiOrderEntity order = new MeishiOrderEntity();
        order.setMeishiOrderUuidNumber(String.valueOf(new Date().getTime()));
        order.setMeishiId(meishiId);
        order.setYonghuId(userId);
        order.setBuyNumber(buyNumber);
        order.setMeishiOrderTypes(3);
        order.setMeishiOrderPaymentTypes(meishiOrderPaymentTypes);
        order.setInsertTime(new Date());

        if(meishiOrderPaymentTypes == 1){ // 余额支付
            Double money = meishiEntity.getMeishiNewMoney() * buyNumber * zhekou.doubleValue();
            // 余额不足校验...
            yonghuEntity.setNewMoney(yonghuEntity.getNewMoney() - money);
            // 增加积分...
            order.setMeishiOrderTruePrice(money);
            // 增加商家余额
            ShangjiaEntity shangjia = shangjiaService.selectById(meishiEntity.getShangjiaId());
            shangjia.setNewMoney(shangjia.getNewMoney() + money);
            shangjiaList.add(shangjia);
        }
        meishiOrderList.add(order);
        meishiList.add(meishiEntity);
        if(map.containsKey("id")) cartIds.add(Integer.valueOf(map.get("id").toString()));
    }
    // 批量保存订单、更新商品、更新商家、更新用户、清空购物车
    meishiOrderService.insertBatch(meishiOrderList);
    meishiService.updateBatchById(meishiList);
    shangjiaService.updateBatchById(shangjiaList);
    yonghuService.updateById(yonghuEntity);
    if(cartIds.size()>0) cartService.deleteBatchIds(cartIds);
    return R.ok();
}

4.2 前端 – 美食详情页(meishi/detail.html) – 收藏与购物车

// 收藏/取消收藏
addMeishiCollection() {
    let _this = this;
    layui.http.request('meishiCollection/list', 'get', {
        meishiId: _this.detail.id,
        yonghuId: localStorage.getItem('userid'),
    }, (res) => {
        if (res.data.list.length == 1) {
            layui.http.requestJson('meishiCollection/delete', 'post', [res.data.list[0].id], function (res) {
                layer.msg('取消成功', { time: 1000, icon: 5 }, function () { window.location.reload(); });
            });
        } else {
            layui.http.requestJson('meishiCollection/add', 'post', {
                yonghuId: localStorage.getItem('userid'),
                meishiId: _this.detail.id,
                meishiCollectionTypes: 1,
            }, function (res) {
                layer.msg('收藏成功', { time: 1000, icon: 6 }, function () { window.location.reload(); });
            });
        }
    });
},
// 添加到购物车
addMeishiCart(){
    if (this.detail.meishiKucunNumber < this.buyNumber) {
        layer.msg(`库存不足`, { time: 2000, icon: 5 }); return;
    }
    layui.http.request('cart/list', 'get', {
        yonghuId: localStorage.getItem('userid'),
        meishiId : this.detail.id,
    }, (res) => {
        if(res.data.list.length > 0){
            layer.msg("该美食已经添加到购物车", { time: 2000, icon: 5 }); return;
        }
        layui.http.requestJson('cart/add', 'post', {
            yonghuId : localStorage.getItem('userid'),
            meishiId : this.detail.id,
            buyNumber: this.buyNumber,
        }, (res) => {
            if(res.code==0) layer.msg('添加成功', { time: 2000, icon: 6 });
            else layer.msg(res.msg, { time: 2000, icon: 2 });
        });
    });
}

4.3 MyBatis Plus 分页查询配置(MeishiDao.xml

<select id="selectListView" parameterType="map" resultType="com.entity.view.MeishiView">
    SELECT a.*, shangjia.shangjia_name as shangjiaName, ...
    FROM meishi a LEFT JOIN shangjia ON a.shangjia_id = shangjia.id
    <where>
        <if test="params.shangjiaId != null">a.shangjia_id = #{params.shangjiaId}</if>
        <if test="params.meishiName != null and params.meishiName != ''">
            a.meishi_name like CONCAT('%',#{params.meishiName},'%')
        </if>
        ...
    </where>
    order by a.${params.orderBy} desc
</select>

5. 部分截图

img1
img2
img3
img4
img5
img6
img7
img8
img9
img10
img11
img12
img13
img14
img15
img16
img17
img18
img19
img20
img21
img22
img23
img24
img25
img26
img27
img28
img29
img30
img31
img32
img33
img34
img35
img36
img37


6. 项目总结

本项目实现了一个功能完整的美食推荐商城系统,覆盖了电商平台常见的用户、商家、管理员三端操作,包含商品管理、购物车、订单、支付(余额/积分)、会员折扣、评价留言、公告发布等核心模块。后端采用 Spring Boot + MyBatis Plus 架构,代码分层清晰(Controller、Service、Dao、Entity、View),通过自定义 Token 拦截器实现无状态认证,支持跨域。前端后台使用 Vue + Element UI,前台使用 LayUI + 原生 JS,实现了良好的用户体验。

总体而言,该系统适合作为毕业设计、课程设计或小型电商平台的起步模板,具备良好的扩展性和参考价值。

7. 资源

https://fifteen.xiaobias.com/source/195

正文到此结束