代码改变世界

Reminder[短信提示工具]开发碎碎念。。。附带感谢移动[smtp.139.com]提供的免费午餐。。

2010-08-31 22:31  stubman  阅读(2911)  评论(19编辑  收藏  举报

  Reminder是我最近开发的一个股票行情短信提示软件(刚做完,还是不够稳定。。)。开发这个小东东的想法缘起前段时间在首页看到一篇介绍开放Web Service的文章,其中有个Web Service就是获取实时股票行情的,看到后突然有了做个行情短信提示软件的想法,主要也是由于个人需求驱动,接着。。。革命开始了。。

  Reminder的基础功能很简单:

  1、实时获取股票行情;

  2、根据股票行情判断是否需要进行提示;

  3、发送短信提示;

  

  可是做起来也挺花时间。。。。接下来逐一介绍吧,这里主要提供思路,代码比较简单,也完成的比较仓促,就不全发了。

  1、获取股票行情

  虽然做Reminder的想法缘起Web Service,不过最后实现的时候是调用了新浪的股票行情接口, 之所以选用后者处于两方面的原因:第一,出于稳定性的考虑,觉得新浪比较靠谱。。第二,个人感觉使用更方便一点,并且一次访问可以提取多个股票行情,配置多只股票的话,访问次数少。代码很简单:

 1         /// <summary>
 2         /// 发出http get请求 获取返回的信息
 3         /// </summary>
 4         /// <param name="url">例:http://hq.sinajs.cn/list=sh0000001</param>
 5         /// <returns></returns>
 6         public string HttpGet(string url)
 7         {
 8             string[] list = new string[2];
 9             HttpWebRequest request = null;
10             HttpWebResponse response = null;
11             string responseHTML = string.Empty;
12 
13             request = (HttpWebRequest)WebRequest.Create(url);
14             request.Method = "GET";
15             //返回HTML
16             response = (HttpWebResponse)request.GetResponse();
17             Stream dataStream = response.GetResponseStream();
18             StreamReader reader = new StreamReader(dataStream, Encoding.GetEncoding("gb2312"));
19             responseHTML = reader.ReadToEnd();
20 
21             return responseHTML;
22         }

  得到返回的HTML之后当然就是进行处理,这里只贴出返回数据的格式,具体应用中要如何定义数据结构请自便。。

返回的HTML
 /// <summary>
        
/// 解析从 WebRequest返回的HTML数据,将其处理成为StockInfo对象
        
/// </summary>
        
/// <param name="stockHtml">       
        
/// 返回的HTML:
        
///var hq_str_sh601003 = " 柳钢股份,5.04,5.05,5.24,5.50,4.96,5.24,5.25,16079772,84917716,11047,5.24,
        
/// 53100,5.23,30300,5.22,24400,5.21,93000,5.20,54500,5.25,44600,5.26,48943,5.27,41154,5.28,40170,
        
/// 5.29,2010-08-24,15:02:07";
        
/// 
        
///0:”大秦铁路”,股票名字;
        
///1:”27.55″,今日开盘价;
        
///2:”27.25″ ,昨日收盘价;
        
///3:”26.91″,当前价格;
        
///4:”27.55″,今日最高价;
        
///5:”26.20″ ,今日最低价;
        
///6:”26.91″,竞买价,即“买一”报价;
        
///7:”26.92″ ,竞卖价,即“卖一”报价;
        
///8:”22114263″ ,成交的股票数,由于股票交易以一百股为基本单位,所以在使用时,通常把该值除以一百;
        
///9:”589824680″ ,成交金额,单位为“元”,为了一目了然,通常以“万元”为成交金额的单位,所以通常把该值除以一万;
        
///10:”4695″,“买一”申请4695 股,即47手;
        
///11:”26.91″,“买一”报价;
        
///12:”57590″ ,“买二”
        
///13:”26.90″,“买二”
        
///14:”14700″ ,“买三”
        
///15:”26.89″ ,“买三”
        
///16:”14300″,“买四”
        
///17:”26.88″,“买四”
        
///18:”15100″ , “买五”
        
///19:”26.87″,“买五”
        
///20:”3100″,“卖一”申报3100股,即31手;
        
///21:”26.92″,“卖一”报价
        
///(22, 23), (24, 25), (26,27), (28, 29) 分别为“卖二”至“卖四的情况”
        
///30:”2008-01-11″ ,日期;
        
///31:”15:05:32″,时间;</param>
        
/// <returns></returns>
        private Dictionary<string ,StockInfo> stockHtmlDeal(string stockHtml)

  2、根据股票行情判断是否需要进行提示;

  定义一个timer对象,一段时间对新浪股票行情接口访问一次,根据自己的需要编写股票行情提示条件判断方法【判断方法不在此赘述】,每个一段时间调用一次:

 1         /// <summary>
 2         /// 定时器
 3         /// </summary>
 4         System.Timers.Timer Time = new System.Timers.Timer(30000);//1000为1秒
 5 
 6         private void TimeSet()
 7         {
 8             Time.Elapsed += new ElapsedEventHandler(ListenerToStock);
 9             Time.AutoReset = true;//设置是执行一次(false)还是一直执行(true); 
10             Time.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
11         }
12 
13         private void ListenerToStock(object sender, ElapsedEventArgs e)
14         {
15              //todo:读股票行情,根据一定算法,判断是否发送提示信息。
16         }

  3、发送短信提示;

  这个部分花费了我大量时间。。并且目前还没达到自己满意的程度,如有可能,还会在这个环节的处理继续加强。

  不过就这部分的开发来说,对我来说是挺有趣的。

  A、现实如此残酷。。。

  开发之前的想法,是以前经常看到一些Fetion的接口,觉得发短信很简单的,找个来调用就可以了,真到了要用的时候,发现网络上基本没有能发送成功的接口。。小弟不才,如果哪位大哥知道,请指点一下,不胜感激~~~~

  B、免费的午餐。。。不怎么可口

  颇费了一番周折之后,基本放弃了调用Fetion接口发短信的想法,折腾中看到免费的午餐 ——编程利用Google日历API发短信、Email,心花怒放,赶紧下了Google Data API,摸索一下Google Calendar的使用,用上了,收到短信提示的时候那叫一个开心~~

  小小Reminder终于快要见到曙光了 ~~~~

   然而,没开心多久,免费的午餐还是不那么好吃的。。。Google Calendar的SMS提示功能用起来很不稳定(可能也是我插入数据频繁),经常延迟很久甚至收不到消息,并且短信提示的内容也不够多(应该说是比较短) ,做到后期,基本感觉到如果用谷歌的提示,Reminder是没什么实用性了。。

  C、终于吃到了可口的免费的午餐

  最后我又想到了Fetion 。。。折腾阿。。想分析一些在用的飞信第三方软件,还特地跑到一个同学那里去请教请教,他做通信方面的,这方面比我强是肯定。。看到他用的139.com邮箱,里面有飞信功能,回家后自己也注册一个,居然看到还有新邮件免费短信提醒功能!!

上图。。。

 

 

  我笑了。。。。真是踏破铁鞋无觅处,得来全不费工夫阿。。。。没那么时间去搞协议分析,真要搞怕要搞到明年了。。。我还是捡现成的吧。。。设置了短信提醒功能后,写个发邮件的方法,用发邮件触发移动的短信提示,而且提示的内容还相当全。。。搞定~出于偷懒的原因。。代码中直接写入了自己的邮箱信息,当然密码是加密了的,配置文件中配置toMailAddress,如果多个用户的话,相当于用我的邮箱往多个指定邮箱发邮件而已。

发送邮件
 1 using System;
 
2 using System.Collections.Generic;
 
3 using System.Text;
 
4 using System.Net.Mail;
 
5 using System.Net;
 
6 namespace ReminderService
 
7 {
 
8     public class ReminderExtend 
 
9     {
10          private static string from = "××××××@139.com";//发件人邮件地址
11          private static string userName = "××××××@139.com";//登录smtp主机时用到的用户名,注意是邮件地址'@'以前的部分
12          private static string password = "××××××";//登录smtp主机时用到的用户密码
13          private static string smtpHost = "smtp.139.com";//发送邮件用到的smtp主机
14 
15         /// <summary>
16         /// 发送邮件
17         /// </summary>
18         /// <param name="to">收件人邮件地址</param>
19         /// <param name="from">发件人邮件地址</param>
20         /// <param name="subject">邮件主题</param>
21         /// <param name="body">邮件内容</param>
22         /// <param name="username">登录smtp主机时用到的用户名,注意是邮件地址'@'以前的部分</param>
23         /// <param name="password">登录smtp主机时用到的用户密码</param>
24         /// <param name="smtpHost">发送邮件用到的smtp主机</param>
25         public static void Send(string to, string subject, string body)
26         {
27             try
28             {
29                 MailAddress _from = new MailAddress(from);
30                 MailAddress _to = new MailAddress(to);
31                 MailMessage message = new MailMessage(_from, _to);
32                 message.Subject = subject;//设置邮件主题
33                 message.IsBodyHtml = true;//设置邮件正文为html格式
34                 message.Body = body;//设置邮件内容
35                 SmtpClient client = new SmtpClient(smtpHost);
36                 //设置发送邮件身份验证方式
37                 //注意如果发件人地址是abc@def.com,则用户名是abc而不是abc@def.com
38                 client.Credentials = new NetworkCredential(userName, password);
39                 client.Send(message);
40             }
41             catch
42             { 要写日志还是干什么自己决定吧。。。}
43         }
44         public static void SendList(List<string> toMailList, string subject, string body)
45         {
46             foreach (string to in toMailList)
47             {
48                 Send(to, subject, body);
49             }
50         }
51     }
52 } 

  用移动的邮箱发短信,爽就一个字。。。短信世家应该也不缺这点短信吧,嘎嘎。。。邮箱刚发出去,短信就到了~~~我真有种写一篇《免费的午餐  续》来歌颂下移动的冲动。。。太贴心了~~


  为了软件的使用方便【配置股票代码,配置邮箱等】以及“安全”,还有一些附属功能

  【附:由于个人原因,我正常上班时间没有电脑可用。。。只好让小小Reminder寄生在女朋友上班用的电脑上。。太邪恶了。。】

  1、配置使用者信息;

  2、配置股票代码信息;

  3、设置开机启动;

  4、启动时即最小化到托盘;  

 

  1、读,写XML配置文件,惭愧的发现以前还真没怎么深入过,亲手做了一下,算是对XPath有了一定认识了。

  2、设置开机启动,就是写注册表,没有做成启动时就设置,而是在winform界面做了个 设置/取消  开机启动的按钮。。。咱不是流氓软件哈
    3、启动时即最小化到托盘花了一些时间,也和工作中主要是进行ASP.NET的开发,Winform做的比较少有关系吧。
最后实现的方法感觉还行,定义了一个类继承ApplicationContext,然后调用Application.Run(ApplicationContext context)方法,跳过了Application.Run(Form mainForm)强制显示form的处理。【简单的在程序中设置form的visible属性解决不了这个问题,因为最后还是会被强制显示

贴代码了。。

代码
 1 using System;
 2 using System.Collections.Generic;
 3 
 4 using System.Windows.Forms;
 5 
 6 namespace ReminderService
 7 {
 8     static class Program
 9     {
10         /// <summary>
11         /// 应用程序的主入口点。
12         /// </summary>
13         [STAThread]
14         static void Main()
15         {
16             Application.EnableVisualStyles();
17             Application.SetCompatibleTextRenderingDefault(false);
18             //Application.Run(new ReminderService());
19 
20             HideOnStartupApplicationContext context = new HideOnStartupApplicationContext(new ReminderService());
21             Application.Run(context);
22            
23         }
24     }
25 
26     /// <summary>
27     /// 用于实现持续启动时候不显示,本来用Application.Run(new ReminderService()),会强制显示
28     /// </summary>
29     internal class HideOnStartupApplicationContext : ApplicationContext
30     {
31         private Form mainFormInternal;
32 
33         // 构造函数,主窗体被存储在mainFormInternal
34         public HideOnStartupApplicationContext(Form mainForm)
35         {
36             this.mainFormInternal = mainForm;
37 
38             this.mainFormInternal.Closed += new EventHandler(mainFormInternal_Closed);
39         }
40 
41         // 当主窗体被关闭时,退出应用程序
42         void mainFormInternal_Closed(object sender, EventArgs e)
43         {
44             Application.Exit();
45         }
46     }
47 }

  

程序部署碎碎念  最后,把程序打包,发给My Girl,教她安好,设置开机启动~~~今天收到了30来条短信,还算不错,不过中间有部分短信延迟。。。看来Reminder还是需要进一步加强哈~~

   期间有过一次更新程序,以为得重新做过一个安装包了,后来确定只需要替换两个文件。。。突然又有了想法, 某些软件的更新应该就是这么做的吧:网络上部署一个存放最新程序的空间,软件启动伴随启动一个update进程,比对当前软件版本与网络空间上最新版本的 版本号,如不一致,则通过这个update进程控制,下载,关闭软件程序,替换文件,重启软件程序。。。。。

  接触winform比较少,说来惭愧,之前还没完整的做过打包,部署等工作,这次算是亲身经历了一回。。My Girl环境还没有.Net Framework。。。略过无数波折。。。。亲身体会到软件部署的难易程度对于软 件生命力的影响何其巨大,要不是我要用,估计她【电脑入门水平】无论如何也不会装个还需要装.NET环境的软件,甚至还装不大来呢。。。抛开平台不说,开 发任何软件,使用(特别是上手)的 简易性永远都是值得追求的。