forked from AcaMate/AcaMate_API
[♻️] PUSH 로직 변경 - uid 와 pid 를 활용해 푸시 전송가능, 뱃지 설정 가능, 컨텐츠 추가 가능
This commit is contained in:
parent
f443f3410c
commit
76d989a4fa
|
@ -25,11 +25,16 @@ public class AppDbContext: DbContext
|
||||||
public DbSet<Location> Location { get; set; }
|
public DbSet<Location> Location { get; set; }
|
||||||
public DbSet<Contact> Contact { get; set; }
|
public DbSet<Contact> Contact { get; set; }
|
||||||
|
|
||||||
|
//MARK: PUSH
|
||||||
|
public DbSet<DBPayload> DBPayload { get; set; }
|
||||||
|
public DbSet<PushCabinet> PushCabinet { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder.Entity<User_Academy>()
|
modelBuilder.Entity<User_Academy>()
|
||||||
.HasKey(ua => new { ua.uid, ua.bid });
|
.HasKey(ua => new { ua.uid, ua.bid });
|
||||||
|
|
||||||
|
modelBuilder.Entity<PushCabinet>()
|
||||||
|
.HasKey(p => new { p.uid, p.pid });
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,10 @@ using AcaMate.Common.Models;
|
||||||
using AcaMate.V1.Models;
|
using AcaMate.V1.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Update.Internal;
|
||||||
|
|
||||||
|
using AcaMate.Common.Data;
|
||||||
using AcaMate.V1.Services;
|
using AcaMate.V1.Services;
|
||||||
|
|
||||||
namespace AcaMate.V1.Controllers;
|
namespace AcaMate.V1.Controllers;
|
||||||
|
@ -14,10 +18,14 @@ public class PushController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<PushController> _logger;
|
private readonly ILogger<PushController> _logger;
|
||||||
private readonly IPushQueue _pushQueue;
|
private readonly IPushQueue _pushQueue;
|
||||||
public PushController(ILogger<PushController> logger, IPushQueue pushQueue)
|
private readonly AppDbContext _dbContext;
|
||||||
|
private readonly IRepositoryService _repositoryService;
|
||||||
|
public PushController(ILogger<PushController> logger, IPushQueue pushQueue, AppDbContext dbContext, IRepositoryService repositoryService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pushQueue = pushQueue;
|
_pushQueue = pushQueue;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_repositoryService = repositoryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet()]
|
[HttpGet()]
|
||||||
|
@ -32,8 +40,7 @@ public class PushController : ControllerBase
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a push notification to the specified device token with the provided payload.
|
/// Sends a push notification to the specified device token with the provided payload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceToken">The device token to send the notification to.</param>
|
///
|
||||||
/// <param name="payload">The payload of the push notification.</param>
|
|
||||||
/// <returns>An IActionResult indicating the result of the operation.</returns>
|
/// <returns>An IActionResult indicating the result of the operation.</returns>
|
||||||
/// <response code="200">Push notification sent successfully.</response>
|
/// <response code="200">Push notification sent successfully.</response>
|
||||||
/// <response code="400">Invalid input parameters.</response>
|
/// <response code="400">Invalid input parameters.</response>
|
||||||
|
@ -42,58 +49,78 @@ public class PushController : ControllerBase
|
||||||
[HttpPost("send")]
|
[HttpPost("send")]
|
||||||
[CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")]
|
[CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
public async Task<IActionResult> SendPush(string deviceToken, [FromBody] Payload payload)
|
public async Task<IActionResult> SendPush([FromBody] PushRequest pushRequest)
|
||||||
{
|
{
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(deviceToken) || payload == null)
|
try
|
||||||
return BadRequest(APIResponse.InvalidInputError());
|
|
||||||
|
|
||||||
var pushRequest = new PushRequest
|
|
||||||
{
|
{
|
||||||
deviceToken = deviceToken,
|
var payload = await _dbContext.DBPayload
|
||||||
payload = payload
|
.Where(p => p.pid == pushRequest.pid)
|
||||||
|
.Select(p => new Payload
|
||||||
|
{
|
||||||
|
aps = new Aps
|
||||||
|
{
|
||||||
|
alert = new Alert { title = p.title, body = p.body, subtitle = p.subtitle ?? "" },
|
||||||
|
category = p.category
|
||||||
|
},
|
||||||
|
pid = pushRequest.pid,
|
||||||
|
content = p.content ?? "",
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
foreach (var uid in pushRequest.uids)
|
||||||
|
{
|
||||||
|
var badge = await _dbContext.PushCabinet
|
||||||
|
.Where(c => c.uid == uid && c.check_yn == false && c.pid != pushRequest.pid)
|
||||||
|
.CountAsync();
|
||||||
|
|
||||||
|
var pushToken = await _dbContext.User
|
||||||
|
.Where(u => u.uid == uid)
|
||||||
|
.Select(u => u.push_token)
|
||||||
|
.FirstOrDefaultAsync() ?? "";
|
||||||
|
|
||||||
|
var pushCabinet = new PushCabinet
|
||||||
|
{
|
||||||
|
uid = uid,
|
||||||
|
pid = pushRequest.pid,
|
||||||
|
send_date = DateTime.Now,
|
||||||
};
|
};
|
||||||
_pushQueue.Enqueue(pushRequest);
|
|
||||||
return Ok("[푸시] 접수 완료");
|
|
||||||
|
|
||||||
//
|
if (payload != null) payload.aps.badge = badge + 1;
|
||||||
//
|
|
||||||
// var isDev = _env.IsDevelopment();
|
var pushData = new PushData
|
||||||
//
|
{
|
||||||
// var pushFileSetting = new PushFileSetting()
|
pushToken = pushToken,
|
||||||
// {
|
payload = payload ?? throw new PushInvalidException("payload is NULL")
|
||||||
// uri = isDev ? "https://api.sandbox.push.apple.com/" : "https://api.push.apple.com/",
|
};
|
||||||
// p12Path = isDev ? "/src/private/AM_Push_Sandbox.p12" : "/src/private/AM_Push.p12",
|
await _repositoryService.SaveDataFK<PushCabinet>(pushCabinet);
|
||||||
// p12PWPath = "/src/private/appleKeys.json",
|
|
||||||
// apnsTopic = "me.myds.ipstein.acamate.AcaMate"
|
_pushQueue.Enqueue(pushData);
|
||||||
// };
|
}
|
||||||
//
|
});
|
||||||
// try
|
|
||||||
// {
|
return Ok(APIResponse.Send("000", "정상", Empty));
|
||||||
// if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFileSetting, payload))
|
|
||||||
// {
|
}
|
||||||
// return Ok(APIResponse.Send("000", "정상", Empty));
|
catch (PushInvalidException ex)
|
||||||
// }
|
{
|
||||||
// else
|
_logger.LogError(ex.Message);
|
||||||
// {
|
return Ok(APIResponse.Send("001", "푸시 송신: 푸시 전송 중 문제 발생",Empty));
|
||||||
// return StatusCode(500, APIResponse.UnknownError());
|
|
||||||
// }
|
}
|
||||||
//
|
catch (Exception ex)
|
||||||
// }
|
{
|
||||||
// catch (ServiceConnectionFailedException failEx)
|
_logger.LogError($"[푸시] {ex.Message}");
|
||||||
// {
|
return StatusCode(500, APIResponse.UnknownError());
|
||||||
// _logger.LogError($"[푸시][에러] : {failEx}");
|
|
||||||
// return StatusCode(300, APIResponse.InternalSeverError());
|
|
||||||
// }
|
|
||||||
// catch (HttpRequestException httpEx)
|
|
||||||
// {
|
|
||||||
// _logger.LogError($"[푸시][에러] : {httpEx}");
|
|
||||||
// return StatusCode(300, APIResponse.InternalSeverError());
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// _logger.LogError($"[푸시][에러] : {ex}");
|
|
||||||
// return StatusCode(500, APIResponse.UnknownError());
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,8 @@ public class UserController : ControllerBase
|
||||||
type = request.type,
|
type = request.type,
|
||||||
device_id = request.device_id,
|
device_id = request.device_id,
|
||||||
auto_login_yn = request.auto_login_yn,
|
auto_login_yn = request.auto_login_yn,
|
||||||
login_date = request.login_date
|
login_date = request.login_date,
|
||||||
|
push_token = request.push_token
|
||||||
};
|
};
|
||||||
var login = new Login
|
var login = new Login
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,3 +52,14 @@ public class ServiceConnectionFailedException : Exception
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PUSH 서비스 중 데이터 사용에 문제가 발생했을시
|
||||||
|
/// </summary>
|
||||||
|
public class PushInvalidException : Exception
|
||||||
|
{
|
||||||
|
public PushInvalidException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,112 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
namespace AcaMate.V1.Models;
|
namespace AcaMate.V1.Models;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iOS Payload 정의
|
||||||
|
* aps 딕셔너리 구성
|
||||||
|
* 1. alert : 실질적으로 사용자에게 노출되는 항목
|
||||||
|
* 1.1. title : 제목
|
||||||
|
* 1.2. subtitle : 부제목
|
||||||
|
* 1.3. body : 본문 내용
|
||||||
|
* 2. badge : 앱 아이콘에 표시할 숫자
|
||||||
|
* 3. sound : 소리인데 "default" 가능
|
||||||
|
* 4. content-available : 백그라운드 업데이트나 사일런트 푸시 송신시 사용한다.
|
||||||
|
* 1로 설정해 사용자가 직접 보지 않아도 앱이 업데이트 되게 할 수 있으며,
|
||||||
|
* UI에 알림이 표시되지 않고 데이터만 전달할 때 필요하다.
|
||||||
|
* 5. mutable-content : 값을 1로 설정해두면 Notification Service Extension을 통해 알림의 내용을 변경할 수 있다.
|
||||||
|
* 6. category : 사용자 인터렉션 구성시에 사용하는 식별자이다.
|
||||||
|
* 7. thread-id : 관련 알림들을 그룹화해 관리할 때 사용한다.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FCM Payload
|
||||||
|
* https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko&_gl=1*3awp3i*_up*MQ..*_ga*MTMyNDk4ODU5MC4xNzQxMTM1MzI3*_ga_CW55HF8NVT*MTc0MTEzNTMyNy4xLjAuMTc0MTEzNTM4My4wLjAuMA..#Notification
|
||||||
|
* {
|
||||||
|
"name": string,
|
||||||
|
"data": { // 입력 전용으로 임의의 가
|
||||||
|
string: string,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
object (Notification)
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
object (AndroidConfig)
|
||||||
|
},
|
||||||
|
"webpush": {
|
||||||
|
object (WebpushConfig)
|
||||||
|
},
|
||||||
|
"apns": {
|
||||||
|
object (ApnsConfig)
|
||||||
|
},
|
||||||
|
"fcm_options": {
|
||||||
|
object (FcmOptions)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Union field target can be only one of the following:
|
||||||
|
"token": string,
|
||||||
|
"topic": string,
|
||||||
|
"condition": string
|
||||||
|
// End of list of possible types for union field target.
|
||||||
|
}
|
||||||
|
* 1. Notification 영역
|
||||||
|
* 1.1. title: 알림 제목
|
||||||
|
* 1.2. body: 알림 내용
|
||||||
|
* 1.3. image: 알림에 표시할 이미지 URL
|
||||||
|
* 1.4. sound: 알림 재생 시 사용할 사운드
|
||||||
|
* 1.5. click_action: 알림 클릭 시 실행할 액션(인텐트 액션 문자열)
|
||||||
|
* 1.6. tag: 동일 태그를 가진 알림끼리 대체 또는 그룹화할 때 사용
|
||||||
|
* 1.7. color: 알림 아이콘 배경색 (16진수 코드 등)
|
||||||
|
* 1.8. body_loc_key 및 body_loc_args: 본문에 사용할 지역화 키와 인자 배열
|
||||||
|
* 1.9. title_loc_key 및 title_loc_args: 제목에 사용할 지역화 키와 인자 배열
|
||||||
|
* 1.10. channel_id: Android Oreo 이상에서 알림 채널 식별자
|
||||||
|
* 1.11. ticker, sticky, event_time, notification_priority, visibility, notification_count, light_settings, vibrate_timings 등: 사용자 경험이나 알림의 동작 방식을 세밀하게 제어할 때 사용
|
||||||
|
* 2. Data 영역 : 임의의 키-값 쌍을 포함하고 UI에 표시되는 내용이 아닌 앱의 로직에 활용할 데이터를 보낼 때 사용한다.
|
||||||
|
* 3. 기타 전송 옵션
|
||||||
|
* 3.1. priority: 메시지 전송 우선순위 (“high” 또는 “normal”)
|
||||||
|
* 3.2. time_to_live (ttl): 메시지 유효 시간(초 단위)
|
||||||
|
* 3.3. collapse_key: 동일 collapse_key를 가진 메시지는 최신 하나로 교체됨
|
||||||
|
* 3.4. restricted_package_name: 메시지를 수신할 앱의 패키지 이름 (Android 전용)
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Table("payload")]
|
||||||
|
public class DBPayload
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[MaxLength(22)]
|
||||||
|
public string pid { get; set; }
|
||||||
|
public string title {get; set;}
|
||||||
|
public string? subtitle {get; set;}
|
||||||
|
public string body {get; set;}
|
||||||
|
public bool alert_yn {get; set;}
|
||||||
|
public string category {get; set;}
|
||||||
|
public string content {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("push_cabinet")]
|
||||||
|
public class PushCabinet
|
||||||
|
{
|
||||||
|
public string uid { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
public DateTime send_date { get; set; }
|
||||||
|
public bool check_yn { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushRequest
|
||||||
|
{
|
||||||
|
public List<string> uids { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class Payload
|
public class Payload
|
||||||
{
|
{
|
||||||
public Aps aps { get; set; }
|
public Aps aps { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
public string content { get; set; }
|
||||||
// public string customKey { get; set; } 이런식으로 추가도 가능
|
// public string customKey { get; set; } 이런식으로 추가도 가능
|
||||||
|
|
||||||
public string ToJson()
|
public string ToJson()
|
||||||
|
@ -51,8 +152,8 @@ public class PushFileSetting
|
||||||
public string apnsTopic { get; set; }
|
public string apnsTopic { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PushRequest
|
public class PushData
|
||||||
{
|
{
|
||||||
public string deviceToken { get; set; }
|
public string pushToken { get; set; }
|
||||||
public Payload payload { get; set; }
|
public Payload payload { get; set; }
|
||||||
}
|
}
|
|
@ -59,6 +59,7 @@ public class User
|
||||||
|
|
||||||
[Required(ErrorMessage = "필수 항목 누락")]
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
public DateTime login_date { get; set; }
|
public DateTime login_date { get; set; }
|
||||||
|
public string? push_token { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,6 +129,7 @@ public class UserAll
|
||||||
|
|
||||||
public bool auto_login_yn { get; set; }
|
public bool auto_login_yn { get; set; }
|
||||||
public DateTime login_date { get; set; }
|
public DateTime login_date { get; set; }
|
||||||
|
public string? push_token { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "필수 항목 누락")]
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
public string email { get; set; }
|
public string email { get; set; }
|
||||||
|
|
|
@ -9,28 +9,28 @@ namespace AcaMate.V1.Services;
|
||||||
|
|
||||||
public interface IPushQueue
|
public interface IPushQueue
|
||||||
{
|
{
|
||||||
void Enqueue(PushRequest request);
|
void Enqueue(PushData pushData);
|
||||||
Task<PushRequest> DequeueAsync(CancellationToken cancellationToken);
|
Task<PushData> DequeueAsync(CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InMemoryPushQueue: IPushQueue
|
public class InMemoryPushQueue: IPushQueue
|
||||||
{
|
{
|
||||||
private readonly ConcurrentQueue<PushRequest> _queue = new ConcurrentQueue<PushRequest>();
|
private readonly ConcurrentQueue<PushData> _queue = new ConcurrentQueue<PushData>();
|
||||||
private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);
|
private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);
|
||||||
|
|
||||||
public void Enqueue(PushRequest request)
|
public void Enqueue(PushData pushData)
|
||||||
{
|
{
|
||||||
if( request is null )
|
if( pushData is null )
|
||||||
throw new ArgumentNullException(nameof(request));
|
throw new ArgumentNullException(nameof(pushData));
|
||||||
_queue.Enqueue(request);
|
_queue.Enqueue(pushData);
|
||||||
_signal.Release();
|
_signal.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PushRequest> DequeueAsync(CancellationToken cancellationToken)
|
public async Task<PushData> DequeueAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _signal.WaitAsync(cancellationToken);
|
await _signal.WaitAsync(cancellationToken);
|
||||||
_queue.TryDequeue(out var request);
|
_queue.TryDequeue(out var pushData);
|
||||||
return request;
|
return pushData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ public class PushBackgroundService : BackgroundService
|
||||||
{
|
{
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var pushRequest = await _queue.DequeueAsync(stoppingToken);
|
var pushData = await _queue.DequeueAsync(stoppingToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _pushService.SendPushNotificationAsync(pushRequest.deviceToken, pushRequest.payload);
|
await _pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@ public interface IRepositoryService
|
||||||
{
|
{
|
||||||
Task<bool> SaveData<T, K>(T entity, Expression<Func<T, K>> key) where T : class;
|
Task<bool> SaveData<T, K>(T entity, Expression<Func<T, K>> key) where T : class;
|
||||||
Task<ValidateToken> ValidateToken(string token, string refresh);
|
Task<ValidateToken> ValidateToken(string token, string refresh);
|
||||||
|
Task<bool> SaveDataFK<T>(T entity) where T : class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RepositoryService: IRepositoryService
|
public class RepositoryService: IRepositoryService
|
||||||
|
@ -141,4 +142,53 @@ public class RepositoryService: IRepositoryService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public async Task SaveData<T, T1>(T pushCabinet)
|
||||||
|
// {
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<bool> SaveDataFK<T>(T entity) where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// EF Core 메타데이터를 통해 엔티티 T의 기본 키 속성 정보를 가져옴
|
||||||
|
var keyProperties = _dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
|
||||||
|
|
||||||
|
// 각 키 속성에 대해, entity에서 실제 키 값을 추출
|
||||||
|
var keyValues = keyProperties
|
||||||
|
.Select(p => typeof(T).GetProperty(p.Name).GetValue(entity))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// 기본 키 값을 이용해서 기존 엔티티를 찾음 (복합 키도 자동으로 처리됨)
|
||||||
|
var existingEntity = await _dbContext.Set<T>().FindAsync(keyValues);
|
||||||
|
|
||||||
|
if (existingEntity != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"[{typeof(T)}] 기존 데이터 발견: 기본 키 값({string.Join(", ", keyValues)})");
|
||||||
|
// 기존 엔티티를 업데이트: 새 entity의 값으로 교체
|
||||||
|
_dbContext.Entry(existingEntity).CurrentValues.SetValues(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"[{typeof(T)}] 신규 데이터 등록: 기본 키 값({string.Join(", ", keyValues)})");
|
||||||
|
// 데이터가 없으면 새 엔티티 추가
|
||||||
|
_dbContext.Set<T>().Add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 변경 사항 저장
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"[{typeof(T)}] DB 저장 완료");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"[{typeof(T)}] 저장 중 오류 발생: {ex}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user