资讯动态

家政小程序全栈实战:登录、订单与支付闭环解析

发布时间:2026/9/16 14:08:48 来源:尧图企业网站定制
简介面向高校毕业设计场景的家政服务微信小程序项目以Java编写RESTful后端接口、微信小程序为前端载体解决服务预约、订单管理、用户登录、会员与支付等常见业务需求。压缩包共170个文件约2.33MB其中js、wxss、wxml对应小程序逻辑、样式与页面结构json存放页面配置及模拟数据jpg/png/gif用于界面展示与操作演示md/txt则为相关说明。项目目录按首页、家政分类、保洁下单、登录、个人中心、订单、会员、支付、设置等模块组织便于按功能阅读和复用压缩包中还包含后端服务相关文件可配合前端核对接口路径与数据交互方式。对理解前后端合作、数据库表设计以及微信小程序项目分层结构有直接帮助。已有55人学习下载适合需要快速搭建家政预约类毕业设计原型、并希望获得完整前后端代码参照的开发者。1. 家政服务微信小程序的项目骨架与选型逻辑解压这个 zip 后先看到的是一串._city、._cleanwindow、._home之类的目录。别被干扰这是 macOS 归档时生成的 AppleDouble 元数据记录权限和时间戳跟业务代码无关。真正有价值的是去掉前缀后的pages一级目录index、login、order、my、cleanwindow这些目录名基本就是功能地图。整套代码的定位很清晰是一个「微信小程序 Java 后端 MySQL」的完整毕业设计闭环用户扫码进来、浏览服务分类、下预约单、支付、评价每一步都有对应页面和接口。适合两类人一是正在做微信小程序毕业设计的学生需要一份能跑通前后端联调的参考工程二是想快速验证「小程序登录态 订单流转 支付回调」这三件事怎么串起来的前后端工程师。它解决的不是高并发问题而是流程完整性问题——把这三条链路理清楚换任何业务都能套用。2. 小程序前端骨架WXML组件树与登录态管理2.1 从 zip 目录反推页面结构前端部分没有用 uni-app 或 Taro而是微信原生的小程序框架WXML 负责结构、WXSS 负责样式、JavaScript 负责交互。先把目录过一遍按功能映射到页面比直接读代码更快页面目录对应业务核心交互pages/index首页服务分类服务列表渲染、分类切换pages/cleanwindow擦窗服务详情选规格、选数量、加购物车pages/order预约单确认时间选择、地址填写、提交订单pages/login用户登录手机号授权、code 换 tokenpages/my个人中心订单列表、评价入口、地址管理pages/logs操作日志联调时查看请求记录页面层级做了两级设计tabBar内只放首页、订单、我的三个入口服务详情、支付结果这类页面通过wx.navigateTo压栈进入。这样做的原因是 tabBar 页面会常驻内存如果把服务详情也塞进 tabBar用户每次切换都要重新拉数据体验反而差。看一个典型的预约确认页 WXML!-- pages/order/order-confirm.wxml -- view classpage picker modemultiSelector bindchangeonTimeChange range{{timeRange}} value{{timeIndex}} view classtime-picker{{selectedTime}}/view /picker view classservice-item wx:for{{cartList}} wx:keyid text{{item.name}}/text text¥{{item.price}}/text /view button classsubmit-btn bindtapsubmitOrder loading{{submitting}}提交预约/button /view这个页面有三个关键点。modemultiSelector的range必须是二维数组第一维是可用日期、第二维是每个日期下的时段value对应每列选中的索引只传一维数组的话第二列根本拉不开。wx:for配合wx:keyid是列表渲染的标准做法wx:key不写或乱写会在控制台报 warning而且列表项顺序变化时状态容易错乱。loading{{submitting}}用来防止重复提交按钮进入 loading 状态后会自动禁用点击这是预约类表单最容易被忽略的细节——用户手抖点两次就生成两笔订单。2.2 自定义顶部导航栏高度适配项目在app.json里配了navigationStyle: custom也就是不用微信默认导航栏。自定义导航栏的好处是视觉上能和业务页面融为一体但代价是必须自己处理状态栏高度。这里有个标准算法// utils/navbar.js function getNavBarHeight() { const { statusBarHeight } wx.getWindowInfo() const capsule wx.getMenuButtonBoundingClientRect() const navBarHeight (capsule.top - statusBarHeight) * 2 capsule.height return { statusBarHeight, navBarHeight, capsule } }wx.getMenuButtonBoundingClientRect()返回的是右上角胶囊按钮就是那个「···」和「⊙」的位置信息。胶囊按钮垂直居中于导航栏所以胶囊顶部到状态栏底部的距离乘以 2再加上胶囊自身高度就是导航栏的总高度。wx.getWindowInfo()是基础库 2.20.1 之后的推荐写法替代了已废弃的wx.getSystemInfoSync()。注意statusBarHeight在不同机型上差异很大iPhone 14 Pro 是 59pxiPhone 8 是 20pxAndroid 主流机型在 24~48px 之间。所以这个值不能写死必须在onLoad里动态获取后通过setData写入页面的paddingTop。如果只适配了 iPhone 而没处理 Android 刘海屏真机调试时会发现标题顶部被摄像头挖孔挡住这是自定义导航栏最常见的翻车现场。2.3 wx.login 到 token 的完整链路登录态是整个小程序的数据源头这个项目的做法是典型的「code 换 token」模式// utils/auth.js function login() { return new Promise((resolve, reject) { wx.login({ success: (res) { wx.request({ url: https://api.example.com/api/auth/login, method: POST, data: { code: res.code }, success: (r) { // r.data 是后端返回的统一响应体 { code, message, data } wx.setStorageSync(token, r.data.data.token) resolve(r.data.data) }, fail: reject }) } }) }) }wx.login拿到的code是一次性的5 分钟内有效且只能使用一次。后端收到 code 后要立刻拿去调微信的jscode2session接口换回openid和session_key然后以openid为用户唯一标识生成 JWT 返回给小程序。如果前端对同一个 code 发起两次请求第二次必然失败所以这里要做防重处理——常见做法是后端把用过的 code 存一份到 Redis 并设置 5 分钟过期或者简单地查一下用户表里有没有对应记录。拿到 token 后后续所有需要鉴权的请求都要带上Authorization: Bearer token头。项目里封装了一个request工具在响应拦截器里判断 HTTP 401 状态码一旦发现 token 过期就自动清理本地缓存并跳转到登录页。这个逻辑必须在wx.request的complete回调里统一处理而不是每个页面各自判断。3. Java后端接口层Spring Boot路由与JWT身份验证3.1 RESTful API 路径设计与统一响应体后端是标准的 Spring Boot 工程Maven 管理依赖打包后在服务器上以 Jar 方式运行。把 Controller 层捋一遍能看出接口设计有清晰的套路——按资源组织路径用 HTTP 方法表达动作方法路径功能鉴权POST/api/auth/login微信登录换取 JWT否GET/api/services服务列表否GET/api/services/{id}服务详情否POST/api/orders创建预约单JWTPOST/api/orders/{id}/cancel取消订单JWTPOST/api/orders/{id}/comment订单评价JWTPOST/api/pay/notify微信支付回调签名校验路径参数{id}对应的就是数据库表的物理主键订单相关的接口挂在/api/orders下而不是拆成独立的 order-controller 命名空间属于「资源嵌套」风格。评价接口挂到订单底下也是这个思路——评价属于订单的一个子资源后续要查「某订单的评价」时URL 直接可以表达出从属关系。所有接口返回统一响应体ResultT// common/Result.java public class ResultT { private Integer code; // 业务状态码200 成功 private String message; // 提示信息 private T data; // 业务数据 public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message ok; r.data data; return r; } public static T ResultT error(Integer code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }为什么不用 Spring 默认的ResponseEntity直接返回因为小程序前端对响应体的处理需要统一格式才能写拦截器。ResultT把业务状态码和 HTTP 状态码解耦比如「订单已取消不能再次取消」这种业务错误HTTP 状态码仍是 200但code字段返回 5001前端拿到后弹出对应的 toast。这样网络层和业务层的错误互不干扰联调时看 code 比看 HTTP 状态码快得多。3.2 JWT 拦截器鉴权与 401 处理登录接口签发 JWT其他接口验证 JWT这层逻辑用 Spring MVC 的HandlerInterceptor来做最干净// config/JwtInterceptor.java public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } try { // 解析 JWT拿到 userId塞进 request attribute Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token.substring(7)) .getBody(); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); return false; } } }secretKey是签名密钥项目里放在application.yml配置文件中生产环境必须用环境变量注入而不是写死在代码里。JWT 的 payload 里只放了userId和过期时间不存手机号等敏感信息因为 JWT 是 Base64 编码的任何人都能解码看到内容只是无法篡改。过期时间设为 7 天和微信小程序的实际使用节奏匹配——用户通常不会每天重新登录一次。拦截器注册时要小心路径匹配注意排除登录接口本身// config/WebMvcConfig.java Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/pay/notify); } }/api/pay/notify必须排除在拦截器之外因为微信支付的回调通知不带我们签发的 JWT它用的是微信自己的签名机制。如果忘记排除支付成功后回调打不进来订单永远停在「待支付」状态这类问题排查起来非常隐蔽。3.3 支付回调的验签与幂等支付回调是订单状态流转最关键的一环。小程序端调起wx.requestPayment后用户输入密码完成支付微信服务器会异步往我们配置的回调地址发一条通知。这条通知不能轻信必须验证签名。用微信支付 V3 的标准做法// service/PayNotifyService.java private boolean verifySignature(HttpServletRequest request, String requestBody) { String timestamp request.getHeader(Wechatpay-Timestamp); String nonce request.getHeader(Wechatpay-Nonce); String signature request.getHeader(Wechatpay-Signature); // 1. 用平台证书验签 // 2. 用请求体加上 timestamp/nonce 构造验签串 // 3. 验签通过后再解析 body 里的 out_trade_no 和 transaction_id // 4. 用 transaction_id 调微信查单接口二次确认金额 }回调处理结束后必须返回{code: SUCCESS}的响应体微信才认为通知送达。处理失败返回非 SUCCESS微信会按间隔重试 15 次。这也带来一个必须处理的问题回调可能重复到达。所以订单表里的pay_status字段从「待支付」改成「已支付」之前必须先查一遍当前状态只有待支付的订单才执行更新逻辑已支付的直接返回成功。4. 数据库建模MySQL表结构与订单状态迁移4.1 核心表结构与建表语句数据库用的是 MySQL 5.7字符集utf8mb4排序规则utf8mb4_general_ci。用utf8mb4而不是utf8是因为小程序端用户昵称里可能出现 Emoji 表情utf8存不下四字节字符会直接报错。核心表一共五张用户表、服务分类表、服务项表、订单表、评价表。订单表是最复杂的一张字段设计直接影响后面所有业务查询CREATE TABLE order_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号, user_id BIGINT NOT NULL COMMENT 下单用户ID, service_item_id BIGINT NOT NULL COMMENT 服务项ID, service_date DATE NOT NULL COMMENT 预约日期, service_time_slot VARCHAR(16) NOT NULL COMMENT 预约时段如 09:00-11:00, address VARCHAR(255) NOT NULL COMMENT 服务地址, amount DECIMAL(10,2) NOT NULL COMMENT 订单金额分转元, pay_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已退款, order_status TINYINT NOT NULL DEFAULT 0 COMMENT 0已创建 1服务中 2已完成 3已取消, transaction_id VARCHAR(64) DEFAULT NULL COMMENT 微信支付单号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status (user_id, order_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT家政服务订单表;DECIMAL(10,2)存金额禁止用 FLOAT 和 DOUBLE因为二进制浮点数无法精确表达十进制小数对账时会出现分级别的差异。order_no是业务订单号由后端生成规则是「日期 随机数 用户ID后四位」比如202506121504321234保证唯一性的同时还能从订单号直接看出下单日期。很多新手直接在表里用id当订单号返回给前端这有两个问题一是暴露了业务量二是微信支付回调里out_trade_no有长度限制自增 ID 在和其他系统对接时不够灵活。4.2 订单状态机用枚举约束非法跳转订单状态不是随便改的从下单到完成要经历一条有向路径。这个项目用 TINYINT 存状态码但控制状态跳转的逻辑必须收敛到一处不能在每个接口里各自判断。状态流转关系如下当前状态允许跳转触发动作0 已创建1 服务中、3 已取消支付成功回调 / 用户取消1 服务中2 已完成用户确认服务完成3 已取消无终态代码层面最关键的是取消订单的判断。创建订单后 30 分钟内可以无条件取消超过这个窗口必须联系客服处理。按下单时间计算取消窗口而不是用户点击按钮的时间这样能避免前后端时钟不一致导致的口径偏差。状态更新用一条带条件判断的 SQLint rows orderMapper.updateStatus(orderId, fromStatus, toStatus, expectedUserId); if (rows 0) { // 说明订单不存在、状态不对或不属于当前用户 throw new BusinessException(订单状态已变更请刷新后重试); }这个写法在并发场景下尤为重要。两个请求同时要对同一笔订单做「取消」和「确认完成」时UPDATE ... WHERE order_status ?的原子性保证只有一个操作能成功另一个更新行数为 0直接抛异常返回给前端。比「先 SELECT 再 UPDATE」的两步操作安全得多省掉了锁的开销。4.3 索引策略与慢查询排查订单表里已经建了一个联合索引idx_user_status (user_id, order_status)这是「我的订单列表」查询的核心索引——WHERE user_id ? AND order_status ?的查询可以直接命中。但实际业务里还有一个高频查询「按日期查当天所有已支付订单」用于后台对账。这个查询走的是create_time上的普通索引如果数据量大了之后变慢可以考虑换成(order_status, create_time)的联合索引。服务分类表的查询频率更高但数据量非常小几十条不需要加索引MySQL 对这种小表的全表扫描比走索引更快。真正需要关注的性能隐患是service_date和service_time_slot的组合查询——用户会筛选「明天上午可预约的所有服务项」如果给service_date单独建索引再用LIKE匹配时段只能走一个索引。遇到这种情况更好的做法是按照「服务项 ID 日期」建预约排期表把时段的占用状态单独拆出去。5. 微信支付V3接入与真机调试尾坑5.1 平台证书与常见报错微信支付 V3 和 V2 最大的区别是回调通知的验签不再用商户私钥而是用微信支付平台证书的公钥所以必须先下载平台证书并妥善保管。项目里配的是apiclient_key.pem商户私钥和wechatpay_platform_cert.pem平台证书密钥文件千万不能上传到 Git 仓库。联调时最容易出错的集中在三个点。第一是「无可用的平台证书」这个报错出现的原因是商户平台上没上传公钥或者本地证书文件路径写错了。第二是「支付功能暂时无法使用」这是小程序端的提示通常不是代码问题而是小程序没有开通微信支付权限或者类目选错导致支付能力被关闭。第三是回调 URL 必须用 HTTPS且不能带自定义端口微信对回调地址的协议有强校验。5.2 真机验证与对账技巧本地联调时微信回调打不到开发机需要先把本地 8080 端口暴露成一个临时公网域名再把域名填到商户平台的支付回调配置里。验证支付全链路是否打通最快的方式不是看日志而是先把微信支付商户后台的「交易账单」导出来再和本地order_info表里transaction_id不为空的记录做比对。能对上的就是真实到账的订单对不上的要么是回调没送达、要么是验签失败被拦下。这个过程比一行行看日志直观得多也能把回调重试、幂等处理这些隐藏逻辑一并验证掉。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价