岗位列表接口

This commit is contained in:
zk
2026-03-20 18:32:03 +08:00
parent 13520a6e39
commit 28e551285d
14 changed files with 734 additions and 19 deletions
@@ -0,0 +1,36 @@
package org.jiayunet.controller;
import lombok.AllArgsConstructor;
import org.jiayunet.pojo.PageResult;
import org.jiayunet.pojo.dto.job.JobDto;
import org.jiayunet.pojo.param.job.JobQueryParam;
import org.jiayunet.service.JobService;
import org.jiayunet.tool.UserSecurityTool;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 岗位接口
*
* @author zk
*/
@RestController
@RequestMapping("/job")
@AllArgsConstructor
public class JobController {
private final JobService jobService;
/**
* 岗位列表查询
* <p>支持按地区/岗位类型/行业/工作类型筛选,自动排除不感兴趣的岗位/公司/地区/行业,返回匹配度和收藏状态</p>
*/
@PostMapping("/list")
public PageResult<JobDto> listJobs(@Validated @RequestBody JobQueryParam param) {
Long userId = UserSecurityTool.getUserId();
return jobService.listJobs(param, userId);
}
}
@@ -0,0 +1,56 @@
package org.jiayunet.pojo.dto.job;
import lombok.Data;
import java.util.List;
/**
* 岗位列表出参
*
* @author zk
*/
@Data
public class JobDto {
/** 岗位ID */
private Long id;
/** 岗位名称 */
private String title;
/** 薪资描述 */
private String salary;
/** 公司名称 */
private String companyName;
/** 公司简称 */
private String companyShortName;
/** 公司类型 */
private String companyType;
/** 公司Logo */
private String companyLogoUrl;
/** 地区名称 */
private String regionName;
/** 岗位类型名称 */
private String categoryName;
/** 岗位标签 */
private List<String> tags;
/** 来源链接 */
private String sourceUrl;
/** 是否收藏 */
private Boolean isFavorite;
/** 匹配总分(0-90 */
private Integer matchScore;
/** 匹配度详情 */
private JobMatchScoreDto matchDetail;
}
@@ -0,0 +1,25 @@
package org.jiayunet.pojo.dto.job;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 岗位匹配度详情DTO
*
* @author zk
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobMatchScoreDto {
/** 行业得分(0-100,百分制) */
private Integer industryScore;
/** 技能得分(0-100,百分制) */
private Integer skillScore;
/** 经验得分(0-100,百分制) */
private Integer experienceScore;
}
@@ -0,0 +1,29 @@
package org.jiayunet.pojo.param.job;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jiayunet.pojo.PageParam;
import java.util.List;
/**
* 岗位查询入参
*
* @author zk
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class JobQueryParam extends PageParam {
/** 地区编码列表 */
private List<String> regionCodes;
/** 岗位类型ID列表 */
private List<Long> categoryIds;
/** 行业ID列表 */
private List<Long> industryIds;
/** 工作类型 0=全职 1=兼职 */
private Integer employmentType;
}
@@ -0,0 +1,153 @@
package org.jiayunet.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jiayunet.mapper.*;
import org.jiayunet.pojo.PageResult;
import org.jiayunet.pojo.dto.job.JobDto;
import org.jiayunet.pojo.dto.job.JobMatchScoreDto;
import org.jiayunet.pojo.param.job.JobQueryParam;
import org.jiayunet.pojo.po.*;
import org.jiayunet.pojo.vo.JobListItemVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* 岗位服务
* <p>主要功能:岗位列表查询、匹配度计算</p>
* <p>依赖:JobMatchService(匹配度计算)</p>
* <p>使用表:bg_job(查询岗位)、bg_user_job_dislike(查询不感兴趣记录)、bg_user_job_favorite(查询收藏状态)、bg_china_regions_code(扩展地区子级)、bg_job_category(扩展岗位类型子级)、bg_industry(扩展行业子级)</p>
*
* @author zk
*/
@Slf4j
@Service
public class JobService {
@Autowired
private JobMapper jobMapper;
@Autowired
private UserJobDislikeMapper userJobDislikeMapper;
@Autowired
private UserJobFavoriteMapper userJobFavoriteMapper;
@Autowired
private ChinaRegionsCodeMapper regionMapper;
@Autowired
private JobCategoryMapper categoryMapper;
@Autowired
private IndustryMapper industryMapper;
@Autowired
private org.jiayunet.service.JobMatchService jobMatchService;
/**
* 岗位列表查询
* <p>1. 扩展筛选条件子级 2. 查询不感兴趣记录 3. 扩展不感兴趣的地区和行业子级 4. 执行分页查询 5. 查询收藏状态 6. 批量计算匹配度 7. 组装返回数据</p>
*/
public PageResult<JobDto> listJobs(JobQueryParam param, Long userId) {
// 1. 扩展筛选条件的子级
List<String> expandedRegionCodes = expandRegionCodes(param.getRegionCodes());
List<Long> expandedCategoryIds = expandCategoryIds(param.getCategoryIds());
List<Long> expandedIndustryIds = expandIndustryIds(param.getIndustryIds());
// 2. 查询用户不感兴趣记录
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> excludeCompanyIds = dislikes.stream().map(UserJobDislike::getCompanyId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
// 4. 提取不感兴趣的地区(直接使用,不扩展子级)
List<String> excludeRegionCodes = dislikes.stream().map(UserJobDislike::getRegionCode).filter(Objects::nonNull).distinct().collect(Collectors.toList());
// 5. 提取不感兴趣的行业(直接使用,不扩展子级)
List<Long> excludeIndustryIds = dislikes.stream().map(UserJobDislike::getIndustryId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
// 6. 执行分页查询
Page<JobListItemVo> page = jobMapper.selectJobPage(param.toPage(), expandedRegionCodes, expandedCategoryIds, expandedIndustryIds, param.getEmploymentType(), excludeJobIds, excludeCompanyIds, excludeRegionCodes, excludeIndustryIds);
// 7. 查询收藏状态
List<Long> jobIds = page.getRecords().stream().map(JobListItemVo::getId).collect(Collectors.toList());
Map<Long, Boolean> favoriteMap = getFavoriteMap(userId, jobIds);
// 8. 批量计算匹配度
Map<Long, Map<String, Integer>> matchScoreMap = jobMatchService.batchCalculateMatchScore(page.getRecords(), userId);
// 9. 组装返回数据
List<JobDto> dtoList = page.getRecords().stream().map(vo -> {
JobDto dto = new JobDto();
BeanUtils.copyProperties(vo, dto);
dto.setIsFavorite(favoriteMap.getOrDefault(vo.getId(), false));
Map<String, Integer> scoreMap = matchScoreMap.get(vo.getId());
if (scoreMap != null) {
JobMatchScoreDto matchScore = new JobMatchScoreDto(scoreMap.get("industryScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore"));
dto.setMatchScore(scoreMap.get("totalScore"));
dto.setMatchDetail(matchScore);
} else {
dto.setMatchScore(0);
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0));
}
return dto;
}).collect(Collectors.toList());
return new PageResult<>(page.getCurrent(), page.getSize(), page.getTotal(), dtoList);
}
/**
* 扩展地区编码(包含自身和所有子级)
* <p>一次查询:code本身 + provinceCode匹配 + cityCode匹配</p>
*/
private List<String> expandRegionCodes(List<String> codes) {
if (codes == null || codes.isEmpty()) {
return Collections.emptyList();
}
List<ChinaRegionsCode> regions = regionMapper.selectList(new LambdaQueryWrapper<ChinaRegionsCode>().in(ChinaRegionsCode::getCode, codes).or().in(ChinaRegionsCode::getProvinceCode, codes).or().in(ChinaRegionsCode::getCityCode, codes));
return regions.stream().map(ChinaRegionsCode::getCode).distinct().collect(Collectors.toList());
}
/**
* 扩展岗位类型ID(包含自身和所有子级)
* <p>一次查询:id本身 + rootId匹配 + parentId匹配</p>
*/
private List<Long> expandCategoryIds(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
List<JobCategory> categories = categoryMapper.selectList(new LambdaQueryWrapper<JobCategory>().in(JobCategory::getId, ids).or().in(JobCategory::getRootId, ids).or().in(JobCategory::getParentId, ids));
return categories.stream().map(JobCategory::getId).distinct().collect(Collectors.toList());
}
/**
* 扩展行业ID(包含自身和所有子级)
* <p>一次查询:id本身 + rootId匹配 + parentId匹配</p>
*/
private List<Long> expandIndustryIds(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
List<Industry> industries = industryMapper.selectList(new LambdaQueryWrapper<Industry>().in(Industry::getId, ids).or().in(Industry::getRootId, ids).or().in(Industry::getParentId, ids));
return industries.stream().map(Industry::getId).distinct().collect(Collectors.toList());
}
/**
* 查询收藏状态Map
* <p>批量查询用户对岗位的收藏状态</p>
*/
private Map<Long, Boolean> getFavoriteMap(Long userId, List<Long> jobIds) {
if (jobIds == null || jobIds.isEmpty()) {
return Collections.emptyMap();
}
List<UserJobFavorite> favorites = userJobFavoriteMapper.selectList(new LambdaQueryWrapper<UserJobFavorite>().eq(UserJobFavorite::getUserId, userId).in(UserJobFavorite::getJobId, jobIds));
return favorites.stream().collect(Collectors.toMap(UserJobFavorite::getJobId, f -> true));
}
}