From 8b062501311c729792863f928dd10e2359bf13dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=88=E7=BB=8F=E8=8E=B9?= Date: Mon, 27 Jul 2026 13:10:19 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A1=8C=E4=B8=9A?= =?UTF-8?q?=E5=A4=A7=E7=B1=BB=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E6=94=BF=E7=AD=96=E4=BF=9D=E5=AD=98=E5=A2=9E=E5=8A=A0=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E6=94=BF=E7=AD=96=E7=95=99=E8=A8=80=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-local.yml | 6 +- .../app/AppPolicyConsultRecordController.java | 68 ++++++++++ .../cms/CmsPolicyConsultRecordController.java | 47 +++++++ .../controller/cms/IndustryController.java | 11 ++ .../domain/policy/PolicyConsultQueryDTO.java | 34 +++++ .../domain/policy/PolicyConsultRecord.java | 80 ++++++++++++ .../domain/policy/PolicyConsultReplyDTO.java | 20 +++ .../policy/PolicyConsultSatisfactionDTO.java | 20 +++ .../domain/policy/PolicyConsultSubmitDTO.java | 23 ++++ .../ruoyi/cms/domain/policy/PolicyInfo.java | 30 +++++ .../policy/PolicyConsultRecordMapper.java | 9 ++ .../ruoyi/cms/service/IIndustryService.java | 6 + .../cms/service/impl/IndustryServiceImpl.java | 11 ++ .../policy/IPolicyConsultRecordService.java | 18 +++ .../impl/PolicyConsultRecordServiceImpl.java | 121 ++++++++++++++++++ .../policy/PolicyConsultRecordMapper.xml | 44 +++++++ .../mapper/policy/PolicyInfoMapper.xml | 30 ++++- 17 files changed, 572 insertions(+), 6 deletions(-) create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultQueryDTO.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultReplyDTO.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/policy/PolicyConsultRecordMapper.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java create mode 100644 ruoyi-bussiness/src/main/resources/mapper/policy/PolicyConsultRecordMapper.xml diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml index dcc71f5..6fa3ae7 100644 --- a/ruoyi-admin/src/main/resources/application-local.yml +++ b/ruoyi-admin/src/main/resources/application-local.yml @@ -87,8 +87,10 @@ spring: multi-statement-allow: true redis: # 本地 - host: 127.0.0.1 - port: 6379 + #host: 127.0.0.1 + #port: 6379 + host: 124.243.245.42 + port: 5379 # 数据库索引 database: 0 # 密码(本地通常无密码) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java new file mode 100644 index 0000000..cc480ba --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java @@ -0,0 +1,68 @@ +package com.ruoyi.cms.controller.app; + +import com.ruoyi.cms.domain.policy.PolicyConsultRecord; +import com.ruoyi.cms.domain.policy.PolicyConsultReplyDTO; +import com.ruoyi.cms.domain.policy.PolicyConsultSatisfactionDTO; +import com.ruoyi.cms.domain.policy.PolicyConsultSubmitDTO; +import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.SiteSecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/app/policyConsultRecord") +@Api(tags = "移动端接口:政策咨询记录") +public class AppPolicyConsultRecordController extends BaseController { + + @Autowired + private IPolicyConsultRecordService policyConsultRecordService; + + /** + * 保存用户提交留言咨询 + */ + @ApiOperation("用户提交留言咨询") + @PostMapping("/submit") + public AjaxResult save(@RequestBody PolicyConsultSubmitDTO policyConsultSubmitDTO){ + if(!SiteSecurityUtils.isLogin()){ + return AjaxResult.error("用户未登录!"); + } + return toAjax(policyConsultRecordService.insert(policyConsultSubmitDTO)); + } + + /** + * 用户查看【自己】的留言咨询记录(留言板只能看自己) + */ + @ApiOperation("用户查看【自己】的留言咨询记录(留言板只能看自己)") + @GetMapping("/my/list/{policyId}") + public TableDataInfo getMyConsult(@ApiParam("政策ID") @PathVariable Long policyId){ + startPage(); + List list = policyConsultRecordService.getUserOwnConsultList(policyId); + return getDataTable(list); + } + + /** + * 用户对咨询进行满意度评价 + */ + @ApiOperation("用户对咨询进行满意度评价") + @PostMapping("/satisfaction") + public AjaxResult satisfaction(@RequestBody PolicyConsultSatisfactionDTO dto){ + if(!SiteSecurityUtils.isLogin()){ + return AjaxResult.error("用户未登录!"); + } + return toAjax(policyConsultRecordService.submitSatisfaction(dto)); + } + + + +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java new file mode 100644 index 0000000..febb7a0 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java @@ -0,0 +1,47 @@ +package com.ruoyi.cms.controller.cms; + +import com.ruoyi.cms.domain.policy.PolicyConsultQueryDTO; +import com.ruoyi.cms.domain.policy.PolicyConsultRecord; +import com.ruoyi.cms.domain.policy.PolicyConsultReplyDTO; +import com.ruoyi.cms.domain.policy.PolicyInfoQuery; +import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/cms/policyConsultRecord") +@Api(tags = "CMS:政策咨询记录管理") +public class CmsPolicyConsultRecordController extends BaseController { + + @Autowired + private IPolicyConsultRecordService policyConsultRecordService; + + + // ========== 管理员接口 ========== + /** + * 管理员回复留言咨询 + */ + @ApiOperation("管理员回复留言咨询") + @PostMapping("/reply") + public AjaxResult reply(@RequestBody PolicyConsultReplyDTO dto){ + return toAjax(policyConsultRecordService.replyConsult(dto)); + } + + /** + * 管理员查看全部咨询存档记录 + */ + @ApiOperation("管理员查看全部咨询存档记录") + @PostMapping("/list") + public TableDataInfo allRecord(@RequestBody PolicyConsultQueryDTO query){ + startPage(); + List list = policyConsultRecordService.getAllConsultRecord(query); + return getDataTable(list); + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/IndustryController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/IndustryController.java index 2cc8e45..270365a 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/IndustryController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/IndustryController.java @@ -243,4 +243,15 @@ public class IndustryController extends BaseController updateIndustry.setParentId(parentId); industryService.updateIndustry(updateIndustry); } + + /** + * 查询第一级行业大类 + */ + @ApiOperation("查询第一级行业大类") + @GetMapping("/first-level") + public AjaxResult getFirstLevelIndustries() + { + List list = industryService.selectFirstLevelIndustryList(); + return success(list); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultQueryDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultQueryDTO.java new file mode 100644 index 0000000..7d3fc30 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultQueryDTO.java @@ -0,0 +1,34 @@ +package com.ruoyi.cms.domain.policy; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel("政策咨询查询") +public class PolicyConsultQueryDTO { + + @ApiModelProperty("页码") + private Integer pageNum = 1; + + @ApiModelProperty("每页条数") + private Integer pageSize = 10; + + @ApiModelProperty("政策名称搜索关键词") + private String searchValue; + + @ApiModelProperty("咨询状态 PENDING待回复、REPLIED已回复") + private String consultStatus; + + @ApiModelProperty("满意度(0-5分)") + private Integer satisfaction; + + // 咨询起始时间 + @ApiModelProperty("咨询起始时间") + private Date beginConsultTime; + // 咨询结束时间 + @ApiModelProperty("咨询结束时间") + private Date endConsultTime; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java new file mode 100644 index 0000000..f1cb323 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java @@ -0,0 +1,80 @@ +package com.ruoyi.cms.domain.policy; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("政策咨询记录") +public class PolicyConsultRecord extends BaseEntity { + + private static final long serialVersionUID = 1L; + /** 咨询渠道 - 留言板 */ + public static final String CHANNEL_MESSAGE_BOARD = "MESSAGE_BOARD"; + /** 咨询渠道 - 在线客服 */ + public static final String CHANNEL_ONLINE_CHAT = "ONLINE_CHAT"; + /** 状态 - 待回复 */ + public static final String STATUS_PENDING = "PENDING"; + /** 状态 - 已回复 */ + public static final String STATUS_REPLIED = "REPLIED"; + /** 状态 - 已办结 */ + public static final String STATUS_CLOSED = "CLOSED"; + /** 主键 */ + @ApiModelProperty("主键") + @TableId(value = "id",type = IdType.AUTO) + private Long id; + + /** 咨询用户ID */ + @ApiModelProperty("咨询用户ID") + private Long userId; + + /** 管理员ID */ + @ApiModelProperty("管理员ID") + private Long adminId; + + /** 浏览咨询对应的政策ID */ + @ApiModelProperty("浏览咨询对应的政策ID") + private Long policyId; + + /** 政策名称 */ + @ApiModelProperty("政策名称") + private String policyName; + + /** 咨询渠道:MESSAGE_BOARD 留言板、ONLINE_CHAT在线客服等 */ + @ApiModelProperty("咨询渠道:MESSAGE_BOARD 留言板") + private String consultChannel; + + /** 用户咨询问题 */ + @ApiModelProperty("用户咨询问题") + private String questionContent; + + /** 管理员回复内容 */ + @ApiModelProperty("管理员回复内容") + private String replyContent; + + /** 状态 PENDING待回复、REPLIED已回复、CLOSED已办结 */ + @ApiModelProperty("状态 PENDING待回复、REPLIED已回复、CLOSED已办结") + private String consultStatus; + + /** 咨询提交时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("咨询提交时间") + private Date consultTime; + + /** 回复时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("回复时间") + private Date replyTime; + + /** 用户满意度 五颗星 每颗星代表1分 */ + @ApiModelProperty("用户满意度 五颗星 每颗星代表1分") + private Integer satisfaction; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultReplyDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultReplyDTO.java new file mode 100644 index 0000000..a68461d --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultReplyDTO.java @@ -0,0 +1,20 @@ +package com.ruoyi.cms.domain.policy; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +@ApiModel("管理员回复") +public class PolicyConsultReplyDTO { + + @NotNull(message = "记录ID不能为空") + @ApiModelProperty(value = "记录ID", required = true, example = "1") + private Long recordId; + + @ApiModelProperty(value = "回复内容", required = true, example = "回复内容") + @NotNull(message = "回复内容不能为空") + private String replyContent; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java new file mode 100644 index 0000000..97bb7aa --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java @@ -0,0 +1,20 @@ +package com.ruoyi.cms.domain.policy; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +@ApiModel("政策咨询满意度评价") +public class PolicyConsultSatisfactionDTO { + + @NotNull(message = "记录ID不能为空") + @ApiModelProperty(value = "记录ID", required = true, example = "1") + private Long recordId; + + @ApiModelProperty(value = "满意度", required = true, example = "1") + @NotNull(message = "满意度不能为空") + private Integer satisfaction; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java new file mode 100644 index 0000000..8228ff4 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java @@ -0,0 +1,23 @@ +package com.ruoyi.cms.domain.policy; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import javax.validation.constraints.NotNull; + +@Data +@ApiModel("用户提交留言") +public class PolicyConsultSubmitDTO { + + @NotNull(message = "政策ID不能为空") + @ApiModelProperty(value = "政策ID", required = true, example = "1") + private Long policyId; + + @NotNull(message = "咨询问题不能为空") + @ApiModelProperty(value = "咨询问题", required = true, example = "咨询问题") + private String questionContent; + + @NotNull(message = "咨询渠道不能为空( 渠道固定传 MESSAGE_BOARD(留言板渠道))") + @ApiModelProperty(value = "咨询渠道", required = true, example = "MESSAGE_BOARD") + private String consultChannel; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java index f59534e..f7ba4ca 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java @@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; @@ -73,4 +74,33 @@ public class PolicyInfo extends BaseEntity { @ApiModelProperty("状态(0启用 1禁用)") private String status; + + @ApiModelProperty("行业大类") + private String industryBigType; + + @ApiModelProperty("政策文号") + private String docNo; + + @JsonFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty("生效日期") + private Date effectiveTime; + + @JsonFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty("失效日期") + private Date expireTime; + + @ApiModelProperty("政策背景") + private String policyBackground; + + @ApiModelProperty("目标宗旨") + private String policyPurpose; + + @ApiModelProperty("具体条款") + private String policyClause; + + @ApiModelProperty("实施细则") + private String implementRule; + + @ApiModelProperty("电子邮件") + private String email; } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/policy/PolicyConsultRecordMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/policy/PolicyConsultRecordMapper.java new file mode 100644 index 0000000..12ba7e2 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/policy/PolicyConsultRecordMapper.java @@ -0,0 +1,9 @@ +package com.ruoyi.cms.mapper.policy; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.policy.PolicyConsultRecord; + +public interface PolicyConsultRecordMapper extends BaseMapper { + + int insert(PolicyConsultRecord policyConsultRecord); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IIndustryService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IIndustryService.java index 1101b5a..41612c4 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IIndustryService.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IIndustryService.java @@ -61,4 +61,10 @@ public interface IIndustryService void importData(); + /** + * 查询第一级行业大类 + * + * @return 行业集合 + */ + public List selectFirstLevelIndustryList(); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/IndustryServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/IndustryServiceImpl.java index de60b98..c8182c5 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/IndustryServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/IndustryServiceImpl.java @@ -34,6 +34,9 @@ public class IndustryServiceImpl extends ServiceImpl im @Autowired private IndustryMapper industryMapper; + /** 行业根节点父ID */ + public static final Long INDUSTRY_ROOT_PARENT_ID = 0L; + /** * 查询行业 * @@ -234,4 +237,12 @@ public class IndustryServiceImpl extends ServiceImpl im } + @Override + public List selectFirstLevelIndustryList() { + Industry industry = new Industry(); + industry.setParentId(INDUSTRY_ROOT_PARENT_ID); + industry.setStatus("0"); + return industryMapper.selectIndustryList(industry); + } + } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java new file mode 100644 index 0000000..8c049a6 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java @@ -0,0 +1,18 @@ +package com.ruoyi.cms.service.policy; + +import com.ruoyi.cms.domain.policy.*; + +import java.util.List; + +public interface IPolicyConsultRecordService { + + int insert(PolicyConsultSubmitDTO policyConsultSubmitDTO); + + int replyConsult(PolicyConsultReplyDTO policyConsultReplyDTO); + + int submitSatisfaction(PolicyConsultSatisfactionDTO dto); + + List getUserOwnConsultList(Long policyId); + + List getAllConsultRecord(PolicyConsultQueryDTO queryDTO); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java new file mode 100644 index 0000000..0ade018 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java @@ -0,0 +1,121 @@ +package com.ruoyi.cms.service.policy.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cms.domain.policy.*; +import com.ruoyi.cms.mapper.policy.PolicyConsultRecordMapper; +import com.ruoyi.cms.mapper.policy.PolicyInfoMapper; +import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SiteSecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +import static com.ruoyi.common.utils.DateUtils.YYYY_MM_DD_HH_MM_SS; + +@Slf4j +@Service +public class PolicyConsultRecordServiceImpl extends ServiceImpl implements IPolicyConsultRecordService { + + @Autowired + private PolicyConsultRecordMapper policyConsultRecordMapper; + + @Autowired + private PolicyInfoMapper policyInfoMapper; + + @Override + public int insert(PolicyConsultSubmitDTO policyConsultSubmitDTO) { + + PolicyInfo policyInfo = policyInfoMapper.selectPolicyInfoById(policyConsultSubmitDTO.getPolicyId()); + PolicyConsultRecord record = new PolicyConsultRecord(); + record.setUserId(SiteSecurityUtils.getUserId()); + record.setPolicyId(policyConsultSubmitDTO.getPolicyId()); + record.setPolicyName(policyInfo.getZcmc()); + record.setConsultChannel(policyConsultSubmitDTO.getConsultChannel()); + record.setQuestionContent(policyConsultSubmitDTO.getQuestionContent()); + record.setConsultStatus(PolicyConsultRecord.STATUS_PENDING); + record.setConsultTime(new Date()); + return policyConsultRecordMapper.insert(record); + } + + /** + * 2.管理员回复咨询 + */ + @Override + public int replyConsult(PolicyConsultReplyDTO dto) { + PolicyConsultRecord record = policyConsultRecordMapper.selectById(dto.getRecordId()); + record.setReplyContent(dto.getReplyContent()); + record.setConsultStatus(PolicyConsultRecord.STATUS_REPLIED); + record.setReplyTime(new Date()); + record.setAdminId(SiteSecurityUtils.getUserId());//管理员id + record.setUpdateBy(SiteSecurityUtils.getUsername());//管理员名称 + record.setUpdateTime(DateUtils.dateTimeNow(YYYY_MM_DD_HH_MM_SS)); + return policyConsultRecordMapper.updateById(record); + } + + /** + * 3.用户评价满意度(存档满意度信息) + */ + @Override + public int submitSatisfaction(PolicyConsultSatisfactionDTO dto) { + PolicyConsultRecord record = policyConsultRecordMapper.selectById(dto.getRecordId()); + // 权限校验:只能评价自己的咨询记录 + if (!record.getUserId().equals(SiteSecurityUtils.getUserId())){ + throw new RuntimeException("无权操作"); + } + record.setSatisfaction(dto.getSatisfaction()); + record.setUpdateTime(DateUtils.dateTimeNow(YYYY_MM_DD_HH_MM_SS)); + return policyConsultRecordMapper.updateById(record); + } + + /** + * 4.【重要】用户查询自己的留言咨询记录 + * 需求备注:留言板自己看自己,只能查询userId匹配的数据 + */ + @Override + public List getUserOwnConsultList(Long policyId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(PolicyConsultRecord::getUserId, SiteSecurityUtils.getUserId()); + // 可选:按政策筛选 + if(policyId != null){ + wrapper.eq(PolicyConsultRecord::getPolicyId, policyId); + } + wrapper.orderByDesc(PolicyConsultRecord::getConsultTime); + return policyConsultRecordMapper.selectList(wrapper); + } + + /** + * 管理员后台:查询全部咨询存档记录(用于台账查看) + */ + @Override + public List getAllConsultRecord(PolicyConsultQueryDTO query) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 状态 + if(StringUtils.isNotEmpty(query.getConsultStatus())){ + wrapper.eq(PolicyConsultRecord::getConsultStatus, query.getConsultStatus()); + } + // 满意度 + if(query.getSatisfaction() != null){ + wrapper.eq(PolicyConsultRecord::getSatisfaction, query.getSatisfaction()); + } + // 政策 + if(StringUtils.isNotEmpty(query.getSearchValue())){ + wrapper.like(PolicyConsultRecord::getPolicyName, query.getSearchValue()); + } + // 时间区间 + if(query.getBeginConsultTime() != null){ + wrapper.ge(PolicyConsultRecord::getConsultTime, query.getBeginConsultTime()); + } + if(query.getEndConsultTime() != null){ + wrapper.le(PolicyConsultRecord::getConsultTime, query.getEndConsultTime()); + } + // 排序:最新咨询靠前 + wrapper.orderByDesc(PolicyConsultRecord::getConsultTime); + return policyConsultRecordMapper.selectList(wrapper); + } +} diff --git a/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyConsultRecordMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyConsultRecordMapper.xml new file mode 100644 index 0000000..2527093 --- /dev/null +++ b/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyConsultRecordMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, admin_id, policy_id, consult_channel, question_content, reply_content, + consult_status, consult_time, reply_time, satisfaction, create_by, update_time + from policy_consult_record + + + + insert into policy_consult_record ( + user_id, admin_id, policy_id, consult_channel, question_content, + reply_content, consult_status, consult_time, reply_time, + satisfaction, create_by, create_time, update_time, del_flag, remark + ) values ( + #{userId}, #{adminId}, #{policyId}, #{consultChannel}, #{questionContent}, + #{replyContent}, #{consultStatus}, #{consultTime}, #{replyTime}, + #{satisfaction}, #{createBy}, now(), now(), '0', #{remark} + ) + + \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyInfoMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyInfoMapper.xml index 980c371..884de50 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyInfoMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/policy/PolicyInfoMapper.xml @@ -26,17 +26,28 @@ + + + + + + + + + - select id, zcmc, zclx, zc_level, source_unit, accept_unit, publish_time, view_num, create_time, policy_tag, status,policy_category + select id, zcmc, zclx, zc_level, source_unit, accept_unit, publish_time, view_num, create_time, policy_tag, status,policy_category,industry_big_type, + doc_no,effective_time,expire_time,policy_background,policy_purpose,policy_clause,implement_rule,email from policy_info select id, zcmc, zclx, zc_level, source_unit, accept_unit, publish_time, zc_content, subsidy_standard, handle_channel, apply_condition, - file_url, file_name, view_num, create_by, create_time, update_by, update_time, remark, policy_tag, status,policy_category + file_url, file_name, view_num, create_by, create_time, update_by, update_time, remark, policy_tag, status,policy_category,industry_big_type, + doc_no,effective_time,expire_time,policy_background,policy_purpose,policy_clause,implement_rule,email from policy_info @@ -78,11 +89,13 @@ insert into policy_info ( zcmc, zclx, zc_level, source_unit, accept_unit, publish_time, zc_content, subsidy_standard, handle_channel, apply_condition, - file_url, file_name, view_num, create_by, create_time, del_flag, remark, policy_tag, status,policy_category + file_url, file_name, view_num, create_by, create_time, del_flag, remark, policy_tag, status,policy_category,industry_big_type, + doc_no,effective_time,expire_time,policy_background,policy_purpose,policy_clause,implement_rule,email ) values ( #{zcmc}, #{zclx}, #{zcLevel}, #{sourceUnit}, #{acceptUnit}, #{publishTime}, #{zcContent}, #{subsidyStandard}, #{handleChannel}, #{applyCondition}, - #{fileUrl}, #{fileName}, #{viewNum}, #{createBy}, now(), '0', #{remark}, #{policyTag}, #{status},#{policyCategory} + #{fileUrl}, #{fileName}, #{viewNum}, #{createBy}, now(), '0', #{remark}, #{policyTag}, #{status},#{policyCategory},#{industryBigType}, + #{docNo}, #{effectiveTime}, #{expireTime}, #{policyBackground}, #{policyPurpose}, #{policyClause}, #{implementRule}, #{email} ) @@ -105,6 +118,15 @@ policy_tag = #{policyTag}, status = #{status}, policy_category = #{policyCategory}, + industry_big_type = #{industryBigType}, + doc_no = #{docNo}, + effective_time = #{effectiveTime}, + expire_time = #{expireTime}, + policy_background = #{policyBackground}, + policy_purpose = #{policyPurpose}, + policy_clause = #{policyClause}, + implement_rule = #{implementRule}, + email = #{email}, update_by = #{updateBy}, update_time = now() From 4201d9c015edf1eed1e9fe4fa30de503b040613c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=88=E7=BB=8F=E8=8E=B9?= Date: Mon, 27 Jul 2026 20:13:17 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E8=BF=94=E5=9B=9E=E8=A1=8C=E4=B8=9A?= =?UTF-8?q?=E5=A4=A7=E7=B1=BB=E5=90=8D=E7=A7=B0=EF=BC=8C=E6=94=BF=E7=AD=96?= =?UTF-8?q?=E7=95=99=E8=A8=80=E5=9B=9E=E5=A4=8D=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/cms/domain/policy/PolicyInfo.java | 3 +++ .../policy/impl/PolicyConsultRecordServiceImpl.java | 5 +++-- .../service/policy/impl/PolicyInfoServiceImpl.java | 13 ++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java index f7ba4ca..8a5fd6c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyInfo.java @@ -78,6 +78,9 @@ public class PolicyInfo extends BaseEntity { @ApiModelProperty("行业大类") private String industryBigType; + @ApiModelProperty("行业大类名称") + private String industryBigTypeName; + @ApiModelProperty("政策文号") private String docNo; diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java index 0ade018..e152053 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java @@ -7,6 +7,7 @@ import com.ruoyi.cms.mapper.policy.PolicyConsultRecordMapper; import com.ruoyi.cms.mapper.policy.PolicyInfoMapper; import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SiteSecurityUtils; import com.ruoyi.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; @@ -52,8 +53,8 @@ public class PolicyConsultRecordServiceImpl extends ServiceImpl Date: Tue, 28 Jul 2026 10:19:45 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E6=89=93=E5=8D=B0=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/app/AppPolicyConsultRecordController.java | 1 + .../com/ruoyi/cms/domain/policy/PolicyConsultRecord.java | 3 +++ .../policy/impl/PolicyConsultRecordServiceImpl.java | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java index cc480ba..5777f6c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java @@ -34,6 +34,7 @@ public class AppPolicyConsultRecordController extends BaseController { @ApiOperation("用户提交留言咨询") @PostMapping("/submit") public AjaxResult save(@RequestBody PolicyConsultSubmitDTO policyConsultSubmitDTO){ + log.info("用户提交留言咨询..."); if(!SiteSecurityUtils.isLogin()){ return AjaxResult.error("用户未登录!"); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java index f1cb323..da3a7ce 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultRecord.java @@ -6,12 +6,15 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.core.domain.BaseEntity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import java.util.Date; @Data +@NoArgsConstructor @EqualsAndHashCode(callSuper = true) @ApiModel("政策咨询记录") public class PolicyConsultRecord extends BaseEntity { diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java index e152053..0ae46c2 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java @@ -11,6 +11,8 @@ import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SiteSecurityUtils; import com.ruoyi.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -29,10 +31,15 @@ public class PolicyConsultRecordServiceImpl extends ServiceImpl Date: Tue, 28 Jul 2026 10:56:35 +0800 Subject: [PATCH 04/14] =?UTF-8?q?pc=E7=AB=AF=E5=92=8C=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E7=AB=AF=E7=94=A8=E6=88=B7id=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/AppPolicyConsultRecordController.java | 17 ++++- .../cms/CmsPolicyConsultRecordController.java | 66 +++++++++++++++++-- .../policy/PolicyConsultSatisfactionDTO.java | 3 + .../domain/policy/PolicyConsultSubmitDTO.java | 3 + .../policy/IPolicyConsultRecordService.java | 2 +- .../impl/PolicyConsultRecordServiceImpl.java | 8 +-- 6 files changed, 88 insertions(+), 11 deletions(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java index 5777f6c..c08a33a 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppPolicyConsultRecordController.java @@ -34,10 +34,14 @@ public class AppPolicyConsultRecordController extends BaseController { @ApiOperation("用户提交留言咨询") @PostMapping("/submit") public AjaxResult save(@RequestBody PolicyConsultSubmitDTO policyConsultSubmitDTO){ - log.info("用户提交留言咨询..."); + log.info("用户提交留言咨询...小程序端"); if(!SiteSecurityUtils.isLogin()){ return AjaxResult.error("用户未登录!"); } + if(SiteSecurityUtils.getUserId() == null){ + return AjaxResult.error("获取用户ID失败!"); + } + policyConsultSubmitDTO.setUserId(SiteSecurityUtils.getUserId()); return toAjax(policyConsultRecordService.insert(policyConsultSubmitDTO)); } @@ -48,7 +52,11 @@ public class AppPolicyConsultRecordController extends BaseController { @GetMapping("/my/list/{policyId}") public TableDataInfo getMyConsult(@ApiParam("政策ID") @PathVariable Long policyId){ startPage(); - List list = policyConsultRecordService.getUserOwnConsultList(policyId); + if(SiteSecurityUtils.getUserId() == null){ + throw new RuntimeException("用户ID不能为空"); + } + Long userId = SiteSecurityUtils.getUserId(); + List list = policyConsultRecordService.getUserOwnConsultList(policyId,userId); return getDataTable(list); } @@ -61,6 +69,11 @@ public class AppPolicyConsultRecordController extends BaseController { if(!SiteSecurityUtils.isLogin()){ return AjaxResult.error("用户未登录!"); } + + if(SiteSecurityUtils.getUserId() == null){ + return AjaxResult.error("获取用户ID失败!"); + } + dto.setUserId(SiteSecurityUtils.getUserId()); return toAjax(policyConsultRecordService.submitSatisfaction(dto)); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java index febb7a0..ca94065 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java @@ -1,20 +1,25 @@ package com.ruoyi.cms.controller.cms; -import com.ruoyi.cms.domain.policy.PolicyConsultQueryDTO; -import com.ruoyi.cms.domain.policy.PolicyConsultRecord; -import com.ruoyi.cms.domain.policy.PolicyConsultReplyDTO; -import com.ruoyi.cms.domain.policy.PolicyInfoQuery; +import com.ruoyi.cms.domain.policy.*; import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; +import com.ruoyi.cms.service.policy.impl.PolicyConsultRecordServiceImpl; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.SiteSecurityUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; +import static jdk.nashorn.internal.runtime.regexp.joni.Config.log; + @RestController @RequestMapping("/cms/policyConsultRecord") @Api(tags = "CMS:政策咨询记录管理") @@ -23,6 +28,59 @@ public class CmsPolicyConsultRecordController extends BaseController { @Autowired private IPolicyConsultRecordService policyConsultRecordService; + private static final Logger log = LoggerFactory.getLogger(CmsPolicyConsultRecordController.class); + + + /** + * 保存用户提交留言咨询 + */ + @ApiOperation("用户提交留言咨询") + @PostMapping("/submit") + public AjaxResult save(@RequestBody PolicyConsultSubmitDTO policyConsultSubmitDTO){ + log.info("用户提交留言咨询...pc端"); + if(!SecurityUtils.isLogin()){ + return AjaxResult.error("用户未登录!"); + } + if(SecurityUtils.getUserId() == null){ + return AjaxResult.error("获取用户ID失败!"); + } + policyConsultSubmitDTO.setUserId(SecurityUtils.getUserId()); + return toAjax(policyConsultRecordService.insert(policyConsultSubmitDTO)); + } + + /** + * 用户查看【自己】的留言咨询记录(留言板只能看自己) + */ + @ApiOperation("用户查看【自己】的留言咨询记录(留言板只能看自己)") + @GetMapping("/my/list/{policyId}") + public TableDataInfo getMyConsult(@ApiParam("政策ID") @PathVariable Long policyId){ + startPage(); + if(SecurityUtils.getUserId() == null){ + throw new RuntimeException("用户ID不能为空"); + } + Long userId = SecurityUtils.getUserId(); + List list = policyConsultRecordService.getUserOwnConsultList(policyId,userId); + return getDataTable(list); + } + + /** + * 用户对咨询进行满意度评价 + */ + @ApiOperation("用户对咨询进行满意度评价") + @PostMapping("/satisfaction") + public AjaxResult satisfaction(@RequestBody PolicyConsultSatisfactionDTO dto){ + if(!SecurityUtils.isLogin()){ + return AjaxResult.error("用户未登录!"); + } + + if(SecurityUtils.getUserId() == null){ + return AjaxResult.error("获取用户ID失败!"); + } + dto.setUserId(SecurityUtils.getUserId()); + return toAjax(policyConsultRecordService.submitSatisfaction(dto)); + } + + // ========== 管理员接口 ========== /** diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java index 97bb7aa..49dd81c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSatisfactionDTO.java @@ -17,4 +17,7 @@ public class PolicyConsultSatisfactionDTO { @ApiModelProperty(value = "满意度", required = true, example = "1") @NotNull(message = "满意度不能为空") private Integer satisfaction; + + @ApiModelProperty(value = "用户ID") + private Long userId; } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java index 8228ff4..1474628 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/policy/PolicyConsultSubmitDTO.java @@ -20,4 +20,7 @@ public class PolicyConsultSubmitDTO { @NotNull(message = "咨询渠道不能为空( 渠道固定传 MESSAGE_BOARD(留言板渠道))") @ApiModelProperty(value = "咨询渠道", required = true, example = "MESSAGE_BOARD") private String consultChannel; + + @ApiModelProperty(value = "咨询用户ID") + private Long userId; } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java index 8c049a6..cb3cc65 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/IPolicyConsultRecordService.java @@ -12,7 +12,7 @@ public interface IPolicyConsultRecordService { int submitSatisfaction(PolicyConsultSatisfactionDTO dto); - List getUserOwnConsultList(Long policyId); + List getUserOwnConsultList(Long policyId, Long userId); List getAllConsultRecord(PolicyConsultQueryDTO queryDTO); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java index 0ae46c2..d51ee9c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java @@ -41,7 +41,7 @@ public class PolicyConsultRecordServiceImpl extends ServiceImpl getUserOwnConsultList(Long policyId) { + public List getUserOwnConsultList(Long policyId, Long userId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(PolicyConsultRecord::getUserId, SiteSecurityUtils.getUserId()); + wrapper.eq(PolicyConsultRecord::getUserId, userId); // 可选:按政策筛选 if(policyId != null){ wrapper.eq(PolicyConsultRecord::getPolicyId, policyId); From 7cc52d896cc0b1a150449ae622a430e567554816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=88=E7=BB=8F=E8=8E=B9?= Date: Tue, 28 Jul 2026 13:33:44 +0800 Subject: [PATCH 05/14] =?UTF-8?q?pc=E7=AB=AF=E6=94=B9=E7=94=A8AppuserId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/CmsPolicyConsultRecordController.java | 18 +++++++----------- .../impl/PolicyConsultRecordServiceImpl.java | 1 - 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java index ca94065..75faedb 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java @@ -2,12 +2,10 @@ package com.ruoyi.cms.controller.cms; import com.ruoyi.cms.domain.policy.*; import com.ruoyi.cms.service.policy.IPolicyConsultRecordService; -import com.ruoyi.cms.service.policy.impl.PolicyConsultRecordServiceImpl; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.SecurityUtils; -import com.ruoyi.common.utils.SiteSecurityUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -18,10 +16,8 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -import static jdk.nashorn.internal.runtime.regexp.joni.Config.log; - @RestController -@RequestMapping("/cms/policyConsultRecord") +@RequestMapping("/d/policyConsultRecord") @Api(tags = "CMS:政策咨询记录管理") public class CmsPolicyConsultRecordController extends BaseController { @@ -41,10 +37,10 @@ public class CmsPolicyConsultRecordController extends BaseController { if(!SecurityUtils.isLogin()){ return AjaxResult.error("用户未登录!"); } - if(SecurityUtils.getUserId() == null){ + if(SecurityUtils.getAppuserId() == null){ return AjaxResult.error("获取用户ID失败!"); } - policyConsultSubmitDTO.setUserId(SecurityUtils.getUserId()); + policyConsultSubmitDTO.setUserId(SecurityUtils.getAppuserId()); return toAjax(policyConsultRecordService.insert(policyConsultSubmitDTO)); } @@ -55,10 +51,10 @@ public class CmsPolicyConsultRecordController extends BaseController { @GetMapping("/my/list/{policyId}") public TableDataInfo getMyConsult(@ApiParam("政策ID") @PathVariable Long policyId){ startPage(); - if(SecurityUtils.getUserId() == null){ + if(SecurityUtils.getAppuserId() == null){ throw new RuntimeException("用户ID不能为空"); } - Long userId = SecurityUtils.getUserId(); + Long userId = SecurityUtils.getAppuserId(); List list = policyConsultRecordService.getUserOwnConsultList(policyId,userId); return getDataTable(list); } @@ -73,10 +69,10 @@ public class CmsPolicyConsultRecordController extends BaseController { return AjaxResult.error("用户未登录!"); } - if(SecurityUtils.getUserId() == null){ + if(SecurityUtils.getAppuserId() == null){ return AjaxResult.error("获取用户ID失败!"); } - dto.setUserId(SecurityUtils.getUserId()); + dto.setUserId(SecurityUtils.getAppuserId()); return toAjax(policyConsultRecordService.submitSatisfaction(dto)); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java index d51ee9c..efe6622 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/policy/impl/PolicyConsultRecordServiceImpl.java @@ -21,7 +21,6 @@ import java.util.List; import static com.ruoyi.common.utils.DateUtils.YYYY_MM_DD_HH_MM_SS; -@Slf4j @Service public class PolicyConsultRecordServiceImpl extends ServiceImpl implements IPolicyConsultRecordService { From 487dc33049e79d1953ef9a0bc5f557111961ee41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=88=E7=BB=8F=E8=8E=B9?= Date: Tue, 28 Jul 2026 13:42:17 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E6=94=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/controller/cms/CmsPolicyConsultRecordController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java index 75faedb..ed35229 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyConsultRecordController.java @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; @RestController -@RequestMapping("/d/policyConsultRecord") +@RequestMapping("/cms/policyConsultRecord") @Api(tags = "CMS:政策咨询记录管理") public class CmsPolicyConsultRecordController extends BaseController { From 5303f8a8ed673d8dc8ca3d67f34192feb2914d74 Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Wed, 29 Jul 2026 11:49:53 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E5=8D=95=E7=82=B9=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E6=B7=BB=E5=8A=A0=E7=9C=81=E3=80=81=E5=B8=82?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/common/core/domain/entity/Company.java | 6 ++++++ .../java/com/ruoyi/framework/web/service/SsoService.java | 2 ++ 2 files changed, 8 insertions(+) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Company.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Company.java index 192a017..2d995ae 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Company.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Company.java @@ -143,6 +143,12 @@ public class Company extends BaseEntity @ApiModelProperty("是否人力资源机构(Y是,N否)") private String isHrs; + @ApiModelProperty("省") + private String companyProvince; + + @ApiModelProperty("市") + private String companyCity; + /** * 岗位列表 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SsoService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SsoService.java index cd5fb9b..22f37b7 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SsoService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SsoService.java @@ -300,6 +300,8 @@ public class SsoService { company.setNature(object.getString("entType")); company.setDescription(object.getString("entIntro")); company.setIsHrs(object.getString("isServiceProvider"));//是否是人力资源机构 + company.setCompanyProvince(object.getString("entProvince")); + company.setCompanyCity(object.getString("entCity")); // company.setScale(object.getString("entSize")); //添加联系人 CompanyContact contact=new CompanyContact(); From 9b7fdafcd1e267cbe79087ffada4c2d7d38083a8 Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Wed, 29 Jul 2026 11:57:54 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BD=91=E9=A1=B5pc?= =?UTF-8?q?=E7=AB=AF=E5=B2=97=E4=BD=8D=E8=AF=A6=E6=83=85=E6=B5=8F=E8=A7=88?= =?UTF-8?q?+1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java index a30f334..0fad330 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java @@ -297,6 +297,8 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe .collect(Collectors.toList()); job.setFilesList(processedFiles); + //浏览量默认添加+1 + this.view(jobId); return job; } From 45289d5666c851f2db1777019060bb17a44eadfc Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Wed, 29 Jul 2026 12:12:19 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E5=A4=84=E7=90=86=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E7=BB=8F=E5=8E=86=E6=97=A5=E6=9C=9F=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/util/remommend/JobRecommendUtil.java | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/remommend/JobRecommendUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/remommend/JobRecommendUtil.java index 60916a7..58f699c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/remommend/JobRecommendUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/remommend/JobRecommendUtil.java @@ -37,6 +37,22 @@ public class JobRecommendUtil { public static final Float EXP_MATCH_LOW1 = 2F; public static final Float EXP_MATCH_LOWER = 1F; + private static final Map CHINA_NUM_MAP; + + static { + CHINA_NUM_MAP = new HashMap<>(); + CHINA_NUM_MAP.put('零', '0'); + CHINA_NUM_MAP.put('一', '1'); + CHINA_NUM_MAP.put('二', '2'); + CHINA_NUM_MAP.put('三', '3'); + CHINA_NUM_MAP.put('四', '4'); + CHINA_NUM_MAP.put('五', '5'); + CHINA_NUM_MAP.put('六', '6'); + CHINA_NUM_MAP.put('七', '7'); + CHINA_NUM_MAP.put('八', '8'); + CHINA_NUM_MAP.put('九', '9'); + } + // =====================基础计算方法===================== /** @@ -48,17 +64,62 @@ public class JobRecommendUtil { if (StrUtil.isBlank(startDateStr)) { return 0; } - LocalDate start = LocalDate.parse(startDateStr + "-01"); + LocalDate start = parseToFirstDayOfMonth(startDateStr); LocalDate end; if (StrUtil.isBlank(endDateStr)) { end = LocalDate.now(); } else { - end = LocalDate.parse(endDateStr + "-01"); + end = parseToFirstDayOfMonth(endDateStr); } Period period = Period.between(start, end); return period.getYears() * 12 + period.getMonths(); } + /** + * 兼容两种格式:yyyy-MM / yyyy-MM-dd,返回当月1号 + */ + private static LocalDate parseToFirstDayOfMonth(String dateStr) { + try { + String source = dateStr.trim(); + //中文年份转阿拉伯数字:二零一六 → 2016 + source = convertChinaYearToNum(source); + + LocalDate date; + if (source.matches("^\\d{4}$")) { + // 仅年份 2016 → 2016-01-01 + date = LocalDate.parse(source + "-01-01"); + } else if (source.matches("^\\d{4}-\\d{2}$")) { + // yyyy-MM + date = LocalDate.parse(source + "-01"); + } else if (source.matches("^\\d{4}-\\d{2}-\\d{2}$")) { + // yyyy-MM-dd + date = LocalDate.parse(source); + } else { + throw new IllegalArgumentException("无法识别日期格式:" + dateStr); + } + return date.withDayOfMonth(1); + } catch (Exception e) { + throw new IllegalArgumentException("日期解析失败,原始值:" + dateStr + + ",支持格式:2016、2016-09、2016-09-09、二零一六", e); + } + } + + /** + * 中文年份字符串转数字年份 + * 例:二零一六 → 2016;二零二五-03 保留后面横杠 + */ + private static String convertChinaYearToNum(String str) { + StringBuilder sb = new StringBuilder(); + for (char c : str.toCharArray()) { + if (CHINA_NUM_MAP.containsKey(c)) { + sb.append(CHINA_NUM_MAP.get(c)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + /** * 计算全部工作经历总月数(简单累加,不处理时间重叠) */ From 3d06b57cf816d7ee5aaf17b2499f02edfcf6d753 Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Wed, 29 Jul 2026 18:35:13 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=B2=97=E4=BD=8D?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/cms/controller/app/AppJobController.java | 1 + .../java/com/ruoyi/cms/controller/cms/CmsJobController.java | 1 + ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java | 2 +- .../main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppJobController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppJobController.java index 4696025..09ed159 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppJobController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppJobController.java @@ -240,6 +240,7 @@ public class AppJobController extends BaseController String errorMsg = "描述中包含敏感词:" + String.join("、", sensitiveWords); return AjaxResult.error(errorMsg, sensitiveWords); } + job.setDataSource("1");//系统自录 jobService.publishJob(job); return success(); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java index b99c454..0f1b934 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java @@ -147,6 +147,7 @@ public class CmsJobController extends BaseController return AjaxResult.error(errorMsg, sensitiveWords); } job.setJobStatus("0"); + job.setDataSource("1");//系统自录 // 无敏感词,执行插入 return toAjax(jobService.insertJob(job)); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java index a8b2fd3..97b6bd0 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java @@ -141,7 +141,7 @@ public class Job extends BaseEntity @ApiModelProperty("是否发布 0未发布 1发布") private Integer isPublish; - @ApiModelProperty("数据来源 5本地石河子模板导入") + @ApiModelProperty("数据来源 5本地石河子模板导入;本地录入1;互联网岗位上传2;") private String dataSource; @ApiModelProperty("岗位链接") diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java index 0fad330..b461d45 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java @@ -1011,6 +1011,7 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe job.setCreateBy(string); job.setCreateTime(formattedDate); job.setRowId(Long.valueOf(rowWork.getId())); + job.setDataSource("2");//互联网岗位上传 jobBatch.add(job); From 0e675a1004a77254e36810ab9169df39401e7406 Mon Sep 17 00:00:00 2001 From: francis-fh Date: Wed, 29 Jul 2026 19:31:57 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E5=BF=BD=E7=95=A5=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- .../src/main/resources/application-local.yml | 158 -- .../cms/service/impl/ESJobSearchImpl.java | 1318 ----------------- .../impl/JobTitleSuggestServiceImpl.java | 187 --- 4 files changed, 4 insertions(+), 1664 deletions(-) delete mode 100644 ruoyi-admin/src/main/resources/application-local.yml delete mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java delete mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java diff --git a/.gitignore b/.gitignore index a177b4d..ebdeb04 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,7 @@ nbdist/ local.sh .claude -/test-reports/ \ No newline at end of file +/test-reports/ +ruoyi-admin/src/main/resources/application-local.yml +ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java +ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml deleted file mode 100644 index 6fa3ae7..0000000 --- a/ruoyi-admin/src/main/resources/application-local.yml +++ /dev/null @@ -1,158 +0,0 @@ -# 项目相关配置 -ruoyi: - # 名称 - name: RuoYi - # 版本 - version: 3.8.8 - # 版权年份 - copyrightYear: 2024 - # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) - profile: /Users/chenyanchang/logs/ruoyi/uploadPath - # 获取ip地址开关 - addressEnabled: false - # 验证码类型 math 数字计算 char 字符验证 - captchaType: math - -# 数据源配置 -spring: - datasource: - type: com.alibaba.druid.pool.DruidDataSource - driverClassName: com.highgo.jdbc.Driver - druid: - # 主库数据源 - # master: - # url: jdbc:highgo://124.243.245.42:5866/shz?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified - # #username: syssso - # username: sysdba - # password: ZKR2024@comzkr - # 正式环境 - # master: - # url: jdbc:highgo://39.98.44.136:6023/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified - # username: sysdba - # password: Hello@2026 - # 测试环境 - master: - url: jdbc:highgo://47.111.103.66:5866/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified - username: sysdba - password: Hello@2026 - # 从库数据源 - slave: - # 从数据源开关/默认关闭 - enabled: false - url: - username: - password: - # 初始连接数 - initialSize: 10 - # 最小连接池数量 - minIdle: 30 - # 最大连接池数量 - maxActive: 50 - # 配置获取连接等待超时的时间 - maxWait: 60000 - # 配置连接超时时间 - connectTimeout: 30000 - # 配置网络超时时间 - socketTimeout: 60000 - # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 - timeBetweenEvictionRunsMillis: 60000 - # 配置一个连接在池中最小生存的时间,单位是毫秒 - minEvictableIdleTimeMillis: 300000 - # 配置一个连接在池中最大生存的时间,单位是毫秒 - maxEvictableIdleTimeMillis: 900000 - # 配置检测连接是否有效 - validationQuery: SELECT version() - testWhileIdle: true - testOnBorrow: false - testOnReturn: false - webStatFilter: - enabled: true - statViewServlet: - enabled: true - # 设置白名单,不填则允许所有访问 - allow: - url-pattern: /druid/* - # 控制台管理用户名和密码 - login-username: ruoyi - login-password: 123456 - filter: - stat: - enabled: true - # 慢SQL记录 - log-slow-sql: true - slow-sql-millis: 1000 - merge-sql: true - wall: - config: - multi-statement-allow: true - redis: - # 本地 - #host: 127.0.0.1 - #port: 6379 - host: 124.243.245.42 - port: 5379 - # 数据库索引 - database: 0 - # 密码(本地通常无密码) - password: - # 连接超时时间 - timeout: 100s - lettuce: - pool: - # 连接池中的最小空闲连接 - min-idle: 0 - # 连接池中的最大空闲连接 - max-idle: 8 - # 连接池的最大数据库连接数 - max-active: 8 - # #连接池最大阻塞等待时间(使用负值表示没有限制) - max-wait: -1ms - -# easy-es -easy-es: - enable: true - banner: false - address: 124.243.245.42:9200 - global-config: - process-index-mode: manual - db-config: - refresh-policy: immediate - username: elastic - password: zkr2024@@.com - -#ai -chat: - baseUrl: http://39.98.44.136:8082 - chatUrl: /v1/chat/completions - chatDetailUrl: /core/chat/getPaginationRecords - chatHistoryUrl: /core/chat/getHistories - updateNameUrl: /core/chat/updateHistory - stickChatUrl: /core/chat/updateHistory - delChatUrl: /core/chat/delHistory - delAllChatUrl: /core/chat/clearHistories - guestUrl: /v1/chat/completions - praiseUrl: /core/chat/feedback/updateUserFeedback - appId: 67cd49095e947ae0ca7fadd8 - apiKey: fastgpt-qMl63276wPZvKAxEkW77bur0sSJpmuC6Ngg9lzyEjufLhsBAurjT55j - model: qd-job-turbo - -audioText: - asr: http://127.0.0.1:8000/asr/file - tts: http://127.0.0.1:19527/synthesize - -#浪潮单点登录相关 -#互联网端: -lc_web_auth: - appId: cloud-out-2fb6330e9c0843e1a1424efda5d604c0 - appSecret: x14lueHbtLQL7Pz2G7gE4wcGCV6TDblO5xfeu9V2wGk= - getTokenUrl: http://218.31.252.15:9081/prod-psout-api/auth/token - getUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/system/app/authorize/user/info - editUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/auth/user/edit -#监管端: -lc_cms_auth: - appId: cloud-9793ee8a8c3d47b8871007ffc4128502 - appSecret: Yi+NACK70UPg8rFvsnnfBUq1wcLD4nm6ilC4II/4C4k= - getTokenUrl: http://218.31.252.15:9081/prod-api/auth/token - getUserInfoUrl: http://218.31.252.15:9081/prod-api/system/app/authorize/user/info - resrtPwdUrl: http://218.31.252.15:9081/prod-api/ps/user/resetPwd/zkr - changeStatusUrl: http://218.31.252.15:9081/prod-api/ps/user/change/status/zkr diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java deleted file mode 100644 index 037ca8c..0000000 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java +++ /dev/null @@ -1,1318 +0,0 @@ -package com.ruoyi.cms.service.impl; - -import com.alibaba.fastjson.JSON; -import com.ruoyi.cms.domain.vo.CompanyVo; -import com.ruoyi.cms.mapper.*; -import com.ruoyi.cms.service.ICompanyService; -import com.ruoyi.cms.util.remommend.JobRecommendUtil; -import com.ruoyi.common.core.domain.entity.AppUser; -import com.ruoyi.cms.domain.ESJobDocument; -import com.ruoyi.cms.domain.Job; -import com.ruoyi.cms.domain.query.ESJobSearch; -import com.ruoyi.cms.mapper.es.EsJobDocumentMapper; -import com.ruoyi.cms.service.IESJobSearchService; -import com.ruoyi.common.core.domain.entity.JobTitle; -import com.ruoyi.cms.util.ListUtil; -import com.ruoyi.cms.util.StringUtil; -import com.ruoyi.common.core.domain.entity.Company; -import com.ruoyi.common.core.domain.entity.UserWorkExperiences; -import com.ruoyi.common.core.redis.RedisCache; -import com.ruoyi.common.core.text.Convert; -import com.ruoyi.common.utils.DateUtils; -import com.ruoyi.common.utils.SiteSecurityUtils; -import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.bean.BeanUtils; -import org.dromara.easyes.core.biz.EsPageInfo; -import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper; -import org.dromara.easyes.core.conditions.update.LambdaEsUpdateWrapper; -import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.unit.DistanceUnit; -import org.elasticsearch.search.sort.SortBuilders; -import org.elasticsearch.search.sort.SortOrder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * 实体模板Service业务层处理 - * - * @author ruoyi - */ -@Service -public class ESJobSearchImpl implements IESJobSearchService -{ - @Autowired - private EsJobDocumentMapper esJobDocumentMapper; - - @Autowired - private JobMapper jobMapper; - - @Autowired - private AppUserServiceImpl appUserService; - @Autowired - private ICompanyService iCompanyService; - @Autowired - private RedisCache redisCache; - @Autowired - private AppUserBlockCompanyMapper appUserBlockCompanyMapper; - @Autowired - private JobApplyMapper jobApplyMapper; - @Autowired - private JobCollectionMapper jobCollectionMapper; - @Autowired - private UserWorkExperiencesMapper userWorkExperiencesMapper; - - // 锁的key(唯一标识ES索引初始化) - private static final String ES_INIT_LOCK_KEY = "es:job_document:init:lock"; - // 锁过期时间(30分钟,确保初始化完成) - private static final Integer LOCK_EXPIRE_SECONDS = 1800; - // 等待锁时间(5分钟,避免无限等待) - private static final Integer WAIT_LOCK_SECONDS = 300; - - - @Autowired - private BussinessDictDataServiceImpl bussinessDictDataServicel; - @Autowired - private JobTitleMapper jobTitleMapper; - Logger logger = LoggerFactory.getLogger(JobServiceImpl.class); - @Autowired - private CompanyMapper companyMapper; - - /** - * 项目启动时,初始化索引及数据 - */ - @PostConstruct - public void init() - { - boolean isLockAcquired = false; - try { - isLockAcquired = acquireDistributedLock(); - if (isLockAcquired) { - resetTextCache(); - } else { - logger.info("其他节点正在初始化ES索引,直接复用,无需重复执行"); - } - } catch (InterruptedException e) { - logger.error("ES索引初始化等待锁异常", e); - Thread.currentThread().interrupt(); - } finally { - if (isLockAcquired) { - releaseDistributedLock(); - } - } - } - - /** - * 基于自定义RedisCache实现分布式锁(无getRedisTemplate适配版) - */ - private boolean acquireDistributedLock() throws InterruptedException { - long start = System.currentTimeMillis(); - while (System.currentTimeMillis() - start < WAIT_LOCK_SECONDS * 1000) { - boolean success = redisCache.setIfAbsent(ES_INIT_LOCK_KEY,"es_init_locked", - LOCK_EXPIRE_SECONDS,TimeUnit.SECONDS - ); - if (success) { - logger.info("成功获取ES初始化分布式锁,key:{}", ES_INIT_LOCK_KEY); - return true; - } - TimeUnit.MILLISECONDS.sleep(500); - } - logger.warn("等待{}秒未获取到ES初始化锁,放弃执行", WAIT_LOCK_SECONDS); - return false; - } - - /** - * 释放分布式锁(用自定义RedisCache的deleteObject方法) - */ - private void releaseDistributedLock() { - try { - redisCache.deleteObject(ES_INIT_LOCK_KEY); - logger.info("已释放ES初始化分布式锁,key:{}", ES_INIT_LOCK_KEY); - } catch (Exception e) { - logger.error("释放ES初始化锁异常", e); - } - } - - @Override - public void checkEsAndFix() { - try { - esJobDocumentMapper.getIndex(); - }catch (Exception e){ - logger.warn("es索引丢失,正在重新刷新"); - this.init(); - } - } - - /** - * 重置全文检索数据 - */ - //todo 定时任务 - @Override - public void resetTextCache() { - logger.info("正在重新刷新es"); - // 删除并重新创建索引 - /*esJobDocumentMapper.deleteIndex("job_document"); - esJobDocumentMapper.createIndex();*/ - if (esJobDocumentMapper.existsIndex("job_document")) { - esJobDocumentMapper.deleteIndex("job_document"); - logger.info("已删除原有job_document索引"); - try { - TimeUnit.MILLISECONDS.sleep(500); - } catch (InterruptedException e) { - logger.error("删除索引后休眠异常", e); - } - } - - if (!esJobDocumentMapper.existsIndex("job_document")) { - esJobDocumentMapper.createIndex(); - logger.info("已创建job_document索引"); - } else { - logger.info("索引已被其他节点创建,直接复用"); - } - - // 分批次处理数据 - int batchSize = 1000; // 每批次处理的数据量 - int offset = 0; // 起始偏移量 - boolean hasMoreData = true; // 是否还有更多数据 - - while (hasMoreData) { - // 分页查询数据 - Map params = new HashMap<>(); - params.put("batchSize", batchSize); - params.put("offset", offset*batchSize); - List jobList = jobMapper.selectAllJob(params); - - if (jobList.isEmpty()) { - hasMoreData = false; // 没有更多数据,退出循环 - } else { - // 转换数据并插入到 Elasticsearch - List esJobDocumentList = new ArrayList<>(); - for (Job job : jobList) { - ESJobDocument esJobDocument = new ESJobDocument(); - BeanUtils.copyBeanProp(esJobDocument, job); - esJobDocument.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); - //类型转换导致赋值问题,重新赋值 - esJobDocument.setScale(org.apache.commons.lang3.StringUtils.isNotEmpty(job.getScale()) ? Integer.parseInt(job.getScale()) : 0); - //企业信息 - CompanyVo vo=job.getCompanyVo(); - esJobDocument.setCompanyVoJson(JSON.toJSONString(vo)); - //岗位联系人 - esJobDocument.setJobContactVoJson(JSON.toJSONString(job.getJobContactList())); - esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ job.getJobId()); - if(!StringUtil.isEmptyOrNull(job.getScale())){ - esJobDocument.setScale(Integer.valueOf(job.getScale())); - }else { - esJobDocument.setScale(8); - } - if(!StringUtil.isEmptyOrNull(job.getExperience())){ - esJobDocument.setExperience_int(Integer.valueOf(job.getExperience())); - }else { - esJobDocument.setExperience("0"); - esJobDocument.setEducation_int(0); - } - if(!StringUtil.isEmptyOrNull(job.getEducation())){ - esJobDocument.setEducation_int(Integer.valueOf(job.getEducation())); - }else { - esJobDocument.setEducation("-1"); - esJobDocument.setExperience_int(-1); - } - if (esJobDocument.getLatitude() != null) { - esJobDocument.setLatAndLon(esJobDocument.getLatitude().toString() + "," + esJobDocument.getLongitude().toString()); - } - if (StringUtils.isNotEmpty(job.getPostingDate())) { - Date date = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS,job.getPostingDate()); - esJobDocument.setPostingDate(date); - } - esJobDocumentList.add(esJobDocument); - } - - // 批量插入当前批次的数据 - esJobDocumentMapper.insertBatch(esJobDocumentList); - - // 更新偏移量 - offset = offset+1; - } - } - } - - @Override - public List selectTextListExceptJobId(ESJobSearch esJobSearch, List jobIds) { - - ESJobSearch newSearch = new ESJobSearch(); - BeanUtils.copyProperties(esJobSearch,newSearch); - boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType()); - //查询 - if(SiteSecurityUtils.isLogin()){ - AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - if (!StringUtil.isEmptyOrNull(esJobSearch.getCode())) { - newSearch.setCode(esJobSearch.getCode()); - } - //求职者 - if (!isCompanyUser) { - setJobSeekerParams(appUser, esJobSearch, newSearch,jobIds); - } - } - - LambdaEsQueryWrapper wrapper = getWrapper(newSearch,jobIds); - - //todo 暂时 - if (isCompanyUser) { - int current = esJobSearch.getCurrent() == null ? 0 : esJobSearch.getCurrent(); - current = Math.max(current, 0); // 非负校验 - int pageSize = esJobSearch.getPageSize() == null ? 10 : esJobSearch.getPageSize(); - pageSize = Math.min(pageSize, 50); // 限制最大条数 - int from = current * pageSize; - wrapper.orderByAsc(ESJobDocument::getId); - wrapper.limit(from, pageSize); - }else{ - wrapper.limit(esJobSearch.getPageSize()); - } - - List esJobDocuments = esJobDocumentMapper.selectList(wrapper); - - if (!isCompanyUser &&esJobDocuments.size() < esJobSearch.getPageSize()) { - // 定义要逐步放宽的搜索条件字段(不放松核心文本搜索条件) - List relaxConditions = new ArrayList<>(); - relaxConditions.add(() -> newSearch.setArea(null)); - relaxConditions.add(() -> newSearch.setExperience(null)); - relaxConditions.add(() -> newSearch.setMaxSalary(null)); - relaxConditions.add(() -> newSearch.setMinSalary(null)); - relaxConditions.add(() -> newSearch.setEducation(null)); - // 保存所有查询到的文档 - List allDocuments = new ArrayList<>(esJobDocuments); - - // 记录已经放宽的条件数量 - int relaxedConditions = 0; - - // 继续放宽条件直到满足分页或所有条件都已放宽 - while (allDocuments.size() < esJobSearch.getPageSize() - && relaxedConditions < relaxConditions.size()) { - // 放宽下一个条件 - relaxConditions.get(relaxedConditions).run(); - relaxedConditions++; - - // 查询新增的文档(不包含之前已经查询到的) - wrapper = getWrapper(newSearch, jobIds); - wrapper.limit(esJobSearch.getPageSize() - allDocuments.size()); - if (!allDocuments.isEmpty()) { - // 排除已经查询到的文档ID - Set existingIds = allDocuments.stream() - .map(ESJobDocument::getId) - .collect(Collectors.toSet()); - wrapper.not().in(ESJobDocument::getId, existingIds); - } - - List newDocuments = esJobDocumentMapper.selectList(wrapper); - allDocuments.addAll(newDocuments); - } - - // 如果总数超过分页大小,截取所需数量 - if (allDocuments.size() > esJobSearch.getPageSize()) { - esJobDocuments = allDocuments.subList(0, esJobSearch.getPageSize()); - } else { - esJobDocuments = allDocuments; - } - } - - return esJobDocuments; - } - - /** - * 拼装参数 - * @param appUser - * @param esJobSearch - * @param newSearch - */ - private void setJobSeekerParams(AppUser appUser, ESJobSearch esJobSearch, ESJobSearch newSearch,List jobIds) { - if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ - List jobTitle = appUser.getJobTitle(); - newSearch.setJobTitle(String.join(",", jobTitle)); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ - newSearch.setJobTitle(esJobSearch.getJobTitle()); - } - if(!StringUtil.isEmptyOrNull(appUser.getEducation())){ - newSearch.setEducation(appUser.getEducation()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ - newSearch.setEducation(esJobSearch.getEducation()); - } - - if(!StringUtil.isEmptyOrNull(appUser.getArea())){ - newSearch.setArea(appUser.getArea()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ - newSearch.setArea(esJobSearch.getArea()); - } - //影响后面按照经验打分,先注释 -// if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){ -// newSearch.setExperience(appUser.getWorkExperience()); -// } - if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ - newSearch.setExperience(esJobSearch.getExperience()); - } - if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){ - newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax())); - } - if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){ - newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin())); - } - // 请求参数覆盖用户资料中的薪资偏好 - if(Objects.nonNull(esJobSearch.getSalaryMin()) || Objects.nonNull(esJobSearch.getMinSalary())){ - newSearch.setMinSalary(Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary()); - } - if(Objects.nonNull(esJobSearch.getSalaryMax()) || Objects.nonNull(esJobSearch.getMaxSalary())){ - newSearch.setMaxSalary(Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ - newSearch.setJobType(esJobSearch.getJobType()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ - newSearch.setType(esJobSearch.getType()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ - newSearch.setDescription(esJobSearch.getDescription()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ - newSearch.setCompanyNature(esJobSearch.getCompanyNature()); - } - //屏蔽企业集合 - List jobs=appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); - if (jobs != null) { - jobIds.addAll(jobs.stream().distinct().collect(Collectors.toList())); - } - //查询投递集合 - List applyNames=jobApplyMapper.queryApplyNames(appUser.getUserId()); - newSearch.setApplyNames(applyNames); - //查询收藏集合 - List collectNames=jobCollectionMapper.queryCollectNames(appUser.getUserId()); - newSearch.setCollectNames(collectNames); - //工作经历集合 - UserWorkExperiences expQuery = new UserWorkExperiences(); - expQuery.setUserId(appUser.getUserId()); - List userWorkExperiences=userWorkExperiencesMapper.getWorkExperiencesList(expQuery); - newSearch.setUserWorkExperiences(userWorkExperiences); - } - - /** - * 新增全文检索数据 - */ - @Override - public Integer addESJobDocument(ESJobDocument esJobDocument) - { - return esJobDocumentMapper.insert(esJobDocument); - } - - /** - * 修改全文检索数据 - */ - @Override - public Integer updateESJobDocument(ESJobDocument esJobDocument) - { - - LambdaEsUpdateWrapper wrapper = new LambdaEsUpdateWrapper<>(); - wrapper.eq(ESJobDocument::getJobId, esJobDocument.getJobId()); - return esJobDocumentMapper.update(esJobDocument, wrapper); - } - - /** - * 根据ID查询全文检索数据 - */ - @Override - public ESJobDocument getESJobDocument(Long jobId) - { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper(); - wrapper.eq(ESJobDocument::getJobId, jobId); - return esJobDocumentMapper.selectOne(wrapper); - } - - /** - * 根据ID删除全文检索数据 - */ - @Override - public void deleteESJobDocument(String ids) - { - for (Long jobId : Convert.toLongArray(ids)) - { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper(); - wrapper.eq(ESJobDocument::getJobId, jobId); - esJobDocumentMapper.delete(wrapper); - } - } - - @Override - public EsPageInfo nearJob(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - //添加屏蔽的企业 - List jobIds=null; - if(SiteSecurityUtils.isLogin()) { - AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); - } - LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); - EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - return esJobDocumentEsPageInfo; - } - - @Override - public EsPageInfo countyJobList(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - LambdaEsQueryWrapper wrapper = getWrapper(jobQuery, null); - //添加屏蔽的企业 - List jobIds=null; - if(SiteSecurityUtils.isLogin()) { - AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); - wrapper.not().in(ESJobDocument::getJobId, jobIds); - } - if(jobQuery.getCountyIds()!=null){ - wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,jobQuery.getCountyIds())); - } - EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - return esJobDocumentEsPageInfo; - } - - @Override - public EsPageInfo subway(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - List jobIds=null; - if(SiteSecurityUtils.isLogin()) { - AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); - } - LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); - EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - return esJobDocumentEsPageInfo; - } - - @Override - public EsPageInfo commercialArea(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - List jobIds=null; - if(SiteSecurityUtils.isLogin()) { - AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); - } - LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); - if(jobQuery.getLongitude()!=null){ - wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); - } - EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - return esJobDocumentEsPageInfo; - } - - @Override - public List littleVideo(ESJobSearch esJobSearch) { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.eq(ESJobDocument::getIsExplain, 1); - wrapper.limit(esJobSearch.getPageSize()); - return esJobDocumentMapper.selectList(wrapper); - } - - @Override - public void updateEs() { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - esJobDocumentMapper.selectList(wrapper).forEach(esJobDocument -> { - esJobDocument.setPostingDate(new Date()); - this.updateESJobDocument(esJobDocument); - }); - } - - @Override - public List littleVideoRandom(List cacheList, Integer count,String jobTitle) { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.eq(ESJobDocument::getIsExplain, 1); - if(!ListUtil.isListEmptyOrNull(cacheList)){ - wrapper.not().in(ESJobDocument::getJobId,cacheList); - } - if(count==null){ - count = 1; - } - if(StringUtil.isEmptyOrNull(jobTitle)){ - wrapper.or(a -> a.like(ESJobDocument::getJobTitle, jobTitle, 5.0f) - .or() - .like(ESJobDocument::getDescription, jobTitle, 1.0f) - ); - } - wrapper.limit(count); - return esJobDocumentMapper.selectList(wrapper); - } - - private LambdaEsQueryWrapper getWrapper(ESJobSearch esJobSearch,List jobIds){ - - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - /*if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ - for (String keyWord:esJobSearch.getJobTitle().split(",")) { - wrapper.and(a -> a.match(ESJobDocument::getJobTitle, keyWord, 5.0f) - ); - } - - }*/ - //优化岗位名称查询,只有命中岗位名称或者分类就行 - String titleStr = esJobSearch.getJobTitle(); - String cateStr = esJobSearch.getJobCategory(); - boolean hasTitle = !StringUtil.isEmptyOrNull(titleStr); - boolean hasCate = !StringUtil.isEmptyOrNull(cateStr); - - if (hasTitle || hasCate) { - wrapper.and(sub -> { - if (hasTitle) { - String[] words = titleStr.split(","); - for (String w : words) { - String word = w.trim(); - if (StringUtil.isEmptyOrNull(word)) { - continue; - } - sub.or(inner -> inner - .match(ESJobDocument::getJobTitle, word, 5.0f) - .or() - .match(ESJobDocument::getJobCategory, word, 5.0f) - ); - } - } - if (hasCate) { - String[] words = cateStr.split(","); - for (String w : words) { - String word = w.trim(); - if (StringUtil.isEmptyOrNull(word)) { - continue; - } - sub.or(inner -> inner - .match(ESJobDocument::getJobTitle, word, 5.0f) - .or() - .match(ESJobDocument::getJobCategory, word, 5.0f) - ); - } - } - }); - } - - if(esJobSearch.getLongitude()!=null){ - wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(esJobSearch.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(esJobSearch.getLatitude().toString()), Double.parseDouble(esJobSearch.getLongitude().toString()))); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ - Integer maxValue = StringUtil.findMaxValue(esJobSearch.getEducation()); - wrapper.and(x->x.eq(ESJobDocument::getEducation_int,maxValue)); - } - // 薪资范围筛选:兼容前端字段 salaryMin/salaryMax 和 minSalary/maxSalary - Long userMinSalary = Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary(); - Long userMaxSalary = Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary(); - - // 岗位最高薪资必须在用户筛选范围内 - // 条件:岗位最大薪资 <= 用户最大薪资 AND 岗位最大薪资 >= 用户最小薪资 - if(Objects.nonNull(userMinSalary) && Objects.nonNull(userMaxSalary)){ - wrapper.and(x->x.le(ESJobDocument::getMaxSalary,userMaxSalary) - .ge(ESJobDocument::getMaxSalary,userMinSalary)); - } else if(Objects.nonNull(userMinSalary)){ - // 只有最小薪资:岗位最大薪资 >= 用户最小薪资 - wrapper.and(x->x.ge(ESJobDocument::getMaxSalary,userMinSalary)); - } else if(Objects.nonNull(userMaxSalary)){ - // 只有最大薪资:岗位最大薪资 <= 用户最大薪资 - wrapper.and(x->x.le(ESJobDocument::getMaxSalary,userMaxSalary)); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ - Integer maxValue = StringUtil.findMaxValue(esJobSearch.getExperience()); - wrapper.and(x->x.eq(ESJobDocument::getExperience_int,maxValue)); - } -// if(!StringUtil.isEmptyOrNull(esJobSearch.getJobCategory())){ -// String targetValue = esJobSearch.getJobCategory(); -// wrapper.and(x -> x.eq(ESJobDocument::getJobCategory, targetValue)); -// } - if(!StringUtil.isEmptyOrNull(esJobSearch.getScale())){ - Integer maxValue = StringUtil.findMaxValue(esJobSearch.getScale()); - wrapper.and(x->x.le(ESJobDocument::getScale,maxValue)); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ - List integers = StringUtil.convertStringToIntegerList(esJobSearch.getArea()); - wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ - wrapper.and(x->x.eq(ESJobDocument::getJobType,esJobSearch.getJobType())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ - wrapper.and(x->x.eq(ESJobDocument::getType,esJobSearch.getType())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ - wrapper.and(x->x.like(ESJobDocument::getDescription,esJobSearch.getDescription())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ - wrapper.and(x->x.eq(ESJobDocument::getCompanyNature,esJobSearch.getCompanyNature())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){ - wrapper.and(x->x.eq(ESJobDocument::getCode,esJobSearch.getCode())); - } - if(esJobSearch.getJobId()!=null){ - wrapper.and(x->x.eq(ESJobDocument::getJobId,esJobSearch.getJobId())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobAddress())){ - wrapper.and(x->x.like(ESJobDocument::getJobAddress,esJobSearch.getJobAddress())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobLocation())){ - wrapper.and(x->x.like(ESJobDocument::getJobLocation,esJobSearch.getJobLocation())); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyName())){ - wrapper.and(x->x.like(ESJobDocument::getCompanyName,esJobSearch.getCompanyName())); - } - // 1.简历工作经历(最高权重) - JobRecommendUtil.appendWorkExpMatch(wrapper, esJobSearch.getUserWorkExperiences()); - //投递记录 - JobRecommendUtil.appendJobTitleMatch(wrapper, esJobSearch.getApplyNames(), JobRecommendUtil.APPLY_TITLE_BOOST); - //收藏记录 - JobRecommendUtil.appendJobTitleMatch(wrapper, esJobSearch.getCollectNames(), JobRecommendUtil.COLLECT_TITLE_BOOST); - - //按时间段来查询数据 - Date startDate = esJobSearch.getStartDate(); - Date endDate = esJobSearch.getEndDate(); - if (Objects.nonNull(startDate) && Objects.nonNull(endDate)) { - String startStr = DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant()); - String endStr = DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant()); - wrapper.between(ESJobDocument::getPostingDate, startStr, endStr); - } else if (Objects.nonNull(startDate)) { - wrapper.ge(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant())); - } else if (Objects.nonNull(endDate)) { - wrapper.le(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant())); - } - // 先开启打分、优先按匹配分数排序(关键词前台命中越高越靠前) - wrapper.trackScores(); - wrapper.sortByScore(SortOrder.DESC); - if(Objects.nonNull(esJobSearch.getOrder())){ - switch (esJobSearch.getOrder()) { - case 0: // 推荐:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - break; - case 1: // 最热:权重 → 热度 - wrapper.orderByDesc(ESJobDocument::getIsHot); - wrapper.orderByDesc(ESJobDocument::getApplyNum); - wrapper.orderByDesc(ESJobDocument::getView); - break; - case 2: // 最新发布:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - break; - case 3: // 最大薪资:权重 → 最高薪资 - wrapper.orderByDesc(ESJobDocument::getMaxSalary); - break; - default: // 默认:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - } - } - //企业用户排除es去除jobIds - boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType()); - if (!isCompanyUser && !ListUtil.isListEmptyOrNull(jobIds)) { - wrapper.not().in(ESJobDocument::getJobId, jobIds); - } - return wrapper; - } - - //todo 存储用户搜索 - @Override - public EsPageInfo appList(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - if(SiteSecurityUtils.isLogin()){ - //AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); - List jobs=appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(SiteSecurityUtils.getUserId()); - if (!ListUtil.isListEmptyOrNull(jobs)) { - wrapper.not().in(ESJobDocument::getJobId, jobs); - } -// if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ -// List jobTitle = appUser.getJobTitle(); -// jobQuery.setJobTitle(String.join(",", jobTitle)); -// } - } - if(hasText(jobQuery.getJobTitle())){ - wrapper.and(a->a.match(ESJobDocument::getJobTitle,jobQuery.getJobTitle().trim(),5.0f)); - } - if(hasText(jobQuery.getEducation())){ - // 支持逗号分隔的多值筛选(如 "3,4") - String eduStr = jobQuery.getEducation().trim(); - if (eduStr.contains(",")) { - List eduList = StringUtil.convertStringToStringList(eduStr); - wrapper.and(a->a.in(ESJobDocument::getEducation,eduList)); - } else { - wrapper.and(a->a.eq(ESJobDocument::getEducation,eduStr)); - } - } - if(hasText(jobQuery.getArea())){ - List integers = StringUtil.convertStringToIntegerList(jobQuery.getArea().trim()); - wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); - } - if(!StringUtil.isEmptyOrNull(jobQuery.getExperience())){ - List integers = StringUtil.convertStringToIntegerList(jobQuery.getExperience()); - wrapper.and(a->a.in(ESJobDocument::getExperience,integers)); - } - Long userMinSalary = Objects.nonNull(jobQuery.getSalaryMin()) ? jobQuery.getSalaryMin() : jobQuery.getMinSalary(); - Long userMaxSalary = Objects.nonNull(jobQuery.getSalaryMax()) ? jobQuery.getSalaryMax() : jobQuery.getMaxSalary(); - if (Objects.nonNull(userMinSalary)) { - wrapper.filter(x -> x.ge(ESJobDocument::getMaxSalary, userMinSalary)); - } - if (Objects.nonNull(userMaxSalary)) { - wrapper.filter(x -> x.le(ESJobDocument::getMinSalary, userMaxSalary)); - } - if(hasText(jobQuery.getScaleDictCode())){ - wrapper.and(a->a.eq(ESJobDocument::getScaleDictCode,jobQuery.getScaleDictCode().trim())); - } - if(hasText(jobQuery.getJobCategory())){ - wrapper.and(a->a.eq(ESJobDocument::getJobCategory,jobQuery.getJobCategory().trim())); - } - if(hasText(jobQuery.getIndustry())){ - wrapper.and(a->a.eq(ESJobDocument::getIndustry,jobQuery.getIndustry().trim())); - } - if(jobQuery.getLongitude()!=null){ - wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); - } - if(jobQuery.getJobLocationAreaCode()!=null){ - wrapper.and(a->a.eq(ESJobDocument::getJobLocationAreaCode,jobQuery.getJobLocationAreaCode())); - } - if(hasText(jobQuery.getScale())){ - wrapper.and(a->a.eq(ESJobDocument::getScale,jobQuery.getScale().trim())); - } - if(hasText(jobQuery.getSearchValue())){ - wrapper.and(a->a.match(ESJobDocument::getDescription,jobQuery.getSearchValue().trim(),4.0f)); - } - if(hasText(jobQuery.getCompanyNature())){ - wrapper.and(a->a.eq(ESJobDocument::getCompanyNature,jobQuery.getCompanyNature().trim())); - } - if(hasText(jobQuery.getType())){ - wrapper.and(a->a.eq(ESJobDocument::getType,jobQuery.getType().trim())); - } - //开始时间 - Date startDate = jobQuery.getStartDate(); - Date endDate = jobQuery.getEndDate(); - if (Objects.nonNull(startDate) && Objects.nonNull(endDate)) { - String startStr = DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant()); - String endStr = DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant()); - wrapper.between(ESJobDocument::getPostingDate, startStr, endStr); - } else if (Objects.nonNull(startDate)) { - wrapper.ge(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant())); - } else if (Objects.nonNull(endDate)) { - wrapper.le(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant())); - } - // 先开启打分、优先按匹配分数排序(关键词前台命中越高越靠前) -// wrapper.trackScores(); -// wrapper.sortByScore(SortOrder.DESC); - if(Objects.nonNull(jobQuery.getOrder())){ - switch (jobQuery.getOrder()) { - case 0: // 推荐:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - break; - case 1: // 最热:权重 → 热度 - wrapper.orderByDesc(ESJobDocument::getIsHot); - wrapper.orderByDesc(ESJobDocument::getApplyNum); - wrapper.orderByDesc(ESJobDocument::getView); - break; - case 2: // 最新发布:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - break; - case 3: // 最大薪资:权重 → 最高薪资 - wrapper.orderByDesc(ESJobDocument::getMaxSalary); - break; - default: // 默认:权重 → 最新发布 - wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); - } - } - EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - return esJobDocumentEsPageInfo; - } - @Override - public EsPageInfo aiList(ESJobSearch jobQuery) { - Integer pageNum = jobQuery.getCurrent(); - Integer pageSize = jobQuery.getPageSize(); - - // 第一次查询:包含区域条件 - EsPageInfo result = doAiListQuery(jobQuery, pageNum, pageSize, true); - - // 降级查询:如果没有找到岗位且有区域条件,忽略区域重新查询 - if ((result.getList() == null || result.getList().isEmpty()) - && (hasText(jobQuery.getArea()) || jobQuery.getJobLocationAreaCode() != null)) { - logger.info("AI岗位查询降级:忽略区域条件重新查询"); - result = doAiListQuery(jobQuery, pageNum, pageSize, false); - } - - return result; - } - - /** - * AI岗位查询核心逻辑 - * @param jobQuery 查询条件 - * @param pageNum 页码 - * @param pageSize 每页大小 - * @param includeArea 是否包含区域条件 - */ - private EsPageInfo doAiListQuery(ESJobSearch jobQuery, Integer pageNum, Integer pageSize, boolean includeArea) { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - - if(hasText(jobQuery.getJobTitle())){ - wrapper.and(a->a.match(ESJobDocument::getJobTitle,jobQuery.getJobTitle().trim(),5.0f) - .or() - .match(ESJobDocument::getDescription,jobQuery.getJobTitle().trim(),1.0f)); - } - if(hasText(jobQuery.getEducation())){ - wrapper.and(a->a.le(ESJobDocument::getEducation,jobQuery.getEducation().trim())); - } - // 区域条件:根据 includeArea 决定是否加入 - if(includeArea && hasText(jobQuery.getArea())){ - List integers = StringUtil.convertStringToIntegerList(jobQuery.getArea().trim()); - wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); - } - if(hasText(jobQuery.getExperience())){ - wrapper.and(a->a.le(ESJobDocument::getExperience,jobQuery.getExperience().trim())); - } - // 薪资范围:岗位最高薪资必须在用户筛选范围内 - if(Objects.nonNull(jobQuery.getMaxSalary())){ - wrapper.and(x->x.le(ESJobDocument::getMaxSalary,jobQuery.getMaxSalary())); - } - if(Objects.nonNull(jobQuery.getMinSalary())){ - wrapper.and(x->x.ge(ESJobDocument::getMaxSalary,jobQuery.getMinSalary())); - } - if(hasText(jobQuery.getScaleDictCode())){ - wrapper.and(a->a.eq(ESJobDocument::getScaleDictCode,jobQuery.getScaleDictCode().trim())); - } - if(hasText(jobQuery.getIndustry())){ - wrapper.and(a->a.eq(ESJobDocument::getIndustry,jobQuery.getIndustry().trim())); - } - if(jobQuery.getLongitude()!=null){ - wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); - } - // 区域编码条件:根据 includeArea 决定是否加入 - if(includeArea && jobQuery.getJobLocationAreaCode()!=null){ - wrapper.and(a->a.eq(ESJobDocument::getJobLocationAreaCode,jobQuery.getJobLocationAreaCode())); - } - if(hasText(jobQuery.getScale())){ - wrapper.and(a->a.eq(ESJobDocument::getScale,jobQuery.getScale().trim())); - } - if(hasText(jobQuery.getSearchValue())){ - wrapper.and(a->a.match(ESJobDocument::getDescription,jobQuery.getSearchValue().trim(),4.0f)); - } - if(hasText(jobQuery.getCompanyNature())){ - wrapper.and(a->a.eq(ESJobDocument::getCompanyNature,jobQuery.getCompanyNature().trim())); - } - if(hasText(jobQuery.getType())){ - wrapper.and(a->a.eq(ESJobDocument::getType,jobQuery.getType().trim())); - } - if(Objects.nonNull(jobQuery.getOrder())){ - if (jobQuery.getOrder()==2){ - wrapper.orderByDesc(ESJobDocument::getPostingDate); - } - if (jobQuery.getOrder()==1){ - wrapper.orderByDesc(ESJobDocument::getIsHot); - wrapper.orderByDesc(ESJobDocument::getApplyNum); - wrapper.orderByDesc(ESJobDocument::getView); - } - } - return esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); - } - /** - * 判断字符串是否有实际内容(非null、非空、非纯空白) - */ - private boolean hasText(String str) { - return str != null && !str.trim().isEmpty(); - } - - @Override - public ESJobDocument selectById(Long jobId) { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.eq(ESJobDocument::getJobId,jobId); - return esJobDocumentMapper.selectOne(wrapper); - } - - @Override - public void fix() { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.eq(ESJobDocument::getIsExplain,1); - List esJobDocuments = esJobDocumentMapper.selectList(wrapper); - for (ESJobDocument esJobDocument : esJobDocuments) { - Job job = new Job(); - BeanUtils.copyProperties(esJobDocument,job); - job.setJobId(null); - jobMapper.insert(job); - } - } - - @Override - public void deleteJob(Long jobId) { - ensureJobDocumentIndexExists(); - LambdaEsQueryWrapper lambdaEsQueryWrapper = new LambdaEsQueryWrapper(); - lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,jobId); - esJobDocumentMapper.delete(lambdaEsQueryWrapper); - } - - @Override - public void updateJob(Long jobId) { - ESJobDocument esJobDocument = new ESJobDocument(); - Job job = jobMapper.selectById(jobId); - if(job!=null){ - if(job.getCompanyId()!=null){ - Company company=iCompanyService.selectCompanyByCompanyId(job.getCompanyId()); - if (company != null) { - job.setCode(company.getCode()); - job.setCompanyNature(company.getCompanyNature()); - } - } - } - - BeanUtils.copyBeanProp(esJobDocument, job); - esJobDocument.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); - esJobDocument.setAppJobUrl("https://xjshzly.longbiosphere.com/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes())); - if(!StringUtil.isEmptyOrNull(job.getScale())){ - esJobDocument.setScale(Integer.valueOf(job.getScale())); - }else { - esJobDocument.setScale(0); - } - if(!StringUtil.isEmptyOrNull(job.getExperience())){ - esJobDocument.setExperience_int(Integer.valueOf(job.getExperience())); - }else { - esJobDocument.setExperience("0"); - esJobDocument.setExperience_int(0); - } - if(!StringUtil.isEmptyOrNull(job.getEducation())){ - esJobDocument.setEducation_int(Integer.valueOf(job.getEducation())); - }else { - esJobDocument.setEducation("-1"); - esJobDocument.setEducation_int(-1); - } - if (esJobDocument.getLatitude() != null) { - esJobDocument.setLatAndLon(esJobDocument.getLatitude().toString() + "," + esJobDocument.getLongitude().toString()); - } - ensureJobDocumentIndexExists(); - - LambdaEsQueryWrapper lambdaEsQueryWrapper = new LambdaEsQueryWrapper(); - lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,job.getJobId()); - esJobDocumentMapper.delete(lambdaEsQueryWrapper); - esJobDocumentMapper.insert(esJobDocument); - } - - /** - * 确保job_document索引存在;缺失时懒创建(仅建空索引与映射,不重建全量数据)。 - * process-index-mode: manual 下easy-es不会自动建索引,缺失时delete/update等写操作 - * 会抛 index_not_found_exception 导致业务接口500并回滚事务,故在写操作前调用以自愈。 - */ - private void ensureJobDocumentIndexExists() { - try { - if (!esJobDocumentMapper.existsIndex("job_document")) { - esJobDocumentMapper.createIndex(); - logger.info("job_document索引不存在,已自动创建"); - } - } catch (Exception e) { - // 并发场景下其他请求可能已创建索引,忽略“索引已存在”类异常 - logger.warn("自动创建job_document索引异常(可能已被其他请求创建,忽略)", e); - } - } - - public List selectByIds(Long[] jobIds) { - LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); - wrapper.in(ESJobDocument::getJobId,jobIds); - return esJobDocumentMapper.selectList(wrapper); - } - - /** - * pc端查询es - * @param esJobSearch - * @param jobIds - * @param appUser - * @return - */ - @Override - public List selectSysTextListExceptJobId(ESJobSearch esJobSearch, List jobIds,AppUser appUser) { - - ESJobSearch newSearch = new ESJobSearch(); - BeanUtils.copyProperties(esJobSearch,newSearch); - - // 判断是否为用户主动搜索(有明确的搜索条件),而非纯推荐场景 - boolean isExplicitSearch = !StringUtil.isEmptyOrNull(esJobSearch.getJobTitle()) - || !StringUtil.isEmptyOrNull(esJobSearch.getJobCategory()) - || !StringUtil.isEmptyOrNull(esJobSearch.getCompanyName()) - || !StringUtil.isEmptyOrNull(esJobSearch.getDescription()); - - // 用户资料偏好仅在"纯推荐"场景下作为个性化过滤条件; - // 用户主动搜索时不应被资料中的薪资/学历/经验/区域等偏好缩小结果范围 - if(appUser != null && !isExplicitSearch){ - if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ - List jobTitle = appUser.getJobTitle(); - newSearch.setJobTitle(String.join(",", jobTitle)); - } - if(!StringUtil.isEmptyOrNull(appUser.getEducation())){ - newSearch.setEducation(appUser.getEducation()); - } - if(!StringUtil.isEmptyOrNull(appUser.getArea())){ - newSearch.setArea(appUser.getArea()); - } - if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){ - newSearch.setExperience(appUser.getWorkExperience()); - } - if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){ - newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax())); - } - if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){ - newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin())); - } - //查询投递集合 - List applyNames=jobApplyMapper.queryApplyNames(appUser.getUserId()); - newSearch.setApplyNames(applyNames); - //查询收藏集合 - List collectNames=jobCollectionMapper.queryCollectNames(appUser.getUserId()); - newSearch.setCollectNames(collectNames); - //工作经历集合 - UserWorkExperiences expQuery = new UserWorkExperiences(); - expQuery.setUserId(appUser.getUserId()); - List userWorkExperiences=userWorkExperiencesMapper.getWorkExperiencesList(expQuery); - newSearch.setUserWorkExperiences(userWorkExperiences); - } - - // 请求参数始终优先于用户资料偏好 - if(Objects.nonNull(esJobSearch.getSalaryMin()) || Objects.nonNull(esJobSearch.getMinSalary())){ - newSearch.setMinSalary(Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary()); - } - if(Objects.nonNull(esJobSearch.getSalaryMax()) || Objects.nonNull(esJobSearch.getMaxSalary())){ - newSearch.setMaxSalary(Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ - newSearch.setArea(esJobSearch.getArea()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ - newSearch.setEducation(esJobSearch.getEducation()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ - newSearch.setExperience(esJobSearch.getExperience()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ - newSearch.setJobTitle(esJobSearch.getJobTitle()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ - newSearch.setJobType(esJobSearch.getJobType()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ - newSearch.setType(esJobSearch.getType()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ - newSearch.setDescription(esJobSearch.getDescription()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ - newSearch.setCompanyNature(esJobSearch.getCompanyNature()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){ - newSearch.setCode(esJobSearch.getCode()); - } - if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyName())){ - newSearch.setCompanyName(esJobSearch.getCompanyName()); - } - - LambdaEsQueryWrapper wrapper = getWrapper(newSearch,jobIds); - - //todo 暂时 - wrapper.limit(esJobSearch.getPageSize()); - List esJobDocuments = esJobDocumentMapper.selectList(wrapper); - - if (esJobDocuments.size() < esJobSearch.getPageSize()) { - // 定义要逐步放宽的搜索条件字段(不放松核心文本搜索条件) - List relaxConditions = new ArrayList<>(); - relaxConditions.add(() -> newSearch.setArea(null)); - relaxConditions.add(() -> newSearch.setExperience(null)); - relaxConditions.add(() -> newSearch.setMaxSalary(null)); - relaxConditions.add(() -> newSearch.setMinSalary(null)); - relaxConditions.add(() -> newSearch.setEducation(null)); - // 保存所有查询到的文档 - List allDocuments = new ArrayList<>(esJobDocuments); - - // 记录已经放宽的条件数量 - int relaxedConditions = 0; - - // 继续放宽条件直到满足分页或所有条件都已放宽 - while (allDocuments.size() < esJobSearch.getPageSize() - && relaxedConditions < relaxConditions.size()) { - // 放宽下一个条件 - relaxConditions.get(relaxedConditions).run(); - relaxedConditions++; - - // 查询新增的文档(不包含之前已经查询到的) - wrapper = getWrapper(newSearch, jobIds); - wrapper.limit(esJobSearch.getPageSize() - allDocuments.size()); - if (!allDocuments.isEmpty()) { - // 排除已经查询到的文档ID - Set existingIds = allDocuments.stream() - .map(ESJobDocument::getId) - .collect(Collectors.toSet()); - wrapper.not().in(ESJobDocument::getId, existingIds); - } - - List newDocuments = esJobDocumentMapper.selectList(wrapper); - allDocuments.addAll(newDocuments); - } - - // 如果总数超过分页大小,截取所需数量 - if (allDocuments.size() > esJobSearch.getPageSize()) { - esJobDocuments = allDocuments.subList(0, esJobSearch.getPageSize()); - } else { - esJobDocuments = allDocuments; - } - } - - return esJobDocuments; - } - - /** - * 将 jobCategory 从数字ID解析为标签文字(如 "45" → "销售顾问") - * 如果已经是文字标签或解析失败,返回原值 - */ - private String resolveJobCategoryLabel(String jobCategory) { - if (StringUtils.isEmpty(jobCategory)) { - return jobCategory; - } - try { - Long categoryId = Long.parseLong(jobCategory); - JobTitle jobTitle = jobTitleMapper.selectById(categoryId); - if (jobTitle != null && StringUtils.isNotEmpty(jobTitle.getJobName())) { - return jobTitle.getJobName(); - } - } catch (NumberFormatException e) { - // 已经是文字标签,直接返回原值 - } - return jobCategory; - } - - /** - * 根据岗位id批量更新es - * @param jobIdList - */ - @Override - public void batchUpdateJob(List jobIdList) { - if (jobIdList == null || jobIdList.isEmpty()) { - return; - } - //索引初始化 - ensureJobDocumentIndexExists(); - //查询所有岗位数据 - List allJobList = jobMapper.selectBatchIds(jobIdList); - if (allJobList == null || allJobList.isEmpty()) { - LambdaEsQueryWrapper delWrapper = new LambdaEsQueryWrapper<>(); - delWrapper.in(ESJobDocument::getJobId, jobIdList); - esJobDocumentMapper.delete(delWrapper); - return; - } - //查询所有企业数据 - List companyIdList = allJobList.stream() - .map(Job::getCompanyId) - .filter(id -> id != null) - .distinct() - .collect(Collectors.toList()); - List companyList = new ArrayList<>(); - if (!companyIdList.isEmpty()) { - companyList = companyMapper.selectBatchIds(companyIdList); - } - Map companyMap = companyList.stream() - .collect(Collectors.toMap(Company::getCompanyId, c -> c)); - - // 批量转换ES - List esDocList = allJobList.stream() - .map(job -> convertToEsDoc(job, companyMap)) - .collect(Collectors.toList()); - - try { - // 批量删除旧索引 - LambdaEsQueryWrapper delWrapper = new LambdaEsQueryWrapper<>(); - delWrapper.in(ESJobDocument::getJobId, jobIdList); - esJobDocumentMapper.delete(delWrapper); - // 批量插入新索引 - esJobDocumentMapper.insertBatch(esDocList); - } catch (Exception e) { - throw new RuntimeException("ES批量更新失败", e); - } - } - - /** - * Job 转 ESJobDocument 抽取单独方法 - */ - private ESJobDocument convertToEsDoc(Job job, Map companyMap) { - ESJobDocument esDoc = new ESJobDocument(); - BeanUtils.copyBeanProp(esDoc, job); - // 企业编码赋值 - if (job.getCompanyId() != null) { - Company company = companyMap.get(job.getCompanyId()); - if (company != null) { - esDoc.setCode(company.getCode()); - esDoc.setCompanyNature(company.getCompanyNature()); - } - } - - esDoc.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); - esDoc.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ job.getJobId()); - esDoc.setScale(parseIntDefaultZero(job.getScale())); - //工作经验 - if (StringUtil.isEmptyOrNull(job.getExperience())) { - esDoc.setExperience("0"); - esDoc.setExperience_int(0); - } else { - Integer expInt = parseIntDefaultZero(job.getExperience()); - esDoc.setExperience_int(expInt); - } - - // 学历字段统一处理 - if (StringUtil.isEmptyOrNull(job.getEducation())) { - esDoc.setEducation("-1"); - esDoc.setEducation_int(-1); - } else { - Integer eduInt = parseIntDefaultMinusOne(job.getEducation()); - esDoc.setEducation_int(eduInt); - } - - // 经纬度拼接 - if (esDoc.getLatitude() != null && esDoc.getLongitude() != null) { - esDoc.setLatAndLon(esDoc.getLatitude() + "," + esDoc.getLongitude()); - } - - return esDoc; - } - - /** - * 字符串转数字,空/非法返回0 - */ - private Integer parseIntDefaultZero(String str) { - if (StringUtil.isEmptyOrNull(str)) { - return 0; - } - try { - return Integer.valueOf(str); - } catch (NumberFormatException e) { - return 0; - } - } - - /** - * 字符串转数字,空/非法返回-1 - */ - private Integer parseIntDefaultMinusOne(String str) { - if (StringUtil.isEmptyOrNull(str)) { - return -1; - } - try { - return Integer.valueOf(str); - } catch (NumberFormatException e) { - return -1; - } - } - - /** - * 岗位名称推荐 - * @param wrapper - * @param nameList - * @param boost - */ - /*private void appendJobTitleMatch(LambdaEsQueryWrapper wrapper, List nameList, Float boost) { - if (CollUtil.isEmpty(nameList)) { - return; - } - for (String title : nameList) { - wrapper.should(w -> w.match(ESJobDocument::getJobTitle, title, boost)); - } - }*/ -} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java deleted file mode 100644 index a21ed2f..0000000 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.ruoyi.cms.service.impl; - -import com.ruoyi.cms.mapper.JobMapper; -import com.ruoyi.cms.service.IJobTitleSuggestService; -import com.ruoyi.cms.util.JobTitleSuggestTokenizer; -import com.ruoyi.common.core.redis.RedisCache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * 岗位名称 IK 分词联想服务实现。 - */ -@Service -public class JobTitleSuggestServiceImpl implements IJobTitleSuggestService { - - private static final Logger logger = LoggerFactory.getLogger(JobTitleSuggestServiceImpl.class); - - /** - * Redis Hash:field 是 IK 分词,value 是包含该分词的完整岗位名称列表。 - */ - private static final String CACHE_KEY = "app:job:title:suggest"; - private static final String BUILDING_CACHE_KEY = CACHE_KEY + ":building"; - private static final String LOCK_KEY = CACHE_KEY + ":rebuild:lock"; - - private static final int DEFAULT_LIMIT = 10; - private static final int MAX_LIMIT = 20; - private static final int LOCK_EXPIRE_MINUTES = 30; - - @Autowired - private JobMapper jobMapper; - - @Autowired - private RedisCache redisCache; - - /** - * 确保服务启动后 Redis 中已有一份缓存;后续由 Quartz 定时任务周期性重建。 - */ - @PostConstruct - public void init() { - rebuildCache(); - } - - @Override - public List suggest(String keyword, Integer limit) { - String normalizedKeyword = JobTitleSuggestTokenizer.normalize(keyword); - if (normalizedKeyword.isEmpty()) { - return Collections.emptyList(); - } - - int resultLimit = normalizeLimit(limit); - try { - Map tokenTitles = redisCache.getCacheMap(CACHE_KEY); - if (tokenTitles == null || tokenTitles.isEmpty()) { - return Collections.emptyList(); - } - - // Hash field 只来自 IK 分词;这里仅对已有词项做前缀过滤,不生成新的前缀词。 - List> matchedTokens = tokenTitles.entrySet().stream() - .filter(entry -> entry.getKey() != null) - .filter(entry -> entry.getKey().startsWith(normalizedKeyword)) - .sorted(Map.Entry.comparingByKey()) - .collect(Collectors.toList()); - - Set result = new LinkedHashSet<>(); - for (Map.Entry entry : matchedTokens) { - if (result.size() >= resultLimit) { - break; - } - - // 先返回匹配到的 IK 分词本身。 - result.add(entry.getKey()); - - // 再返回包含该 IK 分词的完整岗位名称。 - appendTitles(result, entry.getValue(), resultLimit); - } - return new ArrayList<>(result).subList(0, Math.min(result.size(), resultLimit)); - } catch (Exception e) { - // 联想属于非核心能力,Redis 异常时返回空列表,不影响岗位主搜索。 - logger.warn("查询岗位名称 IK 分词缓存失败,keyword={}", keyword, e); - return Collections.emptyList(); - } - } - - @Override - public void rebuildCache() { - String lockValue = UUID.randomUUID().toString(); - boolean lockAcquired = false; - try { - lockAcquired = redisCache.setIfAbsent( - LOCK_KEY, lockValue, LOCK_EXPIRE_MINUTES, TimeUnit.MINUTES); - if (!lockAcquired) { - logger.info("其他节点正在重建岗位名称 IK 分词缓存,本节点跳过"); - return; - } - - List sourceList = jobMapper.selectValidJobTitles(); - Map> cacheData = buildTokenTitles(sourceList); - - // 先写临时 Hash,再原子替换正式 Hash,避免接口读到半成品。 - redisCache.deleteObject(BUILDING_CACHE_KEY); - if (cacheData.isEmpty()) { - redisCache.deleteObject(CACHE_KEY); - } else { - redisCache.setCacheMap(BUILDING_CACHE_KEY, cacheData); - redisCache.redisTemplate.rename(BUILDING_CACHE_KEY, CACHE_KEY); - } - logger.info("岗位名称 IK 分词缓存重建完成,有效岗位名称{}个,IK 词{}个", - sourceList == null ? 0 : sourceList.size(), cacheData.size()); - } catch (Exception e) { - logger.error("重建岗位名称 IK 分词缓存失败", e); - } finally { - if (lockAcquired) { - redisCache.deleteObject(LOCK_KEY); - } - } - } - - private Map> buildTokenTitles(List sourceList) { - Map> tokenTitles = new TreeMap<>(); - if (sourceList == null) { - return Collections.emptyMap(); - } - - for (String title : sourceList) { - String displayTitle = normalizeDisplayTitle(title); - if (displayTitle.isEmpty()) { - continue; - } - - Set tokens = JobTitleSuggestTokenizer.tokenize(displayTitle); - for (String token : tokens) { - tokenTitles.computeIfAbsent(token, key -> new TreeSet<>()).add(displayTitle); - } - } - - Map> result = new LinkedHashMap<>(); - for (Map.Entry> entry : tokenTitles.entrySet()) { - result.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - return result; - } - - private void appendTitles(Set result, Object cachedTitles, int resultLimit) { - if (!(cachedTitles instanceof Iterable)) { - return; - } - for (Object cachedTitle : (Iterable) cachedTitles) { - if (cachedTitle == null || result.size() >= resultLimit) { - break; - } - String title = String.valueOf(cachedTitle); - if (!title.isEmpty()) { - result.add(title); - } - } - } - - private String normalizeDisplayTitle(String title) { - if (title == null) { - return ""; - } - return title.trim().replaceAll("\\s+", " "); - } - - private int normalizeLimit(Integer limit) { - if (limit == null) { - return DEFAULT_LIMIT; - } - return Math.max(1, Math.min(limit, MAX_LIMIT)); - } -} From 5f33be65ca579d237fbf69348809c5d4f4e1c9d7 Mon Sep 17 00:00:00 2001 From: francis-fh Date: Wed, 29 Jul 2026 19:35:53 +0800 Subject: [PATCH 12/14] 11 --- .../resources/mapper/app/CompanyMapper.xml | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml index d66b3c1..e4786f3 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml @@ -44,16 +44,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + select="selectCompanyContactsById" + column="company_id"/> - - - - + + + + + + select company_id, name, location, industry, scale, del_flag, create_by, create_time, update_by, update_time, remark,code,description,nature,company_nature,total_recruitment,registered_address,contact_person,contact_person_phone,is_abnormal,not_pass_reason,status,case when status='2' then update_time else null end reject_time,is_imp_company,imp_company_type,enterprise_type,legal_person,legal_id_card,legal_phone,is_hrs, (select count(1) @@ -155,10 +163,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and fc.del_flag = '0' and fc.review_status = '1' and f.end_time < CURRENT_TIMESTAMP - and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count, - cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone, - cct.company_id as con_company_id,cct.id as con_company_contact_id from company cp - left join company_contact cct on cp.company_id=cct.company_id and cct.del_flag='0' + and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count + from company cp cp.del_flag = '0' and cp.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%') and cp.location = #{location} @@ -250,9 +256,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and fc.review_status = '1' and f.end_time < CURRENT_TIMESTAMP and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count, - su.lc_userid,su.user_id,cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone,cct.company_id as con_company_id from company t + su.lc_userid,su.user_id from company t left join sys_user su on t.code=su.id_card and su.del_flag='0' - left join company_contact cct on t.company_id=cct.company_id and cct.del_flag='0' t.del_flag = '0' and su.lc_userid is not null and t.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%') and t.scale = #{scale} From 7d715736edda541505dd7c0dbb6985958ad45b2a Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Wed, 29 Jul 2026 22:24:02 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-local.yml | 158 ++ .../cms/service/impl/ESJobSearchImpl.java | 1318 +++++++++++++++++ .../impl/JobTitleSuggestServiceImpl.java | 187 +++ 3 files changed, 1663 insertions(+) create mode 100644 ruoyi-admin/src/main/resources/application-local.yml create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml new file mode 100644 index 0000000..6fa3ae7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-local.yml @@ -0,0 +1,158 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.8 + # 版权年份 + copyrightYear: 2024 + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: /Users/chenyanchang/logs/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.highgo.jdbc.Driver + druid: + # 主库数据源 + # master: + # url: jdbc:highgo://124.243.245.42:5866/shz?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified + # #username: syssso + # username: sysdba + # password: ZKR2024@comzkr + # 正式环境 + # master: + # url: jdbc:highgo://39.98.44.136:6023/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified + # username: sysdba + # password: Hello@2026 + # 测试环境 + master: + url: jdbc:highgo://47.111.103.66:5866/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified + username: sysdba + password: Hello@2026 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 10 + # 最小连接池数量 + minIdle: 30 + # 最大连接池数量 + maxActive: 50 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT version() + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: ruoyi + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + redis: + # 本地 + #host: 127.0.0.1 + #port: 6379 + host: 124.243.245.42 + port: 5379 + # 数据库索引 + database: 0 + # 密码(本地通常无密码) + password: + # 连接超时时间 + timeout: 100s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + +# easy-es +easy-es: + enable: true + banner: false + address: 124.243.245.42:9200 + global-config: + process-index-mode: manual + db-config: + refresh-policy: immediate + username: elastic + password: zkr2024@@.com + +#ai +chat: + baseUrl: http://39.98.44.136:8082 + chatUrl: /v1/chat/completions + chatDetailUrl: /core/chat/getPaginationRecords + chatHistoryUrl: /core/chat/getHistories + updateNameUrl: /core/chat/updateHistory + stickChatUrl: /core/chat/updateHistory + delChatUrl: /core/chat/delHistory + delAllChatUrl: /core/chat/clearHistories + guestUrl: /v1/chat/completions + praiseUrl: /core/chat/feedback/updateUserFeedback + appId: 67cd49095e947ae0ca7fadd8 + apiKey: fastgpt-qMl63276wPZvKAxEkW77bur0sSJpmuC6Ngg9lzyEjufLhsBAurjT55j + model: qd-job-turbo + +audioText: + asr: http://127.0.0.1:8000/asr/file + tts: http://127.0.0.1:19527/synthesize + +#浪潮单点登录相关 +#互联网端: +lc_web_auth: + appId: cloud-out-2fb6330e9c0843e1a1424efda5d604c0 + appSecret: x14lueHbtLQL7Pz2G7gE4wcGCV6TDblO5xfeu9V2wGk= + getTokenUrl: http://218.31.252.15:9081/prod-psout-api/auth/token + getUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/system/app/authorize/user/info + editUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/auth/user/edit +#监管端: +lc_cms_auth: + appId: cloud-9793ee8a8c3d47b8871007ffc4128502 + appSecret: Yi+NACK70UPg8rFvsnnfBUq1wcLD4nm6ilC4II/4C4k= + getTokenUrl: http://218.31.252.15:9081/prod-api/auth/token + getUserInfoUrl: http://218.31.252.15:9081/prod-api/system/app/authorize/user/info + resrtPwdUrl: http://218.31.252.15:9081/prod-api/ps/user/resetPwd/zkr + changeStatusUrl: http://218.31.252.15:9081/prod-api/ps/user/change/status/zkr diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java new file mode 100644 index 0000000..d92469d --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java @@ -0,0 +1,1318 @@ +package com.ruoyi.cms.service.impl; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.cms.domain.vo.CompanyVo; +import com.ruoyi.cms.mapper.*; +import com.ruoyi.cms.service.ICompanyService; +import com.ruoyi.cms.util.remommend.JobRecommendUtil; +import com.ruoyi.common.core.domain.entity.AppUser; +import com.ruoyi.cms.domain.ESJobDocument; +import com.ruoyi.cms.domain.Job; +import com.ruoyi.cms.domain.query.ESJobSearch; +import com.ruoyi.cms.mapper.es.EsJobDocumentMapper; +import com.ruoyi.cms.service.IESJobSearchService; +import com.ruoyi.common.core.domain.entity.JobTitle; +import com.ruoyi.cms.util.ListUtil; +import com.ruoyi.cms.util.StringUtil; +import com.ruoyi.common.core.domain.entity.Company; +import com.ruoyi.common.core.domain.entity.UserWorkExperiences; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SiteSecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanUtils; +import org.dromara.easyes.core.biz.EsPageInfo; +import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper; +import org.dromara.easyes.core.conditions.update.LambdaEsUpdateWrapper; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 实体模板Service业务层处理 + * + * @author ruoyi + */ +@Service +public class ESJobSearchImpl implements IESJobSearchService +{ + @Autowired + private EsJobDocumentMapper esJobDocumentMapper; + + @Autowired + private JobMapper jobMapper; + + @Autowired + private AppUserServiceImpl appUserService; + @Autowired + private ICompanyService iCompanyService; + @Autowired + private RedisCache redisCache; + @Autowired + private AppUserBlockCompanyMapper appUserBlockCompanyMapper; + @Autowired + private JobApplyMapper jobApplyMapper; + @Autowired + private JobCollectionMapper jobCollectionMapper; + @Autowired + private UserWorkExperiencesMapper userWorkExperiencesMapper; + + // 锁的key(唯一标识ES索引初始化) + private static final String ES_INIT_LOCK_KEY = "es:job_document:init:lock"; + // 锁过期时间(30分钟,确保初始化完成) + private static final Integer LOCK_EXPIRE_SECONDS = 1800; + // 等待锁时间(5分钟,避免无限等待) + private static final Integer WAIT_LOCK_SECONDS = 300; + + + @Autowired + private BussinessDictDataServiceImpl bussinessDictDataServicel; + @Autowired + private JobTitleMapper jobTitleMapper; + Logger logger = LoggerFactory.getLogger(JobServiceImpl.class); + @Autowired + private CompanyMapper companyMapper; + + /** + * 项目启动时,初始化索引及数据 + */ + @PostConstruct + public void init() + { + boolean isLockAcquired = false; + try { + isLockAcquired = acquireDistributedLock(); + if (isLockAcquired) { + resetTextCache(); + } else { + logger.info("其他节点正在初始化ES索引,直接复用,无需重复执行"); + } + } catch (InterruptedException e) { + logger.error("ES索引初始化等待锁异常", e); + Thread.currentThread().interrupt(); + } finally { + if (isLockAcquired) { + releaseDistributedLock(); + } + } + } + + /** + * 基于自定义RedisCache实现分布式锁(无getRedisTemplate适配版) + */ + private boolean acquireDistributedLock() throws InterruptedException { + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < WAIT_LOCK_SECONDS * 1000) { + boolean success = redisCache.setIfAbsent(ES_INIT_LOCK_KEY,"es_init_locked", + LOCK_EXPIRE_SECONDS,TimeUnit.SECONDS + ); + if (success) { + logger.info("成功获取ES初始化分布式锁,key:{}", ES_INIT_LOCK_KEY); + return true; + } + TimeUnit.MILLISECONDS.sleep(500); + } + logger.warn("等待{}秒未获取到ES初始化锁,放弃执行", WAIT_LOCK_SECONDS); + return false; + } + + /** + * 释放分布式锁(用自定义RedisCache的deleteObject方法) + */ + private void releaseDistributedLock() { + try { + redisCache.deleteObject(ES_INIT_LOCK_KEY); + logger.info("已释放ES初始化分布式锁,key:{}", ES_INIT_LOCK_KEY); + } catch (Exception e) { + logger.error("释放ES初始化锁异常", e); + } + } + + @Override + public void checkEsAndFix() { + try { + esJobDocumentMapper.getIndex(); + }catch (Exception e){ + logger.warn("es索引丢失,正在重新刷新"); + this.init(); + } + } + + /** + * 重置全文检索数据 + */ + //todo 定时任务 + @Override + public void resetTextCache() { + logger.info("正在重新刷新es"); + // 删除并重新创建索引 + /*esJobDocumentMapper.deleteIndex("job_document"); + esJobDocumentMapper.createIndex();*/ + if (esJobDocumentMapper.existsIndex("job_document")) { + esJobDocumentMapper.deleteIndex("job_document"); + logger.info("已删除原有job_document索引"); + try { + TimeUnit.MILLISECONDS.sleep(500); + } catch (InterruptedException e) { + logger.error("删除索引后休眠异常", e); + } + } + + if (!esJobDocumentMapper.existsIndex("job_document")) { + esJobDocumentMapper.createIndex(); + logger.info("已创建job_document索引"); + } else { + logger.info("索引已被其他节点创建,直接复用"); + } + + // 分批次处理数据 + int batchSize = 1000; // 每批次处理的数据量 + int offset = 0; // 起始偏移量 + boolean hasMoreData = true; // 是否还有更多数据 + + while (hasMoreData) { + // 分页查询数据 + Map params = new HashMap<>(); + params.put("batchSize", batchSize); + params.put("offset", offset*batchSize); + List jobList = jobMapper.selectAllJob(params); + + if (jobList.isEmpty()) { + hasMoreData = false; // 没有更多数据,退出循环 + } else { + // 转换数据并插入到 Elasticsearch + List esJobDocumentList = new ArrayList<>(); + for (Job job : jobList) { + ESJobDocument esJobDocument = new ESJobDocument(); + BeanUtils.copyBeanProp(esJobDocument, job); + esJobDocument.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); + //类型转换导致赋值问题,重新赋值 + esJobDocument.setScale(org.apache.commons.lang3.StringUtils.isNotEmpty(job.getScale()) ? Integer.parseInt(job.getScale()) : 0); + //企业信息 + CompanyVo vo=job.getCompanyVo(); + esJobDocument.setCompanyVoJson(JSON.toJSONString(vo)); + //岗位联系人 + esJobDocument.setJobContactVoJson(JSON.toJSONString(job.getJobContactList())); + esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ job.getJobId()); + if(!StringUtil.isEmptyOrNull(job.getScale())){ + esJobDocument.setScale(Integer.valueOf(job.getScale())); + }else { + esJobDocument.setScale(8); + } + if(!StringUtil.isEmptyOrNull(job.getExperience())){ + esJobDocument.setExperience_int(Integer.valueOf(job.getExperience())); + }else { + esJobDocument.setExperience("0"); + esJobDocument.setEducation_int(0); + } + if(!StringUtil.isEmptyOrNull(job.getEducation())){ + esJobDocument.setEducation_int(Integer.valueOf(job.getEducation())); + }else { + esJobDocument.setEducation("-1"); + esJobDocument.setExperience_int(-1); + } + if (esJobDocument.getLatitude() != null) { + esJobDocument.setLatAndLon(esJobDocument.getLatitude().toString() + "," + esJobDocument.getLongitude().toString()); + } + if (StringUtils.isNotEmpty(job.getPostingDate())) { + Date date = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS,job.getPostingDate()); + esJobDocument.setPostingDate(date); + } + esJobDocumentList.add(esJobDocument); + } + + // 批量插入当前批次的数据 + esJobDocumentMapper.insertBatch(esJobDocumentList); + + // 更新偏移量 + offset = offset+1; + } + } + } + + @Override + public List selectTextListExceptJobId(ESJobSearch esJobSearch, List jobIds) { + + ESJobSearch newSearch = new ESJobSearch(); + BeanUtils.copyProperties(esJobSearch,newSearch); + boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType()); + //查询 + if(SiteSecurityUtils.isLogin()){ + AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + if (!StringUtil.isEmptyOrNull(esJobSearch.getCode())) { + newSearch.setCode(esJobSearch.getCode()); + } + //求职者 + if (!isCompanyUser) { + setJobSeekerParams(appUser, esJobSearch, newSearch,jobIds); + } + } + + LambdaEsQueryWrapper wrapper = getWrapper(newSearch,jobIds); + + //todo 暂时 + if (isCompanyUser) { + int current = esJobSearch.getCurrent() == null ? 0 : esJobSearch.getCurrent(); + current = Math.max(current, 0); // 非负校验 + int pageSize = esJobSearch.getPageSize() == null ? 10 : esJobSearch.getPageSize(); + pageSize = Math.min(pageSize, 50); // 限制最大条数 + int from = current * pageSize; + wrapper.orderByAsc(ESJobDocument::getId); + wrapper.limit(from, pageSize); + }else{ + wrapper.limit(esJobSearch.getPageSize()); + } + + List esJobDocuments = esJobDocumentMapper.selectList(wrapper); + + if (!isCompanyUser &&esJobDocuments.size() < esJobSearch.getPageSize()) { + // 定义要逐步放宽的搜索条件字段(不放松核心文本搜索条件) + List relaxConditions = new ArrayList<>(); + relaxConditions.add(() -> newSearch.setArea(null)); + relaxConditions.add(() -> newSearch.setExperience(null)); + relaxConditions.add(() -> newSearch.setMaxSalary(null)); + relaxConditions.add(() -> newSearch.setMinSalary(null)); + relaxConditions.add(() -> newSearch.setEducation(null)); + // 保存所有查询到的文档 + List allDocuments = new ArrayList<>(esJobDocuments); + + // 记录已经放宽的条件数量 + int relaxedConditions = 0; + + // 继续放宽条件直到满足分页或所有条件都已放宽 + while (allDocuments.size() < esJobSearch.getPageSize() + && relaxedConditions < relaxConditions.size()) { + // 放宽下一个条件 + relaxConditions.get(relaxedConditions).run(); + relaxedConditions++; + + // 查询新增的文档(不包含之前已经查询到的) + wrapper = getWrapper(newSearch, jobIds); + wrapper.limit(esJobSearch.getPageSize() - allDocuments.size()); + if (!allDocuments.isEmpty()) { + // 排除已经查询到的文档ID + Set existingIds = allDocuments.stream() + .map(ESJobDocument::getId) + .collect(Collectors.toSet()); + wrapper.not().in(ESJobDocument::getId, existingIds); + } + + List newDocuments = esJobDocumentMapper.selectList(wrapper); + allDocuments.addAll(newDocuments); + } + + // 如果总数超过分页大小,截取所需数量 + if (allDocuments.size() > esJobSearch.getPageSize()) { + esJobDocuments = allDocuments.subList(0, esJobSearch.getPageSize()); + } else { + esJobDocuments = allDocuments; + } + } + + return esJobDocuments; + } + + /** + * 拼装参数 + * @param appUser + * @param esJobSearch + * @param newSearch + */ + private void setJobSeekerParams(AppUser appUser, ESJobSearch esJobSearch, ESJobSearch newSearch,List jobIds) { + if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ + List jobTitle = appUser.getJobTitle(); + newSearch.setJobTitle(String.join(",", jobTitle)); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ + newSearch.setJobTitle(esJobSearch.getJobTitle()); + } + if(!StringUtil.isEmptyOrNull(appUser.getEducation())){ + newSearch.setEducation(appUser.getEducation()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ + newSearch.setEducation(esJobSearch.getEducation()); + } + + if(!StringUtil.isEmptyOrNull(appUser.getArea())){ + newSearch.setArea(appUser.getArea()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ + newSearch.setArea(esJobSearch.getArea()); + } + //影响后面按照经验打分,先注释 +// if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){ +// newSearch.setExperience(appUser.getWorkExperience()); +// } + if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ + newSearch.setExperience(esJobSearch.getExperience()); + } + if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){ + newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax())); + } + if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){ + newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin())); + } + // 请求参数覆盖用户资料中的薪资偏好 + if(Objects.nonNull(esJobSearch.getSalaryMin()) || Objects.nonNull(esJobSearch.getMinSalary())){ + newSearch.setMinSalary(Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary()); + } + if(Objects.nonNull(esJobSearch.getSalaryMax()) || Objects.nonNull(esJobSearch.getMaxSalary())){ + newSearch.setMaxSalary(Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ + newSearch.setJobType(esJobSearch.getJobType()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ + newSearch.setType(esJobSearch.getType()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ + newSearch.setDescription(esJobSearch.getDescription()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ + newSearch.setCompanyNature(esJobSearch.getCompanyNature()); + } + //屏蔽企业集合 + List jobs=appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); + if (jobs != null) { + jobIds.addAll(jobs.stream().distinct().collect(Collectors.toList())); + } + //查询投递集合 + List applyNames=jobApplyMapper.queryApplyNames(appUser.getUserId()); + newSearch.setApplyNames(applyNames); + //查询收藏集合 + List collectNames=jobCollectionMapper.queryCollectNames(appUser.getUserId()); + newSearch.setCollectNames(collectNames); + //工作经历集合 + UserWorkExperiences expQuery = new UserWorkExperiences(); + expQuery.setUserId(appUser.getUserId()); + List userWorkExperiences=userWorkExperiencesMapper.getWorkExperiencesList(expQuery); + newSearch.setUserWorkExperiences(userWorkExperiences); + } + + /** + * 新增全文检索数据 + */ + @Override + public Integer addESJobDocument(ESJobDocument esJobDocument) + { + return esJobDocumentMapper.insert(esJobDocument); + } + + /** + * 修改全文检索数据 + */ + @Override + public Integer updateESJobDocument(ESJobDocument esJobDocument) + { + + LambdaEsUpdateWrapper wrapper = new LambdaEsUpdateWrapper<>(); + wrapper.eq(ESJobDocument::getJobId, esJobDocument.getJobId()); + return esJobDocumentMapper.update(esJobDocument, wrapper); + } + + /** + * 根据ID查询全文检索数据 + */ + @Override + public ESJobDocument getESJobDocument(Long jobId) + { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper(); + wrapper.eq(ESJobDocument::getJobId, jobId); + return esJobDocumentMapper.selectOne(wrapper); + } + + /** + * 根据ID删除全文检索数据 + */ + @Override + public void deleteESJobDocument(String ids) + { + for (Long jobId : Convert.toLongArray(ids)) + { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper(); + wrapper.eq(ESJobDocument::getJobId, jobId); + esJobDocumentMapper.delete(wrapper); + } + } + + @Override + public EsPageInfo nearJob(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + //添加屏蔽的企业 + List jobIds=null; + if(SiteSecurityUtils.isLogin()) { + AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); + } + LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); + EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + return esJobDocumentEsPageInfo; + } + + @Override + public EsPageInfo countyJobList(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + LambdaEsQueryWrapper wrapper = getWrapper(jobQuery, null); + //添加屏蔽的企业 + List jobIds=null; + if(SiteSecurityUtils.isLogin()) { + AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); + wrapper.not().in(ESJobDocument::getJobId, jobIds); + } + if(jobQuery.getCountyIds()!=null){ + wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,jobQuery.getCountyIds())); + } + EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + return esJobDocumentEsPageInfo; + } + + @Override + public EsPageInfo subway(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + List jobIds=null; + if(SiteSecurityUtils.isLogin()) { + AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); + } + LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); + EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + return esJobDocumentEsPageInfo; + } + + @Override + public EsPageInfo commercialArea(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + List jobIds=null; + if(SiteSecurityUtils.isLogin()) { + AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + jobIds = appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(appUser.getUserId()); + } + LambdaEsQueryWrapper wrapper = getWrapper(jobQuery,jobIds); + if(jobQuery.getLongitude()!=null){ + wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); + } + EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + return esJobDocumentEsPageInfo; + } + + @Override + public List littleVideo(ESJobSearch esJobSearch) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.eq(ESJobDocument::getIsExplain, 1); + wrapper.limit(esJobSearch.getPageSize()); + return esJobDocumentMapper.selectList(wrapper); + } + + @Override + public void updateEs() { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + esJobDocumentMapper.selectList(wrapper).forEach(esJobDocument -> { + esJobDocument.setPostingDate(new Date()); + this.updateESJobDocument(esJobDocument); + }); + } + + @Override + public List littleVideoRandom(List cacheList, Integer count,String jobTitle) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.eq(ESJobDocument::getIsExplain, 1); + if(!ListUtil.isListEmptyOrNull(cacheList)){ + wrapper.not().in(ESJobDocument::getJobId,cacheList); + } + if(count==null){ + count = 1; + } + if(StringUtil.isEmptyOrNull(jobTitle)){ + wrapper.or(a -> a.like(ESJobDocument::getJobTitle, jobTitle, 5.0f) + .or() + .like(ESJobDocument::getDescription, jobTitle, 1.0f) + ); + } + wrapper.limit(count); + return esJobDocumentMapper.selectList(wrapper); + } + + private LambdaEsQueryWrapper getWrapper(ESJobSearch esJobSearch,List jobIds){ + + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + /*if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ + for (String keyWord:esJobSearch.getJobTitle().split(",")) { + wrapper.and(a -> a.match(ESJobDocument::getJobTitle, keyWord, 5.0f) + ); + } + + }*/ + //优化岗位名称查询,只有命中岗位名称或者分类就行 + String titleStr = esJobSearch.getJobTitle(); + String cateStr = esJobSearch.getJobCategory(); + boolean hasTitle = !StringUtil.isEmptyOrNull(titleStr); + boolean hasCate = !StringUtil.isEmptyOrNull(cateStr); + + if (hasTitle || hasCate) { + wrapper.and(sub -> { + if (hasTitle) { + String[] words = titleStr.split(","); + for (String w : words) { + String word = w.trim(); + if (StringUtil.isEmptyOrNull(word)) { + continue; + } + sub.or(inner -> inner + .match(ESJobDocument::getJobTitle, word, 5.0f) + .or() + .match(ESJobDocument::getJobCategory, word, 5.0f) + ); + } + } + if (hasCate) { + String[] words = cateStr.split(","); + for (String w : words) { + String word = w.trim(); + if (StringUtil.isEmptyOrNull(word)) { + continue; + } + sub.or(inner -> inner + .match(ESJobDocument::getJobTitle, word, 5.0f) + .or() + .match(ESJobDocument::getJobCategory, word, 5.0f) + ); + } + } + }); + } + + if(esJobSearch.getLongitude()!=null){ + wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(esJobSearch.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(esJobSearch.getLatitude().toString()), Double.parseDouble(esJobSearch.getLongitude().toString()))); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ + Integer maxValue = StringUtil.findMaxValue(esJobSearch.getEducation()); + wrapper.and(x->x.eq(ESJobDocument::getEducation_int,maxValue)); + } + // 薪资范围筛选:兼容前端字段 salaryMin/salaryMax 和 minSalary/maxSalary + Long userMinSalary = Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary(); + Long userMaxSalary = Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary(); + + // 岗位最高薪资必须在用户筛选范围内 + // 条件:岗位最大薪资 <= 用户最大薪资 AND 岗位最大薪资 >= 用户最小薪资 + if(Objects.nonNull(userMinSalary) && Objects.nonNull(userMaxSalary)){ + wrapper.and(x->x.le(ESJobDocument::getMaxSalary,userMaxSalary) + .ge(ESJobDocument::getMaxSalary,userMinSalary)); + } else if(Objects.nonNull(userMinSalary)){ + // 只有最小薪资:岗位最大薪资 >= 用户最小薪资 + wrapper.and(x->x.ge(ESJobDocument::getMaxSalary,userMinSalary)); + } else if(Objects.nonNull(userMaxSalary)){ + // 只有最大薪资:岗位最大薪资 <= 用户最大薪资 + wrapper.and(x->x.le(ESJobDocument::getMaxSalary,userMaxSalary)); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ + Integer maxValue = StringUtil.findMaxValue(esJobSearch.getExperience()); + wrapper.and(x->x.eq(ESJobDocument::getExperience_int,maxValue)); + } +// if(!StringUtil.isEmptyOrNull(esJobSearch.getJobCategory())){ +// String targetValue = esJobSearch.getJobCategory(); +// wrapper.and(x -> x.eq(ESJobDocument::getJobCategory, targetValue)); +// } + if(!StringUtil.isEmptyOrNull(esJobSearch.getScale())){ + Integer maxValue = StringUtil.findMaxValue(esJobSearch.getScale()); + wrapper.and(x->x.le(ESJobDocument::getScale,maxValue)); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ + List integers = StringUtil.convertStringToIntegerList(esJobSearch.getArea()); + wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ + wrapper.and(x->x.eq(ESJobDocument::getJobType,esJobSearch.getJobType())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ + wrapper.and(x->x.eq(ESJobDocument::getType,esJobSearch.getType())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ + wrapper.and(x->x.like(ESJobDocument::getDescription,esJobSearch.getDescription())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ + wrapper.and(x->x.eq(ESJobDocument::getCompanyNature,esJobSearch.getCompanyNature())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){ + wrapper.and(x->x.eq(ESJobDocument::getCode,esJobSearch.getCode())); + } + if(esJobSearch.getJobId()!=null){ + wrapper.and(x->x.eq(ESJobDocument::getJobId,esJobSearch.getJobId())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobAddress())){ + wrapper.and(x->x.like(ESJobDocument::getJobAddress,esJobSearch.getJobAddress())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobLocation())){ + wrapper.and(x->x.like(ESJobDocument::getJobLocation,esJobSearch.getJobLocation())); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyName())){ + wrapper.and(x->x.like(ESJobDocument::getCompanyName,esJobSearch.getCompanyName())); + } + // 1.简历工作经历(最高权重) + JobRecommendUtil.appendWorkExpMatch(wrapper, esJobSearch.getUserWorkExperiences()); + //投递记录 + JobRecommendUtil.appendJobTitleMatch(wrapper, esJobSearch.getApplyNames(), JobRecommendUtil.APPLY_TITLE_BOOST); + //收藏记录 + JobRecommendUtil.appendJobTitleMatch(wrapper, esJobSearch.getCollectNames(), JobRecommendUtil.COLLECT_TITLE_BOOST); + + //按时间段来查询数据 + Date startDate = esJobSearch.getStartDate(); + Date endDate = esJobSearch.getEndDate(); + if (Objects.nonNull(startDate) && Objects.nonNull(endDate)) { + String startStr = DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant()); + String endStr = DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant()); + wrapper.between(ESJobDocument::getPostingDate, startStr, endStr); + } else if (Objects.nonNull(startDate)) { + wrapper.ge(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant())); + } else if (Objects.nonNull(endDate)) { + wrapper.le(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant())); + } + // 先开启打分、优先按匹配分数排序(关键词前台命中越高越靠前) + wrapper.trackScores(); + wrapper.sortByScore(SortOrder.DESC); + if(Objects.nonNull(esJobSearch.getOrder())){ + switch (esJobSearch.getOrder()) { + case 0: // 推荐:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + break; + case 1: // 最热:权重 → 热度 + wrapper.orderByDesc(ESJobDocument::getIsHot); + wrapper.orderByDesc(ESJobDocument::getApplyNum); + wrapper.orderByDesc(ESJobDocument::getView); + break; + case 2: // 最新发布:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + break; + case 3: // 最大薪资:权重 → 最高薪资 + wrapper.orderByDesc(ESJobDocument::getMaxSalary); + break; + default: // 默认:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + } + } + //企业用户排除es去除jobIds + boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType()); + if (!isCompanyUser && !ListUtil.isListEmptyOrNull(jobIds)) { + wrapper.not().in(ESJobDocument::getJobId, jobIds); + } + return wrapper; + } + + //todo 存储用户搜索 + @Override + public EsPageInfo appList(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + if(SiteSecurityUtils.isLogin()){ + //AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId()); + List jobs=appUserBlockCompanyMapper.selectBlockCompanyIdsByUserIdJobs(SiteSecurityUtils.getUserId()); + if (!ListUtil.isListEmptyOrNull(jobs)) { + wrapper.not().in(ESJobDocument::getJobId, jobs); + } +// if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ +// List jobTitle = appUser.getJobTitle(); +// jobQuery.setJobTitle(String.join(",", jobTitle)); +// } + } + if(hasText(jobQuery.getJobTitle())){ + wrapper.and(a->a.match(ESJobDocument::getJobTitle,jobQuery.getJobTitle().trim(),5.0f)); + } + if(hasText(jobQuery.getEducation())){ + // 支持逗号分隔的多值筛选(如 "3,4") + String eduStr = jobQuery.getEducation().trim(); + if (eduStr.contains(",")) { + List eduList = StringUtil.convertStringToStringList(eduStr); + wrapper.and(a->a.in(ESJobDocument::getEducation,eduList)); + } else { + wrapper.and(a->a.eq(ESJobDocument::getEducation,eduStr)); + } + } + if(hasText(jobQuery.getArea())){ + List integers = StringUtil.convertStringToIntegerList(jobQuery.getArea().trim()); + wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); + } + if(!StringUtil.isEmptyOrNull(jobQuery.getExperience())){ + List integers = StringUtil.convertStringToIntegerList(jobQuery.getExperience()); + wrapper.and(a->a.in(ESJobDocument::getExperience,integers)); + } + Long userMinSalary = Objects.nonNull(jobQuery.getSalaryMin()) ? jobQuery.getSalaryMin() : jobQuery.getMinSalary(); + Long userMaxSalary = Objects.nonNull(jobQuery.getSalaryMax()) ? jobQuery.getSalaryMax() : jobQuery.getMaxSalary(); + if (Objects.nonNull(userMinSalary)) { + wrapper.filter(x -> x.ge(ESJobDocument::getMaxSalary, userMinSalary)); + } + if (Objects.nonNull(userMaxSalary)) { + wrapper.filter(x -> x.le(ESJobDocument::getMinSalary, userMaxSalary)); + } + if(hasText(jobQuery.getScaleDictCode())){ + wrapper.and(a->a.eq(ESJobDocument::getScaleDictCode,jobQuery.getScaleDictCode().trim())); + } + if(hasText(jobQuery.getJobCategory())){ + wrapper.and(a->a.eq(ESJobDocument::getJobCategory,jobQuery.getJobCategory().trim())); + } + if(hasText(jobQuery.getIndustry())){ + wrapper.and(a->a.eq(ESJobDocument::getIndustry,jobQuery.getIndustry().trim())); + } + if(jobQuery.getLongitude()!=null){ + wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); + } + if(jobQuery.getJobLocationAreaCode()!=null){ + wrapper.and(a->a.eq(ESJobDocument::getJobLocationAreaCode,jobQuery.getJobLocationAreaCode())); + } + if(hasText(jobQuery.getScale())){ + wrapper.and(a->a.eq(ESJobDocument::getScale,jobQuery.getScale().trim())); + } + if(hasText(jobQuery.getSearchValue())){ + wrapper.and(a->a.match(ESJobDocument::getDescription,jobQuery.getSearchValue().trim(),4.0f)); + } + if(hasText(jobQuery.getCompanyNature())){ + wrapper.and(a->a.eq(ESJobDocument::getCompanyNature,jobQuery.getCompanyNature().trim())); + } + if(hasText(jobQuery.getType())){ + wrapper.and(a->a.eq(ESJobDocument::getType,jobQuery.getType().trim())); + } + //开始时间 + Date startDate = jobQuery.getStartDate(); + Date endDate = jobQuery.getEndDate(); + if (Objects.nonNull(startDate) && Objects.nonNull(endDate)) { + String startStr = DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant()); + String endStr = DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant()); + wrapper.between(ESJobDocument::getPostingDate, startStr, endStr); + } else if (Objects.nonNull(startDate)) { + wrapper.ge(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(startDate.toInstant())); + } else if (Objects.nonNull(endDate)) { + wrapper.le(ESJobDocument::getPostingDate, DateUtils.ES_DATE_FORMATTER.format(endDate.toInstant())); + } + // 先开启打分、优先按匹配分数排序(关键词前台命中越高越靠前) +// wrapper.trackScores(); +// wrapper.sortByScore(SortOrder.DESC); + if(Objects.nonNull(jobQuery.getOrder())){ + switch (jobQuery.getOrder()) { + case 0: // 推荐:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + break; + case 1: // 最热:权重 → 热度 + wrapper.orderByDesc(ESJobDocument::getIsHot); + wrapper.orderByDesc(ESJobDocument::getApplyNum); + wrapper.orderByDesc(ESJobDocument::getView); + break; + case 2: // 最新发布:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + break; + case 3: // 最大薪资:权重 → 最高薪资 + wrapper.orderByDesc(ESJobDocument::getMaxSalary); + break; + default: // 默认:权重 → 最新发布 + wrapper.sort(SortBuilders.fieldSort("postingDate").order(SortOrder.DESC).missing("_last")); + } + } + EsPageInfo esJobDocumentEsPageInfo = esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + return esJobDocumentEsPageInfo; + } + @Override + public EsPageInfo aiList(ESJobSearch jobQuery) { + Integer pageNum = jobQuery.getCurrent(); + Integer pageSize = jobQuery.getPageSize(); + + // 第一次查询:包含区域条件 + EsPageInfo result = doAiListQuery(jobQuery, pageNum, pageSize, true); + + // 降级查询:如果没有找到岗位且有区域条件,忽略区域重新查询 + if ((result.getList() == null || result.getList().isEmpty()) + && (hasText(jobQuery.getArea()) || jobQuery.getJobLocationAreaCode() != null)) { + logger.info("AI岗位查询降级:忽略区域条件重新查询"); + result = doAiListQuery(jobQuery, pageNum, pageSize, false); + } + + return result; + } + + /** + * AI岗位查询核心逻辑 + * @param jobQuery 查询条件 + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param includeArea 是否包含区域条件 + */ + private EsPageInfo doAiListQuery(ESJobSearch jobQuery, Integer pageNum, Integer pageSize, boolean includeArea) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + + if(hasText(jobQuery.getJobTitle())){ + wrapper.and(a->a.match(ESJobDocument::getJobTitle,jobQuery.getJobTitle().trim(),5.0f) + .or() + .match(ESJobDocument::getDescription,jobQuery.getJobTitle().trim(),1.0f)); + } + if(hasText(jobQuery.getEducation())){ + wrapper.and(a->a.le(ESJobDocument::getEducation,jobQuery.getEducation().trim())); + } + // 区域条件:根据 includeArea 决定是否加入 + if(includeArea && hasText(jobQuery.getArea())){ + List integers = StringUtil.convertStringToIntegerList(jobQuery.getArea().trim()); + wrapper.and(x->x.in(ESJobDocument::getJobLocationAreaCode,integers)); + } + if(hasText(jobQuery.getExperience())){ + wrapper.and(a->a.le(ESJobDocument::getExperience,jobQuery.getExperience().trim())); + } + // 薪资范围:岗位最高薪资必须在用户筛选范围内 + if(Objects.nonNull(jobQuery.getMaxSalary())){ + wrapper.and(x->x.le(ESJobDocument::getMaxSalary,jobQuery.getMaxSalary())); + } + if(Objects.nonNull(jobQuery.getMinSalary())){ + wrapper.and(x->x.ge(ESJobDocument::getMaxSalary,jobQuery.getMinSalary())); + } + if(hasText(jobQuery.getScaleDictCode())){ + wrapper.and(a->a.eq(ESJobDocument::getScaleDictCode,jobQuery.getScaleDictCode().trim())); + } + if(hasText(jobQuery.getIndustry())){ + wrapper.and(a->a.eq(ESJobDocument::getIndustry,jobQuery.getIndustry().trim())); + } + if(jobQuery.getLongitude()!=null){ + wrapper.geoDistance(ESJobDocument::getLatAndLon,Double.valueOf(jobQuery.getRadius()), DistanceUnit.KILOMETERS,new GeoPoint(Double.parseDouble(jobQuery.getLatitude().toString()), Double.parseDouble(jobQuery.getLongitude().toString()))); + } + // 区域编码条件:根据 includeArea 决定是否加入 + if(includeArea && jobQuery.getJobLocationAreaCode()!=null){ + wrapper.and(a->a.eq(ESJobDocument::getJobLocationAreaCode,jobQuery.getJobLocationAreaCode())); + } + if(hasText(jobQuery.getScale())){ + wrapper.and(a->a.eq(ESJobDocument::getScale,jobQuery.getScale().trim())); + } + if(hasText(jobQuery.getSearchValue())){ + wrapper.and(a->a.match(ESJobDocument::getDescription,jobQuery.getSearchValue().trim(),4.0f)); + } + if(hasText(jobQuery.getCompanyNature())){ + wrapper.and(a->a.eq(ESJobDocument::getCompanyNature,jobQuery.getCompanyNature().trim())); + } + if(hasText(jobQuery.getType())){ + wrapper.and(a->a.eq(ESJobDocument::getType,jobQuery.getType().trim())); + } + if(Objects.nonNull(jobQuery.getOrder())){ + if (jobQuery.getOrder()==2){ + wrapper.orderByDesc(ESJobDocument::getPostingDate); + } + if (jobQuery.getOrder()==1){ + wrapper.orderByDesc(ESJobDocument::getIsHot); + wrapper.orderByDesc(ESJobDocument::getApplyNum); + wrapper.orderByDesc(ESJobDocument::getView); + } + } + return esJobDocumentMapper.pageQuery(wrapper, pageNum, pageSize); + } + /** + * 判断字符串是否有实际内容(非null、非空、非纯空白) + */ + private boolean hasText(String str) { + return str != null && !str.trim().isEmpty(); + } + + @Override + public ESJobDocument selectById(Long jobId) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.eq(ESJobDocument::getJobId,jobId); + return esJobDocumentMapper.selectOne(wrapper); + } + + @Override + public void fix() { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.eq(ESJobDocument::getIsExplain,1); + List esJobDocuments = esJobDocumentMapper.selectList(wrapper); + for (ESJobDocument esJobDocument : esJobDocuments) { + Job job = new Job(); + BeanUtils.copyProperties(esJobDocument,job); + job.setJobId(null); + jobMapper.insert(job); + } + } + + @Override + public void deleteJob(Long jobId) { + ensureJobDocumentIndexExists(); + LambdaEsQueryWrapper lambdaEsQueryWrapper = new LambdaEsQueryWrapper(); + lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,jobId); + esJobDocumentMapper.delete(lambdaEsQueryWrapper); + } + + @Override + public void updateJob(Long jobId) { + ESJobDocument esJobDocument = new ESJobDocument(); + Job job = jobMapper.selectById(jobId); + if(job!=null){ + if(job.getCompanyId()!=null){ + Company company=iCompanyService.selectCompanyByCompanyId(job.getCompanyId()); + if (company != null) { + job.setCode(company.getCode()); + job.setCompanyNature(company.getCompanyNature()); + } + } + } + + BeanUtils.copyBeanProp(esJobDocument, job); + esJobDocument.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); + esJobDocument.setAppJobUrl("https://xjshzly.longbiosphere.com/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes())); + if(!StringUtil.isEmptyOrNull(job.getScale())){ + esJobDocument.setScale(Integer.valueOf(job.getScale())); + }else { + esJobDocument.setScale(0); + } + if(!StringUtil.isEmptyOrNull(job.getExperience())){ + esJobDocument.setExperience_int(Integer.valueOf(job.getExperience())); + }else { + esJobDocument.setExperience("0"); + esJobDocument.setExperience_int(0); + } + if(!StringUtil.isEmptyOrNull(job.getEducation())){ + esJobDocument.setEducation_int(Integer.valueOf(job.getEducation())); + }else { + esJobDocument.setEducation("-1"); + esJobDocument.setEducation_int(-1); + } + if (esJobDocument.getLatitude() != null) { + esJobDocument.setLatAndLon(esJobDocument.getLatitude().toString() + "," + esJobDocument.getLongitude().toString()); + } + ensureJobDocumentIndexExists(); + + LambdaEsQueryWrapper lambdaEsQueryWrapper = new LambdaEsQueryWrapper(); + lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,job.getJobId()); + esJobDocumentMapper.delete(lambdaEsQueryWrapper); + esJobDocumentMapper.insert(esJobDocument); + } + + /** + * 确保job_document索引存在;缺失时懒创建(仅建空索引与映射,不重建全量数据)。 + * process-index-mode: manual 下easy-es不会自动建索引,缺失时delete/update等写操作 + * 会抛 index_not_found_exception 导致业务接口500并回滚事务,故在写操作前调用以自愈。 + */ + private void ensureJobDocumentIndexExists() { + try { + if (!esJobDocumentMapper.existsIndex("job_document")) { + esJobDocumentMapper.createIndex(); + logger.info("job_document索引不存在,已自动创建"); + } + } catch (Exception e) { + // 并发场景下其他请求可能已创建索引,忽略“索引已存在”类异常 + logger.warn("自动创建job_document索引异常(可能已被其他请求创建,忽略)", e); + } + } + + public List selectByIds(Long[] jobIds) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.in(ESJobDocument::getJobId,jobIds); + return esJobDocumentMapper.selectList(wrapper); + } + + /** + * pc端查询es + * @param esJobSearch + * @param jobIds + * @param appUser + * @return + */ + @Override + public List selectSysTextListExceptJobId(ESJobSearch esJobSearch, List jobIds,AppUser appUser) { + + ESJobSearch newSearch = new ESJobSearch(); + BeanUtils.copyProperties(esJobSearch,newSearch); + + // 判断是否为用户主动搜索(有明确的搜索条件),而非纯推荐场景 + boolean isExplicitSearch = !StringUtil.isEmptyOrNull(esJobSearch.getJobTitle()) + || !StringUtil.isEmptyOrNull(esJobSearch.getJobCategory()) + || !StringUtil.isEmptyOrNull(esJobSearch.getCompanyName()) + || !StringUtil.isEmptyOrNull(esJobSearch.getDescription()); + + // 用户资料偏好仅在"纯推荐"场景下作为个性化过滤条件; + // 用户主动搜索时不应被资料中的薪资/学历/经验/区域等偏好缩小结果范围 + if(appUser != null && !isExplicitSearch){ + if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){ + List jobTitle = appUser.getJobTitle(); + newSearch.setJobTitle(String.join(",", jobTitle)); + } + if(!StringUtil.isEmptyOrNull(appUser.getEducation())){ + newSearch.setEducation(appUser.getEducation()); + } + if(!StringUtil.isEmptyOrNull(appUser.getArea())){ + newSearch.setArea(appUser.getArea()); + } + if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){ + newSearch.setExperience(appUser.getWorkExperience()); + } + if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){ + newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax())); + } + if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){ + newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin())); + } + //查询投递集合 + List applyNames=jobApplyMapper.queryApplyNames(appUser.getUserId()); + newSearch.setApplyNames(applyNames); + //查询收藏集合 + List collectNames=jobCollectionMapper.queryCollectNames(appUser.getUserId()); + newSearch.setCollectNames(collectNames); + //工作经历集合 + UserWorkExperiences expQuery = new UserWorkExperiences(); + expQuery.setUserId(appUser.getUserId()); + List userWorkExperiences=userWorkExperiencesMapper.getWorkExperiencesList(expQuery); + newSearch.setUserWorkExperiences(userWorkExperiences); + } + + // 请求参数始终优先于用户资料偏好 + if(Objects.nonNull(esJobSearch.getSalaryMin()) || Objects.nonNull(esJobSearch.getMinSalary())){ + newSearch.setMinSalary(Objects.nonNull(esJobSearch.getSalaryMin()) ? esJobSearch.getSalaryMin() : esJobSearch.getMinSalary()); + } + if(Objects.nonNull(esJobSearch.getSalaryMax()) || Objects.nonNull(esJobSearch.getMaxSalary())){ + newSearch.setMaxSalary(Objects.nonNull(esJobSearch.getSalaryMax()) ? esJobSearch.getSalaryMax() : esJobSearch.getMaxSalary()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){ + newSearch.setArea(esJobSearch.getArea()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){ + newSearch.setEducation(esJobSearch.getEducation()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){ + newSearch.setExperience(esJobSearch.getExperience()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){ + newSearch.setJobTitle(esJobSearch.getJobTitle()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){ + newSearch.setJobType(esJobSearch.getJobType()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){ + newSearch.setType(esJobSearch.getType()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){ + newSearch.setDescription(esJobSearch.getDescription()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){ + newSearch.setCompanyNature(esJobSearch.getCompanyNature()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){ + newSearch.setCode(esJobSearch.getCode()); + } + if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyName())){ + newSearch.setCompanyName(esJobSearch.getCompanyName()); + } + + LambdaEsQueryWrapper wrapper = getWrapper(newSearch,jobIds); + + //todo 暂时 + wrapper.limit(esJobSearch.getPageSize()); + List esJobDocuments = esJobDocumentMapper.selectList(wrapper); + + if (esJobDocuments.size() < esJobSearch.getPageSize()) { + // 定义要逐步放宽的搜索条件字段(不放松核心文本搜索条件) + List relaxConditions = new ArrayList<>(); + relaxConditions.add(() -> newSearch.setArea(null)); + relaxConditions.add(() -> newSearch.setExperience(null)); + relaxConditions.add(() -> newSearch.setMaxSalary(null)); + relaxConditions.add(() -> newSearch.setMinSalary(null)); + relaxConditions.add(() -> newSearch.setEducation(null)); + // 保存所有查询到的文档 + List allDocuments = new ArrayList<>(esJobDocuments); + + // 记录已经放宽的条件数量 + int relaxedConditions = 0; + + // 继续放宽条件直到满足分页或所有条件都已放宽 + while (allDocuments.size() < esJobSearch.getPageSize() + && relaxedConditions < relaxConditions.size()) { + // 放宽下一个条件 + relaxConditions.get(relaxedConditions).run(); + relaxedConditions++; + + // 查询新增的文档(不包含之前已经查询到的) + wrapper = getWrapper(newSearch, jobIds); + wrapper.limit(esJobSearch.getPageSize() - allDocuments.size()); + if (!allDocuments.isEmpty()) { + // 排除已经查询到的文档ID + Set existingIds = allDocuments.stream() + .map(ESJobDocument::getId) + .collect(Collectors.toSet()); + wrapper.not().in(ESJobDocument::getId, existingIds); + } + + List newDocuments = esJobDocumentMapper.selectList(wrapper); + allDocuments.addAll(newDocuments); + } + + // 如果总数超过分页大小,截取所需数量 + if (allDocuments.size() > esJobSearch.getPageSize()) { + esJobDocuments = allDocuments.subList(0, esJobSearch.getPageSize()); + } else { + esJobDocuments = allDocuments; + } + } + + return esJobDocuments; + } + + /** + * 将 jobCategory 从数字ID解析为标签文字(如 "45" → "销售顾问") + * 如果已经是文字标签或解析失败,返回原值 + */ + private String resolveJobCategoryLabel(String jobCategory) { + if (StringUtils.isEmpty(jobCategory)) { + return jobCategory; + } + try { + Long categoryId = Long.parseLong(jobCategory); + JobTitle jobTitle = jobTitleMapper.selectById(categoryId); + if (jobTitle != null && StringUtils.isNotEmpty(jobTitle.getJobName())) { + return jobTitle.getJobName(); + } + } catch (NumberFormatException e) { + // 已经是文字标签,直接返回原值 + } + return jobCategory; + } + + /** + * 根据岗位id批量更新es + * @param jobIdList + */ + @Override + public void batchUpdateJob(List jobIdList) { + if (jobIdList == null || jobIdList.isEmpty()) { + return; + } + //索引初始化 + ensureJobDocumentIndexExists(); + //查询所有岗位数据 + List allJobList = jobMapper.selectBatchIds(jobIdList); + if (allJobList == null || allJobList.isEmpty()) { + LambdaEsQueryWrapper delWrapper = new LambdaEsQueryWrapper<>(); + delWrapper.in(ESJobDocument::getJobId, jobIdList); + esJobDocumentMapper.delete(delWrapper); + return; + } + //查询所有企业数据 + List companyIdList = allJobList.stream() + .map(Job::getCompanyId) + .filter(id -> id != null) + .distinct() + .collect(Collectors.toList()); + List companyList = new ArrayList<>(); + if (!companyIdList.isEmpty()) { + companyList = companyMapper.selectBatchIds(companyIdList); + } + Map companyMap = companyList.stream() + .collect(Collectors.toMap(Company::getCompanyId, c -> c)); + + // 批量转换ES + List esDocList = allJobList.stream() + .map(job -> convertToEsDoc(job, companyMap)) + .collect(Collectors.toList()); + + try { + // 批量删除旧索引 + LambdaEsQueryWrapper delWrapper = new LambdaEsQueryWrapper<>(); + delWrapper.in(ESJobDocument::getJobId, jobIdList); + esJobDocumentMapper.delete(delWrapper); + // 批量插入新索引 + esJobDocumentMapper.insertBatch(esDocList); + } catch (Exception e) { + throw new RuntimeException("ES批量更新失败", e); + } + } + + /** + * Job 转 ESJobDocument 抽取单独方法 + */ + private ESJobDocument convertToEsDoc(Job job, Map companyMap) { + ESJobDocument esDoc = new ESJobDocument(); + BeanUtils.copyBeanProp(esDoc, job); + // 企业编码赋值 + if (job.getCompanyId() != null) { + Company company = companyMap.get(job.getCompanyId()); + if (company != null) { + esDoc.setCode(company.getCode()); + esDoc.setCompanyNature(company.getCompanyNature()); + } + } + + esDoc.setJobCategory(resolveJobCategoryLabel(job.getJobCategory())); + esDoc.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ job.getJobId()); + esDoc.setScale(parseIntDefaultZero(job.getScale())); + //工作经验 + if (StringUtil.isEmptyOrNull(job.getExperience())) { + esDoc.setExperience("0"); + esDoc.setExperience_int(0); + } else { + Integer expInt = parseIntDefaultZero(job.getExperience()); + esDoc.setExperience_int(expInt); + } + + // 学历字段统一处理 + if (StringUtil.isEmptyOrNull(job.getEducation())) { + esDoc.setEducation("-1"); + esDoc.setEducation_int(-1); + } else { + Integer eduInt = parseIntDefaultMinusOne(job.getEducation()); + esDoc.setEducation_int(eduInt); + } + + // 经纬度拼接 + if (esDoc.getLatitude() != null && esDoc.getLongitude() != null) { + esDoc.setLatAndLon(esDoc.getLatitude() + "," + esDoc.getLongitude()); + } + + return esDoc; + } + + /** + * 字符串转数字,空/非法返回0 + */ + private Integer parseIntDefaultZero(String str) { + if (StringUtil.isEmptyOrNull(str)) { + return 0; + } + try { + return Integer.valueOf(str); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * 字符串转数字,空/非法返回-1 + */ + private Integer parseIntDefaultMinusOne(String str) { + if (StringUtil.isEmptyOrNull(str)) { + return -1; + } + try { + return Integer.valueOf(str); + } catch (NumberFormatException e) { + return -1; + } + } + + /** + * 岗位名称推荐 + * @param wrapper + * @param nameList + * @param boost + */ + /*private void appendJobTitleMatch(LambdaEsQueryWrapper wrapper, List nameList, Float boost) { + if (CollUtil.isEmpty(nameList)) { + return; + } + for (String title : nameList) { + wrapper.should(w -> w.match(ESJobDocument::getJobTitle, title, boost)); + } + }*/ +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java new file mode 100644 index 0000000..a21ed2f --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java @@ -0,0 +1,187 @@ +package com.ruoyi.cms.service.impl; + +import com.ruoyi.cms.mapper.JobMapper; +import com.ruoyi.cms.service.IJobTitleSuggestService; +import com.ruoyi.cms.util.JobTitleSuggestTokenizer; +import com.ruoyi.common.core.redis.RedisCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 岗位名称 IK 分词联想服务实现。 + */ +@Service +public class JobTitleSuggestServiceImpl implements IJobTitleSuggestService { + + private static final Logger logger = LoggerFactory.getLogger(JobTitleSuggestServiceImpl.class); + + /** + * Redis Hash:field 是 IK 分词,value 是包含该分词的完整岗位名称列表。 + */ + private static final String CACHE_KEY = "app:job:title:suggest"; + private static final String BUILDING_CACHE_KEY = CACHE_KEY + ":building"; + private static final String LOCK_KEY = CACHE_KEY + ":rebuild:lock"; + + private static final int DEFAULT_LIMIT = 10; + private static final int MAX_LIMIT = 20; + private static final int LOCK_EXPIRE_MINUTES = 30; + + @Autowired + private JobMapper jobMapper; + + @Autowired + private RedisCache redisCache; + + /** + * 确保服务启动后 Redis 中已有一份缓存;后续由 Quartz 定时任务周期性重建。 + */ + @PostConstruct + public void init() { + rebuildCache(); + } + + @Override + public List suggest(String keyword, Integer limit) { + String normalizedKeyword = JobTitleSuggestTokenizer.normalize(keyword); + if (normalizedKeyword.isEmpty()) { + return Collections.emptyList(); + } + + int resultLimit = normalizeLimit(limit); + try { + Map tokenTitles = redisCache.getCacheMap(CACHE_KEY); + if (tokenTitles == null || tokenTitles.isEmpty()) { + return Collections.emptyList(); + } + + // Hash field 只来自 IK 分词;这里仅对已有词项做前缀过滤,不生成新的前缀词。 + List> matchedTokens = tokenTitles.entrySet().stream() + .filter(entry -> entry.getKey() != null) + .filter(entry -> entry.getKey().startsWith(normalizedKeyword)) + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toList()); + + Set result = new LinkedHashSet<>(); + for (Map.Entry entry : matchedTokens) { + if (result.size() >= resultLimit) { + break; + } + + // 先返回匹配到的 IK 分词本身。 + result.add(entry.getKey()); + + // 再返回包含该 IK 分词的完整岗位名称。 + appendTitles(result, entry.getValue(), resultLimit); + } + return new ArrayList<>(result).subList(0, Math.min(result.size(), resultLimit)); + } catch (Exception e) { + // 联想属于非核心能力,Redis 异常时返回空列表,不影响岗位主搜索。 + logger.warn("查询岗位名称 IK 分词缓存失败,keyword={}", keyword, e); + return Collections.emptyList(); + } + } + + @Override + public void rebuildCache() { + String lockValue = UUID.randomUUID().toString(); + boolean lockAcquired = false; + try { + lockAcquired = redisCache.setIfAbsent( + LOCK_KEY, lockValue, LOCK_EXPIRE_MINUTES, TimeUnit.MINUTES); + if (!lockAcquired) { + logger.info("其他节点正在重建岗位名称 IK 分词缓存,本节点跳过"); + return; + } + + List sourceList = jobMapper.selectValidJobTitles(); + Map> cacheData = buildTokenTitles(sourceList); + + // 先写临时 Hash,再原子替换正式 Hash,避免接口读到半成品。 + redisCache.deleteObject(BUILDING_CACHE_KEY); + if (cacheData.isEmpty()) { + redisCache.deleteObject(CACHE_KEY); + } else { + redisCache.setCacheMap(BUILDING_CACHE_KEY, cacheData); + redisCache.redisTemplate.rename(BUILDING_CACHE_KEY, CACHE_KEY); + } + logger.info("岗位名称 IK 分词缓存重建完成,有效岗位名称{}个,IK 词{}个", + sourceList == null ? 0 : sourceList.size(), cacheData.size()); + } catch (Exception e) { + logger.error("重建岗位名称 IK 分词缓存失败", e); + } finally { + if (lockAcquired) { + redisCache.deleteObject(LOCK_KEY); + } + } + } + + private Map> buildTokenTitles(List sourceList) { + Map> tokenTitles = new TreeMap<>(); + if (sourceList == null) { + return Collections.emptyMap(); + } + + for (String title : sourceList) { + String displayTitle = normalizeDisplayTitle(title); + if (displayTitle.isEmpty()) { + continue; + } + + Set tokens = JobTitleSuggestTokenizer.tokenize(displayTitle); + for (String token : tokens) { + tokenTitles.computeIfAbsent(token, key -> new TreeSet<>()).add(displayTitle); + } + } + + Map> result = new LinkedHashMap<>(); + for (Map.Entry> entry : tokenTitles.entrySet()) { + result.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + return result; + } + + private void appendTitles(Set result, Object cachedTitles, int resultLimit) { + if (!(cachedTitles instanceof Iterable)) { + return; + } + for (Object cachedTitle : (Iterable) cachedTitles) { + if (cachedTitle == null || result.size() >= resultLimit) { + break; + } + String title = String.valueOf(cachedTitle); + if (!title.isEmpty()) { + result.add(title); + } + } + } + + private String normalizeDisplayTitle(String title) { + if (title == null) { + return ""; + } + return title.trim().replaceAll("\\s+", " "); + } + + private int normalizeLimit(Integer limit) { + if (limit == null) { + return DEFAULT_LIMIT; + } + return Math.max(1, Math.min(limit, MAX_LIMIT)); + } +} From 8bb28854880f375868fb4e64ec44c00769d50f7d Mon Sep 17 00:00:00 2001 From: chenshaohua <635616957@qq.com> Date: Thu, 30 Jul 2026 10:52:40 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E9=81=BF=E5=85=8D=E6=9F=A5=E8=AF=A2=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/cms/mapper/CompanyMapper.java | 2 + .../resources/mapper/app/CompanyMapper.xml | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java index 76f2eb2..2c18911 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java @@ -41,4 +41,6 @@ public interface CompanyMapper extends BaseMapper /** 查询企业未参加的已结束户外招聘会。 */ List selectMissedOutdoorFairList(@Param("companyId") Long companyId); + + public List selectCompanyCleanList(CompanySearch company); } diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml index e4786f3..b9afc71 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml @@ -333,4 +333,66 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ORDER BY f.end_time DESC, f.id DESC + + + + select company_id, name, location, industry, scale, del_flag, create_by, create_time, update_by, update_time, remark,code,description,nature,company_nature,total_recruitment,registered_address,contact_person,contact_person_phone,is_abnormal,not_pass_reason,status,case when status='2' then update_time else null end reject_time,is_imp_company,imp_company_type,enterprise_type,legal_person,legal_id_card,legal_phone,is_hrs from company cp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +