package com.ruoyi.web.controller.system;
|
|
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
|
*/
|
@RestController
|
@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 deptId 部门ID(用于根据部门区域配置过滤医院)
|
*/
|
@GetMapping("/search")
|
public AjaxResult searchHospitals(
|
@RequestParam(value = "keyword", required = false) String keyword,
|
@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获取医院详情(从MySQL tb_hosp_data表查询)
|
* @param hospId 医院ID(对应legacy_hosp_id)
|
*/
|
@GetMapping("/detail")
|
public AjaxResult getHospitalDetail(@RequestParam("hospId") Integer hospId) {
|
HospData hospital = hospDataMapper.selectHospDataById(hospId);
|
return success(hospital);
|
}
|
|
/**
|
* 获取常用转出医院列表(从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 = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
|
// 查询常用转出医院ID列表
|
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 = "pageSize", required = false, defaultValue = "50") Integer pageSize) {
|
// 查询常用转入医院ID列表
|
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);
|
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;
|
}
|
}
|
}
|