package com.qianwen.smartman.modules.mdc.service.impl; import java.io.Serializable; import java.sql.Timestamp; import java.time.LocalDate; import java.time.ZoneId; import java.time.chrono.ChronoLocalDate; import java.time.chrono.ChronoLocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qianwen.core.cache.utils.CacheUtil; import com.qianwen.core.log.exception.ServiceException; import com.qianwen.core.mp.support.BaseEntityWrapper; import com.qianwen.core.mp.support.Condition; import com.qianwen.core.mp.support.Query; import com.qianwen.core.redis.lock.RedisLockClient; import com.qianwen.core.secure.utils.AuthUtil; import com.qianwen.core.tool.utils.DateUtil; import com.qianwen.core.tool.utils.Func; import com.qianwen.smartman.common.constant.ExtCacheConstant; import com.qianwen.smartman.common.enums.GlobalWcsTypeEnum; import com.qianwen.smartman.common.utils.Lambda; import com.qianwen.smartman.modules.smis.entity.Employee; import com.qianwen.smartman.modules.smis.entity.Workstation; import com.qianwen.smartman.modules.smis.service.IEmployeeService; import com.qianwen.smartman.modules.smis.service.IGlobalWcsService; import com.qianwen.smartman.modules.smis.service.IWorkstationService; import com.qianwen.smartman.modules.mdc.convert.StatusRecordConvert; import com.qianwen.smartman.modules.mdc.dto.GroupFeedbackCacheDTO; import com.qianwen.smartman.modules.mdc.dto.WorkstationEndAndStartImmediateFeedBackDTO; import com.qianwen.smartman.modules.mdc.dto.WorkstationEndImmediateFeedBackDTO; import com.qianwen.smartman.modules.mdc.dto.WorkstationImmediateFeedBackDTO; import com.qianwen.smartman.modules.mdc.dto.WorkstationNoImmediateFeedBackDTO; import com.qianwen.smartman.modules.mdc.entity.WorkstationFeedback; import com.qianwen.smartman.modules.mdc.enums.FeedbackStatus; import com.qianwen.smartman.modules.mdc.enums.FeedbackType; import com.qianwen.smartman.modules.mdc.mapper.WorkstationFeedbackMapper; import com.qianwen.smartman.modules.mdc.service.IWorkstationFeedbackDetailService; import com.qianwen.smartman.modules.mdc.service.IWorkstationFeedbackService; import com.qianwen.smartman.modules.mdc.vo.StatusRecordVO; import com.qianwen.smartman.modules.mdc.vo.WorkstationFeedbackInfoVO; import com.qianwen.smartman.modules.mdc.wrapper.WorkstationFeedbackWrapper; import cn.hutool.core.collection.ListUtil; @Service public class WorkstationFeedbackServiceImpl extends ServiceImpl implements IWorkstationFeedbackService { static final String GROUP_FEEDBACK_CACHE_KEY = "group:feedback:list:"; @Resource private IWorkstationService workstationService; @Resource @Lazy private IWorkstationFeedbackDetailService feedbackDetailService; @Resource private IGlobalWcsService globalWcsService; @Resource private RedisLockClient redisLockClient; @Resource @Lazy private IWorkstationFeedbackService selfService; @Resource private IEmployeeService employeeService; @Override public IPage workstationPage(Query query, boolean excludeImmediate) { final List immediateFeedback = this.selfService.getImmediateFeedback(); final List workstationList = immediateFeedback.stream().map((v0) -> { return v0.getWorkstationId(); }).collect(Collectors.toList()); IPage page = this.workstationService.page(Condition.getPage(query), Lambda.notIn(excludeImmediate && Func.isNotEmpty(immediateFeedback), (v0) -> { return v0.getId(); }, workstationList)); return new BaseEntityWrapper() { public WorkstationFeedbackInfoVO entityVO(Workstation entity) { WorkstationFeedbackInfoVO vo = new WorkstationFeedbackInfoVO(); vo.setWorkstationId(entity.getId()); vo.setWorkstationName(entity.getName()); vo.setWorkstationCode(entity.getCode()); vo.setWorkstationAvatar(entity.getAvatar()); if (workstationList.contains(entity.getId())) { immediateFeedback.stream().filter(item -> { return item.getWorkstationId().equals(entity.getId()); }).findFirst().ifPresent(feedback -> { vo.setLastImmediateFeedbackTime(feedback.getStartTime()); vo.setFeedbackId(feedback.getId()); Optional.ofNullable(WorkstationFeedbackServiceImpl.this.globalWcsService.getByCode(feedback.getWcs(), GlobalWcsTypeEnum.FEEDBACK)).ifPresent(globalWcs -> { vo.setWcsColor(globalWcs.getColor()); vo.setWcsDesc(globalWcs.getName()); }); }); vo.setToDayHaveFeedback(true); } else { vo.setToDayHaveFeedback(Boolean.valueOf(WorkstationFeedbackServiceImpl.this.feedbackDetailService.toDayHaveFeedbackCount(entity.getId()) != 0)); } return vo; } }.pageVO(page); } @Override public List getImmediateFeedback() { return list(Lambda.eq(WorkstationFeedback::getFeedbackType, Integer.valueOf(FeedbackType.IMMEDIATE.getValue())) .isNull(WorkstationFeedback::getEndTime)); /* return list((Wrapper) Lambda.eq((v0) -> { return v0.getFeedbackType(); }, Integer.valueOf(FeedbackType.IMMEDIATE.getValue())).isNull((v0) -> { return v0.getEndTime(); }));*/ } @Override public WorkstationFeedback getImmediateFeedback(Serializable workstationId) { return getOne(Lambda.eq(WorkstationFeedback::getWorkstationId, workstationId) .eq(WorkstationFeedback::getFeedbackType, FeedbackType.IMMEDIATE.getValue()) .isNull(WorkstationFeedback::getEndTime)); } @Override @Transactional public boolean startFeedbackByImmediate(WorkstationImmediateFeedBackDTO dto) { Date now = new Date(); for (String workstationId : dto.getWorkstationIds()) { this.redisLockClient.lockFair(ExtCacheConstant.WORK_FEEDBACK.concat(":".concat(workstationId)), 30L, 100L, () -> { WorkstationFeedback oldFeedback = this.selfService.getImmediateFeedback(workstationId); if (oldFeedback != null && oldFeedback.getWcs().equals(dto.getWcs())) { return false; } if (oldFeedback != null) { oldFeedback.setEndTime(now); updateById(oldFeedback); } WorkstationFeedback addFeedback = new WorkstationFeedback(); addFeedback.setWorkstationId(Long.parseLong(workstationId)); addFeedback.setStartTime(now); addFeedback.setFeedbackTime(now); if (Func.isNotEmpty(dto.getFeedUser())) { addFeedback.setFeedUser(Long.valueOf(Long.parseLong(dto.getFeedUser()))); } else { addFeedback.setFeedUser(getEmployeeById().getId()); } addFeedback.setFeedbackType(FeedbackType.IMMEDIATE.getValue()); addFeedback.setStatus(FeedbackStatus.WAIT_SYNC.getValue()); addFeedback.setRemark(dto.getDescription()); addFeedback.setWcs(dto.getWcs()); save(addFeedback); CacheUtil.evict(ExtCacheConstant.WORK_FEEDBACK, "immediate:workstationId:", workstationId, false); return true; }); } evictFeedback(now, now, dto.getWorkstationIds()); return true; } @Override @Transactional public boolean startFeedbackByNoImmediate(final WorkstationNoImmediateFeedBackDTO dto) { if (dto.getEndTime().compareTo(new Date()) > 0) { dto.setEndTime(new Date()); } for (String workstationId : dto.getWorkstationIds()) { this.redisLockClient.lockFair(ExtCacheConstant.WORK_FEEDBACK.concat(":").concat(workstationId), 30L, 100L, () -> { existTimeOverImmediateFeedbackException(dto); WorkstationFeedback addFeedback = new WorkstationFeedback(); addFeedback.setWorkstationId(Long.parseLong(workstationId)); addFeedback.setStartTime(dto.getStartTime()); addFeedback.setEndTime(dto.getEndTime()); addFeedback.setFeedbackTime(new Date()); if (Func.isNotEmpty(dto.getFeedUser())) { addFeedback.setFeedUser(Long.parseLong(dto.getFeedUser())); addFeedback.setEndFeedUser(Long.parseLong(dto.getFeedUser())); } else { addFeedback.setFeedUser(getEmployeeById().getId()); addFeedback.setEndFeedUser(getEmployeeById().getId()); } addFeedback.setFeedbackType(FeedbackType.TIME_RANGE.getValue()); addFeedback.setStatus(FeedbackStatus.WAIT_SYNC.getValue()); addFeedback.setRemark(dto.getDescription()); addFeedback.setWcs(dto.getWcs()); return save(addFeedback);//保存到mysql }); } evictFeedback(dto.getStartTime(), dto.getEndTime(), dto.getWorkstationIds()); return true; } /** * 批量清除反馈的缓存,主要用于刷新前端的显示数据 * @param startTime * @param endTime * @param workstationIds */ private void evictFeedback(final Date startTime, final Date endTime, List workstationIds) { LocalDate start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); LocalDate end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); workstationIds.forEach(id -> { evictFeedback(start, end, id); }); } /** * 单个清楚缓存中的反馈,从catcheName=WORK_FEEDBACK_EXP3缓存中删除 实际key为 blade:feedback:group:feedback:list:blade:feedback#259200${workstationId}:yyyy-MM-dd HH:mm:ss * @param start * @param end * @param workstationId */ private void evictFeedback(final LocalDate start, final LocalDate end, final String workstationId) { long distance = ChronoUnit.DAYS.between(start, end);//一共limit几天(就是取几个数) Stream.iterate(start, d -> { return d.plusDays(1L); }).limit(distance + 1).forEach(date -> { String key = workstationId.concat(":").concat(Func.formatDate(date)); CacheUtil.evict(IWorkstationFeedbackService.WORK_FEEDBACK_EXP3, GROUP_FEEDBACK_CACHE_KEY, key, false); }); } /** * 检查是否有正在进行中的状态反馈 * @param dto */ private void existTimeOverImmediateFeedbackException(WorkstationNoImmediateFeedBackDTO dto) { if (dto.getWorkstationIds().stream().map(this.selfService::getImmediateFeedback).filter(Objects::nonNull).anyMatch(feedback -> (dto.getStartTime().getTime() >= feedback.getStartTime().getTime() || dto.getEndTime().getTime() >= feedback.getStartTime().getTime()))) throw new ServiceException("存在正在反馈的状态,请先结束当前反馈再提交。"); /* Stream stream = dto.getWorkstationIds().stream(); IWorkstationFeedbackService iWorkstationFeedbackService = this.selfService; iWorkstationFeedbackService.getClass(); if (stream.map((v1) -> { return r1.getImmediateFeedback(v1); }).filter((v0) -> { return Objects.nonNull(v0); }).anyMatch(feedback -> { return dto.getStartTime().getTime() >= feedback.getStartTime().getTime() || dto.getEndTime().getTime() >= feedback.getStartTime().getTime(); })) { throw new ServiceException("存在正在反馈的状态,请先结束当前反馈再提交。"); }*/ } private Employee getEmployeeById() { Employee employee = this.employeeService.cachedById(AuthUtil.getUserId()); if (employee == null) { employee = this.employeeService.getByUserId(AuthUtil.getUserId()); } if (employee == null) { throw new ServiceException("没有查询到员工信息"); } return employee; } @Override @Transactional public boolean endFeedback(WorkstationEndImmediateFeedBackDTO dto) { WorkstationFeedback feedback = this.selfService.cachedById(dto.getFeedbackId()); if (dto.getEndTime() == null) { dto.setEndTime(new Date()); } if (feedback == null || feedback.getEndTime() != null) { return false; } if (feedback.getStartTime().compareTo(dto.getEndTime()) >= 0) { throw new ServiceException("开始时间必须小于结束时间"); } Date now = new Date(); feedback.setEndTime(dto.getEndTime().compareTo(new Date()) > 0 ? now : dto.getEndTime()); if (Func.isNotEmpty(dto.getFeedUser())) { feedback.setEndFeedUser(Long.valueOf(Long.parseLong(dto.getFeedUser()))); } else { feedback.setFeedUser(getEmployeeById().getId()); } evictFeedback(feedback.getStartTime(), feedback.getEndTime(), Collections.singletonList(feedback.getWorkstationId() + "")); return updateById(feedback); } @Override @Transactional public boolean endAndStartAgainFeedback(WorkstationEndAndStartImmediateFeedBackDTO dto) { WorkstationEndImmediateFeedBackDTO endImmediateFeedBackDTO = new WorkstationEndImmediateFeedBackDTO(); endImmediateFeedBackDTO.setEndTime(dto.getEndTime()); endImmediateFeedBackDTO.setFeedbackId(dto.getEndFeedbackId()); endImmediateFeedBackDTO.setWorkstationId(dto.getWorkstationId()); endImmediateFeedBackDTO.setFeedUser(dto.getFeedUser()); boolean success = endFeedback(endImmediateFeedBackDTO); if (success) { WorkstationImmediateFeedBackDTO start = new WorkstationImmediateFeedBackDTO(); start.setWorkstationIds(ListUtil.toList(new String[]{dto.getWorkstationId()})); start.setWcs(dto.getWcs()); start.setDescription(dto.getDesc()); start.setFeedUser(dto.getFeedUser()); boolean success2 = startFeedbackByImmediate(start); return success2; } return false; } private List allFeedbackByWorkstationId(final LocalDate statusTime, final Serializable workstationId) { return Stream.concat( Stream.of(workstationId).map(this.selfService::getImmediateFeedback) .filter(Objects::nonNull) .filter(feedback -> (statusTime == null || DateUtil.formatDate(feedback.getStartTime()).equals(statusTime.toString()))), list(Lambda.eq(WorkstationFeedback::getWorkstationId, workstationId) .dayBetween(statusTime, WorkstationFeedback::getStartTime, WorkstationFeedback::getEndTime)) .stream() .filter(Objects::nonNull)) .collect(Collectors.toList()); /* Stream of = Stream.of(workstationId); IWorkstationFeedbackService iWorkstationFeedbackService = this.selfService; iWorkstationFeedbackService.getClass(); return (List) Stream.concat(of.map(this::getImmediateFeedback).filter((v0) -> { return Objects.nonNull(v0); }).filter(feedback -> { return statusTime == null || DateUtil.formatDate(feedback.getStartTime()).equals(statusTime.toString()); }), list(Lambda.eq((v0) -> { return v0.getWorkstationId(); }, workstationId).dayBetween(statusTime, (v0) -> { return v0.getStartTime(); }, (v0) -> { return v0.getEndTime(); })).stream().filter((v0) -> { return Objects.nonNull(v0); })).collect(Collectors.toList());*/ } @Override public List groupStatusRecordWithFeedbackCache(final LocalDate statusTime, final Long workstationId, final List recordList) { List statusRecordList = recordList == null ? new ArrayList<>() : recordList; List cancelList = this.feedbackDetailService.cancelFeedback(statusTime, workstationId).stream().map(feedback -> { StatusRecordVO t = new StatusRecordVO(); t.setStartTime(new Timestamp(feedback.getStartTime().getTime())); t.setEndTime(new Timestamp(feedback.getEndTime().getTime())); t.setHumanFeedback(true); t.setFeedbackTime(feedback.getCancelTime() == null ? null : new Timestamp(feedback.getCancelTime().getTime())); t.setWcs(-1); return t; }).collect(Collectors.toList()); String key = String.join(":", workstationId + "", statusTime.toString()); if (statusTime.compareTo((ChronoLocalDate) LocalDate.now()) == 0) { List statusRecordVOList = groupStatusRecordWithFeedback(statusTime, workstationId, statusRecordList, cancelList); return filterDateStatusRecord(statusRecordVOList, statusTime); } GroupFeedbackCacheDTO statusData = CacheUtil.get(IWorkstationFeedbackService.WORK_FEEDBACK_EXP3, GROUP_FEEDBACK_CACHE_KEY, key, () -> { GroupFeedbackCacheDTO dto = new GroupFeedbackCacheDTO(); List list = groupStatusRecordWithFeedback(statusTime, workstationId, statusRecordList, cancelList); dto.setRecordList(list); dto.setStatusNum(statusRecordList.size()); dto.setCancelNum(cancelList.size()); return dto; }, false); //assert statusData != null; if (statusData.getStatusNum() != statusRecordList.size() || statusData.getCancelNum() != cancelList.size()) { //CacheUtil.evict("blade:feedback#259200", "group:feedback:list:", key, Boolean.valueOf(false)); CacheUtil.evict(IWorkstationFeedbackService.WORK_FEEDBACK_EXP3, GROUP_FEEDBACK_CACHE_KEY, key, false); return groupStatusRecordWithFeedbackCache(statusTime, workstationId, statusRecordList); } return filterDateStatusRecord(statusData.getRecordList(), statusTime); /* if ($assertionsDisabled || statusData != null) { if (statusData.getStatusNum() != statusRecordList.size() || statusData.getCancelNum() != cancelList.size()) { CacheUtil.evict(IWorkstationFeedbackService.WORK_FEEDBACK_EXP3, GROUP_FEEDBACK_CACHE_KEY, key, false); return groupStatusRecordWithFeedbackCache(statusTime, workstationId, statusRecordList); } return filterDateStatusRecord(statusData.getRecordList(), statusTime); } throw new AssertionError(); */ } private List groupStatusRecordWithFeedback(final LocalDate statusTime, final Long workstationId, final List statusRecordList, final List cancelList) { List sourceList = Stream.concat(allFeedbackByWorkstationId(statusTime, workstationId).stream().map(WorkstationFeedbackWrapper::entityRecordVO), cancelList.stream()).sorted(Comparator.comparing((v0) -> { return v0.getFeedbackTime(); })).collect(Collectors.toList()); List feedbackList = groupStatusRecordWithFeedback(sourceList, statusTime); List sourceList2 = Stream.concat(statusRecordList.stream().peek(status -> { status.setFeedbackTime(status.getStartTime()); }), feedbackList.stream()).sorted(Comparator.comparing((v0) -> { return v0.getFeedbackTime(); })).collect(Collectors.toList()); return groupStatusRecordWithFeedback(sourceList2, statusTime); } private List groupStatusRecordWithFeedback(final List sourceList, final LocalDate statusTime) { List targetList = new ArrayList<>(); Stream.concat(sourceList.stream().map((v0) -> { return v0.getStartTime(); }), sourceList.stream().map((v0) -> { return v0.getEndTime(); })).distinct().sorted(Comparator.comparing((v0) -> { return v0.getTime(); })).reduce((first, second) -> { Optional reduce = sourceList.stream().filter(item -> { return first.getTime() - item.getStartTime().getTime() >= 0 && item.getEndTime().getTime() - second.getTime() >= 0; }).reduce((a, b) -> { if (!b.isHumanFeedback() && a.isHumanFeedback()) { return a; } return b; }); //StatusRecordConvert statusRecordConvert = StatusRecordConvert.INSTANCE; //statusRecordConvert.getClass(); reduce.map(StatusRecordConvert.INSTANCE::convert).ifPresent(record -> { record.setStartTime(first); record.setEndTime(second); System.out.println(first + " -> " + second + " wcs=" + record.getWcs()); StatusRecordVO latest = targetList.size() == 0 ? null : (StatusRecordVO) targetList.get(targetList.size() - 1); if (latest != null && !record.isPoint() && latest.getWcs().equals(record.getWcs()) && latest.getEndTime().getTime() == record.getStartTime().getTime() && latest.getFeedUserId() != null && record.getFeedUserId() != null && latest.getFeedUserId().equals(record.getFeedUserId())) { latest.setEndTime(record.getEndTime()); } else { targetList.add(record); } }); return second; }); return targetList.stream().filter(item -> { return !item.getWcs().equals(-1) && item.getEndTime().getTime() - item.getStartTime().getTime() >= 1000 && item.getStartTime().toLocalDateTime().toLocalDate().compareTo((ChronoLocalDate) statusTime) <= 0 && item.getEndTime().toLocalDateTime().toLocalDate().compareTo((ChronoLocalDate) statusTime) >= 0; }).collect(Collectors.toList()); } private List filterDateStatusRecord(final List list, final LocalDate statusTime) { if (list.size() > 0) { StatusRecordVO startRecord = list.get(0); StatusRecordVO endRecord = list.get(list.size() - 1); if (startRecord.getStartTime().toLocalDateTime().compareTo((ChronoLocalDateTime) statusTime.atStartOfDay()) < 0) { startRecord.setStartTime(Timestamp.valueOf(statusTime.atStartOfDay())); } if (endRecord.getEndTime().toLocalDateTime().toLocalDate().compareTo((ChronoLocalDate) statusTime) > 0) { endRecord.setEndTime(Timestamp.valueOf(statusTime.atTime(23, 59, 59))); } if (endRecord.isPoint()) { endRecord.setEndTime(new Timestamp(System.currentTimeMillis())); } } return list; } }