perf: optimize report/appointment query paths and add paging

This commit is contained in:
malei 2026-04-15 07:20:56 +08:00
parent 5b19922fd3
commit 3604b4bb78
11 changed files with 183 additions and 24 deletions

View File

@ -3,6 +3,7 @@ package com.petstore.controller;
import com.petstore.entity.Appointment; import com.petstore.entity.Appointment;
import com.petstore.service.AppointmentService; import com.petstore.service.AppointmentService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.HashMap; import java.util.HashMap;
@ -21,19 +22,41 @@ public class AppointmentController {
public Map<String, Object> list( public Map<String, Object> list(
@RequestParam(required = false) Long userId, @RequestParam(required = false) Long userId,
@RequestParam(required = false) Long storeId, @RequestParam(required = false) Long storeId,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status,
@RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer pageSize) {
if (storeId == null && userId == null) {
return Map.of("code", 400, "message", "userId或storeId必填");
}
List<Appointment> appointments; List<Appointment> appointments;
boolean usePaging = page != null || pageSize != null;
if (usePaging) {
int pageNo = page == null ? 1 : page;
int size = pageSize == null ? 20 : pageSize;
pageNo = Math.max(pageNo, 1);
size = Math.min(Math.max(size, 1), 200);
Page<Appointment> paged = appointmentService.pageByScope(userId, storeId, status, pageNo - 1, size);
return Map.of(
"code", 200,
"data", paged.getContent(),
"page", pageNo,
"pageSize", size,
"total", paged.getTotalElements(),
"totalPages", paged.getTotalPages(),
"hasNext", paged.hasNext()
);
}
if (storeId != null) { if (storeId != null) {
appointments = (status != null && !status.isEmpty()) appointments = (status != null && !status.isEmpty())
? appointmentService.getByStoreIdAndStatus(storeId, status) ? appointmentService.getByStoreIdAndStatus(storeId, status)
: appointmentService.getByStoreId(storeId); : appointmentService.getByStoreId(storeId);
} else if (userId != null) { } else {
appointments = (status != null && !status.isEmpty()) appointments = (status != null && !status.isEmpty())
? appointmentService.getByUserIdAndStatus(userId, status) ? appointmentService.getByUserIdAndStatus(userId, status)
: appointmentService.getByUserId(userId); : appointmentService.getByUserId(userId);
} else {
return Map.of("code", 400, "message", "userId或storeId必填");
} }
return Map.of("code", 200, "data", appointments); return Map.of("code", 200, "data", appointments);

View File

@ -7,6 +7,7 @@ import com.petstore.service.StoreService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -47,8 +48,22 @@ public class ReportController {
@GetMapping("/list") @GetMapping("/list")
public Map<String, Object> list(@RequestParam(required = false) Long storeId, public Map<String, Object> list(@RequestParam(required = false) Long storeId,
@RequestParam(required = false) Long userId) { @RequestParam(required = false) Long userId,
List<Report> reports = reportService.list(storeId, userId); @RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer pageSize) {
boolean usePaging = page != null || pageSize != null;
List<Report> reports;
Page<Report> paged = null;
Integer pageNo = null;
Integer size = null;
if (usePaging) {
pageNo = Math.max(page == null ? 1 : page, 1);
size = Math.min(Math.max(pageSize == null ? 20 : pageSize, 1), 200);
paged = reportService.page(storeId, userId, pageNo - 1, size);
reports = paged.getContent();
} else {
reports = reportService.list(storeId, userId);
}
// 附加技师名称并补全图片URL // 附加技师名称并补全图片URL
List<Map<String, Object>> data = reports.stream().map(r -> { List<Map<String, Object>> data = reports.stream().map(r -> {
Map<String, Object> item = new HashMap<>(); Map<String, Object> item = new HashMap<>();
@ -66,6 +81,17 @@ public class ReportController {
item.put("userId", r.getUserId()); item.put("userId", r.getUserId());
return item; return item;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
if (usePaging && paged != null) {
return Map.of(
"code", 200,
"data", data,
"page", pageNo,
"pageSize", size,
"total", paged.getTotalElements(),
"totalPages", paged.getTotalPages(),
"hasNext", paged.hasNext()
);
}
return Map.of("code", 200, "data", data); return Map.of("code", 200, "data", data);
} }

View File

@ -6,7 +6,13 @@ import java.time.LocalDateTime;
@Data @Data
@Entity @Entity
@Table(name = "t_appointment") @Table(
name = "t_appointment",
indexes = {
@Index(name = "idx_appt_store_status_time", columnList = "store_id,status,appointment_time"),
@Index(name = "idx_appt_user_status_time", columnList = "user_id,status,appointment_time")
}
)
public class Appointment { public class Appointment {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -7,7 +7,14 @@ import java.time.LocalDateTime;
@Data @Data
@Entity @Entity
@Table(name = "t_report") @Table(
name = "t_report",
indexes = {
@Index(name = "idx_report_appointment_id", columnList = "appointment_id"),
@Index(name = "idx_report_token", columnList = "report_token"),
@Index(name = "idx_report_store_user_time", columnList = "store_id,user_id,create_time")
}
)
public class Report { public class Report {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -6,7 +6,12 @@ import java.time.LocalDateTime;
@Data @Data
@Entity @Entity
@Table(name = "t_service_type") @Table(
name = "t_service_type",
indexes = {
@Index(name = "idx_service_type_store_id", columnList = "store_id")
}
)
public class ServiceType { public class ServiceType {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -6,7 +6,13 @@ import java.time.LocalDateTime;
@Data @Data
@Entity @Entity
@Table(name = "t_store") @Table(
name = "t_store",
indexes = {
@Index(name = "idx_store_invite_code", columnList = "invite_code"),
@Index(name = "idx_store_owner_id", columnList = "owner_id")
}
)
public class Store { public class Store {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -6,7 +6,13 @@ import java.time.LocalDateTime;
@Data @Data
@Entity @Entity
@Table(name = "t_user") @Table(
name = "t_user",
indexes = {
@Index(name = "idx_user_phone", columnList = "phone"),
@Index(name = "idx_user_store_id", columnList = "store_id")
}
)
public class User { public class User {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -1,6 +1,8 @@
package com.petstore.mapper; package com.petstore.mapper;
import com.petstore.entity.Appointment; import com.petstore.entity.Appointment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
@ -10,4 +12,9 @@ public interface AppointmentMapper extends JpaRepository<Appointment, Long> {
List<Appointment> findByUserIdAndStatus(Long userId, String status); List<Appointment> findByUserIdAndStatus(Long userId, String status);
List<Appointment> findByStoreId(Long storeId); List<Appointment> findByStoreId(Long storeId);
List<Appointment> findByStoreIdAndStatus(Long storeId, String status); List<Appointment> findByStoreIdAndStatus(Long storeId, String status);
Page<Appointment> findByUserId(Long userId, Pageable pageable);
Page<Appointment> findByUserIdAndStatus(Long userId, String status, Pageable pageable);
Page<Appointment> findByStoreId(Long storeId, Pageable pageable);
Page<Appointment> findByStoreIdAndStatus(Long storeId, String status, Pageable pageable);
} }

View File

@ -1,7 +1,29 @@
package com.petstore.mapper; package com.petstore.mapper;
import com.petstore.entity.Report; import com.petstore.entity.Report;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface ReportMapper extends JpaRepository<Report, Long> { public interface ReportMapper extends JpaRepository<Report, Long> {
Optional<Report> findFirstByAppointmentIdOrderByCreateTimeDesc(Long appointmentId);
Optional<Report> findFirstByReportToken(String reportToken);
List<Report> findByStoreIdOrderByCreateTimeDesc(Long storeId);
List<Report> findByUserIdOrderByCreateTimeDesc(Long userId);
List<Report> findByStoreIdAndUserIdOrderByCreateTimeDesc(Long storeId, Long userId);
List<Report> findAllByOrderByCreateTimeDesc();
Page<Report> findByStoreId(Long storeId, Pageable pageable);
Page<Report> findByUserId(Long userId, Pageable pageable);
Page<Report> findByStoreIdAndUserId(Long storeId, Long userId, Pageable pageable);
} }

View File

@ -3,6 +3,10 @@ package com.petstore.service;
import com.petstore.entity.Appointment; import com.petstore.entity.Appointment;
import com.petstore.mapper.AppointmentMapper; import com.petstore.mapper.AppointmentMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -33,6 +37,26 @@ public class AppointmentService {
return appointmentMapper.findByStoreIdAndStatus(storeId, status); return appointmentMapper.findByStoreIdAndStatus(storeId, status);
} }
public Page<Appointment> pageByScope(Long userId, Long storeId, String status, int pageNo, int pageSize) {
Pageable pageable = PageRequest.of(
Math.max(pageNo, 0),
Math.max(pageSize, 1),
Sort.by(Sort.Direction.DESC, "appointmentTime")
);
boolean hasStatus = status != null && !status.isBlank();
if (storeId != null) {
return hasStatus
? appointmentMapper.findByStoreIdAndStatus(storeId, status, pageable)
: appointmentMapper.findByStoreId(storeId, pageable);
}
if (userId != null) {
return hasStatus
? appointmentMapper.findByUserIdAndStatus(userId, status, pageable)
: appointmentMapper.findByUserId(userId, pageable);
}
return Page.empty(pageable);
}
public Appointment getById(Long id) { public Appointment getById(Long id) {
return appointmentMapper.findById(id).orElse(null); return appointmentMapper.findById(id).orElse(null);
} }

View File

@ -7,12 +7,15 @@ import com.petstore.mapper.AppointmentMapper;
import com.petstore.mapper.ReportMapper; import com.petstore.mapper.ReportMapper;
import com.petstore.mapper.UserMapper; import com.petstore.mapper.UserMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -64,23 +67,47 @@ public class ReportService {
} }
public Report getByAppointmentId(Long appointmentId) { public Report getByAppointmentId(Long appointmentId) {
return reportMapper.findAll().stream() if (appointmentId == null) {
.filter(r -> r.getAppointmentId().equals(appointmentId)) return null;
.findFirst() }
.orElse(null); return reportMapper.findFirstByAppointmentIdOrderByCreateTimeDesc(appointmentId).orElse(null);
} }
public Report getByToken(String token) { public Report getByToken(String token) {
return reportMapper.findAll().stream() if (token == null || token.isBlank()) {
.filter(r -> token.equals(r.getReportToken())) return null;
.findFirst() }
.orElse(null); return reportMapper.findFirstByReportToken(token).orElse(null);
} }
public List<Report> list(Long storeId, Long userId) { public List<Report> list(Long storeId, Long userId) {
return reportMapper.findAll().stream() if (storeId != null && userId != null) {
.filter(r -> storeId == null || storeId.equals(r.getStoreId())) return reportMapper.findByStoreIdAndUserIdOrderByCreateTimeDesc(storeId, userId);
.filter(r -> userId == null || userId.equals(r.getUserId())) }
.collect(Collectors.toList()); if (storeId != null) {
return reportMapper.findByStoreIdOrderByCreateTimeDesc(storeId);
}
if (userId != null) {
return reportMapper.findByUserIdOrderByCreateTimeDesc(userId);
}
return reportMapper.findAllByOrderByCreateTimeDesc();
}
public Page<Report> page(Long storeId, Long userId, int pageNo, int pageSize) {
Pageable pageable = PageRequest.of(
Math.max(pageNo, 0),
Math.max(pageSize, 1),
Sort.by(Sort.Direction.DESC, "createTime")
);
if (storeId != null && userId != null) {
return reportMapper.findByStoreIdAndUserId(storeId, userId, pageable);
}
if (storeId != null) {
return reportMapper.findByStoreId(storeId, pageable);
}
if (userId != null) {
return reportMapper.findByUserId(userId, pageable);
}
return reportMapper.findAll(pageable);
} }
} }