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