基于 Spring Boot + Vue 的 AI 智能在线订餐系统:从点餐闭环到语义推荐
本文介绍一个由我完成的前后端分离在线订餐项目。系统采用 Spring Boot + Vue 2 + MySQL 开发,在常规点餐业务之外,还加入了多角色后台、WebSocket 在线沟通、协同过滤推荐,以及基于阿里云百炼 Embedding 的自然语言菜品推荐。
一、项目背景
传统在线订餐系统通常只解决"查看菜品---加入购物车---提交订单"这条基础链路。为了让项目更接近真实业务,我在此基础上补充了用户、商家和管理员三类角色,并将优惠券、积分、余额、资讯、评价、收藏、实时聊天等模块串联起来。
项目的另一个重点是智能推荐。用户不仅可以按照分类、价格、点击量和收藏量筛选菜品,还可以直接输入自然语言需求,例如:
text
想吃清淡一点的素菜,最好烹饪时间不要太长
想找适合两个人吃的海鲜自助
推荐一些香辣口味的中式菜
后端会把用户描述转换为向量,与菜品向量计算语义相似度,再返回匹配度最高的菜品。
二、系统效果展示
1. 用户端首页
首页使用轮播图和"关于我们"区域展示平台信息,顶部导航可以进入菜品、美食资讯、购物车、在线客服和个人中心。

2. 菜品检索与 AI 推荐
菜品列表支持名称、价格区间、菜品分类等条件查询,同时可以按照价格、点击量、收藏数排序。页面右侧是 AI 推荐面板,输入需求后即可获取语义匹配结果。

3. 菜品详情
详情页展示菜品图片、价格、积分、库存、口味、烹饪时长、所属商家等信息,并提供收藏、加入购物车、立即购买、积分兑换、联系商家和富文本评价等功能。

4. 实时在线沟通
用户可以在菜品详情页联系商家,发送文字、表情和商品信息。通信层使用 WebSocket,实现消息的实时推送。

5. 购物车与订单确认
购物车支持勾选商品、修改数量、移除商品和金额汇总。进入结算页后,可以选择收货地址、确认商品清单、填写订单备注,并使用余额或积分完成业务流程。


6. 美食资讯与个人中心
资讯模块包含分类筛选、关键字搜索、热门排行、浏览、点赞和收藏等功能。个人中心集中管理资料、密码、聊天记录、优惠券、订单、地址和收藏。


7. 管理员端与商家端
管理员可以维护用户、商家、菜品分类、菜品、优惠券、充值记录、轮播图、资讯、订单和系统信息;商家则主要维护自己的菜品、优惠券、聊天和订单。


系统登录页支持管理员和商家角色选择,商家也可以通过入口完成注册。

