尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
微信小程序点餐系统:Spring Boot+原生小程序实战
简介本资源是一套完整的微信点餐系统毕业设计开源方案面向计算机专业本科生及Java全栈初学者解决餐饮行业线上化点餐场景下的前后端协同开发实践问题。资源包含基于SSM框架的Java后端、微信小程序前端及配套文档覆盖开题报告、论文、PPT答辩稿、数据库SQL脚本与详细使用说明具备教学演示与二次开发双重价值。压缩包共1247个文件44.1MB核心类型包括119个Java业务逻辑文件、133个Vue组件与172个JS交互脚本辅以231张界面截图、84个WXML结构模板及86个WXSS样式文件完整呈现小程序后台管理双端架构另有bat部署脚本、SQL建库语句及.bak备份文件显著降低环境搭建门槛。目前已有123人学习下载适合用于课程设计参考、毕设选题复用或微信生态开发入门实战。1. 基于微信小程序的点餐系统不是“套模板”而是能跑通支付、订单、库存联动的真实闭环你手头正赶着毕业设计开题导师说“得有前后端、得有真实交互、最好带微信生态”——这时候搜到“微信小程序点餐系统Java源码”第一反应是又一个UI套壳点一下跳转、填个表单就完事我拆过37个标称“完整毕设”的小程序项目80%卡在「下单成功但数据库没写入」「微信支付回调不触发」「管理员后台改了菜品库存小程序端还是显示“有货”」这三处。这个资源不一样它用 Spring Boot MyBatis-Plus 搭建服务端小程序端用原生 WXML/WXSS非 uni-app 折中方案关键路径全部实装——从用户扫码进店 → 浏览带分类/搜索/排序的菜品列表 → 加入购物车支持多规格选配→ 提交订单含地址/备注/优惠券→ 微信 JSAPI 支付 → 后台实时接单打印 → 库存自动扣减 → 订单状态机驱动待支付/已支付/制作中/配送中/已完成/已取消→ 小程序端 WebSocket 推送状态变更。它不是教学 Demo而是一套被实际部署在校内食堂试运行过两周的最小可行系统MVP。适合计算机/软件工程专业本科生做毕设也适合作为 Java Web 小程序双端开发的入门实战靶场——因为所有“玄学失败点”都已在源码里埋了日志钩子和兜底校验。2. 服务端架构与核心模块Spring Boot 2.7.x MySQL 8.0 的稳态组合这套系统没有盲目追新服务端明确锁定 Spring Boot 2.7.x非 3.x原因很实在微信支付 SDK 官方 v3.4.0 版本对 Spring Boot 3.x 的响应式 WebFlux 支持不完善而 2.7.x 与微信支付官方 SDK 兼容性经过生产验证。数据库选用 MySQL 8.0 而非 SQLite 或 H2是因为订单并发写入、库存扣减的行锁机制必须依赖成熟关系型数据库的事务保障。整个后端采用分层结构Controller 层做参数校验与基础路由Service 层封装业务逻辑如OrderService.createOrder()内部串联库存检查、优惠券核销、订单生成、支付预下单Mapper 层用 MyBatis-Plus 自动生成 CRUD但关键方法如decreaseStock()手动编写 SQL 并加FOR UPDATE行锁避免超卖。下面直接看最常被问的两个模块实现。2.1 订单创建与库存扣减的原子性保障订单创建不是简单 insert 一条记录它必须和库存扣减强绑定。源码中OrderServiceImpl.createOrder()方法内部调用stockService.decreaseStock()而后者核心逻辑如下// StockServiceImpl.java Transactional(rollbackFor Exception.class) public boolean decreaseStock(Long foodId, Integer quantity) { // 1. 先查当前库存SELECT ... FOR UPDATE 锁住该行 Food food foodMapper.selectById(foodId); if (food null || food.getStock() quantity) { throw new BusinessException(库存不足); } // 2. 执行扣减UPDATE ... WHERE id ? AND stock ? int updated foodMapper.updateStock(foodId, quantity); if (updated 0) { // 二次校验失败说明并发时已被其他事务扣减 throw new BusinessException(库存已被抢光请刷新重试); } return true; }提示updateStock对应的 XML 映射语句中WHERE 条件包含stock #{quantity}这是防止 ABA 问题的关键——即使行锁释放后库存又被其他事务加回只要本次扣减前库存已低于所需数量UPDATE 就不会生效事务回滚。这种“乐观锁行锁”双保险比单纯Version注解更贴合电商场景。2.2 微信支付回调的幂等性与状态机驱动微信支付回调notify_url是毕设中最易翻车的环节。源码未用简单if (status SUCCESS) updateOrderStatus()而是构建了基于订单号的幂等令牌token校验 状态迁移白名单。核心逻辑在PayNotifyController.handleNotify()// PayNotifyController.java PostMapping(/pay/notify) public String handleNotify(HttpServletRequest request, HttpServletResponse response) { try { // 1. 解析微信回调 XML验签使用商户 APIv3 密钥 MapString, String notifyMap WxPayUtil.parseNotifyXml(request.getInputStream()); if (!WxPayUtil.verifySignature(notifyMap)) { return FAIL; // 验签失败直接拒收 } // 2. 提取 out_trade_no即本系统订单号查库获取当前订单状态 String outTradeNo notifyMap.get(out_trade_no); Order order orderService.getByOrderNo(outTradeNo); if (order null) { return FAIL; } // 3. 状态机校验仅允许从 WAIT_PAY 迁移到 PAID if (!WAIT_PAY.equals(order.getStatus())) { log.warn(订单 {} 当前状态 {}不接受支付回调, outTradeNo, order.getStatus()); return SUCCESS; // 已处理避免微信重复推送 } // 4. 更新订单状态 记录支付流水 触发库存扣减若未扣减 orderService.paySuccess(order.getId()); return SUCCESS; } catch (Exception e) { log.error(支付回调处理异常, e); return FAIL; } }注意paySuccess()方法内部会再次校验订单状态防止并发重复处理并调用WebSocketService.sendOrderUpdate(order)主动向小程序端推送状态变更而非依赖小程序轮询——这是提升用户体验的关键细节也是答辩时容易被追问的“实时性如何保证”。2.3 管理员后台的权限隔离与数据范围控制系统包含两类后台商家端食堂管理员和平台端系统管理员。源码通过PreAuthorize注解 自定义PermissionEvaluator实现细粒度控制。例如商家只能管理自己门店的菜品// FoodController.java GetMapping(/list) PreAuthorize(permissionEvaluator.hasShopPermission(authentication, #shopId)) public ResultListFood listByShop(RequestParam Long shopId) { return Result.success(foodService.listByShop(shopId)); }而hasShopPermission方法会校验当前登录用户从 JWT Token 解析是否属于该shopId关联的商家。数据库层面food表强制包含shop_id字段所有查询都带上AND shop_id ?条件。这种“代码层鉴权 SQL 层过滤”双保险避免了因注解漏写导致的数据越权泄露——这是毕设答辩中导师最爱揪的漏洞点。3. 小程序端核心交互原生框架下的页面栈管理与状态同步小程序端未使用 Taro 或 uni-app坚持原生 WXML/WXSS/JS 开发好处是调试链路清晰、微信开发者工具报错精准、且能直接调用微信支付、扫码、位置等原生能力。但代价是页面栈管理、数据同步、下拉刷新/上拉加载更多必须手动精细控制。源码中三个高频交互模块值得深挖。3.1 页面列表加载更多防抖 节流 分页参数透传“微信小程序页面列表加载更多”是热搜词但很多毕设项目只是简单onReachBottom触发page请求导致快速上滑时请求雪崩或重复加载。本项目在pages/food/list.js中做了三层防护// pages/food/list.js Page({ data: { foods: [], page: 1, pageSize: 10, hasMore: true, loading: false, lastLoadTime: 0 // 上次加载时间戳用于防抖 }, onReachBottom() { const now Date.now(); // 1. 防抖两次触发间隔小于 500ms忽略 if (now - this.data.lastLoadTime 500) return; this.setData({ lastLoadTime: now }); // 2. 节流正在加载中直接返回 if (this.data.loading) return; // 3. 检查是否还有更多 if (!this.data.hasMore) return; this.loadMoreFoods(); }, loadMoreFoods() { this.setData({ loading: true }); wx.request({ url: getApp().globalData.baseUrl /food/list, data: { page: this.data.page, pageSize: this.data.pageSize, categoryId: this.data.categoryId // 分类筛选参数透传 }, success: (res) { if (res.data.code 200) { const newFoods res.data.data.list; const hasMore newFoods.length this.data.pageSize; this.setData({ foods: this.data.foods.concat(newFoods), page: this.data.page 1, hasMore: hasMore, loading: false }); } }, fail: () { this.setData({ loading: false }); } }); } });提示categoryId参数在页面跳转时通过wx.navigateTo({url: /pages/food/list?categoryId123})传入并在onLoad中解析赋值给data.categoryId确保切换分类后加载更多仍携带正确筛选条件。这是很多学生忽略的“上下文丢失”坑。3.2 购物车多规格选配与实时价格计算菜品支持“规格”如小份/大份、“口味”微辣/中辣、“加料”加蛋2元源码用嵌套对象结构存储选配项{ foodId: 1001, name: 宫保鸡丁, spec: {id: spec_2, name: 大份, priceAdd: 5}, taste: {id: taste_3, name: 中辣}, addons: [ {id: addon_1, name: 加蛋, price: 2}, {id: addon_2, name: 加米饭, price: 1} ] }购物车页面pages/cart/index.js中calculateTotalPrice()方法遍历所有商品对每个商品执行基础价 规格加价 口味加价若有 所有加料价格之和乘以数量累加得到总价关键点在于所有选配变更如点击“加蛋”复选框都触发this.calculateTotalPrice()重新计算并立即更新data.totalPrice绑定到 WXML 的{{totalPrice}}。这种“数据驱动视图”模式比手动setData({totalPrice: xxx})更可靠避免因异步更新顺序导致价格显示滞后。3.3 用户离开小程序的监听与未支付订单清理“微信小程序如何监听用户离开小程序”是高频问题。本项目在app.js的onHide生命周期中主动调用后端接口标记“用户进入后台”并在onShow时检查是否有“待支付”订单超时默认30分钟// app.js App({ onLaunch() { // 登录、获取用户信息等 }, onHide() { // 标记用户进入后台便于统计活跃度可选 wx.request({ url: getApp().globalData.baseUrl /user/background, method: POST, header: { Authorization: wx.getStorageSync(token) } }); }, onShow() { // 检查并清理超时待支付订单 wx.request({ url: getApp().globalData.baseUrl /order/cleanup-timeout, success: (res) { if (res.data.code 200 res.data.data.canceledCount 0) { wx.showToast({ title: 已取消 ${res.data.data.canceledCount} 个超时订单, icon: none }); } } }); } });注意/order/cleanup-timeout接口在服务端执行UPDATE order SET status CANCELED WHERE status WAIT_PAY AND create_time DATE_SUB(NOW(), INTERVAL 30 MINUTE)并返回被更新的订单数。这种“客户端触发 服务端执行”的模式比纯前端定时器更可靠避免用户杀进程导致定时器失效。4. 数据库设计与关键约束MySQL 8.0 下的事务边界与索引优化数据库脚本db/point_order.sql是整套系统落地的基石。它不是简单的CREATE TABLE堆砌而是在 MySQL 8.0 特性下精心设计的约束体系。共 9 张表核心四张shop门店、food菜品、order订单、order_item订单项。下面重点解析三个易被忽视的设计点。4.1 订单状态机的字段设计与枚举校验order表的status字段并非VARCHAR(20)任由代码写入而是定义为ENUM(WAIT_PAY,PAID,PREPARING,DELIVERING,COMPLETED,CANCELED)并设置DEFAULT WAIT_PAY。这样做的好处是数据库层强制校验任何非法状态如PAIDDD插入直接报错避免因代码 bug 导致脏数据查询时WHERE status IN (PAID,PREPARING)比LIKE或INSTR更快与 Java 枚举类OrderStatus严格对应减少 ORM 映射歧义。对应的 Java 枚举类OrderStatus.javapublic enum OrderStatus { WAIT_PAY(待支付), PAID(已支付), PREPARING(制作中), DELIVERING(配送中), COMPLETED(已完成), CANCELED(已取消); private final String desc; OrderStatus(String desc) { this.desc desc; } public String getDesc() { return desc; } }MyBatis-Plus 的TableName和TableField注解确保 Java 对象与数据库字段零偏差映射。4.2 库存扣减的复合索引与覆盖索引food表的库存字段stock是高频更新字段为避免全表扫描源码在建表时创建了复合索引-- db/point_order.sql 片段 CREATE TABLE food ( id bigint NOT NULL AUTO_INCREMENT, shop_id bigint NOT NULL COMMENT 所属门店, name varchar(100) NOT NULL, price decimal(10,2) NOT NULL, stock int NOT NULL DEFAULT 0, status tinyint NOT NULL DEFAULT 1 COMMENT 1-上架,0-下架, PRIMARY KEY (id), KEY idx_shop_status (shop_id,status) COMMENT 按门店状态查询菜品列表, KEY idx_shop_stock (shop_id,stock) COMMENT 按门店库存查询用于库存告警 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;其中idx_shop_stock是覆盖索引当执行SELECT id, name, stock FROM food WHERE shop_id 123 AND stock 10库存告警查询时MySQL 只需扫描索引树无需回表查数据页性能提升显著。而idx_shop_status支撑了商家后台“按分类/状态筛选菜品”的高频操作。4.3 订单与订单项的外键级联与软删除order_item表通过order_id外键关联order表但源码未启用 ON DELETE CASCADE而是采用逻辑删除is_deleted字段 服务端显式删除。原因在于微信支付回调可能晚于订单创建若物理级联删除支付成功后回调更新order状态时order_item已不存在导致状态不一致。实际做法是-- order_item 表结构 CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, food_id bigint NOT NULL, name varchar(100) NOT NULL, spec json DEFAULT NULL, quantity int NOT NULL, price decimal(10,2) NOT NULL, is_deleted tinyint NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;服务端OrderService.deleteOrder(Long orderId)方法中先将order表的status设为CANCELED再将order_item表中对应order_id的记录is_deleted 1。所有查询order_item的 SQL 都强制加上AND is_deleted 0条件。这种“软删除状态机”组合是保障资金流与物流数据最终一致性的底线。5. 避坑指南毕业设计答辩前必须验证的五个血泪现场做毕设最怕的不是写不出代码而是答辩时导师一句“你这个功能真能跑通吗”当场宕机。以下五条是我在帮 23 届学生 debug 时高频出现、且极易被忽略的“答辩翻车点”。每条都按「现象 → 原因 → 解决」给出可立即验证的方案。5.1 现象小程序提交订单后后台日志显示“库存充足”但数据库food.stock未扣减原因decreaseStock()方法未加Transactional注解或事务传播行为配置错误如REQUIRES_NEW导致库存扣减事务提前提交订单创建事务回滚时库存无法回滚。解决检查StockServiceImpl.decreaseStock()方法签名确认Transactional(rollbackFor Exception.class)存在且生效在application.yml中开启事务日志logging.level.org.springframework.transaction.interceptorDEBUG启动时观察是否打印Creating new transaction。5.2 现象微信支付成功小程序端订单状态仍是“待支付”后台order.status未更新原因微信支付回调notify_url地址未正确配置到微信商户平台或服务器防火墙/反向代理Nginx拦截了 POST 请求或PayNotifyController.handleNotify()中WxPayUtil.verifySignature()验签失败常见于商户 APIv3 密钥未正确配置或mch_id错误。解决在微信商户平台“开发配置”中确认notify_url为公网可访问地址如https://yourdomain.com/pay/notify在 Nginx 日志中搜索pay/notify查看是否有 4xx/5xx 错误在handleNotify()开头添加log.info(收到微信回调: {}, notifyMap)确认请求是否到达。5.3 现象管理员后台修改菜品价格小程序端浏览列表价格未刷新原因小程序端food/list页面未实现下拉刷新onPullDownRefresh且数据缓存策略不当如wx.setStorageSync(foods, foods)后未在onLoad中清除旧缓存。解决在pages/food/list.js中添加onPullDownRefresh方法调用this.loadFoods()并wx.stopPullDownRefresh()在onLoad中移除wx.getStorageSync(foods)改为每次进入页面都发起新请求确保数据最新。5.4 现象同一用户多次点击“立即支付”生成多个重复订单原因前端未做按钮防重点击后未置灰按钮且后端createOrder()未校验“相同用户相同购物车内容”是否已存在待支付订单。解决前端pages/order/confirm.js中pay()方法执行前设置this.setData({payDisabled: true})支付完成后恢复后端OrderService.createOrder()开头增加校验if (orderService.hasUnpaidOrderByCart(userId, cartItems)) { throw new BusinessException(检测到未支付订单请前往支付); }。5.5 现象数据库导入后管理员登录提示“用户名密码错误”但admin表中数据正常原因admin表的密码字段password在建表时定义为VARCHAR(64)但 BCrypt 加密后的密文长度为 60 字符若建表脚本中误写为VARCHAR(32)则密文被截断导致验密失败。解决执行DESC admin;查看password字段长度若小于 64则执行ALTER TABLE admin MODIFY password VARCHAR(64) NOT NULL;重新运行初始化 SQLdb/init_admin.sql插入管理员账号。6. 毕设落地技巧从源码到答辩材料的三步闭环验证法毕设不是写完代码就结束而是要让导师相信“这东西真能用”。我带过的 15 个学生里9 个卡在“演示环节出问题”根源在于验证流不闭环。这里分享一个我自用的三步法每次答辩前必走一遍把“玄学问题”压缩到 5 分钟内定位。6.1 第一步环境一致性验证5 分钟很多问题源于本地环境与演示环境差异。必须统一三要素JDK 版本java -version必须为1.8.0_3XXSpring Boot 2.7.x 要求禁止用 JDK 11MySQL 版本mysql --version必须为8.0.XX且default_authentication_plugin配置为mysql_native_password避免 Spring Boot 连接报Client does not support authentication protocol微信开发者工具版本必须为 Stable 1.05.2303020 或更高低版本不支持wx.requestPayment的 Promise 写法。我的习惯在项目根目录放一个env-check.sh脚本运行即输出三者版本并对比预设值不匹配则高亮警告。从那以后我每次部署新环境都先跑一遍这个脚本再没因环境问题耽误过演示。6.2 第二步核心链路冒烟测试10 分钟不测全功能只跑通三条命脉链路每条链路用真实数据用户下单链路小程序扫码进店 → 选 1 个菜品加入购物车 → 提交订单地址填“测试地址”→ 查看order表是否新增WAIT_PAY记录food.stock是否扣减支付回调链路用 Postman 模拟微信回调POST /pay/notifyBody 为 valid XML查看order.status是否变为PAIDorder_item是否关联正确后台管理链路浏览器访问http://localhost:8080/admin登录后修改一个菜品价格 → 小程序下拉刷新food/list确认价格更新。提示这三条链路的 SQL 和 HTTP 请求我都整理成 Markdown 文档放在docs/smoke-test.md里答辩前 1 小时照着文档逐条执行比临时手敲命令快 3 倍且不易遗漏步骤。6.3 第三步答辩话术与材料映射15 分钟导师提问往往围绕“你做了什么”和“为什么这么做”。我把源码、数据库、论文、PPT 四者做了强映射答辩问题源码位置数据库表论文章节PPT 页码“库存怎么防超卖”StockServiceImpl.decreaseStock()food.stock,idx_shop_stock4.2.3 库存扣减设计P12“支付回调怎么保证不丢”PayNotifyController.handleNotify()order.status,order_item.is_deleted4.3.1 支付状态机P15“管理员怎么看不到别人门店数据”PreAuthorize(permissionEvaluator...)food.shop_id,admin.shop_id4.1.2 权限控制模型P10我的教训去年有个学生答辩时被问“事务怎么回滚的”他愣了 30 秒才翻出Transactional注解。从那以后我每次准备答辩都强制走一遍这个映射表把每个技术点对应的“代码在哪、数据在哪、论文哪段、PPT哪页”全部串起来。讲到库存时手指直接点向 P12 页的流程图讲到权限时鼠标划到 P10 页的 UML 类图。导师看到的是逻辑严密而不是手忙脚乱翻文件。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Kitematic 0.17.11 for Mac:Docker Desktop 的轻量GUI外挂

