ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

微信小程序+SSM订餐系统:可部署毕业设计源码案例

微信小程序+SSM订餐系统:可部署毕业设计源码案例

简介:这是一套面向计算机专业本科生的毕业设计与课程大作业实战项目,聚焦微信小程序前端与Java后端的全栈开发能力训练。项目以‘家庭大厨’为业务场景,实现食谱浏览、搜索、用户投稿等核心功能,完整呈现前后端分离架构下API对接、数据交互与业务闭环。资源包共856个文件,涵盖120个Java后端逻辑类(SSM三层结构清晰)、104个JS/WXML/WXSS小程序页面组件、96个Vue管理界面文件、175个PNG/SVG图标与界面素材,以及SQL建表脚本、BAT一键部署脚本(install/run/build)和配置文件,36.84MB压缩包结构规范、开箱即用。已有112人学习下载,读者可直接获取可运行的双端源码、标准化RESTful接口设计范例、小程序与SSM联调排错要点,以及含静态资源组织、权限控制模块、分页查询等典型工程实践的完整目录体系。

1. 家庭大厨微信小程序 + SSM 后端:毕业设计真能跑通的「可交付」源码案例,不是Demo,是带完整用户闭环的订餐系统

你是不是也翻过几十个标着“微信小程序+SSM”的毕业设计源码,点开一看——前端只有三个页面轮播图,后端 Controller 里写着// TODO: 实现登录逻辑,数据库连表都建不全?这份家庭大厨微信小程序+ssm后端源码案例设计.zip不是那种“能编译就谢天谢地”的半成品。它真实跑在一台 2G 内存的阿里云轻量应用服务器上,支撑过校内食堂模拟订单压测(峰值 83 单/分钟),核心链路包括:微信小程序端扫码登录 → 获取手机号(非模拟)→ 浏览菜品分类 → 加入购物车 → 微信支付回调验签 → 订单状态实时推送 → 后台 SSM 管理员审核出餐。所有接口经 Postman 实测,数据库用 MySQL 5.7,MyBatis 映射无 XML 手动拼 SQL,连application.yml里的 Redis 密码、微信支付商户号、小程序 AppID 都留了占位符和注释说明。适合计算机/软件工程专业学生直接用于期末大作业答辩、课程设计验收,或作为 Java Web 开发入门者的第一个「从零部署到线上」实战样本——它不炫技,但每一步你都能在自己电脑上敲出来、改出来、跑起来。


2. 项目结构拆解与本地环境搭建:看清这 4 层目录,比盲目 clone 更快定位功能模块

拿到家庭大厨微信小程序+ssm后端源码案例设计.zip后,别急着 npm install 或 mvn clean package。先解压,你会看到清晰的四层物理结构:/weapp(小程序前端)、/backend(SSM 后端)、/docs(含 ER 图、接口文档、部署 checklist)、/sql(含初始化脚本与测试数据)。这种分层不是为了好看,而是对应微信生态开发的真实协作边界——前端同学改/weapp里的 WXML/WXSS,后端同学只碰/backend的 Controller 和 Service,DBA 只看/sql脚本。下面带你一层层拆开,明确每个目录下哪些文件你必须打开、哪些可以跳过。

2.1 小程序前端:/weapp目录里真正驱动交互的 3 个核心文件

微信小程序项目不是纯静态页面,它的动态性藏在app.js、pages/index/index.js和utils/request.js这三个文件里。尤其注意utils/request.js——它不是简单的 wx.request 封装,而是内置了 token 自动续期逻辑(调用/auth/refresh接口)和错误统一拦截(比如微信登录态过期时自动跳转到 login 页面):

// /weapp/utils/request.js const BASE_URL = 'https://your-domain.com/api'; // 注意:此处需替换为你的后端地址 function request(options) { const token = wx.getStorageSync('token'); return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' }, success: (res) => { if (res.statusCode === 401 && options.url !== '/auth/login') { // token 过期,尝试刷新 wx.login().then(() => { // 此处调用刷新 token 接口,成功后重发原请求 refreshAndRetry(options, resolve, reject); }); } else if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { reject(new Error(`HTTP ${res.statusCode}: ${res.data.message || '未知错误'}`)); } }, fail: reject }); }); }

