using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Transactions; using WebLib; namespace 報到系統 { /// /// 簡訊發送服務 /// 單例類,用於定時發送待發送簡訊 /// public sealed class SMSSendingService { private static SMSSendingService _instance; private static readonly object _lockObject = new object(); private bool _busy = false; /// /// 取得單一實例 /// public static SMSSendingService Instance { get { if (_instance == null) { lock (_lockObject) { if (_instance == null) { _instance = new SMSSendingService(); } } } return _instance; } } private SMSSendingService() { } /// /// 是否正在忙碌中 /// public bool IsBusy { get { return _busy; } } /// /// 啟動發送服務,發送所有待發送的簡訊 /// public void Start() { // 若已在忙碌中,則不重複啟動 if (_busy) { return; } try { _busy = true; using (var conn = DAC.NewConnection()) { // 查詢待發送簡訊(Status 為 PENDING 或 SENDING 的) var dacQueue = new DAC_MessageQueue(conn); var messagesToSend = dacQueue.SelectAllPendingOrSending(); // 如果沒有待發送簡訊,直接返回 if (messagesToSend.Count == 0) { _busy = false; return; } // 查詢簡訊發送帳號密碼併填入 messagesToSend 的項目裡 var dacClasses = new DAC_Classes(conn); var classes = dacClasses.SelectAll(); // 查詢簡訊平台的主帳號密碼 var dacMessagePlatforms = new DAC_MMSPlatform(conn); var currentMMSPlatformAccount = dacMessagePlatforms.SelectByCode(PublicVariable.CurrentMMSPlatform); // 查詢專案的簡訊帳號密碼 var dacMessageAccount = new DAC_ProjectMMSAccount(conn); var messageAccounts = dacMessageAccount.SelectAll(); foreach (var message in messagesToSend) { var cls = classes.FirstOrDefault(p => p.ID == message.CourseID); // 沒有設定計畫的,使用預設平台的主要帳號密碼 if (cls == null || string.IsNullOrEmpty(cls.ProjectID)) { message.PlatformCode = PublicVariable.CurrentMMSPlatform; message.MMSAccount = currentMMSPlatformAccount?.Account; message.MMSPassword = currentMMSPlatformAccount?.Password; continue; } var acc = messageAccounts.FirstOrDefault(p => p.ProjectID == cls.ProjectID && p.PlatformCode == PublicVariable.CurrentMMSPlatform); // 沒有設定計畫簡訊帳號密碼的,使用預設平台的主要帳號密碼 if (acc == null || string.IsNullOrEmpty(acc.MMSAccount) || string.IsNullOrEmpty(acc.MMSPassword)) { message.PlatformCode = PublicVariable.CurrentMMSPlatform; message.MMSAccount = currentMMSPlatformAccount?.Account; message.MMSPassword = currentMMSPlatformAccount?.Password; continue; } // 使用計畫指定的帳號密碼 message.MMSAccount = acc.MMSAccount; message.MMSPassword = acc.MMSPassword; message.PlatformCode = PublicVariable.CurrentMMSPlatform; } // 發送 var dacLog = new DAC_MessageLog(conn); foreach (var message in messagesToSend) { SendMessage(message, dacQueue, dacLog); Thread.Sleep(500); } } } catch (Exception ex) { // 記錄錯誤日誌 LogError("SMSSendingService.Start", ex); } finally { _busy = false; } } /// /// 發送單條簡訊 /// private void SendMessage(MessageQueueItem message, DAC_MessageQueue dacQueue, DAC_MessageLog dacLog) { using (TransactionScope ts = DAC.NewTransactionScope()) { try { // 1. 更新簡訊狀態為 SENDING message.Status = "SENDING"; dacQueue.UpdateOne(message); message.DB_APPNO++; // 2. 呼叫簡訊服務商 API 發送 bool sendSuccess = false; string failMessage = ""; try { switch (message.PlatformCode) { case "EVERY8D": sendSuccess = EVERY8DSMSProvider.SendMMS(message, out failMessage); break; case "MITAKE": default: sendSuccess = MITAKESMSProvider.SendMMS(message, out failMessage); break; } } catch (Exception ex) { sendSuccess = false; failMessage = ex.Message; } // 3. 記錄發送結果到 MessageLog MessageLogItem logItem = new MessageLogItem { CourseID = message.CourseID, Status = sendSuccess ? "SENT" : "FAILED", Name = message.Name, Mobile = message.Mobile, Subject = message.Subject, Content = message.Content, DB_APPNO = 1, DB_CRDAT = DateTime.Now, DB_CRUSR = "SYSTEM", DB_TRDAT = DateTime.MinValue, DB_TRUSR = "" }; // 如果失敗,記錄失敗訊息 if (!sendSuccess && !string.IsNullOrEmpty(failMessage)) { logItem.FailMessage = failMessage; } dacLog.InsertOne(logItem); // 4. 刪除 MessageQueue 紀錄 dacQueue.DeleteOne(message); ts.Complete(); } catch (Exception ex) { LogError("SendMessage", ex, message.ID.ToString()); ts.Dispose(); } } } /// /// 記錄錯誤日誌 /// private void LogError(string source, Exception ex, string details = "") { try { string logPath = System.Web.HttpContext.Current?.Server.MapPath("~/Log") ?? "~/Log"; if (!System.IO.Directory.Exists(logPath)) { System.IO.Directory.CreateDirectory(logPath); } string fileName = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"); string message = string.Format( "[{0}] {1}\r\nSource: {2}\r\nException: {3}\r\nDetails: {4}\r\nStackTrace: {5}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), "SMS Sending Error", source, ex.Message, details, ex.StackTrace ); System.IO.File.AppendAllText( System.IO.Path.Combine(logPath, string.Format("SMSError_{0}.txt", DateTime.Now.ToString("yyyyMMdd"))), message ); } catch { // 忽略日誌記錄的錯誤 } } } }