砍价过期逻辑 补充和优化

This commit is contained in:
Jack 2025-11-20 22:13:40 +08:00
parent 528c76a912
commit db5bd06c35
9 changed files with 567 additions and 118 deletions

View File

@ -11,7 +11,6 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -492,7 +491,7 @@ public class DateTimeUtils {
return -1; return -1;
} }
} }
/** /**
* 判断指定时间是否在两个时间点之间包含边界 * 判断指定时间是否在两个时间点之间包含边界
* *
@ -602,6 +601,27 @@ public class DateTimeUtils {
} }
} }
public static boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime) {
log.debug("检查活动时间有效性 - 开始时间: {}, 结束时间: {}, 检查时间: {}", starTime, endTime, checkTime);
if (starTime == null || endTime == null) {
log.error("活动时间验证失败 - 开始时间或结束时间为null. 开始时间: {}, 结束时间: {}", starTime, endTime);
return false;
}
if (checkTime == null) {
checkTime = new Date();
log.warn("检查时间为空,使用当前时间: {}", checkTime);
}
boolean isValid = !checkTime.before(starTime) && !checkTime.after(endTime);
log.debug("活动时间验证结果: {} (检查时间>=开始时间, 检查时间<=结束时间)",
isValid);
return isValid;
}
public static void main(String[] args) { public static void main(String[] args) {
// System.out.println(convertLklDate("2021-02-19")); // 2025-01-02 // System.out.println(convertLklDate("2021-02-19")); // 2025-01-02
// System.out.println(convertLklDate("2021-2-3")); // 2025-01-02 // System.out.println(convertLklDate("2021-2-3")); // 2025-01-02
@ -632,29 +652,29 @@ public class DateTimeUtils {
// System.out.println("当前时间是否在工作时间内:" + isWorkTime); // System.out.println("当前时间是否在工作时间内:" + isWorkTime);
// System.out.println("多个时间段的交集结果:" + isNight); // System.out.println("多个时间段的交集结果:" + isNight);
//
System.out.println("=== 测试 findTimeIntersection ==="); // System.out.println("=== 测试 findTimeIntersection ===");
//
// 测试正常交集情况 // // 测试正常交集情况
List<Pair<String, String>> timeList1 = new ArrayList<>(); // List<Pair<String, String>> timeList1 = new ArrayList<>();
timeList1.add(Pair.of("06:00", "17:00")); // timeList1.add(Pair.of("06:00", "17:00"));
timeList1.add(Pair.of("10:00", "18:00")); // timeList1.add(Pair.of("10:00", "18:00"));
//
Pair<String, String> intersection1 = findTimeInterSection(timeList1); // Pair<String, String> intersection1 = findTimeInterSection(timeList1);
System.out.println("交集结果1: " + intersection1); // 应该是 10:00 - 17:00 // System.out.println("交集结果1: " + intersection1); // 应该是 10:00 - 17:00
//
// 测试无交集情况 //// 测试无交集情况
List<Pair<String, String>> timeList2 = new ArrayList<>(); // List<Pair<String, String>> timeList2 = new ArrayList<>();
timeList2.add(Pair.of("09:00", "12:00")); // timeList2.add(Pair.of("09:00", "12:00"));
timeList2.add(Pair.of("13:00", "17:00")); // timeList2.add(Pair.of("13:00", "17:00"));
//
Pair<String, String> intersection2 = findTimeInterSection(timeList2); // Pair<String, String> intersection2 = findTimeInterSection(timeList2);
System.out.println("交集结果2: " + intersection2); // 应该是null // System.out.println("交集结果2: " + intersection2); // 应该是null
//
// 测试空列表 //// 测试空列表
Pair<String, String> intersection3 = findTimeInterSection(null); // Pair<String, String> intersection3 = findTimeInterSection(null);
System.out.println("交集结果3 (null输入): " + intersection3); // 应该是null // System.out.println("交集结果3 (null输入): " + intersection3); // 应该是null
//
} }
} }

View File

@ -0,0 +1,349 @@
package com.suisung.mall.common.utils;
import com.suisung.mall.core.web.service.RedisService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* DistributedLockHelper综合测试套件
*/
public class DistributedLockHelperTest {
private DistributedLockHelper lockHelper;
@Mock
private RedisService redisService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
lockHelper = new DistributedLockHelper();
ReflectionTestUtils.setField(lockHelper, "redisService", redisService);
System.out.println("=== 初始化测试环境 ===");
}
@Test
public void testTryLock_Success() {
System.out.println("开始执行: testTryLock_Success");
// 给定条件
String lockKey = "test_lock";
long expireTimeSec = 30L;
String expectedKey = "distributed_lock:" + lockKey;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "");
// 当调用时
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
boolean result = lockHelper.tryLock(lockKey, expireTimeSec);
System.out.println("获取锁结果: " + (result ? "成功" : "失败"));
// 那么验证
assertTrue(result, "锁应该成功获取");
verify(redisService).set(eq(expectedKey), anyString(), eq(expireTimeSec));
System.out.println("验证通过: Redis set 方法被正确调用");
System.out.println("测试完成: testTryLock_Success\n");
}
@Test
public void testTryLock_Failure() {
System.out.println("开始执行: testTryLock_Failure");
// 给定条件
String lockKey = "test_lock";
long expireTimeSec = 30L;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "");
// 当调用时
when(redisService.set(anyString(), anyString(), anyLong())).thenThrow(new RuntimeException("Redis错误"));
System.out.println("模拟Redis异常: Redis错误");
// 那么验证
assertThrows(RuntimeException.class, () -> {
lockHelper.tryLock(lockKey, expireTimeSec);
}, "应该抛出RuntimeException");
System.out.println("验证通过: 正确捕获了RuntimeException");
System.out.println("测试完成: testTryLock_Failure\n");
}
@Test
public void testReleaseLock_Success() {
System.out.println("开始执行: testReleaseLock_Success");
// 给定条件
String lockKey = "test_lock";
String expectedKey = "distributed_lock:" + lockKey;
String lockValue = "uuid:1";
System.out.println("测试参数 - 锁键: " + lockKey + ", 锁值: " + lockValue);
// 设置线程本地变量
ReflectionTestUtils.setField(lockHelper, "LOCK_VALUE_HOLDER",
new ThreadLocal<String>() {
@Override
protected String initialValue() {
return lockValue;
}
});
when(redisService.get(expectedKey)).thenReturn(lockValue);
System.out.println("模拟Redis get返回值: " + lockValue);
// 当调用时
lockHelper.releaseLock(lockKey);
System.out.println("执行锁释放操作");
// 那么验证
verify(redisService).del(expectedKey);
System.out.println("验证通过: Redis del 方法被正确调用");
System.out.println("测试完成: testReleaseLock_Success\n");
}
@Test
public void testReleaseLock_WrongOwner() {
System.out.println("开始执行: testReleaseLock_WrongOwner");
// 给定条件
String lockKey = "test_lock";
String expectedKey = "distributed_lock:" + lockKey;
String currentValue = "uuid:1";
String threadValue = "uuid:2"; // 不同的值
System.out.println("测试参数 - 当前锁值: " + currentValue + ", 线程锁值: " + threadValue);
// 设置线程本地变量
ReflectionTestUtils.setField(lockHelper, "LOCK_VALUE_HOLDER",
new ThreadLocal<String>() {
@Override
protected String initialValue() {
return threadValue;
}
});
when(redisService.get(expectedKey)).thenReturn(currentValue);
System.out.println("模拟Redis中当前锁值: " + currentValue);
// 当调用时
lockHelper.releaseLock(lockKey);
System.out.println("执行锁释放操作");
// 那么验证
verify(redisService, never()).del(anyString());
System.out.println("验证通过: Redis del 方法未被调用(防止误删)");
System.out.println("测试完成: testReleaseLock_WrongOwner\n");
}
@Test
public void testConcurrentAccess_SingleLockAcquirer() throws InterruptedException {
System.out.println("开始执行: testConcurrentAccess_SingleLockAcquirer");
// 给定条件
String lockKey = "concurrent_test_lock";
int threadCount = 10;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger successCounter = new AtomicInteger(0);
System.out.println("测试参数 - 并发线程数: " + threadCount + ", 锁键: " + lockKey);
// 模拟Redis并发访问行为
when(redisService.set(anyString(), anyString(), anyLong()))
.thenAnswer(invocation -> {
// 只有第一次调用成功其余都失败
boolean result = successCounter.get() == 0;
if (result) {
System.out.println("线程[" + Thread.currentThread().getName() + "] 成功获取锁");
}
return result;
});
System.out.println("模拟Redis行为: 只允许一个线程获取锁");
// 当执行时
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
executor.submit(() -> {
try {
System.out.println("启动线程[" + threadId + "]");
if (lockHelper.tryLock(lockKey, 30)) {
int count = successCounter.incrementAndGet();
System.out.println("线程[" + threadId + "] 获取锁成功,当前成功计数: " + count);
lockHelper.releaseLock(lockKey);
System.out.println("线程[" + threadId + "] 释放锁");
} else {
System.out.println("线程[" + threadId + "] 获取锁失败");
}
} finally {
latch.countDown();
System.out.println("线程[" + threadId + "] 执行完毕,剩余线程数: " + (latch.getCount()));
}
});
}
boolean completed = latch.await(5, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("所有线程执行完成: " + (completed ? "正常结束" : "超时结束"));
// 那么验证
int successCount = successCounter.get();
assertEquals(1, successCount, "应该只有一个线程获得锁");
System.out.println("验证通过: 实际成功获取锁的线程数为 " + successCount);
System.out.println("测试完成: testConcurrentAccess_SingleLockAcquirer\n");
}
@Test
public void testAutoRenewalFunctionality() throws InterruptedException {
System.out.println("开始执行: testAutoRenewalFunctionality");
// 给定条件
String lockKey = "auto_renew_test_lock";
long expireTimeSec = 5L;
long maxHoldTimeSec = 10L;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "秒, 最大持有时间: " + maxHoldTimeSec + "");
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
when(redisService.get(anyString())).thenReturn("mock-value");
System.out.println("模拟Redis set/get操作成功");
// 当调用时
DistributedLockHelper.LockHolder lockHolder =
lockHelper.tryLockWithAutoRenew(lockKey, expireTimeSec, maxHoldTimeSec);
System.out.println("获取带自动续期功能的锁");
assertNotNull(lockHolder, "锁持有者不应为空");
System.out.println("锁持有者创建成功");
// 等待可能的续期操作
System.out.println("等待3秒观察自动续期行为...");
Thread.sleep(3000);
// 那么验证
verify(redisService, atLeastOnce()).expire(anyString(), anyLong());
System.out.println("验证通过: Redis expire 方法至少被调用一次");
// 清理
lockHolder.close();
System.out.println("关闭锁持有者,清理资源");
System.out.println("测试完成: testAutoRenewalFunctionality\n");
}
@Test
public void testLockHolderAutoClose() {
System.out.println("开始执行: testLockHolderAutoClose");
// 给定条件
String lockKey = "auto_close_test_lock";
System.out.println("测试参数 - 锁键: " + lockKey);
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
when(redisService.get(anyString())).thenReturn("mock-value");
System.out.println("模拟Redis操作成功");
// 当调用时
System.out.println("使用try-with-resources语法获取锁");
try (DistributedLockHelper.LockHolder holder =
lockHelper.tryLockWithAutoRenew(lockKey, 30, 300)) {
assertNotNull(holder, "锁持有者不应为空");
System.out.println("锁持有者创建成功");
}
System.out.println("try-with-resources块结束自动调用close方法");
// 那么验证
verify(redisService).del(anyString());
System.out.println("验证通过: Redis del 方法被正确调用");
System.out.println("测试完成: testLockHolderAutoClose\n");
}
@Test
public void testMultipleLocksIndependence() {
System.out.println("开始执行: testMultipleLocksIndependence");
// 给定条件
String lockKey1 = "lock_1";
String lockKey2 = "lock_2";
System.out.println("测试参数 - 锁键1: " + lockKey1 + ", 锁键2: " + lockKey2);
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
System.out.println("模拟Redis set操作成功");
// 当调用时
boolean lock1Acquired = lockHelper.tryLock(lockKey1, 30);
System.out.println("获取第一个锁: " + (lock1Acquired ? "成功" : "失败"));
boolean lock2Acquired = lockHelper.tryLock(lockKey2, 30);
System.out.println("获取第二个锁: " + (lock2Acquired ? "成功" : "失败"));
// 那么验证
assertTrue(lock1Acquired, "第一个锁应该成功获取");
assertTrue(lock2Acquired, "第二个锁应该成功获取");
System.out.println("两个锁都成功获取");
// 验证它们是不同的键
verify(redisService).set(eq("distributed_lock:" + lockKey1), anyString(), eq(30L));
verify(redisService).set(eq("distributed_lock:" + lockKey2), anyString(), eq(30L));
System.out.println("验证通过: 两个独立的锁键都被正确设置");
System.out.println("测试完成: testMultipleLocksIndependence\n");
}
@Test
public void testThreadIsolation() throws InterruptedException {
System.out.println("开始执行: testThreadIsolation");
// 给定条件
String lockKey = "thread_isolation_lock";
ExecutorService executor = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(2);
AtomicInteger[] results = new AtomicInteger[]{new AtomicInteger(0), new AtomicInteger(0)};
System.out.println("测试参数 - 锁键: " + lockKey + ", 线程数: 2");
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
System.out.println("模拟Redis set操作成功");
// 当执行时
System.out.println("启动两个线程竞争同一个锁");
for (int i = 0; i < 2; i++) {
final int threadIndex = i;
executor.submit(() -> {
try {
System.out.println("线程[" + threadIndex + "] 尝试获取锁");
if (lockHelper.tryLock(lockKey, 30)) {
int count = results[threadIndex].incrementAndGet();
System.out.println("线程[" + threadIndex + "] 获取锁成功,计数: " + count);
// 持有锁一段时间
Thread.sleep(100);
System.out.println("线程[" + threadIndex + "] 持有锁100ms后释放");
lockHelper.releaseLock(lockKey);
System.out.println("线程[" + threadIndex + "] 释放锁");
} else {
System.out.println("线程[" + threadIndex + "] 获取锁失败");
}
} catch (InterruptedException e) {
System.out.println("线程[" + threadIndex + "] 被中断");
Thread.currentThread().interrupt();
} finally {
latch.countDown();
System.out.println("线程[" + threadIndex + "] 执行完毕");
}
});
}
boolean completed = latch.await(5, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("所有线程执行完成: " + (completed ? "正常结束" : "超时结束"));
// 那么验证 - 两个线程都应该能够在不同时间获得锁
int result0 = results[0].get();
int result1 = results[1].get();
assertEquals(1, result0, "线程0应该获得一次锁");
assertEquals(1, result1, "线程1应该获得一次锁");
System.out.println("验证通过: 线程0获得锁次数=" + result0 + ", 线程1获得锁次数=" + result1);
System.out.println("测试完成: testThreadIsolation\n");
}
}