三、技术选型
| 层次 | 技术 | 作用 |
|---|---|---|
| 用户前台 | Vue 2.6、Element UI、Vue Router、Vuex | 页面组件、路由和状态管理 |
| 管理后台 | Vue 2.6、Element UI | 管理员与商家业务界面 |
| 网络请求 | Axios | 调用后端接口 |
| 内容组件 | Swiper、Vue Quill Editor | 轮播图与富文本编辑 |
| 后端框架 | Spring Boot 2.2.2 | Web 服务与业务组织 |
| 持久层 | MyBatis、MyBatis-Plus 2.3 | 数据访问、分页和条件查询 |
| 权限认证 | Apache Shiro | 登录与接口权限控制 |
| 实时通信 | WebSocket | 用户与商家实时聊天 |
| 数据库 | MySQL | 保存业务数据 |
| AI 能力 | DashScope SDK、text-embedding-v4 | 文本向量生成与语义推荐 |
| 开发环境 | JDK 8、Maven、Node.js、npm | 项目构建与运行 |
四、系统架构
项目采用典型的前后端分离结构,用户前台与后台管理端共用一套 Spring Boot 接口。
#mermaid-svg-rn6EGUe3HbH8n2IY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rn6EGUe3HbH8n2IY .error-icon{fill:#552222;}#mermaid-svg-rn6EGUe3HbH8n2IY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rn6EGUe3HbH8n2IY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rn6EGUe3HbH8n2IY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rn6EGUe3HbH8n2IY .marker.cross{stroke:#333333;}#mermaid-svg-rn6EGUe3HbH8n2IY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rn6EGUe3HbH8n2IY p{margin:0;}#mermaid-svg-rn6EGUe3HbH8n2IY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster-label text{fill:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster-label span{color:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster-label span p{background-color:transparent;}#mermaid-svg-rn6EGUe3HbH8n2IY .label text,#mermaid-svg-rn6EGUe3HbH8n2IY span{fill:#333;color:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY .node rect,#mermaid-svg-rn6EGUe3HbH8n2IY .node circle,#mermaid-svg-rn6EGUe3HbH8n2IY .node ellipse,#mermaid-svg-rn6EGUe3HbH8n2IY .node polygon,#mermaid-svg-rn6EGUe3HbH8n2IY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rn6EGUe3HbH8n2IY .rough-node .label text,#mermaid-svg-rn6EGUe3HbH8n2IY .node .label text,#mermaid-svg-rn6EGUe3HbH8n2IY .image-shape .label,#mermaid-svg-rn6EGUe3HbH8n2IY .icon-shape .label{text-anchor:middle;}#mermaid-svg-rn6EGUe3HbH8n2IY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rn6EGUe3HbH8n2IY .rough-node .label,#mermaid-svg-rn6EGUe3HbH8n2IY .node .label,#mermaid-svg-rn6EGUe3HbH8n2IY .image-shape .label,#mermaid-svg-rn6EGUe3HbH8n2IY .icon-shape .label{text-align:center;}#mermaid-svg-rn6EGUe3HbH8n2IY .node.clickable{cursor:pointer;}#mermaid-svg-rn6EGUe3HbH8n2IY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rn6EGUe3HbH8n2IY .arrowheadPath{fill:#333333;}#mermaid-svg-rn6EGUe3HbH8n2IY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rn6EGUe3HbH8n2IY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rn6EGUe3HbH8n2IY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rn6EGUe3HbH8n2IY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rn6EGUe3HbH8n2IY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rn6EGUe3HbH8n2IY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster text{fill:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY .cluster span{color:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rn6EGUe3HbH8n2IY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rn6EGUe3HbH8n2IY rect.text{fill:none;stroke-width:0;}#mermaid-svg-rn6EGUe3HbH8n2IY .icon-shape,#mermaid-svg-rn6EGUe3HbH8n2IY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rn6EGUe3HbH8n2IY .icon-shape p,#mermaid-svg-rn6EGUe3HbH8n2IY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rn6EGUe3HbH8n2IY .icon-shape .label rect,#mermaid-svg-rn6EGUe3HbH8n2IY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rn6EGUe3HbH8n2IY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rn6EGUe3HbH8n2IY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rn6EGUe3HbH8n2IY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实时消息
实时消息
用户前台 Vue
Spring Boot API
商家/管理员后台 Vue
WebSocket
业务 Service
MyBatis-Plus
MySQL
DashScope Embedding
向量相似度排序 Top-K
后端按照 Controller、Service、DAO、Entity 分层组织。Controller 负责接收请求,Service 处理业务逻辑,DAO 与 Mapper 完成数据库访问,最终以统一响应对象返回给前端。
五、角色与功能设计
1. 普通用户
- 注册、登录、资料维护和密码修改;
- 按名称、分类、价格搜索和排序菜品;
- 查看菜品详情,收藏、评价、点赞或点踩评论;
- 领取优惠券,管理个人优惠券;
- 加入购物车、确认地址、创建订单和支付;
- 使用账户余额或积分,查看充值记录;
- 查看订单状态和历史订单;
- 与商家在线聊天;
- 浏览、点赞和收藏美食资讯;
- 使用 AI 自然语言推荐。
2. 商家
- 注册并等待审核;
- 维护店铺资料和菜品信息;
- 管理菜品上下架、库存、价格与积分;
- 发布和维护优惠券;
- 接收用户消息并实时沟通;
- 处理订单及查看不同状态的订单。
3. 管理员
- 管理用户与商家账号;
- 审核商家和业务数据;
- 管理菜品分类、菜品、评价和优惠券;
- 管理订单、充值记录和聊天信息;
- 维护轮播图、美食资讯、资讯分类、关于我们等内容。
六、核心业务流程
一笔订单的主要流程如下:
#mermaid-svg-C5hLLFHzV5aeAUUB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C5hLLFHzV5aeAUUB .error-icon{fill:#552222;}#mermaid-svg-C5hLLFHzV5aeAUUB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C5hLLFHzV5aeAUUB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C5hLLFHzV5aeAUUB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C5hLLFHzV5aeAUUB .marker.cross{stroke:#333333;}#mermaid-svg-C5hLLFHzV5aeAUUB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C5hLLFHzV5aeAUUB p{margin:0;}#mermaid-svg-C5hLLFHzV5aeAUUB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster-label text{fill:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster-label span{color:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster-label span p{background-color:transparent;}#mermaid-svg-C5hLLFHzV5aeAUUB .label text,#mermaid-svg-C5hLLFHzV5aeAUUB span{fill:#333;color:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB .node rect,#mermaid-svg-C5hLLFHzV5aeAUUB .node circle,#mermaid-svg-C5hLLFHzV5aeAUUB .node ellipse,#mermaid-svg-C5hLLFHzV5aeAUUB .node polygon,#mermaid-svg-C5hLLFHzV5aeAUUB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-C5hLLFHzV5aeAUUB .rough-node .label text,#mermaid-svg-C5hLLFHzV5aeAUUB .node .label text,#mermaid-svg-C5hLLFHzV5aeAUUB .image-shape .label,#mermaid-svg-C5hLLFHzV5aeAUUB .icon-shape .label{text-anchor:middle;}#mermaid-svg-C5hLLFHzV5aeAUUB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-C5hLLFHzV5aeAUUB .rough-node .label,#mermaid-svg-C5hLLFHzV5aeAUUB .node .label,#mermaid-svg-C5hLLFHzV5aeAUUB .image-shape .label,#mermaid-svg-C5hLLFHzV5aeAUUB .icon-shape .label{text-align:center;}#mermaid-svg-C5hLLFHzV5aeAUUB .node.clickable{cursor:pointer;}#mermaid-svg-C5hLLFHzV5aeAUUB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-C5hLLFHzV5aeAUUB .arrowheadPath{fill:#333333;}#mermaid-svg-C5hLLFHzV5aeAUUB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-C5hLLFHzV5aeAUUB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-C5hLLFHzV5aeAUUB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C5hLLFHzV5aeAUUB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-C5hLLFHzV5aeAUUB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C5hLLFHzV5aeAUUB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster text{fill:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB .cluster span{color:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-C5hLLFHzV5aeAUUB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-C5hLLFHzV5aeAUUB rect.text{fill:none;stroke-width:0;}#mermaid-svg-C5hLLFHzV5aeAUUB .icon-shape,#mermaid-svg-C5hLLFHzV5aeAUUB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C5hLLFHzV5aeAUUB .icon-shape p,#mermaid-svg-C5hLLFHzV5aeAUUB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-C5hLLFHzV5aeAUUB .icon-shape .label rect,#mermaid-svg-C5hLLFHzV5aeAUUB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C5hLLFHzV5aeAUUB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-C5hLLFHzV5aeAUUB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-C5hLLFHzV5aeAUUB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 加入购物车
立即购买
浏览/搜索菜品
查看详情
购买方式
购物车结算
确认订单
选择收货地址
优惠券/积分/余额处理
创建订单
商家处理
订单完成
订单数据不仅记录订单号、商品、数量、原价和实付金额,还关联用户、商家、地址、联系方式、优惠券和订单状态,使用户端查询与商家端处理能够使用同一套数据模型。
七、AI 推荐的实现思路
1. 菜品向量初始化
系统启动后会异步读取已上架菜品,将菜品名称、分类、口味、烹饪时长、主要食材和菜品介绍拼接为文本,再分批调用 text-embedding-v4 生成向量。每批最多处理 10 条,并将结果保存到菜品表的 embedding 字段中。
示意文本如下:
text
菜品名称:宫保鸡丁,分类:中式菜肴,口味:麻辣,
烹饪时长:25分钟,食材主料:鸡胸肉、花生米,
菜品介绍:经典川味,鸡肉滑嫩,花生酥脆,麻辣鲜香
向量初始化任务采用异步线程执行,不阻塞 Spring Boot 主启动流程。
2. 用户查询向量化
用户提交自然语言需求后,请求会进入 /dashscope/chat 接口。后端先筛选"已上架且存在向量"的菜品,再为用户问题生成查询向量。
核心过程可以概括为:
java
List<CaipinxinxiEntity> candidates = dishService.selectAllByQuery(wrapper);
float[] queryVector = dashScopeService.getQueryEmbedding(question);
List<CaipinxinxiEntity> result =
dashScopeService.getEmbeddingTopk(candidates, queryVector, 5);
3. 余弦相似度与 Top-K
系统逐一计算查询向量和候选菜品向量的余弦相似度:
text
similarity(A, B) = (A · B) / (||A|| × ||B||)
相似度越高,表示菜品描述与用户需求在语义上越接近。计算完成后按得分降序排序,取前 5 个结果返回前端。
这种方式比单纯的 LIKE 查询更灵活。例如用户输入"想吃暖胃又不油腻的东西",即使菜品名称中没有这些字,模型仍可根据菜品描述和食材语义找到合适结果。
4. 基于订单的协同过滤
除了主动输入需求,项目还实现了基于用户的协同过滤:根据历史订单构建"用户---菜品"偏好矩阵,计算用户之间的相似度,再从相似用户购买过、当前用户尚未购买的菜品中生成推荐。
当推荐结果不足时,接口会使用其他已上架菜品补齐列表,从而兼顾个性化推荐与冷启动场景。
因此,系统中存在两类互补的推荐能力:
- 语义推荐:理解用户当下输入的自然语言需求;
- 协同推荐:利用历史订单发现相似用户的消费偏好。
八、WebSocket 实时聊天
后端通过 @ServerEndpoint("/ws") 提供 WebSocket 连接,并使用线程安全的 ConcurrentHashMap 保存在线客户端。连接参数中包含发送方与接收方 ID,消息到达服务端后会被推送给指定用户;同时保留 ping/pong 心跳处理。
java
@OnMessage
public void onMessage(String message) {
if ("ping".equals(message)) {
sendMessage("pong", fromUserId, toUserId);
} else {
sendMessage(message, fromUserId, toUserId);
}
}
业务消息还会写入聊天相关数据表,既保证在线时的实时体验,也支持用户之后查看历史聊天记录。
九、数据库设计
项目数据库包含 22 张主要业务表,核心表如下:
| 数据表 | 说明 |
|---|---|
yonghu |
用户账号、资料、积分与余额 |
shangjia |
商家账号、门店资料、审核状态与余额 |
users |
管理员账号 |
caipinfenlei |
菜品分类 |
caipinxinxi |
菜品详情、库存、价格、积分、上下架和向量 |
cart |
购物车 |
orders |
订单主数据与状态 |
address |
用户收货地址 |
coupon / mycoupon |
优惠券及用户领券记录 |
chargerecord |
充值记录 |
storeup |
收藏记录 |
discusscaipinxinxi |
菜品评论、点赞与点踩 |
chat / chatmessage / friend |
客服与用户、商家聊天关系 |
news / newstype |
美食资讯及分类 |
config |
轮播图配置 |
aboutus / systemintro |
平台介绍信息 |
token |
登录令牌 |
十、项目目录
text
onlineorder-serve-master
├─ db # MySQL 脚本
├─ upload # 示例上传资源
├─ src/main/java/com
│ ├─ controller # 接口控制器
│ ├─ service # 业务服务
│ ├─ dao # 数据访问层
│ ├─ entity # 实体、VO、View、Model
│ ├─ dashscope # Embedding 与语义推荐
│ ├─ ws # WebSocket
│ ├─ config # 框架配置
│ └─ utils # 工具与协同过滤算法
└─ src/main/resources
├─ mapper # MyBatis XML
└─ application.yml # 服务配置
onlineorder-web-master
├─ front # 用户前台,默认端口 8082
└─ admin # 管理员/商家后台,默认端口 8081
十一、本地运行
1. 环境准备
建议准备以下环境:
- JDK 8;
- Maven 3.6 或更高版本;
- MySQL 5.7/8.0;
- Node.js 12/14(项目使用较早版本的
node-sass,使用新版本 Node.js 时可能需要调整依赖); - npm。
2. 初始化数据库
创建数据库并导入项目 SQL:
sql
CREATE DATABASE db_bs2026_onlineorder
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
随后导入:
text
onlineorder-serve-master/db/db_bs2026_onlineorder.sql
3. 修改后端配置
编辑 src/main/resources/application.yml:
yaml
server:
port: 8080
servlet:
context-path: /onlineorder
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db_bs2026_onlineorder?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: 你的数据库密码
dashscope:
api_key: 你的阿里云百炼API-Key
model: text-embedding-v4
不要把真实 API Key 提交到公开仓库。更推荐通过环境变量或外部配置文件注入密钥。
4. 启动后端
bash
cd onlineorder-serve-master
mvn spring-boot:run
后端默认地址:http://localhost:8080/onlineorder
5. 启动用户前台
bash
cd onlineorder-web-master/front
npm install
npm run serve
用户前台默认地址:http://localhost:8082
6. 启动管理后台
bash
cd onlineorder-web-master/admin
npm install
npm run serve
管理后台默认地址:http://localhost:8081
SQL 示例数据中包含本地体验账号:
| 角色 | 账号 | 密码 |
|---|---|---|
| 管理员 | admin |
admin |
| 用户 | user001 |
123456 |
| 商家 | shangjia010 |
123456 |
这些账号仅用于本地演示。部署到公网前必须删除示例账号、修改默认密码,并对密码进行不可逆哈希存储。
十二、项目亮点总结
这个项目并非只有基本的增删改查,而是围绕订餐场景完成了多个业务模块的组合:
- 用户、商家、管理员三端角色和差异化菜单;
- 从菜品检索、购物车到订单处理的完整业务闭环;
- 优惠券、积分、余额、充值等营销与账户能力;
- WebSocket 用户---商家实时聊天;
- 富文本评价、点赞、点踩、收藏和资讯互动;
- 基于历史订单的用户协同过滤推荐;
- 基于 DashScope Embedding 的自然语言语义推荐;
- 异步批量生成菜品向量,降低初始化过程对启动的影响。
在开发过程中,我对前后端分离项目的权限设计、订单状态流转、关系型数据库建模、实时通信和 AI 能力接入都有了更完整的实践。后续还可以继续加入 Redis 缓存、消息队列、支付沙箱、配送轨迹、Elasticsearch 搜索,以及向量数据库等能力,让系统进一步向生产级架构演进。
如果这篇文章对你有帮助,欢迎点赞、收藏和交流。
推荐标签: Spring Boot、Vue、MySQL、Java、WebSocket、人工智能、推荐系统、毕业设计