1.商品下单优化秒杀订单以秒杀价格为准
2.修改水票套票释放逻辑,个人水票发放以次月以1日凌晨为时间节点 3.增加以订单号形式发送水票套票信息
This commit is contained in:
@@ -2,6 +2,7 @@ package com.gxwebsoft.glt.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
|
||||||
import com.gxwebsoft.glt.entity.GltTicketTemplate;
|
import com.gxwebsoft.glt.entity.GltTicketTemplate;
|
||||||
import com.gxwebsoft.glt.entity.GltUserTicket;
|
import com.gxwebsoft.glt.entity.GltUserTicket;
|
||||||
import com.gxwebsoft.glt.entity.GltUserTicketLog;
|
import com.gxwebsoft.glt.entity.GltUserTicketLog;
|
||||||
@@ -12,17 +13,18 @@ import com.gxwebsoft.shop.service.ShopOrderGoodsService;
|
|||||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 套票发放(从订单生成用户套票 + 释放计划)的业务逻辑。
|
* 套票发放(从订单生成用户套票 + 释放计划)的业务逻辑。
|
||||||
@@ -128,6 +130,57 @@ public class GltTicketIssueService {
|
|||||||
tenantId, uniqueGoodsIds, orders.size(), success, skipped, failed);
|
tenantId, uniqueGoodsIds, orders.size(), success, skipped, failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
// @Scheduled(cron = "0/1 * 4-22 * * ?") 没秒钟执行一次
|
||||||
|
public void paySuccessExecute(String orderNo, Integer tenantId){
|
||||||
|
suerTicketRelease(orderNo, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单支付成功,直接发送水票【后期优化订单类型,为水票的订单才需要执行此业务】
|
||||||
|
* @param orderNo 订单号
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
*/
|
||||||
|
public void suerTicketRelease(String orderNo, Integer tenantId){
|
||||||
|
//1.订单为空跳过执行
|
||||||
|
ShopOrder shopOrder = shopOrderService.getByOrderNo(orderNo, tenantId);
|
||||||
|
if(shopOrder == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//2.跳过已完成发放套票订单
|
||||||
|
if(shopOrder.getOrderStatus() == 1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//3.订单商品为空跳过执行
|
||||||
|
List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(shopOrder.getOrderId());
|
||||||
|
if (CollectionUtils.isEmpty(goodsList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//4.执行水票发放业务【】
|
||||||
|
AtomicBoolean release = new AtomicBoolean(false);
|
||||||
|
goodsList.forEach(orderGood ->{
|
||||||
|
IssueOutcome outcome = transactionTemplate.execute(status -> doIssueOne(tenantId, shopOrder, orderGood));
|
||||||
|
if(Arrays.asList(IssueOutcome.ISSUED, IssueOutcome.ALREADY_ISSUED).contains(outcome)){
|
||||||
|
release.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//5.更新商品订单为已完成、已收到赠品状态
|
||||||
|
if (release.get()) {
|
||||||
|
shopOrderService.update(new LambdaUpdateWrapper<ShopOrder>()
|
||||||
|
.eq(ShopOrder::getOrderId, shopOrder.getOrderId())
|
||||||
|
.eq(ShopOrder::getTenantId, tenantId)
|
||||||
|
.eq(ShopOrder::getOrderStatus, 0)
|
||||||
|
.set(ShopOrder::getOrderStatus, 1)
|
||||||
|
.set(ShopOrder::getHasTakeGift, true)
|
||||||
|
.set(ShopOrder::getUpdateTime, LocalDateTime.now())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int issueForOrder(Integer tenantId, Set<Integer> goodsIds, ShopOrder order) {
|
private int issueForOrder(Integer tenantId, Set<Integer> goodsIds, ShopOrder order) {
|
||||||
List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId());
|
List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId());
|
||||||
if (goodsList == null || goodsList.isEmpty()) {
|
if (goodsList == null || goodsList.isEmpty()) {
|
||||||
@@ -304,12 +357,14 @@ public class GltTicketIssueService {
|
|||||||
|
|
||||||
// 若启用了 releasePeriods 且首期释放时机为“支付成功当刻”,则将首期释放量直接计入可用,
|
// 若启用了 releasePeriods 且首期释放时机为“支付成功当刻”,则将首期释放量直接计入可用,
|
||||||
// 避免用户刚购买后短时间内无可用水票;后续期数仍由自动释放任务按 release_time 释放。
|
// 避免用户刚购买后短时间内无可用水票;后续期数仍由自动释放任务按 release_time 释放。
|
||||||
if (useReleasePeriods && !releases.isEmpty() && !Objects.equals(template.getFirstReleaseMode(), 1)) {
|
// if (useReleasePeriods && !releases.isEmpty() && !Objects.equals(template.getFirstReleaseMode(), 1)) {
|
||||||
|
if (!releases.isEmpty() && !Objects.equals(template.getFirstReleaseMode(), 1)) {
|
||||||
GltUserTicketRelease first = releases.get(0);
|
GltUserTicketRelease first = releases.get(0);
|
||||||
Integer firstQtyObj = first.getReleaseQty();
|
Integer firstQtyObj = first.getReleaseQty();
|
||||||
LocalDateTime firstTime = first.getReleaseTime();
|
LocalDateTime firstTime = first.getReleaseTime();
|
||||||
int firstQty = firstQtyObj != null ? firstQtyObj : 0;
|
int firstQty = firstQtyObj != null ? firstQtyObj : 0;
|
||||||
if (firstQty > 0 && (firstTime == null || !firstTime.isAfter(now))) {
|
// if (firstQty > 0 && (firstTime == null || !firstTime.isAfter(now))) {
|
||||||
|
if (firstQty > 0) {
|
||||||
first.setStatus(1);
|
first.setStatus(1);
|
||||||
first.setUpdateTime(now);
|
first.setUpdateTime(now);
|
||||||
|
|
||||||
@@ -376,10 +431,13 @@ public class GltTicketIssueService {
|
|||||||
|
|
||||||
// 首期释放时间
|
// 首期释放时间
|
||||||
LocalDateTime firstReleaseTime;
|
LocalDateTime firstReleaseTime;
|
||||||
|
LocalDateTime referenceTime;
|
||||||
if (Objects.equals(template.getFirstReleaseMode(), 1)) {
|
if (Objects.equals(template.getFirstReleaseMode(), 1)) {
|
||||||
firstReleaseTime = nextMonthSameDay(baseTime);
|
firstReleaseTime = nextMonthSameDay(baseTime);
|
||||||
|
referenceTime = firstReleaseTime.withDayOfMonth(1).toLocalDate().atStartOfDay();
|
||||||
} else {
|
} else {
|
||||||
firstReleaseTime = baseTime;
|
firstReleaseTime = baseTime;
|
||||||
|
referenceTime = firstReleaseTime.withDayOfMonth(1).toLocalDate().atStartOfDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每期释放数量计算
|
// 每期释放数量计算
|
||||||
@@ -393,7 +451,11 @@ public class GltTicketIssueService {
|
|||||||
if (qty <= 0) {
|
if (qty <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
list.add(buildRelease(userTicket, i, qty, firstReleaseTime.plusMonths(i), now));
|
if(i == 0){
|
||||||
|
list.add(buildRelease(userTicket, i, qty, firstReleaseTime, now));
|
||||||
|
}else {
|
||||||
|
list.add(buildRelease(userTicket, i, qty, referenceTime.plusMonths(i), now));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -410,7 +472,11 @@ public class GltTicketIssueService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
remaining -= qty;
|
remaining -= qty;
|
||||||
list.add(buildRelease(userTicket, i, qty, firstReleaseTime.plusMonths(i), now));
|
if(i == 0){
|
||||||
|
list.add(buildRelease(userTicket, i, qty, firstReleaseTime, now));
|
||||||
|
}else {
|
||||||
|
list.add(buildRelease(userTicket, i, qty, referenceTime.plusMonths(i), now));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.gxwebsoft.common.core.utils.WechatCertAutoConfig;
|
|||||||
import com.gxwebsoft.common.core.utils.WechatPayConfigValidator;
|
import com.gxwebsoft.common.core.utils.WechatPayConfigValidator;
|
||||||
import com.gxwebsoft.common.core.web.BaseController;
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
import com.gxwebsoft.common.system.entity.Payment;
|
import com.gxwebsoft.common.system.entity.Payment;
|
||||||
|
import com.gxwebsoft.glt.service.GltTicketIssueService;
|
||||||
import com.gxwebsoft.shop.entity.ShopOrderDelivery;
|
import com.gxwebsoft.shop.entity.ShopOrderDelivery;
|
||||||
import com.gxwebsoft.shop.entity.ShopUserAddress;
|
import com.gxwebsoft.shop.entity.ShopUserAddress;
|
||||||
import com.gxwebsoft.shop.service.*;
|
import com.gxwebsoft.shop.service.*;
|
||||||
@@ -110,6 +111,8 @@ public class ShopOrderController extends BaseController {
|
|||||||
private GltTicketRevokeService gltTicketRevokeService;
|
private GltTicketRevokeService gltTicketRevokeService;
|
||||||
@Resource
|
@Resource
|
||||||
private ShopDealerCommissionRollbackService shopDealerCommissionRollbackService;
|
private ShopDealerCommissionRollbackService shopDealerCommissionRollbackService;
|
||||||
|
@Resource
|
||||||
|
private GltTicketIssueService gltTicketIssueService;
|
||||||
|
|
||||||
@Operation(summary = "分页查询订单")
|
@Operation(summary = "分页查询订单")
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
@@ -763,7 +766,7 @@ public class ShopOrderController extends BaseController {
|
|||||||
@Schema(description = "异步通知11")
|
@Schema(description = "异步通知11")
|
||||||
@PostMapping("/notify/{tenantId}")
|
@PostMapping("/notify/{tenantId}")
|
||||||
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) {
|
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) {
|
||||||
logger.info("异步通知*************** = " + tenantId);
|
logger.info("异步通知*************** = " + body + ",租户:" +tenantId);
|
||||||
|
|
||||||
// 获取支付配置信息用于解密
|
// 获取支付配置信息用于解密
|
||||||
String key = "Payment:1:".concat(tenantId.toString());
|
String key = "Payment:1:".concat(tenantId.toString());
|
||||||
@@ -899,7 +902,7 @@ public class ShopOrderController extends BaseController {
|
|||||||
logger.info("开始解析微信支付异步通知...");
|
logger.info("开始解析微信支付异步通知...");
|
||||||
Transaction transaction = parser.parse(requestParam, Transaction.class);
|
Transaction transaction = parser.parse(requestParam, Transaction.class);
|
||||||
logger.info("✅ 异步通知解析成功 - 交易状态: {}, 商户订单号: {}",
|
logger.info("✅ 异步通知解析成功 - 交易状态: {}, 商户订单号: {}",
|
||||||
transaction.getTradeStateDesc(), transaction.getOutTradeNo());
|
transaction.getTradeState(), transaction.getOutTradeNo());
|
||||||
|
|
||||||
// 使用枚举值判断支付状态,避免依赖状态描述字符串
|
// 使用枚举值判断支付状态,避免依赖状态描述字符串
|
||||||
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
|
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
|
||||||
@@ -934,6 +937,10 @@ public class ShopOrderController extends BaseController {
|
|||||||
System.out.println("实际付款金额 = " + order.getPayPrice());
|
System.out.println("实际付款金额 = " + order.getPayPrice());
|
||||||
// 更新订单状态并处理支付成功后的业务逻辑(包括累加商品销量)
|
// 更新订单状态并处理支付成功后的业务逻辑(包括累加商品销量)
|
||||||
shopOrderService.updateByOutTradeNo(order);
|
shopOrderService.updateByOutTradeNo(order);
|
||||||
|
|
||||||
|
// //支付成功执行一步任务
|
||||||
|
// gltTicketIssueService.paySuccessExecute(order.getOrderNo(), tenantId);
|
||||||
|
|
||||||
return "SUCCESS";
|
return "SUCCESS";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class OrderCreateRequest {
|
|||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
@Schema(description = "秒杀活动ID")
|
@Schema(description = "秒杀活动ID")
|
||||||
private Long activityId;
|
private Integer activityId;
|
||||||
|
|
||||||
@Schema(description = "订单商品列表")
|
@Schema(description = "订单商品列表")
|
||||||
@Valid
|
@Valid
|
||||||
@@ -161,6 +161,9 @@ public class OrderCreateRequest {
|
|||||||
@Data
|
@Data
|
||||||
@Schema(name = "OrderGoodsItem", description = "订单商品项")
|
@Schema(name = "OrderGoodsItem", description = "订单商品项")
|
||||||
public static class OrderGoodsItem {
|
public static class OrderGoodsItem {
|
||||||
|
@Schema(description = "秒杀活动ID")
|
||||||
|
private Integer activityId;
|
||||||
|
|
||||||
@Schema(description = "商品ID", required = true)
|
@Schema(description = "商品ID", required = true)
|
||||||
@NotNull(message = "商品ID不能为空")
|
@NotNull(message = "商品ID不能为空")
|
||||||
private Integer goodsId;
|
private Integer goodsId;
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package com.gxwebsoft.shop.service;
|
|||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||||
|
import com.gxwebsoft.common.core.exception.enums.GlobalErrorCodeConstants;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
||||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||||
import com.gxwebsoft.shop.entity.*;
|
import com.gxwebsoft.shop.entity.*;
|
||||||
|
import com.gxwebsoft.shop.mapper.ShopFlashSaleActivityMapper;
|
||||||
import com.gxwebsoft.shop.service.ShopStoreFenceService;
|
import com.gxwebsoft.shop.service.ShopStoreFenceService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@@ -52,11 +54,16 @@ public class OrderBusinessService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ShopUserAddressService shopUserAddressService;
|
private ShopUserAddressService shopUserAddressService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ShopUserCouponService shopUserCouponService;
|
private ShopUserCouponService shopUserCouponService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ShopStoreFenceService shopStoreFenceService;
|
private ShopStoreFenceService shopStoreFenceService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ShopFlashSaleActivityMapper shopFlashSaleActivityMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建订单
|
* 创建订单
|
||||||
*
|
*
|
||||||
@@ -167,6 +174,8 @@ public class OrderBusinessService {
|
|||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
|
|
||||||
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
|
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
|
||||||
|
Integer activityId = item.getActivityId();
|
||||||
|
|
||||||
// 验证商品ID
|
// 验证商品ID
|
||||||
if (item.getGoodsId() == null) {
|
if (item.getGoodsId() == null) {
|
||||||
throw new BusinessException("商品ID不能为空");
|
throw new BusinessException("商品ID不能为空");
|
||||||
@@ -216,6 +225,25 @@ public class OrderBusinessService {
|
|||||||
productName = goods.getName() + "(" + (item.getSpecInfo() != null ? item.getSpecInfo() : sku.getSku()) + ")";
|
productName = goods.getName() + "(" + (item.getSpecInfo() != null ? item.getSpecInfo() : sku.getSku()) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//秒杀商品价格以秒杀价为准
|
||||||
|
if(activityId != null){
|
||||||
|
request.setActivityId(activityId);
|
||||||
|
ShopFlashSaleActivity saleActivity = shopFlashSaleActivityMapper.selectById(activityId);
|
||||||
|
if(saleActivity == null){
|
||||||
|
throw new BusinessException("秒杀活动数据查询失败!");
|
||||||
|
}
|
||||||
|
if(saleActivity.getStatus() != 0){
|
||||||
|
throw new BusinessException("当前秒杀活动已失效!");
|
||||||
|
}
|
||||||
|
if(saleActivity.getStock() <= 0){
|
||||||
|
throw new BusinessException("当前秒杀活动商品已售罄!");
|
||||||
|
}
|
||||||
|
if(item.getQuantity() > saleActivity.getSaleLimit()){
|
||||||
|
throw new BusinessException("选购数量已超秒杀活动限购数量!");
|
||||||
|
}
|
||||||
|
actualPrice = saleActivity.getPrice();
|
||||||
|
}
|
||||||
|
|
||||||
// 验证实际价格
|
// 验证实际价格
|
||||||
if (actualPrice == null || actualPrice.compareTo(BigDecimal.ZERO) <= 0) {
|
if (actualPrice == null || actualPrice.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
throw new BusinessException("商品价格异常:" + productName);
|
throw new BusinessException("商品价格异常:" + productName);
|
||||||
@@ -651,6 +679,13 @@ public class OrderBusinessService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//秒杀商品价格以秒杀价为准
|
||||||
|
Integer activityId = item.getActivityId();
|
||||||
|
if(activityId != null){
|
||||||
|
ShopFlashSaleActivity saleActivity = shopFlashSaleActivityMapper.selectById(activityId);
|
||||||
|
actualPrice = saleActivity.getPrice();
|
||||||
|
}
|
||||||
|
|
||||||
// 验证库存
|
// 验证库存
|
||||||
if (actualStock == null || actualStock < item.getQuantity()) {
|
if (actualStock == null || actualStock < item.getQuantity()) {
|
||||||
String stockMsg = sku != null ? "商品规格库存不足" : "商品库存不足";
|
String stockMsg = sku != null ? "商品规格库存不足" : "商品库存不足";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ spring:
|
|||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mysql://47.107.249.41:13306/modules?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
|
url: jdbc:mysql://47.107.249.41:13306/modules?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
|
||||||
username: modules
|
username: modules
|
||||||
password: tYmmMGh5wpwXR3ae2
|
password: tYmmMGh5wpwXR3ae
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user