forked from baskbuddy/zhangshun
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
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); }
|
|
}
|
|
}
|
|
|