|
|
@@ -0,0 +1,242 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.etotem.cfc.entity.*;
|
|
|
+import com.etotem.cfc.mapper.*;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class MicroActionService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MicroActionMapper microActionMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MicroActionRecordMapper microActionRecordMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private HealthCheckinMapper healthCheckinMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FamilyMemberMapper familyMemberMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TaskMapper taskMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private PointsLogMapper pointsLogMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private EnergyService energyService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OnboardingService onboardingService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private GrowthTaskService growthTaskService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private SysConfigService sysConfigService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有启用的微行动列表
|
|
|
+ */
|
|
|
+ public List<MicroAction> getAllActions() {
|
|
|
+ return microActionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<MicroAction>()
|
|
|
+ .eq(MicroAction::getStatus, 1)
|
|
|
+ .orderByAsc(MicroAction::getSortOrder)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取今日推荐的微行动
|
|
|
+ * 如果有未使用的新手体验配额,返回随机行动
|
|
|
+ * 如果已完成今日配额,返回null
|
|
|
+ */
|
|
|
+ public MicroAction getTodayAction(Long childId) {
|
|
|
+ // Check if already completed for today
|
|
|
+ MicroActionRecord todayRecord = getTodayRecord(childId);
|
|
|
+ if (todayRecord != null) {
|
|
|
+ return null; // Already completed today
|
|
|
+ }
|
|
|
+ // Get all active actions
|
|
|
+ List<MicroAction> actions = getAllActions();
|
|
|
+ if (actions.isEmpty()) return null;
|
|
|
+
|
|
|
+ // Pick one based on the day of month (consistent for the whole day)
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
|
|
|
+ int index = dayOfMonth % actions.size();
|
|
|
+ return actions.get(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查今日是否已完成微行动
|
|
|
+ */
|
|
|
+ public boolean hasCompletedToday(Long childId) {
|
|
|
+ return getTodayRecord(childId) != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取今日完成记录
|
|
|
+ */
|
|
|
+ private MicroActionRecord getTodayRecord(Long childId) {
|
|
|
+ if (childId == null) return null;
|
|
|
+ String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
|
|
+ return microActionRecordMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<MicroActionRecord>()
|
|
|
+ .eq(MicroActionRecord::getChildId, childId)
|
|
|
+ .apply("DATE(completed_date) = {0}", today)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查今日是否已健康打卡(互斥逻辑)
|
|
|
+ */
|
|
|
+ private boolean hasHealthCheckinToday(Long childId) {
|
|
|
+ if (childId == null) return false;
|
|
|
+ String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
|
|
+ HealthCheckin existing = healthCheckinMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<HealthCheckin>()
|
|
|
+ .eq(HealthCheckin::getChildId, childId)
|
|
|
+ .apply("DATE(checkin_date) = {0}", today)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ return existing != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完成微行动
|
|
|
+ * 与健康打卡互斥:同一天只能选择一种
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public MicroActionRecord completeAction(Long userId, Long childId, Long actionId) {
|
|
|
+ if (childId == null) {
|
|
|
+ throw new RuntimeException("childId不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if already completed micro action today
|
|
|
+ if (hasCompletedToday(childId)) {
|
|
|
+ throw new RuntimeException("今日微行动已完成,请明天再来");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check mutual exclusion with health checkin
|
|
|
+ if (hasHealthCheckinToday(childId)) {
|
|
|
+ throw new RuntimeException("今日已健康打卡,已完成今日签到");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify the action exists and is active
|
|
|
+ MicroAction action = microActionMapper.selectById(actionId);
|
|
|
+ if (action == null || action.getStatus() != 1) {
|
|
|
+ throw new RuntimeException("微行动不存在或已下架");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create record
|
|
|
+ MicroActionRecord record = new MicroActionRecord();
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setChildId(childId);
|
|
|
+ record.setActionId(actionId);
|
|
|
+ record.setCompletedDate(new Date());
|
|
|
+ record.setEarnedEnergy(1);
|
|
|
+ record.setCreatedAt(new Date());
|
|
|
+ microActionRecordMapper.insert(record);
|
|
|
+
|
|
|
+ // Create task in task system (same pattern as health checkin)
|
|
|
+ try {
|
|
|
+ int points = 5;
|
|
|
+ String pointsStr = sysConfigService.getValue("health_checkin_points");
|
|
|
+ if (pointsStr != null && !pointsStr.isEmpty()) {
|
|
|
+ try { points = Integer.parseInt(pointsStr); } catch (NumberFormatException e) { /* use default */ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Task task = new Task();
|
|
|
+ FamilyMember child = familyMemberMapper.selectById(childId);
|
|
|
+ if (child != null) {
|
|
|
+ task.setFamilyId(child.getFamilyId());
|
|
|
+ }
|
|
|
+ task.setCreatorId(userId);
|
|
|
+ task.setExecutorType("child");
|
|
|
+ task.setExecutorId(childId);
|
|
|
+ task.setChildId(childId);
|
|
|
+ task.setTitle("微行动");
|
|
|
+ task.setDescription("自动生成 - " + action.getName());
|
|
|
+ task.setPoints(points);
|
|
|
+ task.setRepeatType("none");
|
|
|
+ task.setCategory("身体类");
|
|
|
+ task.setNeedReview(0);
|
|
|
+ task.setReviewByCategory(0);
|
|
|
+ task.setTaskType("onetime");
|
|
|
+ task.setSourceType("micro_action");
|
|
|
+ task.setSourceId(record.getId());
|
|
|
+ task.setStatus("completed");
|
|
|
+ task.setCompletedAt(new Date());
|
|
|
+ task.setCreatedAt(new Date());
|
|
|
+ task.setUpdatedAt(new Date());
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
+ cal.set(Calendar.MINUTE, 59);
|
|
|
+ cal.set(Calendar.SECOND, 59);
|
|
|
+ task.setDeadline(cal.getTime());
|
|
|
+ taskMapper.insert(task);
|
|
|
+
|
|
|
+ // Update child totalPoints
|
|
|
+ if (child != null) {
|
|
|
+ child.setTotalPoints(child.getTotalPoints() + points);
|
|
|
+ child.setUpdatedAt(new Date());
|
|
|
+ familyMemberMapper.updateById(child);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Insert PointsLog
|
|
|
+ PointsLog pointsLog = new PointsLog();
|
|
|
+ pointsLog.setChildId(childId);
|
|
|
+ pointsLog.setTaskId(task.getId());
|
|
|
+ pointsLog.setAmount(points);
|
|
|
+ pointsLog.setType("earn");
|
|
|
+ pointsLog.setCategory("task");
|
|
|
+ pointsLog.setDescription("微行动");
|
|
|
+ pointsLog.setCreatedAt(new Date());
|
|
|
+ pointsLogMapper.insert(pointsLog);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("微行动任务创建失败: recordId={}, error={}", record.getId(), e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Award energy
|
|
|
+ try {
|
|
|
+ energyService.awardEnergy(childId, "micro_action", record.getId(), 1,
|
|
|
+ "微行动", 30);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("微行动能量发放失败: recordId={}, error={}", record.getId(), e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Trigger onboarding and growth tasks
|
|
|
+ try { onboardingService.completeTask(userId, "FIRST_CHECKIN"); } catch (Exception e) { /* ignore */ }
|
|
|
+ try { growthTaskService.updateProgress(userId, "DAILY_CHECKIN", 1); } catch (Exception e) { log.warn("成长任务签到进度更新失败: userId={}", userId); }
|
|
|
+
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取最近N天的微行动记录
|
|
|
+ */
|
|
|
+ public List<MicroActionRecord> getRecentRecords(Long childId, int days) {
|
|
|
+ if (childId == null) return Collections.emptyList();
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -days);
|
|
|
+ Date since = cal.getTime();
|
|
|
+ return microActionRecordMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<MicroActionRecord>()
|
|
|
+ .eq(MicroActionRecord::getChildId, childId)
|
|
|
+ .ge(MicroActionRecord::getCompletedDate, since)
|
|
|
+ .orderByDesc(MicroActionRecord::getCompletedDate)
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|