轻松聘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.
 
 
 

93 lines
4.6 KiB

package com.mydreamjob.bidirectionalmatch;
import com.mydreamjob.application.Application;
import com.mydreamjob.application.ApplicationRepository;
import com.mydreamjob.auth.User;
import com.mydreamjob.auth.UserRepository;
import com.mydreamjob.company.Company;
import com.mydreamjob.company.CompanyRepository;
import com.mydreamjob.job.Job;
import com.mydreamjob.job.JobService;
import com.mydreamjob.notification.NotificationService;
import com.mydreamjob.profile.StudentProfile;
import com.mydreamjob.profile.StudentProfileRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 匹配成功通知(Outbox 发送)。
*
* <p>由 {@code application_match_events} 中 MATCH_SUCCEEDED(PENDING) 事件驱动;
* 通知通过 NotificationService.create 幂等键落库(同一 application 每方只产生一条),
* 事件标记 SENT;任何失败保持 PENDING,由调度任务重试。重试不会重复发通知。
* <b>REQUIRES_NEW</b>:afterCommit/调度器调用时若处于已完成的同步上下文,REQUIRED 会加入已完成事务
* 导致通知不落库;独立事务保证通知总能提交。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MatchNotificationService {
public static final String TYPE_MATCH_SUCCEEDED = "MATCH_SUCCEEDED";
private final ApplicationMatchEventRepository eventRepository;
private final ApplicationMatchRecordRepository recordRepository;
private final ApplicationRepository applicationRepository;
private final JobService jobService;
private final CompanyRepository companyRepository;
private final StudentProfileRepository profileRepository;
private final UserRepository userRepository;
private final NotificationService notificationService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendMatchSucceeded(Long applicationId) {
ApplicationMatchEvent event = eventRepository
.findByApplicationIdAndEventTypeAndDedupKey(applicationId,
ApplicationMatchEvent.EVENT_MATCH_SUCCEEDED,
ApplicationMatchEvent.EVENT_MATCH_SUCCEEDED)
.orElse(null);
if (event == null || ApplicationMatchEvent.NOTIFY_SENT.equals(event.getNotificationStatus())) {
return; // 无事件或已发送
}
ApplicationMatchRecord record = recordRepository.findByApplicationId(applicationId).orElse(null);
Application app = applicationRepository.findById(applicationId).orElse(null);
if (record == null || app == null) return;
Job job = jobService.getJob(record.getJobId());
Company company = companyRepository.findById(record.getCompanyId()).orElse(null);
String jobTitle = job.getTitle() != null ? job.getTitle() : "岗位";
String companyName = company != null && company.getName() != null ? company.getName() : "企业";
String candidateName = resolveCandidateName(record.getCandidateId());
// 求职者
notificationService.create(record.getCandidateId(), TYPE_MATCH_SUCCEEDED,
"双向匹配成功",
"你与「" + companyName + "」的岗位「" + jobTitle + "」已达成双向匹配,可与企业直聊",
"MATCH", applicationId, "match-succeeded-" + applicationId + "-candidate");
// 企业
if (company != null) {
notificationService.create(company.getOwnerUserId(), TYPE_MATCH_SUCCEEDED,
"双向匹配成功",
"候选人「" + candidateName + "」与你发布的岗位「" + jobTitle + "」已达成双向匹配,可与其直聊",
"MATCH", applicationId, "match-succeeded-" + applicationId + "-company");
}
event.setNotificationStatus(ApplicationMatchEvent.NOTIFY_SENT);
eventRepository.save(event);
log.info("MATCH_SUCCEEDED notification sent: applicationId={}", applicationId);
}
private String resolveCandidateName(Long candidateId) {
StudentProfile profile = profileRepository.findByUserId(candidateId).orElse(null);
if (profile != null && profile.getRealName() != null && !profile.getRealName().isBlank()) {
return profile.getRealName();
}
User user = userRepository.findById(candidateId).orElse(null);
if (user != null && user.getNickname() != null && !user.getNickname().isBlank()) {
return user.getNickname();
}
return "候选人";
}
}