wlzboy
3 天以前 40a8157440e3b906da8f52e07d939d78c3f4c313
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java
@@ -2,18 +2,32 @@
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.HospitalTokenizerUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.HospData;
import com.ruoyi.system.domain.HospitalTokenizerTask;
import com.ruoyi.system.domain.TbHospData;
import com.ruoyi.system.mapper.HospDataMapper;
import com.ruoyi.system.service.HospitalTokenizerAsyncService;
import com.ruoyi.system.service.ISQLHospDataService;
import com.ruoyi.system.service.ITbHospDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
 * 医院数据Controller
 * 从MySQL的tb_hosp_data表查询医院数据
 * hospId对应legacy_hosp_id字段(旧系统的医院ID)
 * 
 * @author ruoyi
 * @date 2024-01-16
@@ -22,30 +36,71 @@
@RequestMapping("/system/hospital")
public class HospDataController extends BaseController {
    
    /**
     * 医院搜索最低匹配分数阈值(低于此分数的结果将被过滤)
     */
    private static final int MIN_MATCH_SCORE_THRESHOLD = 1;
    @Autowired
    private HospDataMapper hospDataMapper;
    @Autowired
    private ISQLHospDataService sqlHospDataService;
    @Autowired
    private ITbHospDataService tbHospDataService;
    @Autowired
    private com.ruoyi.system.mapper.TbHospDataMapper tbHospDataMapper;
    @Autowired
    private HospitalTokenizerAsyncService asyncService;
    
