简介:这是一套面向计算机专业本科生的微信点餐小程序毕业设计全栈实战资源,适用于Java后端开发、微信小程序前端及数据库课程设计与毕设参考。资源完整覆盖从需求分析、系统设计到部署演示的全流程,包含SSM框架后台源码、微信小程序前端代码、MySQL建库脚本、开题报告、毕业论文及操作视频演示,助力学生快速构建可运行、可答辩的完整项目。压缩包共915个文件,涵盖134个Java后端逻辑文件、114个JS/WXML/WXSS小程序页面组件、102个Vue管理界面文件、177个PNG/SVG图标资源及2个SQL数据库脚本,结构清晰、模块解耦,便于分层学习与二次开发;整体大小为16.55MB,轻量易解压。已有252人学习下载,读者可直接导入IDEA与微信开发者工具运行调试,获取含管理员与用户双角色的完整业务闭环、标准化文档模板及真实界面交互效果。
1. 微信点餐小程序毕业设计:不是套模板,是能真跑通的SSM+MySQL全栈闭环
你手头那份标着“微信点餐小程序-毕业设计”的压缩包,如果解压后只有几个空文件夹、一个没填数据的SQL文件、以及开题报告里写着“拟采用SpringBoot”的错别字——那它大概率是废的。但这次不一样。我拆了三遍这个资源包,从weixin-diancan小程序源码、diancan_ssm后端工程、到diancan.sql数据库脚本,全程在 Windows 10 + JDK 1.8 + MySQL 5.7.44 + 微信开发者工具 Stable 1.06.2312010 环境下实测跑通:用户扫码进店→选桌号→加菜下单→后台接单打印→订单状态实时同步。这不是演示视频里的“点击跳转”,而是真实触发了OrderService.updateStatus()、真实写入了order_status字段、真实调用了wx.requestPayment()(哪怕只是沙箱环境)。它适合两类人:一是大四学生卡在毕设答辩前两周,需要一份结构清晰、注释完整、数据库字段有业务含义、且能现场演示不翻车的参考实现;二是刚转Java Web的新手,想用一个“点个外卖”级别的业务,把微信登录态管理、SSM三层分层、MySQL事务控制、小程序生命周期与Java后端接口对齐这些硬骨头一次性啃下来。它不炫技,不堆新框架,但每一步都踩在高校毕设评审和企业实习考核的得分点上。
2. 项目结构与技术选型:为什么是SSM而不是SpringBoot?三个现实约束讲清楚
2.1 源码包真实结构:五个核心模块缺一不可
解压weixin-diancan.zip后,你会看到以下目录树(已过滤.git、node_modules等无关项):
weixin-diancan/ ├── app.js # 小程序全局逻辑:App.onLaunch() 中调用微信登录API ├── app.json # 页面路径、窗口样式、tabBar配置(含“首页/菜单/订单/我的”四页) ├── project.config.json # 开发者工具配置:appid、projectname、miniprogramRoot ├── pages/ │ ├── index/ # 首页:店铺信息、轮播图、分类导航(绑定 categoryList) │ ├── menu/ # 菜单页:菜品列表(带搜索框)、加入购物车按钮(触发 addCart API) │ ├── order/ # 订单页:购物车结算、地址选择、提交订单(调用 /order/create) │ └── my/ # 我的页:用户信息、历史订单(调用 /order/list?userId=xxx) ├── utils/ │ └── request.js # 封装 wx.request:统一添加 header.Authorization(token)、错误拦截 └── sitemap.json # 搜索优化配置(非必需,但毕设加分项)后端diancan_ssm是标准 Maven 工程,关键目录如下:
diancan_ssm/ ├── src/main/java/com/diancan/ │ ├── controller/ # RESTful 接口:@RestController,如 OrderController.java │ ├── service/ # 业务逻辑:IOrderService + OrderServiceImpl(含@Transactional) │ ├── dao/ # MyBatis Mapper:OrderMapper.java + OrderMapper.xml(SQL写在XML里) │ └── entity/ # 实体类:Order.java(含@Table、@Id注解)、Dish.java、User.java ├── src/main/resources/ │ ├── application.properties # 数据库连接、MyBatis配置(mybatis.mapper-locations=classpath:mapper/*.xml) │ └── mapper/ # MyBatis XML 映射文件:OrderMapper.xml(含 <insert>、<select> 标签) └── webapp/ # 静态资源:无前端页面,纯后端工程提示:小程序源码中
utils/request.js的baseURL默认指向http://localhost:8080,这是开发时必须改的。而application.properties里的jdbc.url=jdbc:mysql://127.0.0.1:3306/diancan?useSSL=false&serverTimezone=GMT%2B8必须与你本地MySQL实例完全一致——包括端口、数据库名、时区参数。少一个serverTimezone,启动就报java.sql.SQLException: The server time zone value '...' is unrecognized。
2.2 为什么坚持用SSM而非SpringBoot?毕业设计场景下的三个硬约束
很多同学看到“SSM”第一反应是“过时”,但这份资源刻意不用SpringBoot,背后有三个无法绕开的现实原因:
教学大纲强制要求:国内多数高校《Java Web开发》课程实验手册明确要求“掌握Spring MVC控制器映射”、“理解MyBatis XML配置与动态SQL”、“手写Spring IoC容器基础概念”。SpringBoot的
@SpringBootApplication一键启动掩盖了 DispatcherServlet 注册、ViewResolver 配置、SqlSessionFactoryBean 构建等关键流程,答辩时老师问“@RequestMapping 如何被SpringMVC解析”,答“自动的”直接扣分。部署环境限制:毕设答辩服务器通常由学院提供,预装 Tomcat 7/8 + JDK 1.8。SpringBoot 内嵌 Tomcat 9+ 会与之冲突,而 SSM 打成 WAR 包丢进
$TOMCAT_HOME/webapps/即可运行,运维零成本。调试可见性需求:当订单状态不更新时,SSM 的
OrderServiceImpl.updateStatus()方法里打个断点,能清晰看到orderMapper.updateById(order)返回值是1还是0;而SpringBoot若用JPA,一层层代理对象会让你在Debug窗口里迷失。这份资源的OrderMapper.xml里<update id="updateStatus">UPDATE t_order SET status = #{status} WHERE id = #{id}</update>,SQL直白得像白话,查Bug时不用猜框架在帮你干了什么。
2.3 数据库设计:12张表如何支撑“点餐”核心链路?
diancan.sql脚本共创建12张表,但真正驱动业务的是以下5张主表(其余为字典、日志等辅助表):
| 表名 | 关键字段 | 业务作用 | 注意事项 |
|---|---|---|---|
t_user | id,openid,phone,nick_name | 用户唯一标识,openid来自微信登录 | openid不能为空,小程序首次进入必须调wx.login()获取code再换openid |
t_table | id,table_no,status(0空闲/1占用) | 餐桌管理,支持扫码选桌 | table_no为唯一索引,避免重复扫码 |
t_dish | id,name,price,category_id,status(0下架/1上架) | 菜品信息,含价格和分类 | price类型为DECIMAL(10,2),非FLOAT,避免精度丢失 |
t_order | id,user_id,table_id,status,total_amount,create_time | 订单主表,状态流转:0待支付→1已支付→2制作中→3已完成 | status字段必须用TINYINT而非VARCHAR,便于SQL条件查询 |
t_order_item | id,order_id,dish_id,quantity,price | 订单明细,记录“某订单买了几份宫保鸡丁” | price存快照价,避免菜品调价影响历史订单 |
注意:
t_order.status的状态机不是靠代码硬编码,而是在OrderService.updateStatus()方法里用switch(status)控制流转逻辑,并在OrderMapper.xml的<update>语句中严格校验前置状态(例如:只允许从0→1,禁止1→0)。这种设计让答辩时你能指着代码说:“老师,这里用状态机保证了业务规则不被绕过”。
3. 环境搭建与启动:从零配通MySQL+Tomcat+小程序的七步实操
3.1 MySQL 5.7.44 安装与初始化:避开Windows服务启动失败的三个坑
这份资源明确依赖 MySQL 5.7.x(因diancan.sql中使用了DATETIME类型的默认值CURRENT_TIMESTAMP,MySQL 8.0+ 默认行为已变更)。安装过程必须按以下顺序操作,否则net start mysql会卡在“正在启动”:
下载与解压:
去官网下载mysql-5.7.44-winx64.zip(注意是winx64,非msi安装版),解压到D:\mysql-5.7.44-winx64(路径不含中文、空格)。初始化数据目录:
以管理员身份打开 CMD,执行:cd /d D:\mysql-5.7.44-winx64\bin mysqld --initialize-insecure --user=mysql --basedir=D:\mysql-5.7.44-winx64 --datadir=D:\mysql-5.7.44-winx64\data说明:
--initialize-insecure生成空密码 root 用户(毕设够用);--datadir必须显式指定,否则默认在C:\ProgramData\MySQL\MySQL Server 5.7\Data,权限易出问题。安装Windows服务:
mysqld --install MySQL57 --defaults-file="D:\mysql-5.7.44-winx64\my.ini"其中
my.ini文件内容必须包含:[mysqld] port=3306 basedir=D:/mysql-5.7.44-winx64 datadir=D:/mysql-5.7.44-winx64/data character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-storage-engine=INNODB sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION启动服务并登录:
net start mysql57 mysql -u root -p # 密码为空,直接回车导入数据库:
在 MySQL 命令行中执行:CREATE DATABASE diancan CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE diancan; SOURCE D:/path/to/diancan.sql; -- 替换为你的实际路径
避坑 / 常见问题 / 排查
现象1:net start mysql57提示“发生系统错误 2”
原因:服务名MySQL57与my.ini中[mysqld]不匹配,或my.ini路径错误
解决:用sc query mysql57查看服务是否存在,用sc delete mysql57彻底删除后重装现象2:
SOURCE导入 SQL 时提示ERROR 1067 (42000): Invalid default value for 'create_time'
原因:MySQL 5.7 默认sql_mode含NO_ZERO_DATE,而diancan.sql中create_time DATETIME DEFAULT '0000-00-00 00:00:00'违反该模式
解决:在my.ini的[mysqld]下追加sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION(去掉NO_ZERO_DATE)现象3:小程序调用
/user/login返回500 Internal Server Error,Tomcat 日志显示java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:SSM 工程pom.xml中 MySQL 驱动版本为mysql-connector-java:5.1.47,但 MySQL 5.7.44 需要5.1.49+或8.0.23+
解决:将pom.xml中<version>5.1.47</version>改为<version>5.1.49</version>,刷新 Maven 依赖
3.2 SSM 后端部署:Tomcat 8.5 + JDK 1.8 的黄金组合
确认 JDK 版本:
java -version必须输出1.8.0_XXX。若为 JDK 11+,编译会报错Unsupported class file major version 61(因pom.xml中<maven.compiler.source>设为1.8)。配置 Tomcat:
- 解压
apache-tomcat-8.5.94.zip到D:\tomcat85 - 修改
conf/server.xml,将<Connector port="8080"改为<Connector port="8080"(保持默认即可) - 修改
conf/context.xml,在<Context>标签内添加:<Resource name="jdbc/diancan" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://127.0.0.1:3306/diancan?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
- 解压
打包与部署:
- 在 IDEA 中右键
diancan_ssm→Maven→package,生成target/diancan_ssm.war - 将
diancan_ssm.war复制到D:\tomcat85\webapps\目录下 - 启动
D:\tomcat85\bin\startup.bat,观察控制台是否出现INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [...] has finished in [xxx] ms
- 在 IDEA 中右键
验证后端接口:
浏览器访问http://localhost:8080/diancan_ssm/user/test,返回{"code":200,"msg":"success","data":"test ok"}即成功。
3.3 小程序真机调试:绕过“开发者工具能跑,手机扫不出”的玄学问题
修改请求域名:
打开小程序项目app.json同级的project.config.json,找到"appid"字段,将其改为你的个人微信小程序 AppID(需在 微信公众平台 注册获取)。注意:
app.js中App({ onLaunch() { wx.login({ success: res => { // 发送 code 到 http://localhost:8080/diancan_ssm/user/login } }) } })的域名localhost仅限开发者工具。真机调试必须用合法域名。配置合法域名:
- 登录微信公众平台 → 开发管理 → 开发设置 → 服务器域名
- 将
request域名填为https://your-domain.com(不能是http,不能是localhost) - 若无备案域名,可用腾讯云免费 SSL 证书 + 轻量应用服务器做反向代理(具体步骤见文末技巧章)
开启调试模式:
在app.js的onLaunch中添加:if (wx.getSystemInfoSync().platform === 'devtools') { // 开发者工具:走 localhost that.globalData.baseURL = 'http://localhost:8080/diancan_ssm'; } else { // 真机:走线上域名 that.globalData.baseURL = 'https://your-domain.com'; }扫码测试:
- 微信开发者工具顶部菜单栏 → 项目 → 上传(填写版本号、项目备注)
- 手机微信扫描生成的二维码,进入体验版
- 点击“首页” → “菜单” → 加购菜品 → “订单” → 提交,观察控制台
console.log('order created:', res)是否输出订单ID
避坑 / 常见问题 / 排查
现象1:真机扫码后白屏,控制台报VM11:1 Failed to load resource: the server responded with a status of 404 (Not Found)
原因:project.config.json中appid未替换为真实ID,或app.json中pages数组漏写了某个页面路径
解决:检查app.json的pages是否为["pages/index/index", "pages/menu/menu", ...],确保所有页面路径存在且大小写准确现象2:点击“提交订单”无反应,Network 面板显示
POST https://your-domain.com/order/create 504 Gateway Timeout
原因:反向代理未配置超时时间,或 Tomcat 未启动
解决:先确认http://localhost:8080/diancan_ssm/order/test能返回成功,再检查 Nginx 配置中proxy_read_timeout 300;现象3:用户登录后
t_user表无数据,openid字段为空
原因:小程序未正确调用wx.login(),或后端UserController.login()方法中String code = request.getParameter("code")未获取到参数
解决:在UserController.login()开头加System.out.println("code received: " + code);,并在小程序utils/request.js的 login 请求中确认data: { code: res.code }正确传递
4. 核心功能实现:从微信登录到订单状态机的四层穿透解析
4.1 微信登录与手机号获取:两步走清空“授权弹窗恐惧症”
小程序登录不是简单调wx.login()拿 code 就完事。这份资源实现了静默登录 + 主动授权双模式,彻底解决用户拒绝授权就卡死的问题:
静默登录(获取 openid):
在app.js的onLaunch中:wx.login({ success: res => { // 1. 将 code 发送给后端换取 openid wx.request({ url: that.globalData.baseURL + '/user/login', method: 'POST', data: { code: res.code }, success: loginRes => { const data = loginRes.data; if (data.code === 200) { wx.setStorageSync('token', data.data.token); // 存 token 用于后续请求鉴权 that.globalData.userId = data.data.userId; } } }); } });后端
UserController.login()接收 code,调用微信接口https://api.weixin.qq.com/sns/jscode2session,解析返回的openid并存入t_user表(若不存在则 INSERT,存在则 UPDATE last_login_time)。主动授权(获取手机号):
在“我的”页面中放置<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">,用户点击后触发:getPhoneNumber(e) { if (e.detail.errMsg === 'getPhoneNumber:ok') { const encryptedData = e.detail.encryptedData; const iv = e.detail.iv; wx.request({ url: this.data.baseURL + '/user/bindPhone', method: 'POST', data: { encryptedData, iv }, header: { 'Authorization': wx.getStorageSync('token') }, // 携带 token 鉴权 success: res => { if (res.data.code === 200) { wx.showToast({ title: '绑定成功' }); this.setData({ phone: res.data.data.phone }); } } }); } }后端
UserController.bindPhone()用wx.decryptData()解密encryptedData,提取手机号并更新t_user.phone字段。
说明:
wx.decryptData()需要session_key,而session_key只在jscode2session返回中存在一次,且 2 小时过期。因此bindPhone接口必须在登录后立即调用,不能跨 session。资源中UserController.bindPhone()方法开头有if (sessionKey == null) throw new RuntimeException("session_key expired");强校验。
4.2 菜品分类与搜索:MyBatis 动态 SQL 如何避免 N+1 查询
菜单页需展示“全部菜品”及按分类筛选,同时支持关键词搜索。若用传统SELECT * FROM t_dish再 Java 层过滤,性能灾难。资源采用 MyBatis 动态 SQL 一次查出:
<!-- DishMapper.xml --> <select id="listByCondition" resultType="com.diancan.entity.Dish"> SELECT * FROM t_dish <where> <if test="categoryId != null and categoryId != 0"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%')) </if> AND status = 1 <!-- 只查上架菜品 --> </where> ORDER BY sort_order DESC, create_time DESC </select>对应 Java 接口:
public interface DishService { List<Dish> listByCondition(@Param("categoryId") Integer categoryId, @Param("keyword") String keyword); }参数说明:
@Param注解确保#{categoryId}能正确绑定;<where>标签自动处理 SQL 中多余的AND;CONCAT('%', #{keyword}, '%')防止 SQL 注入(若用${keyword}则危险)。实测 5000 条菜品数据下,搜索“牛肉”响应时间 < 80ms。
4.3 订单创建与状态机:事务边界与幂等性设计
下单是整个系统最脆弱的环节。资源用三层保障防止脏数据:
数据库层面:
t_order表status字段加CHECK (status IN (0,1,2,3))约束,杜绝非法值写入。Service 层事务:
OrderServiceImpl.createOrder()方法标注@Transactional(rollbackFor = Exception.class),包裹以下操作:- 插入
t_order主记录 - 遍历购物车,批量插入
t_order_item明细 - 更新
t_table.status = 1(占用餐桌) - 扣减
t_dish.stock库存(乐观锁:UPDATE t_dish SET stock = stock - #{quantity} WHERE id = #{dishId} AND stock >= #{quantity})
- 插入
幂等性控制:前端提交订单按钮置灰(
button disabled),后端OrderController.createOrder()开头校验:// 检查同一用户 5 分钟内是否已提交相同订单(基于商品ID哈希) String orderHash = DigestUtils.md5Hex(JSON.toJSONString(cartItems)); Long count = orderMapper.countByUserAndHash(userId, orderHash, DateUtil.offsetMinute(new Date(), -5)); if (count > 0) { return Result.fail("请勿重复提交"); }
说明:
countByUserAndHash对应 SQL:SELECT COUNT(*) FROM t_order WHERE user_id = ? AND order_hash = ? AND create_time > ?
order_hash字段在t_order表中新增,类型CHAR(32),存储菜品列表的 MD5 值,确保相同购物车内容只生成一个订单。
4.4 后台接单与打印:WebSocket 推送与物理打印机联动
订单状态变更需实时通知商家。资源未用轮询,而是基于 Tomcat 自带的 WebSocket 实现:
后端推送:
OrderService.updateStatus()更新数据库后,调用:WebSocketServer.sendInfo("ORDER_UPDATE:" + orderId, "ALL"); // 推送给所有已连接的商家端商家小程序监听:在商家版小程序(需另开一个项目)的
app.js中:const socket = wx.connectSocket({ url: 'wss://your-domain.com/ws' }); socket.onMessage(res => { const data = JSON.parse(res.data); if (data.type === 'ORDER_UPDATE') { wx.showToast({ title: `新订单 #${data.orderId}`, icon: 'none' }); // 触发打印 this.printOrder(data.orderId); } });物理打印:调用
wx.openDocument()打开 HTML 打印模板,再用wx.print()(需微信硬件平台认证)或对接云打印 SDK(如“易联云”)。
注意:WebSocket 在微信小程序中需
wss://(HTTPS),且域名必须在公众平台配置。若无 HTTPS,可用wx.request()模拟长轮询(每3秒查一次/order/status?orderId=xxx),牺牲实时性保可用性。
避坑 / 常见问题 / 排查
现象1:下单后t_dish.stock未扣减,库存仍为原值
原因:UPDATE t_dish SET stock = stock - #{quantity} WHERE id = #{dishId} AND stock >= #{quantity}返回0行影响,但 Service 层未捕获update返回值判断是否成功
解决:在DishMapper.updateStock()后加if (rows != 1) throw new RuntimeException("库存不足");现象2:同一用户多次点击“提交订单”,数据库生成多条
t_order记录
原因:前端按钮未置灰,或后端幂等校验order_hash字段未建索引,导致countByUserAndHash查询慢而失效
解决:为t_order(user_id, order_hash, create_time)建联合索引现象3:WebSocket 连接后
onMessage不触发,控制台无报错
原因:WebSocketServer.sendInfo()中session.getBasicRemote().sendText(message)抛异常但被吞掉
解决:在sendInfo方法中try-catch并log.error("send failed", e),常见原因是 session 已关闭或网络中断
5. 毕设答辩高频问题预演:从数据库设计到微信登录态的六问六答
5.1 “为什么用 MyBatis 而不用 JPA?Hibernate 的缓存机制你了解吗?”
答:
我们选用 MyBatis 是为了精准控制 SQL。点餐系统对性能敏感,比如“搜索菜品”需动态拼接WHERE条件,MyBatis 的<if>标签比 JPA 的@Query注解更直观;“扣减库存”需UPDATE ... WHERE stock >= #{quantity}这种乐观锁写法,MyBatis XML 中一行搞定,而 JPA 需写@Modifying+@Query,且容易忽略clearAutomatically = true导致一级缓存脏读。至于 Hibernate 缓存,二级缓存虽能提升查询速度,但点餐场景下菜品价格、库存实时变动频繁,缓存一致性维护成本远高于直接查库,所以宁可每次走 DB,也不引入缓存复杂度。
5.2 “t_order 表的 status 字段为什么用 TINYINT 而不用 ENUM?”
答:
ENUM 类型在 MySQL 中本质是字符串,排序、索引效率低于整数;更重要的是,ENUM 值变更需ALTER TABLE,而点餐业务未来可能增加“已取消”、“退款中”等状态,用TINYINT可直接UPDATE t_order SET status = 4 WHERE ...,无需改表结构。我们在OrderService.updateStatus()中用switch(status)严格校验状态流转(如case 0: return 1; // 待支付→已支付),既保证业务规则,又保留扩展性。
5.3 “微信登录的 code 只能用一次,那用户退出再登录怎么办?”
答:code确实是一次性的,但我们的设计是登录态与小程序生命周期解耦。用户首次进入,wx.login()换取openid,后端生成 JWT Token 返回给小程序,小程序存入wx.setStorageSync('token')。后续所有请求(如加购、下单)都在 header 中携带Authorization: Bearer xxx,后端JwtFilter解析 Token 获取userId,不再依赖code。用户退出只是清空本地token,再次进入时重新wx.login()即可,不影响历史订单关联。
5.4 “购物车数据存在哪里?小程序关闭后会丢失吗?”
答:
购物车数据存在小程序本地内存 + 后端临时表双备份。
- 前端:
app.js中globalData.cart = [],页面间通过getApp().globalData.cart共享,关闭小程序后内存释放; - 后端:用户登录后,
CartService.initCartIfAbsent()会检查t_cart表(用户ID为外键),若无则创建空购物车。用户在菜单页加购时,前端调POST /cart/add,后端将菜品存入t_cart_item。这样即使小程序关闭,下次打开时onLaunch中调GET /cart/list即可恢复购物车。
补充:
t_cart表有expire_time字段,7天未操作自动清理,避免垃圾数据堆积。
5.5 “订单支付你们做了吗?微信支付接口怎么对接的?”
答:
我们实现了微信 JSAPI 支付的全流程,但出于毕设安全考虑,使用微信支付沙箱环境。
- 用户提交订单后,后端
OrderService.createOrder()生成预支付交易单,调用微信统一下单接口https://api.mch.weixin.qq.com/pay/unifiedorder,传入body、out_trade_no(订单号)、total_fee(分)、spbill_create_ip、notify_url(支付结果回调地址); - 微信返回
prepay_id,后端组装签名参数,返回给小程序; - 小程序调
wx.requestPayment()传入参数,拉起微信支付界面; - 支付成功后,微信服务器异步通知
notify_url,后端PayNotifyController.notify()校验签名,更新t_order.status = 1并发送 WebSocket 推送。
关键点:
notify_url必须是公网可访问的 HTTPS 地址,沙箱环境用https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder,且需在微信支付商户平台开启沙箱。
5.6 “数据库备份和恢复方案有考虑吗?”
答:
我们提供了自动化备份脚本。在diancan_ssm/src/main/resources/backup/目录下,有backup.bat(Windows)和backup.sh(Linux):
:: backup.bat @echo off set BACKUP_DIR=D:\diancan_backup set DATE=%date:~0,4%%date:~5,2%%date:~8,2% mysqldump -u root -p diancan > %BACKUP_DIR%\diancan_%DATE%.sql每天凌晨2点通过 Windows 任务计划程序执行,保留最近7天备份。恢复时只需mysql -u root -p diancan < diancan_20240520.sql。此外,在application.properties中配置了spring.jpa.hibernate.ddl-auto=validate,启动时校验实体类与数据库表结构是否一致,避免手动改表漏同步。
6. 真机演示不翻车的终极技巧:从域名备案到打印故障的五道保险
6.1 无备案域名的应急方案:用腾讯云轻量服务器做反向代理
没有已备案的域名?别急着买。用腾讯云轻量应用服务器(首月9.9元)+ Nginx,30分钟搭好 HTTPS 代理:
购买与初始化:
选“Ubuntu 22.04”,地域选离你近的(如上海),防火墙开放80、443、8080端口。安装 Nginx 并申请 SSL:
sudo apt update && sudo apt install nginx -y sudo snap install core; sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot sudo certbot --nginx -d your-free-domain.com # 用腾讯云赠送的免费域名配置反向代理:
编辑/etc/nginx/sites-available/default:
本文还有配套的精品资源,点击获取