package com.tourguide.modules.review.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.tourguide.common.api.PageResponse;
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.modules.order.domain.OrderStatus;
|
import com.tourguide.modules.order.entity.TourOrder;
|
import com.tourguide.modules.order.mapper.TourOrderMapper;
|
import com.tourguide.modules.review.dto.OrderReviewCreateRequest;
|
import com.tourguide.modules.review.dto.OrderReviewResponse;
|
import com.tourguide.modules.review.dto.GuideRatingItemResponse;
|
import com.tourguide.modules.review.dto.GuideRatingResponse;
|
import com.tourguide.modules.review.entity.OrderReview;
|
import com.tourguide.modules.review.mapper.OrderReviewMapper;
|
import com.tourguide.modules.user.entity.GuideUser;
|
import com.tourguide.modules.user.mapper.GuideUserMapper;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.List;
|
import java.math.RoundingMode;
|
import java.util.UUID;
|
import java.util.stream.Collectors;
|
|
@Service
|
public class OrderReviewService {
|
private final OrderReviewMapper reviewMapper;
|
private final TourOrderMapper orderMapper;
|
private final GuideUserMapper guideUserMapper;
|
|
public OrderReviewService(OrderReviewMapper reviewMapper, TourOrderMapper orderMapper, GuideUserMapper guideUserMapper) {
|
this.reviewMapper = reviewMapper;
|
this.orderMapper = orderMapper;
|
this.guideUserMapper = guideUserMapper;
|
}
|
|
public PageResponse<OrderReviewResponse> page(long page, long size, String keyword, Integer status,
|
Integer score, String scenicName,
|
LocalDate startDate, LocalDate endDate) {
|
LambdaQueryWrapper<OrderReview> query = new LambdaQueryWrapper<OrderReview>();
|
if (StringUtils.hasText(keyword)) {
|
String searchKeyword = keyword.trim();
|
query.and(wrapper -> wrapper.like(OrderReview::getVisitorMobile, searchKeyword)
|
.or().like(OrderReview::getGuideMobile, searchKeyword)
|
.or().like(OrderReview::getGuideName, searchKeyword)
|
.or().like(OrderReview::getOrderNo, searchKeyword)
|
.or().like(OrderReview::getReviewNo, searchKeyword));
|
}
|
query.eq(status != null, OrderReview::getStatus, status)
|
.eq(score != null, OrderReview::getScore, score)
|
.eq(StringUtils.hasText(scenicName), OrderReview::getScenicName, scenicName)
|
.ge(startDate != null, OrderReview::getReviewedAt, startDate == null ? null : startDate.atStartOfDay())
|
.lt(endDate != null, OrderReview::getReviewedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay())
|
.orderByDesc(OrderReview::getId);
|
Page<OrderReview> result = reviewMapper.selectPage(new Page<OrderReview>(page, size), query);
|
return new PageResponse<OrderReviewResponse>(result.getRecords().stream()
|
.map(OrderReviewResponse::new).collect(Collectors.toList()),
|
result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
public List<String> scenicOptions() {
|
return reviewMapper.selectScenicOptions();
|
}
|
|
public GuideRatingResponse guideRatings(Long guideId, LocalDate date, long pageNum, long pageSize) {
|
GuideUser guide = guideUserMapper.selectById(guideId);
|
if (guide == null || guide.getStatus() == null || guide.getStatus() != 1) {
|
throw new BusinessException(401, "导游用户不存在或已停用");
|
}
|
|
long normalizedPageNum = Math.max(pageNum, 1L);
|
long normalizedPageSize = Math.min(Math.max(pageSize, 1L), 100L);
|
LambdaQueryWrapper<OrderReview> query = new LambdaQueryWrapper<OrderReview>()
|
.eq(OrderReview::getGuideId, guideId)
|
.eq(OrderReview::getStatus, 1)
|
.ge(date != null, OrderReview::getReviewedAt, date == null ? null : date.atStartOfDay())
|
.lt(date != null, OrderReview::getReviewedAt,
|
date == null ? null : date.plusDays(1).atStartOfDay())
|
.orderByDesc(OrderReview::getReviewedAt)
|
.orderByDesc(OrderReview::getId);
|
Page<OrderReview> result = reviewMapper.selectPage(
|
new Page<OrderReview>(normalizedPageNum, normalizedPageSize), query);
|
BigDecimal score = guide.getRating() == null ? BigDecimal.ZERO : guide.getRating();
|
return new GuideRatingResponse(
|
score,
|
result.getRecords().stream().map(GuideRatingItemResponse::new).collect(Collectors.toList()),
|
result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
@Transactional
|
public OrderReviewResponse create(Long visitorId, OrderReviewCreateRequest request) {
|
TourOrder order = orderMapper.selectById(request.getOrderId());
|
if (order == null) throw new BusinessException(404, "订单不存在");
|
if (order.getVisitorId() == null || !order.getVisitorId().equals(visitorId)) {
|
throw new BusinessException(403, "只能评价本人订单");
|
}
|
if (!OrderStatus.COMPLETED.matches(order.getStatus())) {
|
throw new BusinessException(409, "仅已完成订单可以评价");
|
}
|
if (order.getGuideId() == null) throw new BusinessException(409, "订单尚未关联导游");
|
if (reviewMapper.countByOrderId(order.getId()) > 0) throw new BusinessException(409, "该订单已经评价");
|
|
OrderReview review = new OrderReview();
|
review.setReviewNo(generateReviewNo());
|
review.setOrderId(order.getId());
|
review.setOrderNo(order.getOrderNo());
|
review.setOrderPlacedAt(order.getPlacedAt());
|
review.setVisitorId(order.getVisitorId());
|
review.setVisitorNickname(order.getVisitorNickname());
|
review.setVisitorMobile(order.getVisitorMobile());
|
review.setGuideId(order.getGuideId());
|
review.setGuideName(order.getGuideName());
|
review.setGuideMobile(order.getGuideMobile());
|
review.setScenicName(order.getScenicName());
|
review.setScore(request.getScore());
|
review.setContent(request.getContent());
|
review.setReviewedAt(LocalDateTime.now());
|
review.setStatus(1);
|
review.setDeleted(0);
|
reviewMapper.insert(review);
|
refreshGuideRating(order.getGuideId());
|
return new OrderReviewResponse(review);
|
}
|
|
public OrderReviewResponse detailByOrder(Long visitorId, Long orderId) {
|
TourOrder order = orderMapper.selectById(orderId);
|
if (order == null) throw new BusinessException(404, "order not found");
|
if (order.getVisitorId() == null || !order.getVisitorId().equals(visitorId)) {
|
throw new BusinessException(403, "cannot view another visitor's review");
|
}
|
|
OrderReview review = reviewMapper.selectOne(new LambdaQueryWrapper<OrderReview>()
|
.eq(OrderReview::getOrderId, orderId)
|
.eq(OrderReview::getStatus, 1)
|
.last("LIMIT 1"));
|
if (review == null) throw new BusinessException(404, "review not found");
|
return new OrderReviewResponse(review);
|
}
|
|
@Transactional
|
public void delete(Long id) {
|
OrderReview review = reviewMapper.selectById(id);
|
if (review == null) throw new BusinessException(404, "评价不存在");
|
review.setDeletedAt(LocalDateTime.now());
|
reviewMapper.updateById(review);
|
reviewMapper.deleteById(id);
|
if (review.getGuideId() != null) refreshGuideRating(review.getGuideId());
|
}
|
|
private void refreshGuideRating(Long guideId) {
|
GuideUser guide = guideUserMapper.selectById(guideId);
|
if (guide == null) return;
|
BigDecimal average = reviewMapper.selectAverageScore(guideId);
|
guide.setRating(average == null ? BigDecimal.ZERO : average.setScale(2, RoundingMode.HALF_UP));
|
guideUserMapper.updateById(guide);
|
}
|
|
private String generateReviewNo() {
|
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
return "RV" + time + UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
|
}
|
}
|