You've already forked FrameTour-BE
Compare commits
44 Commits
c03678bd93
...
cdd434317f
Author | SHA1 | Date | |
---|---|---|---|
cdd434317f | |||
f91b98c68e | |||
a9d64402f2 | |||
ada7158a48 | |||
047feec045 | |||
966568156c | |||
57b087a4fb | |||
607c5bc057 | |||
fc8818a595 | |||
b1deabc7c1 | |||
1ac375e491 | |||
60af636639 | |||
792deb5c4d | |||
93a424058a | |||
98ae9f2930 | |||
e2b760caab | |||
5a66856e72 | |||
bc2b2fb10f | |||
4dac46bb46 | |||
3fbfb7df54 | |||
346c484cbc | |||
e95e0a04ff | |||
af79a5ffa6 | |||
5c2629237e | |||
798ff3b9b5 | |||
46fb255e66 | |||
f451b835b9 | |||
c7d5399931 | |||
5bb2bc1ac3 | |||
95d8b742ee | |||
ff320ba3e8 | |||
98bbaccb3a | |||
f2ac6aaea0 | |||
21f76ff9c5 | |||
7d40b8043d | |||
b67fb87989 | |||
42e7b7da95 | |||
6bc94a65a6 | |||
7c2db2ad22 | |||
f6bd7e48a3 | |||
f0aeb27566 | |||
5871beb84e | |||
291b3d620f | |||
32feaa9692 |
@@ -24,6 +24,11 @@ import com.ycwl.basic.model.pc.task.entity.TaskEntity;
|
||||
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
||||
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
|
||||
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
|
||||
import com.ycwl.basic.pricing.dto.PriceCalculationRequest;
|
||||
import com.ycwl.basic.pricing.dto.PriceCalculationResult;
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.service.IPriceCalculationService;
|
||||
import com.ycwl.basic.profitsharing.biz.ProfitSharingBiz;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.repository.OrderRepository;
|
||||
@@ -40,6 +45,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -79,6 +85,8 @@ public class OrderBiz {
|
||||
@Autowired
|
||||
@Lazy
|
||||
private PrinterService printerService;
|
||||
@Autowired
|
||||
private IPriceCalculationService iPriceCalculationService;
|
||||
|
||||
public PriceObj queryPrice(Long scenicId, int goodsType, Long goodsId) {
|
||||
PriceObj priceObj = new PriceObj();
|
||||
@@ -86,9 +94,8 @@ public class OrderBiz {
|
||||
priceObj.setGoodsId(goodsId);
|
||||
ScenicEntity scenic = scenicRepository.getScenic(scenicId);
|
||||
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
|
||||
priceObj.setScenicAllPrice(scenic.getPrice());
|
||||
if (scenicConfig != null) {
|
||||
if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) {
|
||||
if (Boolean.TRUE.equals(scenicConfig.getAllFree())) {
|
||||
// 景区全免
|
||||
priceObj.setFree(true);
|
||||
priceObj.setPrice(BigDecimal.ZERO);
|
||||
@@ -120,13 +127,20 @@ public class OrderBiz {
|
||||
priceObj.setScenicId(video.getScenicId());
|
||||
break;
|
||||
case 1: // source
|
||||
priceObj.setPrice(scenic.getSourceVideoPrice());
|
||||
priceObj.setSlashPrice(scenic.getSourceVideoPrice());
|
||||
priceObj.setFaceId(goodsId);
|
||||
break;
|
||||
case 2: // source
|
||||
priceObj.setPrice(scenic.getSourceImagePrice());
|
||||
priceObj.setSlashPrice(scenic.getSourceImagePrice());
|
||||
FaceEntity face = faceRepository.getFace(goodsId);
|
||||
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
|
||||
ProductItem productItem = new ProductItem();
|
||||
productItem.setProductType(goodsType == 1 ? ProductType.RECORDING_SET : ProductType.PHOTO_SET);
|
||||
productItem.setProductId(scenic.getId().toString());
|
||||
productItem.setPurchaseCount(1);
|
||||
productItem.setScenicId(scenic.getId().toString());
|
||||
calculationRequest.setProducts(Collections.singletonList(productItem));
|
||||
calculationRequest.setUserId(face.getMemberId());
|
||||
calculationRequest.setFaceId(face.getId());
|
||||
PriceCalculationResult priceCalculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
|
||||
priceObj.setPrice(priceCalculationResult.getFinalAmount());
|
||||
priceObj.setSlashPrice(priceCalculationResult.getOriginalAmount());
|
||||
priceObj.setFaceId(goodsId);
|
||||
break;
|
||||
}
|
||||
|
@@ -50,10 +50,10 @@ public class PriceBiz {
|
||||
}).forEach(goodsList::add);
|
||||
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
|
||||
if (scenicConfig != null) {
|
||||
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) {
|
||||
if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) {
|
||||
goodsList.add(new GoodsListRespVO(1L, "录像集"));
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) {
|
||||
if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) {
|
||||
goodsList.add(new GoodsListRespVO(2L, "照片集"));
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class PriceBiz {
|
||||
}
|
||||
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
|
||||
if (scenicConfig != null) {
|
||||
if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) {
|
||||
if (Boolean.TRUE.equals(scenicConfig.getAllFree())) {
|
||||
// 景区全免
|
||||
respVO.setFree(true);
|
||||
respVO.setSlashPrice(BigDecimal.ZERO);
|
||||
|
@@ -1,63 +0,0 @@
|
||||
package com.ycwl.basic.config;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class FeignConfig {
|
||||
|
||||
@Bean
|
||||
public Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.BASIC;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor() {
|
||||
return requestTemplate -> {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
|
||||
// 传递认证头
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (authorization != null) {
|
||||
requestTemplate.header("Authorization", authorization);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ErrorDecoder errorDecoder() {
|
||||
return new FeignErrorDecoder();
|
||||
}
|
||||
|
||||
public static class FeignErrorDecoder implements ErrorDecoder {
|
||||
private final ErrorDecoder defaultErrorDecoder = new Default();
|
||||
|
||||
@Override
|
||||
public Exception decode(String methodKey, feign.Response response) {
|
||||
log.error("Feign调用失败: method={}, status={}, reason={}",
|
||||
methodKey, response.status(), response.reason());
|
||||
|
||||
if (response.status() >= 400 && response.status() < 500) {
|
||||
// 4xx错误,客户端错误
|
||||
return new RuntimeException("客户端请求错误: " + response.reason());
|
||||
} else if (response.status() >= 500) {
|
||||
// 5xx错误,服务器错误
|
||||
return new RuntimeException("服务器内部错误: " + response.reason());
|
||||
}
|
||||
|
||||
return defaultErrorDecoder.decode(methodKey, response);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package com.ycwl.basic.controller.mobile;
|
||||
|
||||
import com.ycwl.basic.model.mobile.claim.ClaimReq;
|
||||
import com.ycwl.basic.model.mobile.claim.ClaimResp;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.pricing.dto.CouponClaimRequest;
|
||||
import com.ycwl.basic.pricing.dto.CouponClaimResult;
|
||||
import com.ycwl.basic.pricing.dto.req.VoucherPrintReq;
|
||||
import com.ycwl.basic.pricing.dto.resp.VoucherPrintResp;
|
||||
import com.ycwl.basic.pricing.service.ICouponService;
|
||||
import com.ycwl.basic.pricing.service.VoucherPrintService;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.util.ScenicConfigManager;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/mobile/claim/v1")
|
||||
@AllArgsConstructor
|
||||
public class AppClaimController {
|
||||
private final FaceRepository faceRepository;
|
||||
private final ScenicRepository scenicRepository;
|
||||
private final VoucherPrintService voucherPrintService;
|
||||
private final ICouponService couponService;
|
||||
|
||||
|
||||
@PostMapping("tryClaim")
|
||||
public ApiResponse<ClaimResp> tryClaim(@RequestBody ClaimReq req) {
|
||||
FaceEntity face = faceRepository.getFace(req.getFaceId());
|
||||
if (face == null) {
|
||||
return ApiResponse.fail("请选择人脸");
|
||||
}
|
||||
ClaimResp claimResp = new ClaimResp();
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||
if (Boolean.TRUE.equals(scenicConfig.getBoolean("voucher_enable"))) {
|
||||
// 可以领券
|
||||
VoucherPrintResp voucherPrintResp = voucherPrintService.queryPrintedVoucher(face.getId());
|
||||
if (voucherPrintResp == null) {
|
||||
// 打印
|
||||
if (req.getMorphId() != null) {
|
||||
voucherPrintResp = voucherPrintService.printVoucherTicket(new VoucherPrintReq(face.getId(), req.getMorphId(), face.getScenicId()));
|
||||
}
|
||||
}
|
||||
if (voucherPrintResp != null) {
|
||||
claimResp.setHasCoupon(false);
|
||||
claimResp.setHasPrint(true);
|
||||
claimResp.setPrintCode(voucherPrintResp.getCode());
|
||||
claimResp.setPrintType(voucherPrintResp.getType());
|
||||
return ApiResponse.success(claimResp);
|
||||
}
|
||||
}
|
||||
if (Boolean.TRUE.equals(scenicConfig.getBoolean("booking_enable"))) {
|
||||
VoucherPrintResp voucherPrintResp = voucherPrintService.queryPrintedVoucher(face.getId());
|
||||
if (voucherPrintResp == null) {
|
||||
// 打印
|
||||
if (req.getMorphId() != null) {
|
||||
voucherPrintResp = voucherPrintService.printBookingTicket(new VoucherPrintReq(face.getId(), req.getMorphId(), face.getScenicId()));
|
||||
}
|
||||
}
|
||||
if (voucherPrintResp != null) {
|
||||
claimResp.setHasCoupon(false);
|
||||
claimResp.setHasPrint(true);
|
||||
claimResp.setPrintCode(voucherPrintResp.getCode());
|
||||
claimResp.setPrintType(voucherPrintResp.getType());
|
||||
return ApiResponse.success(claimResp);
|
||||
}
|
||||
}
|
||||
if (req.getType() != null) {
|
||||
// 第几次进入
|
||||
Integer couponId = scenicConfig.getInteger("coupon_id_for_type_" + req.getType());
|
||||
if (couponId != null) {
|
||||
// 可以领券
|
||||
CouponClaimRequest request = new CouponClaimRequest(face.getMemberId(), Long.valueOf(couponId));
|
||||
CouponClaimResult claimResult = couponService.claimCoupon(request);
|
||||
if (claimResult.isSuccess()) {
|
||||
// 领到了
|
||||
claimResp.setHasCoupon(true);
|
||||
claimResp.setCouponDesc(scenicConfig.getString("coupon_desc_for_type_" + req.getType(), "专属折扣券"));
|
||||
claimResp.setCouponCountdown(scenicConfig.getString("coupon_countdown_for_type_" + req.getType(), "送你优惠,保存美好!"));
|
||||
return ApiResponse.success(claimResp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ApiResponse.fail("异常");
|
||||
}
|
||||
}
|
@@ -0,0 +1,382 @@
|
||||
package com.ycwl.basic.controller.mobile;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.constant.BaseContextHandler;
|
||||
import com.ycwl.basic.mapper.SourceMapper;
|
||||
import com.ycwl.basic.mapper.VideoMapper;
|
||||
import com.ycwl.basic.model.pc.order.req.CreateOrderReqVO;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
|
||||
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
|
||||
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.repository.TemplateRepository;
|
||||
import com.ycwl.basic.repository.VideoTaskRepository;
|
||||
import com.ycwl.basic.service.pc.OrderService;
|
||||
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 com.ycwl.basic.order.service.IOrderService;
|
||||
import com.ycwl.basic.order.dto.OrderV2DetailResponse;
|
||||
import com.ycwl.basic.order.dto.OrderV2ListResponse;
|
||||
import com.ycwl.basic.order.dto.OrderV2PageRequest;
|
||||
import com.ycwl.basic.order.dto.PaymentParamsRequest;
|
||||
import com.ycwl.basic.order.dto.PaymentParamsResponse;
|
||||
import com.ycwl.basic.order.dto.PaymentCallbackResponse;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 移动端订单控制器V2
|
||||
* 包含价格查询和订单管理功能
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/mobile/order/v2")
|
||||
@RequiredArgsConstructor
|
||||
public class AppOrderV2Controller {
|
||||
|
||||
private final IPriceCalculationService priceCalculationService;
|
||||
private final FaceService faceService;
|
||||
private final PriceCacheService priceCacheService;
|
||||
private final IOrderService orderService;
|
||||
private final OrderService oldOrderService;
|
||||
private final SourceMapper sourceMapper;
|
||||
private final VideoMapper videoMapper;
|
||||
private final VideoTaskRepository videoTaskRepository;
|
||||
private final TemplateRepository templateRepository;
|
||||
|
||||
/**
|
||||
* 移动端价格计算
|
||||
* 包含权限验证:验证人脸所属景区与当前用户匹配
|
||||
* 集成Redis缓存机制,提升查询性能
|
||||
*/
|
||||
@PostMapping("/calculate")
|
||||
public ApiResponse<PriceCalculationResult> 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<FaceRespVO> 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);
|
||||
}
|
||||
|
||||
request.getProducts().forEach(product -> {
|
||||
switch (product.getProductType()) {
|
||||
case VLOG_VIDEO:
|
||||
AtomicInteger deviceCount = new AtomicInteger();
|
||||
List<MemberVideoEntity> videoEntities = videoMapper.listRelationByFaceAndTemplate(face.getId(), Long.valueOf(product.getProductId()));
|
||||
if (videoEntities != null && !videoEntities.isEmpty()) {
|
||||
TaskEntity task = videoTaskRepository.getTaskById(videoEntities.getFirst().getTaskId());
|
||||
if (task != null) {
|
||||
Map<String, Object> paramJson = JacksonUtil.parseObject(task.getTaskParams(), Map.class);
|
||||
if (paramJson == null) {
|
||||
deviceCount.set(1);
|
||||
} else {
|
||||
List<String> templatePlaceholder = templateRepository.getTemplatePlaceholder(task.getTemplateId());
|
||||
paramJson.entrySet().stream()
|
||||
.filter(entry -> StringUtils.isNumeric(entry.getKey()))
|
||||
.forEach(entry -> {
|
||||
List<Object> jsonArray = JacksonUtil.parseArray(JacksonUtil.toJSONString(entry.getValue()), Object.class);
|
||||
if (jsonArray != null && !jsonArray.isEmpty()) {
|
||||
for (Object ignored : jsonArray) {
|
||||
if (templatePlaceholder.contains(entry.getKey())) {
|
||||
deviceCount.getAndIncrement();
|
||||
templatePlaceholder.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
product.setQuantity(deviceCount.get());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RECORDING_SET:
|
||||
case PHOTO_SET:
|
||||
SourceReqQuery sourceReqQuery = new SourceReqQuery();
|
||||
sourceReqQuery.setMemberId(currentUserId);
|
||||
sourceReqQuery.setType(product.getProductType() == ProductType.RECORDING_SET ? 1 : 2);
|
||||
sourceReqQuery.setFaceId(face.getId());
|
||||
Integer count = sourceMapper.countUser(sourceReqQuery);
|
||||
product.setQuantity(count);
|
||||
break;
|
||||
default:
|
||||
log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType());
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// 转换为标准价格计算请求
|
||||
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")
|
||||
public ApiResponse<String> 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<FaceRespVO> 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());
|
||||
|
||||
// 使用旧版创建订单逻辑
|
||||
try {
|
||||
Long orderId = oldOrderService.createOrderCompact(currentUserId, request, cachedResult);
|
||||
return ApiResponse.success(String.valueOf(orderId));
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.fail("订单创建失败,请稍后重试");
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
// try {
|
||||
// Long orderId = orderService.createOrder(request, currentUserId, scenicId, cachedResult);
|
||||
//
|
||||
// log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}",
|
||||
// orderId, currentUserId, scenicId, cachedResult.getFinalAmount());
|
||||
//
|
||||
// return ApiResponse.success(orderId.toString());
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// log.error("订单创建失败: userId={}, scenicId={}, error={}", currentUserId, scenicId, e.getMessage(), e);
|
||||
// return ApiResponse.fail("订单创建失败,请稍后重试");
|
||||
// }
|
||||
}
|
||||
|
||||
// ====== 新增移动端订单查询功能 ======
|
||||
|
||||
/**
|
||||
* 用户分页查询自己的订单列表
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<PageInfo<OrderV2ListResponse>> pageUserOrders(@RequestBody OrderV2PageRequest request) {
|
||||
String currentUserIdStr = BaseContextHandler.getUserId();
|
||||
if (currentUserIdStr == null) {
|
||||
log.warn("用户未登录");
|
||||
return ApiResponse.fail("用户未登录");
|
||||
}
|
||||
|
||||
Long currentUserId = Long.valueOf(currentUserIdStr);
|
||||
request.setMemberId(currentUserId); // 设置当前用户ID,确保只查询自己的订单
|
||||
|
||||
log.info("用户查询订单列表: userId={}, request={}", currentUserId, request);
|
||||
|
||||
try {
|
||||
PageInfo<OrderV2ListResponse> pageInfo = orderService.pageOrdersByUser(request);
|
||||
return ApiResponse.success(pageInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户订单列表失败: userId={}", currentUserId, e);
|
||||
return ApiResponse.fail("查询失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户查询自己的订单详情
|
||||
*/
|
||||
@GetMapping("/detail/{orderId}")
|
||||
public ApiResponse<OrderV2DetailResponse> getUserOrderDetail(@PathVariable("orderId") Long orderId) {
|
||||
String currentUserIdStr = BaseContextHandler.getUserId();
|
||||
if (currentUserIdStr == null) {
|
||||
log.warn("用户未登录");
|
||||
return ApiResponse.fail("用户未登录");
|
||||
}
|
||||
|
||||
Long currentUserId = Long.valueOf(currentUserIdStr);
|
||||
|
||||
log.info("用户查询订单详情: userId={}, orderId={}", currentUserId, orderId);
|
||||
|
||||
try {
|
||||
OrderV2DetailResponse detail = orderService.getOrderDetail(orderId);
|
||||
if (detail == null) {
|
||||
return ApiResponse.fail("订单不存在");
|
||||
}
|
||||
|
||||
// 验证订单是否属于当前用户
|
||||
if (!currentUserId.equals(detail.getMemberId())) {
|
||||
log.warn("用户尝试访问他人订单: userId={}, orderId={}, orderOwner={}",
|
||||
currentUserId, orderId, detail.getMemberId());
|
||||
return ApiResponse.fail("无权访问该订单");
|
||||
}
|
||||
|
||||
return ApiResponse.success(detail);
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户订单详情失败: userId={}, orderId={}", currentUserId, orderId, e);
|
||||
return ApiResponse.fail("查询失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 支付相关接口 ======
|
||||
|
||||
/**
|
||||
* 获取订单支付参数
|
||||
* 用于小程序调起支付
|
||||
*/
|
||||
@PostMapping("/{orderId}/payment-params")
|
||||
public ApiResponse<PaymentParamsResponse> getPaymentParams(
|
||||
@PathVariable("orderId") Long orderId,
|
||||
@RequestBody PaymentParamsRequest request) {
|
||||
|
||||
String currentUserIdStr = BaseContextHandler.getUserId();
|
||||
if (currentUserIdStr == null) {
|
||||
log.warn("用户未登录");
|
||||
return ApiResponse.fail("用户未登录");
|
||||
}
|
||||
|
||||
Long currentUserId = Long.valueOf(currentUserIdStr);
|
||||
|
||||
log.info("获取支付参数: userId={}, orderId={}", currentUserId, orderId);
|
||||
|
||||
return oldOrderService.getPaymentParams(orderId, currentUserId, request);
|
||||
|
||||
//
|
||||
// try {
|
||||
// PaymentParamsResponse response = orderService.getPaymentParams(orderId, currentUserId, request);
|
||||
// return ApiResponse.success(response);
|
||||
// } catch (Exception e) {
|
||||
// log.error("获取支付参数失败: userId={}, orderId={}", currentUserId, orderId, e);
|
||||
// return ApiResponse.fail(e.getMessage());
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付回调处理接口
|
||||
* 供第三方支付平台回调使用
|
||||
*/
|
||||
@PostMapping("/payment/callback/{scenicId}")
|
||||
public String handlePaymentCallback(
|
||||
@PathVariable("scenicId") Long scenicId,
|
||||
HttpServletRequest request) {
|
||||
|
||||
log.info("接收支付回调: scenicId={}", scenicId);
|
||||
|
||||
try {
|
||||
PaymentCallbackResponse response = orderService.handlePaymentCallback(scenicId, request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
log.info("支付回调处理成功: scenicId={}, orderId={}, statusChangeType={}",
|
||||
scenicId, response.getOrderId(), response.getStatusChangeType());
|
||||
return "SUCCESS"; // 返回给第三方支付平台的成功标识
|
||||
} else {
|
||||
log.error("支付回调处理失败: scenicId={}, message={}", scenicId, response.getMessage());
|
||||
return "FAIL"; // 返回给第三方支付平台的失败标识
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("支付回调异常: scenicId={}", scenicId, e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,12 +3,12 @@ package com.ycwl.basic.controller.mobile;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.annotation.IgnoreToken;
|
||||
import com.ycwl.basic.constant.BaseContextHandler;
|
||||
import com.ycwl.basic.model.jwt.JwtInfo;
|
||||
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
|
||||
import com.ycwl.basic.model.pc.scenic.resp.ScenicConfigResp;
|
||||
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
|
||||
@@ -16,11 +16,14 @@ import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.service.mobile.AppScenicService;
|
||||
import com.ycwl.basic.service.pc.FaceService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import com.ycwl.basic.utils.JwtTokenUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -49,7 +52,7 @@ public class AppScenicController {
|
||||
|
||||
// 分页查询景区列表
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<PageInfo<ScenicAppVO>> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery){
|
||||
public ApiResponse<PageInfo<ScenicEntity>> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery){
|
||||
String userId = BaseContextHandler.getUserId();
|
||||
if (ENABLED_USER_IDs.contains(userId)) {
|
||||
return appScenicService.pageQuery(scenicReqQuery);
|
||||
|
@@ -2,6 +2,7 @@ package com.ycwl.basic.controller.mobile.manage;
|
||||
|
||||
import com.ycwl.basic.annotation.IgnoreToken;
|
||||
import com.ycwl.basic.constant.BaseContextHandler;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginOldRespVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq;
|
||||
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO;
|
||||
@@ -12,6 +13,7 @@ import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
|
||||
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.service.mobile.AppScenicService;
|
||||
import com.ycwl.basic.service.pc.ScenicAccountService;
|
||||
import com.ycwl.basic.service.pc.ScenicService;
|
||||
@@ -25,10 +27,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN;
|
||||
import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
|
||||
|
||||
/**
|
||||
@@ -45,6 +47,8 @@ public class AppScenicAccountController {
|
||||
private AppScenicService scenicService;
|
||||
@Autowired
|
||||
private ScenicService adminScenicService;
|
||||
@Autowired
|
||||
private ScenicRepository scenicRepository;
|
||||
|
||||
// 登录
|
||||
@PostMapping("/login")
|
||||
@@ -73,8 +77,8 @@ public class AppScenicAccountController {
|
||||
}
|
||||
|
||||
@GetMapping("/myScenicList")
|
||||
public ApiResponse<List<ScenicRespVO>> myScenicList() {
|
||||
List<ScenicRespVO> list;
|
||||
public ApiResponse<List<ScenicV2DTO>> myScenicList() {
|
||||
List<ScenicV2DTO> list = Collections.emptyList();
|
||||
if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
|
||||
String userId = BaseContextHandler.getUserId();
|
||||
ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId));
|
||||
@@ -82,10 +86,12 @@ public class AppScenicAccountController {
|
||||
return ApiResponse.fail("景区账号未绑定景区");
|
||||
}
|
||||
list = account.getScenicId().stream()
|
||||
.map(id -> scenicService.getDetails(id).getData())
|
||||
.map(id -> scenicRepository.getScenicBasic(id))
|
||||
.toList();
|
||||
} else {
|
||||
list = adminScenicService.list(new ScenicReqQuery()).getData();
|
||||
} else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) {
|
||||
ScenicReqQuery query = new ScenicReqQuery();
|
||||
query.setPageSize(1000);
|
||||
list = scenicRepository.list(query);
|
||||
}
|
||||
return ApiResponse.success(list);
|
||||
}
|
||||
|
@@ -0,0 +1,100 @@
|
||||
package com.ycwl.basic.controller.pc;
|
||||
|
||||
import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.service.DefaultConfigIntegrationService;
|
||||
import com.ycwl.basic.utils.ApiConst;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认配置管理控制器
|
||||
* 提供默认配置的增删查改功能
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/default-config")
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultConfigController {
|
||||
|
||||
private final DefaultConfigIntegrationService defaultConfigIntegrationService;
|
||||
|
||||
/**
|
||||
* 获取默认配置列表
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public ApiResponse<List<DefaultConfigDTO>> listDefaultConfigs() {
|
||||
log.info("获取默认配置列表");
|
||||
try {
|
||||
List<DefaultConfigDTO> configs = defaultConfigIntegrationService.listDefaultConfigs();
|
||||
return ApiResponse.success(configs);
|
||||
} catch (Exception e) {
|
||||
log.error("获取默认配置列表失败", e);
|
||||
return ApiResponse.fail("获取默认配置列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置键获取默认配置
|
||||
*/
|
||||
@GetMapping("/{configKey}")
|
||||
public ApiResponse<DefaultConfigDTO> getDefaultConfig(@PathVariable String configKey) {
|
||||
log.info("获取默认配置, configKey: {}", configKey);
|
||||
try {
|
||||
DefaultConfigDTO config = defaultConfigIntegrationService.getDefaultConfig(configKey);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("获取默认配置失败, configKey: {}", configKey, e);
|
||||
return ApiResponse.fail("获取默认配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*/
|
||||
@PostMapping("/")
|
||||
public ApiResponse<DefaultConfigDTO> createDefaultConfig(@RequestBody DefaultConfigDTO request) {
|
||||
log.info("创建默认配置, configKey: {}", request.getConfigKey());
|
||||
try {
|
||||
DefaultConfigDTO config = defaultConfigIntegrationService.createDefaultConfig(request);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("创建默认配置失败, configKey: {}", request.getConfigKey(), e);
|
||||
return ApiResponse.fail("创建默认配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新默认配置
|
||||
*/
|
||||
@PutMapping("/{configKey}")
|
||||
public ApiResponse<DefaultConfigDTO> updateDefaultConfig(@PathVariable String configKey,
|
||||
@RequestBody DefaultConfigDTO request) {
|
||||
log.info("更新默认配置, configKey: {}", configKey);
|
||||
try {
|
||||
DefaultConfigDTO config = defaultConfigIntegrationService.updateDefaultConfig(configKey, request);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("更新默认配置失败, configKey: {}", configKey, e);
|
||||
return ApiResponse.fail("更新默认配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除默认配置
|
||||
*/
|
||||
@DeleteMapping("/{configKey}")
|
||||
public ApiResponse<Void> deleteDefaultConfig(@PathVariable String configKey) {
|
||||
log.info("删除默认配置, configKey: {}", configKey);
|
||||
try {
|
||||
defaultConfigIntegrationService.deleteDefaultConfig(configKey);
|
||||
return ApiResponse.buildResponse(ApiConst.Code.CODE_SUCCESS.code(), null, "删除成功");
|
||||
} catch (Exception e) {
|
||||
log.error("删除默认配置失败, configKey: {}", configKey, e);
|
||||
return ApiResponse.fail("删除默认配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
package com.ycwl.basic.controller.pc;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.constant.BaseContextHandler;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
|
||||
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
|
||||
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.service.mobile.AppScenicService;
|
||||
import com.ycwl.basic.service.mobile.AppStatisticsService;
|
||||
import com.ycwl.basic.service.pc.ScenicAccountService;
|
||||
@@ -17,16 +16,20 @@ import com.ycwl.basic.storage.adapters.IStorageAdapter;
|
||||
import com.ycwl.basic.storage.enums.StorageAcl;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import com.ycwl.basic.utils.WxMpUtil;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN;
|
||||
import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
|
||||
|
||||
/**
|
||||
@@ -50,68 +53,6 @@ public class ScenicController {
|
||||
@Autowired
|
||||
private ScenicAccountService accountService;
|
||||
|
||||
// 分页查询景区
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<PageInfo<ScenicRespVO>> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery) {
|
||||
return scenicService.pageQuery(scenicReqQuery);
|
||||
}
|
||||
// 查询景区列表
|
||||
@PostMapping("/list")
|
||||
public ApiResponse<List<ScenicRespVO>> list(@RequestBody ScenicReqQuery scenicReqQuery) {
|
||||
return scenicService.list(scenicReqQuery);
|
||||
}
|
||||
// 查询景区详情
|
||||
@GetMapping("/getDetail/{id}")
|
||||
public ApiResponse<ScenicRespVO> getDetail(@PathVariable Long id) {
|
||||
return scenicService.getById(id);
|
||||
}
|
||||
// 新增景区
|
||||
@PostMapping("/add")
|
||||
public ApiResponse<Boolean> add(@RequestBody ScenicAddOrUpdateReq scenicAddReq) {
|
||||
return scenicService.add(scenicAddReq);
|
||||
}
|
||||
// 删除景区
|
||||
@GetMapping("/delete/{id}")
|
||||
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
||||
return scenicService.deleteById(id);
|
||||
}
|
||||
// 修改景区
|
||||
@PostMapping("/update")
|
||||
public ApiResponse<Boolean> update(@RequestBody ScenicAddOrUpdateReq scenicAddReq) {
|
||||
return scenicService.update(scenicAddReq);
|
||||
}
|
||||
// 修改景区状态
|
||||
@GetMapping("/updateStatus/{id}")
|
||||
public ApiResponse<Boolean> updateStatus(@PathVariable Long id) {
|
||||
return scenicService.updateStatus(id);
|
||||
}
|
||||
// 新增景区配置
|
||||
@PostMapping("/addConfig")
|
||||
public ApiResponse<Boolean> addConfig(@RequestBody ScenicConfigEntity scenicConfig) {
|
||||
return scenicService.addConfig(scenicConfig);
|
||||
}
|
||||
// 修改景区配置
|
||||
@PostMapping("/updateConfig")
|
||||
public ApiResponse<Boolean> updateConfig(@RequestBody ScenicConfigEntity scenicConfig) {
|
||||
return scenicService.updateConfigById(scenicConfig);
|
||||
}
|
||||
|
||||
// 查询景区配置
|
||||
@GetMapping("/config/{id}")
|
||||
public ApiResponse<ScenicConfigEntity> getConfig(@PathVariable("id") Long id) {
|
||||
return ApiResponse.success(scenicService.getConfig(id));
|
||||
}
|
||||
@PostMapping("/saveConfig/{id}")
|
||||
public ApiResponse saveConfig(@PathVariable("id") Long id, @RequestBody ScenicConfigEntity config) {
|
||||
scenicService.saveConfig(id, config);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
@PostMapping("/saveConfig/undefined")
|
||||
public ApiResponse saveConfig(@RequestBody ScenicConfigEntity config) {
|
||||
scenicService.addConfig(config);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
// 根据景区ID下载小程序二维码
|
||||
@GetMapping("/{id}/QRCode")
|
||||
public ApiResponse<String> downloadQrCode(@PathVariable Long id) {
|
||||
@@ -167,19 +108,19 @@ public class ScenicController {
|
||||
}
|
||||
|
||||
@GetMapping("/myScenicList")
|
||||
public ApiResponse<List<ScenicRespVO>> myScenicList() {
|
||||
List<ScenicRespVO> list = Collections.emptyList();
|
||||
public ApiResponse<List<ScenicV2DTO>> myScenicList() {
|
||||
List<ScenicV2DTO> list = Collections.emptyList();
|
||||
if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
|
||||
String userId = BaseContextHandler.getUserId();
|
||||
ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId));
|
||||
if (account == null || account.getScenicId().isEmpty()) {
|
||||
return ApiResponse.fail("景区账号未绑定景区");
|
||||
}
|
||||
list = account.getScenicId().stream().map(id -> {
|
||||
return appScenicService.getDetails(id).getData();
|
||||
}).toList();
|
||||
} else {
|
||||
list = scenicService.list(new ScenicReqQuery()).getData();
|
||||
list = account.getScenicId().stream().map(id -> scenicRepository.getScenicBasic(id)).toList();
|
||||
} else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) {
|
||||
ScenicReqQuery query = new ScenicReqQuery();
|
||||
query.setPageSize(1000);
|
||||
list = scenicRepository.list(query);
|
||||
}
|
||||
return ApiResponse.success(list);
|
||||
}
|
||||
|
@@ -0,0 +1,336 @@
|
||||
package com.ycwl.basic.controller.pc;
|
||||
|
||||
import com.ycwl.basic.integration.scenic.dto.config.BatchConfigRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.BatchUpdateResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.UpdateConfigRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author:longbinbin
|
||||
* @Date:2024/12/26
|
||||
* 景区管理 V2 版本控制器 - 基于 zt-scenic 集成服务
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/scenic/v2")
|
||||
@RequiredArgsConstructor
|
||||
public class ScenicV2Controller {
|
||||
|
||||
private final ScenicIntegrationService scenicIntegrationService;
|
||||
private final ScenicConfigIntegrationService scenicConfigIntegrationService;
|
||||
|
||||
// ========== 景区基础 CRUD 操作 ==========
|
||||
|
||||
/**
|
||||
* 景区V2核心信息分页列表
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public ApiResponse<ScenicV2ListResponse> listScenics(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name) {
|
||||
log.info("分页查询景区核心信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
|
||||
// 参数验证:限制pageSize最大值为100
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
}
|
||||
|
||||
try {
|
||||
ScenicV2ListResponse response = scenicIntegrationService.listScenics(page, pageSize, status, name);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询景区核心信息列表失败", e);
|
||||
return ApiResponse.fail("分页查询景区列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 景区V2带配置信息分页列表
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
public ApiResponse<ScenicV2WithConfigListResponse> listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name) {
|
||||
log.info("分页查询景区带配置信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
|
||||
// 参数验证:限制pageSize最大值为100
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
}
|
||||
|
||||
try {
|
||||
ScenicV2WithConfigListResponse response = scenicIntegrationService.listScenicsWithConfig(page, pageSize, status, name);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询景区带配置信息列表失败", e);
|
||||
return ApiResponse.fail("分页查询景区带配置信息列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个景区详情
|
||||
*/
|
||||
@GetMapping("/{scenicId}")
|
||||
public ApiResponse<ScenicV2DTO> getScenic(@PathVariable Long scenicId) {
|
||||
log.info("查询景区详情, scenicId: {}", scenicId);
|
||||
try {
|
||||
ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId);
|
||||
return ApiResponse.success(scenic);
|
||||
} catch (Exception e) {
|
||||
log.error("查询景区详情失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("查询景区详情失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询景区列表(支持筛选和分页)- 高级筛选
|
||||
*/
|
||||
@PostMapping("/filter")
|
||||
public ApiResponse<ScenicFilterPageResponse> filterScenics(@RequestBody @Valid ScenicFilterRequest request) {
|
||||
log.info("高级筛选景区列表, 筛选条件数: {}, 页码: {}, 页大小: {}",
|
||||
request.getFilters().size(), request.getPage(), request.getPageSize());
|
||||
try {
|
||||
ScenicFilterPageResponse response = scenicIntegrationService.filterScenics(request);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("高级筛选景区列表失败", e);
|
||||
return ApiResponse.fail("高级筛选景区列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增景区
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public ApiResponse<ScenicV2DTO> createScenic(@RequestBody @Valid CreateScenicRequest request) {
|
||||
log.info("新增景区, name: {}, mpId: {}", request.getName(), request.getMpId());
|
||||
try {
|
||||
ScenicV2DTO scenic = scenicIntegrationService.createScenic(request);
|
||||
return ApiResponse.success(scenic);
|
||||
} catch (Exception e) {
|
||||
log.error("新增景区失败, name: {}", request.getName(), e);
|
||||
return ApiResponse.fail("新增景区失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改景区
|
||||
*/
|
||||
@PutMapping("/{scenicId}")
|
||||
public ApiResponse<ScenicV2DTO> updateScenic(@PathVariable Long scenicId,
|
||||
@RequestBody @Valid UpdateScenicRequest request) {
|
||||
log.info("修改景区, scenicId: {}", scenicId);
|
||||
try {
|
||||
ScenicV2DTO scenic = scenicIntegrationService.updateScenic(scenicId, request);
|
||||
return ApiResponse.success(scenic);
|
||||
} catch (Exception e) {
|
||||
log.error("修改景区失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("修改景区失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除景区
|
||||
*/
|
||||
@DeleteMapping("/{scenicId}")
|
||||
public ApiResponse<Void> deleteScenic(@PathVariable Long scenicId) {
|
||||
log.info("删除景区, scenicId: {}", scenicId);
|
||||
try {
|
||||
scenicIntegrationService.deleteScenic(scenicId);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
log.error("删除景区失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("删除景区失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 景区列表查询(默认1000条)
|
||||
* 只支持根据状态筛选
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public ApiResponse<ScenicV2ListResponse> listScenicsByStatus(@RequestParam(required = false) Integer status) {
|
||||
log.info("查询景区列表, status: {}", status);
|
||||
try {
|
||||
// 默认查询1000条数据,第1页
|
||||
ScenicV2ListResponse scenics = scenicIntegrationService.listScenics(1, 1000, status, null);
|
||||
return ApiResponse.success(scenics);
|
||||
} catch (Exception e) {
|
||||
log.error("查询景区列表失败, status: {}", status, e);
|
||||
return ApiResponse.fail("查询景区列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========== 景区配置管理 ==========
|
||||
|
||||
/**
|
||||
* 获取景区及其配置信息
|
||||
*/
|
||||
@GetMapping("/{scenicId}/with-config")
|
||||
public ApiResponse<ScenicV2WithConfigDTO> getScenicWithConfig(@PathVariable Long scenicId) {
|
||||
log.info("获取景区配置信息, scenicId: {}", scenicId);
|
||||
try {
|
||||
ScenicV2WithConfigDTO scenic = scenicIntegrationService.getScenicWithConfig(scenicId);
|
||||
return ApiResponse.success(scenic);
|
||||
} catch (Exception e) {
|
||||
log.error("获取景区配置信息失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("获取景区配置信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区扁平化配置
|
||||
*/
|
||||
@GetMapping("/{scenicId}/flat-config")
|
||||
public ApiResponse<Map<String, Object>> getScenicFlatConfig(@PathVariable Long scenicId) {
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
try {
|
||||
Map<String, Object> config = scenicIntegrationService.getScenicFlatConfig(scenicId);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("获取景区扁平化配置失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("获取景区扁平化配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区配置列表
|
||||
*/
|
||||
@GetMapping("/{scenicId}/config")
|
||||
public ApiResponse<List<ScenicConfigV2DTO>> listConfigs(@PathVariable Long scenicId) {
|
||||
log.info("获取景区配置列表, scenicId: {}", scenicId);
|
||||
try {
|
||||
List<ScenicConfigV2DTO> configs = scenicConfigIntegrationService.listConfigs(scenicId);
|
||||
return ApiResponse.success(configs);
|
||||
} catch (Exception e) {
|
||||
log.error("获取景区配置列表失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("获取景区配置列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置键获取配置
|
||||
*/
|
||||
@GetMapping("/{scenicId}/config/{configKey}")
|
||||
public ApiResponse<ScenicConfigV2DTO> getConfigByKey(@PathVariable Long scenicId,
|
||||
@PathVariable String configKey) {
|
||||
log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey);
|
||||
try {
|
||||
ScenicConfigV2DTO config = scenicConfigIntegrationService.getConfigByKey(scenicId, configKey);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("根据键获取景区配置失败, scenicId: {}, configKey: {}", scenicId, configKey, e);
|
||||
return ApiResponse.fail("获取配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建景区配置
|
||||
*/
|
||||
@PostMapping("/{scenicId}/config")
|
||||
public ApiResponse<ScenicConfigV2DTO> createConfig(@PathVariable Long scenicId,
|
||||
@RequestBody @Valid CreateConfigRequest request) {
|
||||
log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey());
|
||||
try {
|
||||
ScenicConfigV2DTO config = scenicConfigIntegrationService.createConfig(scenicId, request);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("创建景区配置失败, scenicId: {}, configKey: {}", scenicId, request.getConfigKey(), e);
|
||||
return ApiResponse.fail("创建配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新景区配置
|
||||
*/
|
||||
@PutMapping("/{scenicId}/config/{configId}")
|
||||
public ApiResponse<ScenicConfigV2DTO> updateConfig(@PathVariable Long scenicId,
|
||||
@PathVariable String configId,
|
||||
@RequestBody @Valid UpdateConfigRequest request) {
|
||||
log.info("更新景区配置, scenicId: {}, configId: {}", scenicId, configId);
|
||||
try {
|
||||
ScenicConfigV2DTO config = scenicConfigIntegrationService.updateConfig(scenicId, configId, request);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("更新景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e);
|
||||
return ApiResponse.fail("更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除景区配置
|
||||
*/
|
||||
@DeleteMapping("/{scenicId}/config/{configId}")
|
||||
public ApiResponse<Void> deleteConfig(@PathVariable Long scenicId, @PathVariable String configId) {
|
||||
log.info("删除景区配置, scenicId: {}, configId: {}", scenicId, configId);
|
||||
try {
|
||||
scenicConfigIntegrationService.deleteConfig(scenicId, configId);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
log.error("删除景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e);
|
||||
return ApiResponse.fail("删除配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新景区配置
|
||||
*/
|
||||
@PutMapping("/{scenicId}/config/batch")
|
||||
public ApiResponse<BatchUpdateResponse> batchUpdateConfigs(@PathVariable Long scenicId,
|
||||
@RequestBody @Valid BatchConfigRequest request) {
|
||||
log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size());
|
||||
try {
|
||||
BatchUpdateResponse response = scenicConfigIntegrationService.batchUpdateConfigs(scenicId, request);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("批量更新景区配置失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("批量更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化批量更新景区配置
|
||||
*/
|
||||
@PutMapping("/{scenicId}/flat-config")
|
||||
public ApiResponse<BatchUpdateResponse> batchFlatUpdateConfigs(@PathVariable Long scenicId,
|
||||
@RequestBody Map<String, Object> configs) {
|
||||
log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size());
|
||||
try {
|
||||
BatchUpdateResponse response = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, configs);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("扁平化批量更新景区配置失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("扁平化批量更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
54
src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java
Normal file
54
src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package com.ycwl.basic.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 移动端下单请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class MobileOrderRequest {
|
||||
|
||||
/**
|
||||
* 商品列表
|
||||
*/
|
||||
private List<ProductItem> products;
|
||||
|
||||
/**
|
||||
* 人脸ID(必填,用于权限验证)
|
||||
*/
|
||||
private Long faceId;
|
||||
|
||||
/**
|
||||
* 预期原价(用于价格验证)
|
||||
*/
|
||||
private BigDecimal expectedOriginalAmount;
|
||||
|
||||
/**
|
||||
* 预期最终价格(用于价格验证)
|
||||
*/
|
||||
private BigDecimal expectedFinalAmount;
|
||||
|
||||
/**
|
||||
* 是否自动使用优惠券
|
||||
*/
|
||||
private Boolean autoUseCoupon = true;
|
||||
|
||||
/**
|
||||
* 用户输入的券码
|
||||
*/
|
||||
private String voucherCode;
|
||||
|
||||
/**
|
||||
* 是否自动使用券码优惠
|
||||
*/
|
||||
private Boolean autoUseVoucher = true;
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String remarks;
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.exception;
|
||||
|
||||
import com.ycwl.basic.enums.BizCodeEnum;
|
||||
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
|
||||
@@ -106,4 +107,18 @@ public class CustomExceptionHandle {
|
||||
public ApiResponse<String> handle(SizeLimitExceededException sizeLimitExceededException) {
|
||||
return ApiResponse.buildResponse(415, "文件过大,请重新上传");
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复购买异常处理
|
||||
*/
|
||||
@ExceptionHandler(value = DuplicatePurchaseException.class)
|
||||
public ApiResponse<String> handle(HttpServletResponse response, DuplicatePurchaseException exception) {
|
||||
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
LOGGER.warn("检测到重复购买: productType={}, productId={}, existingOrderId={}, existingOrderNo={}",
|
||||
exception.getProductType(), exception.getProductId(),
|
||||
exception.getExistingOrderId(), exception.getExistingOrderNo());
|
||||
|
||||
// 返回友好的错误信息给前端
|
||||
return ApiResponse.buildResponse(4001, exception.getFriendlyMessage());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
package com.ycwl.basic.integration.common.config;
|
||||
|
||||
import com.ycwl.basic.integration.common.exception.FeignErrorDecoder;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class FeignConfig {
|
||||
|
||||
private final FeignErrorDecoder feignErrorDecoder;
|
||||
|
||||
@Bean
|
||||
public ErrorDecoder errorDecoder() {
|
||||
return feignErrorDecoder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor() {
|
||||
return template -> {
|
||||
template.header("Accept", "application/json");
|
||||
template.header("Content-Type", "application/json");
|
||||
// 可以在这里添加统一的鉴权头
|
||||
// template.header("Authorization", "Bearer " + getToken());
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package com.ycwl.basic.integration.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "integration")
|
||||
public class IntegrationProperties {
|
||||
|
||||
/**
|
||||
* 景区服务配置
|
||||
*/
|
||||
private ScenicConfig scenic = new ScenicConfig();
|
||||
|
||||
@Data
|
||||
public static class ScenicConfig {
|
||||
/**
|
||||
* 是否启用景区服务集成
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
private String serviceName = "zt-scenic";
|
||||
|
||||
/**
|
||||
* 超时配置(毫秒)
|
||||
*/
|
||||
private int connectTimeout = 5000;
|
||||
private int readTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 重试配置
|
||||
*/
|
||||
private boolean retryEnabled = false;
|
||||
private int maxRetries = 3;
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.integration.common.exception;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import feign.Response;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FeignErrorDecoder implements ErrorDecoder {
|
||||
|
||||
private final ErrorDecoder defaultDecoder = new Default();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Exception decode(String methodKey, Response response) {
|
||||
log.warn("Feign调用失败, methodKey: {}, status: {}, reason: {}",
|
||||
methodKey, response.status(), response.reason());
|
||||
|
||||
try {
|
||||
if (response.body() != null) {
|
||||
String body = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
log.warn("响应内容: {}", body);
|
||||
|
||||
try {
|
||||
CommonResponse<?> errorResponse = objectMapper.readValue(body, CommonResponse.class);
|
||||
if (errorResponse.getCode() != null && !errorResponse.getCode().equals(200)) {
|
||||
return new IntegrationException(
|
||||
errorResponse.getCode(),
|
||||
errorResponse.getMessage() != null ? errorResponse.getMessage() : "服务调用失败",
|
||||
extractServiceName(methodKey)
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析错误响应失败", e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("读取响应体失败", e);
|
||||
}
|
||||
|
||||
return new IntegrationException(
|
||||
response.status(),
|
||||
String.format("服务调用失败: %s", response.reason()),
|
||||
extractServiceName(methodKey)
|
||||
);
|
||||
}
|
||||
|
||||
private String extractServiceName(String methodKey) {
|
||||
if (methodKey != null && methodKey.contains("#")) {
|
||||
return methodKey.substring(0, methodKey.indexOf("#"));
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.ycwl.basic.integration.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class IntegrationException extends RuntimeException {
|
||||
private final Integer code;
|
||||
private final String serviceName;
|
||||
|
||||
public IntegrationException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.serviceName = null;
|
||||
}
|
||||
|
||||
public IntegrationException(Integer code, String message, String serviceName) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public IntegrationException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.serviceName = null;
|
||||
}
|
||||
|
||||
public IntegrationException(Integer code, String message, String serviceName, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.ycwl.basic.integration.common.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CommonResponse<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public static <T> CommonResponse<T> success() {
|
||||
return new CommonResponse<>(200, "OK", null);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> success(T data) {
|
||||
return new CommonResponse<>(200, "OK", data);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> success(String message, T data) {
|
||||
return new CommonResponse<>(200, message, data);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> error(Integer code, String message) {
|
||||
return new CommonResponse<>(code, message, null);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> error(String message) {
|
||||
return new CommonResponse<>(5000, message, null);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return code != null && code == 200;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.ycwl.basic.integration.common.response;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageResponse<T> {
|
||||
private List<T> list;
|
||||
private Integer total;
|
||||
private Integer page;
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,397 @@
|
||||
package com.ycwl.basic.integration.common.util;
|
||||
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 配置值转换工具类
|
||||
*
|
||||
* 提供统一的配置Map值类型转换方法,支持多种数据类型的安全转换
|
||||
*/
|
||||
public class ConfigValueUtil {
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Integer值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Integer值,如果转换失败返回null
|
||||
*/
|
||||
public static Integer getIntValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Integer) return (Integer) value;
|
||||
if (value instanceof Number) return ((Number) value).intValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Integer.parseInt((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Long值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Long值,如果转换失败返回null
|
||||
*/
|
||||
public static Long getLongValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Long) return (Long) value;
|
||||
if (value instanceof Number) return ((Number) value).longValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Long.parseLong((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Float值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Float值,如果转换失败返回null
|
||||
*/
|
||||
public static Float getFloatValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Float) return (Float) value;
|
||||
if (value instanceof Double) return ((Double) value).floatValue();
|
||||
if (value instanceof Number) return ((Number) value).floatValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Float.parseFloat((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Double值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Double值,如果转换失败返回null
|
||||
*/
|
||||
public static Double getDoubleValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Double) return (Double) value;
|
||||
if (value instanceof Number) return ((Number) value).doubleValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取BigDecimal值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return BigDecimal值,如果转换失败返回null
|
||||
*/
|
||||
public static BigDecimal getBigDecimalValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof BigDecimal) return (BigDecimal) value;
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return new BigDecimal((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return new BigDecimal(value.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取String值
|
||||
* 如果值是复杂对象(Map/List),会自动转换为JSON字符串
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return String值,如果value为null返回null
|
||||
*/
|
||||
public static String getStringValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
|
||||
// 如果是基础类型,直接转字符串
|
||||
if (value instanceof String || value instanceof Number || value instanceof Boolean) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
// 如果是复杂对象(Map, List等),转换为JSON字符串
|
||||
try {
|
||||
return JacksonUtil.toJSONString(value);
|
||||
} catch (Exception e) {
|
||||
// JSON转换失败,降级为toString
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Boolean值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Boolean值,如果转换失败返回null
|
||||
*/
|
||||
public static Boolean getBooleanValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Boolean) return (Boolean) value;
|
||||
if (value instanceof String) {
|
||||
String str = (String) value;
|
||||
if ("true".equalsIgnoreCase(str) || "1".equals(str)) return true;
|
||||
if ("false".equalsIgnoreCase(str) || "0".equals(str)) return false;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue() != 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取枚举值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param enumClass 枚举类型
|
||||
* @param <T> 枚举类型泛型
|
||||
* @return 枚举值,如果转换失败返回null
|
||||
*/
|
||||
public static <T extends Enum<T>> T getEnumValue(Map<String, Object> config, String key, Class<T> enumClass) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
try {
|
||||
if (value instanceof String) {
|
||||
return Enum.valueOf(enumClass, (String) value);
|
||||
}
|
||||
return Enum.valueOf(enumClass, value.toString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Integer值,如果为null则返回默认值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Integer值或默认值
|
||||
*/
|
||||
public static Integer getIntValue(Map<String, Object> config, String key, Integer defaultValue) {
|
||||
Integer value = getIntValue(config, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取String值,如果为null则返回默认值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return String值或默认值
|
||||
*/
|
||||
public static String getStringValue(Map<String, Object> config, String key, String defaultValue) {
|
||||
String value = getStringValue(config, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Boolean值,如果为null则返回默认值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Boolean值或默认值
|
||||
*/
|
||||
public static Boolean getBooleanValue(Map<String, Object> config, String key, Boolean defaultValue) {
|
||||
Boolean value = getBooleanValue(config, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
// ========== 对象和JSON转换方法 ==========
|
||||
|
||||
/**
|
||||
* 从配置Map中获取原始对象值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return 原始Object值
|
||||
*/
|
||||
public static Object getObjectValue(Map<String, Object> config, String key) {
|
||||
return config.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取并转换为指定类型的对象
|
||||
* 支持JSON字符串自动反序列化
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 目标类型泛型
|
||||
* @return 转换后的对象,如果转换失败返回null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getObjectValue(Map<String, Object> config, String key, Class<T> clazz) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
|
||||
// 如果类型匹配,直接返回
|
||||
if (clazz.isInstance(value)) {
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
// 如果是String类型的JSON,尝试反序列化
|
||||
if (value instanceof String && !clazz.equals(String.class)) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, clazz);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果目标是String,使用增强的字符串转换
|
||||
if (clazz.equals(String.class)) {
|
||||
return (T) getStringValue(config, key);
|
||||
}
|
||||
|
||||
// 其他情况尝试JSON转换
|
||||
try {
|
||||
String json = JacksonUtil.toJSONString(value);
|
||||
return JacksonUtil.parseObject(json, clazz);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取Map类型的值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return Map值,如果转换失败返回null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> getMapValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof Map) {
|
||||
return (Map<String, Object>) value;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, Map.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取List类型的值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return List值,如果转换失败返回null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Object> getListValue(Map<String, Object> config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof List) {
|
||||
return (List<Object>) value;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, List.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map中获取指定元素类型的List值
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @param elementClass List元素类型
|
||||
* @param <T> List元素类型泛型
|
||||
* @return 指定类型的List,如果转换失败返回null
|
||||
*/
|
||||
public static <T> List<T> getListValue(Map<String, Object> config, String key, Class<T> elementClass) {
|
||||
Object value = config.get(key);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseArray((String) value, elementClass);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String json = JacksonUtil.toJSONString(value);
|
||||
return JacksonUtil.parseArray(json, elementClass);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return true如果键存在,false如果不存在
|
||||
*/
|
||||
public static boolean hasKey(Map<String, Object> config, String key) {
|
||||
return config != null && config.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在且值不为null
|
||||
*
|
||||
* @param config 配置Map
|
||||
* @param key 配置键
|
||||
* @return true如果键存在且值不为null
|
||||
*/
|
||||
public static boolean hasNonNullValue(Map<String, Object> config, String key) {
|
||||
return config != null && config.containsKey(key) && config.get(key) != null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.ycwl.basic.integration.scenic.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = "zt-scenic", contextId = "scenic-default-config", path = "/api/scenic/default-config")
|
||||
public interface DefaultConfigClient {
|
||||
|
||||
@GetMapping("/")
|
||||
CommonResponse<List<DefaultConfigDTO>> listDefaultConfigs();
|
||||
|
||||
@GetMapping("/{configKey}")
|
||||
CommonResponse<DefaultConfigDTO> getDefaultConfig(@PathVariable("configKey") String configKey);
|
||||
|
||||
@PostMapping("/")
|
||||
CommonResponse<DefaultConfigDTO> createDefaultConfig(@RequestBody DefaultConfigDTO request);
|
||||
|
||||
@PutMapping("/{configKey}")
|
||||
CommonResponse<DefaultConfigDTO> updateDefaultConfig(@PathVariable("configKey") String configKey,
|
||||
@RequestBody DefaultConfigDTO request);
|
||||
|
||||
@DeleteMapping("/{configKey}")
|
||||
CommonResponse<Void> deleteDefaultConfig(@PathVariable("configKey") String configKey);
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package com.ycwl.basic.integration.scenic.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.*;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@FeignClient(name = "zt-scenic", contextId = "scenic-config-v2", path = "/api/scenic/config/v2")
|
||||
public interface ScenicConfigV2Client {
|
||||
|
||||
@GetMapping("/{scenicId}")
|
||||
CommonResponse<List<ScenicConfigV2DTO>> listConfigs(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@GetMapping("/{scenicId}/key/{configKey}")
|
||||
CommonResponse<ScenicConfigV2DTO> getConfigByKey(@PathVariable("scenicId") Long scenicId,
|
||||
@PathVariable("configKey") String configKey);
|
||||
|
||||
@GetMapping("/{scenicId}/keys")
|
||||
CommonResponse<Map<String, Object>> getFlatConfigs(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@PostMapping("/{scenicId}")
|
||||
CommonResponse<ScenicConfigV2DTO> createConfig(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody CreateConfigRequest request);
|
||||
|
||||
@PutMapping("/{scenicId}/{id}")
|
||||
CommonResponse<ScenicConfigV2DTO> updateConfig(@PathVariable("scenicId") Long scenicId,
|
||||
@PathVariable("id") String id,
|
||||
@RequestBody UpdateConfigRequest request);
|
||||
|
||||
@DeleteMapping("/{scenicId}/{id}")
|
||||
CommonResponse<Void> deleteConfig(@PathVariable("scenicId") Long scenicId,
|
||||
@PathVariable("id") String id);
|
||||
|
||||
@PostMapping("/{scenicId}/batch")
|
||||
CommonResponse<BatchUpdateResponse> batchUpdateConfigs(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody BatchConfigRequest request);
|
||||
|
||||
@PostMapping("/{scenicId}/batchFlatUpdate")
|
||||
CommonResponse<BatchUpdateResponse> batchFlatUpdateConfigs(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody Map<String, Object> configs);
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.ycwl.basic.integration.scenic.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@FeignClient(name = "zt-scenic", contextId = "scenic-v2", path = "/api/scenic/v2")
|
||||
public interface ScenicV2Client {
|
||||
|
||||
@GetMapping("/{scenicId}")
|
||||
CommonResponse<ScenicV2DTO> getScenic(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@GetMapping("/{scenicId}/with-config")
|
||||
CommonResponse<ScenicV2WithConfigDTO> getScenicWithConfig(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
|
||||
@PostMapping("/")
|
||||
CommonResponse<ScenicV2DTO> createScenic(@RequestBody CreateScenicRequest request);
|
||||
|
||||
@PutMapping("/{scenicId}")
|
||||
CommonResponse<ScenicV2DTO> updateScenic(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody UpdateScenicRequest request);
|
||||
|
||||
@DeleteMapping("/{scenicId}")
|
||||
CommonResponse<Void> deleteScenic(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@PostMapping("/filter")
|
||||
CommonResponse<ScenicFilterPageResponse> filterScenics(@RequestBody ScenicFilterRequest request);
|
||||
|
||||
@GetMapping("/")
|
||||
CommonResponse<ScenicV2ListResponse> listScenics(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name);
|
||||
|
||||
@GetMapping("/with-config")
|
||||
CommonResponse<ScenicV2WithConfigListResponse> listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name);
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.ycwl.basic.integration.scenic.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "integration.scenic")
|
||||
public class ScenicIntegrationConfig {
|
||||
|
||||
public ScenicIntegrationConfig() {
|
||||
log.info("ZT-Scenic集成配置初始化完成");
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BatchConfigRequest {
|
||||
@JsonProperty("configs")
|
||||
@NotEmpty(message = "配置列表不能为空")
|
||||
@Valid
|
||||
private List<BatchConfigItem> configs;
|
||||
|
||||
@Data
|
||||
public static class BatchConfigItem {
|
||||
@JsonProperty("configKey")
|
||||
@NotEmpty(message = "配置键不能为空")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BatchUpdateResponse {
|
||||
@JsonProperty("updatedCount")
|
||||
private Integer updatedCount;
|
||||
|
||||
@JsonProperty("createdCount")
|
||||
private Integer createdCount;
|
||||
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class CreateConfigRequest {
|
||||
@JsonProperty("configKey")
|
||||
@NotBlank(message = "配置键不能为空")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("configType")
|
||||
@NotBlank(message = "配置类型不能为空")
|
||||
private String configType;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DefaultConfigDTO {
|
||||
@JsonProperty("configKey")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("configType")
|
||||
private String configType;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class ScenicConfigV2DTO {
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("scenicID")
|
||||
@JsonAlias({"scenicId", "scenicID"})
|
||||
private String scenicId;
|
||||
|
||||
@JsonProperty("configKey")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("configType")
|
||||
private String configType;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("isActive")
|
||||
private Integer isActive;
|
||||
|
||||
@JsonProperty("createTime")
|
||||
private Date createTime;
|
||||
|
||||
@JsonProperty("updateTime")
|
||||
private Date updateTime;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateConfigRequest {
|
||||
@JsonProperty("configKey")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.filter;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class FilterCondition {
|
||||
@JsonProperty("configKey")
|
||||
@NotBlank(message = "配置键不能为空")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
@NotBlank(message = "配置值不能为空")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("operator")
|
||||
private String operator = "eq";
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.filter;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ScenicFilterPageResponse {
|
||||
@JsonProperty("list")
|
||||
private List<ScenicFilterItem> list;
|
||||
|
||||
@JsonProperty("total")
|
||||
private Long total;
|
||||
|
||||
@JsonProperty("page")
|
||||
private Integer page;
|
||||
|
||||
@JsonProperty("pageSize")
|
||||
private Integer pageSize;
|
||||
|
||||
@Data
|
||||
public static class ScenicFilterItem {
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.filter;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ScenicFilterRequest {
|
||||
@JsonProperty("filters")
|
||||
@NotEmpty(message = "筛选条件不能为空")
|
||||
@Valid
|
||||
private List<FilterCondition> filters;
|
||||
|
||||
@JsonProperty("page")
|
||||
private Integer page = 1;
|
||||
|
||||
@JsonProperty("pageSize")
|
||||
private Integer pageSize = 20;
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class CreateScenicRequest {
|
||||
@JsonProperty("name")
|
||||
@NotBlank(message = "景区名称不能为空")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("mpId")
|
||||
@NotNull(message = "小程序ID不能为空")
|
||||
private Integer mpId;
|
||||
|
||||
@JsonProperty("status")
|
||||
private Integer status = 1;
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class ScenicV2DTO {
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("mpId")
|
||||
private Integer mpId;
|
||||
|
||||
@JsonProperty("status")
|
||||
private Integer status;
|
||||
|
||||
@JsonProperty("createTime")
|
||||
private Date createTime;
|
||||
|
||||
@JsonProperty("updateTime")
|
||||
private Date updateTime;
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ScenicV2ListResponse {
|
||||
@JsonProperty("list")
|
||||
private List<ScenicV2DTO> list;
|
||||
|
||||
@JsonProperty("total")
|
||||
private Integer total;
|
||||
|
||||
@JsonProperty("page")
|
||||
private Integer page;
|
||||
|
||||
@JsonProperty("pageSize")
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ScenicV2WithConfigDTO extends ScenicV2DTO {
|
||||
@JsonProperty("config")
|
||||
private Map<String, Object> config;
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ScenicV2WithConfigListResponse {
|
||||
@JsonProperty("list")
|
||||
private List<ScenicV2WithConfigDTO> list;
|
||||
|
||||
@JsonProperty("total")
|
||||
private Integer total;
|
||||
|
||||
@JsonProperty("page")
|
||||
private Integer page;
|
||||
|
||||
@JsonProperty("pageSize")
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateScenicRequest {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("mpId")
|
||||
private Integer mpId;
|
||||
|
||||
@JsonProperty("status")
|
||||
private Integer status;
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
package com.ycwl.basic.integration.scenic.example;
|
||||
|
||||
import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.FilterCondition;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* ZT-Scenic集成服务使用示例
|
||||
* 仅供参考,实际使用时根据业务需要调用相应的服务方法
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ScenicIntegrationExample {
|
||||
|
||||
private final ScenicIntegrationService scenicIntegrationService;
|
||||
private final ScenicConfigIntegrationService scenicConfigIntegrationService;
|
||||
|
||||
/**
|
||||
* 示例:创建景区并设置配置
|
||||
*/
|
||||
public void createScenicWithConfig() {
|
||||
try {
|
||||
// 1. 创建景区
|
||||
CreateScenicRequest createRequest = new CreateScenicRequest();
|
||||
createRequest.setName("测试景区");
|
||||
createRequest.setMpId(1001);
|
||||
|
||||
var scenic = scenicIntegrationService.createScenic(createRequest);
|
||||
log.info("创建景区成功: {}", scenic.getName());
|
||||
|
||||
// 2. 为景区添加配置
|
||||
CreateConfigRequest configRequest = new CreateConfigRequest();
|
||||
configRequest.setConfigKey("tour_time");
|
||||
configRequest.setConfigValue("120");
|
||||
configRequest.setConfigType("int");
|
||||
configRequest.setDescription("游览时长");
|
||||
|
||||
var config = scenicConfigIntegrationService.createConfig(
|
||||
Long.valueOf(scenic.getId()), configRequest);
|
||||
log.info("创建配置成功: {} = {}", config.getConfigKey(), config.getConfigValue());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建景区和配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:筛选景区
|
||||
*/
|
||||
public void filterScenics() {
|
||||
try {
|
||||
FilterCondition condition = new FilterCondition();
|
||||
condition.setConfigKey("tour_time");
|
||||
condition.setConfigValue("120");
|
||||
condition.setOperator("gte");
|
||||
|
||||
ScenicFilterRequest filterRequest = new ScenicFilterRequest();
|
||||
filterRequest.setFilters(Collections.singletonList(condition));
|
||||
filterRequest.setPage(1);
|
||||
filterRequest.setPageSize(10);
|
||||
|
||||
var result = scenicIntegrationService.filterScenics(filterRequest);
|
||||
log.info("筛选到 {} 个景区", result.getTotal());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("筛选景区失败", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.integration.scenic.service;
|
||||
|
||||
import com.ycwl.basic.integration.common.exception.IntegrationException;
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.client.DefaultConfigClient;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultConfigIntegrationService {
|
||||
|
||||
private final DefaultConfigClient defaultConfigClient;
|
||||
|
||||
public List<DefaultConfigDTO> listDefaultConfigs() {
|
||||
log.info("获取默认配置列表");
|
||||
CommonResponse<List<DefaultConfigDTO>> response = defaultConfigClient.listDefaultConfigs();
|
||||
return handleResponse(response, "获取默认配置列表失败");
|
||||
}
|
||||
|
||||
public DefaultConfigDTO getDefaultConfig(String configKey) {
|
||||
log.info("获取指定默认配置, configKey: {}", configKey);
|
||||
CommonResponse<DefaultConfigDTO> response = defaultConfigClient.getDefaultConfig(configKey);
|
||||
return handleResponse(response, "获取指定默认配置失败");
|
||||
}
|
||||
|
||||
public DefaultConfigDTO createDefaultConfig(DefaultConfigDTO request) {
|
||||
log.info("创建默认配置, configKey: {}", request.getConfigKey());
|
||||
CommonResponse<DefaultConfigDTO> response = defaultConfigClient.createDefaultConfig(request);
|
||||
return handleResponse(response, "创建默认配置失败");
|
||||
}
|
||||
|
||||
public DefaultConfigDTO updateDefaultConfig(String configKey, DefaultConfigDTO request) {
|
||||
log.info("更新默认配置, configKey: {}", configKey);
|
||||
CommonResponse<DefaultConfigDTO> response = defaultConfigClient.updateDefaultConfig(configKey, request);
|
||||
return handleResponse(response, "更新默认配置失败");
|
||||
}
|
||||
|
||||
public void deleteDefaultConfig(String configKey) {
|
||||
log.info("删除默认配置, configKey: {}", configKey);
|
||||
CommonResponse<Void> response = defaultConfigClient.deleteDefaultConfig(configKey);
|
||||
handleResponse(response, "删除默认配置失败");
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "zt-scenic");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package com.ycwl.basic.integration.scenic.service;
|
||||
|
||||
import com.ycwl.basic.integration.common.exception.IntegrationException;
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ScenicConfigIntegrationService {
|
||||
|
||||
private final ScenicConfigV2Client scenicConfigV2Client;
|
||||
|
||||
public List<ScenicConfigV2DTO> listConfigs(Long scenicId) {
|
||||
log.info("获取景区配置列表, scenicId: {}", scenicId);
|
||||
CommonResponse<List<ScenicConfigV2DTO>> response = scenicConfigV2Client.listConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区配置列表失败");
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO getConfigByKey(Long scenicId, String configKey) {
|
||||
log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey);
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.getConfigByKey(scenicId, configKey);
|
||||
return handleResponse(response, "根据键获取景区配置失败");
|
||||
}
|
||||
|
||||
public Map<String, Object> getFlatConfigs(Long scenicId) {
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) {
|
||||
log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey());
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.createConfig(scenicId, request);
|
||||
return handleResponse(response, "创建景区配置失败");
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO updateConfig(Long scenicId, String id, UpdateConfigRequest request) {
|
||||
log.info("更新景区配置, scenicId: {}, id: {}", scenicId, id);
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.updateConfig(scenicId, id, request);
|
||||
return handleResponse(response, "更新景区配置失败");
|
||||
}
|
||||
|
||||
public void deleteConfig(Long scenicId, String id) {
|
||||
log.info("删除景区配置, scenicId: {}, id: {}", scenicId, id);
|
||||
CommonResponse<Void> response = scenicConfigV2Client.deleteConfig(scenicId, id);
|
||||
handleResponse(response, "删除景区配置失败");
|
||||
}
|
||||
|
||||
public BatchUpdateResponse batchUpdateConfigs(Long scenicId, BatchConfigRequest request) {
|
||||
log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size());
|
||||
CommonResponse<BatchUpdateResponse> response = scenicConfigV2Client.batchUpdateConfigs(scenicId, request);
|
||||
return handleResponse(response, "批量更新景区配置失败");
|
||||
}
|
||||
|
||||
public BatchUpdateResponse batchFlatUpdateConfigs(Long scenicId, Map<String, Object> configs) {
|
||||
log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size());
|
||||
CommonResponse<BatchUpdateResponse> response = scenicConfigV2Client.batchFlatUpdateConfigs(scenicId, configs);
|
||||
return handleResponse(response, "扁平化批量更新景区配置失败");
|
||||
}
|
||||
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "zt-scenic");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
package com.ycwl.basic.integration.scenic.service;
|
||||
|
||||
import com.ycwl.basic.integration.common.exception.IntegrationException;
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client;
|
||||
import com.ycwl.basic.integration.scenic.client.ScenicV2Client;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ScenicIntegrationService {
|
||||
|
||||
private final ScenicV2Client scenicV2Client;
|
||||
private final ScenicConfigV2Client scenicConfigV2Client;
|
||||
|
||||
public ScenicV2DTO getScenic(Long scenicId) {
|
||||
log.info("获取景区信息, scenicId: {}", scenicId);
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.getScenic(scenicId);
|
||||
return handleResponse(response, "获取景区信息失败");
|
||||
}
|
||||
|
||||
public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) {
|
||||
log.info("获取景区配置信息, scenicId: {}", scenicId);
|
||||
CommonResponse<ScenicV2WithConfigDTO> response = scenicV2Client.getScenicWithConfig(scenicId);
|
||||
return handleResponse(response, "获取景区配置信息失败");
|
||||
}
|
||||
|
||||
public Map<String, Object> getScenicFlatConfig(Long scenicId) {
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
}
|
||||
|
||||
public ScenicV2DTO createScenic(CreateScenicRequest request) {
|
||||
log.info("创建景区, name: {}", request.getName());
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.createScenic(request);
|
||||
return handleResponse(response, "创建景区失败");
|
||||
}
|
||||
|
||||
public ScenicV2DTO updateScenic(Long scenicId, UpdateScenicRequest request) {
|
||||
log.info("更新景区信息, scenicId: {}", scenicId);
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.updateScenic(scenicId, request);
|
||||
return handleResponse(response, "更新景区信息失败");
|
||||
}
|
||||
|
||||
public void deleteScenic(Long scenicId) {
|
||||
log.info("删除景区, scenicId: {}", scenicId);
|
||||
CommonResponse<Void> response = scenicV2Client.deleteScenic(scenicId);
|
||||
handleResponse(response, "删除景区失败");
|
||||
}
|
||||
|
||||
public ScenicFilterPageResponse filterScenics(ScenicFilterRequest request) {
|
||||
log.info("筛选景区, filters: {}", request.getFilters().size());
|
||||
CommonResponse<ScenicFilterPageResponse> response = scenicV2Client.filterScenics(request);
|
||||
return handleResponse(response, "筛选景区失败");
|
||||
}
|
||||
|
||||
public ScenicV2ListResponse listScenics(Integer page, Integer pageSize, Integer status, String name) {
|
||||
log.info("分页查询景区列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
CommonResponse<ScenicV2ListResponse> response = scenicV2Client.listScenics(page, pageSize, status, name);
|
||||
return handleResponse(response, "分页查询景区列表失败");
|
||||
}
|
||||
|
||||
public ScenicV2WithConfigListResponse listScenicsWithConfig(Integer page, Integer pageSize, Integer status, String name) {
|
||||
log.info("分页查询景区带配置列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
CommonResponse<ScenicV2WithConfigListResponse> response = scenicV2Client.listScenicsWithConfig(page, pageSize, status, name);
|
||||
return handleResponse(response, "分页查询景区带配置列表失败");
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "zt-scenic");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -57,4 +57,6 @@ public interface OrderMapper {
|
||||
OrderEntity getUserOrderItem(Long userId, Long scenicId, int orderType, Long configId, Integer goodsType, Long goodsId);
|
||||
|
||||
int updateMemberIdByFaceId(OrderEntity orderEntity);
|
||||
|
||||
List<OrderItemEntity> getOrderItems(Long orderId);
|
||||
}
|
||||
|
@@ -1,71 +0,0 @@
|
||||
package com.ycwl.basic.mapper;
|
||||
|
||||
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq;
|
||||
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
|
||||
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author:longbinbin
|
||||
* @Date:2024/12/2 10:07
|
||||
* 景区管理表
|
||||
*/
|
||||
@Mapper
|
||||
public interface ScenicMapper {
|
||||
List<ScenicRespVO> list(ScenicReqQuery scenicReqQuery);
|
||||
|
||||
ScenicEntity get(Long id);
|
||||
|
||||
ScenicRespVO getById(Long id);
|
||||
|
||||
int add(ScenicAddOrUpdateReq scenic);
|
||||
|
||||
int deleteById(Long id);
|
||||
|
||||
int update(ScenicAddOrUpdateReq scenic);
|
||||
|
||||
int updateStatus(Long id);
|
||||
|
||||
ScenicConfigEntity getConfig(Long scenicId);
|
||||
/**
|
||||
* 添加景区配置
|
||||
*
|
||||
* @param scenicConfig
|
||||
* @return
|
||||
*/
|
||||
int addConfig(ScenicConfigEntity scenicConfig);
|
||||
|
||||
/**
|
||||
* 修改景区配置
|
||||
*
|
||||
* @param scenicConfigEntity
|
||||
* @return
|
||||
*/
|
||||
int updateConfigById(ScenicConfigEntity scenicConfigEntity);
|
||||
|
||||
/**
|
||||
* 根据景区id删除配置
|
||||
*
|
||||
* @param scenicId
|
||||
*/
|
||||
void deleteConfigByScenicId(Long scenicId);
|
||||
|
||||
List<ScenicAppVO> appList(ScenicReqQuery scenicReqQuery);
|
||||
|
||||
ScenicRespVO getAppById(Long id);
|
||||
|
||||
/**
|
||||
* 通过经纬度计算景区距离
|
||||
*
|
||||
* @param scenicIndexVO
|
||||
* @return
|
||||
*/
|
||||
List<ScenicAppVO> scenicListByLnLa(ScenicIndexVO scenicIndexVO);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.model.mobile.claim;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClaimReq {
|
||||
private Long faceId;
|
||||
// 扫码进入的推客ID
|
||||
private Long morphId;
|
||||
// 通知进入的type
|
||||
private Integer type;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.model.mobile.claim;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClaimResp {
|
||||
private Boolean hasPrint;
|
||||
private String printType;
|
||||
private String printCode;
|
||||
private Boolean hasCoupon;
|
||||
private String couponDesc;
|
||||
private String couponCountdown;
|
||||
}
|
@@ -12,6 +12,5 @@ public class PriceObj {
|
||||
private Long goodsId;
|
||||
private Long faceId;
|
||||
private BigDecimal price = BigDecimal.ZERO;
|
||||
private BigDecimal scenicAllPrice;
|
||||
private BigDecimal slashPrice;
|
||||
}
|
||||
|
@@ -7,6 +7,4 @@ public class CreateOrderReqVO {
|
||||
private Long scenicId;
|
||||
private Integer goodsType;
|
||||
private Long goodsId;
|
||||
private String brokerCode;
|
||||
private Long couponId;
|
||||
}
|
||||
|
@@ -67,17 +67,17 @@ public class ScenicConfigEntity {
|
||||
/**
|
||||
* 是否开启全部免费
|
||||
*/
|
||||
private Integer allFree;
|
||||
private Boolean allFree;
|
||||
/**
|
||||
* 是否禁用源视频
|
||||
* 0-否 1-是
|
||||
*/
|
||||
private Integer disableSourceVideo;
|
||||
private Boolean disableSourceVideo;
|
||||
/**
|
||||
* 是否禁用源图片
|
||||
* 0-否 1-是
|
||||
*/
|
||||
private Integer disableSourceImage;
|
||||
private Boolean disableSourceImage;
|
||||
private Integer templateNewVideoType;
|
||||
/**
|
||||
* 是否开启防录屏
|
||||
@@ -130,5 +130,5 @@ public class ScenicConfigEntity {
|
||||
* 是否启用券码功能
|
||||
* 0-禁用 1-启用
|
||||
*/
|
||||
private Integer voucherEnable;
|
||||
private Boolean voucherEnable;
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
package com.ycwl.basic.model.pc.scenic.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author:longbinbin
|
||||
@@ -13,15 +10,17 @@ import java.util.Date;
|
||||
* 景区管理表
|
||||
*/
|
||||
@Data
|
||||
@TableName("scenic")
|
||||
public class ScenicEntity {
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 景区名称
|
||||
*/
|
||||
private String name;
|
||||
private Integer mpId;
|
||||
private String phone;
|
||||
private String logoUrl;
|
||||
// 封面图
|
||||
private String coverUrl;
|
||||
/**
|
||||
* 景区介绍
|
||||
*/
|
||||
@@ -58,12 +57,8 @@ public class ScenicEntity {
|
||||
* 状态 1启用0关闭
|
||||
*/
|
||||
private String status;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
/**
|
||||
* 景区源素材价格,元
|
||||
*/
|
||||
private BigDecimal price;
|
||||
private BigDecimal sourceVideoPrice;
|
||||
private BigDecimal sourceImagePrice;
|
||||
private String kfCodeUrl;
|
||||
}
|
||||
|
@@ -77,10 +77,6 @@ public class ScenicAddOrUpdateReq {
|
||||
private Date createTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
// 景区源素材价格,元
|
||||
private BigDecimal price;
|
||||
private BigDecimal sourceVideoPrice;
|
||||
private BigDecimal sourceImagePrice;
|
||||
|
||||
// 账号
|
||||
private String account;
|
||||
|
@@ -23,51 +23,9 @@ public class ScenicReqQuery extends BaseQueryParameterReq {
|
||||
*/
|
||||
// 景区名称
|
||||
private String name;
|
||||
/**
|
||||
* 景区介绍
|
||||
*/
|
||||
// 景区介绍
|
||||
private String introduction;
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
// 经度
|
||||
private BigDecimal longitude;
|
||||
/***
|
||||
* 纬度
|
||||
*/
|
||||
// 纬度
|
||||
private BigDecimal latitude;
|
||||
/**
|
||||
* 半径(km)
|
||||
*/
|
||||
// 半径(km)
|
||||
private BigDecimal radius;
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
// 省份
|
||||
private String province;
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
// 城市
|
||||
private String city;
|
||||
/**
|
||||
* 区
|
||||
*/
|
||||
// 区
|
||||
private String area;
|
||||
/**
|
||||
* 详细地址
|
||||
*/
|
||||
// 详细地址
|
||||
private String address;
|
||||
/**
|
||||
* 状态 1启用0关闭
|
||||
*/
|
||||
// 状态 1启用0关闭
|
||||
private String status;
|
||||
private Date startTime;
|
||||
private Date endTime;
|
||||
}
|
||||
|
@@ -34,9 +34,9 @@ public class ScenicConfigResp {
|
||||
* 视频保存时间
|
||||
*/
|
||||
private Integer videoStoreDay;
|
||||
private Integer allFree;
|
||||
private Integer disableSourceVideo;
|
||||
private Integer disableSourceImage;
|
||||
private Boolean allFree;
|
||||
private Boolean disableSourceVideo;
|
||||
private Boolean disableSourceImage;
|
||||
private Integer antiScreenRecordType;
|
||||
private Integer videoSourceStoreDay;
|
||||
private Integer imageSourceStoreDay;
|
||||
@@ -45,9 +45,5 @@ public class ScenicConfigResp {
|
||||
|
||||
private String imageSourcePackHint = "";
|
||||
private String videoSourcePackHint = "";
|
||||
/**
|
||||
* 是否启用券码功能
|
||||
* 0-禁用 1-启用
|
||||
*/
|
||||
private Integer voucherEnable;
|
||||
private Boolean voucherEnable;
|
||||
}
|
||||
|
@@ -74,19 +74,6 @@ public class ScenicRespVO {
|
||||
*/
|
||||
// 详细地址
|
||||
private String address;
|
||||
/**
|
||||
* 状态 1启用0关闭
|
||||
*/
|
||||
// 状态 1启用0关闭
|
||||
private Integer status;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
// 景区源素材价格,元
|
||||
private BigDecimal price;
|
||||
private BigDecimal sourceVideoPrice;
|
||||
private BigDecimal sourceImagePrice;
|
||||
// 镜头数
|
||||
private Integer lensNum;
|
||||
private String kfCodeUrl;
|
||||
|
@@ -0,0 +1,89 @@
|
||||
package com.ycwl.basic.order.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.order.dto.*;
|
||||
import com.ycwl.basic.order.service.IOrderService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 订单管理控制器V2 - 管理端
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/order/v2")
|
||||
@RequiredArgsConstructor
|
||||
public class OrderV2Controller {
|
||||
|
||||
private final IOrderService orderService;
|
||||
|
||||
/**
|
||||
* 分页查询订单列表
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<PageInfo<OrderV2ListResponse>> pageOrders(@RequestBody OrderV2PageRequest request) {
|
||||
log.info("分页查询订单列表: {}", request);
|
||||
try {
|
||||
PageInfo<OrderV2ListResponse> pageInfo = orderService.pageOrders(request);
|
||||
return ApiResponse.success(pageInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询订单列表失败", e);
|
||||
return ApiResponse.fail("查询失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单详情
|
||||
*/
|
||||
@GetMapping("/detail/{orderId}")
|
||||
public ApiResponse<OrderV2DetailResponse> getOrderDetail(@PathVariable("orderId") Long orderId) {
|
||||
log.info("查询订单详情: orderId={}", orderId);
|
||||
try {
|
||||
OrderV2DetailResponse detail = orderService.getOrderDetail(orderId);
|
||||
if (detail == null) {
|
||||
return ApiResponse.fail("订单不存在");
|
||||
}
|
||||
return ApiResponse.success(detail);
|
||||
} catch (Exception e) {
|
||||
log.error("查询订单详情失败: orderId={}", orderId, e);
|
||||
return ApiResponse.fail("查询失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单备注
|
||||
*/
|
||||
@PutMapping("/remark/{orderId}")
|
||||
public ApiResponse<String> updateOrderRemarks(@PathVariable("orderId") Long orderId,
|
||||
@RequestBody OrderRemarkRequest request) {
|
||||
log.info("更新订单备注: orderId={}, remarks={}", orderId, request.getRemarks());
|
||||
try {
|
||||
boolean success = orderService.updateOrderRemarks(orderId, request.getRemarks());
|
||||
if (success) {
|
||||
return ApiResponse.success("备注更新成功");
|
||||
} else {
|
||||
return ApiResponse.fail("备注更新失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新订单备注失败: orderId={}", orderId, e);
|
||||
return ApiResponse.fail("更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
*/
|
||||
@PostMapping("/refund")
|
||||
public ApiResponse<String> createRefund(@RequestBody RefundRequest request) {
|
||||
log.info("申请退款: {}", request);
|
||||
try {
|
||||
Long refundId = orderService.createRefundRecord(request);
|
||||
return ApiResponse.success("退款申请已提交,退款ID:" + refundId);
|
||||
} catch (Exception e) {
|
||||
log.error("申请退款失败: {}", request, e);
|
||||
return ApiResponse.fail("退款申请失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单备注修改请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderRemarkRequest {
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String remarks;
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import com.ycwl.basic.order.entity.OrderDiscountV2;
|
||||
import com.ycwl.basic.order.entity.OrderItemV2;
|
||||
import com.ycwl.basic.order.entity.OrderRefundV2;
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单详情响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderV2DetailResponse {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 小程序openId
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 人脸ID
|
||||
*/
|
||||
private Long faceId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 原始金额
|
||||
*/
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 最终金额
|
||||
*/
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private OrderStatus orderStatus;
|
||||
|
||||
/**
|
||||
* 订单状态描述
|
||||
*/
|
||||
private String orderStatusDesc;
|
||||
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
private PaymentStatus paymentStatus;
|
||||
|
||||
/**
|
||||
* 支付状态描述
|
||||
*/
|
||||
private String paymentStatusDesc;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
private RefundStatus refundStatus;
|
||||
|
||||
/**
|
||||
* 退款状态描述
|
||||
*/
|
||||
private String refundStatusDesc;
|
||||
|
||||
/**
|
||||
* 总退款金额
|
||||
*/
|
||||
private BigDecimal totalRefundAmount;
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String remarks;
|
||||
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
private Date payTime;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
private Date completeTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 订单商品明细列表
|
||||
*/
|
||||
private List<OrderItemV2> orderItems;
|
||||
|
||||
/**
|
||||
* 订单优惠记录列表
|
||||
*/
|
||||
private List<OrderDiscountV2> orderDiscounts;
|
||||
|
||||
/**
|
||||
* 订单退款记录列表
|
||||
*/
|
||||
private List<OrderRefundV2> orderRefunds;
|
||||
}
|
131
src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java
Normal file
131
src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单列表响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderV2ListResponse {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 小程序openId
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 人脸ID
|
||||
*/
|
||||
private Long faceId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 原始金额
|
||||
*/
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 最终金额
|
||||
*/
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private OrderStatus orderStatus;
|
||||
|
||||
/**
|
||||
* 订单状态描述
|
||||
*/
|
||||
private String orderStatusDesc;
|
||||
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
private PaymentStatus paymentStatus;
|
||||
|
||||
/**
|
||||
* 支付状态描述
|
||||
*/
|
||||
private String paymentStatusDesc;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
private RefundStatus refundStatus;
|
||||
|
||||
/**
|
||||
* 退款状态描述
|
||||
*/
|
||||
private String refundStatusDesc;
|
||||
|
||||
/**
|
||||
* 总退款金额
|
||||
*/
|
||||
private BigDecimal totalRefundAmount;
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String remarks;
|
||||
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
private Date payTime;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
private Date completeTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 商品数量(订单中商品种类数)
|
||||
*/
|
||||
private Integer itemCount;
|
||||
|
||||
/**
|
||||
* 商品总数量(所有商品数量之和)
|
||||
*/
|
||||
private Integer totalQuantity;
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单分页查询请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderV2PageRequest {
|
||||
|
||||
/**
|
||||
* 页码(从1开始)
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Integer pageSize = 10;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 用户ID(移动端查询时自动设置)
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private String orderStatus;
|
||||
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
private String paymentStatus;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
private String refundStatus;
|
||||
|
||||
/**
|
||||
* 开始创建时间
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 结束创建时间
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
/**
|
||||
* 开始支付时间
|
||||
*/
|
||||
private Date payTimeStart;
|
||||
|
||||
/**
|
||||
* 结束支付时间
|
||||
*/
|
||||
private Date payTimeEnd;
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 支付回调响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class PaymentCallbackResponse {
|
||||
|
||||
/**
|
||||
* 处理是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 支付状态变化类型
|
||||
*/
|
||||
private String statusChangeType;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static PaymentCallbackResponse success(Long orderId, String orderNo, String statusChangeType) {
|
||||
PaymentCallbackResponse response = new PaymentCallbackResponse();
|
||||
response.success = true;
|
||||
response.message = "回调处理成功";
|
||||
response.orderId = orderId;
|
||||
response.orderNo = orderNo;
|
||||
response.statusChangeType = statusChangeType;
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static PaymentCallbackResponse failure(String message) {
|
||||
PaymentCallbackResponse response = new PaymentCallbackResponse();
|
||||
response.success = false;
|
||||
response.message = message;
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应(包含订单信息)
|
||||
*/
|
||||
public static PaymentCallbackResponse failure(String message, Long orderId, String orderNo) {
|
||||
PaymentCallbackResponse response = new PaymentCallbackResponse();
|
||||
response.success = false;
|
||||
response.message = message;
|
||||
response.orderId = orderId;
|
||||
response.orderNo = orderNo;
|
||||
return response;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 获取支付参数请求DTO
|
||||
* 所有参数都是可选的,系统会自动生成商品名称和描述
|
||||
*/
|
||||
@Data
|
||||
public class PaymentParamsRequest {
|
||||
|
||||
// 预留字段,目前所有信息都由系统自动生成
|
||||
// 可以在未来版本中扩展支持自定义参数
|
||||
|
||||
public PaymentParamsRequest() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付参数响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class PaymentParamsResponse {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 支付金额
|
||||
*/
|
||||
private BigDecimal payAmount;
|
||||
|
||||
/**
|
||||
* 是否需要支付(false表示免费订单)
|
||||
*/
|
||||
private Boolean needPay;
|
||||
|
||||
/**
|
||||
* 支付参数(微信小程序调起支付所需的参数)
|
||||
* 包含:appId, timeStamp, nonceStr, package, signType, paySign等
|
||||
*/
|
||||
private Map<String, Object> paymentParams;
|
||||
|
||||
/**
|
||||
* 支付描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String goodsName;
|
||||
|
||||
/**
|
||||
* 创建成功的支付参数响应
|
||||
*/
|
||||
public static PaymentParamsResponse success(Long orderId, String orderNo, BigDecimal payAmount,
|
||||
Boolean needPay, Map<String, Object> paymentParams) {
|
||||
PaymentParamsResponse response = new PaymentParamsResponse();
|
||||
response.orderId = orderId;
|
||||
response.orderNo = orderNo;
|
||||
response.payAmount = payAmount;
|
||||
response.needPay = needPay;
|
||||
response.paymentParams = paymentParams;
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建免费订单的响应
|
||||
*/
|
||||
public static PaymentParamsResponse free(Long orderId, String orderNo) {
|
||||
PaymentParamsResponse response = new PaymentParamsResponse();
|
||||
response.orderId = orderId;
|
||||
response.orderNo = orderNo;
|
||||
response.payAmount = BigDecimal.ZERO;
|
||||
response.needPay = false;
|
||||
response.paymentParams = null;
|
||||
return response;
|
||||
}
|
||||
}
|
52
src/main/java/com/ycwl/basic/order/dto/RefundRequest.java
Normal file
52
src/main/java/com/ycwl/basic/order/dto/RefundRequest.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.ycwl.basic.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 退款申请请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class RefundRequest {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 退款类型
|
||||
*/
|
||||
private String refundType;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
private BigDecimal refundAmount;
|
||||
|
||||
/**
|
||||
* 退款手续费
|
||||
*/
|
||||
private BigDecimal refundFee = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
private String refundReason;
|
||||
|
||||
/**
|
||||
* 退款详细说明
|
||||
*/
|
||||
private String refundDescription;
|
||||
|
||||
/**
|
||||
* 操作备注
|
||||
*/
|
||||
private String operatorRemarks;
|
||||
|
||||
/**
|
||||
* 退款渠道
|
||||
*/
|
||||
private String refundChannel = "ORIGINAL";
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package com.ycwl.basic.order.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.order.enums.DiscountType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单优惠记录表实体V2
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_discount_v2")
|
||||
public class OrderDiscountV2 {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
@TableField("order_id")
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
@TableField("discount_type")
|
||||
private DiscountType discountType;
|
||||
|
||||
/**
|
||||
* 优惠名称
|
||||
*/
|
||||
@TableField("discount_name")
|
||||
private String discountName;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
@TableField("discount_amount")
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 优惠比例
|
||||
*/
|
||||
@TableField("discount_rate")
|
||||
private BigDecimal discountRate;
|
||||
|
||||
/**
|
||||
* 显示顺序
|
||||
*/
|
||||
@TableField("sort_order")
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
@TableField("coupon_id")
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 优惠券码
|
||||
*/
|
||||
@TableField("coupon_code")
|
||||
private String couponCode;
|
||||
|
||||
/**
|
||||
* 券码
|
||||
*/
|
||||
@TableField("voucher_code")
|
||||
private String voucherCode;
|
||||
|
||||
/**
|
||||
* 券码批次ID
|
||||
*/
|
||||
@TableField("voucher_batch_id")
|
||||
private Long voucherBatchId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
}
|
84
src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java
Normal file
84
src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.ycwl.basic.order.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单商品明细表实体V2
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_item_v2")
|
||||
public class OrderItemV2 {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
@TableField("order_id")
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 商品类型
|
||||
*/
|
||||
@TableField("product_type")
|
||||
private String productType;
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
@TableField("product_id")
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
@TableField("product_name")
|
||||
private String productName;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
@TableField("quantity")
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 单价
|
||||
*/
|
||||
@TableField("unit_price")
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
/**
|
||||
* 原始小计
|
||||
*/
|
||||
@TableField("original_amount")
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 最终小计
|
||||
*/
|
||||
@TableField("final_amount")
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
}
|
146
src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java
Normal file
146
src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package com.ycwl.basic.order.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import com.ycwl.basic.order.enums.RefundType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单退款记录表实体V2
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_refund_v2")
|
||||
public class OrderRefundV2 {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
@TableField("order_id")
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 退款单号
|
||||
*/
|
||||
@TableField("refund_no")
|
||||
private String refundNo;
|
||||
|
||||
/**
|
||||
* 退款类型
|
||||
*/
|
||||
@TableField("refund_type")
|
||||
private RefundType refundType;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
@TableField("refund_amount")
|
||||
private BigDecimal refundAmount;
|
||||
|
||||
/**
|
||||
* 退款手续费
|
||||
*/
|
||||
@TableField("refund_fee")
|
||||
private BigDecimal refundFee;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
@TableField("refund_status")
|
||||
private RefundStatus refundStatus;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
@TableField("refund_reason")
|
||||
private String refundReason;
|
||||
|
||||
/**
|
||||
* 退款详细说明
|
||||
*/
|
||||
@TableField("refund_description")
|
||||
private String refundDescription;
|
||||
|
||||
/**
|
||||
* 申请人ID
|
||||
*/
|
||||
@TableField("apply_by")
|
||||
private Long applyBy;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
@TableField("approve_by")
|
||||
private Long approveBy;
|
||||
|
||||
/**
|
||||
* 操作备注
|
||||
*/
|
||||
@TableField("operator_remarks")
|
||||
private String operatorRemarks;
|
||||
|
||||
/**
|
||||
* 支付平台退款单号
|
||||
*/
|
||||
@TableField("payment_refund_id")
|
||||
private String paymentRefundId;
|
||||
|
||||
/**
|
||||
* 退款渠道
|
||||
*/
|
||||
@TableField("refund_channel")
|
||||
private String refundChannel;
|
||||
|
||||
/**
|
||||
* 申请时间
|
||||
*/
|
||||
@TableField("apply_time")
|
||||
private Date applyTime;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
@TableField("approve_time")
|
||||
private Date approveTime;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
@TableField("complete_time")
|
||||
private Date completeTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField("create_by")
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField("update_by")
|
||||
private Long updateBy;
|
||||
}
|
153
src/main/java/com/ycwl/basic/order/entity/OrderV2.java
Normal file
153
src/main/java/com/ycwl/basic/order/entity/OrderV2.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.ycwl.basic.order.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单主表实体V2
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_v2")
|
||||
public class OrderV2 {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
@TableField("order_no")
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
@TableField("member_id")
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 小程序openId
|
||||
*/
|
||||
@TableField("open_id")
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 人脸ID
|
||||
*/
|
||||
@TableField("face_id")
|
||||
private Long faceId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
@TableField("scenic_id")
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 原始金额
|
||||
*/
|
||||
@TableField("original_amount")
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
@TableField("discount_amount")
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 最终金额
|
||||
*/
|
||||
@TableField("final_amount")
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
@TableField("order_status")
|
||||
private OrderStatus orderStatus;
|
||||
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
@TableField("payment_status")
|
||||
private PaymentStatus paymentStatus;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
@TableField("refund_status")
|
||||
private RefundStatus refundStatus;
|
||||
|
||||
/**
|
||||
* 总退款金额
|
||||
*/
|
||||
@TableField("total_refund_amount")
|
||||
private BigDecimal totalRefundAmount;
|
||||
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
@TableField("remarks")
|
||||
private String remarks;
|
||||
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
@TableField("pay_time")
|
||||
private Date payTime;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
@TableField("complete_time")
|
||||
private Date completeTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField("create_by")
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField("update_by")
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
@TableField("deleted")
|
||||
private Integer deleted;
|
||||
|
||||
/**
|
||||
* 删除时间
|
||||
*/
|
||||
@TableField("deleted_at")
|
||||
private Date deletedAt;
|
||||
}
|
60
src/main/java/com/ycwl/basic/order/enums/DiscountType.java
Normal file
60
src/main/java/com/ycwl/basic/order/enums/DiscountType.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.order.enums;
|
||||
|
||||
/**
|
||||
* 优惠类型枚举
|
||||
*/
|
||||
public enum DiscountType {
|
||||
|
||||
/**
|
||||
* 优惠券
|
||||
*/
|
||||
COUPON("COUPON", "优惠券"),
|
||||
|
||||
/**
|
||||
* 券码
|
||||
*/
|
||||
VOUCHER("VOUCHER", "券码"),
|
||||
|
||||
/**
|
||||
* 限时立减
|
||||
*/
|
||||
LIMITED_TIME("LIMITED_TIME", "限时立减"),
|
||||
|
||||
/**
|
||||
* 套餐优惠
|
||||
*/
|
||||
BUNDLE("BUNDLE", "套餐优惠"),
|
||||
|
||||
/**
|
||||
* 一口价优惠
|
||||
*/
|
||||
FIXED_PRICE("FIXED_PRICE", "一口价优惠");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
DiscountType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static DiscountType fromCode(String code) {
|
||||
for (DiscountType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown DiscountType code: " + code);
|
||||
}
|
||||
}
|
70
src/main/java/com/ycwl/basic/order/enums/OrderStatus.java
Normal file
70
src/main/java/com/ycwl/basic/order/enums/OrderStatus.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package com.ycwl.basic.order.enums;
|
||||
|
||||
/**
|
||||
* 订单状态枚举
|
||||
*/
|
||||
public enum OrderStatus {
|
||||
|
||||
/**
|
||||
* 待支付
|
||||
*/
|
||||
PENDING_PAYMENT("PENDING_PAYMENT", "待支付"),
|
||||
|
||||
/**
|
||||
* 已支付
|
||||
*/
|
||||
PAID("PAID", "已支付"),
|
||||
|
||||
/**
|
||||
* 处理中
|
||||
*/
|
||||
PROCESSING("PROCESSING", "处理中"),
|
||||
|
||||
/**
|
||||
* 已完成
|
||||
*/
|
||||
COMPLETED("COMPLETED", "已完成"),
|
||||
|
||||
/**
|
||||
* 已取消
|
||||
*/
|
||||
CANCELLED("CANCELLED", "已取消"),
|
||||
|
||||
/**
|
||||
* 退款中
|
||||
*/
|
||||
REFUNDING("REFUNDING", "退款中"),
|
||||
|
||||
/**
|
||||
* 已退款
|
||||
*/
|
||||
REFUNDED("REFUNDED", "已退款");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
OrderStatus(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static OrderStatus fromCode(String code) {
|
||||
for (OrderStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown OrderStatus code: " + code);
|
||||
}
|
||||
}
|
50
src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java
Normal file
50
src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.ycwl.basic.order.enums;
|
||||
|
||||
/**
|
||||
* 支付状态枚举
|
||||
*/
|
||||
public enum PaymentStatus {
|
||||
|
||||
/**
|
||||
* 未支付
|
||||
*/
|
||||
UNPAID("UNPAID", "未支付"),
|
||||
|
||||
/**
|
||||
* 已支付
|
||||
*/
|
||||
PAID("PAID", "已支付"),
|
||||
|
||||
/**
|
||||
* 已退款
|
||||
*/
|
||||
REFUNDED("REFUNDED", "已退款");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
PaymentStatus(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static PaymentStatus fromCode(String code) {
|
||||
for (PaymentStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown PaymentStatus code: " + code);
|
||||
}
|
||||
}
|
55
src/main/java/com/ycwl/basic/order/enums/RefundStatus.java
Normal file
55
src/main/java/com/ycwl/basic/order/enums/RefundStatus.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.ycwl.basic.order.enums;
|
||||
|
||||
/**
|
||||
* 退款状态枚举
|
||||
*/
|
||||
public enum RefundStatus {
|
||||
|
||||
/**
|
||||
* 无退款
|
||||
*/
|
||||
NO_REFUND("NO_REFUND", "无退款"),
|
||||
|
||||
/**
|
||||
* 部分退款
|
||||
*/
|
||||
PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"),
|
||||
|
||||
/**
|
||||
* 全额退款
|
||||
*/
|
||||
FULL_REFUND("FULL_REFUND", "全额退款"),
|
||||
|
||||
/**
|
||||
* 退款处理中
|
||||
*/
|
||||
REFUND_PROCESSING("REFUND_PROCESSING", "退款处理中");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
RefundStatus(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static RefundStatus fromCode(String code) {
|
||||
for (RefundStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown RefundStatus code: " + code);
|
||||
}
|
||||
}
|
50
src/main/java/com/ycwl/basic/order/enums/RefundType.java
Normal file
50
src/main/java/com/ycwl/basic/order/enums/RefundType.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.ycwl.basic.order.enums;
|
||||
|
||||
/**
|
||||
* 退款类型枚举
|
||||
*/
|
||||
public enum RefundType {
|
||||
|
||||
/**
|
||||
* 全额退款
|
||||
*/
|
||||
FULL_REFUND("FULL_REFUND", "全额退款"),
|
||||
|
||||
/**
|
||||
* 部分退款
|
||||
*/
|
||||
PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"),
|
||||
|
||||
/**
|
||||
* 商品退款
|
||||
*/
|
||||
ITEM_REFUND("ITEM_REFUND", "商品退款");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
RefundType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static RefundType fromCode(String code) {
|
||||
for (RefundType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown RefundType code: " + code);
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.ycwl.basic.order.event;
|
||||
|
||||
/**
|
||||
* 订单事件监听器接口
|
||||
*/
|
||||
public interface OrderEventListener {
|
||||
|
||||
/**
|
||||
* 支付状态变更事件
|
||||
*
|
||||
* @param event 支付状态变更事件
|
||||
*/
|
||||
void onPaymentStatusChanged(PaymentStatusChangeEvent event);
|
||||
|
||||
/**
|
||||
* 退款状态变更事件
|
||||
*
|
||||
* @param event 退款状态变更事件
|
||||
*/
|
||||
void onRefundStatusChanged(RefundStatusChangeEvent event);
|
||||
|
||||
/**
|
||||
* 订单状态变更事件
|
||||
*
|
||||
* @param event 订单状态变更事件
|
||||
*/
|
||||
void onOrderStatusChanged(OrderStatusChangeEvent event);
|
||||
}
|
125
src/main/java/com/ycwl/basic/order/event/OrderEventManager.java
Normal file
125
src/main/java/com/ycwl/basic/order/event/OrderEventManager.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package com.ycwl.basic.order.event;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* 订单事件管理器
|
||||
* 管理事件监听器的注册和事件分发
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OrderEventManager {
|
||||
|
||||
private final List<OrderEventListener> listeners = new ArrayList<>();
|
||||
private final Executor eventExecutor = Executors.newFixedThreadPool(5,
|
||||
r -> new Thread(r, "order-event-thread"));
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("订单事件管理器初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册事件监听器
|
||||
*
|
||||
* @param listener 监听器实例
|
||||
*/
|
||||
public void registerListener(OrderEventListener listener) {
|
||||
if (listener != null && !listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
log.info("注册订单事件监听器: {}", listener.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*
|
||||
* @param listener 监听器实例
|
||||
*/
|
||||
public void removeListener(OrderEventListener listener) {
|
||||
if (listeners.remove(listener)) {
|
||||
log.info("移除订单事件监听器: {}", listener.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布支付状态变更事件
|
||||
*
|
||||
* @param event 支付状态变更事件
|
||||
*/
|
||||
public void publishPaymentStatusChangeEvent(PaymentStatusChangeEvent event) {
|
||||
log.info("发布支付状态变更事件: orderId={}, {} -> {}",
|
||||
event.getOrderId(), event.getOldPaymentStatus(), event.getNewPaymentStatus());
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (OrderEventListener listener : listeners) {
|
||||
try {
|
||||
listener.onPaymentStatusChanged(event);
|
||||
} catch (Exception e) {
|
||||
log.error("处理支付状态变更事件失败: listener={}, orderId={}",
|
||||
listener.getClass().getSimpleName(), event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
}, eventExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布退款状态变更事件
|
||||
*
|
||||
* @param event 退款状态变更事件
|
||||
*/
|
||||
public void publishRefundStatusChangeEvent(RefundStatusChangeEvent event) {
|
||||
log.info("发布退款状态变更事件: orderId={}, refundId={}, {} -> {}",
|
||||
event.getOrderId(), event.getRefundId(),
|
||||
event.getOldRefundStatus(), event.getNewRefundStatus());
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (OrderEventListener listener : listeners) {
|
||||
try {
|
||||
listener.onRefundStatusChanged(event);
|
||||
} catch (Exception e) {
|
||||
log.error("处理退款状态变更事件失败: listener={}, orderId={}, refundId={}",
|
||||
listener.getClass().getSimpleName(), event.getOrderId(), event.getRefundId(), e);
|
||||
}
|
||||
}
|
||||
}, eventExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布订单状态变更事件
|
||||
*
|
||||
* @param event 订单状态变更事件
|
||||
*/
|
||||
public void publishOrderStatusChangeEvent(OrderStatusChangeEvent event) {
|
||||
log.info("发布订单状态变更事件: orderId={}, {} -> {}",
|
||||
event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus());
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (OrderEventListener listener : listeners) {
|
||||
try {
|
||||
listener.onOrderStatusChanged(event);
|
||||
} catch (Exception e) {
|
||||
log.error("处理订单状态变更事件失败: listener={}, orderId={}",
|
||||
listener.getClass().getSimpleName(), event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
}, eventExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的监听器数量
|
||||
*
|
||||
* @return 监听器数量
|
||||
*/
|
||||
public int getListenerCount() {
|
||||
return listeners.size();
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.order.event;
|
||||
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单状态变更事件
|
||||
*/
|
||||
@Data
|
||||
public class OrderStatusChangeEvent {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 旧的订单状态
|
||||
*/
|
||||
private OrderStatus oldOrderStatus;
|
||||
|
||||
/**
|
||||
* 新的订单状态
|
||||
*/
|
||||
private OrderStatus newOrderStatus;
|
||||
|
||||
/**
|
||||
* 变更时间
|
||||
*/
|
||||
private Date changeTime;
|
||||
|
||||
/**
|
||||
* 变更原因
|
||||
*/
|
||||
private String changeReason;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private Long operatorId;
|
||||
|
||||
public OrderStatusChangeEvent(Long orderId, String orderNo,
|
||||
OrderStatus oldStatus, OrderStatus newStatus,
|
||||
String changeReason, Long operatorId) {
|
||||
this.orderId = orderId;
|
||||
this.orderNo = orderNo;
|
||||
this.oldOrderStatus = oldStatus;
|
||||
this.newOrderStatus = newStatus;
|
||||
this.changeReason = changeReason;
|
||||
this.operatorId = operatorId;
|
||||
this.changeTime = new Date();
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.order.event;
|
||||
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付状态变更事件
|
||||
*/
|
||||
@Data
|
||||
public class PaymentStatusChangeEvent {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 旧的支付状态
|
||||
*/
|
||||
private PaymentStatus oldPaymentStatus;
|
||||
|
||||
/**
|
||||
* 新的支付状态
|
||||
*/
|
||||
private PaymentStatus newPaymentStatus;
|
||||
|
||||
/**
|
||||
* 变更时间
|
||||
*/
|
||||
private Date changeTime;
|
||||
|
||||
/**
|
||||
* 变更原因
|
||||
*/
|
||||
private String changeReason;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private Long operatorId;
|
||||
|
||||
public PaymentStatusChangeEvent(Long orderId, String orderNo,
|
||||
PaymentStatus oldStatus, PaymentStatus newStatus,
|
||||
String changeReason, Long operatorId) {
|
||||
this.orderId = orderId;
|
||||
this.orderNo = orderNo;
|
||||
this.oldPaymentStatus = oldStatus;
|
||||
this.newPaymentStatus = newStatus;
|
||||
this.changeReason = changeReason;
|
||||
this.operatorId = operatorId;
|
||||
this.changeTime = new Date();
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
package com.ycwl.basic.order.event;
|
||||
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 退款状态变更事件
|
||||
*/
|
||||
@Data
|
||||
public class RefundStatusChangeEvent {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 退款记录ID
|
||||
*/
|
||||
private Long refundId;
|
||||
|
||||
/**
|
||||
* 退款单号
|
||||
*/
|
||||
private String refundNo;
|
||||
|
||||
/**
|
||||
* 旧的退款状态
|
||||
*/
|
||||
private RefundStatus oldRefundStatus;
|
||||
|
||||
/**
|
||||
* 新的退款状态
|
||||
*/
|
||||
private RefundStatus newRefundStatus;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
private BigDecimal refundAmount;
|
||||
|
||||
/**
|
||||
* 变更时间
|
||||
*/
|
||||
private Date changeTime;
|
||||
|
||||
/**
|
||||
* 变更原因
|
||||
*/
|
||||
private String changeReason;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private Long operatorId;
|
||||
|
||||
public RefundStatusChangeEvent(Long orderId, String orderNo, Long refundId, String refundNo,
|
||||
RefundStatus oldStatus, RefundStatus newStatus,
|
||||
BigDecimal refundAmount, String changeReason, Long operatorId) {
|
||||
this.orderId = orderId;
|
||||
this.orderNo = orderNo;
|
||||
this.refundId = refundId;
|
||||
this.refundNo = refundNo;
|
||||
this.oldRefundStatus = oldStatus;
|
||||
this.newRefundStatus = newStatus;
|
||||
this.refundAmount = refundAmount;
|
||||
this.changeReason = changeReason;
|
||||
this.operatorId = operatorId;
|
||||
this.changeTime = new Date();
|
||||
}
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
package com.ycwl.basic.order.event.impl;
|
||||
|
||||
import com.ycwl.basic.order.event.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 支付状态变更监听器示例
|
||||
* 应用启动后自动注册到事件管理器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PaymentStatusChangeListener implements OrderEventListener {
|
||||
|
||||
@Resource
|
||||
private OrderEventManager orderEventManager;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 应用启动后自动注册到事件管理器
|
||||
orderEventManager.registerListener(this);
|
||||
log.info("支付状态变更监听器注册完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaymentStatusChanged(PaymentStatusChangeEvent event) {
|
||||
log.info("处理支付状态变更事件: orderId={}, orderNo={}, {} -> {}, reason={}",
|
||||
event.getOrderId(), event.getOrderNo(),
|
||||
event.getOldPaymentStatus(), event.getNewPaymentStatus(),
|
||||
event.getChangeReason());
|
||||
|
||||
// 根据支付状态执行相应的业务逻辑
|
||||
switch (event.getNewPaymentStatus()) {
|
||||
case PAID:
|
||||
handlePaymentSuccess(event);
|
||||
break;
|
||||
case REFUNDED:
|
||||
handlePaymentRefunded(event);
|
||||
break;
|
||||
case UNPAID:
|
||||
handlePaymentPending(event);
|
||||
break;
|
||||
default:
|
||||
log.warn("未处理的支付状态: {}", event.getNewPaymentStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefundStatusChanged(RefundStatusChangeEvent event) {
|
||||
log.info("处理退款状态变更事件: orderId={}, refundId={}, {} -> {}",
|
||||
event.getOrderId(), event.getRefundId(),
|
||||
event.getOldRefundStatus(), event.getNewRefundStatus());
|
||||
|
||||
// 可以在这里添加退款相关的业务逻辑
|
||||
// 比如发送通知、更新库存等
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrderStatusChanged(OrderStatusChangeEvent event) {
|
||||
log.info("处理订单状态变更事件: orderId={}, {} -> {}",
|
||||
event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus());
|
||||
|
||||
// 可以在这里添加订单状态相关的业务逻辑
|
||||
// 比如发送用户通知、触发后续流程等
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付成功事件
|
||||
*/
|
||||
private void handlePaymentSuccess(PaymentStatusChangeEvent event) {
|
||||
log.info("处理支付成功: orderId={}", event.getOrderId());
|
||||
|
||||
// 这里可以添加支付成功后的业务逻辑:
|
||||
// 1. 发送支付成功通知
|
||||
// 2. 更新商品库存
|
||||
// 3. 生成相关凭证
|
||||
// 4. 发送短信/邮件通知
|
||||
// 5. 启动履约流程
|
||||
|
||||
// 示例:记录支付成功日志
|
||||
log.info("订单支付成功处理完成: orderId={}, changeTime={}",
|
||||
event.getOrderId(), event.getChangeTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付退款事件
|
||||
*/
|
||||
private void handlePaymentRefunded(PaymentStatusChangeEvent event) {
|
||||
log.info("处理支付退款: orderId={}", event.getOrderId());
|
||||
|
||||
// 这里可以添加支付退款后的业务逻辑:
|
||||
// 1. 恢复商品库存
|
||||
// 2. 取消相关服务
|
||||
// 3. 发送退款通知
|
||||
// 4. 更新会员积分
|
||||
|
||||
// 示例:记录退款处理日志
|
||||
log.info("订单退款处理完成: orderId={}, changeTime={}",
|
||||
event.getOrderId(), event.getChangeTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付待处理事件
|
||||
*/
|
||||
private void handlePaymentPending(PaymentStatusChangeEvent event) {
|
||||
log.info("处理支付待处理: orderId={}", event.getOrderId());
|
||||
|
||||
// 这里可以添加支付待处理的业务逻辑:
|
||||
// 1. 设置支付超时提醒
|
||||
// 2. 预留库存
|
||||
// 3. 发送支付提醒
|
||||
|
||||
// 示例:记录待支付处理日志
|
||||
log.info("订单待支付处理完成: orderId={}, changeTime={}",
|
||||
event.getOrderId(), event.getChangeTime());
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package com.ycwl.basic.order.exception;
|
||||
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
|
||||
/**
|
||||
* 重复购买异常
|
||||
* 当用户尝试购买已经购买过的内容时抛出此异常
|
||||
*/
|
||||
public class DuplicatePurchaseException extends BaseException {
|
||||
|
||||
private final Long existingOrderId;
|
||||
private final String existingOrderNo;
|
||||
private final ProductType productType;
|
||||
private final String productId;
|
||||
|
||||
public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo,
|
||||
ProductType productType, String productId) {
|
||||
super(message);
|
||||
this.existingOrderId = existingOrderId;
|
||||
this.existingOrderNo = existingOrderNo;
|
||||
this.productType = productType;
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo,
|
||||
ProductType productType) {
|
||||
this(message, existingOrderId, existingOrderNo, productType, null);
|
||||
}
|
||||
|
||||
public Long getExistingOrderId() {
|
||||
return existingOrderId;
|
||||
}
|
||||
|
||||
public String getExistingOrderNo() {
|
||||
return existingOrderNo;
|
||||
}
|
||||
|
||||
public ProductType getProductType() {
|
||||
return productType;
|
||||
}
|
||||
|
||||
public String getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取友好的错误消息
|
||||
*/
|
||||
public String getFriendlyMessage() {
|
||||
String productDesc = productType != null ? productType.getDescription() : "商品";
|
||||
if (productId != null) {
|
||||
return String.format("您已购买过该%s(商品ID:%s),订单号:%s", productDesc, productId, existingOrderNo);
|
||||
} else {
|
||||
return String.format("您已购买过%s,订单号:%s", productDesc, existingOrderNo);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.order.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.order.entity.OrderDiscountV2;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单优惠记录表Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderDiscountMapper extends BaseMapper<OrderDiscountV2> {
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.order.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.order.entity.OrderItemV2;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单商品明细表Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderItemMapper extends BaseMapper<OrderItemV2> {
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.order.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.order.entity.OrderRefundV2;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单退款记录表Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderRefundMapper extends BaseMapper<OrderRefundV2> {
|
||||
}
|
12
src/main/java/com/ycwl/basic/order/mapper/OrderV2Mapper.java
Normal file
12
src/main/java/com/ycwl/basic/order/mapper/OrderV2Mapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.order.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.order.entity.OrderV2;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单表Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderV2Mapper extends BaseMapper<OrderV2> {
|
||||
}
|
145
src/main/java/com/ycwl/basic/order/service/IOrderService.java
Normal file
145
src/main/java/com/ycwl/basic/order/service/IOrderService.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.ycwl.basic.order.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.dto.MobileOrderRequest;
|
||||
import com.ycwl.basic.order.dto.*;
|
||||
import com.ycwl.basic.order.entity.OrderV2;
|
||||
import com.ycwl.basic.order.enums.RefundStatus;
|
||||
import com.ycwl.basic.pricing.dto.PriceCalculationResult;
|
||||
import com.ycwl.basic.order.dto.PaymentParamsRequest;
|
||||
import com.ycwl.basic.order.dto.PaymentParamsResponse;
|
||||
import com.ycwl.basic.order.dto.PaymentCallbackResponse;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 订单服务接口
|
||||
*/
|
||||
public interface IOrderService {
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*
|
||||
* @param request 移动端下单请求
|
||||
* @param userId 用户ID
|
||||
* @param scenicId 景区ID
|
||||
* @param priceResult 价格计算结果
|
||||
* @return 订单ID
|
||||
*/
|
||||
Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult);
|
||||
|
||||
/**
|
||||
* 根据订单号查询订单
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @return 订单信息
|
||||
*/
|
||||
OrderV2 getByOrderNo(String orderNo);
|
||||
|
||||
/**
|
||||
* 根据订单ID查询订单
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return 订单信息
|
||||
*/
|
||||
OrderV2 getById(Long orderId);
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param orderStatus 订单状态
|
||||
* @param paymentStatus 支付状态
|
||||
* @return 更新结果
|
||||
*/
|
||||
boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus);
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
*
|
||||
* @return 订单号
|
||||
*/
|
||||
String generateOrderNo();
|
||||
|
||||
// ====== 新增方法 ======
|
||||
|
||||
/**
|
||||
* 分页查询订单列表
|
||||
*
|
||||
* @param request 分页查询请求
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageInfo<OrderV2ListResponse> pageOrders(OrderV2PageRequest request);
|
||||
|
||||
/**
|
||||
* 查询订单详情(包含商品明细、优惠记录、退款记录)
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return 订单详情
|
||||
*/
|
||||
OrderV2DetailResponse getOrderDetail(Long orderId);
|
||||
|
||||
/**
|
||||
* 更新订单备注
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param remarks 备注内容
|
||||
* @return 更新结果
|
||||
*/
|
||||
boolean updateOrderRemarks(Long orderId, String remarks);
|
||||
|
||||
/**
|
||||
* 更新支付状态
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param paymentStatus 支付状态
|
||||
* @return 更新结果
|
||||
*/
|
||||
boolean updatePaymentStatus(Long orderId, String paymentStatus);
|
||||
|
||||
/**
|
||||
* 更新退款状态
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param refundStatus 退款状态
|
||||
* @return 更新结果
|
||||
*/
|
||||
boolean updateRefundStatus(Long orderId, RefundStatus refundStatus);
|
||||
|
||||
/**
|
||||
* 创建退款记录
|
||||
*
|
||||
* @param request 退款申请请求
|
||||
* @return 退款记录ID
|
||||
*/
|
||||
Long createRefundRecord(RefundRequest request);
|
||||
|
||||
/**
|
||||
* 根据用户ID分页查询订单列表(移动端使用)
|
||||
*
|
||||
* @param request 分页查询请求(已设置用户ID)
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageInfo<OrderV2ListResponse> pageOrdersByUser(OrderV2PageRequest request);
|
||||
|
||||
// ====== 支付相关方法 ======
|
||||
|
||||
/**
|
||||
* 获取订单支付参数
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param userId 用户ID(用于权限验证和获取openId)
|
||||
* @param request 支付参数请求
|
||||
* @return 支付参数响应
|
||||
*/
|
||||
PaymentParamsResponse getPaymentParams(Long orderId, Long userId, PaymentParamsRequest request);
|
||||
|
||||
/**
|
||||
* 处理支付回调
|
||||
*
|
||||
* @param scenicId 景区ID
|
||||
* @param request HTTP请求对象
|
||||
* @return 回调处理结果
|
||||
*/
|
||||
PaymentCallbackResponse handlePaymentCallback(Long scenicId, HttpServletRequest request);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -438,7 +438,7 @@ public interface IDiscountDetectionService {
|
||||
@Component
|
||||
public class FlashSaleDiscountProvider implements IDiscountProvider {
|
||||
@Override
|
||||
public String getProviderType() { return "FLASH_SALE"; }
|
||||
public String getProviderType() { return "LIMITED_TIME"; }
|
||||
|
||||
@Override
|
||||
public int getPriority() { return 90; } // 介于券码和优惠券之间
|
||||
|
@@ -0,0 +1,51 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 优惠券领取请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class CouponClaimRequest {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 景区ID(可选,记录在哪个景区领取)
|
||||
*/
|
||||
private String scenicId;
|
||||
|
||||
/**
|
||||
* 领取来源(可选,如:活动页面、签到奖励等)
|
||||
*/
|
||||
private String claimSource;
|
||||
|
||||
public CouponClaimRequest() {
|
||||
}
|
||||
|
||||
public CouponClaimRequest(Long userId, Long couponId) {
|
||||
this.userId = userId;
|
||||
this.couponId = couponId;
|
||||
}
|
||||
|
||||
public CouponClaimRequest(Long userId, Long couponId, String scenicId) {
|
||||
this.userId = userId;
|
||||
this.couponId = couponId;
|
||||
this.scenicId = scenicId;
|
||||
}
|
||||
|
||||
public CouponClaimRequest(Long userId, Long couponId, String scenicId, String claimSource) {
|
||||
this.userId = userId;
|
||||
this.couponId = couponId;
|
||||
this.scenicId = scenicId;
|
||||
this.claimSource = claimSource;
|
||||
}
|
||||
}
|
100
src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java
Normal file
100
src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponClaimRecord;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 优惠券领取结果DTO
|
||||
*/
|
||||
@Data
|
||||
public class CouponClaimResult {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 错误消息(失败时)
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 错误码(失败时)
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 领取记录ID(成功时)
|
||||
*/
|
||||
private Long claimRecordId;
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String couponName;
|
||||
|
||||
/**
|
||||
* 领取时间
|
||||
*/
|
||||
private Date claimTime;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
private String scenicId;
|
||||
|
||||
/**
|
||||
* 创建成功结果
|
||||
*/
|
||||
public static CouponClaimResult success(PriceCouponClaimRecord record, String couponName) {
|
||||
CouponClaimResult result = new CouponClaimResult();
|
||||
result.success = true;
|
||||
result.claimRecordId = record.getId();
|
||||
result.couponId = record.getCouponId();
|
||||
result.couponName = couponName;
|
||||
result.claimTime = record.getClaimTime();
|
||||
result.userId = record.getUserId();
|
||||
result.scenicId = record.getScenicId();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败结果
|
||||
*/
|
||||
public static CouponClaimResult failure(String errorCode, String errorMessage) {
|
||||
CouponClaimResult result = new CouponClaimResult();
|
||||
result.success = false;
|
||||
result.errorCode = errorCode;
|
||||
result.errorMessage = errorMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败结果(仅错误消息)
|
||||
*/
|
||||
public static CouponClaimResult failure(String errorMessage) {
|
||||
return failure("CLAIM_FAILED", errorMessage);
|
||||
}
|
||||
|
||||
// 常用错误码常量
|
||||
public static final String ERROR_COUPON_NOT_FOUND = "COUPON_NOT_FOUND";
|
||||
public static final String ERROR_COUPON_EXPIRED = "COUPON_EXPIRED";
|
||||
public static final String ERROR_COUPON_INACTIVE = "COUPON_INACTIVE";
|
||||
public static final String ERROR_COUPON_OUT_OF_STOCK = "COUPON_OUT_OF_STOCK";
|
||||
public static final String ERROR_ALREADY_CLAIMED = "ALREADY_CLAIMED";
|
||||
public static final String ERROR_INVALID_PARAMS = "INVALID_PARAMS";
|
||||
public static final String ERROR_SYSTEM_ERROR = "SYSTEM_ERROR";
|
||||
}
|
@@ -44,7 +44,7 @@ public class DiscountDetail {
|
||||
detail.setDiscountName("限时立减");
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription("限时优惠,立即享受");
|
||||
detail.setSortOrder(2); // 限时立减排在券码后面
|
||||
detail.setSortOrder(1); // 限时立减排在最前面
|
||||
return detail;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class DiscountDetail {
|
||||
detail.setDiscountName("券码优惠");
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription(String.format("券码 %s - %s", voucherCode, discountTypeName));
|
||||
detail.setSortOrder(1); // 券码优先级最高,排在最前面
|
||||
detail.setSortOrder(2); // 券码显示顺序低于限时立减
|
||||
return detail;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,62 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 移动端价格计算请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class MobilePriceCalculationRequest {
|
||||
|
||||
/**
|
||||
* 商品列表
|
||||
*/
|
||||
private List<ProductItem> 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;
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 券码信息DTO
|
||||
@@ -81,4 +83,10 @@ public class VoucherInfo {
|
||||
* 不可用原因
|
||||
*/
|
||||
private String unavailableReason;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -1,8 +1,10 @@
|
||||
package com.ycwl.basic.pricing.dto.req;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VoucherBatchCreateReq {
|
||||
@@ -12,4 +14,10 @@ public class VoucherBatchCreateReq {
|
||||
private Integer discountType;
|
||||
private BigDecimal discountValue;
|
||||
private Integer totalCount;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null或空列表表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -1,11 +1,13 @@
|
||||
package com.ycwl.basic.pricing.dto.req;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 打印小票请求
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class VoucherPrintReq {
|
||||
|
||||
private Long faceId;
|
||||
|
@@ -1,9 +1,11 @@
|
||||
package com.ycwl.basic.pricing.dto.resp;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VoucherBatchResp {
|
||||
@@ -22,4 +24,10 @@ public class VoucherBatchResp {
|
||||
private String statusName;
|
||||
private Date createTime;
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -14,6 +14,7 @@ public class VoucherPrintResp {
|
||||
* 流水号
|
||||
*/
|
||||
private String code;
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 券码
|
||||
|
@@ -4,18 +4,27 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 券码批次配置实体
|
||||
*/
|
||||
@Data
|
||||
@Slf4j
|
||||
@TableName("price_voucher_batch_config")
|
||||
public class PriceVoucherBatchConfig {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@@ -44,6 +53,19 @@ public class PriceVoucherBatchConfig {
|
||||
*/
|
||||
private BigDecimal discountValue;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表(JSON字符串,数据库字段)
|
||||
*/
|
||||
@TableField("applicable_products")
|
||||
private String applicableProductsJson;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表(业务字段)
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<ProductType> applicableProducts;
|
||||
|
||||
/**
|
||||
* 总券码数量
|
||||
*/
|
||||
@@ -77,4 +99,73 @@ public class PriceVoucherBatchConfig {
|
||||
private Integer deleted;
|
||||
|
||||
private Date deletedAt;
|
||||
|
||||
/**
|
||||
* 获取适用商品类型列表
|
||||
*/
|
||||
public List<ProductType> getApplicableProducts() {
|
||||
if (this.applicableProducts == null && this.applicableProductsJson != null) {
|
||||
this.applicableProducts = parseApplicableProductsFromJson(this.applicableProductsJson);
|
||||
}
|
||||
return this.applicableProducts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置适用商品类型列表
|
||||
*/
|
||||
public void setApplicableProducts(List<ProductType> applicableProducts) {
|
||||
this.applicableProducts = applicableProducts;
|
||||
this.applicableProductsJson = convertApplicableProductsToJson(applicableProducts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON字符串解析商品类型列表
|
||||
*/
|
||||
private List<ProductType> parseApplicableProductsFromJson(String json) {
|
||||
if (json == null || json.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
|
||||
List<String> codeList = objectMapper.readValue(json, typeReference);
|
||||
if (codeList == null || codeList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return codeList.stream()
|
||||
.map(code -> {
|
||||
try {
|
||||
return ProductType.fromCode(code);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("未知的商品类型代码: {}", code);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(type -> type != null)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("解析适用商品类型JSON失败,JSON: {}", json, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将商品类型列表转换为JSON字符串
|
||||
*/
|
||||
private String convertApplicableProductsToJson(List<ProductType> products) {
|
||||
if (products == null || products.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> codeList = products.stream()
|
||||
.map(ProductType::getCode)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
return objectMapper.writeValueAsString(codeList);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("转换适用商品类型为JSON失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
package com.ycwl.basic.pricing.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品类型列表类型处理器
|
||||
* 用于处理券码适用商品类型的JSON序列化和反序列化
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProductTypeListTypeHandler extends BaseTypeHandler<List<ProductType>> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, List<ProductType> parameter, JdbcType jdbcType) throws SQLException {
|
||||
try {
|
||||
// 将ProductType枚举转换为字符串列表
|
||||
List<String> codeList = parameter.stream()
|
||||
.map(ProductType::getCode)
|
||||
.toList();
|
||||
String json = objectMapper.writeValueAsString(codeList);
|
||||
ps.setString(i, json);
|
||||
log.debug("序列化商品类型列表: {}", json);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("序列化商品类型列表失败", e);
|
||||
throw new SQLException("序列化商品类型列表失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductType> getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String json = rs.getString(columnName);
|
||||
return parseJson(json, columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductType> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String json = rs.getString(columnIndex);
|
||||
return parseJson(json, "columnIndex:" + columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductType> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String json = cs.getString(columnIndex);
|
||||
return parseJson(json, "columnIndex:" + columnIndex);
|
||||
}
|
||||
|
||||
private List<ProductType> parseJson(String json, String source) {
|
||||
if (json == null || json.trim().isEmpty()) {
|
||||
log.debug("从{}获取的JSON为空,返回null表示适用所有商品类型", source);
|
||||
return null; // null表示适用所有商品类型
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> codeList = objectMapper.readValue(json, typeReference);
|
||||
if (codeList == null || codeList.isEmpty()) {
|
||||
log.debug("从{}反序列化得到空列表,返回null表示适用所有商品类型", source);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProductType> result = new ArrayList<>();
|
||||
for (String code : codeList) {
|
||||
try {
|
||||
ProductType productType = ProductType.fromCode(code);
|
||||
result.add(productType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("从{}反序列化时发现未知商品类型代码: {}", source, code);
|
||||
// 忽略未知的商品类型代码,继续处理其他类型
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("从{}反序列化商品类型列表成功,数量: {}", source, result.size());
|
||||
return result.isEmpty() ? null : result;
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("从{}反序列化商品类型列表失败,JSON: {}", source, json, e);
|
||||
// 解析失败时返回null,表示适用所有商品类型
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -44,4 +44,11 @@ public interface PriceVoucherBatchConfigMapper extends BaseMapper<PriceVoucherBa
|
||||
* @return 统计信息
|
||||
*/
|
||||
PriceVoucherBatchConfig selectBatchStats(@Param("batchId") Long batchId);
|
||||
|
||||
/**
|
||||
* 根据券码查询对应的券码批次配置
|
||||
* @param voucherCode 券码
|
||||
* @return 券码批次配置
|
||||
*/
|
||||
PriceVoucherBatchConfig selectByVoucherCode(@Param("voucherCode") String voucherCode);
|
||||
}
|
@@ -3,6 +3,8 @@ package com.ycwl.basic.pricing.service;
|
||||
import com.ycwl.basic.pricing.dto.CouponInfo;
|
||||
import com.ycwl.basic.pricing.dto.CouponUseRequest;
|
||||
import com.ycwl.basic.pricing.dto.CouponUseResult;
|
||||
import com.ycwl.basic.pricing.dto.CouponClaimRequest;
|
||||
import com.ycwl.basic.pricing.dto.CouponClaimResult;
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponConfig;
|
||||
|
||||
@@ -59,4 +61,12 @@ public interface ICouponService {
|
||||
* @return 可用优惠券列表
|
||||
*/
|
||||
List<CouponInfo> getUserAvailableCoupons(Long userId);
|
||||
|
||||
/**
|
||||
* 领取优惠券(内部调用方法)
|
||||
*
|
||||
* @param request 领取请求
|
||||
* @return 领取结果
|
||||
*/
|
||||
CouponClaimResult claimCoupon(CouponClaimRequest request);
|
||||
}
|
@@ -14,7 +14,7 @@ public interface IDiscountProvider {
|
||||
|
||||
/**
|
||||
* 获取提供者类型
|
||||
* @return 提供者类型标识,如 "COUPON", "VOUCHER", "FLASH_SALE" 等
|
||||
* @return 提供者类型标识,如 "COUPON", "VOUCHER", "LIMITED_TIME" 等
|
||||
*/
|
||||
String getProviderType();
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user