|
|
@@ -1,20 +1,298 @@
|
|
|
package com.fs.activity.service.impl;
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.fs.activity.domain.AccVoteRecord;
|
|
|
import com.fs.activity.domain.AccWork;
|
|
|
+import com.fs.activity.mapper.AccVoteRecordMapper;
|
|
|
+import com.fs.activity.param.AccVoteRecordRequest;
|
|
|
+import com.fs.activity.param.AccVoteRecordResponse;
|
|
|
+import com.fs.activity.service.AccActivityService;
|
|
|
import com.fs.activity.service.AccWorkService;
|
|
|
import com.fs.activity.mapper.AccWorkMapper;
|
|
|
+import com.fs.common.constant.FsConstants;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.exception.ServiceException;
|
|
|
+import com.fs.common.exception.base.BusinessException;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
/**
|
|
|
* @author Administrator
|
|
|
* @description 针对表【acc_work(作品表)】的数据库操作Service实现
|
|
|
* @createDate 2026-03-05 14:33:08
|
|
|
*/
|
|
|
@Service
|
|
|
+@Slf4j
|
|
|
public class AccWorkServiceImpl extends ServiceImpl<AccWorkMapper, AccWork>
|
|
|
implements AccWorkService{
|
|
|
|
|
|
+ private static final String DAILY_VOTE_LIMIT = "vote:daily:limit";
|
|
|
+
|
|
|
+ private static final String VOTE_LOCK_KEY = "vote:lock:";
|
|
|
+
|
|
|
+ private static final String VOTE_COUNT_KEY = "vote:count:";
|
|
|
+
|
|
|
+ private static final String VOTE_RANKING_KEY = "vote:ranking";
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 每日最大投票数(可配置)
|
|
|
+ private static final int MAX_VOTES_PER_DAY = 3;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AccActivityService accActivityService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate redisTemplate;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AccVoteRecordMapper accVoteRecordMapper ;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询作品
|
|
|
+ *
|
|
|
+ * @param id 作品主键
|
|
|
+ * @return 作品
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public AccWork selectAccWorkById(Long id)
|
|
|
+ {
|
|
|
+ return baseMapper.selectAccWorkById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询作品列表
|
|
|
+ *
|
|
|
+ * @param accWork 作品
|
|
|
+ * @return 作品
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<AccWork> selectAccWorkList(AccWork accWork)
|
|
|
+ {
|
|
|
+ return baseMapper.selectAccWorkList(accWork);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新增作品
|
|
|
+ *
|
|
|
+ * @param accWork 作品
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int insertAccWork(AccWork accWork)
|
|
|
+ {
|
|
|
+ return baseMapper.insertAccWork(accWork);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修改作品
|
|
|
+ *
|
|
|
+ * @param accWork 作品
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int updateAccWork(AccWork accWork)
|
|
|
+ {
|
|
|
+ return baseMapper.updateAccWork(accWork);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量删除作品
|
|
|
+ *
|
|
|
+ * @param ids 需要删除的作品主键
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int deleteAccWorkByIds(Long[] ids)
|
|
|
+ {
|
|
|
+ return baseMapper.deleteAccWorkByIds(ids);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除作品信息
|
|
|
+ *
|
|
|
+ * @param id 作品主键
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int deleteAccWorkById(Long id)
|
|
|
+ {
|
|
|
+ return baseMapper.deleteAccWorkById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description: 上传活动视频
|
|
|
+ * @Param:
|
|
|
+ * @Return:
|
|
|
+ * @Author xgb
|
|
|
+ * @Date 2026/3/9 10:40
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public R uploadVideo(AccWork accWork) {
|
|
|
+
|
|
|
+ accWork.setStatus(0);
|
|
|
+ R r = accActivityService.checkActivityStatus(accWork.getActivityId());
|
|
|
+ if(!r.get("code").equals(200)){
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询是否上传了视频,已上传更新
|
|
|
+ AccWork work = baseMapper.selectOne(new LambdaQueryWrapper<AccWork>()
|
|
|
+ .eq(AccWork::getActivityId, accWork.getActivityId())
|
|
|
+ .eq(AccWork::getTeamId, accWork.getTeamId()));
|
|
|
+ if(work!=null){
|
|
|
+ accWork.setId(work.getId());
|
|
|
+ if (baseMapper.updateById(accWork)<1){
|
|
|
+ return R.error("更新失败");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ if (baseMapper.insert(accWork)<1){
|
|
|
+ return R.error("上传失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description: 投票
|
|
|
+ * @Param:
|
|
|
+ * @Return:
|
|
|
+ * @Author xgb
|
|
|
+ * @Date 2026/3/9 11:16
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public R castVote(AccVoteRecordRequest request) {
|
|
|
+
|
|
|
+ Long workId = request.getWorkId();
|
|
|
+ Long userId = request.getUserId();
|
|
|
+ Long activityId = request.getActivityId();
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ String todayStr = today.format(DateTimeFormatter.ISO_DATE);
|
|
|
+
|
|
|
+ // 检测活动状态
|
|
|
+ R r = accActivityService.checkActivityStatus(activityId);
|
|
|
+ if(!r.get("code").equals(200)){
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 检查用户今日投票次数是否已达上限
|
|
|
+ int todayVoteCount = getUserTodayVoteCount(userId);
|
|
|
+ if (todayVoteCount >= MAX_VOTES_PER_DAY) {
|
|
|
+ throw new ServiceException("今日投票次数已达上限(" + MAX_VOTES_PER_DAY + "次)");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 使用Redis分布式锁防止重复提交
|
|
|
+ String lockKey = VOTE_LOCK_KEY + userId + ":" + workId + ":" + todayStr;
|
|
|
+ Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
|
|
|
+ if (Boolean.FALSE.equals(locked)) {
|
|
|
+ throw new ServiceException("请勿重复提交");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 引入 Redisson
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+ boolean lockAcquired = false;
|
|
|
+ try {
|
|
|
+ lockAcquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
|
|
|
+ if (lockAcquired) {
|
|
|
+
|
|
|
+ // 5. 检查今日是否已给该作品投票
|
|
|
+ if (accVoteRecordMapper.selectCount(new LambdaQueryWrapper<AccVoteRecord>()
|
|
|
+ .eq(AccVoteRecord::getUserId, userId)
|
|
|
+ .eq(AccVoteRecord::getWorkId, workId)
|
|
|
+ .eq(AccVoteRecord::getVoteDate, today)) > 0) {
|
|
|
+ throw new ServiceException("今日已给该作品投票");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 创建投票记录
|
|
|
+ AccVoteRecord record = new AccVoteRecord();
|
|
|
+ record.setWorkId(workId);
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setVoteDate(new Date());
|
|
|
+ record.setIpAddress(request.getIpAddress());
|
|
|
+ if(accVoteRecordMapper.insert(record)<1){
|
|
|
+ throw new ServiceException("投票失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ AccWork work = new AccWork();
|
|
|
+ work.setId(workId);
|
|
|
+ // 7. 更新作品票数
|
|
|
+ work.setVoteCount(work.getVoteCount() + 1);
|
|
|
+ baseMapper.updateById(work);
|
|
|
+
|
|
|
+ // 8. 更新Redis缓存
|
|
|
+ // 8.1 更新作品票数缓存
|
|
|
+ String countKey = VOTE_COUNT_KEY + workId;
|
|
|
+ redisTemplate.opsForValue().increment(countKey);
|
|
|
+
|
|
|
+ // 8.2 更新排行榜(使用ZSet)
|
|
|
+ redisTemplate.opsForZSet().incrementScore(VOTE_RANKING_KEY, String.valueOf(workId), 1);
|
|
|
+
|
|
|
+
|
|
|
+ // 8.4 更新用户今日投票计数
|
|
|
+ String dailyLimitKey = DAILY_VOTE_LIMIT + activityId +":"+ userId + ":" + todayStr;
|
|
|
+ Long currentVotes = redisTemplate.opsForValue().increment(dailyLimitKey);
|
|
|
+ redisTemplate.expire(dailyLimitKey, 2, TimeUnit.DAYS);
|
|
|
+
|
|
|
+ log.info("用户{}给作品{}投票成功,今日第{}票,当前作品总票数:{}",
|
|
|
+ userId, workId, currentVotes, work.getVoteCount());
|
|
|
+
|
|
|
+ // 9. 返回结果
|
|
|
+ AccVoteRecordResponse response = new AccVoteRecordResponse();
|
|
|
+ response.setWorkId(workId);
|
|
|
+ response.setWorkName(work.getWorkName());
|
|
|
+ response.setCurrentVotes(work.getVoteCount());
|
|
|
+ return R.ok().put("data", response);
|
|
|
+ }else {
|
|
|
+ throw new ServiceException("服务繁忙,请稍后再试");
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ } finally {
|
|
|
+ if (lockAcquired && lock.isHeldByCurrentThread()) {
|
|
|
+ try {
|
|
|
+ lock.unlock();
|
|
|
+ } catch (IllegalMonitorStateException e) {
|
|
|
+ log.warn("尝试释放非当前线程持有的锁");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户今日投票数
|
|
|
+ */
|
|
|
+ private int getUserTodayVoteCount(Long userId) {
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ String todayStr = today.format(DateTimeFormatter.ISO_DATE);
|
|
|
+ String dailyLimitKey = DAILY_VOTE_LIMIT + userId + ":" + todayStr;
|
|
|
+
|
|
|
+ Integer count = (Integer) redisTemplate.opsForValue().get(dailyLimitKey);
|
|
|
+ if (count != null) {
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return accVoteRecordMapper.selectCount(new LambdaQueryWrapper<AccVoteRecord>()
|
|
|
+ .eq(AccVoteRecord::getUserId, userId).eq(AccVoteRecord::getVoteDate, todayStr));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|