| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- package com.etotem.cfc.service;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
- import com.etotem.cfc.entity.HealthReportDraft;
- import com.etotem.cfc.mapper.HealthReportDraftMapper;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.util.Date;
- /**
- * 健康报告草稿服务 — 两阶段入库的中间态管理。
- * 草稿保存时间默认 24 小时,过期后 confirm 接口拒绝。
- */
- @Slf4j
- @Service
- public class HealthReportDraftService {
- /** 草稿有效期(毫秒) */
- public static final long DRAFT_TTL_MS = 72L * 60 * 60 * 1000;
- @Resource
- private HealthReportDraftMapper draftMapper;
- /**
- * 创建草稿。返回带 id 的实体。
- */
- public HealthReportDraft createDraft(Long userId, Long familyId, String reportType,
- String fileUrl, String originalFilename, String payloadJson) {
- return createDraft(userId, familyId, reportType, fileUrl, originalFilename, payloadJson, null);
- }
- /**
- * 创建草稿(含文件哈希,用于重复上传检测)。
- */
- public HealthReportDraft createDraft(Long userId, Long familyId, String reportType,
- String fileUrl, String originalFilename,
- String payloadJson, String fileHash) {
- return createDraft(userId, familyId, reportType, fileUrl, originalFilename, payloadJson, fileHash, null);
- }
- /**
- * 创建草稿(含文件哈希 + 归属成员ID,A2/DAN 自动采集必需)。
- */
- public HealthReportDraft createDraft(Long userId, Long familyId, String reportType,
- String fileUrl, String originalFilename,
- String payloadJson, String fileHash, Long memberId) {
- HealthReportDraft draft = new HealthReportDraft();
- draft.setUserId(userId);
- draft.setFamilyId(familyId);
- draft.setMemberId(memberId);
- draft.setReportType(reportType);
- draft.setFileUrl(fileUrl);
- draft.setOriginalFilename(originalFilename);
- draft.setPayloadJson(payloadJson);
- draft.setFileHash(fileHash);
- draft.setStatus("pending");
- Date now = new Date();
- draft.setCreatedAt(now);
- draft.setUpdatedAt(now);
- draft.setExpireAt(new Date(now.getTime() + DRAFT_TTL_MS));
- draftMapper.insert(draft);
- log.info("草稿已创建: draftId={}, userId={}, reportType={}, memberId={}, fileHash={}",
- draft.getId(), userId, reportType, memberId, fileHash);
- return draft;
- }
- /**
- * 按主键查询草稿(无视状态/过期,供异步采集等内部逻辑使用)。
- */
- public HealthReportDraft getDraftById(Long draftId) {
- return draftMapper.selectById(draftId);
- }
- /**
- * 查询草稿(仅返回 pending 且未过期的)。
- */
- public HealthReportDraft getActiveDraft(Long draftId) {
- HealthReportDraft draft = draftMapper.selectById(draftId);
- if (draft == null) {
- return null;
- }
- if (!"pending".equals(draft.getStatus())) {
- return null;
- }
- if (draft.getExpireAt() != null && draft.getExpireAt().before(new Date())) {
- // expire_at 可能被 ON UPDATE CURRENT_TIMESTAMP 在解析写回时重置;
- // 仍以 created_at + TTL 为准,避免刚解析完的草稿被误判过期。
- Date created = draft.getCreatedAt();
- if (created == null || System.currentTimeMillis() - created.getTime() > DRAFT_TTL_MS) {
- return null;
- }
- log.warn("草稿 expire_at 已过期但 created_at 仍在 TTL 内,放行: draftId={}, expireAt={}, createdAt={}",
- draftId, draft.getExpireAt(), created);
- }
- return draft;
- }
- /**
- * 标记草稿已确认(成功入库后调用)。
- */
- public void markConfirmed(Long draftId, Long reportId) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getStatus, "confirmed")
- .set(HealthReportDraft::getConfirmedReportId, reportId)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 标记草稿已丢弃。
- */
- public void markDiscarded(Long draftId) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getStatus, "discarded")
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 清理过期草稿(可由定时任务调用)。
- */
- public int purgeExpired() {
- LambdaQueryWrapper<HealthReportDraft> qw = new LambdaQueryWrapper<>();
- qw.lt(HealthReportDraft::getExpireAt, new Date())
- .eq(HealthReportDraft::getStatus, "pending");
- int count = draftMapper.selectCount(qw).intValue();
- if (count > 0) {
- draftMapper.delete(qw);
- log.info("已清理 {} 条过期草稿", count);
- }
- return count;
- }
- /**
- * 更新草稿载荷 JSON(解析完成后写入)
- */
- public void updatePayload(Long draftId, String payloadJson) {
- Date now = new Date();
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getPayloadJson, payloadJson)
- .set(HealthReportDraft::getExpireAt, new Date(now.getTime() + DRAFT_TTL_MS))
- .set(HealthReportDraft::getUpdatedAt, now);
- draftMapper.update(null, uw);
- }
- public void updateNote(Long draftId, String note) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getRemark, note)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 标记异步解析状态(collecting/completed/failed)
- */
- public void updateParseStatus(Long draftId, String parseStatus) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getParseStatus, parseStatus)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 查询某用户仍处于"采集中"的草稿(供报告列表顶部展示进度)
- */
- public java.util.List<HealthReportDraft> listCollectingByUser(Long userId) {
- if (userId == null) {
- return java.util.Collections.emptyList();
- }
- LambdaQueryWrapper<HealthReportDraft> qw = new LambdaQueryWrapper<>();
- qw.eq(HealthReportDraft::getUserId, userId)
- .eq(HealthReportDraft::getParseStatus, "collecting")
- .orderByDesc(HealthReportDraft::getCreatedAt)
- .last("LIMIT 20");
- return draftMapper.selectList(qw);
- }
- /**
- * 异步解析开始时记录解析方式
- */
- public void markCollecting(Long draftId, String parseMethod) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getParseStatus, "collecting")
- .set(HealthReportDraft::getParseMethod, parseMethod)
- .set(HealthReportDraft::getParseError, null)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 解析方式确定后单独更新(用于异步流程:先置 collecting,指纹判定后写具体方式)
- */
- public void updateParseMethod(Long draftId, String parseMethod) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getParseMethod, parseMethod)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 报告类型变更后单独更新(DAN 识别完成:draft.reportType 置为 dan_a2,前端按此跳转报告页)
- */
- public void updateReportType(Long draftId, String reportType) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getReportType, reportType)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 异步解析失败记录错误
- */
- public void markCollectFailed(Long draftId, String error) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- String safe = error != null && error.length() > 2000 ? error.substring(0, 2000) : error;
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getParseStatus, "failed")
- .set(HealthReportDraft::getParseError, safe)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- /**
- * 异步解析完成 + 已入库,写入状态与关联报告ID
- */
- public void markCollectCompleted(Long draftId, Long reportId) {
- LambdaUpdateWrapper<HealthReportDraft> uw = new LambdaUpdateWrapper<>();
- uw.eq(HealthReportDraft::getId, draftId)
- .set(HealthReportDraft::getParseStatus, "completed")
- .set(HealthReportDraft::getParseError, null)
- .set(HealthReportDraft::getStatus, "confirmed")
- .set(HealthReportDraft::getConfirmedReportId, reportId)
- .set(HealthReportDraft::getUpdatedAt, new Date());
- draftMapper.update(null, uw);
- }
- }
|