feat: 新增一个动物字典管理接口
This commit is contained in:
parent
2bbeab1198
commit
f19091e0b9
@ -75,16 +75,16 @@ public class JobRunner implements ApplicationRunner {
|
||||
ao.setStartTime(DateUtil.beginOfYear(DateUtil.offset(ao.getEndTime(), DateField.YEAR,-1)));
|
||||
}
|
||||
envWqDataService.wqLastData(ao);
|
||||
// log.info("--------------------wq最新一条数据---------------------");
|
||||
// eqJobService.eqAnchorPointLastDate(ao);
|
||||
// log.info("--------------------eq最新一条数据---------------------");
|
||||
// eqJobService.eqAnchorPointLastDate(ao);
|
||||
// log.info("--------------------eq最新一条数据---------------------");
|
||||
// dwJobService.wtLastData(ao);
|
||||
// log.info("--------------------wt最新一条数据---------------------");
|
||||
// dwJobService.wtvtPointData(ao);
|
||||
// log.info("--------------------wt最新一条数据---------------------");
|
||||
// fhJobService.zqLastData(ao);
|
||||
// log.info("--------------------fh最新一条数据---------------------");
|
||||
log.info("--------------------wq最新一条数据---------------------");
|
||||
eqJobService.eqAnchorPointLastDate(ao);
|
||||
log.info("--------------------eq最新一条数据---------------------");
|
||||
eqJobService.eqAnchorPointLastDate(ao);
|
||||
log.info("--------------------eq最新一条数据---------------------");
|
||||
dwJobService.wtLastData(ao);
|
||||
log.info("--------------------wt最新一条数据---------------------");
|
||||
dwJobService.wtvtPointData(ao);
|
||||
log.info("--------------------wt最新一条数据---------------------");
|
||||
fhJobService.zqLastData(ao);
|
||||
log.info("--------------------fh最新一条数据---------------------");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,249 @@
|
||||
package com.yfd.platform.qgc_base.controller;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yfd.platform.annotation.Log;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.qgc_base.domain.SdAnimalDictoryB;
|
||||
import com.yfd.platform.qgc_base.domain.SdEngInfoBHOperateRequest;
|
||||
import com.yfd.platform.qgc_base.service.ISdAnimalDictoryBService;
|
||||
import com.yfd.platform.qgc_data.service.AttachmentUploadService;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 动物字典表 前端控制器
|
||||
* </p>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/env/animalDictory")
|
||||
@Tag(name = "动物字典管理")
|
||||
public class SdAnimalDictoryBController {
|
||||
|
||||
@Resource
|
||||
private ISdAnimalDictoryBService sdAnimalDictoryBService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
private AttachmentUploadService attachmentUploadService;
|
||||
|
||||
@PostMapping("/queryPageList")
|
||||
@Operation(summary = "分页查询动物字典列表(支持动态过滤和排序)")
|
||||
public ResponseResult queryPageList(@RequestBody DataSourceRequest request) {
|
||||
Page<SdAnimalDictoryB> page = sdAnimalDictoryBService.queryPageList(request);
|
||||
return ResponseResult.successData(page);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询动物字典列表(支持动态过滤和排序,不分页)")
|
||||
public ResponseResult list(@RequestBody DataSourceRequest request) {
|
||||
List<SdAnimalDictoryB> list = DataSourceRequestUtil.executeList(request, SdAnimalDictoryB.class, sdAnimalDictoryBService);
|
||||
return ResponseResult.successData(list);
|
||||
}
|
||||
|
||||
@GetMapping("/listByName")
|
||||
@Operation(summary = "根据名称查询所有动物字典")
|
||||
public ResponseResult listByName(@RequestParam(required = false) String name) {
|
||||
return ResponseResult.successData(sdAnimalDictoryBService.list(
|
||||
new LambdaQueryWrapper<SdAnimalDictoryB>()
|
||||
.eq(StrUtil.isNotBlank(name), SdAnimalDictoryB::getName, name)
|
||||
.select(SdAnimalDictoryB::getId, SdAnimalDictoryB::getName, SdAnimalDictoryB::getAlias)
|
||||
));
|
||||
}
|
||||
|
||||
@GetMapping("/getById")
|
||||
@Operation(summary = "根据ID查询动物字典")
|
||||
public ResponseResult getById(@RequestParam String id) {
|
||||
return ResponseResult.successData(sdAnimalDictoryBService.getById(id));
|
||||
}
|
||||
|
||||
@Log(module = "动物字典管理", value = "新增动物字典")
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增动物字典")
|
||||
public ResponseResult add(@RequestParam("data") String dataStr,
|
||||
@RequestParam(value = "files", required = false) List<MultipartFile> files) {
|
||||
try {
|
||||
SdEngInfoBHOperateRequest request = objectMapper.readValue(dataStr, SdEngInfoBHOperateRequest.class);
|
||||
SdAnimalDictoryB entity = request == null || request.getEngInfo() == null ? null
|
||||
: objectMapper.convertValue(request.getEngInfo(), SdAnimalDictoryB.class);
|
||||
if (entity == null) {
|
||||
return ResponseResult.error("数据不能为空");
|
||||
}
|
||||
entity.setId(IdUtil.fastUUID());
|
||||
entity.setCode(entity.getId());
|
||||
|
||||
// 名称重名校验
|
||||
if (sdAnimalDictoryBService.existsByName(entity.getName())) {
|
||||
return ResponseResult.error("该动物名称已存在");
|
||||
}
|
||||
|
||||
// 上传文件并设置附件ID到实体
|
||||
applyUploadedFiles(entity, files);
|
||||
|
||||
boolean result = sdAnimalDictoryBService.add(entity, request.getSource());
|
||||
return result ? ResponseResult.success("新增成功") : ResponseResult.error("新增失败");
|
||||
} catch (Exception e) {
|
||||
log.error("新增动物字典失败", e);
|
||||
return ResponseResult.error("新增失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Log(module = "动物字典管理", value = "修改动物字典")
|
||||
@PostMapping("/update")
|
||||
@Operation(summary = "修改动物字典")
|
||||
public ResponseResult update(@RequestParam("data") String dataStr,
|
||||
@RequestParam(value = "files", required = false) List<MultipartFile> files) {
|
||||
try {
|
||||
SdEngInfoBHOperateRequest request = objectMapper.readValue(dataStr, SdEngInfoBHOperateRequest.class);
|
||||
Map<String, Object> engInfo = request == null ? null : request.getEngInfo();
|
||||
if (engInfo == null || engInfo.get("id") == null) {
|
||||
return ResponseResult.error("数据或ID不能为空");
|
||||
}
|
||||
|
||||
// 名称重名校验(仅当本次修改了名称时校验)
|
||||
Object newName = engInfo.get("name");
|
||||
if (newName != null && StrUtil.isNotBlank(newName.toString())) {
|
||||
String name = newName.toString();
|
||||
if (sdAnimalDictoryBService.existsByNameExcludeId(name, (String) engInfo.get("id"))) {
|
||||
return ResponseResult.error("该动物名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取修改前的实体,用于后续清理旧文件
|
||||
SdAnimalDictoryB before = sdAnimalDictoryBService.getById((String) engInfo.get("id"));
|
||||
List<String> oldAttachmentIds = before != null ? collectAttachmentIds(before) : Collections.emptyList();
|
||||
|
||||
// 上传新文件并设置附件ID
|
||||
SdAnimalDictoryB tempEntity = objectMapper.convertValue(engInfo, SdAnimalDictoryB.class);
|
||||
applyUploadedFiles(tempEntity, files);
|
||||
// 将文件处理后 inffile 合并回 patch map
|
||||
engInfo.put("inffile", tempEntity.getInffile());
|
||||
|
||||
boolean result = sdAnimalDictoryBService.update(engInfo, request.getSource());
|
||||
if (result) {
|
||||
// 清理被替换的旧文件
|
||||
SdAnimalDictoryB after = sdAnimalDictoryBService.getById((String) engInfo.get("id"));
|
||||
deleteReplacedFiles(oldAttachmentIds, collectAttachmentIds(after));
|
||||
}
|
||||
return result ? ResponseResult.success("修改成功") : ResponseResult.error("修改失败");
|
||||
} catch (Exception e) {
|
||||
log.error("修改动物字典失败", e);
|
||||
return ResponseResult.error("修改失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Log(module = "动物字典管理", value = "删除动物字典")
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除动物字典")
|
||||
public ResponseResult delete(@RequestBody SdEngInfoBHOperateRequest request) {
|
||||
List<String> ids = request == null ? null : request.getIds();
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return ResponseResult.error("ID不能为空");
|
||||
}
|
||||
|
||||
// 删除前先收集所有需要清理的附件ID
|
||||
List<String> allAttachmentIds = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
SdAnimalDictoryB entity = sdAnimalDictoryBService.getById(id);
|
||||
if (entity != null) {
|
||||
allAttachmentIds.addAll(collectAttachmentIds(entity));
|
||||
}
|
||||
}
|
||||
|
||||
boolean result = sdAnimalDictoryBService.delete(ids, request.getSource());
|
||||
if (result) {
|
||||
// 删除关联的附件
|
||||
deleteAttachments(allAttachmentIds);
|
||||
}
|
||||
return result ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
|
||||
}
|
||||
|
||||
@GetMapping("/similar")
|
||||
@Operation(summary = "查询相似动物列表")
|
||||
public ResponseResult findSimilarAnimals(
|
||||
@RequestParam String name,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer limit) {
|
||||
return ResponseResult.successData(sdAnimalDictoryBService.findSimilarAnimals(name, limit));
|
||||
}
|
||||
|
||||
// ==================== 文件处理辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 上传文件并追加附件ID到inffile字段(与已有ID以逗号拼接)
|
||||
*/
|
||||
private void applyUploadedFiles(SdAnimalDictoryB entity, List<MultipartFile> files) {
|
||||
if (files == null || files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> attachmentIds = attachmentUploadService.uploadMultipartFiles(files);
|
||||
if (attachmentIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String newIds = attachmentIds.stream()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.joining(","));
|
||||
if (StrUtil.isBlank(newIds)) {
|
||||
return;
|
||||
}
|
||||
String existing = StrUtil.isNotBlank(entity.getInffile()) ? entity.getInffile() : "";
|
||||
String merged = StrUtil.isBlank(existing) ? newIds : existing + "," + newIds;
|
||||
entity.setInffile(merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集实体中inffile字段的附件ID
|
||||
*/
|
||||
private List<String> collectAttachmentIds(SdAnimalDictoryB entity) {
|
||||
List<String> ids = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(entity.getInffile())) {
|
||||
Arrays.stream(entity.getInffile().split(","))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.forEach(ids::add);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除被替换的旧文件(old中有但new中没有的)
|
||||
*/
|
||||
private void deleteReplacedFiles(List<String> oldIds, List<String> newIds) {
|
||||
for (String oldId : oldIds) {
|
||||
if (StrUtil.isBlank(oldId)) continue;
|
||||
if (!newIds.contains(oldId)) {
|
||||
attachmentUploadService.deleteFile(oldId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除附件
|
||||
*/
|
||||
private void deleteAttachments(List<String> attachmentIds) {
|
||||
for (String id : attachmentIds) {
|
||||
if (StrUtil.isNotBlank(id)) {
|
||||
attachmentUploadService.deleteFile(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,217 @@
|
||||
package com.yfd.platform.qgc_base.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.yfd.platform.annotation.FieldChinese;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 动物字典信息表(陆生动物、鸟类等,用于动物监测数据中动物物种的标识)
|
||||
* </p>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("SD_ANIMALDICTORY_B")
|
||||
public class SdAnimalDictoryB implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键 */
|
||||
@TableId(type = IdType.INPUT)
|
||||
@FieldChinese("主键")
|
||||
private String id;
|
||||
|
||||
/** 编码 */
|
||||
@FieldChinese("编码")
|
||||
private String code;
|
||||
|
||||
/** 名称 */
|
||||
@FieldChinese("名称")
|
||||
private String name;
|
||||
|
||||
/** 英文名称 */
|
||||
@FieldChinese("英文名称")
|
||||
private String nameEn;
|
||||
|
||||
/** 俗名 */
|
||||
@FieldChinese("俗名")
|
||||
private String alias;
|
||||
|
||||
/** 动物类群:1=哺乳动物 2=鸟类 3=两栖动物 4=爬行动物 5=节肢动物 6=其他 */
|
||||
@FieldChinese("动物类群")
|
||||
private Integer category;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String categoryName;
|
||||
|
||||
/** 分类(生境类型):1=陆生 2=水生 3=两栖 */
|
||||
@FieldChinese("生境类型")
|
||||
private Integer type;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String typeName;
|
||||
|
||||
/** 是否珍稀动物:0=否 1=是 */
|
||||
@FieldChinese("是否珍稀动物")
|
||||
private Integer rare;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String rareName;
|
||||
|
||||
/** 物种来源:1=本土物种 2=外来物种 */
|
||||
@FieldChinese("物种来源")
|
||||
private Integer specOrigin;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String specOriginName;
|
||||
|
||||
/** 保护级别 */
|
||||
@FieldChinese("保护级别")
|
||||
private Integer ptype;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String ptypeName;
|
||||
|
||||
/** 保护/灭绝等级:1=极危 2=濒危 3=易危 4=近危 5=低危 */
|
||||
@FieldChinese("保护等级")
|
||||
private Integer situation;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String situationName;
|
||||
|
||||
/** 主要生活环境 */
|
||||
@FieldChinese("主要生活环境")
|
||||
private Integer habitat;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String habitatName;
|
||||
|
||||
/** 门 */
|
||||
@FieldChinese("门")
|
||||
private String filum;
|
||||
|
||||
/** 纲 */
|
||||
@FieldChinese("纲")
|
||||
private String classis;
|
||||
|
||||
/** 目 */
|
||||
@FieldChinese("目")
|
||||
private String orders;
|
||||
|
||||
/** 科 */
|
||||
@FieldChinese("科")
|
||||
private String family;
|
||||
|
||||
/** 属 */
|
||||
@FieldChinese("属")
|
||||
private String genus;
|
||||
|
||||
/** 种 */
|
||||
@FieldChinese("种")
|
||||
private String species;
|
||||
|
||||
/** 体型大小 */
|
||||
@FieldChinese("体型大小")
|
||||
private String fsz;
|
||||
|
||||
/** 食性:1=肉食 2=草食 3=杂食 */
|
||||
@FieldChinese("食性")
|
||||
private String feedingHabit;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String feedingHabitName;
|
||||
|
||||
/** 活动时律:1=昼行 2=夜行 3=晨昏 */
|
||||
@FieldChinese("活动时律")
|
||||
private String activityTime;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String activityTimeName;
|
||||
|
||||
/** 分布区域 */
|
||||
@FieldChinese("分布区域")
|
||||
private String distribution;
|
||||
|
||||
/** 形态特征/识别特征 */
|
||||
@FieldChinese("形态特征")
|
||||
private String symbol;
|
||||
|
||||
/** 栖息习性 */
|
||||
@FieldChinese("栖息习性")
|
||||
private String habitation;
|
||||
|
||||
/** LOGO */
|
||||
@FieldChinese("LOGO")
|
||||
private String logo;
|
||||
|
||||
/** 介绍 */
|
||||
@FieldChinese("介绍")
|
||||
private String introduce;
|
||||
|
||||
/** 介绍弹窗图片 */
|
||||
@FieldChinese("介绍弹窗图片")
|
||||
private String inffile;
|
||||
|
||||
/** 附件 */
|
||||
@FieldChinese("附件")
|
||||
private String fid;
|
||||
|
||||
/** 描述 */
|
||||
@FieldChinese("描述")
|
||||
private String description;
|
||||
|
||||
/** 是否启用:0=禁用 1=启用 */
|
||||
@FieldChinese("是否启用")
|
||||
private Integer enable;
|
||||
|
||||
/** 系统内置项:0=否 1=是 */
|
||||
@FieldChinese("系统内置项")
|
||||
private Integer internal;
|
||||
|
||||
/** 排序 */
|
||||
@FieldChinese("排序")
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 创建人 */
|
||||
@FieldChinese("创建人")
|
||||
private String recordUser;
|
||||
|
||||
/** 创建时间 */
|
||||
@FieldChinese("创建时间")
|
||||
private Date recordTime;
|
||||
|
||||
/** 更新人 */
|
||||
@FieldChinese("更新人")
|
||||
private String modifyUser;
|
||||
|
||||
/** 更新时间 */
|
||||
@FieldChinese("更新时间")
|
||||
private Date modifyTime;
|
||||
|
||||
/** 是否已删除 */
|
||||
@FieldChinese("是否已删除")
|
||||
private Integer isDeleted;
|
||||
|
||||
/** 删除人 */
|
||||
@FieldChinese("删除人")
|
||||
private String deleteUser;
|
||||
|
||||
/** 删除时间 */
|
||||
@FieldChinese("删除时间")
|
||||
private Date deleteTime;
|
||||
|
||||
/** 数据来源 */
|
||||
@FieldChinese("数据来源")
|
||||
private String vlsr;
|
||||
|
||||
/** 数据来源时间 */
|
||||
@FieldChinese("数据来源时间")
|
||||
private Date vlsrTm;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.yfd.platform.qgc_base.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.qgc_base.domain.SdAnimalDictoryB;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SdAnimalDictoryBMapper extends BaseMapper<SdAnimalDictoryB> {
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.yfd.platform.qgc_base.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.qgc_base.domain.SdAnimalDictoryB;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ISdAnimalDictoryBService extends IService<SdAnimalDictoryB> {
|
||||
|
||||
Page<SdAnimalDictoryB> queryPageList(DataSourceRequest request);
|
||||
|
||||
Page<SdAnimalDictoryB> selectPage(String name, String code, Integer category, Integer type, Page<SdAnimalDictoryB> page);
|
||||
|
||||
boolean add(SdAnimalDictoryB entity, String source);
|
||||
|
||||
boolean update(SdAnimalDictoryB entity, String source);
|
||||
|
||||
boolean update(Map<String, Object> patchMap, String source) throws Exception;
|
||||
|
||||
boolean delete(List<String> ids, String source);
|
||||
|
||||
SdAnimalDictoryB getById(String id);
|
||||
|
||||
/**
|
||||
* 检查名称是否已存在(不过滤IS_DELETED,因为唯一约束不区分软删除)
|
||||
*/
|
||||
boolean existsByName(String name);
|
||||
|
||||
/**
|
||||
* 检查名称是否已被其他记录占用(排除指定ID)
|
||||
*/
|
||||
boolean existsByNameExcludeId(String name, String excludeId);
|
||||
|
||||
List<SdAnimalDictoryB> findSimilarAnimals(String name, Integer limit);
|
||||
}
|
||||
@ -0,0 +1,523 @@
|
||||
package com.yfd.platform.qgc_base.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.qgc_base.domain.SdAnimalDictoryB;
|
||||
import com.yfd.platform.qgc_base.mapper.SdAnimalDictoryBMapper;
|
||||
import com.yfd.platform.qgc_base.service.ISdAnimalDictoryBService;
|
||||
import com.yfd.platform.qgc_base.service.IMsOperationLogService;
|
||||
import com.yfd.platform.utils.CodeToNameMetadataBo;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
import com.yfd.platform.utils.DictCodeToNameConverter;
|
||||
import com.yfd.platform.utils.SecurityUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 动物字典 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class SdAnimalDictoryBServiceImpl extends ServiceImpl<SdAnimalDictoryBMapper, SdAnimalDictoryB> implements ISdAnimalDictoryBService {
|
||||
|
||||
private static final String TABLE_NAME = "SD_ANIMALDICTORY_B";
|
||||
|
||||
@Resource
|
||||
private IMsOperationLogService msOperationLogService;
|
||||
|
||||
@Resource
|
||||
private DictCodeToNameConverter dictCodeToNameConverter;
|
||||
|
||||
@Resource
|
||||
private PatchUpdateHelper patchUpdateHelper;
|
||||
|
||||
/** SdAnimalDictoryB 中 @TableField(exist = false) 的字段名 + serialVersionUID */
|
||||
private static final Set<String> TRANSIENT_FIELDS = Set.of(
|
||||
"categoryName", "typeName", "rareName", "specOriginName",
|
||||
"ptypeName", "situationName", "habitatName", "feedingHabitName",
|
||||
"activityTimeName", "serialVersionUID"
|
||||
);
|
||||
|
||||
/**
|
||||
* 代码转名称元数据配置列表
|
||||
*/
|
||||
private static final List<CodeToNameMetadataBo> CODE_TO_NAME_META_LIST = new ArrayList<>();
|
||||
|
||||
static {
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("category").modifyProperty("categoryName").dictType("STATIC").dictSource("ANIMAL_CATEGORY").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("type").modifyProperty("typeName").dictType("STATIC").dictSource("HABITAT_TYPE").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("rare").modifyProperty("rareName").dictType("STATIC").dictSource("TY_SF").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("specOrigin").modifyProperty("specOriginName").dictType("STATIC").dictSource("SPEC_ORIGIN").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("ptype").modifyProperty("ptypeName").dictType("STATIC").dictSource("PTYPE").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("situation").modifyProperty("situationName").dictType("STATIC").dictSource("SITUATION").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("habitat").modifyProperty("habitatName").dictType("STATIC").dictSource("HABITAT").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("feedingHabit").modifyProperty("feedingHabitName").dictType("STATIC").dictSource("FEEDING_HABIT").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("activityTime").modifyProperty("activityTimeName").dictType("STATIC").dictSource("ACTIVITY_TIME").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SdAnimalDictoryB> queryPageList(DataSourceRequest request) {
|
||||
Page<SdAnimalDictoryB> page = DataSourceRequestUtil.executeQuery(request, SdAnimalDictoryB.class, this);
|
||||
dictCodeToNameConverter.convertCodeToName(page, CODE_TO_NAME_META_LIST);
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean add(SdAnimalDictoryB entity, String source) {
|
||||
boolean result = this.save(entity);
|
||||
if (result) {
|
||||
dictCodeToNameConverter.convertCodeToName(entity, CODE_TO_NAME_META_LIST);
|
||||
msOperationLogService.recordAddDetailLog(entity.getId(), TABLE_NAME, entity, source, CODE_TO_NAME_META_LIST);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean update(SdAnimalDictoryB entity, String source) {
|
||||
SdAnimalDictoryB before = this.getById(entity.getId());
|
||||
boolean result = this.updateById(entity);
|
||||
if (result && before != null) {
|
||||
dictCodeToNameConverter.convertCodeToName(before, CODE_TO_NAME_META_LIST);
|
||||
dictCodeToNameConverter.convertCodeToName(entity, CODE_TO_NAME_META_LIST);
|
||||
msOperationLogService.recordModifyDetailLog(entity.getId(), TABLE_NAME, before, entity, source, CODE_TO_NAME_META_LIST);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean update(Map<String, Object> patchMap, String source) throws Exception {
|
||||
return patchUpdateHelper.execute(
|
||||
this, patchMap, source, TABLE_NAME, SdAnimalDictoryB.class,
|
||||
TRANSIENT_FIELDS, CODE_TO_NAME_META_LIST, "id");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean delete(List<String> ids, String source) {
|
||||
if (ids == null || ids.isEmpty()) return false;
|
||||
int count = 0;
|
||||
for (String id : ids) {
|
||||
SdAnimalDictoryB entity = getById(id);
|
||||
LambdaUpdateWrapper<SdAnimalDictoryB> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(SdAnimalDictoryB::getId, id);
|
||||
wrapper.set(SdAnimalDictoryB::getIsDeleted, 1);
|
||||
wrapper.set(SdAnimalDictoryB::getDeleteUser, SecurityUtils.getCurrentUsername());
|
||||
wrapper.set(SdAnimalDictoryB::getDeleteTime, new Date());
|
||||
if (this.update(wrapper)) {
|
||||
dictCodeToNameConverter.convertCodeToName(entity, CODE_TO_NAME_META_LIST);
|
||||
msOperationLogService.recordDeleteDetailLog(entity.getName(), TABLE_NAME, entity, source, CODE_TO_NAME_META_LIST);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByName(String name) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return false;
|
||||
}
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SdAnimalDictoryB::getName, name);
|
||||
return count(wrapper) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByNameExcludeId(String name, String excludeId) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return false;
|
||||
}
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SdAnimalDictoryB::getName, name);
|
||||
if (StringUtils.hasText(excludeId)) {
|
||||
wrapper.ne(SdAnimalDictoryB::getId, excludeId);
|
||||
}
|
||||
return count(wrapper) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdAnimalDictoryB getById(String id) {
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SdAnimalDictoryB::getId, id)
|
||||
.eq(SdAnimalDictoryB::getIsDeleted, 0);
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SdAnimalDictoryB> selectPage(String name, String code, Integer category, Integer type, Page<SdAnimalDictoryB> page) {
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SdAnimalDictoryB::getIsDeleted, 0);
|
||||
|
||||
if (StringUtils.hasText(name)) {
|
||||
wrapper.like(SdAnimalDictoryB::getName, name);
|
||||
}
|
||||
if (StringUtils.hasText(code)) {
|
||||
wrapper.eq(SdAnimalDictoryB::getCode, code);
|
||||
}
|
||||
if (category != null) {
|
||||
wrapper.eq(SdAnimalDictoryB::getCategory, category);
|
||||
}
|
||||
if (type != null) {
|
||||
wrapper.eq(SdAnimalDictoryB::getType, type);
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(SdAnimalDictoryB::getOrderIndex);
|
||||
return page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SdAnimalDictoryB> findSimilarAnimals(String name, Integer limit) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int resultLimit = (limit != null && limit > 0) ? limit : 10;
|
||||
String rawSearchName = name.trim();
|
||||
String searchName = normalizeText(rawSearchName);
|
||||
|
||||
List<SdAnimalDictoryB> candidates = querySimilarCandidates(rawSearchName, searchName, resultLimit);
|
||||
if (candidates.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<String, Integer> searchScoreMap = new HashMap<>();
|
||||
for (SdAnimalDictoryB animal : candidates) {
|
||||
if (animal == null || !StringUtils.hasText(animal.getId())) {
|
||||
continue;
|
||||
}
|
||||
int score = calculateSearchScore(searchName, animal);
|
||||
if (score > 0) {
|
||||
searchScoreMap.put(animal.getId(), score);
|
||||
}
|
||||
}
|
||||
|
||||
if (searchScoreMap.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SdAnimalDictoryB reference = candidates.stream()
|
||||
.filter(a -> a != null && searchScoreMap.containsKey(a.getId()))
|
||||
.max((a1, a2) -> {
|
||||
int scoreCompare = Integer.compare(searchScoreMap.get(a1.getId()), searchScoreMap.get(a2.getId()));
|
||||
if (scoreCompare != 0) return scoreCompare;
|
||||
int orderCompare = Integer.compare(nullSafe(a1.getOrderIndex()), nullSafe(a2.getOrderIndex()));
|
||||
if (orderCompare != 0) return orderCompare;
|
||||
return safeText(a2.getName()).compareTo(safeText(a1.getName()));
|
||||
})
|
||||
.orElse(null);
|
||||
|
||||
if (reference == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return candidates.stream()
|
||||
.filter(a -> a != null && StringUtils.hasText(a.getId()))
|
||||
.map(a -> new AbstractMap.SimpleEntry<>(a, buildFinalScore(reference, a, searchScoreMap.getOrDefault(a.getId(), 0))))
|
||||
.filter(e -> e.getValue() > 0)
|
||||
.sorted((e1, e2) -> {
|
||||
int scoreCompare = Integer.compare(e2.getValue(), e1.getValue());
|
||||
if (scoreCompare != 0) return scoreCompare;
|
||||
int orderCompare = Integer.compare(nullSafe(e2.getKey().getOrderIndex()), nullSafe(e1.getKey().getOrderIndex()));
|
||||
if (orderCompare != 0) return orderCompare;
|
||||
return safeText(e1.getKey().getName()).compareTo(safeText(e2.getKey().getName()));
|
||||
})
|
||||
.limit(resultLimit)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<SdAnimalDictoryB> querySimilarCandidates(String rawSearchName, String normalizedSearchName, int resultLimit) {
|
||||
int candidateLimit = Math.min(Math.max(resultLimit * 30, 120), 500);
|
||||
LinkedHashMap<String, SdAnimalDictoryB> candidateMap = new LinkedHashMap<>();
|
||||
|
||||
mergeCandidates(candidateMap, queryExactCandidates(rawSearchName, normalizedSearchName, candidateLimit));
|
||||
if (candidateMap.size() < candidateLimit) {
|
||||
mergeCandidates(candidateMap, queryContainsCandidates(rawSearchName, normalizedSearchName, candidateLimit));
|
||||
}
|
||||
if (candidateMap.size() < candidateLimit) {
|
||||
mergeCandidates(candidateMap, queryTokenCandidates(normalizedSearchName, candidateLimit));
|
||||
}
|
||||
|
||||
return new ArrayList<>(candidateMap.values());
|
||||
}
|
||||
|
||||
private void mergeCandidates(Map<String, SdAnimalDictoryB> candidateMap, List<SdAnimalDictoryB> candidates) {
|
||||
if (candidates == null || candidates.isEmpty()) return;
|
||||
for (SdAnimalDictoryB animal : candidates) {
|
||||
if (animal == null || !StringUtils.hasText(animal.getId())) continue;
|
||||
candidateMap.putIfAbsent(animal.getId(), animal);
|
||||
}
|
||||
}
|
||||
|
||||
private List<SdAnimalDictoryB> queryExactCandidates(String rawSearchName, String normalizedSearchName, int limit) {
|
||||
if (!StringUtils.hasText(rawSearchName)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||
wrapper.and(w -> w
|
||||
.eq(SdAnimalDictoryB::getName, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getAlias, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getNameEn, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getSpecies, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getGenus, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getFamily, rawSearchName)
|
||||
.or().eq(SdAnimalDictoryB::getOrders, rawSearchName)
|
||||
.or().apply("LOWER(NAME) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(ALIAS) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(NAME_EN) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(SPECIES) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(GENUS) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(FAMILY) = {0}", normalizedSearchName)
|
||||
.or().apply("LOWER(ORDERS) = {0}", normalizedSearchName));
|
||||
wrapper.orderByDesc(SdAnimalDictoryB::getOrderIndex);
|
||||
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||
}
|
||||
|
||||
private List<SdAnimalDictoryB> queryContainsCandidates(String rawSearchName, String normalizedSearchName, int limit) {
|
||||
if (!StringUtils.hasText(rawSearchName)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String likeValue = "%" + rawSearchName + "%";
|
||||
String normalizedLikeValue = "%" + normalizedSearchName + "%";
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||
wrapper.and(w -> w
|
||||
.like(SdAnimalDictoryB::getName, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getAlias, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getNameEn, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getSpecies, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getGenus, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getFamily, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getOrders, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getIntroduce, rawSearchName)
|
||||
.or().like(SdAnimalDictoryB::getAlias, rawSearchName)
|
||||
.or().apply("LOWER(NAME) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(ALIAS) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(NAME_EN) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(SPECIES) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(GENUS) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(FAMILY) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(ORDERS) LIKE {0}", normalizedLikeValue)
|
||||
.or().apply("LOWER(INTRODUCE) LIKE {0}", normalizedLikeValue));
|
||||
wrapper.orderByDesc(SdAnimalDictoryB::getOrderIndex);
|
||||
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||
}
|
||||
|
||||
private List<SdAnimalDictoryB> queryTokenCandidates(String normalizedSearchName, int limit) {
|
||||
Set<String> tokens = splitTokens(normalizedSearchName).stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.filter(token -> token.length() >= 2)
|
||||
.limit(5)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
if (tokens.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||
wrapper.and(w -> {
|
||||
boolean first = true;
|
||||
for (String token : tokens) {
|
||||
String likeValue = "%" + token + "%";
|
||||
if (!first) {
|
||||
w.or();
|
||||
}
|
||||
w.apply("LOWER(NAME) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(ALIAS) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(NAME_EN) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(SPECIES) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(GENUS) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(FAMILY) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(ORDERS) LIKE {0}", likeValue)
|
||||
.or().apply("LOWER(INTRODUCE) LIKE {0}", likeValue);
|
||||
first = false;
|
||||
}
|
||||
});
|
||||
wrapper.orderByDesc(SdAnimalDictoryB::getOrderIndex);
|
||||
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<SdAnimalDictoryB> buildCandidateSelectWrapper() {
|
||||
LambdaQueryWrapper<SdAnimalDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(
|
||||
SdAnimalDictoryB::getId,
|
||||
SdAnimalDictoryB::getCode,
|
||||
SdAnimalDictoryB::getName,
|
||||
SdAnimalDictoryB::getNameEn,
|
||||
SdAnimalDictoryB::getAlias,
|
||||
SdAnimalDictoryB::getIntroduce,
|
||||
SdAnimalDictoryB::getOrders,
|
||||
SdAnimalDictoryB::getFamily,
|
||||
SdAnimalDictoryB::getGenus,
|
||||
SdAnimalDictoryB::getSpecies,
|
||||
SdAnimalDictoryB::getCategory,
|
||||
SdAnimalDictoryB::getType,
|
||||
SdAnimalDictoryB::getHabitat,
|
||||
SdAnimalDictoryB::getFeedingHabit,
|
||||
SdAnimalDictoryB::getActivityTime,
|
||||
SdAnimalDictoryB::getPtype,
|
||||
SdAnimalDictoryB::getSituation,
|
||||
SdAnimalDictoryB::getRare,
|
||||
SdAnimalDictoryB::getOrderIndex,
|
||||
SdAnimalDictoryB::getIsDeleted
|
||||
);
|
||||
wrapper.eq(SdAnimalDictoryB::getIsDeleted, 0);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private int calculateSimilarity(SdAnimalDictoryB ref, SdAnimalDictoryB target) {
|
||||
if (ref == null || target == null) {
|
||||
return 0;
|
||||
}
|
||||
if (StringUtils.hasText(ref.getId()) && ref.getId().equals(target.getId())) {
|
||||
return 5000;
|
||||
}
|
||||
int score = 0;
|
||||
score += calculateFieldSimilarity(ref.getName(), target.getName(), 45);
|
||||
score += calculateFieldSimilarity(ref.getNameEn(), target.getNameEn(), 22);
|
||||
score += calculateFieldSimilarity(ref.getAlias(), target.getAlias(), 26);
|
||||
score += calculateFieldSimilarity(ref.getSpecies(), target.getSpecies(), 30);
|
||||
score += calculateFieldSimilarity(ref.getGenus(), target.getGenus(), 24);
|
||||
score += calculateFieldSimilarity(ref.getFamily(), target.getFamily(), 18);
|
||||
score += calculateFieldSimilarity(ref.getOrders(), target.getOrders(), 14);
|
||||
score += calculateFieldSimilarity(ref.getIntroduce(), target.getIntroduce(), 8);
|
||||
|
||||
if (bothEqual(ref.getCategory(), target.getCategory())) score += 5;
|
||||
if (bothEqual(ref.getType(), target.getType())) score += 5;
|
||||
if (bothEqual(ref.getHabitat(), target.getHabitat())) score += 5;
|
||||
if (sameText(ref.getFeedingHabit(), target.getFeedingHabit())) score += 3;
|
||||
if (sameText(ref.getActivityTime(), target.getActivityTime())) score += 3;
|
||||
if (bothEqual(ref.getPtype(), target.getPtype())) score += 2;
|
||||
if (bothEqual(ref.getSituation(), target.getSituation())) score += 2;
|
||||
if (bothEqual(ref.getRare(), target.getRare())) score += 2;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private int calculateSearchScore(String searchName, SdAnimalDictoryB animal) {
|
||||
int score = 0;
|
||||
score += calculateFieldSearchScore(searchName, animal.getName(), 220, 140, 90, 40);
|
||||
score += calculateFieldSearchScore(searchName, animal.getAlias(), 180, 120, 80, 30);
|
||||
score += calculateFieldSearchScore(searchName, animal.getNameEn(), 160, 100, 70, 25);
|
||||
score += calculateFieldSearchScore(searchName, animal.getSpecies(), 140, 90, 60, 25);
|
||||
score += calculateFieldSearchScore(searchName, animal.getGenus(), 120, 80, 55, 20);
|
||||
score += calculateFieldSearchScore(searchName, animal.getFamily(), 100, 70, 45, 18);
|
||||
score += calculateFieldSearchScore(searchName, animal.getOrders(), 90, 60, 40, 15);
|
||||
score += calculateFieldSearchScore(searchName, animal.getIntroduce(), 45, 30, 0, 12);
|
||||
return score;
|
||||
}
|
||||
|
||||
private int buildFinalScore(SdAnimalDictoryB reference, SdAnimalDictoryB current, int searchScore) {
|
||||
int totalScore = searchScore;
|
||||
if (reference != null) {
|
||||
totalScore += calculateSimilarity(reference, current);
|
||||
}
|
||||
return totalScore;
|
||||
}
|
||||
|
||||
private int calculateFieldSearchScore(String searchText, String candidate, int exactScore, int containsScore,
|
||||
int reverseContainsScore, int overlapMaxScore) {
|
||||
String normalizedSearch = normalizeText(searchText);
|
||||
String normalizedCandidate = normalizeText(candidate);
|
||||
if (!StringUtils.hasText(normalizedSearch) || !StringUtils.hasText(normalizedCandidate)) {
|
||||
return 0;
|
||||
}
|
||||
if (normalizedCandidate.equals(normalizedSearch)) {
|
||||
return exactScore;
|
||||
}
|
||||
if (normalizedCandidate.contains(normalizedSearch)) {
|
||||
return containsScore;
|
||||
}
|
||||
if (reverseContainsScore > 0 && normalizedSearch.contains(normalizedCandidate) && normalizedCandidate.length() >= 2) {
|
||||
return reverseContainsScore;
|
||||
}
|
||||
return calculateTextOverlapScore(normalizedSearch, normalizedCandidate, overlapMaxScore);
|
||||
}
|
||||
|
||||
private int calculateFieldSimilarity(String left, String right, int exactScore) {
|
||||
String normalizedLeft = normalizeText(left);
|
||||
String normalizedRight = normalizeText(right);
|
||||
if (!StringUtils.hasText(normalizedLeft) || !StringUtils.hasText(normalizedRight)) {
|
||||
return 0;
|
||||
}
|
||||
if (normalizedLeft.equals(normalizedRight)) {
|
||||
return exactScore;
|
||||
}
|
||||
if (normalizedLeft.contains(normalizedRight) || normalizedRight.contains(normalizedLeft)) {
|
||||
return Math.max(1, exactScore / 2);
|
||||
}
|
||||
return calculateTextOverlapScore(normalizedLeft, normalizedRight, Math.max(1, exactScore / 3));
|
||||
}
|
||||
|
||||
private int calculateTextOverlapScore(String left, String right, int maxScore) {
|
||||
if (!StringUtils.hasText(left) || !StringUtils.hasText(right) || maxScore <= 0) {
|
||||
return 0;
|
||||
}
|
||||
Set<String> leftTokens = splitTokens(left);
|
||||
Set<String> rightTokens = splitTokens(right);
|
||||
if (leftTokens.isEmpty() || rightTokens.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
long commonCount = leftTokens.stream().filter(rightTokens::contains).count();
|
||||
if (commonCount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
int base = Math.min(leftTokens.size(), rightTokens.size());
|
||||
if (base <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int) Math.min(maxScore, Math.round((double) commonCount * maxScore / base));
|
||||
}
|
||||
|
||||
private Set<String> splitTokens(String text) {
|
||||
String normalized = normalizeText(text);
|
||||
if (!StringUtils.hasText(normalized)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> tokens = new LinkedHashSet<>();
|
||||
String[] parts = normalized.split("[\\s,,;;、/\\\\()()]+");
|
||||
for (String part : parts) {
|
||||
if (StringUtils.hasText(part)) {
|
||||
tokens.add(part);
|
||||
}
|
||||
}
|
||||
if (tokens.isEmpty()) {
|
||||
for (int i = 0; i < normalized.length(); i++) {
|
||||
char ch = normalized.charAt(i);
|
||||
if (!Character.isWhitespace(ch)) {
|
||||
tokens.add(String.valueOf(ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private boolean bothEqual(Integer a, Integer b) {
|
||||
return a != null && a.equals(b) && !ObjectUtil.isEmpty(a);
|
||||
}
|
||||
|
||||
private boolean sameText(String a, String b) {
|
||||
return StringUtils.hasText(a) && StringUtils.hasText(b) && a.equals(b);
|
||||
}
|
||||
|
||||
private int nullSafe(Integer v) {
|
||||
return v == null ? 0 : v;
|
||||
}
|
||||
|
||||
private String normalizeText(String text) {
|
||||
return StringUtils.hasText(text) ? text.trim().toLowerCase(Locale.ROOT) : "";
|
||||
}
|
||||
|
||||
private String safeText(String text) {
|
||||
return text == null ? "" : text;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user