家政预约小程序后端源码深度剖析:支付回调实战

本文适合有一定开发基础的后端/全栈工程师,将从数据库设计、订单状态机、智能派单引擎、支付回调等核心模块入手,逐层拆解家政O2O系统的关键代码实现。

一、源码整体工程结构

一个标准的家政O2O系统通常采用如下工程结构:

jiazheng-parent/

├── jiazheng-common/ # 通用工具模块

│ ├── utils/ # JSON转换、加密解密、Excel导入导出

│ └── config/ # 全局配置(跨域、拦截器)

├── jiazheng-api/ # API接口定义模块(OpenAPI 3.0规范)

├── jiazheng-service/ # 核心业务逻辑模块

│ ├── user/ # 用户服务

│ ├── order/ # 订单服务

│ ├── dispatch/ # 派单服务

│ └── payment/ # 支付服务

├── jiazheng-dao/ # 数据访问层(MyBatis-Plus)

├── jiazheng-admin/ # 管理后台(Vue + Element UI)

└── jiazheng-web/ # 用户端API接口

这种模块化设计使得每个业务模块可以独立开发、测试与部署,便于团队协作和后续扩展。

二、核心数据库表结构设计

数据库设计是家政系统的"地基",直接影响系统的性能和可维护性。以下是几个核心表的DDL设计示例。

