前言:

距离上一篇博客,整整一个月的时间了。人不能懒下来,必须有个阶段性的总结,算是对我这个阶段的一个反思。人只有在总结的过程中才会发现自己的不足。

公司每天都要在OA系统上上班点击签到,下班点击签退,每天都要写工作日志。有的时候头脑不清醒或者忙过头了(别说你们没有过),就会忘记签到或者签退,有时候甚至忘记写工作日志。这会直接导致扣人工啊有木有,所以我才有了这个想法。首先声明,开发这个东西并不是博主对工作不认真不负责任,也并不是偷懒。相反,第一,可以避免因工作过忙忘记签到扣工资;第二,在开发的过程中你学到的东西是快速的,有趣的,让自己受益的。对于每个公司来说,OA系统都是他们的公司机密,所以博主并不会贴源码,只在这里阐述一个开发流程与思想,让你感觉到做一个自己觉得有趣的产品,思想的火花是多么不可思议。

一. 用到的模板与技术

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 上:

  • 在Visual Studio 2012中,打开扩展管理器(工具 > 扩展和更新
  • 选择在线 > Visual Studio库和搜索“ 现代UI “
  • 选择现代为WPF的UI模板,然后单击“ 下载“,下载并安装。

关于 Modern UI 的介绍和使用,请参考http://mui.codeplex.com/,博主不再累赘。

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 = ;
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;
}

HtmlHelper

吐槽一下自己,这个类其实可以优化,比如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.签到成功提示

五. 后话        

在这个浮躁的世界里,我们不可以浮躁。人要有梦想,不然跟条咸鱼有什么分别。虽然我在追求自己喜欢的生活方式上有着各种阻碍,但是我还是认为,以自己喜欢的方式生活才是最开心的。人就一辈子,只要能承担责任,还有什么必要跟自己过不去呢?昨天我朋友从凤凰古城给我寄来一张明信片,我感慨万千,于是回复了一句:祝前程似锦,山明水秀,兄弟情谊,万古长青。切记,莫屈服,要以自己喜欢的方式生活。

也献给你们,亲爱的园友。

最新文章

  1. 使用mybatis-generator在自动生成Model类和Mapper文件
  2. c# 字符串连接使用“+”和string.format格式化两种方式
  3. 2016HUAS暑假集训训练2 F - A Simple Problem with Integers
  4. Oracle 10g DataGuard手记之基础配置
  5. matlab 函数说明--fspecial
  6. jquery前端性能优化(持续添加。。。)
  7. 九个Console命令,让 JS 调试更简单
  8. BZOJ 3931: [CQOI2015]网络吞吐量( 最短路 + 最大流 )
  9. AppCompatActivity工具栏的设置(返回操作)
  10. HUST 1584 摆放餐桌
  11. php get接口,并在浏览器中以json格式返回查找到的数据
  12. OGNL中的#、%和$符号的用法
  13. 【ERROR】no matching function for call to &#39;std::basic_ifstream&lt;char&gt;::basic_ifstream
  14. Webpack + vue 搭建
  15. UI自动化selenium
  16. 【python】xsspider零碎知识点
  17. 2018.08.29 NOIP模拟 pmatrix(线性筛)
  18. React 入门学习笔记2
  19. 角点检测:Harris角点及Shi-Tomasi角点检测
  20. sql语句学习(第二季

热门文章

  1. 模拟 Codeforces Round #297 (Div. 2) A. Vitaliy and Pie
  2. 136 Single Number 数组中除一个数外其他数都出现两次,找出只出现一次的数
  3. qconbeijing2017
  4. nginx 80端口重定向到443端口
  5. Python3 写入文件
  6. Ubuntu 创建docker 容器 系列一
  7. css3纯手写loading效果
  8. 51nod 1432 独木舟
  9. Using URL Schemes to Communicate with Apps
  10. 什么是cookie(前段时间看到别人简历上把cookie和localStorage混淆了所以专门又去了解了下)