1. 同城理发预约系统的技术架构设计
理发预约系统作为典型的O2O服务类应用,需要处理线上预约与线下服务的无缝衔接。基于Java技术栈的全渠道系统(小程序+公众号+H5)通常采用分层架构设计,确保各终端体验一致且可维护性强。
1.1 核心模块划分
系统主要包含以下功能模块:
- 用户端服务:处理注册登录、理发师查询、预约下单、订单管理等功能
- 商户端服务:提供理发师管理、服务项目配置、预约时段设置等后台功能
- 支付服务:集成微信支付完成交易闭环
- 消息服务:处理预约提醒、变更通知等即时消息
- 定时任务:处理超时未支付订单的自动释放
1.2 技术选型建议
后端技术栈推荐组合:
java复制// Spring Boot基础依赖
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// 微信开发SDK
implementation 'com.github.binarywang:weixin-java-mp:4.1.0'
implementation 'com.github.binarywang:weixin-java-miniapp:4.1.0'
// 数据库及缓存
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.redisson:redisson-spring-boot-starter:3.17.0'
2. 多终端接入实现方案
2.1 微信小程序接入
小程序端需要处理用户授权和接口调用:
javascript复制// 小程序端登录逻辑
wx.login({
success: res => {
wx.request({
url: 'https://api.example.com/auth/miniapp',
method: 'POST',
data: { code: res.code },
success: loginRes => {
// 存储服务端返回的token
wx.setStorageSync('token', loginRes.data.token)
}
})
}
})
后端对应的接口实现:
java复制@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private WxMaService wxMaService;
@PostMapping("/miniapp")
public ResponseEntity<?> miniAppLogin(@RequestBody LoginRequest request) {
try {
WxMaJscode2SessionResult session = wxMaService.getUserService()
.getSessionInfo(request.getCode());
// 处理用户登录逻辑
String token = authService.createToken(session.getOpenid());
return ResponseEntity.ok(new TokenResponse(token));
} catch (WxErrorException e) {
return ResponseEntity.status(401).body("登录失败");
}
}
}
2.2 微信公众号接入
公众号需要处理网页授权和JS-SDK调用:
java复制// 网页授权配置
@Bean
public WxMpService wxMpService() {
WxMpService service = new WxMpServiceImpl();
WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
config.setAppId("wxAppId");
config.setSecret("wxSecret");
service.setWxMpConfigStorage(config);
return service;
}
// 网页授权接口
@GetMapping("/auth/redirect")
public String authRedirect(@RequestParam String code) {
try {
WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
String openId = accessToken.getOpenId();
// 处理用户登录逻辑
return "redirect:/index?openid=" + openId;
} catch (WxErrorException e) {
return "error";
}
}
2.3 H5页面适配方案
对于纯H5端,需要考虑以下技术要点:
- 响应式布局适配不同设备
- 微信内浏览器特殊处理(微信JS-SDK调用)
- 非微信环境下的备用登录方案
核心的H5支付跳转逻辑:
javascript复制function requestPayment(params) {
if (isWeixinBrowser()) {
// 微信环境使用JSAPI支付
wx.chooseWXPay({
timestamp: params.timeStamp,
nonceStr: params.nonceStr,
package: params.package,
signType: params.signType,
paySign: params.paySign,
success: function(res) {
// 支付成功处理
}
});
} else {
// 非微信环境使用H5支付
window.location.href = params.h5Url;
}
}
3. 预约业务核心逻辑实现
3.1 预约时段管理
采用位图法高效处理时段占用情况:
java复制@Entity
@Table(name = "schedule_slots")
public class ScheduleSlot {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "barber_id")
private Long barberId;
@Column(name = "schedule_date")
private LocalDate scheduleDate;
@Column(name = "time_slots", columnDefinition = "BIGINT")
private Long timeSlots; // 使用64位long表示30分钟间隔的48个时段
// 检查时段是否可用
public boolean isSlotAvailable(int slotIndex) {
return (timeSlots & (1L << slotIndex)) == 0;
}
// 占用时段
public void reserveSlot(int slotIndex) {
timeSlots |= (1L << slotIndex);
}
}
3.2 并发预约处理
使用Redis分布式锁防止超订:
java复制public ReservationResult reserveTimeSlot(Long barberId, LocalDateTime datetime) {
String lockKey = "reserve:" + barberId + ":" + datetime;
// 尝试获取分布式锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
if (!locked) {
return ReservationResult.fail("当前时段正在被其他用户预约");
}
try {
// 查询并更新数据库
ScheduleSlot slot = scheduleRepository
.findByBarberIdAndDate(barberId, datetime.toLocalDate());
int slotIndex = calculateSlotIndex(datetime.toLocalTime());
if (!slot.isSlotAvailable(slotIndex)) {
return ReservationResult.fail("该时段已被预约");
}
slot.reserveSlot(slotIndex);
scheduleRepository.save(slot);
return ReservationResult.success();
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
4. 微信支付集成实战
4.1 支付配置管理
建议将支付配置集中管理:
java复制@Configuration
public class WxPayConfiguration {
@Value("${wx.pay.appId}")
private String appId;
@Value("${wx.pay.mchId}")
private String mchId;
@Value("${wx.pay.mchKey}")
private String mchKey;
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
@Bean
public WxPayService wxPayService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(appId);
payConfig.setMchId(mchId);
payConfig.setMchKey(mchKey);
payConfig.setNotifyUrl(notifyUrl);
WxPayService payService = new WxPayServiceImpl();
payService.setConfig(payConfig);
return payService;
}
}
4.2 多端支付统一处理
封装统一的支付服务:
java复制@Service
public class PaymentService {
@Autowired
private WxPayService wxPayService;
public PaymentResponse createPayment(PaymentRequest request) {
switch (request.getPlatform()) {
case MINI_APP:
return createMiniAppPayment(request);
case PUBLIC_ACCOUNT:
return createJsapiPayment(request);
case H5:
return createH5Payment(request);
default:
throw new IllegalArgumentException("不支持的支付平台");
}
}
private PaymentResponse createH5Payment(PaymentRequest request) {
try {
WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
orderRequest.setBody("理发服务预约-" + request.getServiceName());
orderRequest.setOutTradeNo(request.getOrderNo());
orderRequest.setTotalFee(request.getAmount());
orderRequest.setSpbillCreateIp(request.getClientIp());
orderRequest.setTradeType(WxPayConstants.TradeType.MWEB);
orderRequest.setSceneInfo(buildSceneInfo(request));
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(orderRequest);
return PaymentResponse.h5(result.getMwebUrl());
} catch (WxPayException e) {
throw new PaymentException("支付创建失败", e);
}
}
private String buildSceneInfo(PaymentRequest request) {
JsonObject sceneInfo = new JsonObject();
JsonObject h5Info = new JsonObject();
h5Info.addProperty("type", "Wap");
sceneInfo.add("h5_info", h5Info);
return sceneInfo.toString();
}
}
5. 系统优化与注意事项
5.1 性能优化建议
-
缓存策略:
- 理发师信息、服务项目等低频变更数据使用Redis缓存
- 预约时段数据采用本地缓存+Redis二级缓存
-
数据库优化:
- 为barber_id和schedule_date建立复合索引
- 大表考虑分库分表策略
-
异步处理:
- 支付结果通知、短信提醒等非核心流程采用消息队列异步处理
5.2 常见问题排查
支付回调处理注意事项:
java复制@PostMapping("/pay/notify")
public String payNotify(HttpServletRequest request) {
try {
// 1. 验证签名
WxPayNotifyResponse notifyResponse = wxPayService.parseOrderNotifyResult(request);
// 2. 处理业务逻辑
if ("SUCCESS".equals(notifyResponse.getResultCode())) {
orderService.handlePaymentSuccess(
notifyResponse.getOutTradeNo(),
notifyResponse.getTransactionId()
);
}
// 3. 返回成功响应
return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
} catch (WxPayException e) {
// 记录错误日志
log.error("支付通知处理失败", e);
return "<xml><return_code><![CDATA[FAIL]]></return_code></xml>";
}
}
多端同步问题解决方案:
- 使用WebSocket实现实时状态推送
- 采用轮询机制作为降级方案
- 关键状态变更时主动调用微信模板消息
6. 部署与监控
6.1 容器化部署建议
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: barber-booking:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: booking
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
volumes:
- mysql_data:/var/lib/mysql
volumes:
redis_data:
mysql_data:
6.2 监控指标配置
建议监控以下关键指标:
- 预约成功率
- 支付转化率
- 各时段理发师负载
- API响应时间P99值
- 系统错误率
使用Prometheus配置示例:
yaml复制scrape_configs:
- job_name: 'booking_app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
