fix(push): 修复设备换绑驱逐时 NonUniqueResultException

findByAppKeyAndDeviceId / findByAppKeyAndToken 改为返回 List,
避免 DB 中存在多条同 appKey+deviceId 记录时 Hibernate 抛出
NonUniqueResultException(HTTP 500)。
驱逐逻辑改为 stream + forEach,将所有旧用户记录一并清除。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-26 13:49:38 +08:00
父节点 86ab6e73ba
当前提交 7194126a4c
共有 2 个文件被更改,包括 11 次插入11 次删除

查看文件

@ -19,8 +19,8 @@ public interface DeviceTokenRepository extends JpaRepository<DeviceTokenEntity,
List<DeviceTokenEntity> findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId);
void deleteByAppKeyAndUserIdAndVendor(String appKey, String userId, DeviceTokenEntity.Vendor vendor);
void deleteByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId);
// Find device by appKey+deviceId regardless of userId (for cross-user eviction)
Optional<DeviceTokenEntity> findByAppKeyAndDeviceId(String appKey, String deviceId);
// Find device by appKey+token regardless of userId (token rotation detection)
Optional<DeviceTokenEntity> findByAppKeyAndToken(String appKey, String token);
// Find all entries for this device across all users (for cross-user eviction)
List<DeviceTokenEntity> findByAppKeyAndDeviceId(String appKey, String deviceId);
// Find all entries for this token across all users (token rotation detection)
List<DeviceTokenEntity> findByAppKeyAndToken(String appKey, String token);
}

查看文件

@ -247,18 +247,18 @@ public class PushDispatcher {
String romVersion) {
String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token);
// Evict previous owner: if this device (by deviceId OR token) is registered to another user, remove it
tokenRepository.findByAppKeyAndDeviceId(appKey, resolvedDeviceId)
// Evict all other users who hold this device (by deviceId) or this token
tokenRepository.findByAppKeyAndDeviceId(appKey, resolvedDeviceId).stream()
.filter(prev -> !userId.equals(prev.getUserId()))
.ifPresent(prev -> {
log.info("Device {} re-registered by new user={}, evicting previous user={}", resolvedDeviceId, userId, prev.getUserId());
.forEach(prev -> {
log.info("Device {} re-registered by user={}, evicting previous user={}", resolvedDeviceId, userId, prev.getUserId());
recordLog(prev, DeviceLoginLogEntity.EventType.UNREGISTER);
tokenRepository.delete(prev);
});
tokenRepository.findByAppKeyAndToken(appKey, token)
tokenRepository.findByAppKeyAndToken(appKey, token).stream()
.filter(prev -> !userId.equals(prev.getUserId()))
.ifPresent(prev -> {
log.info("Token re-registered by new user={}, evicting previous user={} deviceId={}", userId, prev.getUserId(), prev.getDeviceId());
.forEach(prev -> {
log.info("Token re-registered by user={}, evicting previous user={} deviceId={}", userId, prev.getUserId(), prev.getDeviceId());
recordLog(prev, DeviceLoginLogEntity.EventType.UNREGISTER);
tokenRepository.delete(prev);
});