@@ -1,5 +1,6 @@
package org.jiayunet.service ;
import org.jiayunet.ai.AiChatAbility ;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper ;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page ;
import lombok.extern.slf4j.Slf4j ;
@@ -8,11 +9,16 @@ import org.jiayunet.pojo.PageResult;
import org.jiayunet.pojo.dto.job.JobDetailDto ;
import org.jiayunet.pojo.dto.job.JobDto ;
import org.jiayunet.pojo.dto.job.JobMatchScoreDto ;
import org.jiayunet.pojo.dto.job.JobAgentRecommendDto ;
import org.jiayunet.pojo.param.job.JobAgentRecommendParam ;
import org.jiayunet.pojo.param.job.JobAgentTaskQueryParam ;
import org.jiayunet.pojo.param.job.JobApplyQueryParam ;
import org.jiayunet.pojo.param.job.JobFavoriteQueryParam ;
import org.jiayunet.pojo.param.job.JobQueryParam ;
import org.jiayunet.pojo.po.* ;
import org.jiayunet.tool.HttpTool ;
import org.jiayunet.exception.BusinessException ;
import org.jiayunet.exception.BusinessExpCodeEnum ;
import org.jiayunet.pojo.vo.JobApplyCountVo ;
import org.jiayunet.pojo.vo.JobFavoriteCountVo ;
import org.jiayunet.pojo.vo.JobListItemVo ;
@@ -20,6 +26,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.stereotype.Service ;
import com.fasterxml.jackson.databind.JsonNode ;
import java.time.Instant ;
import java.util.* ;
import java.util.stream.Collectors ;
@@ -66,6 +73,12 @@ public class JobService {
@Autowired
private JobMatchService jobMatchService ;
@Autowired
private UserJobIntentionMapper userJobIntentionMapper ;
@Autowired
private AiChatAbility aiChatAbility ;
/**
* 岗位列表查询
* <p>方法逻辑流程:</p>
@@ -96,7 +109,10 @@ public class JobService {
List < UserJobDislike > dislikes = userJobDislikeMapper . selectList ( new LambdaQueryWrapper < UserJobDislike > ( ) . eq ( UserJobDislike : : getUserId , userId ) ) ;
// 3. 提取排除列表
List < Long > excludeJobIds = dislikes . stream ( ) . map ( UserJobDislike : : getJobId ) . filter ( Objects : : nonNull ) . distinct ( ) . collect ( Collectors . toList ( ) ) ;
List < Long > excludeJobIds = new ArrayList < > ( dislikes . stream ( ) . map ( UserJobDislike : : getJobId ) . filter ( Objects : : nonNull ) . distinct ( ) . collect ( Collectors . toList ( ) ) ) ;
if ( param . getExcludeJobIds ( ) ! = null & & ! param . getExcludeJobIds ( ) . isEmpty ( ) ) {
excludeJobIds . addAll ( param . getExcludeJobIds ( ) ) ;
}
List < Long > excludeCompanyIds = dislikes . stream ( ) . map ( UserJobDislike : : getCompanyId ) . filter ( Objects : : nonNull ) . distinct ( ) . collect ( Collectors . toList ( ) ) ;
// 4. 提取不感兴趣的地区(直接使用,不扩展子级)
@@ -561,4 +577,104 @@ public class JobService {
return vo ;
}
/**
* 求职助手岗位推荐
* <p>1. 查求职意向构造筛选条件(无意向则不设条件) 2. 排除已推荐的+已投递的 3. 取前35条候选 4. AI精筛返回8-10个</p>
*/
public JobAgentRecommendDto recommendJobs ( JobAgentRecommendParam param , Long userId ) {
// 1. 查求职意向
UserJobIntention intention = userJobIntentionMapper . selectOne ( new LambdaQueryWrapper < UserJobIntention > ( ) . eq ( UserJobIntention : : getUserId , userId ) ) ;
// 2. 构造查询参数
JobQueryParam queryParam = new JobQueryParam ( ) ;
queryParam . setPageNum ( 1 ) ;
queryParam . setPageSize ( 35 ) ;
if ( intention ! = null ) {
queryParam . setCategoryIds ( intention . getCategoryIds ( ) ) ;
queryParam . setRegionCodes ( intention . getRegionCodes ( ) ) ;
queryParam . setIndustryIds ( intention . getIndustryIds ( ) ) ;
queryParam . setEmploymentType ( intention . getEmploymentType ( ) ) ;
}
// 3. 合并排除列表:入参传的 + 已投递/待投递的
List < Long > excludeIds = new ArrayList < > ( ) ;
if ( param . getExcludeJobIds ( ) ! = null ) {
excludeIds . addAll ( param . getExcludeJobIds ( ) ) ;
}
List < UserJobApplication > applications = userJobApplicationMapper . selectList ( new LambdaQueryWrapper < UserJobApplication > ( ) . eq ( UserJobApplication : : getUserId , userId ) ) ;
applications . forEach ( a - > excludeIds . add ( a . getJobId ( ) ) ) ;
queryParam . setExcludeJobIds ( excludeIds ) ;
// 4. 查询候选岗位(完整列表数据)
PageResult < JobDto > candidates = listJobs ( queryParam , userId ) ;
if ( candidates . getList ( ) . isEmpty ( ) ) {
JobAgentRecommendDto dto = new JobAgentRecommendDto ( ) ;
dto . setSummary ( " 暂无合适的岗位推荐 " ) ;
dto . setList ( Collections . emptyList ( ) ) ;
return dto ;
}
// 5. 批量查岗位详情(title + description + requirement)
List < Long > candidateJobIds = candidates . getList ( ) . stream ( ) . map ( JobDto : : getId ) . collect ( Collectors . toList ( ) ) ;
List < Job > jobDetails = jobMapper . selectList ( new LambdaQueryWrapper < Job > ( ) . in ( Job : : getId , candidateJobIds ) . select ( Job : : getId , Job : : getTitle , Job : : getDescription , Job : : getRequirement ) ) ;
Map < Long , Job > jobDetailMap = jobDetails . stream ( ) . collect ( Collectors . toMap ( Job : : getId , j - > j ) ) ;
// 6. 构造候选岗位映射(供AI返回后过滤使用)
Map < Long , JobDto > candidateMap = candidates . getList ( ) . stream ( ) . collect ( Collectors . toMap ( JobDto : : getId , d - > d ) ) ;
// 7. 构造AI输入
StringBuilder jobInfo = new StringBuilder ( ) ;
for ( JobDto dto : candidates . getList ( ) ) {
Job detail = jobDetailMap . get ( dto . getId ( ) ) ;
jobInfo . append ( " ID: " ) . append ( dto . getId ( ) )
. append ( " \ n标题: " ) . append ( dto . getTitle ( ) )
. append ( " \ n公司: " ) . append ( dto . getCompanyName ( ) )
. append ( " \ n岗位职责: " ) . append ( detail ! = null ? detail . getDescription ( ) : " " )
. append ( " \ n任职要求: " ) . append ( detail ! = null ? detail . getRequirement ( ) : " " )
. append ( " \ n--- \ n " ) ;
}
String preferenceInfo = param . getPreference ( ) ! = null ? param . getPreference ( ) : " 无特殊偏好 " ;
String systemPrompt = " 你是一个求职推荐助手。根据用户的偏好,从候选岗位中选出8-10个最合适的岗位。 " +
" 返回JSON格式:{ \" summary \" : \" 推荐说明(20字以内) \" , \" jobIds \" :[岗位ID列表]}。 " +
" 只返回JSON,不要其他内容。 " ;
String userMessage = " 【用户偏好】 \ n " + preferenceInfo + " \ n \ n【候选岗位】 \ n " + jobInfo ;
// 8. 调用AI
String aiResponse = aiChatAbility . chat ( " job-recommend " , systemPrompt , userMessage ) ;
String json = cleanAiResponse ( aiResponse ) ;
// 9. 解析AI返回,过滤出选中的岗位
try {
JsonNode root = HttpTool . objectMapper . readTree ( json ) ;
String summary = root . path ( " summary " ) . asText ( " 为你精选了合适的岗位 " ) ;
List < Long > selectedIds = new ArrayList < > ( ) ;
root . path ( " jobIds " ) . forEach ( node - > selectedIds . add ( node . asLong ( ) ) ) ;
List < JobDto > selectedJobs = selectedIds . stream ( )
. map ( candidateMap : : get )
. filter ( Objects : : nonNull )
. collect ( Collectors . toList ( ) ) ;
JobAgentRecommendDto result = new JobAgentRecommendDto ( ) ;
result . setSummary ( summary ) ;
result . setList ( selectedJobs ) ;
return result ;
} catch ( Exception e ) {
log . error ( " AI推荐结果解析失败, response={} " , json , e ) ;
throw new BusinessException ( BusinessExpCodeEnum . UNKNOWN_ERROR , " AI推荐结果解析失败 " ) ;
}
}
/** 清理AI返回的markdown代码块和控制字符 */
private String cleanAiResponse ( String response ) {
String json = response . trim ( ) ;
if ( json . startsWith ( " ``` " ) ) {
json = json . replaceAll ( " ^``` \\ w* \\ n? " , " " ) . replaceAll ( " \\ n?```$ " , " " ) . trim ( ) ;
}
json = json . replaceAll ( " [ \\ x00- \\ x08 \\ x0B \\ x0C \\ x0E- \\ x1F] " , " " ) ;
return json ;
}
}