CREATE TABLE `user` (

`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',

`open_id` VARCHAR(64) UNIQUE COMMENT '微信openId',

`union_id` VARCHAR(64) COMMENT '微信unionId(多端打通)',

`phone` VARCHAR(11) UNIQUE COMMENT '手机号',

`password` VARCHAR(128) COMMENT 'BCrypt加密密码',

`nickname` VARCHAR(50) COMMENT '昵称',

`avatar` VARCHAR(255) COMMENT '头像URL',

`real_name` VARCHAR(20) COMMENT '真实姓名',

`id_card` VARCHAR(18) COMMENT '身份证号(AES加密)',

`user_type` TINYINT DEFAULT 0 COMMENT '0普通用户 1服务人员 2机构',

`status` TINYINT DEFAULT 1 COMMENT '1正常 0禁用',

`is_deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除',

`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,

`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

INDEX `idx_phone` (`phone`),

INDEX `idx_open_id` (`open_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

这里有几个值得注意的设计细节:

  • 密码使用BCrypt强哈希算法加盐存储

  • 身份证号等敏感信息使用AES-GCM加密

  • 引入逻辑删除字段is_deleted,配合MyBatis-Plus的@TableLogic注解

  • 审计字段(create_time/update_time)通过MyBatis-Plus自动填充

2.2 订单表(order)

订单表是系统的核心事务表,设计时需要在第三范式查询性能之间做平衡

CREATE TABLE `order` (

`id` BIGINT PRIMARY KEY AUTO_INCREMENT,

`order_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '订单编号',

`user_id` BIGINT NOT NULL COMMENT '用户ID',

`worker_id` BIGINT COMMENT '服务人员ID',

`service_id` BIGINT NOT NULL COMMENT '服务项ID',

`service_name` VARCHAR(100) COMMENT '服务名称(冗余)',

`worker_name` VARCHAR(20) COMMENT '服务者姓名(冗余)',

`worker_phone` VARCHAR(11) COMMENT '服务者电话(冗余)',

`worker_avatar` VARCHAR(255) COMMENT '服务者头像(冗余)',

`address` VARCHAR(255) NOT NULL COMMENT '服务地址',

`lat` DECIMAL(10,7) COMMENT '纬度',

`lng` DECIMAL(10,7) COMMENT '经度',

`appoint_time` DATETIME NOT NULL COMMENT '预约时间',

`total_amount` DECIMAL(10,2) NOT NULL COMMENT '总金额',

`paid_amount` DECIMAL(10,2) DEFAULT 0 COMMENT '已付金额',

`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1待支付 2待接单 3已接单 4服务中 5已完成 6已取消 7售后中',

`remark` VARCHAR(500) COMMENT '用户备注',

`cancel_reason` VARCHAR(200) COMMENT '取消原因',

`is_deleted` TINYINT DEFAULT 0,

`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,

`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

INDEX `idx_user_id` (`user_id`),

INDEX `idx_worker_id` (`worker_id`),

INDEX `idx_status` (`status`),

INDEX `idx_appoint_time` (`appoint_time`),

INDEX `idx_order_no` (`order_no`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

关键设计思路

  • 冗余服务者姓名、联系方式、头像URL,避免高频联表查询

  • 订单编号使用独立序列生成,便于分库分表

  • 经纬度字段为智能派单提供地理位置数据支撑

2.3 分销关系表(distribution_relations)

对于支持分销裂变的家政系统,分销关系表的设计至关重要:

CREATE TABLE `distribution_relations` (

`id` INT PRIMARY KEY AUTO_INCREMENT,

`inviter_id` INT NOT NULL COMMENT '邀请人ID',

`invitee_id` INT NOT NULL COMMENT '被邀请人ID',

`level` TINYINT NOT NULL COMMENT '分销层级 1/2/3',

`device_fingerprint` VARCHAR(64) COMMENT '设备指纹(防刷)',

`status` TINYINT DEFAULT 1 COMMENT '1有效 0失效',

`commission_rate` DECIMAL(5,2) COMMENT '佣金比例',

`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY `uk_invitee` (`invitee_id`),

INDEX `idx_inviter` (`inviter_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销关系表';

三、订单状态机实现

订单状态管理是家政系统的核心业务逻辑之一,必须防止非法状态流转。推荐使用状态模式状态机来实现。

3.1 订单状态枚举定义

public enum OrderStatus {

PENDING_PAY(1, "待支付"),

PENDING_ACCEPT(2, "待接单"),

ACCEPTED(3, "已接单"),

IN_SERVICE(4, "服务中"),

COMPLETED(5, "已完成"),

CANCELLED(6, "已取消"),

AFTER_SALES(7, "售后中");

private final int code;

private final String desc;

OrderStatus(int code, String desc) {

this.code = code;

this.desc = desc;

}

public int getCode() { return code; }

public String getDesc() { return desc; }

}

3.2 状态转换规则与校验

@Service

public class OrderStateMachine {

// 定义合法的状态转换映射

private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = new HashMap<>();

static {

// 待支付 → 待接单 / 已取消

TRANSITIONS.put(OrderStatus.PENDING_PAY,

Set.of(OrderStatus.PENDING_ACCEPT, OrderStatus.CANCELLED));

// 待接单 → 已接单 / 已取消

TRANSITIONS.put(OrderStatus.PENDING_ACCEPT,

Set.of(OrderStatus.ACCEPTED, OrderStatus.CANCELLED));

// 已接单 → 服务中 / 已取消

TRANSITIONS.put(OrderStatus.ACCEPTED,

Set.of(OrderStatus.IN_SERVICE, OrderStatus.CANCELLED));

// 服务中 → 已完成

TRANSITIONS.put(OrderStatus.IN_SERVICE,

Set.of(OrderStatus.COMPLETED));

// 已完成 → 售后中

TRANSITIONS.put(OrderStatus.COMPLETED,

Set.of(OrderStatus.AFTER_SALES));

}

@Transactional

public void transition(Long orderId, OrderStatus targetStatus) {

Order order = orderMapper.selectById(orderId);

OrderStatus current = OrderStatus.values()order.getStatus() - 1;

// 校验状态转换是否合法

Set<OrderStatus> allowed = TRANSITIONS.get(current);

if (allowed == null || !allowed.contains(targetStatus)) {

throw new BusinessException("非法状态流转: " + current + " → " + targetStatus);

}

// 更新订单状态

order.setStatus(targetStatus.getCode());

orderMapper.updateById(order);

// 触发状态变更事件(消息推送、日志记录等)

publishStatusChangeEvent(order, current, targetStatus);

}

}

这种设计确保了订单状态流转的原子性合规性,非法请求(如"已完成"状态不可取消)会被直接拦截。

四、智能派单引擎实现

智能派单是家政O2O系统的核心竞争力。派单逻辑通常综合考虑距离、评分、空闲状态、技能匹配等多个维度。

4.1 服务人员位置存储(Redis GEO)

使用Redis的GEO数据结构存储服务人员实时位置,实现高效的附近人员查询:

@Service

public class WorkerLocationService {

@Autowired

private RedisTemplate<String, Object> redisTemplate;

private static final String GEO_KEY = "worker:locations";

/**

* 更新服务人员位置

*/

public void updateLocation(Long workerId, Double lng, Double lat) {

redisTemplate.opsForGeo().add(GEO_KEY,

new Point(lng, lat), workerId.toString());

// 设置过期时间,超时未更新则认为离线

redisTemplate.expire(GEO_KEY, 5, TimeUnit.MINUTES);

}

/**

* 查询附近5公里内的服务人员

*/

public List<Long> findNearbyWorkers(Double lng, Double lat, Integer radius) {

GeoResults<RedisGeoCommands.GeoLocation<Object>> results =

redisTemplate.opsForGeo().search(

GEO_KEY,

new Circle(new Point(lng, lat), new Distance(radius, Metrics.KILOMETERS)),

GeoRadiusCommandArgs.newGeoRadiusArgs().limit(50)

);

return results.getContent().stream()

.map(result -> Long.valueOf(result.getContent().getName().toString()))

.collect(Collectors.toList());

}

}

4.2 派单策略实现

@Service

public class DispatchService {

@Autowired

private WorkerLocationService locationService;

@Autowired

private WorkerService workerService;

@Autowired

private OrderService orderService;

/**

* 智能派单:综合多维度评分,选出最优服务人员

*/

@Transactional

public DispatchResult autoDispatch(Order order) {

// 1. 获取附近5公里内的服务人员

List<Long> nearbyWorkerIds = locationService.findNearbyWorkers(

order.getLng(), order.getLat(), 5

);

if (CollectionUtils.isEmpty(nearbyWorkerIds)) {

return DispatchResult.fail("附近暂无可用服务人员");

}

// 2. 获取服务人员的详细信息(评分、当前订单量、技能标签)

List<Worker> workers = workerService.listByIds(nearbyWorkerIds);

// 3. 综合评分排序

Worker bestWorker = workers.stream()

.filter(w -> w.getStatus() == WorkerStatus.ONLINE)

.filter(w -> w.getCurrentOrderCount() < w.getMaxConcurrentOrders())

.filter(w -> matchSkill(w, order.getServiceId()))

.max(Comparator

.comparingDouble(Worker::getRating) // 评分优先

.thenComparingInt(Worker::getCurrentOrderCount) // 订单量少优先

)

.orElse(null);

if (bestWorker == null) {

// 无匹配结果时触发手动派单流程

return DispatchResult.fail("暂无匹配的服务人员,已转人工派单");

}

// 4. 派单:锁定服务人员并更新订单

order.setWorkerId(bestWorker.getId());

order.setStatus(OrderStatus.ACCEPTED.getCode());

orderService.updateOrder(order);

// 5. 发送派单通知(WebSocket + 模板消息)

notificationService.notifyWorker(bestWorker.getId(), order);

notificationService.notifyUser(order.getUserId(), order);

return DispatchResult.success(bestWorker);

}

/**

* 技能匹配校验

*/

private boolean matchSkill(Worker worker, Long serviceId) {

// 检查服务人员的技能标签是否包含该服务类型

return worker.getSkillTags().contains(getServiceCategory(serviceId));

}

}

派单策略还可以更复杂------基于改进型匈牙利算法,综合距离(40%)、评分(30%)、响应速度(20%)、技能匹配(10%)的权重进行最优匹配。

五、支付回调处理

支付是家政O2O系统的关键环节,支付回调的可靠处理直接影响用户体验和资金安全。

5.1 微信支付回调处理(Node.js版)

// Node.js 支付回调处理

app.post('/pay/wechat/notify', async (req, res) => {

try {

const xmlData = req.body;

const result = await wechatPay.verifyNotify(xmlData);

if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {

const orderId = result.out_trade_no;

const transactionId = result.transaction_id;

const totalFee = result.total_fee / 100; // 分转元

// 更新订单状态(需保证幂等性)

const order = await Order.findOne({ where: { orderNo: orderId } });

if (order && order.status === OrderStatus.PENDING_PAY) {

await order.update({

status: OrderStatus.PENDING_ACCEPT,

paidAmount: totalFee,

transactionId: transactionId,

paidTime: new Date()

});

// 触发派单流程(异步)

await dispatchQueue.add({ orderId: order.id });

// 发送支付成功通知

await notificationService.sendPaymentSuccess(order.userId, orderId);

}

// 返回成功响应给微信

res.send(successXmlResponse());

} else {

// 支付失败处理

logger.error('Payment failed', result);

res.send(failXmlResponse(result.return_msg));

}

} catch (error) {

logger.error('Payment callback error', error);

res.send(failXmlResponse('处理异常'));

}

});

关键设计要点

  • 回调处理必须幂等------同一订单的多次回调不会重复处理

  • 使用消息队列将派单流程异步化,避免阻塞回调响应

  • 记录完整的支付日志,便于对账和问题排查

六、WebSocket实时通信

服务人员接单后,需要通过WebSocket向用户端推送实时位置

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override

public void registerStompEndpoints(StompEndpointRegistry registry) {

registry.addEndpoint("/track").setAllowedOrigins("*");

}

@Override

public void configureMessageBroker(MessageBrokerRegistry registry) {

registry.enableSimpleBroker("/topic");

registry.setApplicationDestinationPrefixes("/app");

}

}

@Controller

public class LocationController {

@Autowired

private SimpMessagingTemplate messagingTemplate;

/**

* 服务人员位置更新 → 推送给用户

*/

public void pushLocation(Long orderId, Double lng, Double lat) {

Map<String, Object> data = new HashMap<>();

data.put("orderId", orderId);

data.put("lng", lng);

data.put("lat", lat);

data.put("timestamp", System.currentTimeMillis());

messagingTemplate.convertAndSend("/topic/order/" + orderId + "/location", data);

}

}

总结

本文从数据库设计订单状态机智能派单引擎支付回调处理实时通信五个维度,深入剖析了家政O2O系统的核心代码实现。这些模块共同构成了一个完整的家政服务数字化平台的技术底座。

在实际开发中,还需要关注以下工程化要点:

  • 使用JWT实现无状态身份认证

  • 配合MyBatis-Plus的自动填充和逻辑删除简化开发

  • 引入Docker + Kubernetes实现容器化部署与弹性伸缩

希望本文能为正在开发或研究家政O2O系统的开发者提供有价值的代码参考。

相关推荐
MichaelJohn1 小时前
从零星白屏到“启发式缓存”,记录一次刚接手屎山的惊险排查
前端
小钻风33661 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李1 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端
大鱼>1 小时前
深入Real-ESRGAN架构:RRDBNet设计精髓与ONNX/TensorRT部署优化
人工智能·深度学习·架构
程序员黑豆2 小时前
鸿蒙应用开发:6种图片加载方式详解
前端·华为·harmonyos
半个落月2 小时前
用 React 搭一个 WebGPU 模型加载页:从状态驱动到可复用进度条
前端·react.js
雪隐2 小时前
个人电脑玩AI-13让5060 Ti给你打工——我用 0.9B 小模型终结了"谁来记会议纪要"这个世纪难题
前端·人工智能·后端
橘子星2 小时前
我一个前端切图仔,凭什么能在浏览器里跑大模型?
前端·javascript·前端框架
qq_452396232 小时前
第二篇:《Go 开发环境搭建:SDK、IDE、Module 与 Hello World》
开发语言·ide·golang
70asunflower2 小时前
初学者理解 Web 工作原理(完全教程)
前端