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 发送)。 * *
由 {@code application_match_events} 中 MATCH_SUCCEEDED(PENDING) 事件驱动; * 通知通过 NotificationService.create 幂等键落库(同一 application 每方只产生一条), * 事件标记 SENT;任何失败保持 PENDING,由调度任务重试。重试不会重复发通知。 * REQUIRES_NEW: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 "候选人"; } }