Project(DLL) : ServicePlugin
Configuration
Duration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Configuration
{
public enum Duration
{
Day,
Week,
Month
}
}
Logger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace ScheduleService.Configuration
{
public class Logger : ConfigurationElement
{
/// <summary>
/// DLL檔案位置
/// </summary>
[ConfigurationProperty("binary")]
public string Binary
{
get { return (string)this["binary"]; }
set { this["binary"] = value; }
}
/// <summary>
/// 實體名稱
/// </summary>
[ConfigurationProperty("assembly", IsRequired = true)]
public string Assembly
{
get { return (string)this["assembly"]; }
set { this["assembly"] = value; }
}
}
}
MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace ScheduleService.Configuration
{
/// <summary>
/// 已不使用
/// </summary>
public sealed class MemoryUsage : ConfigurationElement
{
[ConfigurationProperty("interval", IsRequired = false, DefaultValue = 60)]
public int Interval
{
get { return (int)this["interval"]; }
set { this["interval"] = value; }
}
[ConfigurationProperty("monitor", IsRequired = false, DefaultValue = false)]
public bool Monitor
{
get { return (bool)this["monitor"]; }
set { this["monitor"] = value; }
}
}
}
Module.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Runtime.Serialization;
using ScheduleService.Remoting;
namespace ScheduleService.Configuration
{
public sealed class Module : ConfigurationElement
{
/// <summary>
/// 模組名稱
/// </summary>
[ConfigurationProperty("name", IsKey = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
/// <summary>
/// 介面名稱
/// </summary>
[ConfigurationProperty("type", IsRequired = true)]
public string Type
{
get { return (string)this["type"]; }
set { this["type"] = value; }
}
/// <summary>
/// DLL檔案位置
/// </summary>
[ConfigurationProperty("binary")]
public string Binary
{
get { return (string)this["binary"]; }
set { this["binary"] = value; }
}
/// <summary>
/// 實體名稱
/// </summary>
[ConfigurationProperty("assembly", IsRequired = true)]
public string Assembly
{
get { return (string)this["assembly"]; }
set { this["assembly"] = value; }
}
/// <summary>
/// 執行間隔(秒)
/// </summary>
[ConfigurationProperty("interval", IsRequired = false)]
public int Interval
{
get { return (int)this["interval"]; }
set { this["interval"] = value; }
}
/// <summary>
/// 執行週期
/// </summary>
[ConfigurationProperty("duration", IsRequired = false, DefaultValue = Duration.Day)]
public Duration Duration
{
get { return (Duration)this["duration"]; }
set { this["duration"] = value; }
}
/// <summary>
/// 執行時間點(格式tt:MM:dd)
/// </summary>
[ConfigurationProperty("executePoint", IsRequired = false)]
public string ExecutePoint
{
get { return (string)this["executePoint"]; }
set { this["executePoint"] = value; }
}
/// <summary>
/// 執行失敗時, 嘗試重新執行一次
/// </summary>
[ConfigurationProperty("reTryOnFailed", IsRequired = false, DefaultValue = false)]
public bool ReTryOnFailed
{
get { return (bool)this["reTryOnFailed"]; }
set { this["reTryOnFailed"] = value; }
}
public DateTime StartTime { get; set; }
public DateTime LastExecuted { get; set; }
public DateTime NextExecute { get; set; }
public Exception Exception { get; set; }
public override string ToString()
{
return string.Format("<module name=\"{0}\" type=\"{1}\" binary=\"{2}\" assembly=\"{3}\" interval=\"{4}\" />", this.Name, this.Type, this.Binary, this.Assembly, this.Interval);
}
public override int GetHashCode()
{
return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
}
public override bool Equals(object compareTo)
{
if (compareTo == null || !(compareTo is Module)) return false;
Module obj = compareTo as Module;
return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
== string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
}
public ModuleData ToModuleData()
{
ModuleData data = new ModuleData();
data.Assembly = this.Assembly;
data.Binary = this.Binary;
data.Exception = this.Exception;
data.Interval = this.Interval;
data.LastExecuted = this.LastExecuted;
data.Name = this.Name;
data.NextExecute = this.NextExecute;
data.StartTime = this.StartTime;
data.Type = this.Type;
data.Duration = this.Duration;
data.ExecutePoint = this.ExecutePoint;
data.ReTryOnFailed = this.ReTryOnFailed;
return data;
}
}
}
ModuleCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;
using System.Configuration;
namespace ScheduleService.Configuration
{
public sealed class ModuleCollection : ConfigurationElementCollection
{
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.AddRemoveClearMap;
}
}
public Module this[int index]
{
get { return (Module)BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemove(index);
BaseAdd(index, value);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new Module();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as Module).Name;
}
#region Operation
public void Add(Module module)
{
BaseAdd(module);
}
public void Clear()
{
BaseClear();
}
public void Rmove(Module module)
{
BaseRemove(module.Name);
}
public void Remove(string name)
{
BaseRemove(name);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
#endregion
}
}
MonitorPort.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace ScheduleService.Configuration
{
public sealed class MonitorPort : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public int Value
{
get { return (int)this["value"]; }
set { this["value"] = value; }
}
}
}
Plugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace ScheduleService.Configuration
{
public sealed class Plugin : ConfigurationSection
{
///// <summary>
///// 重載間隔
///// </summary>
//[ConfigurationProperty("reloadInterval")]
//public ReloadInterval ReloadConfigInterval
//{
// get { return (ReloadInterval)this["reloadInterval"]; }
// set { this["reloadInterval"] = value; }
//}
///// <summary>
///// 監控記憶體使用情況
///// </summary>
//[ConfigurationProperty("memoryUsage")]
//public MemoryUsage MemoryUsage
//{
// get { return (MemoryUsage)this["memoryUsage"]; }
// set { this["memoryUsage"] = value; }
//}
/// <summary>
/// 監控通訊埠
/// </summary>
[ConfigurationProperty("monitorPort")]
public MonitorPort MonitorPort
{
get { return (MonitorPort)this["monitorPort"]; }
set { this["monitorPort"] = value; }
}
/// <summary>
/// 外掛log
/// </summary>
[ConfigurationProperty("exceptionHandler")]
public Logger ExceptionHandler
{
get { return (Logger)this["exceptionHandler"]; }
set { this["exceptionHandler"] = value; }
}
/// <summary>
/// 模組
/// </summary>
[ConfigurationProperty("plugins")]
[ConfigurationCollection(typeof(ModuleCollection), AddItemName = "module", ClearItemsName = "clearModules", RemoveItemName = "removeModules")]
public ModuleCollection Plugins
{
get { return (ModuleCollection)this["plugins"]; }
set { this["plugins"] = value; }
}
}
}
ReloadInterval.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace ScheduleService.Configuration
{
/// <summary>
/// 已不使用
/// </summary>
public sealed class ReloadInterval : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public int Value
{
get { return (int)this["value"]; }
set { this["value"] = value; }
}
}
}
Plugin
IAlarmPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Plugin
{
public interface IAlarmPlugin
{
void ExecuteAt(object sender, System.Timers.ElapsedEventArgs e);
}
}
IComboPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Plugin
{
public interface IComboPlugin
{
bool OnExecuting();
void OnExecuted();
}
}
IComplexPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Plugin
{
public interface IComplexPlugin : ISimplePlugin, IComboPlugin, IGeneralPlugin
{
}
}
IGeneralPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Plugin
{
public interface IGeneralPlugin
{
bool OnStart();
void OnStop();
}
}
ISimplePlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Plugin
{
public interface ISimplePlugin
{
void Execute(object sender, System.Timers.ElapsedEventArgs e);
}
}
PluginLoader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Configuration;
using ScheduleService.Configuration;
using ScheduleService.Utilily;
namespace ScheduleService.Plugin
{
public class PluginLoader
{
private List<Module> LoadedPlugins;
/// <summary>
/// 載入目前執行個體的設定檔
/// </summary>
public void LoadConfig()
{
LoadConfig(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);
}
/// <summary>
/// 載入指定的設定檔
/// </summary>
/// <param name="configPath"></param>
public void LoadConfig(string configPath)
{
try
{
LoadedPlugins = new List<Module>();
#region 重新載入設定檔
ConfigurationManager.RefreshSection("scheduleService.plugin");
ModuleCollection modules = ((Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin")).Plugins;
foreach (Module m in modules)
{
LoadedPlugins.Add(m);
}
#endregion
#region 以讀檔方式重新載入設定檔
//XElement root = XElement.Load(configPath);
//IEnumerator<XElement> itor = root.XPathSelectElements("//scheduleService.plugin/plugins/module").GetEnumerator();
//while (itor.MoveNext())
//{
// try
// {
// Module m = new Module();
// m.Assembly = itor.Current.Attribute("assembly").Value;
// m.Binary = itor.Current.Attribute("binary").Value;
// m.Name = itor.Current.Attribute("name").Value;
// m.Type = itor.Current.Attribute("type").Value;
// m.Interval = int.Parse(itor.Current.Attribute("interval").Value);
// LoadedPlugins.Add(m);
// }
// catch (Exception ex)
// {
// ServiceLogger.Log(string.Format("載入模組{0}時發生錯誤", itor.Current.ToString()), ex);
// }
//}
#endregion
}
catch (Exception ex)
{
ServiceLogger.Log(string.Format("載入設定檔{0}時發生錯誤", configPath), ex);
}
}
/// <summary>
/// 取得指定模組資料
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public Module GetModule(string name)
{
if (LoadedPlugins == null)
{
LoadConfig();
}
foreach (Module m in LoadedPlugins)
{
if (m.Name == name)
return m;
}
return null;
}
/// <summary>
/// 取得所有模組
/// </summary>
/// <returns></returns>
public Module[] GetModules()
{
if (LoadedPlugins == null)
{
LoadConfig();
}
return LoadedPlugins.ToArray();
}
/// <summary>
/// 取得所有模組設定字串
/// </summary>
/// <returns></returns>
public string[] GetModuleStrings()
{
if (LoadedPlugins == null)
{
LoadConfig();
}
List<string> list = new List<string>();
foreach (Module m in LoadedPlugins)
{
list.Add(m.ToString());
}
return list.ToArray();
}
}
}
Timer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using ScheduleService.Configuration;
using ScheduleService.Utilily;
using System.Text.RegularExpressions;
namespace ScheduleService.Plugin
{
public class Timer : System.Timers.Timer
{
private bool flag;
/// <summary>
/// 模組的實體物件
/// </summary>
public object Instance { get; private set; }
/// <summary>
/// 執行模組的資料
/// </summary>
public ScheduleService.Configuration.Module Source { get; private set; }
public Timer(ScheduleService.Configuration.Module module)
: base()
{
try
{
Source = module;
base.Enabled = true;
Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + Source.Binary);
switch (Source.Type)
{
case "ScheduleService.Plugin.ISimplePlugin":
case "ScheduleService.Plugin.IComplexPlugin":
ISimplePlugin simplePlugin = (ISimplePlugin)assembly.CreateInstance(Source.Assembly);
base.Interval = module.Interval * 1000;
base.Elapsed += new System.Timers.ElapsedEventHandler(Simple_Elapsed);
this.Instance = simplePlugin;
this.Source.NextExecute = DateTime.Now.AddSeconds(this.Source.Interval);
break;
case "ScheduleService.Plugin.IAlarmPlugin": // 定時類型
IAlarmPlugin alarmPlugin = (IAlarmPlugin)assembly.CreateInstance(Source.Assembly);
base.Interval = 30000; // 每30秒執行檢查
base.Elapsed += new System.Timers.ElapsedEventHandler(Alarm_Elapsed);
this.Instance = alarmPlugin;
this.Source.NextExecute = this.NextExecute(DateTime.Now);
break;
case "ScheduleService.Plugin.IGeneralPlugin": // 未測試
IGeneralPlugin generalPlugin = (IGeneralPlugin)assembly.CreateInstance(Source.Assembly);
base.Enabled = false;
this.Instance = generalPlugin;
break;
}
}
catch (Exception ex)
{
ServiceLogger.Log(string.Format("建立'{0}'實體時發生錯誤", Source.Assembly), ex);
throw ex;
}
}
void Alarm_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (this.flag)
{
ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
return;
}
if (string.Format("{0:yyyyMMddHHmm}", this.NextExecute(DateTime.Now.AddMinutes(-1))) == string.Format("{0:yyyyMMddHHmm}", DateTime.Now))
{
this.flag = true;
this.Enabled = false;
this.Source.LastExecuted = e.SignalTime; // 執行時間
this.Source.NextExecute = this.NextExecute(DateTime.Now.AddMinutes(5)); // 預計下次執行時間
try
{
this.ExecuteAlarm(sender, e);
this.Source.Exception = null;
}
catch (Exception ex)
{
this.Source.Exception = ex;
ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);
if (this.Source.ReTryOnFailed)
{
// 延遲30秒後重試
System.Threading.Thread.Sleep(30000);
ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
try
{
this.ExecuteAlarm(sender, e);
}
catch (Exception exx)
{
ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
}
}
}
// 強迫執行時間大於60秒, 避免重覆執行
System.Threading.Thread.Sleep(60000);
this.Enabled = true;
this.flag = false;
// 回收記憶體
GC.Collect();
}
}
private void ExecuteAlarm(object sender, System.Timers.ElapsedEventArgs e)
{
IComboPlugin comboPlugin = this.Instance as IComboPlugin;
IAlarmPlugin alarmPlugin = this.Instance as IAlarmPlugin;
// OnExecuting
bool isCanceled = false;
if (this.Instance is IComboPlugin && comboPlugin != null)
{
isCanceled = !comboPlugin.OnExecuting();
}
// Execute
if (isCanceled)
{
ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
}
else
{
ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
alarmPlugin.ExecuteAt(sender, e);
ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
}
// OnExecuted
if (this.Instance is IComboPlugin && comboPlugin != null)
{
comboPlugin.OnExecuted();
}
}
void Simple_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (this.flag)
{
ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
return;
}
this.flag = true;
this.Enabled = false;
this.Source.LastExecuted = e.SignalTime; // 執行時間
this.Source.NextExecute = e.SignalTime.AddSeconds(this.Source.Interval); // 預計下次執行時間
try
{
this.ExecuteSimple(sender, e);
this.Source.Exception = null;
}
catch (Exception ex)
{
this.Source.Exception = ex;
ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);
if (this.Source.ReTryOnFailed)
{
// 延遲30秒後重試
System.Threading.Thread.Sleep(30000);
ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
try
{
this.ExecuteSimple(sender, e);
}
catch (Exception exx)
{
ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
}
}
}
this.Enabled = true;
this.flag = false;
// 回收記憶體
GC.Collect();
}
private void ExecuteSimple(object sender, System.Timers.ElapsedEventArgs e)
{
IComboPlugin comboPlugin = this.Instance as IComboPlugin;
ISimplePlugin simplePlugin = this.Instance as ISimplePlugin;
// OnExecuting
bool isCanceled = false;
if (this.Instance is IComboPlugin && comboPlugin != null)
{
isCanceled = !comboPlugin.OnExecuting();
}
// Execute
if (isCanceled)
{
ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
}
else
{
ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
simplePlugin.Execute(sender, e);
ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
}
// OnExecuted
if (this.Instance is IComboPlugin && comboPlugin != null)
{
comboPlugin.OnExecuted();
}
}
/// <summary>
/// 計算下次執行時間
/// </summary>
/// <param name="baseTime"></param>
/// <returns></returns>
private DateTime NextExecute(DateTime baseTime)
{
int duration = -1;
int hour = -1;
int minute = -1;
string[] time = this.Source.ExecutePoint.Split(new char[] { ':' });
if (Regex.IsMatch(this.Source.ExecutePoint, "^(\\d+:)?\\d+:\\d+$"))
{
switch (time.Length)
{
case 3:
// 計算週期的值
duration = int.Parse(time[0]);
switch (this.Source.Duration)
{
case Duration.Week:
if (duration < 0 || duration > 6) throw new FormatException("執行時間點:星期數值錯誤");
break;
case Duration.Month:
if (duration < 0 || duration > 31) throw new FormatException("執行時間點:日期數值錯誤");
break;
}
// 小時值
hour = int.Parse(time[1]);
if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
// 分鐘值
minute = int.Parse(time[2]);
if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
break;
case 2:
// 小時值
hour = int.Parse(time[0]);
if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
// 分鐘值
minute = int.Parse(time[1]);
if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
break;
default:
throw new FormatException("執行時間點:時間格式錯誤");
}
DateTime nextExecute = baseTime;
switch (this.Source.Duration)
{
case Duration.Day:
nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0);
if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(1);
break;
case Duration.Week:
int todayOfWeek = (int)DateTime.Now.DayOfWeek; // 今天的星期
int leftDays = duration - todayOfWeek < 0 ? duration - todayOfWeek + 7 : duration - todayOfWeek; // 距下次執行日期的天數
nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0).AddDays(leftDays);
if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(7);
break;
case Duration.Month:
duration = Math.Min(DateTime.DaysInMonth(baseTime.Year, baseTime.Month), duration); // 數值大於當月天數時, 在最後一天執行
nextExecute = new DateTime(baseTime.Year, baseTime.Month, duration, hour, minute, 0);
if (nextExecute < baseTime) nextExecute = nextExecute.AddMonths(1);
break;
}
return nextExecute;
}
else
{
throw new FormatException("執行時間點:時間格式錯誤");
}
}
}
}
Remoting
MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace ScheduleService.Remoting
{
[Serializable]
public class MemoryUsage
{
public int NonpagedSystemMemorySize { get; set; }
public long NonpagedSystemMemorySize64 { get; set; }
public int PagedMemorySize { get; set; }
public long PagedMemorySize64 { get; set; }
public int PagedSystemMemorySize { get; set; }
public long PagedSystemMemorySize64 { get; set; }
public int PeakPagedMemorySize { get; set; }
public long PeakPagedMemorySize64 { get; set; }
public int PeakVirtualMemorySize { get; set; }
public long PeakVirtualMemorySize64 { get; set; }
public int PeakWorkingSet { get; set; }
public long PeakWorkingSet64 { get; set; }
public int PrivateMemorySize { get; set; }
public long PrivateMemorySize64 { get; set; }
public int VirtualMemorySize { get; set; }
public long VirtualMemorySize64 { get; set; }
public int WorkingSet { get; set; }
public long WorkingSet64 { get; set; }
protected internal void Refresh()
{
Process p = Process.GetCurrentProcess();
this.NonpagedSystemMemorySize = p.NonpagedSystemMemorySize;
this.NonpagedSystemMemorySize64 = p.NonpagedSystemMemorySize64;
this.PagedMemorySize = p.PagedMemorySize;
this.PagedMemorySize64 = p.PagedMemorySize64;
this.PagedSystemMemorySize = p.PagedSystemMemorySize;
this.PagedSystemMemorySize64 = p.PagedSystemMemorySize64;
this.PeakPagedMemorySize = p.PeakPagedMemorySize;
this.PeakPagedMemorySize64 = p.PeakPagedMemorySize64;
this.PeakVirtualMemorySize = p.PeakVirtualMemorySize;
this.PeakVirtualMemorySize64 = p.PeakVirtualMemorySize64;
this.PeakWorkingSet = p.PeakWorkingSet;
this.PeakWorkingSet64 = p.PeakWorkingSet64;
this.PrivateMemorySize = p.PrivateMemorySize;
this.PrivateMemorySize64 = p.PrivateMemorySize64;
this.VirtualMemorySize = p.VirtualMemorySize;
this.VirtualMemorySize64 = p.VirtualMemorySize64;
this.WorkingSet = p.WorkingSet;
this.WorkingSet64 = p.WorkingSet64;
}
}
}
ModuleData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;
namespace ScheduleService.Remoting
{
[Serializable]
public sealed class ModuleData
{
public string Name { get; set; }
public string Type { get; set; }
public string Binary { get; set; }
public string Assembly { get; set; }
public int Interval { get; set; }
public Duration Duration { get; set; }
public string ExecutePoint { get; set; }
public bool ReTryOnFailed { get; set; }
public DateTime StartTime { get; set; }
public DateTime LastExecuted { get; set; }
public DateTime NextExecute { get; set; }
public Exception Exception { get; set; }
public override int GetHashCode()
{
return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
}
public override bool Equals(object compareTo)
{
if (compareTo == null || !(compareTo is ModuleData)) return false;
ModuleData obj = compareTo as ModuleData;
return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
== string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
}
}
}
Monitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;
namespace ScheduleService.Remoting
{
public interface Monitor
{
MemoryUsage GetUsage();
ModuleData[] GetModules();
}
}
ServiceClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Net;
namespace ScheduleService.Remoting
{
public class ServiceClient : Monitor
{
private TcpChannel channel;
private Monitor remotingObj;
public void Connect(string host, int port)
{
if (channel != null)
{
this.Close();
}
IPAddress[] ip = Dns.GetHostAddresses(host);
if (ip.Count() == 0) throw new Exception("無法解析主機名稱或ip");
this.channel = new TcpChannel();
ChannelServices.RegisterChannel(this.channel, false);
remotingObj = Activator.GetObject(
typeof(Monitor),
string.Format("tcp://{0}:{1}/ScheduleService.Remoting", ip[0], port)
) as Monitor;
}
public void Close()
{
if (this.channel != null)
{
ChannelServices.UnregisterChannel(this.channel);
this.channel.StopListening(null);
this.channel = null;
}
GC.Collect();
}
#region Monitor 成員
public MemoryUsage GetUsage()
{
try
{
if (remotingObj == null)
return null;
else
return remotingObj.GetUsage();
}
catch //(Exception ex)
{
return null;
}
}
public ModuleData[] GetModules()
{
try
{
if (remotingObj == null)
return null;
else
return remotingObj.GetModules();
}
catch //(Exception ex)
{
return null;
}
}
#endregion
}
}
ServiceMonitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
namespace ScheduleService.Remoting
{
public class ServiceMonitor : MarshalByRefObject, Monitor
{
private TcpChannel channel;
public void StartListen(int port)
{
// 結束既有的監聽
if (this.channel != null)
{
this.StopListen();
}
this.channel = new TcpChannel(port);
ChannelServices.RegisterChannel(this.channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServiceMonitor),
"ScheduleService.Remoting",
WellKnownObjectMode.SingleCall
);
}
public void StopListen()
{
if (this.channel != null)
{
ChannelServices.UnregisterChannel(this.channel);
this.channel.StopListening(null);
this.channel = null;
}
GC.Collect();
}
#region Monitor 成員
public MemoryUsage GetUsage()
{
MemoryUsage usage = new MemoryUsage();
usage.Refresh();
return usage;
}
public ModuleData[] GetModules()
{
if (ScheduleServiceBase.ModuleList == null)
{
return new ModuleData[0];
}
else
{
List<ModuleData> list = new List<ModuleData>();
foreach (ScheduleService.Configuration.Module m in ScheduleServiceBase.ModuleList)
{
list.Add(m.ToModuleData());
}
return list.ToArray();
}
}
#endregion
}
}
Utilily
Convertor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Utilily
{
public class Convertor
{
public static string MatchSize(double size, SizeName sizeName)
{
if (size < 1024 || sizeName == SizeName.TB)
{
return string.Format("{0:####.##}{1}", size, sizeName);
}
else
{
return Convertor.MatchSize(size / 1024, (SizeName)(((int)sizeName) + 1));
}
}
}
}
ExceptionHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Utilily
{
public interface ExceptionHandler
{
void Log(string description);
void Log(string description, Exception ex);
void Log(string description, Exception ex, object data);
}
}
FileoutputHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ScheduleService.Configuration;
namespace ScheduleService.Utilily
{
public class FileoutputHandler : ScheduleService.Utilily.ExceptionHandler
{
#region ExceptionHandler 成員
public void Log(string description)
{
File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
string.Format(@"Time : {0}
Description : {1}
", DateTime.Now, description));
}
public void Log(string description, Exception ex)
{
File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
string.Format(@"Time : {0}
Description : {1}
Message : {2}
Source : {3}
Exception type : {4}
Stack : {5}
", DateTime.Now, description, ex.Message, ex.Source, ex.GetType().Name, ex.StackTrace));
}
public void Log(string description, Exception ex, object data)
{
this.Log(description, ex);
}
#endregion
}
}
PathHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ScheduleService.Utilily
{
public class PathHelper
{
/// <summary>
/// 程式目錄
/// </summary>
public static string ApplicationPath
{
get { return AppDomain.CurrentDomain.BaseDirectory; }
}
/// <summary>
/// 模組目錄
/// </summary>
public static string PluginPath
{
get { return string.Format("{0}Plugin\\", PathHelper.ApplicationPath); }
}
/// <summary>
/// Log目錄
/// </summary>
public static string LogPath
{
get
{
string logPath = string.Format("{0}Log\\", PathHelper.ApplicationPath);
if (!Directory.Exists(logPath))
Directory.CreateDirectory(logPath);
return logPath;
}
}
public static string TempPath
{
get
{
string tempPath = string.Format("{0}Temp\\", PathHelper.ApplicationPath);
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
return tempPath;
}
}
}
}
ServiceLogger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Utilily;
namespace ScheduleService.Utilily
{
public class ServiceLogger
{
private static ExceptionHandler exHandler;
public static void SetHandler(ExceptionHandler handler)
{
exHandler = handler;
}
public static void Log(string description)
{
if (exHandler != null)
exHandler.Log(description);
}
public static void Log(string description, Exception ex)
{
if (exHandler != null)
exHandler.Log(description, ex);
}
public static void Log(string description, Exception ex, object data)
{
if (exHandler != null)
exHandler.Log(description, ex, data);
}
}
}
SizeName.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScheduleService.Utilily
{
public enum SizeName : int
{
Bytes = 1,
KB = 2,
MB = 3,
GB = 4,
TB = 5
}
}
ScheduleServiceBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Plugin;
using System.Reflection;
using ScheduleService.Configuration;
using System.Diagnostics;
using System.Configuration;
using ScheduleService.Utilily;
using System.IO;
using ScheduleService.Remoting;
namespace ScheduleService
{
public class ScheduleServiceBase : System.ServiceProcess.ServiceBase
{
/// <summary>
/// 排程的timer
/// </summary>
private List<Timer> tasks;
/// <summary>
/// 目前載入的模組
/// </summary>
private static List<ScheduleService.Configuration.Module> moduleList;
protected internal static List<ScheduleService.Configuration.Module> ModuleList { get { return ScheduleServiceBase.moduleList; } }
/// <summary>
/// 設定檔監控
/// </summary>
private FileSystemWatcher configWatcher;
/// <summary>
/// 遠端監控
/// </summary>
private ServiceMonitor monitor;
public ScheduleServiceBase()
{
this.tasks = new List<Timer>();
ScheduleServiceBase.moduleList = new List<ScheduleService.Configuration.Module>();
this.configWatcher = new FileSystemWatcher();
this.monitor = new ServiceMonitor();
// 指定Log記錄方式
ServiceLogger.SetHandler(new FileoutputHandler());
ServiceLogger.Log("狀態: 啟動服務");
ScheduleService.Configuration.Plugin plugin = null;
try
{
plugin = (ScheduleService.Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin");
}
catch (Exception ex)
{
ServiceLogger.Log("初始設定檔時發生錯誤", ex);
}
try
{
if (plugin != null && plugin.ExceptionHandler != null && !string.IsNullOrEmpty(plugin.ExceptionHandler.Binary))
{
Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + plugin.ExceptionHandler.Binary);
ExceptionHandler handler = (ExceptionHandler)assembly.CreateInstance(plugin.ExceptionHandler.Assembly);
if (handler == null)
throw new Exception("建立例外處理器實體失敗");
else
ServiceLogger.SetHandler(handler);
}
}
catch (Exception ex)
{
ServiceLogger.Log("設定例外處理器時發生錯誤", ex);
}
try
{
// 起始remoting
this.monitor.StartListen(plugin.MonitorPort.Value);
}
catch (Exception ex)
{
ServiceLogger.Log("設定遠端監控時發生錯誤", ex);
}
}
/// <summary>
/// 測試用
/// </summary>
/// <param name="args"></param>
public void DoStart(string[] args)
{
this.OnStart(args);
}
/// <summary>
/// 啟始設定檔載入程式
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
try
{
#region 監控設定是否改變
this.configWatcher.Path = PathHelper.ApplicationPath;
this.configWatcher.Filter = "*.config";
this.configWatcher.IncludeSubdirectories = false;
this.configWatcher.EnableRaisingEvents = true;
this.configWatcher.Changed += new FileSystemEventHandler(configWatcher_Changed);
#endregion
// 載入模組
this.ChangeModule();
}
catch (Exception ex)
{
ServiceLogger.Log("起動程式時發生錯誤", ex);
}
}
/// <summary>
/// 異動時新增/移除模組
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void configWatcher_Changed(object sender, FileSystemEventArgs e)
{
// 暫止監控
this.configWatcher.EnableRaisingEvents = false;
// 設定檔變更時
if (e.Name == string.Format("{0}.config", AppDomain.CurrentDomain.FriendlyName))
{
this.ChangeModule();
}
// 恢復監控
this.configWatcher.EnableRaisingEvents = true;
}
/// <summary>
/// 新增/移除模組
/// </summary>
private void ChangeModule()
{
try
{
PluginLoader loader = new PluginLoader();
ScheduleService.Configuration.Module[] plugins = loader.GetModules();
#region 加入新增的模組
foreach (ScheduleService.Configuration.Module m in plugins)
{
if (!ScheduleServiceBase.moduleList.Contains(m))
{
try
{
Timer timer = new Timer(m);
// 初始化(OnInit)
if (timer.Instance is IGeneralPlugin)
{
IGeneralPlugin complexPlugin = (IGeneralPlugin)timer.Instance;
if (!complexPlugin.OnStart())
{
ServiceLogger.Log(string.Format("模組[{0}]初始化時由前置作業終止,未載入模組", m.Name));
continue;
}
}
timer.Start();
this.tasks.Add(timer);
ScheduleServiceBase.moduleList.Add(m);
// 記錄時間
m.StartTime = DateTime.Now;
ServiceLogger.Log(string.Format("加入新模組[{0}]", m.Name));
}
catch (Exception ex)
{
ServiceLogger.Log(string.Format("加入模組[{0}]時發生錯誤", m.Name), ex);
}
}
}
#endregion
#region 移除模組
ScheduleService.Configuration.Module[] currentModule = loader.GetModules();
for (int i = this.tasks.Count - 1; i >= 0; i--)
{
Timer timer = this.tasks[i];
try
{
if (!currentModule.Contains(timer.Source))
{
timer.Enabled = false;
timer.Stop();
this.tasks.Remove(timer);
ScheduleServiceBase.ModuleList.Remove(timer.Source);
// 回收(Dispose)
if (timer.Instance is IGeneralPlugin)
{
IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
generalPlugin.OnStop();
}
ServiceLogger.Log(string.Format("已移除模組[{0}]", timer.Source.Name));
}
}
catch (Exception ex)
{
ServiceLogger.Log(string.Format("移除模組[{0}]時發生錯誤", timer.Source.Name), ex);
}
}
#endregion
// 回收記憶體
GC.Collect();
}
catch (Exception ex)
{
ServiceLogger.Log("模組異動時發生錯誤", ex);
}
}
/// <summary>
/// 測試用
/// </summary>
public void DoStop()
{
this.OnStop();
}
protected override void OnStop()
{
// 停止監控設定檔
this.configWatcher.EnableRaisingEvents = false;
// 停止remoting channel
this.monitor.StopListen();
foreach (ScheduleService.Plugin.Timer timer in this.tasks)
{
try
{
timer.Enabled = false;
timer.Stop();
if (timer.Instance is IGeneralPlugin)
{
IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
generalPlugin.OnStop();
}
}
catch (Exception ex)
{
ServiceLogger.Log("停止服務時發生錯誤", ex);
}
}
ServiceLogger.Log("狀態: 停止服務");
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="scheduleService.plugin" type="ScheduleService.Configuration.Plugin, ServicePlugin"/>
</configSections>
<scheduleService.plugin>
<monitorPort value="9876" />
<!--<exceptionHandler binary="MyLib.dll" assembly="MyLib.MyHandler" />-->
<plugins>
<!--<module name="PluginName" type="ScheduleService.Plugin.IGeneralPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" />-->
<!--<module name="PluginName" type="ScheduleService.Plugin.ISimplePlugin" binary="MyLib.dll" assembly="MyLib.ClassName" interval="10" reTryOnFailed="false" />-->
<!--
druation = Day | Week | Month
executePoint = (tt:)HH:mm
-->
<!--<module name="PluginName" type="ScheduleService.Plugin.IAlarmPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" duration="Month" executePoint="3:15:32" reTryOnFailed="false" />-->
</plugins>
</scheduleService.plugin>
</configuration>
Review of installation/uninstallation
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil ServiceController.exe
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil /u ServiceController.exe