| | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.atomic.AtomicLong; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | @Autowired |
| | | private SatisfactionScriptMapper satisfactionScriptMapper; |
| | | |
| | | public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds) { |
| | | //先清理一下重复数据 |
| | | List<Long> repeatIds = satisfactionMapper.selectRepeatDetailIds(); |
| | | if (!CollectionUtils.isEmpty(repeatIds)) { |
| | | satisfactionMapper.deleteRepeatDetailByIds(repeatIds); |
| | | /** |
| | | * 重复明细清理的“最近一次执行时间”(毫秒)。见 {@link #cleanRepeatDetailsIfDue()}。 |
| | | */ |
| | | private static final AtomicLong LAST_REPEAT_CLEAN_AT = new AtomicLong(0L); |
| | | |
| | | /** |
| | | * 重复明细清理的最小间隔:10 分钟。 |
| | | */ |
| | | private static final long REPEAT_CLEAN_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10); |
| | | |
| | | /** |
| | | * 清理 service_subtask_detail 里的重复明细(同一 sub_id + questiontext 只留最新一条)。 |
| | | * <p> |
| | | * ⚠️ <b>为什么要有节流</b>:{@code selectRepeatDetailIds} 是一条 |
| | | * 「对整张 service_subtask_detail 按 (sub_id, questiontext) 分组 + HAVING COUNT(*) > 1」的 |
| | | * 全表聚合,且<b>没有任何日期/病区/子任务范围</b>;一旦查出重复还会执行逻辑删除(写操作)。 |
| | | * 原实现在每次满意度统计请求前都无条件跑一遍,于是: |
| | | * <ul> |
| | | * <li>统计接口的性能与用户所选条件<b>完全无关</b>,明细表越大越慢;</li> |
| | | * <li>一个只读接口在偷偷做 DML。</li> |
| | | * </ul> |
| | | * 而统计 SQL 本身已经用 {@code d.id = (SELECT MAX(...))} 做了“同题只取最新一条”的去重, |
| | | * 所以这个前置清理对本次统计结果<b>不是必需</b>的。这里改成“每个实例最多 10 分钟清一次”, |
| | | * 既保留清理能力,又不让每个请求都为它买单。 |
| | | * <p> |
| | | * 更彻底的做法是把它挪到定时任务里(只留一个维护入口),需要的话可以再调整。 |
| | | */ |
| | | private void cleanRepeatDetailsIfDue() { |
| | | long now = System.currentTimeMillis(); |
| | | long last = LAST_REPEAT_CLEAN_AT.get(); |
| | | if (now - last < REPEAT_CLEAN_INTERVAL_MS) { |
| | | return; |
| | | } |
| | | // CAS 抢占:并发请求里只放一个线程去清理,其余直接返回 |
| | | if (!LAST_REPEAT_CLEAN_AT.compareAndSet(last, now)) { |
| | | return; |
| | | } |
| | | try { |
| | | List<Long> repeatIds = satisfactionMapper.selectRepeatDetailIds(); |
| | | if (!CollectionUtils.isEmpty(repeatIds)) { |
| | | satisfactionMapper.deleteRepeatDetailByIds(repeatIds); |
| | | log.info("满意度统计前置清理:逻辑删除重复明细 {} 条", repeatIds.size()); |
| | | } |
| | | } catch (Exception e) { |
| | | // 清理失败不能影响统计本身 |
| | | log.warn("满意度统计前置清理重复明细失败,已跳过本次清理", e); |
| | | } |
| | | } |
| | | |
| | | public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds) { |
| | | //前置清理重复明细(已节流:每个实例最多 10 分钟一次,不再每个请求都跑全表 GROUP BY) |
| | | cleanRepeatDetailsIfDue(); |
| | | List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = satisfactionMapper.statQuestionOption(taskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType, subtaskIds); |
| | | |
| | | // 处理 targetvalue:按 & 分割成多条记录 |
| | |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | // 2. 判断按科室还是按病区 |
| | | boolean byDept = vo.getLeaveldeptcodes() != null; |
| | | boolean byDistrict = !byDept && vo.getLeavehospitaldistrictcodes() != null; |
| | | // 2. 判断按科室还是按病区(离院 / 在院两组参数都算) |
| | | boolean byDept = vo.getLeaveldeptcodes() != null || vo.getDeptcodes() != null; |
| | | boolean byDistrict = !byDept |
| | | && (vo.getLeavehospitaldistrictcodes() != null || vo.getHospitaldistrictcodes() != null); |
| | | if (!byDept && !byDistrict) { |
| | | return Collections.emptyMap(); |
| | | } |
| | |
| | | } |
| | | |
| | | public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, Date startFinishTime, Date endFinishTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) { |
| | | //先清理一下重复数据 |
| | | List<Long> repeatIds = satisfactionMapper.selectRepeatDetailIds(); |
| | | if (!CollectionUtils.isEmpty(repeatIds)) { |
| | | satisfactionMapper.deleteRepeatDetailByIds(repeatIds); |
| | | } |
| | | //前置清理重复明细(已节流:每个实例最多 10 分钟一次,不再每个请求都跑全表 GROUP BY) |
| | | cleanRepeatDetailsIfDue(); |
| | | |
| | | List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = satisfactionMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType); |
| | | // SQL 已按「题目 + 选项」汇总数量,这里再按题目合并(不同模板的 targetvalue 选项顺序不同,同一选项会拆成多行), |