File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.itn.itnhub.provision;
import kr.itn.itnhub.config.MattermostProperties;
import kr.itn.itnhub.mattermost.MattermostClient;
import kr.itn.itnhub.mattermost.MattermostException;
import kr.itn.itnhub.org.Organization;
import kr.itn.itnhub.org.OrganizationMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* 기관의 채널 2개(문정원/법률검토)를 보장한다. VBA M07의 이식.
*
* 판정 순서 (한쪽 채널당):
* 1. DB에 Channel ID가 있으면 그대로 둔다 → CACHED
* 2. 서버에 내부명으로 채널이 있으면 ID를 가져온다 → RECOVERED
* 3. 표시명으로 있으면 ID를 가져온다 → RECOVERED
* 4. 없을 때만 새로 만든다 → CREATED
*
* 트랜잭션을 쓰지 않는다. 외부 API 호출이라 롤백이 불가능하고,
* 한쪽이 실패해도 성공한 쪽의 ID는 반드시 남아야 재시도가 안전해지기 때문이다.
* 절대 @Transactional을 붙이지 말 것.
*/
@Service
public class ChannelProvisionService {
private static final Logger log = LoggerFactory.getLogger(ChannelProvisionService.class);
private final MattermostClient mattermost;
private final OrganizationMapper mapper;
private final MattermostProperties props;
public ChannelProvisionService(MattermostClient mattermost,
OrganizationMapper mapper,
MattermostProperties props) {
this.mattermost = mattermost;
this.mapper = mapper;
this.props = props;
}
public ProvisionResult provision(Long orgId) {
Organization org = mapper.findById(orgId);
if (org == null) {
throw new IllegalArgumentException("기관을 찾을 수 없습니다: " + orgId);
}
if (!org.hasRequiredContact()) {
return new ProvisionResult(
ProvisionOutcome.FAILED,
ProvisionOutcome.FAILED,
"담당자 정보가 채워지지 않아 채널을 만들 수 없습니다.");
}
StringBuilder messages = new StringBuilder();
ProvisionOutcome mj = ensure(org, ChannelKind.MJ,
org.getChannelIdMj(), props.channelNameMj(), messages);
ProvisionOutcome law = ensure(org, ChannelKind.LAW,
org.getChannelIdLaw(), props.channelNameLaw(), messages);
return new ProvisionResult(mj, law, messages.toString().trim());
}
private ProvisionOutcome ensure(Organization org,
ChannelKind kind,
String storedChannelId,
String label,
StringBuilder messages) {
if (storedChannelId != null && !storedChannelId.isBlank()) {
return ProvisionOutcome.CACHED;
}
String internalName = ChannelNaming.internalName(
org.getOrgNo(), org.getOrgName(), kind);
String displayName = ChannelNaming.displayName(
org.getOrgNo(), org.getOrgName(), label);
try {
Optional<String> recovered = mattermost.findChannelIdByInternalName(internalName);
if (recovered.isEmpty()) {
recovered = mattermost.findChannelIdByDisplayName(displayName);
}
if (recovered.isPresent()) {
applyChannelId(org.getId(), kind, recovered.get());
return ProvisionOutcome.RECOVERED;
}
String created = mattermost.createPrivateChannel(internalName, displayName);
applyChannelId(org.getId(), kind, created);
return ProvisionOutcome.CREATED;
} catch (MattermostException e) {
log.warn("채널 처리 실패 org={} channel={}", org.getOrgName(), displayName, e);
messages.append("[").append(label).append("] ")
.append(e.getMessage()).append(System.lineSeparator());
return ProvisionOutcome.FAILED;
}
}
/**
* Channel ID를 저장할 컬럼(mj/law)을 결정하는 유일한 지점.
* 문자열 키 기반 동적 분기 대신 정적 타입(ChannelKind)으로만 분기하여,
* 잘못된 컬럼에 쓰는 실수를 컴파일 타임에 막는다.
*/
private void applyChannelId(Long orgId, ChannelKind kind, String channelId) {
if (kind == ChannelKind.MJ) {
mapper.updateChannelIdMj(orgId, channelId);
} else {
mapper.updateChannelIdLaw(orgId, channelId);
}
}
}