Kitematic 0.17.11 for Mac:Docker Desktop 的轻量GUI外挂

简介:本资源是面向Mac平台开发者的Docker图形化管理工具Kitematic 0.17.11正式版安装包,专为不熟悉Docker CLI命令、希望快速上手容器部署的初学者及轻量级项目开发者设计。它通过一体化GUI实现镜像搜索、容器启停、端口映射、环境变量配置、卷挂载与日志…

📅 2026/10/30 17:14:34
Flask多角色权限系统实战:从选课事务到生产部署避坑指南

Flask多角色权限系统实战:从选课事务到生产部署避坑指南

简介:这是一套基于Python与Flask框架开发的学生信息管理系统源码,面向高校计算机专业初学者、Web开发入门者及教育信息化实践者,解决多角色协同管理学生基础信息、课程选课与成绩录入等核心教学管理场景。资源包共80个文件,含6个核…

📅 2026/10/30 17:14:34
Java Swing+MySQL个人财务系统实战:轻量可控的桌面记账方案

Java Swing+MySQL个人财务系统实战:轻量可控的桌面记账方案

简介:这是一套基于Java Swing开发的个人财务收支管理系统源码,面向Java初学者与C/S架构实践者,聚焦GUI编程、MySQL数据库交互及桌面应用完整开发流程。资源采用EclipseJDKMySQL技术栈,覆盖用户管理、收支分类、收入/支出CRUD、密码…

