1. 项目概述
这个SpringBoot摄影预约系统是一个典型的B/S架构应用,我最近刚完成了一个类似的项目交付。这类系统本质上是通过技术手段解决影楼、摄影工作室的预约管理痛点。从技术栈来看,SpringBoot+MyBatis的组合已经成为当前Java领域开发这类管理系统的标配方案。
在实际开发过程中,我发现摄影预约业务有几个关键特性:首先是时间敏感性强,摄影师档期和场地资源都需要精确到小时甚至半小时的粒度;其次是服务非标化明显,不同套餐包含的服装、场景、精修数量等参数差异很大;最后是支付流程复杂,可能涉及定金、尾款、补款等多种交易场景。这些业务特性直接影响了我们的技术方案选型。
2. 核心需求解析
2.1 预约业务建模
摄影预约的核心是资源时间片的占用管理。在我的实现中,采用了两层时间片设计:
- 基础时间片:按30分钟为一个单元
- 服务时间片:根据套餐类型动态计算(如婚纱摄影可能需要连续占用4个基础时间片)
数据库设计时特别注意了并发控制,在schedule表上使用了乐观锁机制。这里有个实际踩过的坑:初期使用简单的时间段字符串存储(如"9:00-12:00"),后来发现跨天预约和时区处理会有问题,最终改为UNIX时间戳存储。
2.2 套餐与定价体系
摄影套餐的灵活性是系统设计的难点。我最终采用的方案是:
java复制// 套餐基础实体
@Entity
public class PhotographyPackage {
@Id
@GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name="package_options")
private Map<String, Integer> options; // 如 "精修张数":20
@OneToMany(cascade=CascadeType.ALL)
private List<PricingRule> pricingRules;
}
定价规则采用策略模式实现,支持:
- 固定价格
- 时段浮动价格(如周末加价)
- 附加项累加价格(如加服装费用)
3. 技术实现要点
3.1 SpringBoot定制配置
在application.yml中做了这些关键配置:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/photo_booking?useSSL=false&serverTimezone=Asia/Shanghai
hikari:
maximum-pool-size: 20
connection-timeout: 30000
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm
特别注意了时区问题,中国用户必须显式配置GMT+8,否则前端显示的时间会错乱。这个坑我在第一个版本就踩到了。
3.2 预约冲突检测算法
核心算法实现:
java复制public boolean checkAvailability(Long photographerId, Long studioId,
LocalDateTime start, LocalDateTime end) {
// 检查摄影师档期
List<Booking> photographerBookings = bookingRepository
.findByPhotographerIdAndStatusNot(photographerId, BookingStatus.CANCELLED);
// 检查影棚占用
List<Booking> studioBookings = bookingRepository
.findByStudioIdAndStatusNot(studioId, BookingStatus.CANCELLED);
return Stream.concat(photographerBookings.stream(), studioBookings.stream())
.noneMatch(b -> timeOverlap(b.getStartTime(), b.getEndTime(), start, end));
}
private boolean timeOverlap(LocalDateTime s1, LocalDateTime e1,
LocalDateTime s2, LocalDateTime e2) {
return s1.isBefore(e2) && e1.isAfter(s2);
}
这个实现经过了三次优化:
- 初版使用SQL查询WHERE条件判断时间重叠,但复杂SQL难以维护
- 第二版改为内存中计算,但N+1查询问题严重
- 最终版使用JPA的预加载+内存计算平衡了性能和可维护性
4. 关键业务实现
4.1 支付对接方案
支付流程采用了状态机模式:
mermaid复制stateDiagram-v2
[*] --> PENDING
PENDING --> PAID_DEPOSIT: 支付定金
PAID_DEPOSIT --> COMPLETED: 支付尾款
PENDING --> CANCELLED: 用户取消
PAID_DEPOSIT --> REFUNDING: 申请退款
实际对接微信支付时,需要注意:
- 定金支付和尾款支付要使用不同的商户订单号前缀
- 退款API的调用频率限制(微信侧是1QPS)
- 异步通知处理要保证幂等性
我的实现方案是在支付日志表添加唯一索引:
sql复制ALTER TABLE payment_log
ADD UNIQUE INDEX idx_out_trade_no (out_trade_no, payment_type);
4.2 影棚资源管理
影棚资源采用了树形结构设计:
- 场地(如"3号摄影棚")
- 固定设备(如"环形补光灯组")
- 可预约时段(关联到时间片表)
在后台管理界面实现了可视化的拖拽式排班表,核心是基于FullCalendar的定制开发。这里有个性能优化点:当同时加载多个摄影师的日历时,需要使用批量查询:
java复制@Repository
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
@Query("SELECT s FROM Schedule s WHERE s.photographer.id IN :photographerIds " +
"AND s.date BETWEEN :start AND :end")
List<Schedule> findSchedulesInBatch(
@Param("photographerIds") List<Long> photographerIds,
@Param("start") LocalDate start,
@Param("end") LocalDate end);
}
5. 部署与运维实践
5.1 多环境配置
使用Spring Profiles实现环境隔离:
code复制src/main/resources/
├── application.yml
├── application-dev.yml
├── application-test.yml
└── application-prod.yml
生产环境特别配置了:
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST:photo-db-cluster}:3306/photo_booking_prod
hikari:
maximum-pool-size: 50
redis:
cluster:
nodes: ${REDIS_NODES:redis-cluster:6379}
5.2 健康检查端点
添加了自定义的健康检查指标:
java复制@RestController
@RequestMapping("/actuator")
public class HealthCheckController {
@GetMapping("/db-check")
public ResponseEntity<?> checkDbConnection() {
try {
jdbcTemplate.execute("SELECT 1");
return ResponseEntity.ok().build();
} catch (Exception e) {
return ResponseEntity.status(503).build();
}
}
}
在Kubernetes部署时,这个端点用于配置livenessProbe:
yaml复制livenessProbe:
httpGet:
path: /actuator/db-check
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
6. 典型问题排查
6.1 并发预约冲突
现象:两个用户同时预约同一时段有时会成功
解决方案:
- 数据库层面添加唯一索引
sql复制ALTER TABLE booking
ADD UNIQUE INDEX idx_photographer_time (photographer_id, start_time);
- 业务代码添加synchronized块(仅限单机部署)
- 分布式环境下改用Redis分布式锁
6.2 微信支付回调丢失
现象:偶尔用户已付款但订单状态未更新
排查过程:
- 检查支付日志表的notify_received字段
- 发现部分记录received_time为null但trade_state为SUCCESS
- 确认是微信回调时服务短暂不可用
最终解决方案:
- 添加定时任务补偿查询
java复制@Scheduled(cron = "0 */5 * * * ?")
public void checkPendingPayments() {
List<Payment> pendings = paymentRepository
.findByStatusAndCreateTimeAfter(PaymentStatus.PENDING,
LocalDateTime.now().minusHours(2));
pendings.forEach(p -> {
WxPayOrderQueryResult result = wxPayService.queryOrder(p.getOutTradeNo());
if ("SUCCESS".equals(result.getTradeState())) {
paymentService.processPaymentSuccess(p.getOutTradeNo());
}
});
}
7. 系统扩展方向
在实际运营中,这套系统还可以进一步扩展:
- 智能推荐引擎
- 基于用户历史预约数据推荐相似风格摄影师
- 实现思路:使用协同过滤算法
python复制# 伪代码示例
def recommend_photographers(user_id):
user_vector = get_user_preferences(user_id)
photographers = Photographer.objects.all()
return sorted(photographers,
key=lambda p: cosine_similarity(user_vector, p.style_vector))
- 虚拟试衣间集成
- 使用Three.js实现Web端3D试衣
- 需要与服装供应商的库存系统对接
- 摄影师KPI系统
- 自动生成摄影师的工作效率报告
- 关键指标:单位时间成单率、客户满意度评分
这套系统从技术实现上看,采用了SpringBoot的经典技术栈,但在业务模型处理上有不少创新点。特别是在时间片管理和套餐灵活性方面,经过三个版本的迭代才形成现在的稳定方案。对于想要学习SpringBoot实战开发的同学,这个项目涉及的技术要点非常全面,从基础CRUD到复杂的业务逻辑、支付对接、性能优化都有涵盖。
