484 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.ResultCode;
import com.designhub.entity.Contest;
import com.designhub.entity.ContestParticipant;
import com.designhub.entity.User;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.ContestMapper;
import com.designhub.mapper.ContestParticipantMapper;
import com.designhub.mapper.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 竞赛活动Service
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ContestService {
private final ContestMapper contestMapper;
private final ContestParticipantMapper participantMapper;
private final UserMapper userMapper;
private final NotificationService notificationService;
private final ObjectMapper objectMapper;
/**
* 创建竞赛
*/
@Transactional
public Contest createContest(Contest contest) {
// 验证时间
if (contest.getStartTime().isAfter(contest.getEndTime())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "开始时间不能晚于结束时间");
}
contest.setStatus("draft");
contest.setParticipantCount(0);
contest.setDeleted(0);
contestMapper.insert(contest);
log.info("创建竞赛成功: id={}, name={}", contest.getId(), contest.getName());
return contest;
}
/**
* 更新竞赛
*/
@Transactional
public Contest updateContest(Long id, Contest contest) {
Contest existing = contestMapper.selectById(id);
if (existing == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
// 进行中的竞赛不能修改规则
if ("active".equals(existing.getStatus()) && contest.getRules() != null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的竞赛不能修改规则");
}
contest.setId(id);
contestMapper.updateById(contest);
return contestMapper.selectById(id);
}
/**
* 发布竞赛(从草稿变为待开始或进行中)
*/
@Transactional
public Contest publishContest(Long id) {
Contest contest = contestMapper.selectById(id);
if (contest == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
if (!"draft".equals(contest.getStatus())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "只有草稿状态的竞赛可以发布");
}
LocalDateTime now = LocalDateTime.now();
if (contest.getStartTime().isBefore(now)) {
contest.setStatus("active");
} else {
contest.setStatus("scheduled");
}
contestMapper.updateById(contest);
// 初始化参与者
initParticipants(contest);
// 发送通知
notifyContestStart(contest);
return contest;
}
/**
* 初始化竞赛参与者
*/
private void initParticipants(Contest contest) {
List<User> users;
if ("all".equals(contest.getParticipantScope())) {
// 全体设计师
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getDeleted, 0);
users = userMapper.selectList(wrapper);
} else if ("department".equals(contest.getParticipantScope())) {
// 指定部门
try {
List<Long> deptIds = objectMapper.readValue(contest.getDepartmentIds(),
new TypeReference<List<Long>>() {});
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(User::getDeptId, deptIds).eq(User::getDeleted, 0);
users = userMapper.selectList(wrapper);
} catch (JsonProcessingException e) {
throw new BusinessException(ResultCode.PARAM_ERROR, "部门ID格式错误");
}
} else {
// 指定用户
try {
List<Long> userIds = objectMapper.readValue(contest.getUserIds(),
new TypeReference<List<Long>>() {});
users = userMapper.selectBatchIds(userIds);
} catch (JsonProcessingException e) {
throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID格式错误");
}
}
for (User user : users) {
ContestParticipant participant = new ContestParticipant();
participant.setContestId(contest.getId());
participant.setUserId(user.getId());
participant.setUserName(user.getRealName());
participant.setDeptId(user.getDeptId());
participant.setDeptName(user.getDeptName());
participant.setTaskCompleted(0);
participant.setFiveStarCount(0);
participant.setEarlyCompletionCount(0);
participant.setPraiseCount(0);
participant.setNoDelayDays(0);
participant.setTotalScore(0);
participant.setCurrentRank(0);
participant.setRewardClaimed(0);
participant.setRewardScore(0);
participantMapper.insert(participant);
}
// 更新参与人数
contest.setParticipantCount(users.size());
contestMapper.updateById(contest);
}
/**
* 发送竞赛开始通知
*/
private void notifyContestStart(Contest contest) {
LambdaQueryWrapper<ContestParticipant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ContestParticipant::getContestId, contest.getId());
List<ContestParticipant> participants = participantMapper.selectList(wrapper);
for (ContestParticipant p : participants) {
notificationService.sendNotification(
p.getUserId(),
"竞赛活动开始",
String.format("竞赛「%s」已开始!快来参与吧!", contest.getName()),
"contest",
contest.getId()
);
}
}
/**
* 获取竞赛列表
*/
public IPage<Contest> getContestList(Integer page, Integer pageSize, String status) {
Page<Contest> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<Contest> wrapper = new LambdaQueryWrapper<>();
if (status != null && !status.isEmpty()) {
wrapper.eq(Contest::getStatus, status);
}
wrapper.orderByDesc(Contest::getCreateTime);
return contestMapper.selectPage(pageParam, wrapper);
}
/**
* 获取竞赛详情
*/
public Contest getContestById(Long id) {
Contest contest = contestMapper.selectById(id);
if (contest == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
return contest;
}
/**
* 获取竞赛排行榜
*/
public List<ContestParticipant> getLeaderboard(Long contestId, Integer limit) {
List<ContestParticipant> list = participantMapper.getLeaderboard(contestId);
if (limit != null && list.size() > limit) {
return list.subList(0, limit);
}
return list;
}
/**
* 获取用户在竞赛中的排名和数据
*/
public ContestParticipant getUserContestData(Long contestId, Long userId) {
LambdaQueryWrapper<ContestParticipant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ContestParticipant::getContestId, contestId)
.eq(ContestParticipant::getUserId, userId);
ContestParticipant participant = participantMapper.selectOne(wrapper);
if (participant != null) {
// 计算排名
Integer rank = participantMapper.getUserRank(contestId, userId);
participant.setCurrentRank(rank);
}
return participant;
}
/**
* 更新参与者数据(当任务完成时调用)
*/
@Transactional
public void updateParticipantStats(Long userId, boolean fiveStar, boolean earlyCompletion) {
// 获取用户参与的所有进行中的竞赛
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<Contest> contestWrapper = new LambdaQueryWrapper<>();
contestWrapper.eq(Contest::getStatus, "active");
List<Contest> activeContests = contestMapper.selectList(contestWrapper);
for (Contest contest : activeContests) {
LambdaQueryWrapper<ContestParticipant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ContestParticipant::getContestId, contest.getId())
.eq(ContestParticipant::getUserId, userId);
ContestParticipant participant = participantMapper.selectOne(wrapper);
if (participant != null) {
participant.setTaskCompleted(participant.getTaskCompleted() + 1);
if (fiveStar) {
participant.setFiveStarCount(participant.getFiveStarCount() + 1);
}
if (earlyCompletion) {
participant.setEarlyCompletionCount(participant.getEarlyCompletionCount() + 1);
}
// 重新计算总分
int totalScore = calculateScore(contest, participant);
participant.setTotalScore(totalScore);
participantMapper.updateById(participant);
}
}
}
/**
* 计算参与者得分
*/
private int calculateScore(Contest contest, ContestParticipant participant) {
try {
Map<String, Map<String, Integer>> rules = objectMapper.readValue(
contest.getRules(), new TypeReference<Map<String, Map<String, Integer>>>() {});
int score = 0;
if (rules.containsKey("taskCompleted")) {
score += participant.getTaskCompleted() * rules.get("taskCompleted").getOrDefault("score", 2);
}
if (rules.containsKey("fiveStarReview")) {
score += participant.getFiveStarCount() * rules.get("fiveStarReview").getOrDefault("score", 10);
}
if (rules.containsKey("earlyCompletion")) {
score += participant.getEarlyCompletionCount() * rules.get("earlyCompletion").getOrDefault("score", 5);
}
if (rules.containsKey("praise")) {
score += participant.getPraiseCount() * rules.get("praise").getOrDefault("score", 15);
}
return score;
} catch (JsonProcessingException e) {
log.error("解析竞赛规则失败", e);
return 0;
}
}
/**
* 结束竞赛并发放奖励
*/
@Transactional
public void endContest(Long id) {
Contest contest = contestMapper.selectById(id);
if (contest == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
if (!"active".equals(contest.getStatus())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "只有进行中的竞赛可以结束");
}
contest.setStatus("ended");
contestMapper.updateById(contest);
// 发放奖励
distributeRewards(contest);
log.info("竞赛结束: id={}, name={}", contest.getId(), contest.getName());
}
/**
* 发放竞赛奖励
*/
private void distributeRewards(Contest contest) {
try {
List<Map<String, Object>> rewards = objectMapper.readValue(
contest.getRewards(), new TypeReference<List<Map<String, Object>>>() {});
List<ContestParticipant> leaderboard = participantMapper.getLeaderboard(contest.getId());
for (int i = 0; i < leaderboard.size(); i++) {
ContestParticipant participant = leaderboard.get(i);
int rank = i + 1;
participant.setCurrentRank(rank);
// 查找对应排名的奖励
for (Map<String, Object> reward : rewards) {
int rewardRank = (Integer) reward.get("rank");
if (rank == rewardRank) {
int rewardScore = (Integer) reward.get("score");
participant.setRewardScore(rewardScore);
// 发送通知
notificationService.sendNotification(
participant.getUserId(),
"竞赛获奖通知",
String.format("恭喜!您在竞赛「%s」中获得第%d名,奖励%d积分!",
contest.getName(), rank, rewardScore),
"contest",
contest.getId()
);
break;
}
}
participantMapper.updateById(participant);
}
} catch (JsonProcessingException e) {
log.error("解析竞赛奖励配置失败", e);
}
}
/**
* 领取竞赛奖励
*/
@Transactional
public boolean claimReward(Long contestId, Long userId) {
LambdaQueryWrapper<ContestParticipant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ContestParticipant::getContestId, contestId)
.eq(ContestParticipant::getUserId, userId);
ContestParticipant participant = participantMapper.selectOne(wrapper);
if (participant == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "您未参与此竞赛");
}
if (participant.getRewardClaimed() == 1) {
throw new BusinessException(ResultCode.PARAM_ERROR, "奖励已领取");
}
if (participant.getRewardScore() == null || participant.getRewardScore() <= 0) {
throw new BusinessException(ResultCode.PARAM_ERROR, "暂无奖励可领取");
}
// 更新用户积分
User user = userMapper.selectById(userId);
int currentScore = user.getScore() != null ? user.getScore() : 0;
user.setScore(currentScore + participant.getRewardScore());
userMapper.updateById(user);
// 标记已领取
participant.setRewardClaimed(1);
participant.setRewardClaimTime(LocalDateTime.now());
participantMapper.updateById(participant);
return true;
}
/**
* 定时任务:自动开始和结束竞赛
*/
@Scheduled(fixedRate = 60000) // 每分钟检查一次
@Transactional
public void checkContestStatus() {
LocalDateTime now = LocalDateTime.now();
// 自动开始待开始的竞赛
LambdaQueryWrapper<Contest> startWrapper = new LambdaQueryWrapper<>();
startWrapper.eq(Contest::getStatus, "scheduled")
.le(Contest::getStartTime, now);
List<Contest> toStart = contestMapper.selectList(startWrapper);
for (Contest contest : toStart) {
contest.setStatus("active");
contestMapper.updateById(contest);
notifyContestStart(contest);
log.info("竞赛自动开始: id={}, name={}", contest.getId(), contest.getName());
}
// 自动结束已到期的竞赛
LambdaQueryWrapper<Contest> endWrapper = new LambdaQueryWrapper<>();
endWrapper.eq(Contest::getStatus, "active")
.le(Contest::getEndTime, now);
List<Contest> toEnd = contestMapper.selectList(endWrapper);
for (Contest contest : toEnd) {
endContest(contest.getId());
}
}
/**
* 取消竞赛
*/
@Transactional
public void cancelContest(Long id) {
Contest contest = contestMapper.selectById(id);
if (contest == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
if ("ended".equals(contest.getStatus())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "已结束的竞赛不能取消");
}
contest.setStatus("cancelled");
contestMapper.updateById(contest);
log.info("竞赛已取消: id={}, name={}", contest.getId(), contest.getName());
}
/**
* 删除竞赛
*/
@Transactional
public void deleteContest(Long id) {
Contest contest = contestMapper.selectById(id);
if (contest == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在");
}
if ("active".equals(contest.getStatus())) {
throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的竞赛不能删除");
}
contestMapper.deleteById(id);
// 删除参与者数据
LambdaQueryWrapper<ContestParticipant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ContestParticipant::getContestId, id);
participantMapper.delete(wrapper);
}
}