轻松聘AI开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

68 lines
3.7 KiB

package com.mydreamjob.talentinvitation;
import com.mydreamjob.talentdiscovery.CandidateKeyGenerator;
import com.mydreamjob.talentdiscovery.TalentDiscoveryCandidateSource;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.List;
/** 在当前 V41 合格候选池内重算 opaque key,绝不通过 key 反解学生 ID。 */
@Component
@RequiredArgsConstructor
public class TalentInvitationCandidateResolver {
private final TalentDiscoveryCandidateSource candidateSource;
private final CandidateKeyGenerator candidateKeyGenerator;
public Resolution resolve(Long companyId, Long jobId, String candidateKey, LocalDateTime capturedNow) {
if (companyId == null || jobId == null || candidateKey == null || capturedNow == null) {
return Resolution.none();
}
byte[] expected = candidateKey.getBytes(StandardCharsets.UTF_8);
Long match = null;
int matches = 0;
List<TalentDiscoveryCandidateSource.CandidateSeed> candidates = candidateSource.findEligibleCandidates(companyId);
List<Long> candidateIds = candidates.stream().map(TalentDiscoveryCandidateSource.CandidateSeed::studentUserId).toList();
Map<Long, TalentDiscoveryCandidateSource.PreferenceSignal> preferences = safeMap(candidateSource.findPreferences(candidateIds));
Map<Long, TalentDiscoveryCandidateSource.ScoreSignal> riasec = safeMap(candidateSource.findLatestRiasec(candidateIds));
Map<Long, TalentDiscoveryCandidateSource.ScoreSignal> ability = safeMap(candidateSource.findLatestAbility(candidateIds, capturedNow));
LocalDate bucket = capturedNow.toLocalDate();
for (TalentDiscoveryCandidateSource.CandidateSeed candidate : candidates) {
// 候选池要求至少有一个可用推荐信号;缺失信号不得按 0 分放行。
if (!hasSignal(candidate.studentUserId(), preferences, riasec, ability)) continue;
String generated = candidateKeyGenerator.generate(companyId, jobId, candidate.studentUserId(), bucket);
if (MessageDigest.isEqual(expected, generated.getBytes(StandardCharsets.UTF_8))) {
match = candidate.studentUserId();
matches++;
}
}
return matches == 1 ? new Resolution(match, true) : Resolution.none();
}
private boolean hasSignal(Long studentId,
Map<Long, TalentDiscoveryCandidateSource.PreferenceSignal> preferences,
Map<Long, TalentDiscoveryCandidateSource.ScoreSignal> riasec,
Map<Long, TalentDiscoveryCandidateSource.ScoreSignal> ability) {
TalentDiscoveryCandidateSource.PreferenceSignal p = preferences.get(studentId);
if (p != null && (p.expectedSalaryMin() != null || p.expectedSalaryMax() != null
|| !p.cities().isEmpty() || !p.employmentTypes().isEmpty()
|| !p.industries().isEmpty() || !p.jobTypes().isEmpty())) return true;
TalentDiscoveryCandidateSource.ScoreSignal i = riasec.get(studentId);
if (i != null && i.scores() != null && !i.scores().isEmpty()) return true;
TalentDiscoveryCandidateSource.ScoreSignal a = ability.get(studentId);
return a != null && a.scores() != null && !a.scores().isEmpty();
}
private <K, V> Map<K, V> safeMap(Map<K, V> value) {
return value == null ? Map.of() : value;
}
public record Resolution(Long studentUserId, boolean matched) {
public static Resolution none() { return new Resolution(null, false); }
}
}