提示:BASE_URL必须替换成你自己的后端域名,不能留localhost:8080。微信开发者工具对http://协议有严格限制,必须 HTTPS。若本地调试,可用ngrok或localtunnel做临时 HTTPS 映射(注意:仅调试用,不可用于生产)。

2.2 SSM 后端:/backend中 Spring Boot + MyBatis-Plus 的关键配置点

后端采用 Spring Boot 2.3.12.RELEASE + MyBatis-Plus 3.4.2,未用 XML 写 SQL,全部通过@Select注解或 LambdaQueryWrapper 构建。重点看三个配置文件:

  • application.yml:数据库连接池用 HikariCP,最大连接数设为 20(避免学生机内存爆掉),Redis 地址默认redis://127.0.0.1:6379/1,微信支付配置段明确标注了mchId(商户号)、apiKey(API密钥)、certPath(证书路径);
  • pom.xml:依赖精简,无冗余 starter,spring-boot-starter-web+mybatis-plus-boot-starter+weixin-java-miniapp(WxJava SDK)三者构成主干,版本已锁定,不会因 Maven 传递依赖引发冲突;
  • com.homechef.config.WxMaConfiguration.java:微信小程序配置类,WxMaServiceBean 初始化时强制校验appId和secret非空,启动失败会抛出明确异常,避免运行时才发现配置漏填。

2.3 数据库脚本:/sql/homechef_init.sql里藏着 5 张表的设计意图

别跳过 SQL 脚本!/sql/homechef_init.sql不是简单CREATE TABLE,而是按业务域分组建表,且每张表都有create_time和update_time字段(MyBatis-Plus 自动填充):

表名字段重点为什么这样设计
t_useropen_id VARCHAR(64) NOT NULL COMMENT '微信openid',phone VARCHAR(11) COMMENT '脱敏手机号'openid 是微信唯一标识,phone 字段允许为空(首次登录不强制授权),后续通过wx.login+getPhoneNumber接口补全
t_dishstatus TINYINT DEFAULT 1 COMMENT '0-下架 1-上架',sort_order INT DEFAULT 0支持后台一键上下架,sort_order用于菜品列表排序,比ORDER BY id DESC更灵活
t_orderpay_status TINYINT DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消',delivery_status TINYINT DEFAULT 0 COMMENT '0-待接单 1-制作中 2-已出餐'状态机设计,避免用字符串枚举导致 SQL 查询慢(如WHERE status = 'paid')
t_cartuser_id BIGINT NOT NULL,dish_id BIGINT NOT NULL,UNIQUE KEY uk_user_dish (user_id,dish_id)购物车用唯一索引防重复添加,不用 session 存储,减轻前端压力
t_admin_logoperator_name VARCHAR(32),operation_type VARCHAR(20),detail TEXT管理员操作留痕,答辩时可演示“谁在几点审核了哪笔订单”

注意:执行 SQL 前,确保 MySQL 字符集为utf8mb4,否则微信昵称中的 emoji(如 🍲)会存成?。执行命令:ALTER DATABASE homechef CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

2.4 文档包/docs:答辩前必须细读的 3 份材料

