feat(channel): add event handling service
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
package com.sino.mci.channel.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ChannelAccountEventRequest {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String eventId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String eventType;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String accountId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String tenantCode;
|
||||||
|
|
||||||
|
private String timestamp;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map<String, Object> data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package com.sino.mci.channel.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.sino.mci.channel.dto.ChannelAccountEventRequest;
|
||||||
|
import com.sino.mci.channel.entity.ChannelAccount;
|
||||||
|
import com.sino.mci.channel.entity.ChannelEventRecord;
|
||||||
|
import com.sino.mci.channel.repository.ChannelAccountMapper;
|
||||||
|
import com.sino.mci.channel.repository.ChannelEventRecordMapper;
|
||||||
|
import com.sino.mci.exception.BusinessException;
|
||||||
|
import com.sino.mci.send.entity.SendRecord;
|
||||||
|
import com.sino.mci.send.repository.SendRecordMapper;
|
||||||
|
import com.sino.mci.send.service.SendCallbackService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChannelAccountEventService {
|
||||||
|
|
||||||
|
private static final int LOGIN_STATUS_PENDING = 0;
|
||||||
|
private static final int LOGIN_STATUS_SUCCESS = 1;
|
||||||
|
private static final int LOGIN_STATUS_EXPIRED = 2;
|
||||||
|
private static final int LOGIN_STATUS_FAILED = 3;
|
||||||
|
|
||||||
|
private static final int ONLINE_STATUS_OFFLINE = 0;
|
||||||
|
private static final int ONLINE_STATUS_ONLINE = 1;
|
||||||
|
private static final int ONLINE_STATUS_ERROR = 2;
|
||||||
|
|
||||||
|
private static final int SEND_STATUS_SUCCESS = 2;
|
||||||
|
private static final int SEND_STATUS_FAILED = 3;
|
||||||
|
|
||||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
private final ChannelAccountMapper accountMapper;
|
||||||
|
private final ChannelEventRecordMapper eventRecordMapper;
|
||||||
|
private final SendRecordMapper sendRecordMapper;
|
||||||
|
private final SendCallbackService sendCallbackService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void handleEvent(ChannelAccountEventRequest request) {
|
||||||
|
if (isDuplicate(request.getEventId())) {
|
||||||
|
log.debug("重复事件已忽略: eventId={}", request.getEventId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelAccount account = findAccount(request.getTenantCode(), request.getAccountId());
|
||||||
|
|
||||||
|
switch (request.getEventType()) {
|
||||||
|
case "heartbeat":
|
||||||
|
handleHeartbeat(account, request.getData());
|
||||||
|
break;
|
||||||
|
case "login_status_changed":
|
||||||
|
handleLoginStatusChanged(account, request.getData());
|
||||||
|
break;
|
||||||
|
case "online_status_changed":
|
||||||
|
handleOnlineStatusChanged(account, request.getData());
|
||||||
|
break;
|
||||||
|
case "send_result":
|
||||||
|
handleSendResult(request.getData());
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
log.warn("channel-service 上报错误: accountId={}, data={}", request.getAccountId(), request.getData());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn("未知事件类型: {}", request.getEventType());
|
||||||
|
}
|
||||||
|
|
||||||
|
saveEventRecord(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDuplicate(String eventId) {
|
||||||
|
return eventRecordMapper.selectById(eventId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveEventRecord(ChannelAccountEventRequest request) {
|
||||||
|
ChannelEventRecord record = new ChannelEventRecord();
|
||||||
|
record.setEventId(request.getEventId());
|
||||||
|
record.setAccountId(request.getAccountId());
|
||||||
|
record.setEventType(request.getEventType());
|
||||||
|
record.setCreateTime(LocalDateTime.now());
|
||||||
|
eventRecordMapper.insert(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelAccount findAccount(String tenantCode, String accountId) {
|
||||||
|
ChannelAccount account = accountMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ChannelAccount>()
|
||||||
|
.eq(ChannelAccount::getTenantCode, tenantCode)
|
||||||
|
.eq(ChannelAccount::getAccountId, accountId));
|
||||||
|
if (account == null) {
|
||||||
|
throw new BusinessException("渠道账号不存在: " + accountId);
|
||||||
|
}
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHeartbeat(ChannelAccount account, Map<String, Object> data) {
|
||||||
|
account.setLoginStatus(parseLoginStatus(getString(data, "login_status")));
|
||||||
|
account.setOnlineStatus(parseOnlineStatus(getString(data, "online_status")));
|
||||||
|
account.setLastHeartbeat(LocalDateTime.now());
|
||||||
|
Integer riskLevel = getInteger(data, "risk_level");
|
||||||
|
if (riskLevel != null) {
|
||||||
|
account.setRiskLevel(riskLevel);
|
||||||
|
}
|
||||||
|
accountMapper.updateById(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLoginStatusChanged(ChannelAccount account, Map<String, Object> data) {
|
||||||
|
String current = getString(data, "current");
|
||||||
|
account.setLoginStatus(parseLoginStatus(current));
|
||||||
|
if ("success".equalsIgnoreCase(current)) {
|
||||||
|
account.setOnlineStatus(ONLINE_STATUS_ONLINE);
|
||||||
|
}
|
||||||
|
accountMapper.updateById(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOnlineStatusChanged(ChannelAccount account, Map<String, Object> data) {
|
||||||
|
account.setOnlineStatus(parseOnlineStatus(getString(data, "current")));
|
||||||
|
accountMapper.updateById(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSendResult(Map<String, Object> data) {
|
||||||
|
Long recordId = getLong(data, "record_id");
|
||||||
|
if (recordId == null) {
|
||||||
|
log.warn("send_result 事件缺少 record_id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SendRecord record = sendRecordMapper.selectById(recordId);
|
||||||
|
if (record == null) {
|
||||||
|
log.warn("发送记录不存在: recordId={}", recordId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean success = getBoolean(data, "success");
|
||||||
|
record.setSendStatus(Boolean.TRUE.equals(success) ? SEND_STATUS_SUCCESS : SEND_STATUS_FAILED);
|
||||||
|
record.setFinishTime(LocalDateTime.now());
|
||||||
|
record.setChannelResult(toJson(data.get("channel_result")));
|
||||||
|
sendRecordMapper.updateById(record);
|
||||||
|
|
||||||
|
sendCallbackService.sendCallback(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseLoginStatus(String status) {
|
||||||
|
if ("success".equalsIgnoreCase(status)) {
|
||||||
|
return LOGIN_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
if ("expired".equalsIgnoreCase(status)) {
|
||||||
|
return LOGIN_STATUS_EXPIRED;
|
||||||
|
}
|
||||||
|
if ("failed".equalsIgnoreCase(status)) {
|
||||||
|
return LOGIN_STATUS_FAILED;
|
||||||
|
}
|
||||||
|
return LOGIN_STATUS_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseOnlineStatus(String status) {
|
||||||
|
if ("online".equalsIgnoreCase(status)) {
|
||||||
|
return ONLINE_STATUS_ONLINE;
|
||||||
|
}
|
||||||
|
if ("error".equalsIgnoreCase(status)) {
|
||||||
|
return ONLINE_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
return ONLINE_STATUS_OFFLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getString(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getInteger(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number n) {
|
||||||
|
return n.intValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getLong(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number n) {
|
||||||
|
return n.longValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String s) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean getBoolean(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Boolean b) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJson(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return OBJECT_MAPPER.writeValueAsString(value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user