359 lines
12 KiB
Java
359 lines
12 KiB
Java
package com.designhub.service;
|
||
|
||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||
import com.designhub.common.ResultCode;
|
||
import com.designhub.entity.User;
|
||
import com.designhub.entity.UserGoal;
|
||
import com.designhub.exception.BusinessException;
|
||
import com.designhub.mapper.UserGoalMapper;
|
||
import com.designhub.mapper.UserMapper;
|
||
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.time.temporal.TemporalAdjusters;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* 用户目标Service
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class UserGoalService {
|
||
|
||
private final UserGoalMapper userGoalMapper;
|
||
private final UserMapper userMapper;
|
||
private final NotificationService notificationService;
|
||
|
||
/**
|
||
* 创建目标
|
||
*/
|
||
@Transactional
|
||
public UserGoal createGoal(UserGoal goal) {
|
||
// 计算周期时间
|
||
calculatePeriodTime(goal);
|
||
|
||
goal.setCurrentValue(0);
|
||
goal.setStatus("active");
|
||
goal.setRewardClaimed(0);
|
||
goal.setDeleted(0);
|
||
|
||
userGoalMapper.insert(goal);
|
||
|
||
log.info("创建目标成功: userId={}, goalName={}", goal.getUserId(), goal.getGoalName());
|
||
return goal;
|
||
}
|
||
|
||
/**
|
||
* 计算周期开始和结束时间
|
||
*/
|
||
private void calculatePeriodTime(UserGoal goal) {
|
||
LocalDateTime now = LocalDateTime.now();
|
||
|
||
switch (goal.getPeriod()) {
|
||
case "daily":
|
||
goal.setPeriodStart(now.toLocalDate().atStartOfDay());
|
||
goal.setPeriodEnd(now.toLocalDate().atTime(23, 59, 59));
|
||
break;
|
||
case "weekly":
|
||
goal.setPeriodStart(now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)).toLocalDate().atStartOfDay());
|
||
goal.setPeriodEnd(now.with(TemporalAdjusters.nextOrSame(java.time.DayOfWeek.SUNDAY)).toLocalDate().atTime(23, 59, 59));
|
||
break;
|
||
case "monthly":
|
||
goal.setPeriodStart(now.with(TemporalAdjusters.firstDayOfMonth()).toLocalDate().atStartOfDay());
|
||
goal.setPeriodEnd(now.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate().atTime(23, 59, 59));
|
||
break;
|
||
case "quarterly":
|
||
int quarter = (now.getMonthValue() - 1) / 3;
|
||
LocalDateTime quarterStart = LocalDateTime.of(now.getYear(), quarter * 3 + 1, 1, 0, 0);
|
||
goal.setPeriodStart(quarterStart);
|
||
goal.setPeriodEnd(quarterStart.plusMonths(3).minusSeconds(1));
|
||
break;
|
||
case "yearly":
|
||
goal.setPeriodStart(now.with(TemporalAdjusters.firstDayOfYear()).toLocalDate().atStartOfDay());
|
||
goal.setPeriodEnd(now.with(TemporalAdjusters.lastDayOfYear()).toLocalDate().atTime(23, 59, 59));
|
||
break;
|
||
case "custom":
|
||
// 自定义周期,使用传入的时间
|
||
if (goal.getPeriodStart() == null || goal.getPeriodEnd() == null) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "自定义周期必须指定开始和结束时间");
|
||
}
|
||
break;
|
||
default:
|
||
goal.setPeriodStart(now);
|
||
goal.setPeriodEnd(now.plusMonths(1));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新目标
|
||
*/
|
||
@Transactional
|
||
public UserGoal updateGoal(Long id, UserGoal goal) {
|
||
UserGoal existing = userGoalMapper.selectById(id);
|
||
if (existing == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
|
||
goal.setId(id);
|
||
userGoalMapper.updateById(goal);
|
||
|
||
return userGoalMapper.selectById(id);
|
||
}
|
||
|
||
/**
|
||
* 更新目标进度
|
||
*/
|
||
@Transactional
|
||
public UserGoal updateProgress(Long goalId, Integer progressValue) {
|
||
UserGoal goal = userGoalMapper.selectById(goalId);
|
||
if (goal == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
|
||
goal.setCurrentValue(progressValue);
|
||
|
||
// 检查是否完成
|
||
if (progressValue >= goal.getTargetValue() && !"completed".equals(goal.getStatus())) {
|
||
goal.setStatus("completed");
|
||
goal.setCompletedTime(LocalDateTime.now());
|
||
|
||
// 发送完成通知
|
||
notificationService.sendNotification(
|
||
goal.getUserId(),
|
||
"目标达成",
|
||
String.format("恭喜!您的目标「%s」已完成!", goal.getGoalName()),
|
||
"goal",
|
||
goal.getId()
|
||
);
|
||
}
|
||
|
||
userGoalMapper.updateById(goal);
|
||
return goal;
|
||
}
|
||
|
||
/**
|
||
* 增量更新目标进度
|
||
*/
|
||
@Transactional
|
||
public void incrementProgress(Long userId, String goalType, int increment) {
|
||
// 获取用户当前进行中的该类型目标
|
||
LambdaQueryWrapper<UserGoal> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(UserGoal::getUserId, userId)
|
||
.eq(UserGoal::getGoalType, goalType)
|
||
.eq(UserGoal::getStatus, "active")
|
||
.le(UserGoal::getPeriodStart, LocalDateTime.now())
|
||
.ge(UserGoal::getPeriodEnd, LocalDateTime.now());
|
||
|
||
List<UserGoal> goals = userGoalMapper.selectList(wrapper);
|
||
|
||
for (UserGoal goal : goals) {
|
||
int newValue = goal.getCurrentValue() + increment;
|
||
updateProgress(goal.getId(), newValue);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取用户目标列表
|
||
*/
|
||
public List<UserGoal> getUserGoals(Long userId, String status) {
|
||
LambdaQueryWrapper<UserGoal> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(UserGoal::getUserId, userId);
|
||
|
||
if (status != null && !status.isEmpty()) {
|
||
wrapper.eq(UserGoal::getStatus, status);
|
||
}
|
||
|
||
wrapper.orderByDesc(UserGoal::getCreateTime);
|
||
return userGoalMapper.selectList(wrapper);
|
||
}
|
||
|
||
/**
|
||
* 获取用户当前活跃目标
|
||
*/
|
||
public List<UserGoal> getActiveGoals(Long userId) {
|
||
LocalDateTime now = LocalDateTime.now();
|
||
LambdaQueryWrapper<UserGoal> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(UserGoal::getUserId, userId)
|
||
.eq(UserGoal::getStatus, "active")
|
||
.le(UserGoal::getPeriodStart, now)
|
||
.ge(UserGoal::getPeriodEnd, now)
|
||
.orderByAsc(UserGoal::getPeriodEnd);
|
||
|
||
return userGoalMapper.selectList(wrapper);
|
||
}
|
||
|
||
/**
|
||
* 获取目标详情
|
||
*/
|
||
public UserGoal getGoalById(Long id) {
|
||
UserGoal goal = userGoalMapper.selectById(id);
|
||
if (goal == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
return goal;
|
||
}
|
||
|
||
/**
|
||
* 领取目标奖励
|
||
*/
|
||
@Transactional
|
||
public boolean claimReward(Long goalId, Long userId) {
|
||
UserGoal goal = userGoalMapper.selectById(goalId);
|
||
if (goal == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
|
||
if (!goal.getUserId().equals(userId)) {
|
||
throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标");
|
||
}
|
||
|
||
if (!"completed".equals(goal.getStatus())) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "目标未完成,无法领取奖励");
|
||
}
|
||
|
||
if (goal.getRewardClaimed() == 1) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "奖励已领取");
|
||
}
|
||
|
||
if (goal.getRewardScore() == null || goal.getRewardScore() <= 0) {
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "此目标无积分奖励");
|
||
}
|
||
|
||
// 更新用户积分
|
||
User user = userMapper.selectById(userId);
|
||
int currentScore = user.getScore() != null ? user.getScore() : 0;
|
||
user.setScore(currentScore + goal.getRewardScore());
|
||
userMapper.updateById(user);
|
||
|
||
// 标记已领取
|
||
goal.setRewardClaimed(1);
|
||
userGoalMapper.updateById(goal);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 取消目标
|
||
*/
|
||
@Transactional
|
||
public void cancelGoal(Long id, Long userId) {
|
||
UserGoal goal = userGoalMapper.selectById(id);
|
||
if (goal == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
|
||
if (!goal.getUserId().equals(userId)) {
|
||
throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标");
|
||
}
|
||
|
||
goal.setStatus("cancelled");
|
||
userGoalMapper.updateById(goal);
|
||
}
|
||
|
||
/**
|
||
* 删除目标
|
||
*/
|
||
@Transactional
|
||
public void deleteGoal(Long id, Long userId) {
|
||
UserGoal goal = userGoalMapper.selectById(id);
|
||
if (goal == null) {
|
||
throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在");
|
||
}
|
||
|
||
if (!goal.getUserId().equals(userId)) {
|
||
throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标");
|
||
}
|
||
|
||
userGoalMapper.deleteById(id);
|
||
}
|
||
|
||
/**
|
||
* 定时任务:检查过期目标
|
||
*/
|
||
@Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行
|
||
@Transactional
|
||
public void checkExpiredGoals() {
|
||
LocalDateTime now = LocalDateTime.now();
|
||
|
||
LambdaQueryWrapper<UserGoal> wrapper = new LambdaQueryWrapper<>();
|
||
wrapper.eq(UserGoal::getStatus, "active")
|
||
.lt(UserGoal::getPeriodEnd, now);
|
||
|
||
List<UserGoal> expiredGoals = userGoalMapper.selectList(wrapper);
|
||
|
||
for (UserGoal goal : expiredGoals) {
|
||
if (goal.getCurrentValue() >= goal.getTargetValue()) {
|
||
goal.setStatus("completed");
|
||
goal.setCompletedTime(now);
|
||
} else {
|
||
goal.setStatus("failed");
|
||
|
||
// 发送未完成通知
|
||
notificationService.sendNotification(
|
||
goal.getUserId(),
|
||
"目标未完成",
|
||
String.format("很遗憾,您的目标「%s」未能在期限内完成。继续努力!", goal.getGoalName()),
|
||
"goal",
|
||
goal.getId()
|
||
);
|
||
}
|
||
userGoalMapper.updateById(goal);
|
||
}
|
||
|
||
log.info("检查过期目标完成,处理了{}个目标", expiredGoals.size());
|
||
}
|
||
|
||
/**
|
||
* 创建预设目标(系统推荐)
|
||
*/
|
||
@Transactional
|
||
public UserGoal createPresetGoal(Long userId, String presetType) {
|
||
UserGoal goal = new UserGoal();
|
||
goal.setUserId(userId);
|
||
|
||
switch (presetType) {
|
||
case "weekly_task":
|
||
goal.setGoalType("task_count");
|
||
goal.setGoalName("本周完成10个任务");
|
||
goal.setDescription("在本周内完成10个设计任务");
|
||
goal.setTargetValue(10);
|
||
goal.setPeriod("weekly");
|
||
goal.setRewardScore(50);
|
||
break;
|
||
case "monthly_task":
|
||
goal.setGoalType("task_count");
|
||
goal.setGoalName("本月完成40个任务");
|
||
goal.setDescription("在本月内完成40个设计任务");
|
||
goal.setTargetValue(40);
|
||
goal.setPeriod("monthly");
|
||
goal.setRewardScore(200);
|
||
break;
|
||
case "monthly_score":
|
||
goal.setGoalType("score");
|
||
goal.setGoalName("本月获得1000积分");
|
||
goal.setDescription("在本月内累计获得1000积分");
|
||
goal.setTargetValue(1000);
|
||
goal.setPeriod("monthly");
|
||
goal.setRewardScore(100);
|
||
break;
|
||
case "top_10":
|
||
goal.setGoalType("rank");
|
||
goal.setGoalName("进入排行榜前10");
|
||
goal.setDescription("在本月排行榜中进入前10名");
|
||
goal.setTargetValue(10);
|
||
goal.setPeriod("monthly");
|
||
goal.setRewardScore(150);
|
||
break;
|
||
default:
|
||
throw new BusinessException(ResultCode.PARAM_ERROR, "未知的预设目标类型");
|
||
}
|
||
|
||
return createGoal(goal);
|
||
}
|
||
}
|