前言:html
距離上一篇博客,整整一個月的時間了。人不能懶下來,必須有個階段性的總結,算是對我這個階段的一個反思。人只有在總結的過程當中纔會發現本身的不足。前端
公司天天都要在OA系統上上班點擊簽到,下班點擊簽退,天天都要寫工做日誌。有的時候頭腦不清醒或者忙過頭了(別說大家沒有過),就會忘記簽到或者簽退,有時候甚至忘記寫工做日誌。這會直接致使扣人工啊有木有,因此我纔有了這個想法。首先聲明,開發這個東西並非博主對工做不認真不負責任,也並非偷懶。相反,第一,能夠避免因工做過忙忘記簽到扣工資;第二,在開發的過程當中你學到的東西是快速的,有趣的,讓本身受益的。對於每一個公司來講,OA系統都是他們的公司機密,因此博主並不會貼源碼,只在這裏闡述一個開發流程與思想,讓你感受到作一個本身以爲有趣的產品,思想的火花是多麼難以想象。web
一. 用到的模板與技術正則表達式
1. WPF編程
相比傳統的WinForm,WPF真是太強大了,不管在UI仍是在多線程的處理上,以及一些其它的改進,都預示着WinForm將被WPF取代(這只是理論上,事實上,由於不少產品都是多年前開發的,用的是WinForm,若是要整個框架移植到WPF將是一件痛苦的事,反正產品沒功能上的問題,這個移植就顯得不必了。因此目前,不少公司依然使用着WinForm的技術,開發者都在這個基礎上對產品縫縫補補,更沒有機會接觸WPF了,就算是會這門技術的人,也找不到這個職位。好比個人公司就是)。本軟件全面採用WPF技術,使用XAML佈局以及作一些加強用戶體驗的動畫。設計模式
2. Modern UI瀏覽器
Modern UI 是基於WPF的一個開源項目,託管在 code plex 上。你能夠參考如下方法把 Modern UI 的模板添加到你的 Visual Studio 上:服務器
關於 Modern UI 的介紹和使用,請參考http://mui.codeplex.com/,博主再也不累贅。cookie
3. 多線程多線程
任何一個涉及到下載數據的程序都應該使用多線程編程,咱們另開一個線程去下載數據的話,界面就不會有「假死」現象,用戶體驗顯著提高。在WPF中,若是要在子線程中獲取或者設置界面UI的值也是很簡單的事情,這個WPF都爲咱們處理好了,很方便使用。
4. Lambda表達式
Lambda表達式是一個匿名方法,你沒必要再爲只使用一次的方法獨立寫成一個函數(好比委託)。在WPF中在子線程裏獲取界面控件的值的時候就使用了Lambda表達式。Lambda表達式是委託的實現方法。
5. MVVM設計模式
MVVM 是 Model-View-ViewModel 的縮寫,看字面你就能想到是什麼意思吧。使用它的好處是,若是綁定的數據上下文改變了,會自動通知UI作出相應的更改。這也是比WinForm進步的地方。對於開發人員來講至關方便。固然MVVM不僅這些內容。
6. XML配置文件的操做
由於要保存用戶名密碼、是否開啓自動簽到動能、自動簽到的時間等等數據,就用到了App.config,實際上這是一個XML文件。
7. 系統托盤的處理
不少程序都有這個功能,主要是爲了讓程序在後臺繼續運行,以便時間到了就自動簽到或者簽退。
8. 開機自啓
早上一來開機就自動啓動,而後自動簽到,會很爽吧,都徹底不用本身動手。主要是寫入註冊表操做。
9. 模擬瀏覽器請求(重點)
使用HttpWatch來抓包,使用HttpWebRequest和HttpWebResponse來模擬瀏覽器的行爲,要理解HTTP請求協議,固然在asp.net下還要理解asp.net網站與普通網站的差別(asp.net的原理)。asp.net的網站,使用服務器控件開發的話,頁面上會有一大堆「垃圾代碼」,用來保存頁面狀態,控件狀態等等信息,這些信息在發送post報文的時候也須要發送過去,並且它的值的長度很長。
10. 正則表達式
軟件裏面大量使用了正則表達式從服務器返回的html頁面來獲取咱們須要的數據,好比個人工做日誌列表,簽到記錄等等。關於正則表達式無非兩個類,Match/MatchCollection、Regex。有興趣的本身去了解一下正則表達式的使用,即使是作前端開發的,也須要掌握js下的正則表達式來作客戶端的表單驗證。
二. 核心代碼HtmlHelper
public static class HtmlHelper { private static string cookieHeader = string.Empty; /// <summary> /// 添加日誌 /// </summary> /// <param name="strUrl">請求的url</param> /// <param name="param">參數</param> /// <returns></returns> public static string PostData(string strUrl, string param) { string strResult = ""; HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strUrl); myHttpWebRequest.AllowAutoRedirect = true; myHttpWebRequest.KeepAlive = true; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, imagepeg, applicationnd.ms-excel, application/msword, application/x-shockwave-flash, */*"; myHttpWebRequest.Timeout = 10000; myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 2.0.50727)"; myHttpWebRequest.ContentType = "application/x-www-form-urlencoded"; myHttpWebRequest.Method = "POST"; myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); Stream MyRequestStrearm = myHttpWebRequest.GetRequestStream(); StreamWriter MyStreamWriter = new StreamWriter(MyRequestStrearm, Encoding.ASCII); //把數據寫入HttpWebRequest的Request流 MyStreamWriter.Write(param); //關閉打開對象 MyStreamWriter.Close(); MyRequestStrearm.Close(); HttpWebResponse response = null; System.IO.StreamReader sr = null; response = (HttpWebResponse)myHttpWebRequest.GetResponse(); sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8 strResult = sr.ReadToEnd(); return strResult; } /// <summary> /// 功能描述:模擬登陸頁面,提交登陸數據進行登陸,並記錄Header中的cookie /// </summary> /// <param name="strURL">登陸數據提交的頁面地址</param> /// <param name="strArgs">用戶登陸數據</param> /// <returns></returns> public static string PostLogin(string strURL, string strArgs) { string strResult = ""; HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strURL); myHttpWebRequest.AllowAutoRedirect = true; myHttpWebRequest.KeepAlive = true; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, imagepeg, applicationnd.ms-excel, application/msword, application/x-shockwave-flash, */*"; myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 2.0.50727)"; myHttpWebRequest.ContentType = "application/x-www-form-urlencoded"; myHttpWebRequest.Method = "POST"; myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); CookieContainer myCookieContainer = new CookieContainer(); myHttpWebRequest.CookieContainer = myCookieContainer; Stream MyRequestStrearm = myHttpWebRequest.GetRequestStream(); StreamWriter MyStreamWriter = new StreamWriter(MyRequestStrearm, Encoding.ASCII); //把數據寫入HttpWebRequest的Request流 MyStreamWriter.Write(strArgs); //關閉打開對象 MyStreamWriter.Close(); MyRequestStrearm.Close(); HttpWebResponse response = null; System.IO.StreamReader sr = null; response = (HttpWebResponse)myHttpWebRequest.GetResponse(); cookieHeader = myHttpWebRequest.CookieContainer.GetCookieHeader(new Uri(strURL)); sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8 strResult = sr.ReadToEnd(); return strResult; } /**/ /// <summary> /// 功能描述:在PostLogin成功登陸後記錄下Headers中的cookie,而後獲取此網站上其餘頁面的內容 /// </summary> /// <param name="strURL">獲取網站的某頁面的地址</param> /// <param name="strReferer">引用的地址</param> /// <returns>返回頁面內容</returns> public static string GetPage(string strURL) { string strResult = ""; HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strURL); myHttpWebRequest.ContentType = "textml"; myHttpWebRequest.Method = "GET"; myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); HttpWebResponse response = null; System.IO.StreamReader sr = null; response = (HttpWebResponse)myHttpWebRequest.GetResponse(); sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8 strResult = sr.ReadToEnd(); return strResult; } /// <summary> /// 獲取隱藏控件的value /// </summary> /// <param name="loginHtml">HTML頁面代碼字符串</param> /// <param name="regex">要獲取的值的正則表達式</param> /// <param name="replaceLeft">左邊要刪除的字符串</param> /// <param name="replaceRight">右邊要刪除的字符串</param> /// <returns></returns> public static string GetHiddenValue(string loginHtml, string regex, string replaceLeft, string replaceRight) { string viewState = string.Empty; Match match = new Regex(regex).Match(loginHtml); if (match.Success) { viewState = match.Value.Replace(replaceLeft, ""); viewState = viewState.Replace(replaceRight, ""); } return viewState; }
吐槽一下本身,這個類其實能夠優化,好比PostLogin和PostData其實能夠合併,GetHiddenValue也能夠寫得更好,只是工做這邊還比較忙,剛剛接手了一個任務,是把項目的結構全面改版,使得每個功能就是一個小項目,這樣管理起來方便不少,因此沒有時間進行優化,作好了本身能用就用着先,仍是工做比較重要。
很明顯,裏面有4個方法,每一個方法的做用以及調用方式都有很詳細的註釋,我就不重複了。至於更多的代碼我就不貼了,畢竟涉及到商業機密的問題。講講原理吧。
三. 原理
首先,咱們知道http是無狀態鏈接,每次瀏覽器向服務器端發送請求,服務器返回數據以後就斷開了,你下一次請求的時候服務器並不知道你是否已經登陸,那麼asp.net下服務器怎麼知道你的登錄狀態呢?當你登錄以後,服務器會給瀏覽器發送一個cookie,用來標識你的登陸狀態,下一次請求的時候瀏覽器會把這個cookie一同發給服務器,服務器接收到以後驗證你發過來的cookie數據,而後就知道你是否已經登錄過。若是沒登錄,就不讓你請求別的頁面數據。咱們可使用HttpWebRequest和HttpWebResponse類來模擬瀏覽器的請求。
登錄以後,咱們要寫工做日誌,就要把日誌內容拼成要提交的報文,而後post到服務器,這就是一個post請求過程。還有一種請求叫作get請求,這種請求是不提交報文的,直接發請求,而後服務器就會返回一個html頁面,而後咱們就能夠利用正則表達式來獲取咱們須要的數據了。
這裏有一個值得注意的地方,由於咱們的程序要一個掛在電腦上,以便它能夠到時間後自動簽退或者簽到,可是服務器爲你保存的登陸狀態是有時間段的,若是過了一段時間你沒有請求操做,服務器認爲你已經斷開鏈接,再也不爲你保持登陸狀態(咱們公司的OA系統彷佛是半個小時),因此咱們進行一個請求以前要判斷一下登陸狀態是否還保持着,若是斷開了就從新登陸一下再進行請求。怎麼判斷登陸狀態呢?咱們公司的OA系統會彈出一個提示框提示身份驗證過時,而這個提示框固然是在html頁面上的,咱們只須要請求一下主頁,看它返回的html頁面中是否包含身份驗證過時這個信息就好了(別說你不知道html頁面其實就是一個字符串)。
四. 曬圖
1.登錄(其實只是保存了用戶名密碼,並無真正登錄,到須要進行登錄操做的時候才登錄,好比提交日誌、簽到、簽退、獲取日誌列表、登陸信息等等,固然並非每次這些操做都登錄,只要登陸狀態還保持着,就不須要從新登錄了)
2.主頁
3.添加工做日誌
4.個性化(模板自帶)
5.用戶信息(用於登錄)
6.系統托盤
7.簽到成功提示
五. 後話
在這個浮躁的世界裏,咱們不能夠浮躁。人要有夢想,否則跟條鹹魚有什麼分別。雖然我在追求本身喜歡的生活方式上有着各類阻礙,可是我仍是認爲,以本身喜歡的方式生活纔是最開心的。人就一生,只要能承擔責任,還有什麼必要跟本身過不去呢?昨天我朋友從鳳凰古城給我寄來一張明信片,我感慨萬千,因而回復了一句:祝前程似錦,山明水秀,兄弟情誼,萬古長青。切記,莫屈服,要以本身喜歡的方式生活。
也獻給大家,親愛的園友。