在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前言:本提醒服务,是由C#语言开发的,主要由windows服务项目和winform项目组成,运行服务可实现功能:向钉钉自定义机器人群组里,定时,定次,推送多个自定义消息内容,并实现主要功能的日志记录。 可以说功能强大!!!
备注: 本文主要2部分:1-关键代码,2-安装步骤。
A-关键代码: 1-服务:
public partial class MyTipsService : ServiceBase { public MyTipsService() { InitializeComponent(); } protected override void OnStart(string[] args) { //服务启动 List<TimeCycle> timeCycleList = new List<TimeCycle> { new TimeCycle { ID=1, Action =this.SendTipsToDingding, BeginTime="09:05:00", EndTime="09:15:00", MaxActionTimes=2, ActionSeconds=120 }, new TimeCycle { ID=2, Action =this.SendTipsToDingding, BeginTime="17:50:00", EndTime="18:05:00", MaxActionTimes=2, ActionSeconds=120 }, new TimeCycle { ID=3, Action =this.MyProjectBugTips, BeginTime="09:10:00", EndTime="09:15:00", MaxActionTimes=1, ActionSeconds=1 }, }; MyLog.WriteLog("服务启动"); MyServiceHelp myServiceHelp = new MyServiceHelp(timeCycleList); myServiceHelp.Start(); } protected override void OnStop() { //服务终止 MyLog.WriteLog("服务终止"); } /// <summary> /// 测试方法 /// </summary> public void Test() { MyProjectBugTips(); } #region 获取提醒消息 /// <summary> /// 每天上下班提醒 /// </summary> private void SendTipsToDingding() { MyLog.WatchAction(() => { StringBuilder strBuilder = new StringBuilder(); DateTime now = DateTime.Now; if (now.Hour < 12) { strBuilder.Append("现在时间是:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n"); strBuilder.Append("上班记得打卡!打卡迟到时间不能大于60分钟,多1分钟扣10块!\r\n"); strBuilder.Append("上班记得佩戴胸牌!被抓住一次扣30块钱!\r\n"); } else { strBuilder.Append("现在时间是:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n"); strBuilder.Append("下班记得打卡!\r\n"); } string shangbanTipMessage = strBuilder.ToString(); if (!string.IsNullOrEmpty(shangbanTipMessage)) { DingDingHelp dingdingInstance = new DingDingHelp(ConfigHelper.GetAppSettingValue("dingdingGroupUrl")); string result = dingdingInstance.SendMesasge(shangbanTipMessage, "接收人手机号"); MyLog.WriteLog("发送打卡提醒消息结果:" + result); } }); } /// <summary> /// 我的项目BUG的提醒 /// </summary> private void MyProjectBugTips() { MyLog.WatchAction(() => { DateTime now = DateTime.Now; List<string> bugWhereList = new List<string>(4); bugWhereList.Add(string.Format(@"{0}!='{1}'", Sys_commonlog._LOGTYPE_, "HttpException")); bugWhereList.Add(string.Format(@"{0}>='{1}'", Sys_commonlog._CREATETIME_, now.AddDays(-1))); bugWhereList.Add(string.Format(@"{0}<='{1}'", Sys_commonlog._CREATETIME_, now)); //A-获取所有人的异常监控配置 NameValueCollection userNameValueColl = ConfigHelper.GetSectionNameValueCollection("bugUser"); Dictionary<string, List<string>> userKeyWordListDic = new Dictionary<string, List<string>>(userNameValueColl.Count); List<string> userNameList = new List<string>(userNameValueColl.Count); List<string> userPhoneList = new List<string>(userNameValueColl.Count); List<string> allKeyWordWhereList = new List<string>(0); string[] userArray = null; foreach (string userKeyAt in userNameValueColl) { if (!string.IsNullOrEmpty(userKeyAt)) { userArray = userKeyAt.Split(','); if (userArray.Length > 1) { string userName = userArray[0]; userNameList.Add(userName); userPhoneList.Add(userArray[1]); userKeyWordListDic.Add(userName, userNameValueColl[userKeyAt].Split(',').ToList()); if (userKeyWordListDic[userName].Count > 0) { foreach (string keyWord in userKeyWordListDic[userName]) { allKeyWordWhereList.Add(string.Format(@"{0} LIKE '%{1}%'", Sys_commonlog._URL_, keyWord)); } } } } } userNameValueColl = null; string whereSql = string.Format(@"{0} AND ({1})", string.Join(" AND ", bugWhereList), string.Join(" OR ", allKeyWordWhereList)); allKeyWordWhereList.Clear(); allKeyWordWhereList = null; bugWhereList.Clear(); bugWhereList = null; //B-获取所有站点 string[] websiteNameArray = ConfigHelper.GetAppSettingValue("websiteName").Split(','); //C-收集每个人每个站的BUG汇总信息 Dictionary<string, List<Sys_commonlog>> websiteDataListDic = new Dictionary<string, List<Sys_commonlog>>(websiteNameArray.Length); Sys_commonlogDAL instance = new Sys_commonlogDAL(); foreach (string webSiteName in websiteNameArray) { websiteDataListDic.Add(webSiteName, instance.Select(whereSql, Sys_commonlog._ID_ + " DESC", Conn.GetConnectionString("Constr_" + webSiteName))); } List<UserExectionHelp> userExectionHelpList = new List<UserExectionHelp>(userNameList.Count); List<Sys_commonlog> userWebsiteCommonLogList = null; List<WebSiteExectionHelp> webSiteExectionHelpList = null; WebSiteExectionHelp webSiteExection = null; int userBugTotalCount = 0; List<string> userKeyWordList = null; for (var i = 0; i < userNameList.Count; i++) { userKeyWordList = userKeyWordListDic[userNameList[i]]; webSiteExectionHelpList = new List<WebSiteExectionHelp>(websiteNameArray.Length); foreach (var websiteDicItem in websiteDataListDic) { foreach (var userKeyWord in userKeyWordList) { userWebsiteCommonLogList = websiteDicItem.Value.FindAll(item => item.Url.Split('?')[0].Contains(userKeyWord)); if (userWebsiteCommonLogList.Count > 0) { break; } } if (userWebsiteCommonLogList != null && userWebsiteCommonLogList.Count > 0) { userBugTotalCount += userWebsiteCommonLogList.Count; webSiteExection = new WebSiteExectionHelp { WebsiteName = websiteDicItem.Key, TotalCount = userWebsiteCommonLogList.Count, ExectionHelpList = userWebsiteCommonLogList }; webSiteExectionHelpList.Add(webSiteExection); } } if (webSiteExectionHelpList.Count > 0) { userExectionHelpList.Add(new UserExectionHelp { UserName = userNameList[i], UserPhone = userPhoneList[i], TotalCount = userBugTotalCount, WebSiteExectionHelpList = webSiteExectionHelpList }); } //重置bug总数 userBugTotalCount = 0; } //D-循环输出信息 DingDingHelp dingdingInstance = new DingDingHelp(ConfigHelper.GetAppSettingValue("dingdingGroupUrl")); Sys_commonlog execItem = null; StringBuilder strBuilder = new StringBuilder(); //等待发送的消息数量 int toBeSendMessageCount = 0; userExectionHelpList.ForEach(item => { strBuilder.AppendFormat("网站异常_{0}_{1}_BUG总数:{2}:\r\n", item.UserName, now.ToShortDateString(), item.TotalCount); MyLog.WriteLog(string.Format("网站异常_{0}_{1}_BUG总数:{2}:", item.UserName, now.ToShortDateString(), item.TotalCount)); item.WebSiteExectionHelpList.ForEach(webItem => { for (var i = 0; i < webItem.ExectionHelpList.Count; i++) { execItem = webItem.ExectionHelpList[i]; strBuilder.AppendFormat("【{0}-BUG-{1}】:\r\n", webItem.WebsiteName, (i + 1)); strBuilder.AppendFormat("ID:{0}\nLogType:{1}\nCreateTime:{2}\nLogContent:{3}\nUrl:{4}\r\n", execItem.ID, execItem.LogType, execItem.CreateTime, execItem.LogContent, execItem.Url); toBeSendMessageCount++; //超出50条自动发送 if (toBeSendMessageCount >= 50) { dingdingInstance.SendMesasge(strBuilder.ToString(), item.UserPhone); toBeSendMessageCount = 0; strBuilder.Clear(); } } }); //发送剩余未发送消息数量 if (toBeSendMessageCount > 0) { dingdingInstance.SendMesasge(strBuilder.ToString(), item.UserPhone); strBuilder.Clear(); } toBeSendMessageCount = 0; }); strBuilder = null; userNameList.Clear(); userNameList = null; userPhoneList.Clear(); userPhoneList = null; userExectionHelpList.Clear(); userExectionHelpList = null; if (userWebsiteCommonLogList != null) { userWebsiteCommonLogList.Clear(); userWebsiteCommonLogList = null; } websiteDataListDic.Clear(); websiteDataListDic = null; if (webSiteExectionHelpList != null) { webSiteExectionHelpList.Clear(); webSiteExection = null; } }); } #endregion }
原来服务帮助类中遇到了很多问题,如:如何统计多个任务中,每个任务的已执行次数问题? 如何让多个任务准时执行? 如何让多个任务中,每个任务按照自己的任务间隔执行? 针对这些问题,就设计了一个TimeCycel类(包含一个任务的ID,任务内容,任务的开始时间,任务时间间隔,任务已执行次数,任务最大执行次数)。
重点:服务运行后会形成一个单例模式,计时器,及服务帮助类的所有属性的初始化值,均会存在该单例中,因此我们可以将每个任务的信息记录在每个任务的实例中去,这样更合理;另外在任务执行时,新增了多线程的使用来处理任务的准时和执行次数问题。 原本走过一些弯路:原本TimeCycel类中没有任务已执行次数属性,我是通过一个方法计算的:当前时间-任务开始时间/任务执行间隔,计算的,但是这个方式计算存在时延问题以及任务的执行快慢,无法保证执行次数和执行间隔的准确性,使用每个任务的实例单独统计快捷方便,保证了已执行次数的准确性。
public class MyServiceHelp { public MyServiceHelp(List<TimeCycle> timeCycleList) { this.TimeCycleList = timeCycleList; this.Timer = new Timer(); } /// <summary> /// 服务专属计时器 /// </summary> private System.Timers.Timer Timer; /// <summary> /// 默认计时器时间间隔1秒(提高计时器开始时间准确度) /// </summary> private double DefaultTimerInterval = 1 * 1000; /// <summary> /// 设置多个循环周期 /// </summary> public List<TimeCycle> TimeCycleList { get; set; } /// <summary> /// 更新一个计时器的计时周期 /// </summary> /// <param name="newTimerInterval">新的计时周期</param> /// <param name="isFirstStart">是否是首次更新计时器周期</param> public void UpdateTimeInterval(double newTimerInterval, bool isFirstStart = false) { if (this.Timer != null && newTimerInterval > 0) { this.Timer.Stop(); if (this.Timer.Interval != newTimerInterval) { this.Timer.Interval = newTimerInterval; } if (isFirstStart) { this.Timer.Elapsed += new System.Timers.ElapsedEventHandler(this.ServiceAction); } this.Timer.AutoReset = true; this.Timer.Start(); } } /// <summary> /// 内部辅助方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ServiceAction(object sender, ElapsedEventArgs e) { List<TimeCycle> currentTimeCycleList = new List<TimeCycle>(0); DateTime now = DateTime.Now; DateTime cycleBeginTime; DateTime cycleEndTime; foreach (TimeCycle timeCycle in this.TimeCycleList) { cycleBeginTime = Convert.ToDateTime(timeCycle.BeginTime); cycleBeginTime = now.Date.AddHours(cycleBeginTime.Hour).AddMinutes(cycleBeginTime.Minute).AddSeconds(cycleBeginTime.Second); cycleEndTime = Convert.ToDateTime(timeCycle.EndTime); cycleEndTime = now.Date.AddHours(cycleEndTime.Hour).AddMinutes(cycleEndTime.Minute).AddSeconds(cycleEndTime.Second); if (cycleEndTime < cycleBeginTime) { cycleEndTime = cycleEndTime.AddDays(1); } if (now >= cycleBeginTime && now <= cycleEndTime) { if (timeCycle.ActionExecutionTimes < timeCycle.MaxActionTimes) { TimeSpan timeSpan = now - cycleBeginTime; bool isCanAction = (int)timeSpan.TotalSeconds % timeCycle.ActionSeconds == 0 ? true : false; if (isCanAction) { timeCycle.ActionExecutionTimes++; currentTimeCycleList.Add(timeCycle); } } } else { //不在计时周期内,已执行次数清零 timeCycle.ActionExecutionTimes = 0; } } //找到当前循环周期后,执行周期内动作 if (currentTimeCycleList.Count > 0) { currentTimeCycleList.ForEach(item => { //使用多线程执行任务,让代码快速执行 Task.Run(item.Action); }); } } public void Start() { //设置首次计时器周期(首次动作执行,是在计时器启动后在设置的时间间隔后做出的动作) this.UpdateTimeInterval(this.DefaultTimerInterval, true); } } /// <summary> /// 计时周期类 /// </summary> public class TimeCycle { /// <summary> /// 唯一标识 /// </summary> public int ID { get; set; } /// <summary> /// 开始时间(误差1秒=取决于计时器默认时间间隔) /// </summary> public string BeginTime { get; set; } /// <summary> /// 结束时间 /// </summary> public string EndTime { get; set; } /// <summary> /// 最大执行次数 /// </summary> public int MaxActionTimes { get; set; } /// <summary> /// 计时周期内执行的动作(动作会在到达开始时间后的) /// </summary> public Action Action { get; set; } /// <summary> /// 动作执行时间间隔(秒) /// </summary> public int ActionSeconds { get; set; } /// <summary> /// 方法执行次数 /// </summary> internal int ActionExecutionTimes { get; set; } }
2-服务管理windowform代码:
public partial class Form1 : Form { public Form1() { InitializeComponent(); this.textServicePath.Text = ConfigHelper.GetAppSettingValue("serviceExeUrl"); } private static string serviceName = "MyTips"; private void btnStartService_Click(object sender, EventArgs e) { this.InstallService(); } private void btnEndService_Click(object sender, EventArgs e) { this.UninstallService(); } private void btnTestService_Click(object sender, EventArgs e) { new MyTipsService().Test(); } //判断服务是否存在 private bool IsServiceExisted() { ServiceController[] services = ServiceController.GetServices(); foreach (ServiceController sc in services) { if (sc.ServiceName.ToLower() == serviceName.ToLower()) { return true; } } return false; } //安装服务 private void InstallService() { if (IsServiceExisted() == false) { string[] args = new string[] { this.textServicePath.Text }; ManagedInstallerClass.InstallHelper(args); MessageBox.Show("安装成功!"); using (ServiceController control = new ServiceController(serviceName)) { if (control.Status == ServiceControllerStatus.Stopped) { control.Start(); } } } else { MessageBox.Show("已安装!"); } } //卸载服务 private void UninstallService() { if (IsServiceExisted()) { //停止服务 using (ServiceController control = new ServiceController(serviceName)) { if (control.Status == ServiceControllerStatus.Running) { control.Stop(); } } //开始卸载 string[] args = new string[] { "/u", this.textServicePath.Text }; ManagedInstallerClass.InstallHelper(args); MessageBox.Show("卸载成功!"); } else { MessageBox.Show("已卸载!"); } } //查找项目下、安装目录下服务EXE文件路径 private void btnFindExe_Click(object sender, EventArgs e) { string[] resultArray = Directory.GetFiles(Directory.GetCurrentDirectory(), "我的每日提醒项目.exe", SearchOption.AllDirectories); if (resultArray.Length > 0) { this.textServicePath.Text = resultArray[0]; } } }
B-安装过程: 准备资料: 1-钉钉群组添加自定义机器人,生成webhook链接URL。 2-一个windows服务项目。 3-一个管理服务安装,卸载,启动,停止的winform窗体项目。
1-创建自定义机器人: 在钉钉的群组里,有群机器人的入口进入,添加一个自定义机器人:
添加成功后,会生成一个开发的webhook推送消息的接口地址:
2-一个windows服务项目,以VS2015为例:
在服务的设计图里,右键添加安装程序:
安装程序如下:我们可以设置关于服务的一些说明和程序设置:
说明相关:描述,显示名,服务名称 服务程序账户设置:这里我们要选择本地系统
至此一个服务的项目及安装程序就搭建起来了。
3-一个管理服务安装,卸载,启动,停止的winform窗体项目: 这里我只展示页面,具体创建很简单,就不多说了,我将安装和服务的启用,卸载和服务的停用写在一起了。
|
请发表评论