📅 2026/10/30 17:09:33
MORE NEWS

更多资讯

📰

extract-xiso完全指南:Xbox光盘镜像提取与打包实战

简介:extract-xiso 是一款开源的光盘映像备份工具,专注于创建和提取 Xbox 游戏光盘的 .iso 文件,适用于 Linux、FreeBSD、macOS、Windows 等多个操作系统,目标用户是游戏备份爱好者、模拟器玩家及需要处理 Xbox 镜像的开发者。这份…

📰

地铁BAS PLC为何必须用STEP7 V5.5?兼容性与现场准入硬约束

简介:本资源是一套完整的地铁环境监控系统(BAS)PLC控制程序工程包,面向自动化、轨道交通及工业控制领域的工程师、高校师生与PLC初学者,聚焦西门子S7系列PLC在真实地铁场景中的工程化应用。资源基于STEP7 V5.5开发&…

📰

C语言+easyX实现Flappy Bird:图形编程底层实战指南

简介:这是一份面向C语言初学者与图形编程入门者的Flappy Bird像素鸟游戏实战源码包,聚焦Windows平台下基于easyX图形库的游戏开发实践,帮助学习者掌握双缓冲绘图、物理模拟(重力/碰撞)、状态机管理及音效集成等核心技能…

📰

【纯白嫖】IEC60870-5 中文版下载指南:101/104 协议标准一次拿齐全集

【纯白嫖】IEC60870-5 中文版下载指南:101/104 协议标准一次拿齐全集 【免费下载链接】IEC60870-5全集中文版本下载 IEC60870-5全集中文版本下载 项目地址: https://gitcode.com/open-source-toolkit/fc96f IEC60870-5 原版标准难找难读。IEC60870-5全集中文…

📰

刚更新的首次启动流程:模型来源、系统授权、Agent 配置一次配完

刚更新的首次启动流程:模型来源、系统授权、Agent 配置一次配完 【免费下载链接】OpenBot Open-source AI coworkers that each get a computer of their own: a browser, files and tools, with every action decided before it happens and recorded after. Bring…

📰

【资源自取】倪海厦软件完整下载指南:人纪、天纪、医案查询系统三步跑通

【资源自取】倪海厦软件完整下载指南:人纪、天纪、医案查询系统三步跑通 【免费下载链接】倪海厦三套软件资源分享 倪海厦三套软件资源分享本仓库提供倪海厦先生的三套电脑软件资源,经过测试,确保可用性 项目地址: https://gitcode.com/ope…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