修改竞争力分析

This commit is contained in:
sh
2026-03-23 16:27:16 +08:00
parent 158a656d5e
commit 40b8788227
2 changed files with 172 additions and 82 deletions

View File

@@ -14,9 +14,12 @@ import com.ruoyi.cms.mapper.AppUserMapper;
import com.ruoyi.cms.mapper.JobApplyMapper;
import com.ruoyi.cms.mapper.JobMapper;
import com.ruoyi.cms.service.IBussinessDictTypeService;
import com.ruoyi.cms.util.RoleUtils;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.common.core.domain.entity.AppUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@@ -186,22 +189,6 @@ public class JobCollectionServiceImpl extends ServiceImpl<JobCollectionMapper,Jo
throw new RuntimeException("岗位不存在");
}
// 获取所有申请该岗位的用户
List<AppUser> appUsers = appUserMapper.selectByJobId(jobId);
Long applyCount = (long) appUsers.size();
if (appUsers.isEmpty()) {
// 没有人申请返回默认值或0
CompetitivenessResponse emptyResponse = new CompetitivenessResponse();
emptyResponse.setTotalApplicants(0);
emptyResponse.setMatchScore(0);
emptyResponse.setRank(0);
emptyResponse.setPercentile(0);
RadarChart radarChart = new RadarChart();
emptyResponse.setRadarChart(radarChart);
return emptyResponse;
}
// ================== 数据字典映射(用于排序比较)==================
// 假设你有字典工具类,如 DictUtils.getSort("education", value) 返回排序值
// 这里我们用 Map 模拟字典排序(越小要求越低)
@@ -212,81 +199,53 @@ public class JobCollectionServiceImpl extends ServiceImpl<JobCollectionMapper,Jo
// 构建学历排序映射dictLabel -> dictSort越小要求越低
Map<String, Integer> educationRank = educationDict.stream()
.collect(Collectors.toMap(
BussinessDictData::getDictLabel,
BussinessDictData::getDictValue,
data -> Math.toIntExact(data.getDictSort()),
(a, b) -> a // 若有重复 key保留第一个
));
Map<String, Integer> experienceRank = experienceDict.stream()
.collect(Collectors.toMap(
BussinessDictData::getDictLabel,
BussinessDictData::getDictValue,
data -> Math.toIntExact(data.getDictSort()),
(a, b) -> a
));
// ================== 提取岗位要求 ==================
Integer jobEducationRank = educationRank.getOrDefault(job.getEducation(), 999);
Integer jobExperienceRank = experienceRank.getOrDefault(job.getExperience(), 999);
Integer jobMinSalary = job.getMinSalary() != null ? job.getMinSalary().intValue() : 0;
String jobLocation = job.getJobLocation();
// 获取所有申请该岗位的用户
List<AppUser> appUsers = appUserMapper.selectByJobId(jobId);
appUsers = appUsers == null ? new ArrayList<>() : appUsers;
Long applyCount = (long) appUsers.size();
//获取求职者状态
Long loginUserId = RoleUtils.getAppUserId();
boolean isLogin = loginUserId != null;
AppUser currentAppUser = new AppUser();
if (isLogin) {
AppUser tempUser = appUserMapper.selectById(loginUserId);
if (tempUser != null && !StringUtil.IS_COMPANY_USER.equals(tempUser.getIsCompanyUser())) {
currentAppUser = tempUser;
}
}
if (appUsers.isEmpty()) {
// 没有人申请返回默认值或0
CompetitivenessResponse emptyResponse = new CompetitivenessResponse();
emptyResponse.setTotalApplicants(0);
//计算匹配度
int realMatchScore = isLogin && !StringUtil.IS_COMPANY_USER.equals(currentAppUser.getIsCompanyUser())? calculateUserMatchScore(currentAppUser, job, educationRank, experienceRank): 0;
emptyResponse.setMatchScore(realMatchScore);
emptyResponse.setRank(0);
emptyResponse.setPercentile(0);
emptyResponse.setRadarChart(buildUserRadarChart(currentAppUser,job,educationRank,experienceRank));
return emptyResponse;
}
// ================== 聚合用户数据用于分析 ==================
List<UserCompetitiveness> userScores = new ArrayList<>();
for (AppUser user : appUsers) {
int matchScore = 0;
int totalFactors = 6; // 年龄、经验、学历、技能(暂用期望岗位匹配)、薪资、地点
// 1. 学历匹配(越接近越好)
Integer userEduRank = educationRank.getOrDefault(user.getEducation(), 999);
if (userEduRank <= jobEducationRank) {
matchScore += 1; // 达标
} else if (userEduRank == jobEducationRank + 1) {
matchScore += 0.5; // 略高也算匹配
}
// 2. 经验匹配
Integer userExpRank = experienceRank.getOrDefault(user.getExperience(), 999);
if (userExpRank >= jobExperienceRank) {
matchScore += 1; // 满足经验要求
}
// 3. 薪资匹配(用户期望 <= 岗位最大薪资,且不低于最小太多)
Integer userMinSalary = parseSalary(user.getSalaryMin());
if (userMinSalary != null && userMinSalary <= job.getMaxSalary()) {
if (userMinSalary >= job.getMinSalary()) {
matchScore += 1;
} else if (userMinSalary >= job.getMinSalary() * 0.8) {
matchScore += 0.5;
}
}
// 4. 地点匹配
if (user.getArea() != null) {
if(user.getArea().contains(jobLocation) || jobLocation.contains(user.getArea())){
matchScore += 1;
}
}
// 5. 年龄估算(从生日计算)
int userAge = getUserAge(DateUtils.stringToDateWithYmd(user.getBirthDate(),DateUtils.YYYY_MM_DD));
// 假设最佳年龄区间为 22-35越接近越匹配
if (userAge >= 22 && userAge <= 35) {
matchScore += 1;
} else if (userAge >= 18 && userAge <= 45) {
matchScore += 0.5;
}
// 6. 技能/岗位匹配(简化:期望岗位是否包含该岗位关键词)
boolean jobMatch = user.getJobTitle() != null &&
user.getJobTitle().stream().anyMatch(title -> title.contains(job.getJobTitle()) || job.getJobTitle().contains(title));
if (jobMatch) {
matchScore += 1;
}
// 匹配度0-6 → 映射为 0-100 分
int finalScore = (int) Math.round((matchScore / (double) totalFactors) * 100);
userScores.add(new UserCompetitiveness(user, finalScore));
int matchScore = calculateUserMatchScore(user, job, educationRank, experienceRank);
userScores.add(new UserCompetitiveness(user, matchScore));
}
// 按匹配度降序
@@ -314,19 +273,128 @@ public class JobCollectionServiceImpl extends ServiceImpl<JobCollectionMapper,Jo
response.setTotalApplicants(Math.toIntExact(applyCount));
// Top 用户的匹配分
int topMatchScore = userScores.get(0).getScore();
/*int topMatchScore = userScores.get(0).getScore();
response.setMatchScore(topMatchScore);
// 排名Top1
response.setRank(1);
// 百分位top用户超过多少人100%
response.setPercentile(100);
// 排名Top1
response.setRank(1);*/
if (isLogin && !StringUtil.IS_COMPANY_USER.equals(currentAppUser.getIsCompanyUser())) {
int userMatchScore = calculateUserMatchScore(currentAppUser, job, educationRank, experienceRank);
response.setMatchScore(userMatchScore);
int userRank = 1;
for (UserCompetitiveness uc : userScores) {
if (uc.getScore() > userMatchScore) {
userRank++;
}
}
response.setRank(userRank);
int percentile = (int) Math.round(((applyCount - userRank) * 1.0 / applyCount) * 100);
response.setPercentile(percentile);
} else {
response.setMatchScore(0);
response.setRank(0);
response.setPercentile(0);
}
response.setRadarChart(radarChart);
return response;
}
/**
* 构建雷达图
* @param user
* @param job
* @param educationRank
* @param experienceRank
* @return
*/
private RadarChart buildUserRadarChart(AppUser user, Job job, Map<String, Integer> educationRank, Map<String, Integer> experienceRank) {
RadarChart radarChart = new RadarChart();
int ageScore = getAgeScore(DateUtils.stringToDateWithYmd(user.getBirthDate(),DateUtils.YYYY_MM_DD));
int expScore = getExperienceScore(user.getExperience(), job.getExperience(), experienceRank);
int eduScore = getEducationScore(user.getEducation(), job.getEducation(), educationRank);
int skillScore = getSkillScore(user, job);
int salaryScore = getSalaryScore(user, job);
int locScore = getLocationScore(user, job);
// 赋值雷达图
radarChart.setAge(ageScore);
radarChart.setExperience(expScore);
radarChart.setEducation(eduScore);
radarChart.setSkill(skillScore);
radarChart.setSalary(salaryScore);
radarChart.setLocation(locScore);
return radarChart;
}
/**
* 构建匹配度
* @param user
* @param job
* @param educationRank
* @param experienceRank
* @return
*/
private int calculateUserMatchScore(AppUser user, Job job, Map<String, Integer> educationRank, Map<String, Integer> experienceRank) {
int matchScore = 0;
int totalFactors = 6;
Integer userEduRank = educationRank.getOrDefault(user.getEducation(), 999);
Integer jobEduRank = educationRank.getOrDefault(job.getEducation(), 999);
//判断学历
if (userEduRank <= jobEduRank) {
matchScore += 1;
} else if (userEduRank == jobEduRank + 1) {
matchScore += 0.5;
}
//判断工作经验
Integer userExpRank = experienceRank.getOrDefault(user.getExperience(), 999);
Integer jobExpRank = experienceRank.getOrDefault(job.getExperience(), 999);
if (userExpRank >= jobExpRank) {
matchScore += 1;
}
//判断薪资
Integer userMinSalary = parseSalary(user.getSalaryMin());
if (userMinSalary != null && userMinSalary <= job.getMaxSalary()) {
if (userMinSalary >= job.getMinSalary()) {
matchScore += 1;
} else if (userMinSalary >= job.getMinSalary() * 0.8) {
matchScore += 0.5;
}
}
//判断区域
if (user.getArea() != null && job.getJobLocationAreaCode() != null) {
//if(user.getArea().contains(job.getJobLocation()) || job.getJobLocation().contains(user.getArea())){
String userArea = user.getArea().trim();
String jobArea = job.getJobLocationAreaCode().trim();
if(userArea.equals(jobArea)){
matchScore += 1;
}
}
//判断年龄
if (!StringUtil.IS_COMPANY_USER.equals(user.getIsCompanyUser())) {
int userAge = 0;
String birthDate = user.getBirthDate();
if (StringUtils.isNotBlank(birthDate)) {
userAge = getUserAge(DateUtils.stringToDateWithYmd(birthDate, DateUtils.YYYY_MM_DD));
}else if (StringUtils.isNotBlank(user.getIdCard())) {
birthDate = StringUtil.getBirthDateFromIdCard(user.getIdCard());
userAge = getUserAge(DateUtils.stringToDateWithYmd(birthDate, DateUtils.YYYY_MM_DD));
}
if (userAge >= 22 && userAge <= 35) {
matchScore += 1;
} else if (userAge >= 18 && userAge <= 45) {
matchScore += 0.5;
}
}
//判断岗位
boolean jobMatch = user.getJobTitle() != null &&
user.getJobTitle().stream().anyMatch(title -> title.contains(job.getJobTitle()) || job.getJobTitle().contains(title));
if (jobMatch) {
matchScore += 1;
}
return (int) Math.round((matchScore / (double) totalFactors) * 100);
}
private int getUserAge(Date birthDate) {
if (birthDate == null) return 0;
try {

View File

@@ -74,4 +74,26 @@ public class RoleUtils {
}
return appUser.getIsCompanyUser();
}
/**
* 获取userId
* @return
*/
public static Long getAppUserId(){
try {
Long userId=null;
LoginSiteUser loginSiteUser = SiteSecurityUtils.getLoginUser();
AppUser appUser = loginSiteUser.getUser();
if (appUser != null) {
userId=appUser.getUserId();
}else{
LoginUser loginUser = SecurityUtils.getLoginUser();
SysUser sysUser = loginUser.getUser();
userId=sysUser.getAppUserId();
}
return userId;
} catch (Exception e) {
return null;
}
}
}