View File

@ -33,11 +33,11 @@ public interface ShopActivityCutpriceService extends IBaseService<ShopActivityCu
/** /**
* 砍价活动是否可以下单 * 砍价活动是否可以下单
* *
* @param ac_id 活动 自增Id * @param activity_id 活动 自增Id
* @param order_user_id 下单用户 * @param order_user_id 下单用户
* @return * @return
*/ */
Pair<Boolean, String> canDoOrderCutPriceActivity(Integer ac_id, Integer order_user_id); Pair<Boolean, String> canDoOrderCutPriceActivity(Integer activity_id, Integer order_user_id);
/** /**

View File

@ -25,6 +25,7 @@ import com.suisung.mall.common.modules.activity.ShopActivityGroupbooking;
import com.suisung.mall.common.modules.activity.ShopActivityGroupbookingHistory; import com.suisung.mall.common.modules.activity.ShopActivityGroupbookingHistory;
import com.suisung.mall.common.modules.store.ShopStoreActivityBase; import com.suisung.mall.common.modules.store.ShopStoreActivityBase;
import com.suisung.mall.common.utils.CheckUtil; import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.common.utils.I18nUtil; import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.core.web.service.RedisService; import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
@ -314,7 +315,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
// 如果用户未参与该砍价活动则创建新的砍价记录 // 如果用户未参与该砍价活动则创建新的砍价记录
if (cutprice_row == null) { if (cutprice_row == null) {
if (!checkStockResult.getFirst()) { if (checkStockResult != null && !checkStockResult.getFirst()) {
// 库存不够立即更改活动状态为已结束 // 库存不够立即更改活动状态为已结束
shopStoreActivityBaseService.updateActivityState(activity_id, StateCode.ACTIVITY_STATE_FINISHED); shopStoreActivityBaseService.updateActivityState(activity_id, StateCode.ACTIVITY_STATE_FINISHED);
throw new ApiException(checkStockResult.getSecond()); throw new ApiException(checkStockResult.getSecond());
@ -377,9 +378,11 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
// 设置活动规则信息 // 设置活动规则信息
activity_row.put("activity_rule", JSONUtil.parseObj(activity_row.get("activity_rule"))); activity_row.put("activity_rule", JSONUtil.parseObj(activity_row.get("activity_rule")));
if (!checkStockResult.getFirst()) { if (checkStockResult != null && !checkStockResult.getFirst()) {
// 库存不够立即更改活动状态为已结束
shopStoreActivityBaseService.updateActivityState(activity_id, StateCode.ACTIVITY_STATE_FINISHED);
activity_row.put("can_buy_now", CommonConstant.Disable2); activity_row.put("can_buy_now", CommonConstant.Disable2);
activity_row.put("cannot_buy_now_reason", "抱歉,您慢了一步,商品已抢空!"); activity_row.put("cannot_buy_now_reason", "抱歉,商品已抢空,活动已结束");
} else { } else {
// 判断是否可以立即购买 // 判断是否可以立即购买
boolean canBuyNow = cutprice_row != null boolean canBuyNow = cutprice_row != null
@ -618,7 +621,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
} }
// 2. 检查活动是否在有效期内 // 2. 检查活动是否在有效期内
if (!shopStoreActivityBaseService.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime(), new Date())) { if (!DateTimeUtils.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime(), new Date())) {
return Pair.of(false, I18nUtil._("该活动已过期,请检查。")); return Pair.of(false, I18nUtil._("该活动已过期,请检查。"));
} }
@ -686,7 +689,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
} }
// 检查活动是否过期 // 检查活动是否过期
boolean isActivityTimeValid = shopStoreActivityBaseService.isActivityTimeValid( boolean isActivityTimeValid = DateTimeUtils.isActivityTimeValid(
shopStoreActivityBase.getActivity_starttime(), shopStoreActivityBase.getActivity_starttime(),
shopStoreActivityBase.getActivity_endtime(), shopStoreActivityBase.getActivity_endtime(),
new Date() new Date()

View File

@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.api.StateCode; import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.modules.store.ShopStoreActivityBase; import com.suisung.mall.common.modules.store.ShopStoreActivityBase;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService; import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService;
import com.suisung.mall.shop.config.SpringUtil; import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService; import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService;
@ -28,33 +29,57 @@ public class UpdateActivityStatusJob extends QuartzJobBean {
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Logger logger = LoggerFactory.getLogger(UpdateActivityStatusJob.class); Logger logger = LoggerFactory.getLogger(UpdateActivityStatusJob.class);
ShopStoreActivityBaseService shopStoreActivityBaseService = SpringUtil.getBean(ShopStoreActivityBaseService.class); ShopStoreActivityBaseService shopStoreActivityBaseService = SpringUtil.getBean(ShopStoreActivityBaseService.class);
ShopActivityCutpriceService shopActivityCutpriceService = SpringUtil.getBean(ShopActivityCutpriceService.class);
TransactionTemplate transactionTemplate = SpringUtil.getBean(TransactionTemplate.class); TransactionTemplate transactionTemplate = SpringUtil.getBean(TransactionTemplate.class);
int page = 1; int page = 1;
List<ShopStoreActivityBase> activityBaseList; List<ShopStoreActivityBase> activityBaseList;
QueryWrapper<ShopStoreActivityBase> queryWrapper = new QueryWrapper<>(); QueryWrapper<ShopStoreActivityBase> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("activity_state", StateCode.ACTIVITY_STATE_WAITING); queryWrapper.in("activity_state", StateCode.ACTIVITY_STATE_WAITING, StateCode.ACTIVITY_STATE_NORMAL);
queryWrapper.le("activity_starttime", new Date()); // queryWrapper.le("activity_starttime", new Date());
while (CollUtil.isNotEmpty(activityBaseList = shopStoreActivityBaseService.lists(queryWrapper, page, 20).getRecords())) { while (CollUtil.isNotEmpty(activityBaseList = shopStoreActivityBaseService.lists(queryWrapper, page, 20).getRecords())) {
for (ShopStoreActivityBase storeActivityBase : activityBaseList) { for (ShopStoreActivityBase storeActivityBase : activityBaseList) {
if (storeActivityBase.getActivity_state() != null
&& storeActivityBase.getActivity_state().intValue() == StateCode.ACTIVITY_STATE_WAITING
&& DateTimeUtils.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime(), new Date())) {
Boolean result = transactionTemplate.execute(status -> {
storeActivityBase.setActivity_state(StateCode.ACTIVITY_STATE_NORMAL);
Map<String, Object> activityBaseMap = Convert.toMap(String.class, Object.class, storeActivityBase);
Boolean result = transactionTemplate.execute(status -> { if (!shopStoreActivityBaseService.editActivityBase(storeActivityBase.getActivity_id(), activityBaseMap)) {
storeActivityBase.setActivity_state(StateCode.ACTIVITY_STATE_NORMAL); status.isRollbackOnly();
Map<String, Object> activityBaseMap = Convert.toMap(String.class, Object.class, storeActivityBase); return false;
}
if (!shopStoreActivityBaseService.editActivityBase(storeActivityBase.getActivity_id(), activityBaseMap)) { return true;
status.isRollbackOnly(); });
return false;
if (Boolean.FALSE.equals(result)) {
logger.error(String.format("activity_id : %s 开启出错", storeActivityBase.getActivity_id()));
} }
return true;
});
if (Boolean.FALSE.equals(result)) {
logger.error(String.format("activity_id : %s 开启出错", storeActivityBase.getActivity_id()));
} }
if (storeActivityBase.getActivity_state() != null
&& storeActivityBase.getActivity_state().intValue() == StateCode.ACTIVITY_STATE_NORMAL
&& !DateTimeUtils.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime(), new Date())) {
Boolean result = transactionTemplate.execute(status -> {
storeActivityBase.setActivity_state(StateCode.ACTIVITY_STATE_FINISHED);
Map<String, Object> activityBaseMap = Convert.toMap(String.class, Object.class, storeActivityBase);
if (!shopStoreActivityBaseService.editActivityBase(storeActivityBase.getActivity_id(), activityBaseMap)) {
status.isRollbackOnly();
return false;
}
return true;
});
if (Boolean.FALSE.equals(result)) {
logger.error(String.format("activity_id : %s 设置过期出错", storeActivityBase.getActivity_id()));
}
}
} }
try { try {
@ -65,8 +90,18 @@ public class UpdateActivityStatusJob extends QuartzJobBean {
page++; page++;
} }
// 砍价活动时间到更新砍价订单的过期状态
autoUpdateCutPriceStateJob();
}
/**
* 只针对砍价活动时间到更新砍价订单的过期状态
*/
private void autoUpdateCutPriceStateJob() {
ShopActivityCutpriceService shopActivityCutpriceService = SpringUtil.getBean(ShopActivityCutpriceService.class);
// 活动时间到更新砍价订单的过期状态 // 活动时间到更新砍价订单的过期状态
shopActivityCutpriceService.autoUpdateCutPriceStateJob(); shopActivityCutpriceService.autoUpdateCutPriceStateJob();
} }
} }

