feat(service): Console_SendMail을 Windows 서비스로 변환

- MailService.cs 추가: ServiceBase 상속받는 Windows 서비스 클래스
- Program.cs 수정: 서비스/콘솔 모드 지원, 설치/제거 기능 추가
- 프로젝트 설정: System.ServiceProcess 참조 추가
- 배치 파일 추가: 서비스 설치/제거/콘솔실행 스크립트

주요 기능:
- Windows 서비스로 백그라운드 실행
- 명령행 인수로 모드 선택 (-install, -uninstall, -console)
- EventLog를 통한 서비스 로깅
- 안전한 서비스 시작/중지 처리

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ChiKyun Kim
2025-09-11 09:08:40 +09:00
parent 777fcd5d89
commit 6bd4f84192
49 changed files with 46882 additions and 102 deletions

View File

@@ -35,6 +35,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@@ -49,6 +50,9 @@
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="MailService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Settings.Designer.cs">

View File

@@ -0,0 +1,36 @@
@echo off
echo EETGW Mail Service 설치 중...
REM 관리자 권한 확인
net session >nul 2>&1
if %errorLevel% == 0 (
echo 관리자 권한이 확인되었습니다.
) else (
echo 이 스크립트는 관리자 권한으로 실행해야 합니다.
echo 관리자 권한으로 다시 실행해주세요.
pause
exit /b 1
)
REM 서비스 설치
Console_SendMail.exe -install
if %errorLevel% == 0 (
echo.
echo 서비스 설치가 완료되었습니다.
echo 서비스를 시작하시겠습니까? (Y/N)
set /p choice="선택: "
if /i "%choice%"=="Y" (
net start EETGWMailService
if %errorLevel% == 0 (
echo 서비스가 성공적으로 시작되었습니다.
) else (
echo 서비스 시작에 실패했습니다.
)
)
) else (
echo 서비스 설치에 실패했습니다.
)
echo.
pause

View File

@@ -0,0 +1,112 @@
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
namespace Console_SendMail
{
public partial class MailService : ServiceBase
{
private CancellationTokenSource _cancellationTokenSource;
private Task _serviceTask;
public MailService()
{
InitializeComponent();
ServiceName = "EETGWMailService";
CanStop = true;
CanPauseAndContinue = false;
AutoLog = true;
}
protected override void OnStart(string[] args)
{
try
{
EventLog.WriteEntry(ServiceName, "메일 서비스가 시작됩니다.", EventLogEntryType.Information);
_cancellationTokenSource = new CancellationTokenSource();
_serviceTask = Task.Run(() => DoWork(_cancellationTokenSource.Token));
EventLog.WriteEntry(ServiceName, "메일 서비스가 성공적으로 시작되었습니다.", EventLogEntryType.Information);
}
catch (Exception ex)
{
EventLog.WriteEntry(ServiceName, $"서비스 시작 중 오류 발생: {ex.Message}", EventLogEntryType.Error);
throw;
}
}
protected override void OnStop()
{
try
{
EventLog.WriteEntry(ServiceName, "메일 서비스를 중지합니다.", EventLogEntryType.Information);
_cancellationTokenSource?.Cancel();
if (_serviceTask != null)
{
// 최대 30초 대기
if (!_serviceTask.Wait(TimeSpan.FromSeconds(30)))
{
EventLog.WriteEntry(ServiceName, "서비스 중지 시간 초과", EventLogEntryType.Warning);
}
}
EventLog.WriteEntry(ServiceName, "메일 서비스가 중지되었습니다.", EventLogEntryType.Information);
}
catch (Exception ex)
{
EventLog.WriteEntry(ServiceName, $"서비스 중지 중 오류 발생: {ex.Message}", EventLogEntryType.Error);
}
finally
{
_cancellationTokenSource?.Dispose();
_serviceTask?.Dispose();
}
}
private void DoWork(CancellationToken cancellationToken)
{
// 기존 프로그램의 while 루프 로직을 여기로 이동
EventLog.WriteEntry(ServiceName, "메일 서비스 작업을 시작합니다.", EventLogEntryType.Information);
while (!cancellationToken.IsCancellationRequested)
{
try
{
// 기존 메일 처리 로직 실행
Program.ExecuteMailOperations();
// 1초 대기 (cancellation token으로 중단 가능)
if (cancellationToken.WaitHandle.WaitOne(1000))
{
break; // 취소 요청 시 종료
}
}
catch (Exception ex)
{
EventLog.WriteEntry(ServiceName, $"메일 처리 중 오류 발생: {ex.Message}", EventLogEntryType.Error);
// 오류 발생 시 5초 대기 후 재시도
if (cancellationToken.WaitHandle.WaitOne(5000))
{
break;
}
}
}
EventLog.WriteEntry(ServiceName, "메일 서비스 작업이 종료되었습니다.", EventLogEntryType.Information);
}
private void InitializeComponent()
{
//
// MailService
//
this.ServiceName = "EETGWMailService";
}
}
}

