fix(push): InternalPushController 补传 deviceId;设备换绑时驱逐原用户

- InternalPushController.testOffline 补传 deviceId=null,修复编译失败
- DeviceTokenRepository 新增 findByAppKeyAndDeviceId / findByAppKeyAndToken
- registerToken 在同 appKey 下同设备/同 token 被新用户注册时,
  自动删除旧用户的 push token,防止旧用户继续收到新用户的消息

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-26 13:42:48 +08:00
父节点 68f6145c71
当前提交 b4c236d234
共有 3 个文件被更改,包括 22 次插入0 次删除

查看文件

@ -96,6 +96,7 @@ public class InternalPushController {
PushDiagnosticsService.TestPushResult result = diagnosticsService.sendTestOfflineMessage(
request.appKey(),
request.userId(),
null,
request.title(),
request.body(),
request.payload());

查看文件

@ -19,4 +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);
}

查看文件

@ -246,6 +246,23 @@ public class PushDispatcher {
String appVersion,
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)
.filter(prev -> !userId.equals(prev.getUserId()))
.ifPresent(prev -> {
log.info("Device {} re-registered by new user={}, evicting previous user={}", resolvedDeviceId, userId, prev.getUserId());
recordLog(prev, DeviceLoginLogEntity.EventType.UNREGISTER);
tokenRepository.delete(prev);
});
tokenRepository.findByAppKeyAndToken(appKey, token)
.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());
recordLog(prev, DeviceLoginLogEntity.EventType.UNREGISTER);
tokenRepository.delete(prev);
});
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId);
if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) {
existing = tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor);