尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

家政预约微信小程序开发实战:Spring Boot订单状态机与支付对接

发布时间:2026/9/8 14:48:04

资讯中心
01
ARTICLE

家政预约微信小程序开发实战:Spring Boot订单状态机与支付对接

家政预约微信小程序开发实战:Spring Boot订单状态机与支付对接
简介微信小程序家政预约项目源代码适合小程序开发者、家政服务从业者或产品经理参考源码采用小程序原生语法页面与逻辑分层清晰围绕预约服务核心流程实现了服务项目展示、技师/美容师选择、预约下单、订单管理、我的地址等模块能直接用于家政保洁、美容美发等预约类场景的需求落地。资源包共52个文件涵盖12个js逻辑文件、8个wxml页面结构文件、9个wxss样式文件、10个json配置文件及12个png图片和1张jpg背景图js文件承担业务逻辑、接口调用与工具函数json负责全局及页面配置wxml与wxss分别搭建页面结构和样式图片素材则用于界面展示与图标装饰整体仅219KB小巧轻量便于快速下载与阅读。已有815人学习下载适合希望从零了解小程序前后端交互、页面路由及预约流程的开发者进行断点调试和二次改造也可作为快速搭建预约类小程序的参考框架。1. 项目背景与整体架构设计1.1 核心需求解析最近整理了一份家政预约微信小程序的完整源代码不少朋友在后台留言问实现思路这里系统分享一下。这个项目解决的是上门保洁、家电清洗、保姆月嫂等家政服务在线预约的问题核心流程是用户通过微信小程序浏览服务项目、选择时间、在线支付然后后台自动派单给家政人员。整个项目分为四个端微信小程序用户端、管理后台PC Web、服务人员端H5以及后端API服务。这套代码的价值在于它覆盖了一条完整的业务闭环而不只是某个页面的Demo。从服务分类、技师管理、订单流转、支付回调、消息通知到评价体系每一环都有对应的落地方案。技术选型上后端采用Spring Boot 2.7 MyBatis Plus MySQL 8.0缓存用的Redis定时任务用Quartz。鉴权这块没有引入重型的Spring Security而是用JWT Token 拦截器的方式原因很简单——小程序端每次请求都带token服务端无状态校验即可没必要把security那一套过滤器链全部塞进来增加排查难度。文件存储用的阿里云OSS用于存放服务项目图片、用户头像和订单凭证。提示这套技术栈算是家政类小程序相当成熟的组合社区资料多遇到问题搜解决方案很方便。如果你想快速启动业务不建议在这个阶段引入微服务架构单体应用足够支撑早期的业务迭代。1.2 代码库结构与模块划分整个工程采用多模块Maven结构我会把目录结构和职责提前梳理清楚具体说明如下home-service/ ├── home-api/ # 小程序端接口模块统一前缀 /api/user ├── home-admin/ # 管理后台接口模块统一前缀 /api/admin ├── home-common/ # 公共工具类常量统一返回体 ├── home-provider/ # 业务核心逻辑service层实现 ├── home-job/ # 定时任务模块订单超时处理、排班 ├── home-mapper/ # MyBatis Plus mapper与实体 └── sql/ # 数据库DDL脚本可直接执行前端目录小程序端按页面维度组织每个页面一个文件夹包含四项基本文件pages/ ├── index/ # 首页服务分类 推荐技师 ├── order/ # 下单页核心包含选时间、选地址、选服务 ├── orderList/ # 订单列表多状态切换 ├── orderDetail/ # 订单详情包含取消、支付、评价入口 ├── user/ # 个人中心包含地址管理、优惠券、余额 ├── login/ # 微信授权登录页 └── common/ # 公共组件顶部导航、空状态、加载动画等数据库一共设计了12张表其中几张核心表如下表名说明关键字段t_service_item服务项目表price原价、discount_price折扣价、sales销量t_service_category服务分类表parent_id支持二级分类t_technician家政人员表status0空闲/1忙碌、score评分t_order订单主表order_status状态机、pay_status支付状态t_order_time订单时间表work_date服务日期、time_slot时间段t_address用户地址表is_default默认地址标记t_coupon优惠券表limit_price满减门槛t_evaluation评价表order_id订单号、images晒图订单表的order_status设计成了状态机这是整个系统的核心后面会展开讲。2. 核心业务模块拆解2.1 预约下单的业务流程设计家政预约和普通电商下单有本质区别核心差异在于“时间资源”的分配。买一件衣服库存充足就能直接下单但预约一个保洁阿姨必须确认她在你选的那个时间段有空不存在“无限制接单”。所以我设计了两阶段下单流程第一步“选时间”第二步“创建预约单占位”第三步“支付确认”。关键代码如下// 下单流程核心方法 Transactional(rollbackFor Exception.class) public OrderVO createOrder(CreateOrderRequest request) { // 1. 校验服务项目是否存在且上架 ServiceItem item serviceItemMapper.selectById(request.getItemId()); if (item null || item.getStatus() ! 1) { throw new BizException(服务项目不存在或已下架); } // 2. 校验时间段是否仍可预约分布式锁防止超卖 String lockKey order:time: request.getTechnicianId() : request.getWorkDate() : request.getTimeSlot(); boolean lock redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (!lock) { throw new BizException(该时间段预约人数较多请重新选择); } try { // 3. 查询该技师该时间段是否已有冲突订单 Integer count orderMapper.selectConflictCount( request.getTechnicianId(), request.getWorkDate(), request.getTimeSlot() ); if (count 0) { throw new BizException(该时间段已被预约请选择其他时间); } // 4. 计算实付金额原价 - 优惠券 - 余额抵扣 BigDecimal payAmount calculatePayAmount(request); // 5. 创建订单状态为待支付 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(currentUserId()); order.setOrderStatus(0); // 0-待支付 order.setPayAmount(payAmount); orderMapper.insert(order); // 6. 锁定时间段 lockTimeSlot(order.getId(), request); return buildOrderVO(order); } finally { redisTemplate.delete(lockKey); } }这里有个值得注意的细节第2步和第3步是双保险。Redis锁用来防止商品详情页并发点击提交MySQL的唯一索引uk_technician_slot则用于防止底层并发下的数据冲突这条索引是在t_order表上加的(technician_id, work_date, time_slot)联合唯一索引。我试过只加MySQL索引在高并发下会有大量DuplicateKeyException异常需要额外代码捕获我也试过只用Redis锁但Redis宕机后会有并发插入成功导致数据不一致。两者都保留是生产环境压测后最稳定的方案。2.2 时间段计价与优惠策略家政服务计价不同于普通商品不同时间段价格不同。这个项目采用了三段式定价工作日白天、工作日晚上、周末节假日。不同时间段的基础价格不同再叠加优惠券和余额抵扣。-- 时间段计价表结构 CREATE TABLE t_price_config ( id BIGINT PRIMARY KEY AUTO_INCREMENT, item_id BIGINT NOT NULL COMMENT 服务项目ID, time_type TINYINT NOT NULL COMMENT 1-工作日白天 2-工作日晚上 3-周末/节假日, price DECIMAL(10,2) NOT NULL COMMENT 该时间段价格, extra_fee DECIMAL(10,2) DEFAULT 0.00 COMMENT 附加费如夜间服务费, is_active TINYINT DEFAULT 1, UNIQUE KEY uk_item_time (item_id, time_type) );下单时根据用户选择的日期计算对应的time_type再取出对应价格。优惠券的设计也踩过一个坑最开始把优惠券金额简单地“满减”但用户在下单页看到的价格和支付时不一致容易引发投诉。后来调整为下单时实时计算最优券前端只是展示可用券列表和预估优惠最终金额以服务端计算结果为准前端不做任何金额计算。private BigDecimal calculatePayAmount(CreateOrderRequest request) { BigDecimal basePrice getPriceByTime(request.getItemId(), request.getWorkDate()); BigDecimal discount BigDecimal.ZERO; // 如果用户选择了优惠券校验是否可用 if (request.getCouponId() ! null) { Coupon coupon couponMapper.selectById(request.getCouponId()); // 校验有效期、最低消费、是否属于该用户 if (coupon ! null coupon.getUserId().equals(currentUserId()) coupon.getLimitPrice().compareTo(basePrice) 0 coupon.getExpireTime().isAfter(LocalDateTime.now())) { discount coupon.getAmount(); } } // 余额抵扣 BigDecimal balanceDeduct request.getUseBalance() ? userMapper.selectBalance(currentUserId()) : BigDecimal.ZERO; return basePrice.subtract(discount).subtract(balanceDeduct).max(BigDecimal.ZERO); }2.3 订单状态机与取消规则订单状态是整个系统的“神经系统”。我在设计订单状态时采用了状态机模式State Machine而不是简单的if-else判断这样当状态流转复杂时用户取消、超时关闭、退款中、退款完成、投诉介入代码不会变成一团乱麻。状态定义如下0 - 待支付 → 可取消用户、可超时关闭系统 1 - 已支付 → 可申请退款、等待分配技师 2 - 待服务 → 已分配技师等待上门 3 - 服务中 → 技师开始服务 4 - 待评价 → 服务完成等待用户评价 5 - 已完成 → 评价完毕 6 - 已取消 → 终态 7 - 退款中 → 用户申请退款等待审核 8 - 已退款 → 终态我用一个枚举类管理状态流转加入状态值时定义允许的转移路径public enum OrderStatus { PENDING_PAY(0, 待支付), PAID(1, 已支付), WAIT_SERVE(2, 待服务), SERVING(3, 服务中), WAIT_EVALUATE(4, 待评价), COMPLETED(5, 已完成), CANCELLED(6, 已取消), REFUNDING(7, 退款中), REFUNDED(8, 已退款); private final Integer code; private final String desc; // 定义允许的状态转移路径 private static final MapInteger, SetInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(0, new HashSet(Arrays.asList(1, 6))); TRANSITIONS.put(1, new HashSet(Arrays.asList(2, 6, 7))); // ... 省略其他状态 } public static boolean canTransition(Integer from, Integer to) { SetInteger allowed TRANSITIONS.get(from); return allowed ! null allowed.contains(to); } }这个设计有个巨大的好处改状态的地方只有一个入口所有订单状态变更都走统一方法在这个方法里做状态校验、日志记录和消息推送业务代码里不会有漏判的状态组合。3. 微信小程序端的关键实践3.1 底部导航栏、顶部导航与安全区适配小程序端的底部导航栏是微信原生的必须在app.json里的tabBar字段配置不能通过View模拟。原因在于原生TabBar有更好的性能且能实现系统级的下拉刷新拦截。{ tabBar: { color: #999999, selectedColor: #1296db, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/home.png, selectedIconPath: images/home_active.png }, { pagePath: pages/orderList/orderList, text: 订单, iconPath: images/order.png, selectedIconPath: images/order_active.png }, { pagePath: pages/user/user, text: 我的, iconPath: images/user.png, selectedIconPath: images/user_active.png } ] } }顶部导航栏的高度适配也是很多开发者会忽略的细节。iPhone的刘海屏、灵动岛与普通Android设备的顶部安全区不一样。在处理自定义导航栏时光靠固定的44px会出问题需要通过对胶囊按钮位置的测量来动态计算出导航栏高度// 获取胶囊按钮位置信息 const capsule wx.getMenuButtonBoundingClientRect(); // 获取系统信息 const systemInfo wx.getSystemInfoSync(); // 计算导航栏高度 const navBarHeight (capsule.top - systemInfo.statusBarHeight) * 2 capsule.height systemInfo.statusBarHeight;这个公式的推导思路是胶囊按钮中心点大致等于状态栏到导航栏中心的距离因此导航栏总高度约等于“胶囊按钮到顶部的距离 × 2 胶囊按钮高度”这是业界一种非常通用的计算方式。注意wx.getMenuButtonBoundingClientRect()仅在自定义导航栏模式下才建议使用在统一使用原生导航栏时无需关心该值。3.2 订单列表、下拉刷新与分页加载订单列表是家政小程序里数据量增长最快的页面。早期的实现是把所有订单一次性查询返回用户订单多了之后页面加载明显卡顿。后来改成了分页查询 触底加载 下拉刷新的经典方案Page({ data: { orderList: [], page: 1, pageSize: 10, hasMore: true, loading: false, statusTabIndex: 0 }, // 切换状态标签 switchStatusTab(e) { const index e.currentTarget.dataset.index; this.setData({ statusTabIndex: index, orderList: [], page: 1, hasMore: true }); this.loadOrders(true); }, // 触底加载更多 onReachBottom() { if (this.data.hasMore !this.data.loading) { this.loadOrders(false); } }, // 下拉刷新 onPullDownRefresh() { this.setData({ page: 1, hasMore: true }); this.loadOrders(true).finally(() { wx.stopPullDownRefresh(); }); }, loadOrders(reset false) { const { page, pageSize, statusTabIndex, orderList } this.data; const currentPage reset ? 1 : page; this.setData({ loading: true }); wx.request({ url: ${app.globalData.baseUrl}/api/user/order/list, data: { page: currentPage, pageSize, status: this.getStatusByIndex(statusTabIndex) }, success: (res) { if (res.data.code 0) { const list res.data.data.list; const hasMore currentPage * pageSize res.data.data.total; this.setData({ orderList: reset ? list : orderList.concat(list), page: currentPage 1, hasMore }); } }, complete: () { this.setData({ loading: false }); } }); } });这里有一个项目早期踩过的坑状态切换后没有清空旧的订单列表导致用户从“待支付”切到“已完成”时短暂显示上一批订单体验很差。后来强制在切换时清空列表并显示骨架屏页面刷新是不是老数据整体观感提升很大。3.3 价格计算与金额展示家政服务价格展示涉及多个层级服务项目原价、时间段加价、满减优惠、会员折扣、优惠券抵扣。这些必须保持“前端展示”和“后端实算”一致性。项目里专门封装了一个价格展示组件输入服务项目ID和选择的时间段组件内部异步加载价格配置并展示Component({ properties: { itemId: Number, workDate: String }, data: { priceText: , originalPriceText: , loading: false }, lifetimes: { attached() { this.fetchPrice(); } }, methods: { fetchPrice() { this.setData({ loading: true }); wx.request({ url: ${app.globalData.baseUrl}/api/user/service/price, data: { itemId: this.properties.itemId, workDate: this.properties.workDate }, success: (res) { if (res.data.code 0) { const data res.data.data; this.setData({ priceText: data.price, originalPriceText: data.originalPrice }); } }, complete: () { this.setData({ loading: false }); } }); } } });这个组件做到所有用到价格的地方都复用避免每个页面各写一套价格逻辑维护成本大幅下降。4. 微信支付v3对接与避坑记录4.1 从商户平台到支付回调的完整链路微信支付v3是小程序支付当前的主流方案相比v2最显著的变化是接口采用RESTful风格、参数通过JSON传递、回调数据使用AES-256-GCM加密。接入前需要准备好以下参数商户号mchid小程序AppIDAPI v3密钥32位字符串用于解密回调数据APIv3证书序列号从商户平台证书管理中获取商户私钥通过openssl生成的pem文件初始化支付的核心流程如下PostMapping(/pay) public Result pay(RequestBody PayRequest request) { String openid userService.getOpenid(request.getUserId()); // 构建支付参数 MapString, Object body new HashMap(); body.put(appid, wxPayProperties.getAppId()); body.put(mchid, wxPayProperties.getMchId()); body.put(description, 家政服务- request.getItemName()); body.put(out_trade_no, request.getOrderNo()); body.put(notify_url, wxPayProperties.getNotifyUrl()); // 金额单位必须为分 MapString, Object amount new HashMap(); amount.put(total, request.getPayAmount().multiply(new BigDecimal(100)).intValue()); amount.put(currency, CNY); body.put(amount, amount); Payer payer new Payer(); payer.setOpenid(openid); body.put(payer, payer); // 调用统一下单接口 String response wxPayClient.post(/v3/pay/transactions/jsapi, JSON.toJSONString(body), MediaType.APPLICATION_JSON); // 解析返回的prepay_id生成二次签名参数 JSONObject json JSON.parseObject(response); String prepayId json.getString(prepay_id); // 小程序端需要的5个参数timeStamp、nonceStr、package、signType、paySign MapString, String payParams buildPayParams(prepayId); return Result.success(payParams); }注意支付金额的单位是分而业务数据库里存的是元DECIMAL类型。这个转换在对接支付时极其容易出错建议在金额进入支付层之前统一转为分为单位的字符串避免浮点精度问题。4.2 回调验签与金额校验支付回调是整个链路中安全要求最高的一环。微信服务器会向你配置的notify_url发送POST请求请求头中包含Wechatpay-Signature签名、Wechatpay-Timestamp时间戳、Wechatpay-Nonce随机数body则是加密后的数据。验签和解密的顺序不能搞反先验签确认数据来自微信再解密读取订单数据。以下是回调处理的框架代码PostMapping(/notify) public String notify(RequestBody String data, RequestHeader(Wechatpay-Timestamp) String timestamp, RequestHeader(Wechatpay-Nonce) String nonce, RequestHeader(Wechatpay-Signature) String signature, RequestHeader(Wechatpay-Serial) String serial) { // 1. 构造验签名文 String message timestamp \n nonce \n data \n; // 2. 使用微信平台证书验证签名 boolean verify wxPayCertificate.verify(message, signature, serial); if (!verify) { return failResponse(签名验证失败); } // 3. 解密数据AES-256-GCM JSONObject decryptData wxPayCipher.decrypt(data); String outTradeNo decryptData.getString(out_trade_no); String tradeState decryptData.getString(trade_state); Integer totalPay decryptData.getJSONObject(amount).getInteger(total); // 4. 查询本地订单核对金额防篡改 Order order orderMapper.selectByOrderNo(outTradeNo); if (order null) { return failResponse(订单不存在); } BigDecimal expectAmount order.getPayAmount().multiply(new BigDecimal(100)); if (expectAmount.intValue() ! totalPay) { return failResponse(金额不一致); } // 5. 更新订单状态 if (SUCCESS.equals(tradeState) order.getOrderStatus() 0) { orderService.markPaid(order.getId()); } // 6. 返回成功给微信避免重复推送 return successResponse(); }金额校验是最关键的环节如果只校验订单号不校验金额攻击者可以构造支付金额为0.01元的请求体跳过后端校验直接更改订单状态。这是支付安全的重中之重。4.3 小程序违规导致支付功能暂停的处理因为各种原因小程序可能会被平台判定违规导致支付功能暂停。遇到这种情况最首要的是冷静评估业务影响用户端无法发起支付所有待支付订单会卡住。我当时的处理步骤在小程序管理后台的“处罚中心”查看违规详情确认违规类型和申诉通道如果是可申诉的误判准备材料截图、说明、整改报告在3个工作日内提交申诉同时在代码层面做好降级方案——用户在支付页提示“系统维护中请稍后再试”或“当前服务暂不可用”并把用户的联系方式记录下来方便恢复后主动联系如果业务紧急可临时引导用户走线下支付客服转账但这需要有独立的客服通道而且需要谨慎评估是否合规。提醒这类问题的核心是防患于未然开发阶段就要严格检查代码中是否包含平台明确禁止的内容尤其是涉及虚拟支付、诱导分享、关键词踩线等行为一旦触发即使申诉成功也需要时间恢复。4.4 支付链路常见异常排查支付对接的坑多且隐蔽我在实际调试中归纳出一份高频问题排查表现象可能原因排查方向调起支付报“缺少参数”微信签名paySign生成错误检查参数拼接顺序和签名算法注意时间戳是秒级字符串调起支付报“签名错误”商户私钥不匹配或签名算法错误确认是否使用SHA256withRSA验签用的证书序列号是否与请求头一致支付成功但订单状态未更新回调未收到或回调验签失败查看后端日志的notify请求记录检查回调地址是否公网可访问回调解密失败APIv3密钥错误确认v3密钥是32位且是商户平台设置的APIv3密钥支付金额与订单不符前端金额计算错误务必以后端计算为准前端展示仅供参考订单超时未关闭定时任务未触发或漏单检查Quartz任务配置确认扫描范围包含所有待支付订单5. 小程序反编译与源码保护思路5.1 反编译的可行性与实际效果在小程序开发圈子里反编译一直是个敏感又现实的问题。微信小程序的代码会经过本地编译生成wxapkg包存放在本地缓存中。通过工具可以把wxapkg还原出可以阅读的JS、JSON、WXML文件。这在分析竞品功能、排查线上问题时确实有用。常见的获取wxapkg包的路径有两种。第一种是PC端微信小程序加载完以后本地文件目录里会出现一串哈希值命名的wxapkg文件第二种是Android设备通过/data/data/com.tencent.mm/MicroMsg/目录下的缓存文件。拿到包后用开源的工具如wxappUnpacker或CrackMinApp解包能得到大部分逻辑代码。但需要明白一个现实反编译出来的代码是可读性大打折扣的。开发时用的变量名、方法名会被压缩混淆注释全部丢失WXML的标签结构也会打散。你看到的可能是var a123;function b(){return a456}这样的形态业务逻辑需要花大量时间逆向理解。5.2 如何做好源码保护既然有反编译开发者就需要有逆向思维来保护自己的劳动成果。我总结了几条实际可用的方案第一关键逻辑放在服务端。小程序端的代码再怎么保护一旦被反编译就能看到完整逻辑。真正核心的业务规则价格计算、优惠叠加、订单状态流转必须放到服务端小程序端只做展示和调用接口这样即使前端代码被完全反编译攻击者也只能看到一堆接口调用。第二接口层面做签名和鉴权。对于关键接口客户端请求时需要携带签名参数通常是token 时间戳 参数按规则拼接的MD5服务端校验签名合法性防止请求被篡改或重放。第三动态加载实现基础防护。小程序支持分包加载和动态创建页面敏感的页面可以放到分包中通过业务跳转动态加载不在主包中直接暴露。第四代码混淆。微信开发者工具提供了代码上传时“将JS代码编译成ES5后混淆”的选项同时也可以接第三方混淆插件加密关键业务逻辑代码。一句话总结面对攻击者前端代码的加密保护永远是“加深门槛”而非“绝对安全”把核心资产放到服务端才是真正的安全边界。6. 常见问题与排查技巧实录6.1 网络不可用时的全局提示方案家政预约类小程序对网络要求很高用户在弱网环境如电梯、地下车库下打开小程序如果只靠wx.request自带的fail回调提示每个页面都要写一遍错误处理体验是割裂的。我采用的方案是封装统一的请求层在请求发起的入口处判断网络状态同时在所有请求的fail回调中统一弹出全局网络错误提示。具体实现如下// request.js 核心封装 const request (options) { wx.getNetworkType({ success: (res) { if (res.networkType none) { wx.showToast({ title: 网络不可用请检查网络设置, icon: none }); return; } wx.request({ url: baseUrl options.url, data: options.data || {}, method: options.method || GET, header: { Content-Type: application/json, Authorization: getToken() }, success: (res) { if (res.data.code 401) { // token过期跳转登录 wx.navigateTo({ url: /pages/login/login }); return; } options.success options.success(res.data); }, fail: () { wx.showToast({ title: 网络请求异常请稍后重试, icon: none }); options.fail options.fail(); } }); } }); }; // 同时监听网络状态变化 wx.onNetworkStatusChange((res) { if (!res.isConnected) { wx.showToast({ title: 网络连接已断开, icon: none }); } });在页面中只要调用request封装的方法全局错误提示统一触发不需要每个页面单独写网络异常处理。6.2 单选框组件在小程序端的样式与交互实现小程序原生的radio组件在iOS和Android上的渲染效果差异明显尤其在不同机型上显示大小不稳定。项目里实现了一套自定义单选按钮确保在两端视觉一致。自定义单选按钮的思路用view模拟单选框的选中态点击时切换选中样式同时用数据驱动绑定选中值。view classradio-group view classradio-item {{selectedValue item.value ? active : }} wx:for{{options}} wx:keyvalue bindtaponSelect >.radio-item { display: flex; align-items: center; padding: 20rpx; border: 2rpx solid #e5e5e5; border-radius: 12rpx; margin-bottom: 20rpx; } .radio-item.active { border-color: #1296db; background-color: rgba(18, 150, 219, 0.06); } .radio-dot { width: 32rpx; height: 32rpx; border-radius: 50%; border: 2rpx solid #ccc; margin-right: 16rpx; position: relative; } .radio-item.active .radio-dot { border-color: #1296db; } .radio-item.active .radio-dot::after { content: ; position: absolute; width: 16rpx; height: 16rpx; background: #1296db; border-radius: 50%; top: 50%; left: 50%; transform: translate(-50%, -50%); }这里选用了border ::after居中圆点来模拟单选中态而不是直接用背景色填充整个圆这样在视觉上更接近原生控件的效果同时兼容性也更好。6.3 小程序内嵌H5工具栏返回箭头消失的问题在小程序中用web-view组件内嵌H5页面会发现H5页面左上角会出现一个返回箭头点击返回上一页。当你从H5的某个内部页面跳转到另一个内部页面后如H5内部使用history.pushState返回箭头可能会消失这是微信web-view的逻辑限制。处理方案是在H5端通过wx.miniProgram.postMessage与小程序通信// H5页面中 function goBack() { // 判断是否有历史记录 if (window.history.length 1) { window.history.back(); } else { // 通知小程序端返回上一页 wx.miniProgram.postMessage({ data: { type: EXIT_WEBVIEW } }); wx.miniProgram.navigateBack(); } }小程序端需要在web-view所在页面的bindmessage事件中接收消息Page({ onWebviewMessage(e) { const data e.detail.data; if (data.type EXIT_WEBVIEW) { wx.navigateBack(); } } });这个方案的思路是H5内部的历史栈自己能处理就自己处理处理不了的时候把事件抛给小程序让小程序的导航栈接管返回逻辑。6.4 下载源代码后的运行配置检查这是很多拿到源代码的同学最容易卡住的一步。下载项目后直接启动通常会遇到数据库连接失败、端口冲突、AppID不匹配等问题这个项目的启动配置很关键后端启动配置application.ymlspring.datasource.url改成你自己MySQL的地址、端口和库名spring.datasource.username/password数据库账号密码wx.pay.app-id/wx.pay.mch-id替换成你自己的小程序AppID和商户号oss.access-key/oss.secret-key如果用到阿里云OSS这里必须填你自己的密钥小程序端配置project.config.jsonappid替换成你的小程序AppID测试时可以使用测试号urlCheck: false开发阶段关闭域名校验否则真机调试会提示“不在合法域名列表”数据库初始化执行项目根目录下的init.sql脚本注意MySQL版本8.0以下需要修改字符集配置端口检查后端默认端口8088如果有其他服务占用修改server.port并同步修改小程序端request.js中的baseUrl。7. 调试工具与抓包实战7.1 微信小程序抓包工具的选择与环境配置开发过程中调试接口是高频操作。小程序端的抓包比普通网页麻烦一些自动化测试、问题定位和反编译分析都离不开抓包。我常用的工具是Charles和Burp Suite两者的核心思路相同通过代理转发让小程序的所有请求经过代理工具从而观察和分析HTTP/HTTPS流量。在模拟器微信开发者工具里配置代理比较简单开发者工具自带Network面板可以看到大部分请求。但真机调试时就复杂一些因为微信小程序在真机上默认不走系统代理需要在手机WiFi设置中手动配置代理服务器地址和端口且要保证手机和电脑在同一局域网。Burp Suite抓取PC端微信小程序的步骤大致如下启动Burp Suite在Proxy Options中添加一个监听端口如8080绑定地址设为127.0.0.1在PC微信的代理设置中配置HTTP代理为127.0.0.1:8080微信客户端内可设置或通过系统代理手机端如果想抓包需让手机代理指向电脑的局域网IP注意PC防火墙需放行监听端口需要安装Burp的CA证书到系统受信任根证书否则HTTPS流量无法解密。注意抓包只能用于自己开发的小程序或已授权的安全测试未经授权抓取和分析他人小程序流量是不道德甚至违法的行为。7.2 小程序抓包的常见难点与破解思路小程序请求抓包比普通网页难主要卡在两个地方一是校验绑定。很多小程序的请求会校验Referer或Host头如果代理工具转发的请求头与原始不同服务端可能拒绝响应。解决这类问题可用Burp的Match and Replace功能把请求头中的Host和Referer强制改写为原始值。二是HTTPS证书校验。微信小程序内部对证书链做过特殊处理即使安装了代理证书部分场景仍会提示“证书校验失败”。这时需要在客户端代码层面处理比如在wx.request中把sslVerify设为false仅限开发阶段或者通过perfect-forward-secrecy等手段配合。不过说白了如果小程序前端代码没有做额外的安全防护通过反编译拿到代码后直接从代码中看到接口域名、请求参数构造方式后续的抓包分析会顺利很多。7.3 小程序反编译的实用工具链反编译不是单一工具能搞定的通常需要组合使用。我的常用工具链如下工具用途备注wxappUnpacker解包wxapkg支持子包与主包合并CrackMinApp还原WXML/WXSS与微信开发者工具配合恢复项目结构HBuilderX反编译后导入通过“重新构建”恢复uni-app语法Source Map工具JS可读性还原适用于保留sourceMap上传的生产包整个反编译流程大致是从缓存目录拿到wxapkg → 使用解包工具解开 → 得到pages目录下的WXML/JS/JSON/WXSS → 导入开发者工具即可阅读大部分逻辑。8. 这个项目后续还能怎么扩展跑通这套家政预约源码只是开始。在实际运营中我建议在以下几个方向上做扩展第一个方向是智能派单算法。当前版本是“用户选时间 → 系统分配空闲技师”后续可以做成“用户只选服务时间 → 系统根据技师位置、评分、历史服务记录自动匹配最优人选”要引入LBS基于位置服务数据计算技师到用户家的通勤时间把通勤时间纳入排班计算。第二个方向是会员等级体系。家政服务属于高频黏性消费适合做会员体系——按月付、季度付、年付不同折扣会员享受优先派单和专属客服。给不同等级的会员不同的积分倍率和生日福利能显著提升用户留存。第三个方向是多门店隔离。当前版本是单店模型实际运营中家政公司通常有多个服务网点每个网点覆盖一定范围的社区。如果横向扩展需要把technician_id和service_item_id都关联到store_id用户下单时自动匹配对应区域的门店。第四个方向是IoT设备联动。比如智能门锁临时密码发放、清洁机器人联动调度等可以形成更强的服务闭环但这需要硬件接入复杂度会明显上升。我自己的体会是家政预约类小程序的核心不是炫酷的界面而是订单调度系统的稳定性和可用性。源码里的状态机设计、时间段锁、支付回调校验这些才是真正决定项目成败的地方。拿到代码后建议先从数据库表和订单状态流转读起理解清楚了再跑前端界面这样遇到问题才能快速定位。最后再分享一个小技巧开发阶段一定要在微信开发者工具里开启“不校验合法域名”选项同时把project.config.json里的urlCheck设为false否则后端接口一调试一个“域名不合法”白白浪费时间。这套项目源码从架构、核心逻辑、支付到小程序端实现已经把家政预约项目的所有关键环节串了起来适合拿来作为学习和二次开发的基石。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。