View File

@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
namespace Console_SendMail
{
@@ -62,116 +66,288 @@ namespace Console_SendMail
return list_to;
}
static void Main(string[] args)
{
Console.WriteLine($"mail start ver 2508051140");
while (true)
// 명령행 인수 처리
if (args.Length > 0)
{
//메일대기내역전송
var tsSendMail = DateTime.Now - ChkSendMailTime;
if (tsSendMail.TotalMilliseconds > 1000)
string command = args[0].ToLower();
switch (command)
{
try { SendMail(); }
catch { }
finally { ChkSendMailTime = DateTime.Now; }
case "-install":
case "/install":
InstallService();
return;
case "-uninstall":
case "/uninstall":
UninstallService();
return;
case "-console":
case "/console":
RunAsConsole();
return;
case "-help":
case "/help":
case "/?":
ShowHelp();
return;
}
//자동생성 메일 작성
var tsAutoMake = DateTime.Now - ChkMakeAutoTime;
if (tsAutoMake.TotalMinutes >= 10)
{
try { MakeAutoMail(); }
catch { }
finally { ChkMakeAutoTime = DateTime.Now; }
}
//프로젝트업데이트알림
var tsPrjUpdateweek = DateTime.Now - ChkMakePrjUpdateWeekTime;
if (tsPrjUpdateweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeUpdateRequireProject(); }
catch { }
finally { ChkMakePrjUpdateWeekTime = DateTime.Now; }
}
///스케쥴 기한 알림(주)
var tsScheDayweek = DateTime.Now - ChkMakeSchDayWeekTime;
if (tsScheDayweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeScheduleDayWeek(); }
catch { }
finally { ChkMakeSchDayWeekTime = DateTime.Now; }
}
///스케쥴 기한 알림(일)
var tsScheDay = DateTime.Now - ChkMakeSchDay;
if (tsScheDay.TotalMinutes > 30 && DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeScheduleDay(); }
catch { }
finally { ChkMakeSchDay = DateTime.Now; }
}
///스케쥴없음
var tsNoSchedule = DateTime.Now - ChkNoSchedule;
if (tsNoSchedule.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_NoSchedule(); }
catch { }
finally { ChkNoSchedule = DateTime.Now; }
}
///업무일지(주간)
var tsjobweek = DateTime.Now - ChkJObreportWeek;
if (tsjobweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 18)
{
try { Mail_JobReportWeek(); }
catch { }
finally { ChkJObreportWeek = DateTime.Now; }
}
///업무일지(일)
var tsjobday = DateTime.Now - ChkJobreportDay;
if (tsjobday.TotalMinutes > 15 &&
DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday &&
DateTime.Now.DayOfWeek != DayOfWeek.Monday &&
DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 18)
{
try { Mail_JobReportDay(); }
catch { }
finally { ChkJobreportDay = DateTime.Now; }
}
///휴가신청(Remind) - 230611
var tsTakeaRest = DateTime.Now - ChkTakeARest;
if (tsTakeaRest.TotalMinutes > 15 &&
DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday &&
DateTime.Now.Hour >= 17)
{
try { Mail_Take_a_rest_remind(); }
catch { }
finally { ChkTakeARest = DateTime.Now; }
}
}
Console.WriteLine("mail end");
// 서비스로 실행
if (Environment.UserInteractive)
{
// 대화형 모드에서는 콘솔로 실행
RunAsConsole();
}
else
{
// 서비스로 실행
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new MailService()
};
ServiceBase.Run(ServicesToRun);
}
}
private static void ShowHelp()
{
Console.WriteLine("EETGW Mail Service");
Console.WriteLine("사용법:");
Console.WriteLine(" Console_SendMail.exe - 서비스로 실행");
Console.WriteLine(" Console_SendMail.exe -console - 콘솔 모드로 실행");
Console.WriteLine(" Console_SendMail.exe -install - 서비스 설치");
Console.WriteLine(" Console_SendMail.exe -uninstall - 서비스 제거");
Console.WriteLine(" Console_SendMail.exe -help - 도움말 표시");
}
private static void InstallService()
{
try
{
string servicePath = $"\"{System.Reflection.Assembly.GetExecutingAssembly().Location}\"";
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "sc.exe",
Arguments = $"create EETGWMailService binPath= \"{servicePath}\" DisplayName= \"EETGW Mail Service\" start= auto",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process process = Process.Start(startInfo))
{
process.WaitForExit();
if (process.ExitCode == 0)
{
Console.WriteLine("서비스가 성공적으로 설치되었습니다.");
Console.WriteLine("서비스를 시작하려면 다음 명령을 실행하세요:");
Console.WriteLine("net start EETGWMailService");
}
else
{
string error = process.StandardError.ReadToEnd();
Console.WriteLine($"서비스 설치 실패: {error}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"서비스 설치 중 오류 발생: {ex.Message}");
}
}
private static void UninstallService()
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "sc.exe",
Arguments = "delete EETGWMailService",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process process = Process.Start(startInfo))
{
process.WaitForExit();
if (process.ExitCode == 0)
{
Console.WriteLine("서비스가 성공적으로 제거되었습니다.");
}
else
{
string error = process.StandardError.ReadToEnd();
Console.WriteLine($"서비스 제거 실패: {error}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"서비스 제거 중 오류 발생: {ex.Message}");
}
}
private static void RunAsConsole()
{
// 중복실행 방지 체크
if (!CheckSingleInstance())
{
return; // 프로그램 종료
}
Console.WriteLine($"mail start ver 2508051140 (콘솔 모드)");
Console.WriteLine("종료하려면 Ctrl+C를 누르세요.");
// Ctrl+C 핸들러 설정
Console.CancelKeyPress += (sender, e) =>
{
Console.WriteLine("\n메일 서비스를 종료합니다...");
e.Cancel = false;
};
try
{
while (true)
{
ExecuteMailOperations();
Thread.Sleep(1000); // 1초 대기
}
}
catch (Exception ex)
{
Console.WriteLine($"오류 발생: {ex.Message}");
}
finally
{
Console.WriteLine("mail end");
}
}
/// <summary>
/// 메일 관련 작업들을 실행하는 메서드 (서비스와 콘솔에서 공통 사용)
/// </summary>
public static void ExecuteMailOperations()
{
//메일대기내역전송
var tsSendMail = DateTime.Now - ChkSendMailTime;
if (tsSendMail.TotalMilliseconds > 1000)
{
try { SendMail(); }
catch { }
finally { ChkSendMailTime = DateTime.Now; }
}
//자동생성 메일 작성
var tsAutoMake = DateTime.Now - ChkMakeAutoTime;
if (tsAutoMake.TotalMinutes >= 10)
{
try { MakeAutoMail(); }
catch { }
finally { ChkMakeAutoTime = DateTime.Now; }
}
//프로젝트업데이트알림
var tsPrjUpdateweek = DateTime.Now - ChkMakePrjUpdateWeekTime;
if (tsPrjUpdateweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeUpdateRequireProject(); }
catch { }
finally { ChkMakePrjUpdateWeekTime = DateTime.Now; }
}
///스케쥴 기한 알림(주)
var tsScheDayweek = DateTime.Now - ChkMakeSchDayWeekTime;
if (tsScheDayweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeScheduleDayWeek(); }
catch { }
finally { ChkMakeSchDayWeekTime = DateTime.Now; }
}
///스케쥴 기한 알림(일)
var tsScheDay = DateTime.Now - ChkMakeSchDay;
if (tsScheDay.TotalMinutes > 30 && DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_MakeScheduleDay(); }
catch { }
finally { ChkMakeSchDay = DateTime.Now; }
}
///스케쥴없음
var tsNoSchedule = DateTime.Now - ChkNoSchedule;
if (tsNoSchedule.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 10 && DateTime.Now.Hour <= 18)
{
try { Mail_NoSchedule(); }
catch { }
finally { ChkNoSchedule = DateTime.Now; }
}
///업무일지(주간)
var tsjobweek = DateTime.Now - ChkJObreportWeek;
if (tsjobweek.TotalMinutes > 30 && DateTime.Now.DayOfWeek == DayOfWeek.Monday && DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 18)
{
try { Mail_JobReportWeek(); }
catch { }
finally { ChkJObreportWeek = DateTime.Now; }
}
///업무일지(일)
var tsjobday = DateTime.Now - ChkJobreportDay;
if (tsjobday.TotalMinutes > 15 &&
DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday &&
DateTime.Now.DayOfWeek != DayOfWeek.Monday &&
DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 18)
{
try { Mail_JobReportDay(); }
catch { }
finally { ChkJobreportDay = DateTime.Now; }
}
///휴가신청(Remind) - 230611
var tsTakeaRest = DateTime.Now - ChkTakeARest;
if (tsTakeaRest.TotalMinutes > 15 &&
DateTime.Now.DayOfWeek != DayOfWeek.Saturday &&
DateTime.Now.DayOfWeek != DayOfWeek.Sunday &&
DateTime.Now.Hour >= 17)
{
try { Mail_Take_a_rest_remind(); }
catch { }
finally { ChkTakeARest = DateTime.Now; }
}
}
/// <summary>
/// 중복실행 방지 체크
/// </summary>
/// <returns>단일 인스턴스인 경우 true, 중복실행인 경우 false</returns>
static bool CheckSingleInstance()
{
string processName = Process.GetCurrentProcess().ProcessName;
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length > 1)
{
// 중복실행 감지
string message = $"⚠️ 프로그램이 이미 실행 중입니다!\n\n" +
"동시에 여러 개의 프로그램을 실행할 수 없습니다.\n\n" +
"해결방법을 선택하세요:";
Console.WriteLine(message);
// 현재 실행을 취소
return false;
}
return true; // 단일 인스턴스
}
}
}

View File

@@ -0,0 +1,7 @@
@echo off
echo EETGW Mail Service - 콘솔 모드로 실행
REM 콘솔 모드로 실행
Console_SendMail.exe -console
pause

View File

@@ -0,0 +1,34 @@
@echo off
echo EETGW Mail Service 제거 중...
REM 관리자 권한 확인
net session >nul 2>&1
if %errorLevel% == 0 (
echo 관리자 권한이 확인되었습니다.
) else (
echo 이 스크립트는 관리자 권한으로 실행해야 합니다.
echo 관리자 권한으로 다시 실행해주세요.
pause
exit /b 1
)
REM 서비스 중지
echo 서비스를 중지합니다...
net stop EETGWMailService 2>nul
if %errorLevel% == 0 (
echo 서비스가 중지되었습니다.
) else (
echo 서비스가 실행 중이 아니거나 중지에 실패했습니다.
)
REM 서비스 제거
Console_SendMail.exe -uninstall
if %errorLevel% == 0 (
echo 서비스 제거가 완료되었습니다.
) else (
echo 서비스 제거에 실패했습니다.
)
echo.
pause