diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java new file mode 100644 index 0000000..ce36186 --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -0,0 +1,176 @@ +package com.ycwl.basic.controller.mobile; + +import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.utils.ApiResponse; +import com.ycwl.basic.pricing.dto.*; +import com.ycwl.basic.pricing.service.IPriceCalculationService; +import com.ycwl.basic.service.pc.FaceService; +import com.ycwl.basic.service.PriceCacheService; +import com.ycwl.basic.model.pc.face.resp.FaceRespVO; +import com.ycwl.basic.dto.MobileOrderRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; + +/** + * 移动端订单控制器V2 + * 包含价格查询和订单管理功能 + */ +@Slf4j +@RestController +@RequestMapping("/api/mobile/order/v2") +@RequiredArgsConstructor +public class AppOrderV2Controller { + + private final IPriceCalculationService priceCalculationService; + private final FaceService faceService; + private final PriceCacheService priceCacheService; + + /** + * 移动端价格计算 + * 包含权限验证:验证人脸所属景区与当前用户匹配 + * 集成Redis缓存机制,提升查询性能 + */ + @PostMapping("/calculate") + public ApiResponse calculatePrice(@RequestBody MobilePriceCalculationRequest request) { + // 获取当前登录用户ID + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("移动端价格计算:用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + log.info("移动端价格计算请求: userId={}, faceId={}, products={}", + currentUserId, request.getFaceId(), request.getProducts().size()); + + // 验证faceId参数 + if (request.getFaceId() == null) { + log.warn("移动端价格计算:faceId参数缺失"); + return ApiResponse.fail("faceId参数不能为空"); + } + + // 查询人脸信息进行权限验证 + ApiResponse faceResponse = faceService.getById(request.getFaceId()); + if (!faceResponse.isSuccess() || faceResponse.getData() == null) { + log.warn("移动端价格计算:人脸信息不存在, faceId={}", request.getFaceId()); + return ApiResponse.fail("人脸信息不存在"); + } + + FaceRespVO face = faceResponse.getData(); + Long scenicId = face.getScenicId(); + + // 先尝试从Redis缓存获取价格计算结果 + PriceCalculationResult cachedResult = priceCacheService.getCachedPriceResult( + currentUserId, scenicId, request.getProducts()); + + if (cachedResult != null) { + log.info("命中价格缓存: userId={}, scenicId={}, finalAmount={}", + currentUserId, scenicId, cachedResult.getFinalAmount()); + return ApiResponse.success(cachedResult); + } + + // 转换为标准价格计算请求 + PriceCalculationRequest standardRequest = request.toStandardRequest(currentUserId, scenicId); + + // 执行价格计算 + PriceCalculationResult result = priceCalculationService.calculatePrice(standardRequest); + + // 将计算结果缓存到Redis + String cacheKey = priceCacheService.cachePriceResult(currentUserId, scenicId, request.getProducts(), result); + + log.info("移动端价格计算完成: userId={}, scenicId={}, originalAmount={}, finalAmount={}, cacheKey={}", + currentUserId, scenicId, result.getOriginalAmount(), result.getFinalAmount(), cacheKey); + + return ApiResponse.success(result); + } + + /** + * 移动端下单接口 + * 验证价格缓存有效性,确保5分钟内使用缓存价格下单 + */ + @PostMapping("/add-order") + public ApiResponse addOrder(@RequestBody MobileOrderRequest request) { + // 获取当前登录用户ID + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("移动端下单:用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + log.info("移动端下单请求: userId={}, faceId={}, products={}, expectedFinalAmount={}", + currentUserId, request.getFaceId(), request.getProducts().size(), request.getExpectedFinalAmount()); + + // 验证必填参数 + if (request.getFaceId() == null) { + log.warn("移动端下单:faceId参数缺失"); + return ApiResponse.fail("faceId参数不能为空"); + } + + if (request.getProducts() == null || request.getProducts().isEmpty()) { + log.warn("移动端下单:商品列表为空"); + return ApiResponse.fail("商品列表不能为空"); + } + + if (request.getExpectedFinalAmount() == null) { + log.warn("移动端下单:预期价格参数缺失"); + return ApiResponse.fail("预期价格不能为空"); + } + + // 查询人脸信息进行权限验证 + ApiResponse faceResponse = faceService.getById(request.getFaceId()); + if (!faceResponse.isSuccess() || faceResponse.getData() == null) { + log.warn("移动端下单:人脸信息不存在, faceId={}", request.getFaceId()); + return ApiResponse.fail("人脸信息不存在"); + } + + FaceRespVO face = faceResponse.getData(); + Long scenicId = face.getScenicId(); + + // 验证并消费价格缓存(一次性使用) + PriceCalculationResult cachedResult = priceCacheService.validateAndConsumePriceCache( + currentUserId, scenicId, request.getProducts()); + + if (cachedResult == null) { + log.warn("移动端下单:价格缓存已过期或不存在, userId={}, scenicId={}", currentUserId, scenicId); + return ApiResponse.fail("价格信息已过期,请重新查询价格后再下单"); + } + + // 验证价格是否匹配 + if (cachedResult.getFinalAmount().compareTo(request.getExpectedFinalAmount()) != 0) { + log.warn("移动端下单:价格不匹配, cached={}, expected={}, userId={}, scenicId={}", + cachedResult.getFinalAmount(), request.getExpectedFinalAmount(), currentUserId, scenicId); + return ApiResponse.fail("价格信息不匹配,请重新查询价格后再下单"); + } + + // 验证原价是否匹配(可选) + if (request.getExpectedOriginalAmount() != null && + cachedResult.getOriginalAmount().compareTo(request.getExpectedOriginalAmount()) != 0) { + log.warn("移动端下单:原价不匹配, cached={}, expected={}, userId={}, scenicId={}", + cachedResult.getOriginalAmount(), request.getExpectedOriginalAmount(), currentUserId, scenicId); + return ApiResponse.fail("原价信息不匹配,请重新查询价格后再下单"); + } + + log.info("价格缓存验证通过: userId={}, scenicId={}, finalAmount={}", + currentUserId, scenicId, cachedResult.getFinalAmount()); + + // TODO: 实现订单创建逻辑 + // 1. 生成订单号 + // 2. 创建订单基础信息 + // 3. 保存订单商品明细 + // 4. 记录使用的优惠券和券码信息 + // 5. 更新优惠券和券码状态 + // 6. 初始化订单状态为待支付 + // 7. 发送订单创建成功通知 + + String orderId = "ORDER_" + System.currentTimeMillis(); // 临时订单号生成逻辑 + + log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", + orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + + return ApiResponse.success(orderId); + } +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java b/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java new file mode 100644 index 0000000..d7e49dd --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java @@ -0,0 +1,62 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 移动端价格计算请求DTO + */ +@Data +public class MobilePriceCalculationRequest { + + /** + * 商品列表 + */ + private List products; + + /** + * 人脸ID(必填,用于权限验证) + */ + private Long faceId; + + /** + * 是否自动使用优惠券 + */ + private Boolean autoUseCoupon = true; + + /** + * 用户输入的券码 + */ + private String voucherCode; + + /** + * 是否自动使用券码优惠 + */ + private Boolean autoUseVoucher = true; + + /** + * 是否仅预览优惠(不实际使用) + */ + private Boolean previewOnly = false; + + /** + * 转换为标准价格计算请求 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @return 标准价格计算请求 + */ + public PriceCalculationRequest toStandardRequest(Long userId, Long scenicId) { + PriceCalculationRequest request = new PriceCalculationRequest(); + request.setProducts(this.products); + request.setUserId(userId); + request.setScenicId(scenicId); + request.setFaceId(this.faceId); + request.setAutoUseCoupon(this.autoUseCoupon); + request.setVoucherCode(this.voucherCode); + request.setAutoUseVoucher(this.autoUseVoucher); + request.setPreviewOnly(this.previewOnly); + return request; + } +} \ No newline at end of file