    /**
     * 搜索医院
     * 搜索医院(从MySQL tb_hosp_data表查询)
     * 支持根据医院名称、地址、地域进行模糊搜索
     * @param keyword 搜索关键词(医院名称、地址、简称、省市区)
     * @param region 地域关键词(用于过滤省市区)
     * @param deptId 部门ID(用于根据部门区域配置过滤医院)
     */
    @GetMapping("/search")
    public AjaxResult searchHospitals(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam(value = "region", required = false) String region) {
        List<HospData> list = hospDataMapper.searchHospitals(keyword, region);
        Integer homeHospId=hospDataMapper.getHomeHospId();
        if(homeHospId>0 && list.stream().count()>0 && list.stream().filter(hospData -> hospData.getHospId().equals(homeHospId)).count()<=0) {
            HospData hospData=   hospDataMapper.selectHospDataById(homeHospId);
            list.add(0,hospData);
            @RequestParam(value = "deptId", required = false) Long deptId,
            @RequestParam(value = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
        List<HospData> list;
        // 如果keyword为空,使用部门区域过滤查询
        if (keyword == null || keyword=="") {
            if (deptId != null) {
                list = hospDataMapper.searchHospitalsByDeptRegion("", deptId);
            } else {
                list = hospDataMapper.searchHospitals("", "");
            }
        } else {
                list = hospDataMapper.searchHospitals(keyword, "");
        }
        // 限制返回数量
        if (pageSize != null && pageSize > 0 && list.size() > pageSize) {
            list = list.subList(0, pageSize);
        }
        // 确保"家中"在结果中
        Integer homeHospId = hospDataMapper.getHomeHospId();
        if(homeHospId > 0 && !list.isEmpty() && list.stream().noneMatch(hospData -> hospData.getHospId().equals(homeHospId))) {
            HospData hospData = hospDataMapper.selectHospDataById(homeHospId);
            if (hospData != null) {
                list.add(0, hospData);
            }
        }
        return success(list);
    }
    
    /**
     * 根据ID获取医院详情
     * 根据ID获取医院详情(从MySQL tb_hosp_data表查询)
     * @param hospId 医院ID(对应legacy_hosp_id)
     */
    @GetMapping("/detail")
    public AjaxResult getHospitalDetail(@RequestParam("hospId") Integer hospId) {
@@ -54,49 +109,355 @@
    }
    
    /**
     * 获取常用转出医院列表
     * 获取常用转出医院列表(从MySQL tb_hosp_data表查询)
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @param region 地域关键词(可选)
     * @param pageSize 返回结果数量限制(默认50)
     */
    @GetMapping("/frequent/out")
    public AjaxResult getFrequentOutHospitals(
            @RequestParam("serviceOrdClass") String serviceOrdClass,
            @RequestParam(value = "region", required = false) String region) {
            @RequestParam(value = "region", required = false) String region,
            @RequestParam(value = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
        // 查询常用转出医院ID列表
        List<Integer> hospIds = hospDataMapper.selectFrequentOutHospitalIds(serviceOrdClass);
        if (hospIds == null || hospIds.isEmpty()) {
        logger.info("getFrequentOutHospitals 传入的 serviceOrdClass :{}",serviceOrdClass);
        List<Integer> hospIds = sqlHospDataService.selectFrequentOutHospitalIds(serviceOrdClass);
        logger.info(" getFrequentOutHospitals 查询出来的 hospIds :{}",hospIds.toArray().length);
        if (hospIds.isEmpty()) {
            return success();
        }
        // 根据ID列表查询医院详情
        List<HospData> hospitals = hospDataMapper.selectHospDataByIds(hospIds, region);
        // 限制返回数量
        if (pageSize != null && pageSize > 0 && hospitals.size() > pageSize) {
            hospitals = hospitals.subList(0, pageSize);
        }
        return success(hospitals);
    }
    
    /**
     * 获取常用转入医院列表
     * 获取常用转入医院列表(从MySQL tb_hosp_data表查询)
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @param region 地域关键词(可选)
     * @param pageSize 返回结果数量限制(默认50)
     */
    @GetMapping("/frequent/in")
    public AjaxResult getFrequentInHospitals(
            @RequestParam("serviceOrdClass") String serviceOrdClass,
            @RequestParam(value = "region", required = false) String region) {
            @RequestParam(value = "region", required = false) String region,
            @RequestParam(value = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
        // 查询常用转入医院ID列表
        List<Integer> hospIds = hospDataMapper.selectFrequentInHospitalIds(serviceOrdClass);
        if (hospIds == null || hospIds.isEmpty()) {
        logger.info("getFrequentInHospitals 传入的 serviceOrdClass {}",serviceOrdClass);
        List<Integer> hospIds = sqlHospDataService.selectFrequentInHospitalIds(serviceOrdClass);
        logger.info("getFrequentInHospitals 查询出来的 hospIds {}",hospIds.toArray().length);
        if (hospIds.isEmpty()) {
            return success();
        }
        Integer homeHospId=hospDataMapper.getHomeHospId();
        // 根据ID列表查询医院详情
        List<HospData> hospitals = hospDataMapper.selectHospDataByIds(hospIds, region);
        // 限制返回数量
        if (pageSize != null && pageSize > 0 && hospitals.size() > pageSize) {
            hospitals = hospitals.subList(0, pageSize);
        }
        if(homeHospId>0) {
         HospData hospData=   hospDataMapper.selectHospDataById(homeHospId);
         HospData hospData = hospDataMapper.selectHospDataById(homeHospId);
         hospitals.add(0,hospData);
        }
        return success(hospitals);
    }
    /**
     * 根据部门区域配置搜索医院(从MySQLtb_hosp_data表查询,支持省、市、县/区等多级区域)
     * @param keyword 搜索关键词
     * @param deptId 部门ID
     * @param pageSize 返回结果数量限制(默认50)
     */
    @GetMapping("/search/by-dept-region")
    public AjaxResult searchHospitalsByDeptRegion(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam("deptId") Long deptId,
            @RequestParam(value = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
        logger.info("根据部门区域配置搜索医院:deptId={}, keyword={}, pageSize={}", deptId, keyword, pageSize);
        // 调用Mapper查询,自动根据部门的区域配置过滤医院
        List<HospData> hospitals = hospDataMapper.searchHospitalsByDeptRegion(keyword, deptId);
        logger.info("查询到医院数量:{}", hospitals.size());
        // 限制返回数量
        if (pageSize != null && pageSize > 0 && hospitals.size() > pageSize) {
            hospitals = hospitals.subList(0, pageSize);
        }
        // 确保"家中"在结果中
        Integer homeHospId = hospDataMapper.getHomeHospId();
        if (homeHospId > 0 && hospitals.stream().noneMatch(h -> h.getHospId().equals(homeHospId))) {
            HospData homeHosp = hospDataMapper.selectHospDataById(homeHospId);
            if (homeHosp != null) {
                hospitals.add(0, homeHosp);
            }
        }
        return success(hospitals);
    }
    /**
     * 批量生成所有医院的分词(异步)
     * 管理员接口,用于初始化或重新生成医院分词
     *
     * @return 任务ID
     */
    @GetMapping("/generateKeywords")
    public AjaxResult generateAllHospitalKeywords() {
        logger.info("开始批量生成医院分词(异步)...");
        try {
            // 生成任务ID
            String taskId = UUID.randomUUID().toString().replace("-", "");
            // 异步执行任务
            asyncService.executeTokenizerTask(taskId);
            logger.info("医院分词任务已启动: taskId={}", taskId);
            // 立即返回任务ID
            return success()
                .put("taskId", taskId)
                .put("message", "分词任务已启动,请查询任务进度");
        } catch (Exception e) {
            logger.error("启动医院分词任务失败", e);
            return error("启动失败:" + e.getMessage());
        }
    }
    /**
     * 查询医院分词任务进度
     *
     * @param taskId 任务ID
     * @return 任务进度信息
     */
    @GetMapping("/getTaskProgress")
    public AjaxResult getTaskProgress(@RequestParam("taskId") String taskId) {
        try {
            HospitalTokenizerTask task = asyncService.getTaskStatus(taskId);
            if (task == null) {
                return error("任务不存在或已过期");
            }
            return success(task);
        } catch (Exception e) {
            logger.error("查询任务进度失败: taskId={}", taskId, e);
            return error("查询失败:" + e.getMessage());
        }
    }
    /**
     * 基于分词匹配搜索医院
     * 前端传入医院信息,进行分词后与数据库中的分词匹配
     * 根据匹配的分词数量进行权重排序,匹配越多排名越靠前
     *
     * @param searchText 搜索文本(医院名称、地址等)
     * @param pageSize 返回结果数量限制(默认50)
     * @return 匹配的医院列表(按匹配度排序)
     */
    @GetMapping("/searchByKeywords")
    public AjaxResult searchHospitalsByKeywords(
            @RequestParam("searchText") String searchText,
            @RequestParam(value = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
        logger.info("基于分词匹配搜索医院:searchText={}, pageSize={}", searchText, pageSize);
        if (searchText == null || searchText.trim().isEmpty()) {
            return error("搜索文本不能为空");
        }
        try {
            long startTime = System.currentTimeMillis();
            // 1. 对前端传入的搜索文本进行分词
            String searchKeywords = HospitalTokenizerUtil.tokenizeSearchText(searchText);
            logger.info("搜索文本分词结果:{}", searchKeywords);
            if (searchKeywords.isEmpty()) {
                return success(new ArrayList<>());
            }
            // 2. 将分词结果拆分为关键词列表,用于数据库预过滤
            String[] keywordArray = searchKeywords.split(",");
            List<String> keywordList = new ArrayList<>();
            for (String keyword : keywordArray) {
                String trimmed = keyword.trim();
                if (!trimmed.isEmpty() && trimmed.length() >= 2) { // 只使用2个字以上的关键词
                    keywordList.add(trimmed);
                }
            }
            if (keywordList.isEmpty()) {
                logger.warn("没有有效的关键词用于搜索");
                return success(new ArrayList<>());
            }
            logger.info("使用关键词进行数据库预过滤: {}", keywordList);
            // 3. 通过数据库层面预过滤,只查询可能匹配的医院(而不是所有医院)
            List<TbHospData> candidateHospitals = tbHospDataMapper.selectTbHospDataByKeywords(keywordList, "0");
            long queryTime = System.currentTimeMillis();
            logger.info("数据库预过滤完成,候选医院数量: {}, 耗时: {}ms", candidateHospitals.size(), queryTime - startTime);
            // 4. 提取候选医院的地区名称(从 hopsArea 字段)
            Set<String> districtNames = new HashSet<>();
            for (TbHospData hospital : candidateHospitals) {
                if (StringUtils.isNotBlank(hospital.getHopsArea())) {
                    // 提取地区名,移除常见后缀
                    String area = hospital.getHopsArea()
                        .replace("区", "")
                        .replace("市", "")
                        .replace("县", "")
                        .trim();
                    if (area.length() > 0) {
                        districtNames.add(area);
                    }
                }
            }
            logger.info("提取到 {} 个独特地区名称", districtNames.size());
            // 5. 对候选医院计算匹配分数,并过滤出有匹配的医院
            List<HospitalMatchResult> matchResults = new ArrayList<>();
            long matchStartTime = System.currentTimeMillis();
            for (TbHospData hospital : candidateHospitals) {
                if (hospital.getHospKeywords() == null || hospital.getHospKeywords().isEmpty()) {
                    continue;
                }
                // 计算匹配分数(传入医院名称和地区名称集合)
                int matchScore = HospitalTokenizerUtil.calculateMatchScore(
                    searchKeywords,
                    hospital.getHospKeywords(),
                    hospital.getHospName(),
                    districtNames
                );
                // 只保留匹配分数达到阈值的医院
                if (matchScore >= MIN_MATCH_SCORE_THRESHOLD) {
                    matchResults.add(new HospitalMatchResult(hospital, matchScore));
                }
            }
            long matchTime = System.currentTimeMillis();
            logger.info("匹配计算完成,找到 {} 个匹配的医院(分数>={}}),耗时: {}ms",
                matchResults.size(), MIN_MATCH_SCORE_THRESHOLD, matchTime - matchStartTime);
            // 4. 按匹配分数降序排序,分数相同时按医院名称长度升序排序(名称越短越靠前)
            matchResults.sort(Comparator
                .comparingInt(HospitalMatchResult::getMatchScore).reversed()
                .thenComparingInt(result -> result.getHospital().getHospName().length()));
            // 5. 限制返回数量
            if (pageSize != null && pageSize > 0 && matchResults.size() > pageSize) {
                matchResults = matchResults.subList(0, pageSize);
            }
            // 6. 转换为HospData对象返回(包含匹配分数)
            List<HospDataWithScore> result = new ArrayList<>();
            for (HospitalMatchResult matchResult : matchResults) {
                TbHospData tbHospData = matchResult.getHospital();
                HospData hospData = convertToHospData(tbHospData);
                result.add(new HospDataWithScore(hospData, matchResult.getMatchScore()));
                logger.debug("医院: {}, 匹配分数: {}",
                    hospData.getHospName(), matchResult.getMatchScore());
            }
            logger.info("返回 {} 个医院结果", result.size());
            long totalTime = System.currentTimeMillis() - startTime;
            logger.info("搜索完成 - 总耗时: {}ms, 数据库查询: {}ms, 匹配计算: {}ms",
                totalTime, queryTime - startTime, matchTime - matchStartTime);
            return success(result);
        } catch (Exception e) {
            logger.error("分词匹配搜索失败", e);
            return error("搜索失败:" + e.getMessage());
        }
    }
    /**
     * 将TbHospData转换为HospData
     */
    private HospData convertToHospData(TbHospData tbHospData) {
        HospData hospData = new HospData();
        hospData.setHospId(tbHospData.getLegacyHospId());
        hospData.setHospName(tbHospData.getHospName());
        hospData.setHospCityId(tbHospData.getHospCityId());
        hospData.setHospShort(tbHospData.getHospShort());
        hospData.setHopsProvince(tbHospData.getHopsProvince());
        hospData.setHopsCity(tbHospData.getHopsCity());
        hospData.setHopsArea(tbHospData.getHopsArea());
        hospData.setHospAddress(tbHospData.getHospAddress());
        hospData.setHospTel(tbHospData.getHospTel());
        hospData.setHospUnitId(tbHospData.getHospUnitId());
        hospData.setHospState(tbHospData.getHospState());
        hospData.setHospOaId(tbHospData.getHospOaId());
        hospData.setHospIntroducerId(tbHospData.getHospIntroducerId());
        if (tbHospData.getHospIntroducerDate() != null) {
            hospData.setHospIntroducerDate(tbHospData.getHospIntroducerDate().toString());
        }
        hospData.setHospLevel(tbHospData.getHospLevel());
        return hospData;
    }
    /**
     * 医院匹配结果内部类
     */
    private static class HospitalMatchResult {
        private TbHospData hospital;
        private int matchScore;
        public HospitalMatchResult(TbHospData hospital, int matchScore) {
            this.hospital = hospital;
            this.matchScore = matchScore;
        }
        public TbHospData getHospital() {
            return hospital;
        }
        public int getMatchScore() {
            return matchScore;
        }
    }
    /**
     * 医院数据与匹配分数包装类
     */
    private static class HospDataWithScore {
        private HospData hospital;
        private int matchScore;
        public HospDataWithScore(HospData hospital, int matchScore) {
            this.hospital = hospital;
            this.matchScore = matchScore;
        }
        public HospData getHospital() {
            return hospital;
        }
        public int getMatchScore() {
            return matchScore;
        }
    }
}