improvement: Device External ID (UUID) 도입 (#275)
This commit is contained in:
parent
8ccde89dc0
commit
44f6defa84
|
|
@ -7,7 +7,7 @@ public class DeviceAgreeRequestDto
|
|||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[JsonPropertyName("push_agreed")]
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ public class DeviceDeleteRequestDto
|
|||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ public class DeviceInfoRequestDto
|
|||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace SPMS.Application.DTOs.Device;
|
|||
public class DeviceInfoResponseDto
|
||||
{
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("device_token")]
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public class DeviceListResponseDto
|
|||
public class DeviceSummaryDto
|
||||
{
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ namespace SPMS.Application.DTOs.Device;
|
|||
|
||||
public class DeviceRegisterRequestDto
|
||||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[JsonPropertyName("device_token")]
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace SPMS.Application.DTOs.Device;
|
|||
public class DeviceRegisterResponseDto
|
||||
{
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("is_new")]
|
||||
public bool IsNew { get; set; }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ public class DeviceTagsRequestDto
|
|||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[JsonPropertyName("tags")]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ public class DeviceUpdateRequestDto
|
|||
{
|
||||
[Required]
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("device_token")]
|
||||
public string? DeviceToken { get; set; }
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class PushLogItemDto
|
|||
public string MessageCode { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("device_id")]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public class PushScheduleRequestDto
|
|||
[Required]
|
||||
public string SendType { get; set; } = string.Empty;
|
||||
|
||||
public long? DeviceId { get; set; }
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public class PushSendRequestDto
|
|||
public string MessageCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public long DeviceId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
public Dictionary<string, string>? Variables { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public interface IDeviceService
|
|||
Task<DeviceInfoResponseDto> GetInfoAsync(long serviceId, DeviceInfoRequestDto request);
|
||||
Task UpdateAsync(long serviceId, DeviceUpdateRequestDto request);
|
||||
Task DeleteAsync(long serviceId, DeviceDeleteRequestDto request);
|
||||
Task AdminDeleteAsync(long deviceId);
|
||||
Task AdminDeleteAsync(string externalDeviceId);
|
||||
Task<DeviceListResponseDto> GetListAsync(long? serviceId, DeviceListRequestDto request);
|
||||
Task<byte[]> ExportAsync(long? serviceId, DeviceExportRequestDto request);
|
||||
Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ namespace SPMS.Application.Interfaces;
|
|||
|
||||
public interface ITokenCacheService
|
||||
{
|
||||
Task<CachedDeviceInfo?> GetDeviceInfoAsync(long serviceId, long deviceId);
|
||||
Task SetDeviceInfoAsync(long serviceId, long deviceId, CachedDeviceInfo info);
|
||||
Task InvalidateAsync(long serviceId, long deviceId);
|
||||
Task<CachedDeviceInfo?> GetDeviceInfoAsync(long serviceId, string deviceId);
|
||||
Task SetDeviceInfoAsync(long serviceId, string deviceId, CachedDeviceInfo info);
|
||||
Task InvalidateAsync(long serviceId, string deviceId);
|
||||
Task InvalidateByServiceAsync(long serviceId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ public class DeviceService : IDeviceService
|
|||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.ExternalDeviceId = request.DeviceId;
|
||||
existing.Platform = platform;
|
||||
existing.OsVersion = request.OsVersion;
|
||||
existing.AppVersion = request.AppVersion;
|
||||
|
|
@ -43,14 +44,15 @@ public class DeviceService : IDeviceService
|
|||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
_deviceRepository.Update(existing);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
await _tokenCache.InvalidateAsync(serviceId, existing.Id);
|
||||
await _tokenCache.InvalidateAsync(serviceId, existing.ExternalDeviceId);
|
||||
|
||||
return new DeviceRegisterResponseDto { DeviceId = existing.Id, IsNew = false };
|
||||
return new DeviceRegisterResponseDto { DeviceId = existing.ExternalDeviceId, IsNew = false };
|
||||
}
|
||||
|
||||
var device = new Device
|
||||
{
|
||||
ServiceId = serviceId,
|
||||
ExternalDeviceId = request.DeviceId,
|
||||
DeviceToken = request.DeviceToken,
|
||||
Platform = platform,
|
||||
OsVersion = request.OsVersion,
|
||||
|
|
@ -64,18 +66,18 @@ public class DeviceService : IDeviceService
|
|||
|
||||
await _deviceRepository.AddAsync(device);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
return new DeviceRegisterResponseDto { DeviceId = device.Id, IsNew = true };
|
||||
return new DeviceRegisterResponseDto { DeviceId = device.ExternalDeviceId, IsNew = true };
|
||||
}
|
||||
|
||||
public async Task<DeviceInfoResponseDto> GetInfoAsync(long serviceId, DeviceInfoRequestDto request)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
var device = await _deviceRepository.GetByExternalIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
return new DeviceInfoResponseDto
|
||||
{
|
||||
DeviceId = device.Id,
|
||||
DeviceId = device.ExternalDeviceId,
|
||||
DeviceToken = device.DeviceToken,
|
||||
Platform = device.Platform.ToString().ToLowerInvariant(),
|
||||
OsVersion = device.OsVersion,
|
||||
|
|
@ -92,7 +94,7 @@ public class DeviceService : IDeviceService
|
|||
|
||||
public async Task UpdateAsync(long serviceId, DeviceUpdateRequestDto request)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
var device = await _deviceRepository.GetByExternalIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
|
|
@ -106,12 +108,12 @@ public class DeviceService : IDeviceService
|
|||
device.UpdatedAt = DateTime.UtcNow;
|
||||
_deviceRepository.Update(device);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.Id);
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.ExternalDeviceId);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long serviceId, DeviceDeleteRequestDto request)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
var device = await _deviceRepository.GetByExternalIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
|
|
@ -119,12 +121,13 @@ public class DeviceService : IDeviceService
|
|||
device.UpdatedAt = DateTime.UtcNow;
|
||||
_deviceRepository.Update(device);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.Id);
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.ExternalDeviceId);
|
||||
}
|
||||
|
||||
public async Task AdminDeleteAsync(long deviceId)
|
||||
public async Task AdminDeleteAsync(string externalDeviceId)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
||||
var results = await _deviceRepository.FindAsync(d => d.ExternalDeviceId == externalDeviceId);
|
||||
var device = results.FirstOrDefault();
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
|
|
@ -132,7 +135,7 @@ public class DeviceService : IDeviceService
|
|||
device.UpdatedAt = DateTime.UtcNow;
|
||||
_deviceRepository.Update(device);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
await _tokenCache.InvalidateAsync(device.ServiceId, device.Id);
|
||||
await _tokenCache.InvalidateAsync(device.ServiceId, device.ExternalDeviceId);
|
||||
}
|
||||
|
||||
public async Task<DeviceListResponseDto> GetListAsync(long? serviceId, DeviceListRequestDto request)
|
||||
|
|
@ -169,7 +172,7 @@ public class DeviceService : IDeviceService
|
|||
{
|
||||
Items = items.Select(d => new DeviceSummaryDto
|
||||
{
|
||||
DeviceId = d.Id,
|
||||
DeviceId = d.ExternalDeviceId,
|
||||
Platform = d.Platform.ToString().ToLowerInvariant(),
|
||||
Model = d.DeviceModel,
|
||||
PushAgreed = d.PushAgreed,
|
||||
|
|
@ -259,7 +262,7 @@ public class DeviceService : IDeviceService
|
|||
|
||||
public async Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
var device = await _deviceRepository.GetByExternalIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
|
|
@ -275,7 +278,7 @@ public class DeviceService : IDeviceService
|
|||
|
||||
public async Task SetAgreeAsync(long serviceId, DeviceAgreeRequestDto request)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
var device = await _deviceRepository.GetByExternalIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
if (device == null)
|
||||
throw new SpmsException(ErrorCodes.DeviceNotFound, "존재하지 않는 디바이스입니다.", 404);
|
||||
|
||||
|
|
@ -286,7 +289,7 @@ public class DeviceService : IDeviceService
|
|||
device.MktAgreeUpdatedAt = DateTime.UtcNow;
|
||||
_deviceRepository.Update(device);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.Id);
|
||||
await _tokenCache.InvalidateAsync(serviceId, device.ExternalDeviceId);
|
||||
}
|
||||
|
||||
private static Platform ParsePlatform(string platform)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class PushService : IPushService
|
|||
Target = new PushTargetDto
|
||||
{
|
||||
Type = "device_ids",
|
||||
Value = JsonSerializer.SerializeToElement(new[] { request.DeviceId })
|
||||
Value = JsonSerializer.SerializeToElement(new[] { request.DeviceId }) // UUID string
|
||||
},
|
||||
CreatedBy = message.CreatedBy,
|
||||
CreatedAt = DateTime.UtcNow.ToString("o")
|
||||
|
|
@ -126,7 +126,7 @@ public class PushService : IPushService
|
|||
if (sendType != "single" && sendType != "tag")
|
||||
throw new SpmsException(ErrorCodes.BadRequest, "send_type은 single 또는 tag만 허용됩니다.", 400);
|
||||
|
||||
if (sendType == "single" && request.DeviceId == null)
|
||||
if (sendType == "single" && string.IsNullOrWhiteSpace(request.DeviceId))
|
||||
throw new SpmsException(ErrorCodes.BadRequest, "send_type=single 시 device_id는 필수입니다.", 400);
|
||||
|
||||
if (sendType == "tag" && (request.Tags == null || request.Tags.Count == 0))
|
||||
|
|
@ -144,7 +144,7 @@ public class PushService : IPushService
|
|||
target = new PushTargetDto
|
||||
{
|
||||
Type = "device_ids",
|
||||
Value = JsonSerializer.SerializeToElement(new[] { request.DeviceId!.Value })
|
||||
Value = JsonSerializer.SerializeToElement(new[] { request.DeviceId! }) // UUID string
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -252,7 +252,7 @@ public class PushService : IPushService
|
|||
{
|
||||
SendId = l.Id,
|
||||
MessageCode = l.Message?.MessageCode ?? string.Empty,
|
||||
DeviceId = l.DeviceId,
|
||||
DeviceId = l.Device?.ExternalDeviceId ?? l.DeviceId.ToString(),
|
||||
Status = l.Status.ToString().ToLowerInvariant(),
|
||||
FailReason = l.FailReason,
|
||||
SentAt = l.SentAt
|
||||
|
|
@ -301,7 +301,7 @@ public class PushService : IPushService
|
|||
Target = new PushTargetDto
|
||||
{
|
||||
Type = "device_ids",
|
||||
Value = JsonSerializer.SerializeToElement(new[] { row.DeviceId })
|
||||
Value = JsonSerializer.SerializeToElement(new[] { row.DeviceId }) // UUID string
|
||||
},
|
||||
CreatedBy = message.CreatedBy,
|
||||
CreatedAt = DateTime.UtcNow.ToString("o"),
|
||||
|
|
@ -450,7 +450,8 @@ public class PushService : IPushService
|
|||
if (values.Length <= deviceIdIndex)
|
||||
continue;
|
||||
|
||||
if (!long.TryParse(values[deviceIdIndex], out var deviceId))
|
||||
var deviceId = values[deviceIdIndex];
|
||||
if (string.IsNullOrWhiteSpace(deviceId))
|
||||
continue;
|
||||
|
||||
var variables = new Dictionary<string, string>();
|
||||
|
|
@ -468,7 +469,7 @@ public class PushService : IPushService
|
|||
|
||||
private class CsvRow
|
||||
{
|
||||
public long DeviceId { get; init; }
|
||||
public string DeviceId { get; init; } = string.Empty;
|
||||
public Dictionary<string, string> Variables { get; init; } = new();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace SPMS.Domain.Entities;
|
|||
public class Device : BaseEntity
|
||||
{
|
||||
public long ServiceId { get; set; }
|
||||
public string ExternalDeviceId { get; set; } = string.Empty;
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
public Platform Platform { get; set; }
|
||||
public string? AppVersion { get; set; }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public interface IDeviceRepository : IRepository<Device>
|
|||
{
|
||||
Task<Device?> GetByServiceAndTokenAsync(long serviceId, string deviceToken);
|
||||
Task<Device?> GetByIdAndServiceAsync(long id, long serviceId);
|
||||
Task<Device?> GetByExternalIdAndServiceAsync(string externalId, long serviceId);
|
||||
Task<int> GetActiveCountByServiceAsync(long serviceId);
|
||||
Task<IReadOnlyList<Device>> GetByPlatformAsync(long serviceId, Platform platform);
|
||||
Task<(IReadOnlyList<Device> Items, int TotalCount)> GetPagedAsync(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public class TokenCacheService : ITokenCacheService
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<CachedDeviceInfo?> GetDeviceInfoAsync(long serviceId, long deviceId)
|
||||
public async Task<CachedDeviceInfo?> GetDeviceInfoAsync(long serviceId, string deviceId)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -45,7 +45,7 @@ public class TokenCacheService : ITokenCacheService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task SetDeviceInfoAsync(long serviceId, long deviceId, CachedDeviceInfo info)
|
||||
public async Task SetDeviceInfoAsync(long serviceId, string deviceId, CachedDeviceInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -60,7 +60,7 @@ public class TokenCacheService : ITokenCacheService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task InvalidateAsync(long serviceId, long deviceId)
|
||||
public async Task InvalidateAsync(long serviceId, string deviceId)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -97,6 +97,6 @@ public class TokenCacheService : ITokenCacheService
|
|||
}
|
||||
}
|
||||
|
||||
private string BuildKey(long serviceId, long deviceId) =>
|
||||
private string BuildKey(long serviceId, string deviceId) =>
|
||||
$"{_settings.InstanceName}device:token:{serviceId}:{deviceId}";
|
||||
}
|
||||
|
|
|
|||
1269
SPMS.Infrastructure/Migrations/20260303023033_AddExternalDeviceId.Designer.cs
generated
Normal file
1269
SPMS.Infrastructure/Migrations/20260303023033_AddExternalDeviceId.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,41 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SPMS.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddExternalDeviceId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ExternalDeviceId",
|
||||
table: "Device",
|
||||
type: "varchar(36)",
|
||||
maxLength: 36,
|
||||
nullable: false,
|
||||
defaultValue: "")
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Device_ServiceId_ExternalDeviceId",
|
||||
table: "Device",
|
||||
columns: new[] { "ServiceId", "ExternalDeviceId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Device_ServiceId_ExternalDeviceId",
|
||||
table: "Device");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExternalDeviceId",
|
||||
table: "Device");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -282,6 +282,11 @@ namespace SPMS.Infrastructure.Migrations
|
|||
.HasMaxLength(255)
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.Property<string>("ExternalDeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("varchar(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("tinyint(1)")
|
||||
|
|
@ -316,6 +321,9 @@ namespace SPMS.Infrastructure.Migrations
|
|||
|
||||
b.HasIndex("ServiceId", "DeviceToken");
|
||||
|
||||
b.HasIndex("ServiceId", "ExternalDeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Device", (string)null);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public class DeviceConfiguration : IEntityTypeConfiguration<Device>
|
|||
builder.Property(e => e.Id).ValueGeneratedOnAdd();
|
||||
|
||||
builder.Property(e => e.ServiceId).IsRequired();
|
||||
builder.Property(e => e.ExternalDeviceId).HasMaxLength(36).IsRequired();
|
||||
builder.Property(e => e.DeviceToken).HasMaxLength(255).IsRequired();
|
||||
builder.Property(e => e.Platform).HasColumnType("tinyint").IsRequired();
|
||||
builder.Property(e => e.AppVersion).HasMaxLength(20);
|
||||
|
|
@ -33,6 +34,7 @@ public class DeviceConfiguration : IEntityTypeConfiguration<Device>
|
|||
.HasForeignKey(e => e.ServiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(e => new { e.ServiceId, e.ExternalDeviceId }).IsUnique();
|
||||
builder.HasIndex(e => new { e.ServiceId, e.DeviceToken });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ public class DeviceRepository : Repository<Device>, IDeviceRepository
|
|||
return await _dbSet.FirstOrDefaultAsync(d => d.Id == id && d.ServiceId == serviceId);
|
||||
}
|
||||
|
||||
public async Task<Device?> GetByExternalIdAndServiceAsync(string externalId, long serviceId)
|
||||
{
|
||||
return await _dbSet.FirstOrDefaultAsync(d => d.ExternalDeviceId == externalId && d.ServiceId == serviceId);
|
||||
}
|
||||
|
||||
public async Task<int> GetActiveCountByServiceAsync(long serviceId)
|
||||
{
|
||||
return await _dbSet.CountAsync(d => d.ServiceId == serviceId && d.IsActive);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public class PushSendLogRepository : Repository<PushSendLog>, IPushSendLogReposi
|
|||
{
|
||||
var query = _dbSet
|
||||
.Include(l => l.Message)
|
||||
.Include(l => l.Device)
|
||||
.Where(l => l.ServiceId == serviceId);
|
||||
|
||||
if (messageId.HasValue)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class DeadTokenCleanupWorker : BackgroundService
|
|||
{
|
||||
var batch = await context.Set<Device>()
|
||||
.Where(d => !d.IsActive && d.UpdatedAt < cutoffUtc)
|
||||
.Select(d => new { d.Id, d.ServiceId })
|
||||
.Select(d => new { d.Id, d.ServiceId, d.ExternalDeviceId })
|
||||
.Take(BatchSize)
|
||||
.ToListAsync(stoppingToken);
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ public class DeadTokenCleanupWorker : BackgroundService
|
|||
|
||||
// 삭제된 디바이스의 Redis 캐시 무효화
|
||||
foreach (var item in batch)
|
||||
await _tokenCache.InvalidateAsync(item.ServiceId, item.Id);
|
||||
await _tokenCache.InvalidateAsync(item.ServiceId, item.ExternalDeviceId);
|
||||
|
||||
_logger.LogInformation("DeadTokenCleanupWorker 배치 삭제: {BatchCount}건 (누적: {TotalDeleted}건), 캐시 무효화 완료",
|
||||
deletedInBatch, totalDeleted);
|
||||
|
|
|
|||
|
|
@ -310,21 +310,21 @@ public class PushWorker : BackgroundService
|
|||
{
|
||||
case "single":
|
||||
{
|
||||
var deviceIds = ParseDeviceIds(message.Target);
|
||||
if (deviceIds.Count == 0) return [];
|
||||
var externalIds = ParseExternalDeviceIds(message.Target);
|
||||
if (externalIds.Count == 0) return [];
|
||||
|
||||
var devices = new List<Device>();
|
||||
foreach (var id in deviceIds)
|
||||
foreach (var externalId in externalIds)
|
||||
{
|
||||
// Redis 캐시 우선 조회
|
||||
var cached = await _tokenCache.GetDeviceInfoAsync(message.ServiceId, id);
|
||||
// Redis 캐시 우선 조회 (ExternalDeviceId 기준)
|
||||
var cached = await _tokenCache.GetDeviceInfoAsync(message.ServiceId, externalId);
|
||||
if (cached != null)
|
||||
{
|
||||
if (cached.IsActive && cached.PushAgreed)
|
||||
{
|
||||
devices.Add(new Device
|
||||
{
|
||||
Id = id,
|
||||
ExternalDeviceId = externalId,
|
||||
ServiceId = message.ServiceId,
|
||||
DeviceToken = cached.Token,
|
||||
Platform = (Platform)cached.Platform,
|
||||
|
|
@ -336,10 +336,10 @@ public class PushWorker : BackgroundService
|
|||
}
|
||||
|
||||
// 캐시 미스 → DB 조회 후 캐시 저장
|
||||
var device = await deviceRepo.GetByIdAndServiceAsync(id, message.ServiceId);
|
||||
var device = await deviceRepo.GetByExternalIdAndServiceAsync(externalId, message.ServiceId);
|
||||
if (device != null)
|
||||
{
|
||||
await _tokenCache.SetDeviceInfoAsync(message.ServiceId, id,
|
||||
await _tokenCache.SetDeviceInfoAsync(message.ServiceId, externalId,
|
||||
new CachedDeviceInfo(device.DeviceToken, (int)device.Platform,
|
||||
device.IsActive, device.PushAgreed));
|
||||
|
||||
|
|
@ -383,7 +383,7 @@ public class PushWorker : BackgroundService
|
|||
}
|
||||
}
|
||||
|
||||
private static List<long> ParseDeviceIds(PushTargetDto target)
|
||||
private static List<string> ParseExternalDeviceIds(PushTargetDto target)
|
||||
{
|
||||
if (target.Value == null) return [];
|
||||
|
||||
|
|
@ -392,8 +392,8 @@ public class PushWorker : BackgroundService
|
|||
if (target.Value.Value.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
return target.Value.Value.EnumerateArray()
|
||||
.Where(e => e.ValueKind == JsonValueKind.Number)
|
||||
.Select(e => e.GetInt64())
|
||||
.Where(e => e.ValueKind == JsonValueKind.String)
|
||||
.Select(e => e.GetString()!)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user