View File

@ -1607,6 +1607,29 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
} }
} }
// 判断是否砍价购买
Integer acId = getParameter("ac_id", Integer.class);
// 判断是否为砍价活动
Boolean isCutPriceActivity = shopStoreActivityBaseService.isCutPriceActivity(activityId);
if (CheckUtil.isNotEmpty(activityId) && isCutPriceActivity) {
// 判断砍价商品库存是否已售罄
if (shopStoreActivityBaseService.isActProdSoldOut(activityId)) {
logger.error("检查商品库存:{}", activityId);
throw new ApiException(I18nUtil._("砍价商品已售罄,请选择其他商品。"));
}
// 校验砍价活动用户是否可以立即购买了
Pair<Boolean, String> canDoOrderCutPriceActivity = shopActivityCutpriceService.canDoOrderCutPriceActivity(activityId, userId);
if (!canDoOrderCutPriceActivity.getFirst()) {
logger.error("检查活动是否达标:{}", canDoOrderCutPriceActivity.getSecond());
throw new ApiException(I18nUtil._("活动已过期或未砍到底价,暂无法提交订单。"));
}
cartData.put("ac_id", acId); // shop_activity_cutprice 活动参与记录的自增id
cartData.put("activity_id", activityId);
cartData.put("activity_type", StateCode.ACTIVITY_TYPE_CUTPRICE);
}
// 添加保存订单关键方法 // 添加保存订单关键方法
List<String> orderIdRow = addOrder(cartData, true, false, null); List<String> orderIdRow = addOrder(cartData, true, false, null);
@ -1632,25 +1655,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
// cartData.put("packing_fee", packingFee); // cartData.put("packing_fee", packingFee);
// } // }
// 判断是否砍价购买
Integer acId = getParameter("ac_id", Integer.class);
// 判断是否为砍价活动
Boolean isCutPriceActivity = shopStoreActivityBaseService.isCutPriceActivity(activityId);
if (CheckUtil.isNotEmpty(acId) && isCutPriceActivity) {
cartData.put("ac_id", acId); // shop_activity_cutprice 活动参与记录的自增id
// 判断砍价商品库存是否已售罄
if (shopStoreActivityBaseService.isActProdSoldOut(activityId)) {
throw new ApiException(I18nUtil._("砍价商品已售罄,请选择其他商品。"));
}
// 校验砍价活动用户是否可以立即购买了
Pair<Boolean, String> canDoOrderCutPriceActivity = shopActivityCutpriceService.canDoOrderCutPriceActivity(acId, userId);
if (!canDoOrderCutPriceActivity.getFirst()) {
logger.error(canDoOrderCutPriceActivity.getSecond());
throw new ApiException(I18nUtil._("砍价活动未达标,请继续努力,再提交订单。"));
}
}
// 判断是否拼团购买 // 判断是否拼团购买
if (gbId != null) { if (gbId != null) {
@ -6680,6 +6684,10 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
info_row.setCoupon_type_id(shopProductIndex.getCoupon_type_id()); info_row.setCoupon_type_id(shopProductIndex.getCoupon_type_id());
} }
// 保存活动id和类型
info_row.setActivity_id(Convert.toStr(cart_data.get("activity_id"), "0"));
info_row.setActivity_type_id(Convert.toStr(cart_data.get("activity_type_id"), "0"));
// 预约订单检测 // 预约订单检测
Integer bookingState = Convert.toInt(getParameter("booking_state")); Integer bookingState = Convert.toInt(getParameter("booking_state"));
if (CheckUtil.isNotEmpty(bookingState) && CommonConstant.Order_Booking_State_YY.equals(bookingState)) { if (CheckUtil.isNotEmpty(bookingState) && CommonConstant.Order_Booking_State_YY.equals(bookingState)) {

View File

@ -69,15 +69,15 @@ public interface ShopStoreActivityBaseService extends IBaseService<ShopStoreActi
*/ */
boolean isActivityTimeValid(ShopStoreActivityBase shopStoreActivityBase, Date checkTime); boolean isActivityTimeValid(ShopStoreActivityBase shopStoreActivityBase, Date checkTime);
/** // /**
* 验证活动时间是否有效 // * 验证活动时间是否有效
* // *
* @param starTime 活动开始时间 // * @param starTime 活动开始时间
* @param endTime 活动结束时间 // * @param endTime 活动结束时间
* @param checkTime 待验证时间 // * @param checkTime 待验证时间
* @return 时间是否有效 // * @return 时间是否有效
*/ // */
boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime); // boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime);
Map listsMarketing(); Map listsMarketing();

View File

@ -15,6 +15,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.api.ResultCode; import com.suisung.mall.common.api.ResultCode;
@ -36,10 +37,7 @@ import com.suisung.mall.common.modules.product.ShopProductItem;
import com.suisung.mall.common.modules.store.ShopStoreActivityBase; import com.suisung.mall.common.modules.store.ShopStoreActivityBase;
import com.suisung.mall.common.modules.store.ShopStoreActivityItem; import com.suisung.mall.common.modules.store.ShopStoreActivityItem;
import com.suisung.mall.common.modules.store.ShopStoreBase; import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.utils.CheckUtil; import com.suisung.mall.common.utils.*;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.common.utils.UserInfoService;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.activity.service.*; import com.suisung.mall.shop.activity.service.*;
import com.suisung.mall.shop.base.service.AccountBaseConfigService; import com.suisung.mall.shop.base.service.AccountBaseConfigService;
@ -2095,29 +2093,38 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
Date starTime = shopStoreActivityBase.getActivity_starttime(); Date starTime = shopStoreActivityBase.getActivity_starttime();
Date endTime = shopStoreActivityBase.getActivity_endtime(); Date endTime = shopStoreActivityBase.getActivity_endtime();
return isActivityTimeValid(starTime, endTime, checkTime); return DateTimeUtils.isActivityTimeValid(starTime, endTime, checkTime);
} }
/** // /**
* 验证活动时间是否有效 // * 验证活动时间是否有效
* // *
* @param starTime 活动开始时间 // * @param starTime 活动开始时间
* @param endTime 活动结束时间 // * @param endTime 活动结束时间
* @param checkTime 待验证时间 // * @param checkTime 待验证时间
* @return 时间是否有效 // * @return 时间是否有效
*/ // */
@Override // @Override
public boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime) { // public boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime) {
if (starTime == null || endTime == null) { // logger.debug("检查活动时间有效性 - 开始时间: {}, 结束时间: {}, 检查时间: {}", starTime, endTime, checkTime);
return false; //
} // if (starTime == null || endTime == null) {
// logger.error("活动时间验证失败 - 开始时间或结束时间为null. 开始时间: {}, 结束时间: {}", starTime, endTime);
// return false;
// }
//
// if (checkTime == null) {
// checkTime = new Date();
// logger.warn("检查时间为空,使用当前时间: {}", checkTime);
// }
//
// boolean isValid = !checkTime.before(starTime) && !checkTime.after(endTime);
// logger.debug("活动时间验证结果: {} (检查时间>=开始时间, 检查时间<=结束时间)",
// isValid);
//
// return isValid;
// }
if (checkTime == null) {
checkTime = new Date();
}
return !checkTime.before(starTime) && !checkTime.after(endTime);
}
@Override @Override
public Map listsMarketing() { public Map listsMarketing() {
@ -4460,10 +4467,9 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
try { try {
// 直接使用LambdaUpdateWrapper提高性能避免创建额外实体对象 // 直接使用LambdaUpdateWrapper提高性能避免创建额外实体对象
boolean result = lambdaUpdate() boolean result = update(new UpdateWrapper<ShopStoreActivityBase>()
.eq(ShopStoreActivityBase::getActivity_id, activity_id) .eq("activity_id", activity_id)
.set(ShopStoreActivityBase::getActivity_state, activity_state) .set("activity_state", activity_state));
.update();
if (result) { if (result) {
logger.info("活动状态更新成功活动ID: {},新状态: {}", activity_id, activity_state); logger.info("活动状态更新成功活动ID: {},新状态: {}", activity_id, activity_state);
@ -4488,26 +4494,22 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
public boolean isActProdSoldOut(Integer activity_id) { public boolean isActProdSoldOut(Integer activity_id) {
// 参数校验检查活动ID是否有效 // 参数校验检查活动ID是否有效
if (activity_id == null || activity_id <= 0) { if (activity_id == null || activity_id <= 0) {
logger.warn("活动商品售完检查失败无效的活动ID - {}", activity_id); logger.info("活动商品售完检查失败无效的活动ID - {}", activity_id);
return true; // 将无效输入视为"已售完" return true; // 将无效输入视为"已售完"
} }
try { try {
// 直接从数据库查询活动记录只获取需要的字段以提高性能 // 直接从数据库查询活动记录只获取需要的字段以提高性能
ShopStoreActivityBase activity = lambdaQuery() ShopStoreActivityBase activity = get(activity_id);
.select(ShopStoreActivityBase::getProduct_count) // 只查询库存字段
.eq(ShopStoreActivityBase::getActivity_id, activity_id)
.one();
// 如果活动不存在视为已售完 // 如果活动不存在视为已售完
if (activity == null) { if (activity == null) {
logger.warn("活动商品售完检查未找到活动记录activity_id={}", activity_id); logger.info("活动商品售完检查未找到活动记录activity_id={}", activity_id);
return true; return true;
} }
// 获取商品库存数量 // 获取商品库存数量
Integer productCount = activity.getProduct_count(); Integer productCount = activity.getProduct_count();
logger.debug("活动商品库存检查activity_id={}, 库存数量={}", activity_id, productCount); logger.info("活动商品库存检查activity_id={}, 库存数量={}", activity_id, productCount);
// 如果库存为null或小于等于0视为已售完 // 如果库存为null或小于等于0视为已售完
if (productCount == null || productCount <= 0) { if (productCount == null || productCount <= 0) {
@ -4518,9 +4520,9 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
// 获取该活动已成功的订单数量 // 获取该活动已成功的订单数量
long soldCount = shopOrderInfoService.fetchActivityOrderSuccessCount( long soldCount = shopOrderInfoService.fetchActivityOrderSuccessCount(
Convert.toStr(activity_id), Convert.toStr(activity_id),
Convert.toStr(StateCode.ACTIVITY_TYPE_CUTPRICE)); ""); //Convert.toStr(StateCode.ACTIVITY_TYPE_CUTPRICE)
logger.debug("活动商品售完检查activity_id={}, 已售数量={}, 库存数量={}", logger.info("活动商品售完检查activity_id={}, 已售数量={}, 库存数量={}",
activity_id, soldCount, productCount); activity_id, soldCount, productCount);
// 如果已售数量大于等于库存数量则已售完 // 如果已售数量大于等于库存数量则已售完
@ -5209,7 +5211,7 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
.ge("activity_item_endtime", activity.get("activity_starttime")); .ge("activity_item_endtime", activity.get("activity_starttime"));
List<ShopStoreActivityItem> item_row = shopStoreActivityItemService.find(itemQueryWrapper); List<ShopStoreActivityItem> item_row = shopStoreActivityItemService.find(itemQueryWrapper);
List<Long> item_id_row = item_row.stream().filter(s->s.getActivity_item_state().equals(StateCode.ACTIVITY_STATE_NORMAL)).map(s -> s.getItem_id()).distinct().collect(Collectors.toList()); List<Long> item_id_row = item_row.stream().filter(s -> s.getActivity_item_state().equals(StateCode.ACTIVITY_STATE_NORMAL)).map(s -> s.getItem_id()).distinct().collect(Collectors.toList());
List<Integer> activity_id_row = item_row.stream().map(s -> s.getActivity_id()).distinct().collect(Collectors.toList()); List<Integer> activity_id_row = item_row.stream().map(s -> s.getActivity_id()).distinct().collect(Collectors.toList());
List<Long> used_id_row = new ArrayList<>(); List<Long> used_id_row = new ArrayList<>();

View File

@ -48,6 +48,7 @@ import com.suisung.mall.common.pojo.dto.StoreBizTimeInfoDTO;
import com.suisung.mall.common.service.impl.BaiduMapServiceImpl; import com.suisung.mall.common.service.impl.BaiduMapServiceImpl;
import com.suisung.mall.common.utils.*; import com.suisung.mall.common.utils.*;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService;
import com.suisung.mall.shop.base.service.AccountBaseConfigService; import com.suisung.mall.shop.base.service.AccountBaseConfigService;
import com.suisung.mall.shop.base.service.ShopBaseProductTagService; import com.suisung.mall.shop.base.service.ShopBaseProductTagService;
import com.suisung.mall.shop.base.service.ShopBaseStoreCategoryService; import com.suisung.mall.shop.base.service.ShopBaseStoreCategoryService;
@ -139,6 +140,11 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
@Lazy @Lazy
@Autowired @Autowired
private ShopStoreAnalyticsService shopStoreAnalyticsService; private ShopStoreAnalyticsService shopStoreAnalyticsService;
@Lazy
@Autowired
private ShopActivityCutpriceService shopActivityCutpriceService;
@Autowired @Autowired
private AccountBaseConfigService accountBaseConfigService; private AccountBaseConfigService accountBaseConfigService;
@Autowired @Autowired
@ -1104,18 +1110,44 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
if (CheckUtil.isNotEmpty(activity_type_id)) { if (CheckUtil.isNotEmpty(activity_type_id)) {
queryWrapper.eq("activity_type_id", activity_type_id); queryWrapper.eq("activity_type_id", activity_type_id);
} else { } else {
queryWrapper.in("activity_type_id", StateCode.ACTIVITY_TYPE_BARGAIN, queryWrapper.in("activity_type_id",
StateCode.ACTIVITY_TYPE_BARGAIN,
StateCode.ACTIVITY_TYPE_GIFT, StateCode.ACTIVITY_TYPE_GIFT,
StateCode.ACTIVITY_TYPE_LIMITED_DISCOUNT, StateCode.ACTIVITY_TYPE_LIMITED_DISCOUNT,
StateCode.ACTIVITY_TYPE_DISCOUNT_PACKAGE, StateCode.ACTIVITY_TYPE_DISCOUNT_PACKAGE,
StateCode.ACTIVITY_TYPE_DIY_PACKAGE, StateCode.ACTIVITY_TYPE_DIY_PACKAGE,
StateCode.ACTIVITY_TYPE_REDUCTION); StateCode.ACTIVITY_TYPE_REDUCTION,
StateCode.ACTIVITY_TYPE_CUTPRICE);
} }
// 为执行排序设置默认排序 // 为执行排序设置默认排序
queryWrapper.orderByDesc("activity_id"); queryWrapper.orderByDesc("activity_id");
Page<ShopStoreActivityBase> lists = shopStoreActivityBaseService.lists(queryWrapper, 1, 500); Page<ShopStoreActivityBase> lists = shopStoreActivityBaseService.lists(queryWrapper, 1, 200);
// Optimized version with improved code quality and readability
List<ShopStoreActivityBase> filteredActivities = new ArrayList<>();
for (ShopStoreActivityBase activity : lists.getRecords()) {
// Check if it's a cut-price activity that needs validation
if (activity.getActivity_type_id() != null
&& activity.getActivity_type_id() == StateCode.ACTIVITY_TYPE_CUTPRICE) {
// Validate cut-price activity stock and expiration
Pair<Boolean, String> checkStockResult = shopActivityCutpriceService.checkCutPriceExpiredAndStock(activity);
if (checkStockResult != null && !checkStockResult.getFirst()) {
// Insufficient stock or expired - update activity state to finished
shopStoreActivityBaseService.updateActivityState(activity.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED);
continue; // Skip adding this activity to results
}
}
// Add valid activities to filtered list
filteredActivities.add(activity);
}
// Update the page records with filtered results
lists.setRecords(filteredActivities);
Map data = toMobileResult(lists); Map data = toMobileResult(lists);
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();