2026-05-05 17:54:59 +08:00
|
|
|
package com.xuqm.push.service;
|
|
|
|
|
|
|
|
|
|
import com.xuqm.common.security.JwtUtil;
|
|
|
|
|
import com.xuqm.push.entity.DeviceLoginLogEntity;
|
|
|
|
|
import com.xuqm.push.entity.DeviceTokenEntity;
|
|
|
|
|
import com.xuqm.push.repository.DeviceLoginLogRepository;
|
|
|
|
|
import com.xuqm.push.repository.DeviceTokenRepository;
|
|
|
|
|
import io.jsonwebtoken.Claims;
|
|
|
|
|
import org.springframework.data.domain.Page;
|
|
|
|
|
import org.springframework.data.domain.PageRequest;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
|
|
|
|
|
@Service
|
|
|
|
|
public class PushDiagnosticsService {
|
|
|
|
|
|
|
|
|
|
private final DeviceTokenRepository tokenRepository;
|
|
|
|
|
private final DeviceLoginLogRepository logRepository;
|
|
|
|
|
private final ImPresenceClient presenceClient;
|
|
|
|
|
private final PushDispatcher pushDispatcher;
|
|
|
|
|
private final JwtUtil jwtUtil;
|
|
|
|
|
|
|
|
|
|
public PushDiagnosticsService(DeviceTokenRepository tokenRepository,
|
|
|
|
|
DeviceLoginLogRepository logRepository,
|
|
|
|
|
ImPresenceClient presenceClient,
|
|
|
|
|
PushDispatcher pushDispatcher,
|
|
|
|
|
JwtUtil jwtUtil) {
|
|
|
|
|
this.tokenRepository = tokenRepository;
|
|
|
|
|
this.logRepository = logRepository;
|
|
|
|
|
this.presenceClient = presenceClient;
|
|
|
|
|
this.pushDispatcher = pushDispatcher;
|
|
|
|
|
this.jwtUtil = jwtUtil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PushTokenDiagnostics searchByToken(String token, String appIdHint) {
|
|
|
|
|
Optional<DeviceTokenEntity> tokenMatch = tokenRepository.findFirstByToken(token);
|
2026-05-07 19:39:42 +08:00
|
|
|
String appKey = appIdHint;
|
2026-05-05 17:54:59 +08:00
|
|
|
String userId = null;
|
|
|
|
|
String tokenType = "UNKNOWN";
|
|
|
|
|
|
|
|
|
|
if (tokenMatch.isPresent()) {
|
|
|
|
|
DeviceTokenEntity device = tokenMatch.get();
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey = device.getAppId();
|
2026-05-05 17:54:59 +08:00
|
|
|
userId = device.getUserId();
|
|
|
|
|
tokenType = "PUSH";
|
|
|
|
|
} else {
|
|
|
|
|
Optional<ImPresenceClient.PresenceStatus> resolved = presenceClient.resolveToken(token);
|
|
|
|
|
if (resolved.isPresent()) {
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey = resolved.get().appKey();
|
2026-05-05 17:54:59 +08:00
|
|
|
userId = resolved.get().userId();
|
|
|
|
|
tokenType = "IM";
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
Claims claims = jwtUtil.parse(token);
|
|
|
|
|
userId = claims.getSubject();
|
2026-05-07 19:39:42 +08:00
|
|
|
String claimAppId = claims.get("appKey", String.class);
|
|
|
|
|
appKey = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId;
|
2026-05-05 17:54:59 +08:00
|
|
|
tokenType = "IM";
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
// Keep UNKNOWN and return an empty diagnostic.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 19:39:42 +08:00
|
|
|
if (appKey == null || appKey.isBlank() || userId == null || userId.isBlank()) {
|
|
|
|
|
return new PushTokenDiagnostics(tokenType, appKey, userId, false, 0L, false, null, List.of(), List.of());
|
2026-05-05 17:54:59 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-07 19:39:42 +08:00
|
|
|
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
|
|
|
|
|
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
|
|
|
|
|
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
|
|
|
|
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
|
2026-05-05 17:54:59 +08:00
|
|
|
.stream()
|
|
|
|
|
.map(DeviceInfo::from)
|
|
|
|
|
.toList();
|
|
|
|
|
DeviceInfo deliverable = deliverableDevices.isEmpty() ? null : deliverableDevices.get(0);
|
|
|
|
|
boolean canSendOffline = !presence.online() && !deliverableDevices.isEmpty();
|
|
|
|
|
return new PushTokenDiagnostics(
|
|
|
|
|
tokenType,
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey,
|
2026-05-05 17:54:59 +08:00
|
|
|
userId,
|
|
|
|
|
presence.online(),
|
|
|
|
|
presence.lastSeenAt(),
|
|
|
|
|
canSendOffline,
|
|
|
|
|
deliverable,
|
|
|
|
|
deliverableDevices,
|
|
|
|
|
devices.stream().map(DeviceInfo::from).toList());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 19:39:42 +08:00
|
|
|
public PushTokenDiagnostics searchByUserId(String appKey, String userId) {
|
|
|
|
|
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
|
|
|
|
|
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
|
|
|
|
|
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
|
|
|
|
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
|
2026-05-05 23:17:57 +08:00
|
|
|
.stream()
|
|
|
|
|
.map(DeviceInfo::from)
|
|
|
|
|
.toList();
|
|
|
|
|
DeviceInfo deliverable = deliverableDevices.isEmpty() ? null : deliverableDevices.get(0);
|
|
|
|
|
boolean canSendOffline = !presence.online() && !deliverableDevices.isEmpty();
|
|
|
|
|
return new PushTokenDiagnostics(
|
|
|
|
|
"USER",
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey,
|
2026-05-05 23:17:57 +08:00
|
|
|
userId,
|
|
|
|
|
presence.online(),
|
|
|
|
|
presence.lastSeenAt(),
|
|
|
|
|
canSendOffline,
|
|
|
|
|
deliverable,
|
|
|
|
|
deliverableDevices,
|
|
|
|
|
devices.stream().map(DeviceInfo::from).toList());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 19:39:42 +08:00
|
|
|
public TestPushResult sendTestOfflineMessage(String appKey, String userId, String title, String body, String payload) {
|
|
|
|
|
List<DeviceInfo> targets = pushDispatcher.selectedPushTargets(appKey, userId)
|
2026-05-05 17:54:59 +08:00
|
|
|
.stream()
|
|
|
|
|
.map(DeviceInfo::from)
|
|
|
|
|
.toList();
|
|
|
|
|
if (!targets.isEmpty()) {
|
2026-05-07 19:39:42 +08:00
|
|
|
pushDispatcher.pushToUser(appKey, userId, title, body, payload);
|
2026-05-05 17:54:59 +08:00
|
|
|
}
|
2026-05-07 19:39:42 +08:00
|
|
|
return new TestPushResult(appKey, userId, !targets.isEmpty(), targets.size(), targets);
|
2026-05-05 17:54:59 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-07 19:39:42 +08:00
|
|
|
public Page<DeviceLoginLogEntity> deviceLogs(String appKey, String userId, int page, int size) {
|
2026-05-05 17:54:59 +08:00
|
|
|
int safePage = Math.max(page, 0);
|
|
|
|
|
int safeSize = Math.min(Math.max(size, 1), 200);
|
2026-05-07 19:39:42 +08:00
|
|
|
return logRepository.findByAppIdAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize));
|
2026-05-05 17:54:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public record PushTokenDiagnostics(
|
|
|
|
|
String tokenType,
|
2026-05-07 19:39:42 +08:00
|
|
|
String appKey,
|
2026-05-05 17:54:59 +08:00
|
|
|
String userId,
|
|
|
|
|
boolean online,
|
|
|
|
|
long lastSeenAt,
|
|
|
|
|
boolean canSendOfflineMessage,
|
|
|
|
|
DeviceInfo deliverableDevice,
|
|
|
|
|
List<DeviceInfo> deliverableDevices,
|
|
|
|
|
List<DeviceInfo> devices) {}
|
|
|
|
|
|
|
|
|
|
public record TestPushResult(
|
2026-05-07 19:39:42 +08:00
|
|
|
String appKey,
|
2026-05-05 17:54:59 +08:00
|
|
|
String userId,
|
|
|
|
|
boolean sent,
|
|
|
|
|
int targetCount,
|
|
|
|
|
List<DeviceInfo> targets) {}
|
|
|
|
|
|
|
|
|
|
public record DeviceInfo(
|
|
|
|
|
String id,
|
|
|
|
|
String vendor,
|
|
|
|
|
String tokenPreview,
|
|
|
|
|
String platform,
|
|
|
|
|
String deviceId,
|
|
|
|
|
String brand,
|
|
|
|
|
String model,
|
|
|
|
|
String osVersion,
|
|
|
|
|
String appVersion,
|
|
|
|
|
boolean receivePush,
|
|
|
|
|
LocalDateTime lastLoginAt,
|
|
|
|
|
LocalDateTime updatedAt) {
|
|
|
|
|
static DeviceInfo from(DeviceTokenEntity entity) {
|
|
|
|
|
return new DeviceInfo(
|
|
|
|
|
entity.getId(),
|
|
|
|
|
entity.getVendor().name(),
|
|
|
|
|
PushDispatcher.previewToken(entity.getToken()),
|
|
|
|
|
entity.getPlatform(),
|
|
|
|
|
entity.getDeviceId(),
|
|
|
|
|
entity.getBrand(),
|
|
|
|
|
entity.getModel(),
|
|
|
|
|
entity.getOsVersion(),
|
|
|
|
|
entity.getAppVersion(),
|
|
|
|
|
entity.isReceivePush(),
|
|
|
|
|
entity.getLastLoginAt(),
|
|
|
|
|
entity.getUpdatedAt());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|