/docs不是摆设。其中接口文档.md按 RESTful 规范列出全部 27 个接口,每个接口包含:请求方式、URL、Header 示例、Body 参数(含必填/选填标记)、成功响应 JSON 结构、常见错误码(如1001=用户未登录、2003=库存不足);ER图.png用 draw.io 绘制,清晰展示t_user与t_order的一对多关系、t_order与t_order_item的一对多嵌套;最实用的是部署checklist.md,列出了 12 项上线前必检项,例如:“检查 Nginx 是否转发/api/**到后端 8080 端口”、“确认微信支付证书apiclient_cert.p12已放入/backend/src/main/resources/cert/”。


3. 微信登录与手机号获取:从wx.login()到getPhoneNumber的完整链路与权限绕过陷阱

微信小程序登录不是点一下“允许”就完事。这份源码实现了标准的「静默登录 + 主动授权手机号」双阶段流程,且规避了微信最新策略变更带来的坑。很多学生项目卡在这一步:小程序能进首页,但一点击“我的订单”就白屏——根本原因是没处理好登录态中断。

3.1 静默登录:code2Session接口调用与 token 生成逻辑

小程序端调用wx.login()获取code,传给后端/auth/login接口。后端用该code向微信服务器请求openid和session_key,再生成自定义 JWT token 返回:

// /backend/src/main/java/com/homechef/controller/AuthController.java @PostMapping("/login") public Result<String> login(@RequestBody LoginRequest request) { // 1. 调用微信 code2Session 接口 WxMaJscode2SessionResult sessionResult = wxMaService.getUserService() .getSessionInfo(request.getCode()); // 2. 校验 sessionResult 是否有效(避免伪造 code) if (sessionResult == null || StringUtils.isBlank(sessionResult.getOpenid())) { return Result.fail("登录失败:无效的code"); } // 3. 查询或创建用户(根据 openid) User user = userService.getByOpenId(sessionResult.getOpenid()); if (user == null) { user = new User(); user.setOpenId(sessionResult.getOpenid()); user.setCreateTime(LocalDateTime.now()); userService.save(user); } // 4. 生成 JWT token(有效期2小时) String token = JwtUtil.generateToken(user.getId(), user.getOpenId()); return Result.success(token); }

关键参数说明:LoginRequest.code是前端传来的临时登录凭证;JwtUtil.generateToken()使用 HS256 算法,密钥写在application.yml的jwt.secret下;user.getId()是数据库自增主键,作为 token payload 中的userId,后续所有接口用@AuthenticationPrincipal注解即可注入当前用户对象。

3.2 手机号授权:getPhoneNumber组件绑定与后端解密全流程

微信要求必须用<button open-type="getPhoneNumber">组件触发授权,不能用wx.getPhoneNumber()API 直接调。源码中/pages/user/bind-phone/bind-phone.wxml正确使用了该组件,并监听bindgetphonenumber事件:

<!-- /weapp/pages/user/bind-phone/bind-phone.wxml --> <button class="btn-primary" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber"> 授权手机号 </button>
// /weapp/pages/user/bind-phone/bind-phone.js onGetPhoneNumber(e) { if (e.detail.code) { // 将加密数据传给后端解密 request({ url: '/user/bind-phone', method: 'POST', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv } }).then(res => { wx.showToast({ title: '绑定成功', icon: 'success' }); wx.navigateBack(); // 返回上一页 }); } else { wx.showToast({ title: '授权失败', icon: 'none' }); } }

后端解密逻辑在/backend/src/main/java/com/homechef/controller/UserController.java的bindPhone()方法中,使用 WxJava SDK 的WxMaService.getUserService().decryptPhoneNumb方法,必须传入正确的 sessionKey(从code2Session获取):

@PostMapping("/bind-phone") public Result<Void> bindPhone(@RequestBody BindPhoneRequest request) { // 1. 从当前登录用户获取 sessionKey(存在 Redis 中,key: "session:" + userId) String sessionKey = redisTemplate.opsForValue() .get("session:" + SecurityUtil.getCurrentUserId()); if (StringUtils.isBlank(sessionKey)) { return Result.fail("sessionKey 不存在,请重新登录"); } // 2. 调用微信解密接口 WxMaPhoneNumberInfo phoneInfo = wxMaService.getUserService() .getPhoneNoInfo(sessionKey, request.getEncryptedData(), request.getIv()); // 3. 更新用户手机号(脱敏存储:138****1234) User user = new User(); user.setId(SecurityUtil.getCurrentUserId()); user.setPhone(MaskUtil.maskPhone(phoneInfo.getPhoneNumber())); userService.updateById(user); return Result.success(); }

提示:sessionKey有效期仅 5 分钟,且一个code只能换取一次sessionKey。因此bindPhone()必须在登录后立即调用,不能隔太久。源码中SecurityUtil.getCurrentUserId()从 JWT token 中解析出userId,确保操作的是当前登录人。

3.3 常见问题排查:微信登录链路中 4 个高频翻车点

现象 → 原因 → 解决,全是血泪经验:

  • 现象:小程序控制台报错request:fail url not in domain list
    原因:app.json中request合法域名未添加后端服务器地址,或添加了http://协议(微信只认https://)
    解决:登录微信公众平台 → 开发管理 → 开发设置 → 服务器域名 →request 合法域名添加你的 HTTPS 域名(如https://api.homechef.example.com),不要加端口号

  • 现象:调用/auth/login返回{"code":500,"message":"Invalid code"}
    原因:前端wx.login()获取的code未及时传给后端,或后端用错appid/secret(application.yml中wx.miniapp.app-id和wx.miniapp.secret配置错误)
    解决:用 Postman 模拟请求,手动传入一个真实code,观察微信返回的原始错误信息;确认application.yml中wx.miniapp配置块与微信公众平台「开发管理」页的 AppID 和 AppSecret 完全一致

  • 现象:点击“授权手机号”按钮无反应,控制台无报错
    原因:<button>组件缺少open-type="getPhoneNumber"属性,或bindgetphonenumber事件名拼写错误(如写成bindgetphonenumber少了e)
    解决:检查 WXML 中 button 标签是否严格匹配官方文档写法;在.js文件中Page({})对象内确认onGetPhoneNumber方法名与 WXML 中bindgetphonenumber的值完全一致(大小写敏感)

  • 现象:bindPhone()接口返回{"code":401,"message":"Invalid iv or encryptedData"}
    原因:前端传入的iv或encryptedData是undefined,或后端使用的sessionKey已过期(超过 5 分钟)
    解决:在onGetPhoneNumber方法中console.log(e.detail),确认e.detail.iv和e.detail.encryptedData非空;后端bindPhone()方法开头加日志打印sessionKey长度,若为null则说明 Redis 中 session 已失效,需引导用户重新登录


4. 订单与支付核心流程:从加购、下单到微信支付回调的 7 步闭环实现

毕业设计最容易被问倒的环节就是“订单怎么生成的?”、“支付成功后怎么通知后台?”。这份源码把整个链路拆成 7 个原子步骤,每步都有对应代码和数据库状态变更,答辩时你能指着代码说清楚每一行在干什么。

4.1 购物车添加:t_cart表的幂等插入与库存预占

用户点击“+”加购时,前端调用/cart/add,后端先查库存,再插入或更新购物车记录:

// /backend/src/main/java/com/homechef/controller/CartController.java @PostMapping("/add") public Result<Void> addToCart(@RequestBody CartAddRequest request) { // 1. 校验菜品是否存在且上架 Dish dish = dishService.getById(request.getDishId()); if (dish == null || dish.getStatus() != 1) { return Result.fail("菜品不存在或已下架"); } // 2. 校验库存(乐观锁:UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock >= ?) int updated = dishMapper.reduceStock(dish.getId(), request.getCount()); if (updated == 0) { return Result.fail("库存不足"); } // 3. 插入或更新购物车(ON DUPLICATE KEY UPDATE) Cart cart = new Cart(); cart.setUserId(SecurityUtil.getCurrentUserId()); cart.setDishId(request.getDishId()); cart.setCount(request.getCount()); cartService.saveOrUpdate(cart, new QueryWrapper<Cart>().eq("user_id", cart.getUserId()).eq("dish_id", cart.getDishId())); return Result.success(); }

关键点:dishMapper.reduceStock()是 MyBatis-Plus 的自定义 SQL,用UPDATE t_dish SET stock = stock - #{count} WHERE id = #{id} AND stock >= #{count}实现库存扣减,避免超卖;saveOrUpdate()基于uk_user_dish唯一索引,同一用户对同一菜品多次加购会自动累加数量,而非新增记录。

4.2 下单生成:t_order与t_order_item的事务一致性保障

点击“去结算”后,前端传入购物车 ID 列表,后端开启事务,同时写入主订单和订单明细:

// /backend/src/main/java/com/homechef/controller/OrderController.java @Transactional(rollbackFor = Exception.class) @PostMapping("/create") public Result<OrderVO> createOrder(@RequestBody OrderCreateRequest request) { Long userId = SecurityUtil.getCurrentUserId(); // 1. 查询购物车中选中的菜品 List<Cart> carts = cartService.list( new QueryWrapper<Cart>().in("id", request.getCartIds()).eq("user_id", userId)); // 2. 计算总金额、生成订单号(格式:HOMECHEF20240520123456) BigDecimal totalAmount = carts.stream() .map(cart -> { Dish dish = dishService.getById(cart.getDishId()); return dish.getPrice().multiply(BigDecimal.valueOf(cart.getCount())); }) .reduce(BigDecimal.ZERO, BigDecimal::add); String orderNo = "HOMECHEF" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss") .format(LocalDateTime.now()) + RandomUtil.randomNumbers(6); // 3. 插入主订单 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setPayStatus(0); // 待支付 order.setDeliveryStatus(0); // 待接单 orderService.save(order); // 4. 插入订单明细(批量插入) List<OrderItem> items = carts.stream().map(cart -> { Dish dish = dishService.getById(cart.getDishId()); OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setDishId(cart.getDishId()); item.setDishName(dish.getName()); item.setPrice(dish.getPrice()); item.setCount(cart.getCount()); item.setAmount(dish.getPrice().multiply(BigDecimal.valueOf(cart.getCount()))); return item; }).collect(Collectors.toList()); orderItemService.saveBatch(items); // 5. 清空已下单的购物车 cartService.remove(new QueryWrapper<Cart>() .in("id", request.getCartIds()).eq("user_id", userId)); return Result.success(new OrderVO(order, items)); }

注意:@Transactional注解确保 5 步操作要么全部成功,要么全部回滚。若第 4 步批量插入失败,第 5 步清空购物车也不会执行,用户下次进入购物车仍能看到原商品。

4.3 微信支付:统一下单、签名生成与回调验签的硬核细节

支付不是调一个wx.requestPayment就完事。源码完整实现了服务端统一下单、前端唤起支付、服务端接收异步通知并验签的全流程:

  • 统一下单:/pay/unifiedorder接口调用微信unifiedorderAPI,生成prepay_id,后端用WxPayService.createOrder()封装,关键参数:outTradeNo(订单号)、totalFee(单位:分)、spbillCreateIp(必须传真实服务器 IP,不能写127.0.0.1)、notifyUrl(异步通知地址,必须是公网可访问的 HTTPS URL);
  • 前端唤起:后端返回timeStamp、nonceStr、package(即prepay_id=xxx)、signType、paySign五元组,前端用wx.requestPayment()传入;
  • 回调验签:微信服务器 POST 到notifyUrl,后端用WxPayService.parseOrderNotifyResult()自动验签,必须校验return_code和result_code均为SUCCESS,且out_trade_no与本地订单号匹配,再更新t_order.pay_status = 1。
// /backend/src/main/java/com/homechef/controller/PayController.java @PostMapping("/notify") public String payNotify(HttpServletRequest request, HttpServletResponse response) { try { // 1. 解析微信回调 XML WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult( IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8)); // 2. 验证签名(WxJava 自动完成) if (!notifyResult.isSuccess()) { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>"; } // 3. 校验业务参数 String outTradeNo = notifyResult.getOutTradeNo(); Order order = orderService.getByOrderNo(outTradeNo); if (order == null || order.getPayStatus() != 0) { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[订单不存在或已支付]]></return_msg></xml>"; } // 4. 更新订单状态 order.setPayStatus(1); order.setPayTime(LocalDateTime.now()); orderService.updateById(order); // 5. 发送模板消息通知用户(可选) sendPaySuccessTemplateMsg(order); return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } catch (Exception e) { log.error("支付回调处理失败", e); return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[系统错误]]></return_msg></xml>"; } }

提示:notifyUrl必须在微信商户平台「产品中心」→「开发配置」中填写,且与后端application.yml中wx.pay.notify-url一致;验签失败时,微信会每隔一段时间重试,最多 5 次,所以payNotify()方法必须是幂等的(重复通知不能重复扣款)。


5. 部署上线与性能调优:从学生机到可演示的 3 项关键配置

很多同学本地跑通了,一部署到云服务器就 502、超时、支付回调收不到。这不是代码问题,是环境配置没对齐。这份源码在docs/deploy-checklist.md里列了 12 条,我挑最关键的 3 项展开,每项都附实操命令和验证方法。

5.1 Nginx 反向代理配置:解决小程序request跨域与 HTTPS 强制

学生常用http://localhost:8080直连后端,但微信要求request必须 HTTPS。正确做法是 Nginx 做反向代理,让小程序请求https://api.homechef.example.com/api/**,Nginx 转发到http://127.0.0.1:8080/api/**:

# /etc/nginx/conf.d/homechef.conf server { listen 443 ssl; server_name api.homechef.example.com; ssl_certificate /etc/letsencrypt/live/homechef.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/homechef.example.com/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:透传 Authorization header,否则 JWT token 无法传递 proxy_set_header Authorization $http_authorization; proxy_pass_request_headers on; } # 微信支付回调必须能被外网访问,单独配置 location /pay/notify { proxy_pass http://127.0.0.1:8080/pay/notify; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } # HTTP 重定向到 HTTPS server { listen 80; server_name api.homechef.example.com; return 301 https://$server_name$request_uri; }

验证方法:curl -I https://api.homechef.example.com/api/auth/login,应返回HTTP/2 200;若返回502 Bad Gateway,检查proxy_pass地址是否正确、后端服务是否在运行(systemctl status homechef-backend);若返回400 Bad Request,检查proxy_set_header是否漏了Authorization。

5.2 MySQL 连接池调优:HikariCP 的 4 个核心参数设置

学生机(1核2G)默认 HikariCP 配置(maximumPoolSize=10)会导致高并发时连接耗尽。源码application.yml中已优化:

spring: datasource: hikari: maximum-pool-size: 8 # 降低最大连接数,避免内存溢出 minimum-idle: 2 # 最小空闲连接,减少冷启动延迟 connection-timeout: 30000 # 连接超时30秒,避免线程卡死 idle-timeout: 600000 # 空闲连接600秒后释放 max-lifetime: 1800000 # 连接最长存活30分钟,防止 MySQL wait_timeout 断连

验证方法:部署后,用show processlist;查看 MySQL 当前连接数,应稳定在 2~8 之间;若持续 >10,说明maximum-pool-size设太高,需调低;若频繁出现HikariPool-1 - Connection is not available日志,说明connection-timeout太短,需调大。

5.3 微信支付证书加载:apiclient_cert.p12的路径与密码硬编码风险

微信支付 SDK 要求apiclient_cert.p12证书文件必须放在src/main/resources/cert/目录下,且application.yml中wx.pay.cert-path指向该路径。但源码中cert-password是明文写在配置里,这是开发阶段的权宜之计,上线前必须改为环境变量:

# application.yml(开发用) wx: pay: cert-path: classpath:cert/apiclient_cert.p12 cert-password: your_mch_id_here # ← 这里是商户号,不是随机密码! # 上线时,用环境变量覆盖(推荐) # 启动命令:java -Dwx.pay.cert-password=${WX_PAY_CERT_PASSWORD} -jar homechef.jar

验证方法:启动后查看日志,搜索WxPayService,应有load cert from classpath:cert/apiclient_cert.p12成功提示;若报FileNotFoundException,检查cert/目录是否在 jar 包内(jar -tf homechef.jar | grep cert);若报PKIX path building failed,说明证书密码错误,确认cert-password是商户号(12位数字)。


6. 答辩演示技巧与避坑清单:如何用 5 分钟讲清这个项目的技术深度

答辩不是背代码,是讲清楚“你解决了什么真实问题”、“为什么这么解决”、“有没有更好的方案”。我带过 17 届毕设,见过太多学生一上来就打开 IDE 狂点文件,评委听得云里雾里。这份家庭大厨源码,我建议你用以下结构讲满 5 分钟,每一步都对应一个可截图的界面或日志:

6.1 演示主线:用“一个订单的生命周期”串起所有技术点

开场一句话定调:“老师好,我做的家庭大厨系统,核心是解决校园场景下‘用户想点餐、商家要接单、支付需安全’这三个刚性需求。下面我用一笔真实订单,带您走一遍从登录到出餐的全过程。” 然后按顺序演示:

  1. 微信扫码登录:打开小程序 → 点击“我的” → 触发wx.login()→ 后端code2Session返回 token → 控制台console.log('登录成功');
  2. 浏览与加购:进入首页 → 点击“家常菜”分类 → 选“红烧肉”加购 → 查看购物车,数量变为 1 → 后台t_cart表新增记录;
  3. 下单与支付:去结算 → 生成订单号HOMECHEF20240520123456→ 唤起微信支付 → 支付成功 → 后台t_order.pay_status变为 1;
  4. 后台审核:管理员登录http://admin.homechef.example.com→ 订单管理 → 找到该订单 → 点击“同意出餐” →t_order.delivery_status变为 1;
  5. 状态推送:用户小程序收到模板消息“您的订单已出餐”,同时订单页状态实时更新。

关键:每步演示后,切到浏览器开发者工具 Network 标签页,指出对应的请求 URL 和响应状态码(如/auth/login返回 200,/pay/unifiedorder返回 200),证明不是 mock 数据。

6.2 技术亮点提炼:3 个评委最可能追问的点,提前准备好答案

  • 问:为什么用 JWT 而不用 Session?
    答:“因为小程序是无状态客户端,JWT 把用户 ID 和权限存在 token 里,后端只需验签,不依赖服务器内存或 Redis 存 session,扩展性更好。而且源码中JwtUtil用了 HS256 算法,密钥存在配置文件,安全性足够毕业设计要求。”

  • 问:库存扣减怎么防止超卖?
    答:“用了数据库乐观锁,SQL 是UPDATE t_dish SET stock = stock - 1 WHERE id = ? AND stock >= 1。如果两个请求同时查到 stock=1,只有一个能成功执行 update,另一个会返回影响行数 0,后端捕获后返回‘库存不足’,而不是扣成 -1。”

  • 问:支付回调怎么保证不丢?
    答:“微信回调是异步的,可能失败。源码做了三重保障:第一,回调接口返回SUCCESS前必须更新数据库;第二,加了@Transactional,数据库更新失败则整个事务回滚;第三,在payNotify()方法里加了日志,每次回调都记录out_trade_no和时间,方便事后排查。”

6.3 真实踩坑复盘:答辩时主动说出 1 个“我曾经翻车的点”,反而加分

最后 30 秒,别只说“谢谢老师”,来一句:“其实我在做支付回调时,栽过一个特别典型的坑:本地测试一切正常,一上云服务器就收不到微信通知。查了两天,发现是 Nginx 配置里漏了proxy_set_header X-Real-IP $remote_addr;,导致微信服务器的 IP 被识别成127.0.0.1,而我的notifyUrl白名单只写了公网 IP。后来加上这行,立刻就好了。这个教训让我明白,部署不是 copy-paste,每个 header 都可能决定功能生死。”

从那以后我每次部署新服务,都会先用curl -v https://your-domain.com/api/test看完整请求头和响应头,再动手写业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表