一个基于 Spring Boot + JPA + MySQL 的上门家政系统核心功能示例代码,涵盖了用户、服务人员、服务项目、订单管理的典型实现。
项目结构
css
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 核心部分)
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)
kotlin
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)
kotlin
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)
kotlin
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)
java
package com.housekeeping.entity;
public enum OrderStatus {
PENDING, // 待确认
CONFIRMED, // 已确认
PROCESSING, // 服务中
COMPLETED, // 已完成
CANCELLED // 已取消
}
2.5 订单实体(Order.java)
kotlin
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 层
less
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 类
kotlin
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;
}
kotlin
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)
scss
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)
less
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)
kotlin
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)
ini
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. 主启动类
typescript
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)
java
@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)、支付集成 、服务人员自动分配 、位置距离排序等高级功能。