以下是一个基于 Spring Boot + JPA + MySQL 的上门家政系统核心功能示例代码,涵盖了用户、服务人员、服务项目、订单管理的典型实现。
项目结构
src/main/java/com/housekeeping/ ├── entity/ │ ├── User.java │ ├── Staff.java │ ├── ServiceItem.java │ ├── Order.java │ └── OrderStatus.java ├── repository/ │ ├── UserRepository.java │ ├── StaffRepository.java │ ├── ServiceItemRepository.java │ └── OrderRepository.java ├── service/ │ ├── OrderService.java │ └── UserService.java ├── controller/ │ ├── OrderController.java │ └── UserController.java ├── dto/ │ ├── OrderCreateDTO.java │ └── OrderResponseDTO.java └── HousekeepingApplication.java
1. Maven 依赖(pom.xml 核心部分)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
2. 实体类
2.1 用户实体(User.java)
package com.housekeeping.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; @Data @Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String phone; private String address; private LocalDateTime createTime; }
2.2 家政人员实体(Staff.java)
package com.housekeeping.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; @Data @Entity @Table(name = "staff") public class Staff { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String phone; private String skill; // 擅长服务类型 private Double rating; // 平均评分 private Boolean available; // 是否可接单 private LocalDateTime createTime; }
2.3 服务项目实体(ServiceItem.java)
package com.housekeeping.entity; import lombok.Data; import javax.persistence.*; @Data @Entity @Table(name = "service_item") public class ServiceItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 如:日常保洁、深度保洁 private String description; private Double pricePerHour; // 每小时价格 }
2.4 订单状态枚举(OrderStatus.java)
package com.housekeeping.entity; public enum OrderStatus { PENDING, // 待确认 CONFIRMED, // 已确认 PROCESSING, // 服务中 COMPLETED, // 已完成 CANCELLED // 已取消 }
2.5 订单实体(Order.java)
package com.housekeeping.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; @Data @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private Long userId; @Column(nullable = false) private Long staffId; @Column(nullable = false) private Long serviceItemId; private LocalDateTime serviceStartTime; private Integer durationHours; // 服务时长(小时) private Double totalPrice; private String address; @Enumerated(EnumType.STRING) private OrderStatus status; private String comment; // 评价内容 private Integer rating; // 评分1-5 private LocalDateTime createTime; private LocalDateTime updateTime; }
3. Repository 层
package com.housekeeping.repository; import com.housekeeping.entity.Order; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.time.LocalDateTime; import java.util.List; public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByUserId(Long userId); List<Order> findByStaffId(Long staffId); // 查询某家政人员在指定时间段是否有重叠订单 @Query("SELECT COUNT(o) FROM Order o WHERE o.staffId = :staffId " + "AND o.status != 'CANCELLED' " + "AND o.serviceStartTime < :endTime " + "AND (o.serviceStartTime + (o.durationHours || ' hour') ) > :startTime") int countOverlappingOrders(@Param("staffId") Long staffId, @Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); }
注意:上述 JPA 时间运算依赖数据库方言,实际开发中更推荐使用原生查询或改用 Java 逻辑。此处为演示冲突检测思路。
4. DTO 类
package com.housekeeping.dto; import lombok.Data; import java.time.LocalDateTime; @Data public class OrderCreateDTO { private Long userId; private Long staffId; private Long serviceItemId; private LocalDateTime serviceStartTime; private Integer durationHours; private String address; }
package com.housekeeping.dto; import lombok.Data; import com.housekeeping.entity.OrderStatus; import java.time.LocalDateTime; @Data public class OrderResponseDTO { private Long id; private Long userId; private Long staffId; private Long serviceItemId; private LocalDateTime serviceStartTime; private Integer durationHours; private Double totalPrice; private String address; private OrderStatus status; private LocalDateTime createTime; }
5. 服务层
5.1 订单服务(OrderService.java)
package com.housekeeping.service; import com.housekeeping.dto.OrderCreateDTO; import com.housekeeping.dto.OrderResponseDTO; import com.housekeeping.entity.*; import com.housekeeping.repository.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; private final StaffRepository staffRepository; private final ServiceItemRepository serviceItemRepository; @Transactional public OrderResponseDTO createOrder(OrderCreateDTO dto) { // 校验用户、家政人员、服务项目是否存在 User user = userRepository.findById(dto.getUserId()) .orElseThrow(() -> new RuntimeException("用户不存在")); Staff staff = staffRepository.findById(dto.getStaffId()) .orElseThrow(() -> new RuntimeException("家政人员不存在")); ServiceItem item = serviceItemRepository.findById(dto.getServiceItemId()) .orElseThrow(() -> new RuntimeException("服务项目不存在")); if (!staff.getAvailable()) { throw new RuntimeException("该家政人员当前不可接单"); } // 时间冲突检测(简化:通过Java检查重叠) LocalDateTime start = dto.getServiceStartTime(); LocalDateTime end = start.plusHours(dto.getDurationHours()); boolean conflict = orderRepository.findAll().stream() .filter(o -> o.getStaffId().equals(staff.getId())) .filter(o -> o.getStatus() != OrderStatus.CANCELLED) .anyMatch(o -> { LocalDateTime oStart = o.getServiceStartTime(); LocalDateTime oEnd = oStart.plusHours(o.getDurationHours()); return start.isBefore(oEnd) && end.isAfter(oStart); }); if (conflict) { throw new RuntimeException("该时间段已有其他订单,请重新选择时间"); } // 计算总价 Double totalPrice = item.getPricePerHour() * dto.getDurationHours(); Order order = new Order(); order.setUserId(user.getId()); order.setStaffId(staff.getId()); order.setServiceItemId(item.getId()); order.setServiceStartTime(start); order.setDurationHours(dto.getDurationHours()); order.setTotalPrice(totalPrice); order.setAddress(dto.getAddress()); order.setStatus(OrderStatus.PENDING); order.setCreateTime(LocalDateTime.now()); order.setUpdateTime(LocalDateTime.now()); Order saved = orderRepository.save(order); return convertToDTO(saved); } @Transactional public void cancelOrder(Long orderId, Long userId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new RuntimeException("订单不存在")); if (!order.getUserId().equals(userId)) { throw new RuntimeException("无权操作此订单"); } if (order.getStatus() != OrderStatus.PENDING) { throw new RuntimeException("只有待确认的订单才能取消"); } order.setStatus(OrderStatus.CANCELLED); order.setUpdateTime(LocalDateTime.now()); orderRepository.save(order); } @Transactional public void completeOrder(Long orderId, Long staffId, String comment, Integer rating) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new RuntimeException("订单不存在")); if (!order.getStaffId().equals(staffId)) { throw new RuntimeException("无权操作此订单"); } if (order.getStatus() != OrderStatus.PROCESSING) { throw new RuntimeException("只有服务中的订单才能完成"); } order.setStatus(OrderStatus.COMPLETED); order.setComment(comment); order.setRating(rating); order.setUpdateTime(LocalDateTime.now()); orderRepository.save(order); } private OrderResponseDTO convertToDTO(Order order) { OrderResponseDTO dto = new OrderResponseDTO(); dto.setId(order.getId()); dto.setUserId(order.getUserId()); dto.setStaffId(order.getStaffId()); dto.setServiceItemId(order.getServiceItemId()); dto.setServiceStartTime(order.getServiceStartTime()); dto.setDurationHours(order.getDurationHours()); dto.setTotalPrice(order.getTotalPrice()); dto.setAddress(order.getAddress()); dto.setStatus(order.getStatus()); dto.setCreateTime(order.getCreateTime()); return dto; } }
6. 控制器层
6.1 订单控制器(OrderController.java)
package com.housekeeping.controller; import com.housekeeping.dto.OrderCreateDTO; import com.housekeeping.dto.OrderResponseDTO; import com.housekeeping.service.OrderService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/orders") @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @PostMapping("/create") public ResponseEntity<?> createOrder(@RequestBody OrderCreateDTO dto) { try { OrderResponseDTO order = orderService.createOrder(dto); return ResponseEntity.ok(order); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } } @PutMapping("/cancel/{orderId}") public ResponseEntity<?> cancelOrder(@PathVariable Long orderId, @RequestParam Long userId) { try { orderService.cancelOrder(orderId, userId); return ResponseEntity.ok("订单已取消"); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } } @PutMapping("/complete/{orderId}") public ResponseEntity<?> completeOrder(@PathVariable Long orderId, @RequestParam Long staffId, @RequestParam String comment, @RequestParam Integer rating) { try { orderService.completeOrder(orderId, staffId, comment, rating); return ResponseEntity.ok("订单已完成并评价"); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } } }
6.2 用户控制器示例(UserController.java)
package com.housekeeping.controller; import com.housekeeping.entity.Order; import com.housekeeping.repository.OrderRepository; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { private final OrderRepository orderRepository; @GetMapping("/{userId}/orders") public List<Order> getUserOrders(@PathVariable Long userId) { return orderRepository.findByUserId(userId); } }
7. 配置文件(application.properties)
spring.datasource.url=jdbc:mysql://localhost:3306/housekeeping?useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
8. 主启动类
package com.housekeeping; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HousekeepingApplication { public static void main(String[] args) { SpringApplication.run(HousekeepingApplication.class, args); } }
9. 初始化测试数据(可选 CommandLineRunner)
@Component @RequiredArgsConstructor public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final StaffRepository staffRepository; private final ServiceItemRepository serviceItemRepository; @Override public void run(String... args) throws Exception { if (userRepository.count() == 0) { User user = new User(); user.setName("张三"); user.setPhone("13800000000"); user.setAddress("北京市朝阳区xxx"); user.setCreateTime(LocalDateTime.now()); userRepository.save(user); Staff staff = new Staff(); staff.setName("李阿姨"); staff.setPhone("13911111111"); staff.setSkill("日常保洁、深度保洁"); staff.setRating(4.9); staff.setAvailable(true); staff.setCreateTime(LocalDateTime.now()); staffRepository.save(staff); ServiceItem item = new ServiceItem(); item.setName("日常保洁"); item.setDescription("全屋基础清洁"); item.setPricePerHour(50.0); serviceItemRepository.save(item); } } }
核心业务说明
- 下单流程:用户选择服务项目、家政人员、上门时间 → 系统校验人员可用性及时间冲突 → 自动计费 → 生成待确认订单。
- 时间冲突检测:通过遍历该人员的未取消订单,判断时间区间是否有重叠。
- 订单状态流转:PENDING(待确认)→ CONFIRMED(员工确认后)→ PROCESSING(开始服务)→ COMPLETED(完成+评价),或直接 CANCELLED。
- 评价体系:订单完成后可填写文字评价和1-5星评分,后续用于展示家政人员平均分。
此示例可直接扩展为完整项目,加入登录认证(JWT/Spring Security)、支付集成、服务人员自动分配、位置距离排序等高级功能。