| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- package com.etotem.cfc.service;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.etotem.cfc.dto.EmotionAlertVO;
- import com.etotem.cfc.entity.EmotionAlert;
- import com.etotem.cfc.entity.EmotionCheckin;
- import com.etotem.cfc.mapper.EmotionAlertMapper;
- import com.etotem.cfc.mapper.EmotionCheckinMapper;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.util.*;
- import java.util.stream.Collectors;
- /**
- * 情绪风险告警服务 —— 在每次情绪打卡完成后自动检查风险模式:
- * 1. 连续低情绪(3天+ moodScore <= 4)
- * 2. 情绪锐降(比前一天下降 4+ 分)
- * 3. 备注中的危机关键词
- */
- @Slf4j
- @Service
- public class EmotionAlertService {
- @Resource
- private EmotionAlertMapper emotionAlertMapper;
- @Resource
- private EmotionCheckinMapper emotionCheckinMapper;
- /**
- * 检查情绪打卡是否存在风险,返回新创建的告警列表
- */
- public List<EmotionAlert> checkRiskAfterCheckin(EmotionCheckin checkin) {
- List<EmotionAlert> newAlerts = new ArrayList<>();
- // 1. 检查备注中是否含危机关键词
- if (checkin.getNote() != null) {
- Set<String> matched = matchCrisisKeywords(checkin.getNote());
- if (!matched.isEmpty()) {
- EmotionAlert alert = createAlert(
- checkin.getChildId(),
- "crisis_keyword",
- "high",
- "⚠️ 危机关键词预警",
- "情绪打卡备注中检测到危机关键词:" + String.join(", ", matched) + ",建议及时关注。",
- checkin.getId()
- );
- newAlerts.add(alert);
- }
- }
- // 2. 检查是否有 moodScore 数据
- if (checkin.getMoodScore() == null) {
- return newAlerts;
- }
- // 3. 检查连续低情绪
- List<EmotionCheckin> recent = getRecentCheckins(checkin.getChildId(), 7);
- List<EmotionCheckin> lowMoodDays = recent.stream()
- .filter(e -> e.getMoodScore() != null && e.getMoodScore() <= 4)
- .collect(Collectors.toList());
- if (lowMoodDays.size() >= 3) {
- // 检查是否已有未解决的连续低情绪告警
- boolean hasExisting = hasActiveAlert(checkin.getChildId(), "consecutive_low_mood");
- if (!hasExisting) {
- EmotionAlert alert = createAlert(
- checkin.getChildId(),
- "consecutive_low_mood",
- lowMoodDays.size() >= 5 ? "high" : "medium",
- "⚡ 连续低情绪提醒",
- "最近" + recent.size() + "天中有" + lowMoodDays.size() + "天情绪偏低(≤4分),建议关注孩子心理状态。",
- checkin.getId()
- );
- newAlerts.add(alert);
- }
- }
- // 4. 检查情绪锐降 (与最近一次打卡比较)
- if (recent.size() >= 2) {
- EmotionCheckin prev = recent.get(1); // 上一次
- if (prev.getMoodScore() != null && (prev.getMoodScore() - checkin.getMoodScore()) >= 4) {
- boolean hasDeclineAlert = hasActiveAlert(checkin.getChildId(), "sharp_decline");
- if (!hasDeclineAlert) {
- EmotionAlert alert = createAlert(
- checkin.getChildId(),
- "sharp_decline",
- "medium",
- "📉 情绪明显下降",
- "情绪评分从" + prev.getMoodScore() + "分降至" + checkin.getMoodScore() + "分,下降幅度较大。",
- checkin.getId()
- );
- newAlerts.add(alert);
- }
- }
- }
- return newAlerts;
- }
- /**
- * 批量创建检查告警(用于后台定时任务或全量扫描)
- */
- public List<EmotionAlertVO> getActiveAlerts(Long childId) {
- LambdaQueryWrapper<EmotionAlert> wrapper = new LambdaQueryWrapper<EmotionAlert>()
- .eq(EmotionAlert::getChildId, childId)
- .eq(EmotionAlert::getResolved, false)
- .orderByDesc(EmotionAlert::getCreatedAt);
- return emotionAlertMapper.selectList(wrapper)
- .stream().map(this::toVO).collect(Collectors.toList());
- }
- public void resolveAlert(Long alertId) {
- EmotionAlert alert = emotionAlertMapper.selectById(alertId);
- if (alert != null) {
- alert.setResolved(true);
- alert.setResolvedAt(new Date());
- emotionAlertMapper.updateById(alert);
- }
- }
- // === 内部方法 ===
- private static final Set<String> CRISIS_KEYWORDS = new HashSet<>(Arrays.asList(
- "自杀", "不想活", "死了算了", "活不下去", "自残", "伤害自己",
- "好痛苦", "没意思", "撑不下去了", "想死", "结束",
- "suicide", "kill myself", "end my life", "self-harm", "cutting"
- ));
- private Set<String> matchCrisisKeywords(String text) {
- Set<String> matched = new HashSet<>();
- for (String keyword : CRISIS_KEYWORDS) {
- if (text.contains(keyword)) {
- matched.add(keyword);
- }
- }
- return matched;
- }
- private List<EmotionCheckin> getRecentCheckins(Long childId, int limit) {
- return emotionCheckinMapper.selectLatestByChildId(childId, limit);
- }
- private boolean hasActiveAlert(Long childId, String alertType) {
- LambdaQueryWrapper<EmotionAlert> wrapper = new LambdaQueryWrapper<EmotionAlert>()
- .eq(EmotionAlert::getChildId, childId)
- .eq(EmotionAlert::getAlertType, alertType)
- .eq(EmotionAlert::getResolved, false);
- return emotionAlertMapper.selectCount(wrapper) > 0;
- }
- private EmotionAlert createAlert(Long childId, String alertType, String severity,
- String title, String message, Long relatedCheckinId) {
- EmotionAlert alert = new EmotionAlert();
- alert.setChildId(childId);
- alert.setAlertType(alertType);
- alert.setSeverity(severity);
- alert.setTitle(title);
- alert.setMessage(message);
- alert.setRelatedCheckinId(relatedCheckinId);
- alert.setResolved(false);
- alert.setCreatedAt(new Date());
- emotionAlertMapper.insert(alert);
- log.info("情绪告警创建: childId={}, type={}, severity={}", childId, alertType, severity);
- return alert;
- }
- private EmotionAlertVO toVO(EmotionAlert alert) {
- EmotionAlertVO vo = new EmotionAlertVO();
- vo.setId(alert.getId());
- vo.setChildId(alert.getChildId());
- vo.setAlertType(alert.getAlertType());
- vo.setSeverity(alert.getSeverity());
- vo.setTitle(alert.getTitle());
- vo.setMessage(alert.getMessage());
- vo.setResolved(alert.getResolved());
- vo.setCreatedAt(alert.getCreatedAt());
- return vo;
- }
- }
|