|
|
@@ -0,0 +1,370 @@
|
|
|
+package com.fs.his.service.impl;
|
|
|
+
|
|
|
+import com.fs.common.core.page.TableDataInfo;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
+import com.fs.his.domain.FsDoctor;
|
|
|
+import com.fs.his.mapper.FsDoctorMapper;
|
|
|
+import com.fs.his.service.IPollingAssignDoctorService;
|
|
|
+import com.fs.his.vo.PharmacistAssignmentVO;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class IPollingAssignDoctorServiceImpl implements IPollingAssignDoctorService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsDoctorMapper doctorMapper;
|
|
|
+
|
|
|
+ // 轮询计数器key
|
|
|
+ private static final String ROUND_ROBIN_INDEX_KEY = "pharmacist:round:index";
|
|
|
+
|
|
|
+ // 分配记录key前缀(用于后续查询)
|
|
|
+ private static final String ASSIGNMENT_RECORD_KEY_PREFIX = "pharmacist:assign:record:";
|
|
|
+
|
|
|
+ // 处方关联记录key前缀
|
|
|
+ private static final String PRESCRIBE_ASSIGN_KEY_PREFIX = "pharmacist:prescribe:";
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public FsDoctor getNextPharmacist(String prescribeCode) {
|
|
|
+ // 1. 实时从数据库查询在线药师(按ID排序保证顺序)
|
|
|
+ List<FsDoctor> onlineDoctors = doctorMapper.selectRandomOnlineDoctorForPackage();
|
|
|
+
|
|
|
+ FsDoctor selectedDoctor;
|
|
|
+ boolean isOnline;
|
|
|
+ int onlineCount = onlineDoctors != null ? onlineDoctors.size() : 0;
|
|
|
+
|
|
|
+ if (onlineDoctors != null && !onlineDoctors.isEmpty()) {
|
|
|
+ // 有在线药师
|
|
|
+ if (onlineDoctors.size() == 1) {
|
|
|
+ // 只有一个在线药师,直接分配
|
|
|
+ selectedDoctor = onlineDoctors.get(0);
|
|
|
+ log.info("只有一个在线药师 - ID:{}, 姓名:{}",
|
|
|
+ selectedDoctor.getDoctorId(), selectedDoctor.getDoctorName());
|
|
|
+ } else {
|
|
|
+ // 多个在线药师,轮询分配
|
|
|
+ selectedDoctor = roundRobinSelect(onlineDoctors);
|
|
|
+ log.info("轮询分配在线药师 - ID:{}, 姓名:{}, 当前在线药师数:{}",
|
|
|
+ selectedDoctor.getDoctorId(), selectedDoctor.getDoctorName(), onlineCount);
|
|
|
+ }
|
|
|
+ isOnline = true;
|
|
|
+ } else {
|
|
|
+ // 无在线药师:兜底分配离线药师
|
|
|
+ selectedDoctor = getOfflinePharmacist();
|
|
|
+ isOnline = false;
|
|
|
+ onlineCount = 0;
|
|
|
+ log.info("无在线药师,分配离线药师 - ID:{}, 姓名:{}",
|
|
|
+ selectedDoctor.getDoctorId(), selectedDoctor.getDoctorName());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 保存完整的分配记录(关联处方编号)
|
|
|
+ saveAssignmentRecord(selectedDoctor, prescribeCode, isOnline, onlineCount);
|
|
|
+
|
|
|
+ return selectedDoctor;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public FsDoctor getNextPharmacistWithPrescribeCode(String prescribeCode) {
|
|
|
+ return getNextPharmacist(prescribeCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 轮询选择药师(支持1-N个药师)
|
|
|
+ * @param onlineDoctors 当前在线的药师列表(实时查询的)
|
|
|
+ */
|
|
|
+ private FsDoctor roundRobinSelect(List<FsDoctor> onlineDoctors) {
|
|
|
+ // 如果只有1个药师,直接返回(不需要轮询)
|
|
|
+ if (onlineDoctors.size() == 1) {
|
|
|
+ return onlineDoctors.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Redis原子自增实现轮询(2个及以上药师才需要)
|
|
|
+ Long index = redisCache.incr(ROUND_ROBIN_INDEX_KEY, 1L);
|
|
|
+
|
|
|
+ // 设置过期时间(7天,避免key永久存在)
|
|
|
+ if (index == 1) {
|
|
|
+ redisCache.expire(ROUND_ROBIN_INDEX_KEY, 7, TimeUnit.DAYS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算真实索引(从0开始,适配list下标从0开始)
|
|
|
+ int realIndex = (int)((index - 1) % onlineDoctors.size());
|
|
|
+
|
|
|
+ log.debug("轮询计数器 - 当前值:{}, 在线药师总数:{}, 选中索引:{}",
|
|
|
+ index, onlineDoctors.size(), realIndex);
|
|
|
+
|
|
|
+ return onlineDoctors.get(realIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取离线药师(兜底)
|
|
|
+ */
|
|
|
+ private FsDoctor getOfflinePharmacist() {
|
|
|
+ FsDoctor doctor = doctorMapper.selectPackageFsDoctorType2Ids();
|
|
|
+ if (doctor == null) {
|
|
|
+ log.error("无可用药师");
|
|
|
+ throw new CustomException("当前无可用药师,请稍后再试");
|
|
|
+ }
|
|
|
+ return doctor;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存完整的分配记录(关联处方编号)
|
|
|
+ */
|
|
|
+ private void saveAssignmentRecord(FsDoctor doctor, String prescribeCode, boolean isOnline, int onlineCount) {
|
|
|
+ try {
|
|
|
+ String dateKey = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
|
|
|
+ String timeKey = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date());
|
|
|
+
|
|
|
+ // 记录ID:时间戳_处方编号
|
|
|
+ String recordId = System.currentTimeMillis() + "_" + prescribeCode;
|
|
|
+
|
|
|
+ // 记录格式:记录ID|时间|处方编号|药师ID|药师姓名|是否在线|在线药师总数
|
|
|
+ String recordValue = recordId + "|" +
|
|
|
+ timeKey + "|" +
|
|
|
+ prescribeCode + "|" +
|
|
|
+ doctor.getDoctorId() + "|" +
|
|
|
+ doctor.getDoctorName() + "|" +
|
|
|
+ (isOnline ? "在线" : "离线") + "|" +
|
|
|
+ onlineCount;
|
|
|
+
|
|
|
+ // 1. 按日期存储的列表(用于按日期查询)
|
|
|
+ String dateListKey = ASSIGNMENT_RECORD_KEY_PREFIX + dateKey;
|
|
|
+ redisCache.setVoice(dateListKey, recordValue);
|
|
|
+ redisCache.expire(dateListKey, 3, TimeUnit.DAYS);
|
|
|
+
|
|
|
+ // 2. 按处方编号存储(用于通过处方号反查)
|
|
|
+ String prescribeKey = PRESCRIBE_ASSIGN_KEY_PREFIX + prescribeCode;
|
|
|
+ redisCache.setCacheObject(prescribeKey, recordValue, 3 * 24 * 60 * 60, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ // 3. 按药师ID存储的列表(用于查询某个药师的分配记录)
|
|
|
+ String doctorKey = ASSIGNMENT_RECORD_KEY_PREFIX + "doctor:" + doctor.getDoctorId() + ":" + dateKey;
|
|
|
+ redisCache.setVoice(doctorKey, recordValue);
|
|
|
+ redisCache.expire(doctorKey, 3, TimeUnit.DAYS);
|
|
|
+
|
|
|
+ log.debug("保存分配记录成功 - 处方号:{}, 药师:{}, 记录ID:{}",
|
|
|
+ prescribeCode, doctor.getDoctorName(), recordId);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存分配记录失败 - 处方号:{}, 药师ID:{}", prescribeCode, doctor.getDoctorId(), e);
|
|
|
+ // 不影响主流程
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据处方编号查询分配的药师
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public String getPharmacistByPrescribeCode(String prescribeCode) {
|
|
|
+ try {
|
|
|
+ String prescribeKey = PRESCRIBE_ASSIGN_KEY_PREFIX + prescribeCode;
|
|
|
+ return redisCache.getCacheObject(prescribeKey);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询处方分配记录失败 - 处方号:{}", prescribeCode, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询某天的所有分配记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<String> getRecordsByDate(String date) {
|
|
|
+ try {
|
|
|
+ String dateListKey = ASSIGNMENT_RECORD_KEY_PREFIX + date;
|
|
|
+ return redisCache.getCacheList(dateListKey);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询日期分配记录失败 - 日期:{}", date, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询某药师某天的分配记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<String> getRecordsByDoctorAndDate(Long doctorId, String date) {
|
|
|
+ try {
|
|
|
+ String doctorKey = ASSIGNMENT_RECORD_KEY_PREFIX + "doctor:" + doctorId + ":" + date;
|
|
|
+ return redisCache.getCacheList(doctorKey);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询药师分配记录失败 - 药师ID:{}, 日期:{}", doctorId, date, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置轮询计数器(可选,一般用于测试)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void resetRoundRobin() {
|
|
|
+ redisCache.deleteObject(ROUND_ROBIN_INDEX_KEY);
|
|
|
+ log.info("轮询计数器已重置");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前轮询索引(可选,用于监控)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Long getCurrentIndex() {
|
|
|
+ return redisCache.getCacheObject(ROUND_ROBIN_INDEX_KEY);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 新增的分页查询方法 ==========
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TableDataInfo getRecordsByDateWithPage(String date, Integer pageNum, Integer pageSize) {
|
|
|
+ try {
|
|
|
+ // 1. 获取当天的所有记录
|
|
|
+ List<String> records = getRecordsByDate(date);
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(new ArrayList<>(), 0);
|
|
|
+ dataInfo.setCode(200);
|
|
|
+ dataInfo.setMsg("查询成功");
|
|
|
+ return dataInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 转换为VO并排序(最新的在前面)
|
|
|
+ List<PharmacistAssignmentVO> voList = convertToVOList(records, date);
|
|
|
+
|
|
|
+ // 3. 计算分页
|
|
|
+ int total = voList.size();
|
|
|
+ int fromIndex = (pageNum - 1) * pageSize;
|
|
|
+ int toIndex = Math.min(fromIndex + pageSize, total);
|
|
|
+
|
|
|
+ List<PharmacistAssignmentVO> pageList;
|
|
|
+ if (fromIndex >= total) {
|
|
|
+ pageList = new ArrayList<>();
|
|
|
+ } else {
|
|
|
+ pageList = voList.subList(fromIndex, toIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 返回TableDataInfo
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(pageList, total);
|
|
|
+ dataInfo.setCode(200);
|
|
|
+ dataInfo.setMsg("查询成功");
|
|
|
+ return dataInfo;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("分页查询分配记录失败 - 日期:{}", date, e);
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(new ArrayList<>(), 0);
|
|
|
+ dataInfo.setCode(500);
|
|
|
+ dataInfo.setMsg("查询失败:" + e.getMessage());
|
|
|
+ return dataInfo;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TableDataInfo getRecordsByDoctorAndDateWithPage(Long doctorId, String date, Integer pageNum, Integer pageSize) {
|
|
|
+ try {
|
|
|
+ // 1. 获取药师当天的记录
|
|
|
+ List<String> records = getRecordsByDoctorAndDate(doctorId, date);
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(new ArrayList<>(), 0);
|
|
|
+ dataInfo.setCode(200);
|
|
|
+ dataInfo.setMsg("查询成功");
|
|
|
+ return dataInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 转换为VO并排序
|
|
|
+ List<PharmacistAssignmentVO> voList = convertToVOList(records, date);
|
|
|
+
|
|
|
+ // 3. 计算分页
|
|
|
+ int total = voList.size();
|
|
|
+ int fromIndex = (pageNum - 1) * pageSize;
|
|
|
+ int toIndex = Math.min(fromIndex + pageSize, total);
|
|
|
+
|
|
|
+ List<PharmacistAssignmentVO> pageList;
|
|
|
+ if (fromIndex >= total) {
|
|
|
+ pageList = new ArrayList<>();
|
|
|
+ } else {
|
|
|
+ pageList = voList.subList(fromIndex, toIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 返回TableDataInfo
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(pageList, total);
|
|
|
+ dataInfo.setCode(200);
|
|
|
+ dataInfo.setMsg("查询成功");
|
|
|
+ return dataInfo;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("分页查询药师分配记录失败 - 药师ID:{}, 日期:{}", doctorId, date, e);
|
|
|
+ TableDataInfo dataInfo = new TableDataInfo(new ArrayList<>(), 0);
|
|
|
+ dataInfo.setCode(500);
|
|
|
+ dataInfo.setMsg("查询失败:" + e.getMessage());
|
|
|
+ return dataInfo;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将Redis记录列表转换为VO列表(按时间倒序)
|
|
|
+ */
|
|
|
+ private List<PharmacistAssignmentVO> convertToVOList(List<String> records, String date) {
|
|
|
+ List<PharmacistAssignmentVO> voList = new ArrayList<>();
|
|
|
+
|
|
|
+ for (String record : records) {
|
|
|
+ PharmacistAssignmentVO vo = parseRecordToVO(record);
|
|
|
+ if (vo != null) {
|
|
|
+ vo.setAssignDate(date);
|
|
|
+ voList.add(vo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按recordId倒序排序(最新的在前面)
|
|
|
+ voList.sort((a, b) -> b.getRecordId().compareTo(a.getRecordId()));
|
|
|
+
|
|
|
+ return voList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析单条记录为VO
|
|
|
+ */
|
|
|
+ private PharmacistAssignmentVO parseRecordToVO(String record) {
|
|
|
+ try {
|
|
|
+ String[] parts = record.split("\\|");
|
|
|
+ if (parts.length >= 7) {
|
|
|
+ PharmacistAssignmentVO vo = new PharmacistAssignmentVO();
|
|
|
+ vo.setRecordId(parts[0]); // 记录ID(包含时间戳)
|
|
|
+ vo.setAssignTime(parts[1]); // 分配时间
|
|
|
+ vo.setPrescribeCode(parts[2]); // 处方编号
|
|
|
+ vo.setDoctorId(Long.parseLong(parts[3])); // 药师ID
|
|
|
+ vo.setDoctorName(parts[4]); // 药师姓名
|
|
|
+ vo.setOnlineStatus(parts[5]); // 在线状态
|
|
|
+ vo.setOnlineCount(Integer.parseInt(parts[6])); // 在线药师总数
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析记录失败: {}", record, e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前药师数量情况(用于监控)
|
|
|
+ */
|
|
|
+ public java.util.Map<String, Object> getPharmacistStats() {
|
|
|
+ java.util.Map<String, Object> stats = new java.util.HashMap<>();
|
|
|
+
|
|
|
+ List<FsDoctor> onlineDoctors = doctorMapper.selectRandomOnlineDoctorForPackage();
|
|
|
+ stats.put("onlineCount", onlineDoctors != null ? onlineDoctors.size() : 0);
|
|
|
+ stats.put("onlineDoctors", onlineDoctors != null ?
|
|
|
+ onlineDoctors.stream()
|
|
|
+ .map(d -> d.getDoctorId() + "-" + d.getDoctorName())
|
|
|
+ .collect(Collectors.toList()) :
|
|
|
+ new ArrayList<>());
|
|
|
+
|
|
|
+ stats.put("currentIndex", redisCache.getCacheObject(ROUND_ROBIN_INDEX_KEY));
|
|
|
+ stats.put("usingRoundRobin", onlineDoctors != null && onlineDoctors.size() > 1);
|
|
|
+
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
+}
|