前言:本提醒服務,是由C#語言開發的,主要由windows服務項目和winform項目組成,運行服務可實現功能:向釘釘自定義機器人羣組裏,定時,定次,推送多個自定義消息內容,並實現主要功能的日誌記錄。 能夠說功能強大!!!web
備註: 本文主要2部分:1-關鍵代碼,2-安裝步驟。 windows
A-關鍵代碼:多線程
1-服務:ide
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,任務內容,任務的開始時間,任務時間間隔,任務已執行次數,任務最大執行次數)。網站
重點:服務運行後會造成一個單例模式,計時器,及服務幫助類的全部屬性的初始化值,均會存在該單例中,所以咱們能夠將每一個任務的信息記錄在每一個任務的實例中去,這樣更合理;另外在任務執行時,新增了多線程的使用來處理任務的準時和執行次數問題。ui
本來走過一些彎路:本來TimeCycel類中沒有任務已執行次數屬性,我是經過一個方法計算的:當前時間-任務開始時間/任務執行間隔,計算的,可是這個方式計算存在時延問題以及任務的執行快慢,沒法保證執行次數和執行間隔的準確性,使用每一個任務的實例單獨統計快捷方便,保證了已執行次數的準確性。this
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代碼:spa
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窗體項目:
這裏我只展現頁面,具體建立很簡單,就很少說了,我將安裝和服務的啓用,卸載和服務的停用寫在一塊兒了。