企业应用微型Windows服务架构
背景:质量检测集团企业内部有N多系统,这些系统之间有关联性并相互协作,很多业务需要Windows服务大显身手
举例:客户通过网站下定单 > 企业内部收到客户快递的样品在实验室系统登记 > 实验室系统检测 > 实验室系统出具检测报告 > 客户通过浏览器查看报告
运用Windows服务进行:
- 报告文件同步
- 对客户的短信、邮件通知
- 对内部工作人员进行短信、邮件通知
- 进行分公司内网与公共服务器数据同步
……
以上只是一个例子,实际情况要复杂的多,很多个服务放在一起运行、维护很不方便,混乱不堪,如何通过一种巧妙的方式进行管理、部署、监控,让管理人员可操作性更强呢?博主写了一个小程序来达到上面的要求,请大家指点一二。
技术点:
- XML读写
- 多线程
- 反射
- Windows服务安装卸载脚本
思路:
- windows服务项目是一个装载服务的容器,它负责配置并调用功能组件
- 提供一个统一的编程接口,使后续所有的功能组件都需要实现这个接口方便服务容器去调用
- 植入功能组件:实现以上接口并单独做自己的事情,与其他功能组件互不相干
- 放到Windows服务容器,配置组件信息(执行间隔,程序集名称,类名等)
以上模式有点像一个多媒体播放器程序,自身提供了解析mp3,mp4,wma,rmvb等格式的功能
播放器就是一个装载容器,解析媒体文件格式的组件就相当于插件
当有一种新的媒体格式比如(flv)诞生后,开发者们可以实现播放器提供的接口并解析这种(flv)媒体文件,然后通过配置文件添加到播放器目录即可
当播放器要播放flv的文件,就会去加载解析flv的组件
每一种对媒体格式的解析组件互不影响,这符合松耦合、工厂模式的设计思想
废话说到这里,贴代码:
接口:
1 public interface IRun 2 { 3 void Run(); 4 }
配置文件Config.xml,配置程序集的执行时间间隔,程序集名称,类名
1 <?xml version="1.0" encoding="utf-8" ?> 2 <Configuration> 3 <!-- 4 assembly:程序集名称 5 class:类名 6 method:固定Run(),默认为Run,对应类需要实现接口IRun并实现Run() 7 --> 8 <AssemblyConfig> 9 <Assembly Interval="30000" Assembly="FCL.Monitor" Class="Monitor" /> 10 <Assembly Interval="30000" Assembly="FCL.Monitor" Class="Monitor_Vancl" /> 11 <Assembly Interval="30000" Assembly="FCL.Monitor" Class="CreateNotify" /> 12 <Assembly Interval="30000" Assembly="FCL.Monitor" Class="CreateNotify_Vancl" /> 13 <Assembly Interval="1000" Assembly="FCL.Email" Class="SendEmail" /> 14 </AssemblyConfig> 15 </Configuration>
容器加载后需要构造的实体类
1 public class AssemblyInfo 2 { 3 /// <summary> 4 /// 执行间隔 5 /// </summary> 6 public int Interval { get; set; } 7 8 /// <summary> 9 /// 程序集名称 10 /// </summary> 11 public string AssemblyName { get; set; } 12 /// <summary> 13 /// 类名 14 /// </summary> 15 public string ClassName { get; set; } 16 /// <summary> 17 /// 方法名 18 /// </summary> 19 public readonly string MethodName = "Run"; 20 }
服务启动需要加载已经配置的程序集
1 /// <summary> 2 /// 服务启动 3 /// </summary> 4 /// <param name="args"></param> 5 protected override void OnStart(string[] args) 6 { 7 Log.log.Info(DateTime.Now + " 服务启动"); 8 9 //读取xml配置 10 DataSet ds = new DataSet(); 11 ds.ReadXml(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.xml")); 12 DataTable dt = ds.Tables[1]; 13 List<AssemblyInfo> assemblyInfoList = new List<AssemblyInfo>(); 14 AssemblyInfo assemblyInfo; 15 foreach (DataRow row in dt.Rows) 16 { 17 assemblyInfo = new AssemblyInfo(); 18 assemblyInfo.AssemblyName = row["Assembly"].ToString(); 19 assemblyInfo.ClassName = row["Class"].ToString(); 20 assemblyInfo.Interval = Convert.ToInt32(row["Interval"]); 21 assemblyInfoList.Add(assemblyInfo); 22 23 Log.log.Info(DateTime.Now 24 + " 程序集名称:" + assemblyInfo.AssemblyName 25 + ",类名:" + assemblyInfo.ClassName 26 + ",执行间隔:" + assemblyInfo.Interval); 27 } 28 29 //加载XML程序集配置 30 ExcuteAssembly.ExcuteListAssembly(assemblyInfoList); 31 }
以及相关用线程、反射执行程序集
public class ExcuteAssembly { /// <summary> /// 执行批量程序集 /// </summary> /// <param name="assemblyList">程序集集合</param> public static void ExcuteListAssembly(List<AssemblyInfo> assemblyList) { try { Thread thread; foreach (AssemblyInfo item in assemblyList) { thread = new Thread(new ParameterizedThreadStart(ExcuteSingleAssembly)); thread.IsBackground = true; thread.Start(item); } } catch (Exception ex) { Log.log.Error(DateTime.Now + " 创建多个线程时: " + ex.Message); } } /// <summary> /// 执行单个程序集 /// </summary> /// <param name="obj">程序集</param> public static void ExcuteSingleAssembly(object obj) { while (true) { try { AssemblyInfo assembly = obj as AssemblyInfo; Thread.Sleep(assembly.Interval); Log.log.Debug("ExcuteSingleAssembly:" + (assembly as AssemblyInfo).AssemblyName); if (assembly != null && assembly is AssemblyInfo) { Assembly a = Assembly.Load(assembly.AssemblyName); //Type规则:命名空间名称=程序集名 Type type = a.GetType(assembly.AssemblyName + "." + assembly.ClassName); Log.log.Debug("Type:" + type.ToString()); IRun run = (IRun)Activator.CreateInstance(type); MethodInfo method = type.GetMethod(assembly.MethodName); Log.log.Debug("MethodInfo:" + method.ToString()); BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; method.Invoke(run, flags, Type.DefaultBinder, null, null); } } catch (Exception ex) { Log.log.Error(DateTime.Now + " 执行程序集时出错: " + ex.Message); } } } }
以上就是服务容器的核心代码,下面贴一个组件代码
1 /// <summary> 2 /// 监测ERP数据库的单据状态,并生成消息日志 3 /// </summary> 4 public class Monitor : IRun 5 { 6 public void Run() 7 { 8 new DAL.Sys_ApplyFormNotifyLog().CreateNotify(); 9 } 10 }
关于服务的安装运行卸载也写了批处理
安装:
echo 清理原有服务项. . . %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil /U FCL.WinService.exe echo 清理完毕,开始安装后台服务. . . %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil FCL.WinService.exe echo 服务安装完毕 pause
启动:
echo 启动服务. . .
net start FCLService
pause
停止:
echo 停止服务. . .
net stop FCLService
pause
到此结束,程序里有很多需要改进之处,使程序更加灵活、稳定、强壮,请大家多多指点。