|
|
@@ -0,0 +1,107 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.etotem.cfc.dto.ProductReviewRequest;
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
+import com.etotem.cfc.entity.ProductReview;
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductReviewMapper;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.Calendar;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 普通商品评价服务实现(能力键 {@code shop.review.create},R1)。
|
|
|
+ *
|
|
|
+ * <p>设计 {@code docs/superpowers/specs/2026-11-14-product-order-gap-arch-design.md} §3.1 R1 校验链:
|
|
|
+ * 订单存在 / {@code productType != 'activity'}(活动评价零污染)/ {@code status=completed} /
|
|
|
+ * {@code buyerId==userId}(评价人为下单人本人)/ {@code confirmTime >= now-90d}(Q4 窗口)/
|
|
|
+ * {@link ProductReviewMapper#findByOrderId} 幂等(一单一评,表唯一键 {@code uk_order_id})。</p>
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class ProductReviewServiceImpl extends ServiceImpl<ProductReviewMapper, ProductReview>
|
|
|
+ implements ProductReviewService {
|
|
|
+
|
|
|
+ /** 90 天评价窗口(Q4 默认口径)。 */
|
|
|
+ private static final int REVIEW_WINDOW_DAYS = 90;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProductReviewMapper reviewMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProductOrderMapper productOrderMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public String createReview(ProductReviewRequest request, Long reviewerId) {
|
|
|
+ // 入参守卫
|
|
|
+ if (request == null || request.getOrderId() == null) {
|
|
|
+ throw new RuntimeException("订单ID不能为空");
|
|
|
+ }
|
|
|
+ if (request.getRating() == null || request.getRating() < 1 || request.getRating() > 5) {
|
|
|
+ throw new RuntimeException("评价星级必须在 1-5 之间");
|
|
|
+ }
|
|
|
+ if (request.getReviewText() != null && request.getReviewText().length() > 500) {
|
|
|
+ throw new RuntimeException("评价文字不能超过500字");
|
|
|
+ }
|
|
|
+ List<String> images = request.getImages();
|
|
|
+ if (images != null && images.size() > 3) {
|
|
|
+ throw new RuntimeException("晒图最多3张");
|
|
|
+ }
|
|
|
+
|
|
|
+ Long orderId = request.getOrderId();
|
|
|
+
|
|
|
+ // 1) 订单存在
|
|
|
+ ProductOrder order = productOrderMapper.selectById(orderId);
|
|
|
+ if (order == null) {
|
|
|
+ throw new RuntimeException("订单不存在");
|
|
|
+ }
|
|
|
+ // 2) 活动评价零污染(普通商品评价域,活动订单走 ReviewOrderService)
|
|
|
+ if ("activity".equals(order.getProductType())) {
|
|
|
+ throw new RuntimeException("活动订单不支持商品评价");
|
|
|
+ }
|
|
|
+ // 3) 状态 = completed
|
|
|
+ if (!"completed".equals(order.getStatus())) {
|
|
|
+ throw new RuntimeException("订单未完成,暂不可评价");
|
|
|
+ }
|
|
|
+ // 4) 评价人为下单人本人
|
|
|
+ if (order.getBuyerId() == null || !order.getBuyerId().equals(reviewerId)) {
|
|
|
+ throw new RuntimeException("只能评价本人订单");
|
|
|
+ }
|
|
|
+ // 5) 90 天窗口:confirmTime >= now - 90d
|
|
|
+ Date confirmTime = order.getConfirmTime();
|
|
|
+ if (confirmTime == null) {
|
|
|
+ throw new RuntimeException("订单缺少确认收货时间,暂不可评价");
|
|
|
+ }
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -REVIEW_WINDOW_DAYS);
|
|
|
+ if (confirmTime.before(cal.getTime())) {
|
|
|
+ throw new RuntimeException("评价窗口已过期(确认收货后90天内可评价)");
|
|
|
+ }
|
|
|
+ // 6) 一单一评幂等(findByOrderId 查重,唯一键 uk_order_id 兜底)
|
|
|
+ ProductReview existing = reviewMapper.findByOrderId(orderId);
|
|
|
+ if (existing != null) {
|
|
|
+ throw new RuntimeException("该订单已评价");
|
|
|
+ }
|
|
|
+
|
|
|
+ Date now = new Date();
|
|
|
+ ProductReview review = new ProductReview();
|
|
|
+ review.setOrderId(orderId);
|
|
|
+ review.setProductId(order.getProductId());
|
|
|
+ review.setReviewerId(reviewerId);
|
|
|
+ review.setRating(request.getRating());
|
|
|
+ review.setReviewText(request.getReviewText() != null ? request.getReviewText() : "");
|
|
|
+ review.setImages(images != null && !images.isEmpty() ? JSON.toJSONString(images) : null);
|
|
|
+ review.setStatus(1);
|
|
|
+ review.setReviewAt(now);
|
|
|
+ review.setCreatedAt(now);
|
|
|
+ review.setUpdatedAt(now);
|
|
|
+ reviewMapper.insert(review);
|
|
|
+ return "评价提交成功";
|
|
|
+ }
|
|
|
+}
|