diff --git a/src/main/java/xyz/playedu/api/bus/UserBus.java b/src/main/java/xyz/playedu/api/bus/UserBus.java index 8620c93..5df2956 100644 --- a/src/main/java/xyz/playedu/api/bus/UserBus.java +++ b/src/main/java/xyz/playedu/api/bus/UserBus.java @@ -60,6 +60,7 @@ public class UserBus { return CollectionUtils.intersection(courseDepIds, userDepIds).size() > 0; } + // 注意,调用该方法需要考虑到并发写入问题 public void userLearnDurationRecord(User user, Course course, CourseHour hour) { Long curTime = System.currentTimeMillis(); diff --git a/src/main/java/xyz/playedu/api/controller/frontend/HourController.java b/src/main/java/xyz/playedu/api/controller/frontend/HourController.java index 211864e..d46b770 100644 --- a/src/main/java/xyz/playedu/api/controller/frontend/HourController.java +++ b/src/main/java/xyz/playedu/api/controller/frontend/HourController.java @@ -32,8 +32,10 @@ import xyz.playedu.api.service.CourseService; import xyz.playedu.api.service.ResourceService; import xyz.playedu.api.service.UserCourseHourRecordService; import xyz.playedu.api.types.JsonResponse; +import xyz.playedu.api.util.RedisDistributedLock; import java.util.HashMap; +import java.util.concurrent.TimeUnit; /** * @Author 杭州白书科技有限公司 @@ -58,6 +60,8 @@ public class HourController { @Autowired private UserCanSeeCourseCache userCanSeeCourseCache; @Autowired private CourseCache courseCache; + @Autowired private RedisDistributedLock redisDistributedLock; + @GetMapping("/{id}") @SneakyThrows public JsonResponse detail( @@ -108,13 +112,23 @@ public class HourController { if (duration <= 0) { return JsonResponse.error("duration参数错误"); } - User user = FCtx.getUser(); + Course course = courseCache.findOrFail(courseId); - userCanSeeCourseCache.check(user, course, true); CourseHour hour = hourService.findOrFail(id, courseId); + userCanSeeCourseCache.check(FCtx.getUser(), course, true); + + // 获取锁 + String lockKey = String.format("record:%d", FCtx.getId()); + boolean tryLock = redisDistributedLock.tryLock(lockKey, 5, TimeUnit.SECONDS); + if (!tryLock) { + return JsonResponse.error("请稍后再试"); + } userCourseHourRecordService.storeOrUpdate( - user.getId(), course.getId(), hour.getId(), duration, hour.getDuration()); + FCtx.getId(), course.getId(), hour.getId(), duration, hour.getDuration()); + + // 此处未考虑上面代码执行失败释放锁 + redisDistributedLock.releaseLock(lockKey); return JsonResponse.success(); } @@ -127,7 +141,19 @@ public class HourController { Course course = courseCache.findOrFail(courseId); CourseHour hour = hourService.findOrFail(id, courseId); userCanSeeCourseCache.check(FCtx.getUser(), course, true); + + // 获取锁 + String lockKey = String.format("ping:%d", FCtx.getId()); + boolean tryLock = redisDistributedLock.tryLock(lockKey, 5, TimeUnit.SECONDS); + if (!tryLock) { + return JsonResponse.error("请稍后再试"); + } + userBus.userLearnDurationRecord(FCtx.getUser(), course, hour); + + // 此处未考虑上面代码执行失败释放锁 + redisDistributedLock.releaseLock(lockKey); + return JsonResponse.success(); } } diff --git a/src/main/java/xyz/playedu/api/listener/UserCourseHourFinishedListener.java b/src/main/java/xyz/playedu/api/listener/UserCourseHourFinishedListener.java index 7043663..7805098 100644 --- a/src/main/java/xyz/playedu/api/listener/UserCourseHourFinishedListener.java +++ b/src/main/java/xyz/playedu/api/listener/UserCourseHourFinishedListener.java @@ -17,7 +17,6 @@ package xyz.playedu.api.listener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import xyz.playedu.api.event.UserCourseHourFinishedEvent; @@ -39,7 +38,6 @@ public class UserCourseHourFinishedListener { @Autowired private CourseHourService hourService; - @Async @EventListener public void userCourseProgressUpdate(UserCourseHourFinishedEvent evt) { Integer hourCount = hourService.getCountByCourseId(evt.getCourseId()); diff --git a/src/main/java/xyz/playedu/api/listener/UserLearnCourseUpdateListener.java b/src/main/java/xyz/playedu/api/listener/UserLearnCourseUpdateListener.java index e94ed0f..6e379df 100644 --- a/src/main/java/xyz/playedu/api/listener/UserLearnCourseUpdateListener.java +++ b/src/main/java/xyz/playedu/api/listener/UserLearnCourseUpdateListener.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import xyz.playedu.api.event.UserLearnCourseUpdateEvent; @@ -39,7 +38,6 @@ public class UserLearnCourseUpdateListener { @Autowired private UserLearnDurationStatsService userLearnDurationStatsService; - @Async @EventListener public void storeLearnDuration(UserLearnCourseUpdateEvent event) { // 观看时长统计 diff --git a/src/main/java/xyz/playedu/api/service/impl/UserLearnDurationStatsServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/UserLearnDurationStatsServiceImpl.java index c737d74..8b0b716 100644 --- a/src/main/java/xyz/playedu/api/service/impl/UserLearnDurationStatsServiceImpl.java +++ b/src/main/java/xyz/playedu/api/service/impl/UserLearnDurationStatsServiceImpl.java @@ -15,6 +15,8 @@ */ package xyz.playedu.api.service.impl; +import cn.hutool.core.date.DateTime; + import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.SneakyThrows; @@ -42,10 +44,7 @@ public class UserLearnDurationStatsServiceImpl @Override @SneakyThrows public void storeOrUpdate(Integer userId, Long startTime, Long endTime) { - // 处理日期 - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - String date = simpleDateFormat.format(new Date(endTime)); - // duration + String date = new DateTime().toDateStr(); Long duration = endTime - startTime; UserLearnDurationStats stats = @@ -54,7 +53,7 @@ public class UserLearnDurationStatsServiceImpl UserLearnDurationStats newStats = new UserLearnDurationStats(); newStats.setUserId(userId); newStats.setDuration(duration); - newStats.setCreatedDate(simpleDateFormat.parse(date)); + newStats.setCreatedDate(new DateTime(date)); save(newStats); return; }