我们曾经走过一样的岁月,也曾经为一样的事情流泪和欢笑,
我们宁愿忽视那些不同的想法和生活方式;
我们坚信,虽然我们的样子已经被社会打磨成千面,但是体温相似,情有共通。

宝玉

写写生活,不谈技术!

hello world!

I am Jim!

写在西工大官方BBS重开之际

喜欢上西工大BBS,是在大概大二的时候,在几个师兄的影响下,开始泡那时候的Openlab,也就是那时候的官方BBS,当时国内高校最有技术含量的原创BBS之一,在上面找到很多乐趣,交了很多朋友,翻翻那时候的贴子,俨然也是一小愤青,什么都不懂,很幼稚,但是也很富有朝气和激情。不幸的是,02年的时候Openlab被关闭了,我和很多老用户一样,经历了很多要重开的传闻,日复一日的去打开浏览器刷新那个熟悉的ip地址,只是等到毕业也没有等到重开。也曾在一些外校的BBS或者天涯之类大型BBS的西工大板块混迹过,无奈再也找不到当年那种归属感。本以为从此和OL无缘,毕业后03年底,机缘巧合,在Moody的帮助下拿到了以前OL的数据库和程序,经历了一番波折终于重新调通了,当在自己的电脑上打开那熟悉的网页,那一刻,泪流满面!毕竟,它记录了我青春的印记,借用饭否重开后也是《岁月神偷》里的一句话:“在变幻的生命里,岁月,原来是最大的小偷。”

Openlab从03年重开到现在,跌跌撞撞,经历过网站的生死存亡,走了很多弯路,做了很多错事,辉煌过,低谷过,停滞不前过,最重要的是,它一路走来,到现在它还在继续!
官方BBS关闭了8年后,终于要重开了,对我这样一个西工大的校友来说,是个好消息,无论对于在校生,还是我们这些已离开学校的老校友,都需要有这么一个网上交流的平台,需要一个对母校感情寄托的平台,虽然有现在的Openlab,但是毕竟有很多事情不是Openlab能替代的,本来,民间和官方的BBS,就是一种互补和共存的关系。只是希望,未来,学校能好好利用官方包括民间的BBS这个平台,更好来解决师生们实实在在的问题,更好的起到一个沟通交流的作用,并且,不要再哪一天又给关闭了⋯⋯

表弟落榜了

表弟高考落榜了,离分数线还有一大截,似乎大家都在意料之中,因为表弟上的是一所很普通的高中,因为表弟平时成绩也不太好。表弟考完就到上海的小姑厂里打工,工作很枯燥很辛苦,但是他还是做的很踏实,没叫苦没叫累的坚持下来了。

对于落榜后的表弟的去向,姑姑和姑父没什么主意,家里经济不太好,这个分数去复读,好一点复读班得花不少钱,不复习也不知道该如何,主要还是看表弟自己怎么想的,毕竟表弟也不小了,中间征询过我的意见,我的意见是还是复习一年,并且最好是能和表弟当面聊聊。

昨天他过来了,和他一起聊了聊,希望能力所能及的帮帮他。对于落榜后的打算,他初步打算是上一个高职,学点技能直接工作,我还是建议他能再复读一年,给自己一个机会,给父母一个机会,毕竟没有一个大学文凭,将来的路会更难,虽然有个大学文凭毕业也很难。我从三个方面给了他建议:

首先是自我定位,由于在一个普通的高中,周围同学的成绩都不好,周围的亲戚朋友甚至父母有意无意也会说一些对他没信心的话,这样慢慢的,自己也就把自己定位成一个很普通的学生,一个差生,也慢慢没有上进心,说到底就是自信问题了,如果自己都对自己没信心,那么真的很难。

其次是目标问题,其实无论是有意识的还是无意识的,我们做事情总是有目的的,学习也是如此,我记得我上学时学习总觉得是被家长和老师逼着压着学的,从来都不是主动去学的,所以我一直都很贪玩,直到第一次高考落榜后,进了补习班,才开始明确高考的目标学习的目标,才真正开始努力去学习。

最后才是学习方法的问题,很久以后我才明白,其实无论学什么,都没有什么捷径的,无非是重复重复再重复,也许有的人聪明可以少重复一些,但再聪明也需要不停的反复学习记忆,当然并非不是没有学习方法或不需要讲究学习方法。我觉得首先做事情最好是要有计划,当对自己有了客观正确的定位,有了明确的目标,那么就可以针对性制定计划,长期的计划和短期的计划,执行自己的计划并不断修正自己的计划,一步步向目标接近,才比较容易取得成功;然后就是根据自己的情况找到一个最适合自己效率最高的学习方法。

我知道仅仅靠这样的一次聊天是不可能去改变表弟太多,我只是作为一个过来人给他指一条相对正确的路,最终路是怎么走的还是看他自己。

希望表弟以及千千万万和他一样落榜的同学将来有个光明的前程!

遭遇小偷

来上海后,买了辆新自行车,一周后,一次回家吃晚饭,停在小区里面单位楼门口,一小时后出来已经无影无踪了。

前些天晚上和老婆一起骑车去逛街,停在地下入口门口,用链子锁将两车锁在一起,到了回来的时候,发现车居然没锁,锁还在车筐,想想我这马大哈的行为也不是一次两次觉得可能是忘记锁了,觉得上海治安还真好,还是车太破,这都没被偷,拿起锁一看,才发现原来锁已经被撬开了,只是正好我们来的巧,回想一下我们出来的时候看到一个白衣服年轻人正在车子旁边,也没在意,估计撬完锁刚想骑走,见人来了想等人走了再骑走,没想到刚好是车的主人。

今天中午在复旦那边坐公交,人不是很多,但是还是有人在门口挤,正在掏钱包刷卡,突然感觉左边裤兜有异样,低头一看发现有人用一个白毛巾遮着手在我裤兜里面掏,我手机正在里面呢,见我发现,正在挤的两个人迅速的下车跑了。

一点回忆,关于造土火箭

小时候,我特喜欢研究火箭,这也是为什么后来我报考西工大,每次过年时候的烟花炮竹,我总要从里面弄火药下来研究如果造火箭,当然都是闭门造车那种,所以制造的火箭从来不能飞,只会爆炸,我尝试了很多方法,其中有一次很危险,那时候小学,我拿一个废旧电线天线管子,装满火药,堵住一个口,另一个口留一个小洞,拿个蜡烛加热底部,期望它能尾部喷火飞起来,当时我留了个心眼,人没在现场,躲在窗户外面观察,就看加热没多一会天线管就爆了,进去看全部是天线管碎片,有的嵌到墙上、窗户上、门里面、书里面,都好深,好久后都在翻书的时候发现里面还嵌着一小铁片,想想都蛮后怕!

推荐使用Google Reader

以前我都是用离线的RSS Reader,前不久才开始用Google Reader(http://reader.google.com),感觉不错,可以有效的养成阅读习惯,并且可以通过Reader的Share功能和好友互动,当然负面作用就是每天都要花一些阅读时间在上面,不过如果在路上或者马桶上面用手机看感觉还是不错的。

推荐一个我觉得还不错的RSS地址:

http://feed.feedsky.com/yeeyan

http://www.dapenti.com/blog/rss2.asp?name=xilei

http://feed.comgeo.net/

六一 & 生日

按照老家的规矩都是阴历生日,所以对应的阳历日期都不固定,童年最快乐的生日之一是在某个六一儿童节,既是生日又是六一儿童节,学校还包了场革命电影,那感觉真是太美好了。

九年前生日,和老婆确定恋爱关系,九年来,我们在一起过了每一个生日,度过了很多美好的时光。

时隔多年,又一次在六一过生日,童年早已远去,但在儿女身上得到了延续,和他们在一起玩乐时,能感受曾经快乐的童年。

生日快乐

大多数时候都是在忙碌中,经常会忽略很多事情,很少陪小朋友们出去玩,讲故事哄小朋友的工作大多数是老婆在做的,父母总是劝告我少熬夜注意身体,我总是笑着答应然后一如既往。很疼儿子,也以他为骄傲,但是却还是要扮演一个严父的角色,有时候还会打他,所以他有点怕我,也许这样的状态会持续到他很大的时候;女儿很乖巧很会撒娇,另外是女孩子也比较宠她一点,所以老被她欺负;老婆是个好妈妈,从来不打骂他们,对他们很耐心很讲道理,他们也最喜欢她,这就很好了。

今天老婆生日,但是我却没记得,还是爸妈提醒的,也没有准备什么礼物,只是晚上一起看了场电影,还好老夫老妻的老婆也没跟我计较。曾和老婆讨论过我们在一起这么多年什么时候最快乐,想想还是刚毕业一两年的时候,那时候二人世界,工资虽然不高,但是也勉强够花,又没有房子的压力和小孩的束缚,经常可以出去玩玩,去购物,逢年过节的还会浪漫一下。

把技术卖给不懂技术的人

曾参加过一次技术聚会,大多数都是程序员,一起的有台湾的郭安定老师,郭老师经历比较丰富,席间就有人问他,程序员怎么赚钱?郭老师的回答很精辟:“把技术卖个不懂技术但需要技术的人”,仔细回味一下,确实是这么个理,一般程序员圈子都比较小,比较宅,打交道的也都是程序员为主的技术圈子,很难把自己和技术推销出去,而且还有很多程序员热衷于把自己的技术推向给程序员,这可真难,比如有人写了一个号称很牛X的权限管理系统,想推销给程序员用顺便卖点钱,这可真难啊,大多数程序员一定会不屑一顾的说:“这有啥,我也能写一个”,殊不知程序员的一大乐趣就是“重复发明轮子”。

最近有个朋友找我帮忙,他需要刷新网页点击,本来是手动按F5的,太慢太累太辛苦了,但是又没有什么好办法,为了提高效率,还打算花2000块钱买个二手电脑和拉根网线来做这事。很好的哥们,于是就花了半小时帮他写了个小程序,这程序要是程序员看起来肯定不屑一顾,太简单了,但是对于朋友来说可是帮了他大忙,这在他看来太神奇了,不用辛苦去手动按F5了,只要开着程序,挂一晚上就能刷个十几万的点击,最重要是,省了一笔钱买设备。

技术,卖个不懂技术的人才值钱,才能最大的体现他的价值。

曾祖父当年写过的对联

曾祖父当年也是我们市才子,桃李满门,相当有声望,可惜文革时受到迫害而死,大多诗集作品都不可觅了,幸好曾为当地名胜“小孤山”梳妆亭题过一副对联,至今仍在。联嵌“梳妆”二字:


潮落濯足潮生濯缨谢绝人间脂粉


月缺如梳月圆如镜居然天上妆台

北京的地铁

在北京生活了六年,其中有差不过将近5年是住在回龙观,回龙观是郊区,都在五环外了,所以离城里还是蛮远的,好在回龙观地铁很便利,有回龙观和龙泽两个大站,各个小区和城铁之间都有公交和黑车往返,还是比较便利的,我在北京期间主要的交通方式就是地铁加出租。要到什么地方去就先找最近的地铁站,然后到了后再打个折,经济实惠,不像坐公交要担心堵车和晕车。

从回龙观坐地铁主要是两个方向,东直门和西直门,每天早上往西直门方向的人真的是非常夸张,高峰期不亚于春运,一般要等两趟,第一趟把别人推上车,第二趟等着别人把你推上车。每次8点前后,地铁站门口都要用各种围栏将人群分成一条长队,以保证同时进入地铁的人不至于太多,所以每次这个时候进站是非常慢的,经常要排上一二十分钟,所以一般早上,要么早走要么晚走。

还好我一直是往东边走,东边人要少很多,尤其是过了立水桥后,加上后来上班越来越晚了,经常在9点后,那基本都有座位,但是要小心区间车,因为地铁高峰时会多发车,高峰过了一些车就要回库,所以经常不小心坐上区间车就只能坐一站,然后就要等下一趟车,最重要是这么一折腾就没座位了,后来有经验了车来了看看车头就知道是不是区间车了。从西直门开过来的方向,霍营就是一个车库,从东直门过来就要注意是不是到立水桥的区间车了。一般我下班也比较晚一点,从东直门回来,因为是起点站,基本都有座位,要是没座位,就等一趟,这样肯定能有个座位。

地铁13号线伴我度过了很多时光,最多的时候就是拿手机看小说,有座位就拿出笔记本写写程序,一般从回龙观到东直门半小时,半小时足够我写不少代码 了,Openlab很多代码就是在13号线完成的。在城铁里面写程序是一种境界,首先你要无视周围人的眼神,然后要尽快进入状态,利用有限时间集中解决问题, 实际上在城铁这种环境,没有网络没有电话没有熟人的干扰,反而是容易进入状态,效率很高。前几年翻译过一本书,主要就是利用的城铁时间。

有几次到火车站接人,四五点就到回龙观地铁站,那时候人可真少,一节车厢两三个人,坐着躺着都行。我不太喜欢往西边走,西边人特别多,基本上不太可能有座 位,而且西直门的换乘是最痛苦的,差不多要走两站地,有人说北京最远的距离是西直门到西直门,说的就是著名的西直门桥和西直门地铁换乘,真不知道地铁的设 计者是不是脑袋被门夹了。

看谁表现好

下班回家,带了瓶同事送的饮料(可怜我现在所有同事送的吃的喝的全都得带回家给他们留着)回家。晚上吃饭前,我对两个小朋友说,看谁表现好谁先吃完,谁表现好就由他来给大家分饮料,两个小朋友积极响应!

吃饭果然表现很好,两个小家伙比赛着往嘴巴里面扒饭,哥哥毕竟是大两岁,吃起来还是要快一些,很快就只剩下两口饭了,奶奶就要给哥哥打汤,哥哥摇头拒绝了——担心喝汤耽误时间被妹妹抢先了呀,呵呵。

结果是哥哥先吃完的,哥哥吃完就跑下桌等饮料了,妹妹一看哥哥先吃完了,于是马上放下碗筷,跑到哥哥座位那,拿起哥哥吃过的饭碗和勺子,说帮哥哥把碗送到厨房水池里面,一看饭碗吃的不是很干净,还帮哥哥扒的干干净净,大家哭笑不得,又不好挫伤小朋友积极性,于是表扬了一下妹妹。

哥哥一看妹妹因此受表扬了,坐不住了,马上跑过来,端起一盘菜,说菜凉了,给送到了厨房了,其他人还正在吃着呢……

女儿的善良

女儿是个典型的女孩子,会撒娇,会发脾气,很可爱,也很善良。

哥哥打她了,哭了,没一会,奶奶给他们发东西吃,奶奶就说,哥哥不乖,欺负妹妹了,不给他吃了,女儿马上就说,哥哥没打我,他是闹着玩的,给他吃吧。

吃饭的时候,哥哥不好好吃饭,跑下桌子去玩,爷爷生气了,吓唬他说要拿棍子来打了,女儿马上说爷爷别打,哥哥来吃饭了。

女儿很喜欢吃桔子,桔子剥完,分成两半,一半自己留着,一半分给别人吃。当然有的时候有好吃的,她也会嘴上说要留给哥哥,结果还是都把它吃光了 :)

希望等她慢慢长大了,会继续保持这份善良,但最重要的是,希望她不会因为善良而受到任何伤害。

顺便提一下,虽然故事中,哥哥当了一个不光彩的角色,但是哥哥还是一个好哥哥,绝大多数时候都会护着妹妹,尤其是在爸爸妈妈不在的时候,那是一定什么事情都让着妹妹,什么都护着妹妹的。

重回自习室

我不喜欢自习室!上中学上自习,为了高考,上自习总是盼着停电,一停电就可以早点回家了,一下自习,就赶紧收拾东西回家。大学上自习,为了考试,除了大一上学期还经常去自习实,而且还有很多次半道去了机房,到后来就基本没上过自习了,要不就是快考试了,赶紧去抱佛脚。

而现在,我偶尔会吃完晚饭,和老婆一起,带上一本书,纸和笔,坐到大学自习室里面,安安心心的看书,写写画画,直到自习室关闭。小的时候,读书上自习,是“被逼”的,因为父母老师的管教,因为升学的压力,不得不去上自习。现在没有考试了,没有父母老师的管教了,但是我还是走到了自习室,为了弥补以前没有学好的知识,为了给儿女更好的未来,所以我又回到了自习室,这次我是主动的。

如果,如果能回到从前,我会多去上上自习,好好学学那些对我有用的课程。但是没有如果,所以我现在要重新回到自习室,学习以前没学好的知识。

使用TFS来自动部署站点和Window Service

前言

先问各位看官两个问题:
1. 你们用TFS么?
2. 你们做自动部署么?怎么做的?

这写博客不同讲课,没法及时互动,那我只好自问自答一把了:
1. 用,当然用,按我了解的情况来看,源码管理我想一定是使用率最高的,甚至很多公司只用了TFS的源码管理功能
2. 做,最开始想用TFS来做,但是发现TFS做Build很方便,但是部署貌似不支持,就改用cc.net了,最后研究发现用TFS也是可以实现自动Build + 自动部署的,不仅WebSite,而且Window Service。

那么接下来说说如何来用TFS做自动部署。

使用TFS自动生成Build

使用Team Explorer的向导来创建一个生成定义很方便:

  • 首先设置生成代理:Build->Manage Build Agents
  • 创建生成定义:Build->New Build Definition

这个我想对于绝大部分人都不会有问题或障碍,网上相关文章也很多,也不在此赘述。

到TFS2008之后,就支持设置自动生成了,可以设置每天或者每次Check In生成一次最新的Build。但是当你满怀期望的去看生成的结果,却发现自动生成是很强大,但是部署还得手动去做,在我看来主要有以下几个问题:

  1. Build出来的结果是按照日期和版本来生成目录,所以不可能直接将Build的结果部署到网站中
  2. 对于Window Service程序,如果一个解决方案下有多个Window Service项目,所有项目的编译结果会放到根目录下,比较混乱,不利于分别部署。

基于以上的原因,我不得不寻找其他方案,于是开始使用cc.net来作为自动编译部署工具。

使用CC.Net来自动生成部署

CC.Net全名CruiseControl.Net,可以方便的来进行自动编译部署,相关使用说明推荐看看《[原创]如何用CruiseControl.Net来进行持续化集成》,使用CC.Net来部署WindowService和网站,我的CC.Net配置流程如下:

image

参考配置文件:

<cruisecontrol xmlns:cb="urn:ccnet.config.builder">

  <!-- This is your CruiseControl.NET Server Configuration file. Add your projects below! -->

  <!--

         <project name="MyFirstProject" />

     -->

 

  <project name="MyProject">

    <workingDirectory>d:\dailybuild</workingDirectory>

    <artifactDirectory>d:\dailybuild</artifactDirectory>

    <category>MyProject</category>

    <sourcecontrol type="vsts" autoGetSource="true"  applyLabel="false">

      <server>http://tfsserver:8080</server>

      <domain>mydomain</domain>

      <project>$/MyProject/</project>

      <workingDirectory>d:\dailybuild\MyProject</workingDirectory>

      <cleanCopy>true</cleanCopy>

    </sourcecontrol>

    <tasks>

      <msbuild>

        <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>

        <workingDirectory>D:\dailybuild\MyProject\src</workingDirectory>

        <projectFile>mysolution.sln</projectFile>

        <logger>d:\dailybuild\Rodemeyer.MsBuildToCCnet.dll</logger>

        <buildArgs>/v:quiet /noconlog /p:Configuration=Debug</buildArgs>

        <targets>Build</targets>

        <timeout>900</timeout>

      </msbuild>

      <exec>

        <executable>iisreset</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>/stop</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\PrecompiledWeb</sourceDir>

        <publishDir>D:\MyProject\WebSites</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\WebApps\MyComPlusProject\bin\Debug</sourceDir>

        <publishDir>D:\MyProject\Components</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

     <!-- 在服务器上注册生成的Com+组件-->

 

 

 

      <exec>

        <executable>Regasm.exe</executable>

        <baseDirectory>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\</baseDirectory>

        <buildArgs>D:\MyProject\Components\MyComPlus.dll /tlb:D:\MyProject\Components\MyComPlus.tlb /CodeBase</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>     

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>stop "My Service1"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>stop "My Service 2"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\Services\MyService1\bin\Debug</sourceDir>

        <publishDir>D:\MyProject\Services\MyService1</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\Services\MyService2\bin\Debug</sourceDir>

          <publishDir>D:\MyProject\Services\MyService2</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>start "My Service2"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>start "My Service1"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>iisreset</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>/start</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <email from="tfsreport(a)openlab.net.cn" mailhost="mailserver" mailhostUsername="tfsreport" mailhostPassword="password"  includeDetails="TRUE">

        <users>

          <user name="宝玉" group="buildmaster" address="xxx(a)openlab.net.cn"/>       

        </users>

        <groups>

          <group name="developers" notification="change"/>

          <group name="buildmaster" notification="always"/>

        </groups>

      </email>

    </tasks>

    <triggers>

      <scheduleTrigger time="17:00" buildCondition="ForceBuild" name="Scheduled">

        <weekDays>

          <weekDay>Monday</weekDay>

          <weekDay>Tuesday</weekDay>

          <weekDay>Wednesday </weekDay>

          <weekDay>Thursday </weekDay>

          <weekDay>Friday</weekDay>

        </weekDays>

      </scheduleTrigger>

    </triggers>

    <labeller type="dateLabeller" />

  </project>

 

</cruisecontrol>

 

 

总的来说,CC.Net还是不错的,基本能满足自动部署要求,如果说有什么不足,那么主要就是:

  • 如果编译失败或者部署失败,那么会导致服务需要手动去启动,因为在部署配置中,首先会停止所有相关的服务,如果后续失败,那么会直接跳出,导致后面启动服务的命令不能执行,只能手动执行(也许有其他办法?)
  • 需要额外的客户端,不能直接通过Team Explorer来管理

对TFSBuild进行自定义

偶然发现TFS的Build定义是可以扩展的,在创建一个生成定义后,TFS默认会在源代码管理的TeamBuildTypes目录下创建一个生成定义目录,里面包含两个文件:“TFSBuild.proj”和“TFSBuild.rsp”。其中TFSBuild.proj是XML格式,包含了生成定义的配置,可以对起进行编辑扩展。MSDN中对它的格式有详细说明,感兴趣的朋友可以去了解一下:《Understanding Team Foundation Build Configuration Files》,对于我来说,要求很简单,就是在编译完成的时候,可以通过配置,来进行部署操作,要达到这个目的,就需要用到"target”元素了,通过这个元素,可以让TFSBuild在编译过程的特定时刻执行相应任务,例如:

<Project …> 

  <!--   在Build后,条件为编译没被中断  -->

  <Target Name="AfterDropBuild" Condition=" '$(BuildBreak)'!='true' "> 

    <!-- 删除ctemp目录下所有文件-->

    <Exec Command='del /f /s /q c:\temp\*'/> 

  </Target> 

</Project> 

 

从编译部署流程上来说,基本上和CC.Net类似,通过类似于cc.net配置的方式,我们可以加上相应的Target元素,让它完成停止服务、拷贝文件、启动服务等功能。但是一般我们的TFS是单独的服务器,TFS的Build是在TFS服务器(BuildServer默认都是安装在一起的)上,而需要部署的Application Server是在另外的服务器上,那么如果使用TFSBuild进行自动部署,就必须跨服务器进行部署,包括:停止目标应用服务器的相关服务、拷贝编译后文件到目标文件、启动目标应用服务器相关服务。

为什么CC.Net没有这个问题?一般来说CC.Net的Server直接是安装在待部署的目标服务器上,直接通过它去TFS中获取代码并本机编译部署,所以不需要跨服务器进行部署,网络结构图如下:

image

那么这个问题有两种解决方案:

1. 在需要部署的应用服务器上,安装TFS Build Server,并新建对应的Build Agent,将,这样网络结构基本类似于CC.Net,同样是本机编译本机部署,不需要进行跨服务器操作。但是这只是在理论层面觉得可行,但是我在目标服务器上安装Build Server都失败了,所以并没有验证是否可行,如果有朋友验证过这种方案,也请反馈,谢谢。

2. 借助PSTools这个第三方工具,PSTools是一个很有名的远程操作工具,借助它的PSExec可以在远程服务器中执行命令,当然需要有目标服务器的管理员权限的账号密码、开通目标服务器的139和445端口,借助它,我们就可以让TFS Build在目标服务器中执行停止和启动相关服务器的命令,从而完成部署。相关网络结构图如下:

image

借助PSTools配置TFSBuild进行自动部署

首先需要在TFS服务器上安装PSTools,PSTools下载地址:http://technet.microsoft.com/en-us/sysinternals/bb896649.aspx 

要小心被杀毒软件干掉哦,解压后放到TFS服务器的本地磁盘:

image

前面说了,TFS Build有一个问题是,如果一个解决方案中多个Window Service项目,在Build后,会一起放在Build目录的根目录,不方便分别部署,所以我一般的做法是每个Window Service一个单独的解决方案。

对于我们来说,一般需要自动部署的,有两个环境,一个是开发环境,一个是测试环境,两个环境的配置是不一样的,如果要让自动部署时可以自动选择对应的配置,我的做法是这样的:在解决方案中有多个配置文件,每个环境对应不同的配置文件,在对应环境生成Build的时候,选择对应的配置文件,如下图所示:

image

并且对于每一种部署环境,都会有一个对应的Build定义,不同环境相同服务的配置参数是略有不同的,如下图所示:

TeamBuild定义:
image  
SourceControl中对应的TeamBuildTypes定义

image

接下来我们可以来完成对Window Service和网站的自动部署,参考配置如下:

Windows Service更新配置

    <!--TFSBuild完成后执行-->

    <Target Name="AfterDropBuild">

        <!--定义变量,表示远程服务器地址-->

        <CreateProperty value="\\192.168.1.3">

            <Output TaskParameter="Value" PropertyName ="RemoteServer" />

        </CreateProperty>

        <!--定义变量,表示部署的环境,dev表示开发环境,qa表示测试环境-->

        <CreateProperty value="dev">

            <Output TaskParameter="Value" PropertyName ="DeployEnvironment" />

        </CreateProperty>

        <!--上面两个变量主要是为了方便开发环境和测试环境共用部署配置,只需要修改各自以上两个变量值即可,下面都一样-->

        <PropertyGroup>

            <!--要部署服务在目标服务器中的部署地址-->

            <MyDropLocation>$(RemoteServer)\Services\SSOService</MyDropLocation>

            <!--TFS Server中的本地PSTools文件路径-->

            <RemoteExecutePathFilename>d:\pstools\psexec.exe</RemoteExecutePathFilename>

        </PropertyGroup>

 

        <ItemGroup>

            <MyDropFiles Include="$(OutDir)\**\*.*"/>

        </ItemGroup>

 

        <!--第一步,借助PSExec远程停止目标服务器相关服务-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)   -i 0 -s -accepteula net stop "nop SSOService"'  IgnoreExitCode="true" ContinueOnError="true"/>

 

        <!--第二步,将编译后的文件复制替换到目标服务器的指定文件夹-->

        <Copy

            SourceFiles="@(MyDropFiles)"

            DestinationFiles="@(MyDropFiles->'$(MyDropLocation)\%(RecursiveDir)%(Filename)%(Extension)')"

            ContinueOnError="true"

        />

        <!--第三步,根据当前Build对应的环境选择相关配置文件,并删除多余的配置文件-->

        <Copy SourceFiles="$(MyDropLocation)\Configs\connectionStrings.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\Configs\connectionStrings.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\connectionStrings.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\connectionStrings.qa.config" ContinueOnError="true" />

 

        <Copy SourceFiles="$(MyDropLocation)\Configs cc.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\Configs cc.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs cc.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs cc.qa.config" ContinueOnError="true" />

 

        <!--第四步,借助PSExec远程启动目标服务器相关服务-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)   -i 0 -s -accepteula net start "nop SSOService"' IgnoreExitCode="true" ContinueOnError="true" />

 

    </Target>

 

站点更新配置

    <!--TFSBuild完成后执行-->

    <Target Name="AfterDropBuild">

        <!--定义变量,表示要部署的目标远程服务器地址-->

        <CreateProperty value="\\192.168.1.3">

            <Output TaskParameter="Value" PropertyName ="RemoteServer" />

        </CreateProperty>

        <!--定义变量,表示部署的环境,dev表示开发环境,qa表示测试环境-->

        <CreateProperty value="dev">

            <Output TaskParameter="Value" PropertyName ="DeployEnvironment" />

        </CreateProperty>

        <!--上面两个变量主要是为了方便开发环境和测试环境共用部署配置,只需要修改各自以上两个变量值即可,下面都一样-->

        <PropertyGroup>

            <!--要部署服务在目标服务器中的部署地址-->

            <MyDropLocation>$(RemoteServer)\WebSites</MyDropLocation>

            <!--TFS Server中的本地PSTools文件路径-->

            <RemoteExecutePathFilename>d:\pstools\psexec.exe</RemoteExecutePathFilename>

        </PropertyGroup>

        <!--编译后的站点目录-->

        <ItemGroup>

            <MyDropFiles Include="$(OutDir)_PublishedWebsites\**\*.*"/>

        </ItemGroup>

 

        <!--第一步,借助PSExec远程停止要部署的目标服务器IIS-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)  -i 0 -s -accepteula iisreset /stop'  IgnoreExitCode="true" ContinueOnError="true"/>

 

        <!--第二步,拷贝网站程序到要部署的目标服务器的站点目录-->

        <Copy

            SourceFiles="@(MyDropFiles)"

            DestinationFiles="@(MyDropFiles->'$(MyDropLocation)\%(RecursiveDir)%(Filename)%(Extension)')"

            ContinueOnError="true"

        />

 

        <!--第三步,根据当前Build对应的环境选择相关配置文件,并删除多余的配置文件-->

        <Copy SourceFiles="$(MyDropLocation)\SSO\appSettings.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\SSO\appSettings.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\SSO\appSettings.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\SSO\appSettings.qa.config" ContinueOnError="true" />

 

        <!--第四步,借助PSExec远程启动要部署的目标服务器IIS-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)  -i 0 -s -accepteula iisreset /start' IgnoreExitCode="true" ContinueOnError="true" />

 

    </Target>

 

一个小Tips:有的项目需要特殊的Build参数,例如要允许Unsafe代码,那么需要修改MSBuild参数,这个时候,就要去修改对应的TFSBuild.rsp文件,例如

 

# This is a response file for MSBuild
# Add custom MSBuild command line options in this file
/v:quiet /noconlog /p:Configuration=Debug

 

后记

配置过程是比较麻烦一点,不知道TFS2010是否有所改进,不过配置好后,部署就成了一件非常方便的事情,现在对于我们来说,每天下午5点,TFS会自动去编译部署QA环境,编译完了发通知给相关人士(需要自己主动订阅TFS Alert),在开发环境中,有新的代码CheckIn,需要部署,只要手动选择相应的Build定义,让它开始Build就好了,不再需要去服务器上手动操作。

希望以上说明对各位有所帮助,如果有问题,也请反馈。

对HttpContext的封装

是不是我们经常会有这样的代码:

 

        int userId;

        int.TryParse(Request.QueryString["UserId"< span>], out userId)

 

 

这样取强类型的Url参数,会比较麻烦,那么很自然的就有了类似如下的封装

        public int GetIntFromQueryString(string key, int defaultValue)

        {

            HttpContext context = HttpContext.Current;

            string queryStringValue;

            queryStringValue = context.Request[key];

 

            if (queryStringValue == null)

                return defaultValue;

 

            if (queryStringValue.IndexOf("#") > 0)

                queryStringValue = queryStringValue.Substring(0, queryStringValue.IndexOf("#"));

            int.TryParse(queryStringValue, out defaultValue);

            return defaultValue;

        }

 

 

恩,现在简单一点了,变成了这样:

 

int userId = GetIntFromQueryString("UserId", -1);

 

这样就够了么?不够,注意"UserId"仍然不是强类型,没有智能提示,没有拼写检查,那么理想的封装应该是怎样的?看看下面的调用代码:

int userId = UserContext.Current.UserId;

 

再看看参考的封装代码,注意其中的ThreadStatic,并且想想为什么是ThreadStatic

 

    public class User Context

    {

        HttpContext context;

 

        [ThreadStatic< span>]

        static UserContext userContext = null;

 

        public UserContext()

            : this(HttpContext.Current)

        {

           

        }

 

        public UserContext(HttpContext context)

        {

            this.context = context;

        }

 

        public static UserContext Current

        {

            get {

                if (userContext == null)

                {

                    userContext = new UserContext();

                }

                return userContext;

            }

        }

 

        public int UserId

        {

            get {

                return GetIntFromQueryString("UserId", -1);

            }

        }

 

        public int GetIntFromQueryString(string key, int defaultValue)

        {

            if (context == null)

                return defaultValue;

 

            string queryStringValue;

            queryStringValue = context.Request[key];

 

            if (queryStringValue == null)

                return defaultValue;

 

            if (queryStringValue.IndexOf("#") > 0)

                queryStringValue = queryStringValue.Substring(0, queryStringValue.IndexOf("#"));

 

            int.TryParse(queryStringValue, out defaultValue);

 

            return defaultValue;

 

        }

 

    }

 

 

 

iphone不能打开联系人、短信等问题解决

这问题在我用iphone的时候遇到过很多次,一般出现在安装完某个软件之后,现象就是一点短信图标,闪一下就关了,点联系人也是一样,可以打电话,但是无法查看未接来电、联系人等,基本上没法用了。

前几次都是直接重装系统了,不过总不是个事,忍不住骂了苹果这系统稳定性真差!windows mobile虽然有些方面不如iphone但是稳定性还是很好的,很少会出这种问题。

骂归骂问题总还是要解决的,今天试着用winscp连接上iphone,然后到/private/var/mobile/Library/AddressBook目录把目录下的两个文件的属性里面的权限改成777,发现一切OK了!

再BS一下苹果!

女儿的小心思

场景一:周末的时候,带女儿去一个朋友家,坐出租去的,不知道是晕车还是什么原因,车没开一会,小家伙想下车,因为还没到目的地,所以我们没同意,小家伙就有点闹,然后过了一会,她说要尿尿了,我们赶紧让司机靠边停车了,给她把尿,结果半天才憋了一点出来,尿完上车继续开,结果没多远又喊要尿尿……

场景二:吃饭的时候,哥哥先吃完下去玩去了,她就对我说,椅子倒了,我一看,旁边果然有个小椅子倒地上了,我说没关系,但是她不同意,非要下去扶椅子,扶完椅子马上就跑去找哥哥玩去了。

场景三:我去卫生间,关上门,这小丫头就在门口喊我开门,没搭理她,她就去找奶奶,说要臭臭,奶奶让她在客厅用小马桶,不干,非要去卫生间臭臭,当时老婆就猜到这家伙肯定不是真的要臭臭。

场景四:时间biu~的回到二十五年前,我两岁,姑姑带我去县城,路过茶水摊,第一次看到彩色的汽水,觉得肯定很好喝,很想喝,于是走过去没多远我就对姑姑说我渴了,姑姑说前面有喝茶的地方,我说刚才那就有,于是姑姑就带我回到刚才的茶水摊,这时候我才指着汽水说我要喝这个。

你拍攝的 IMG_5460。

 

偷糖记

某日,妹妹跟着哥哥一起跑到了爷爷奶奶房间,哥哥很熟练的爬到窗台上拿出来一袋冰糖,从里面挤了一颗放到嘴巴,很惬意的含起来。妹妹在旁边眼巴巴的看着,着急的喊:“给妹妹!”,于是哥哥马上哄妹妹说:“别着急,我给你拿。”,然后从袋子里面倒了两粒冰糖递给妹妹,妹妹拿到糖后很高兴的吃起来。

铁环的记忆

爸妈过来的时候,特地在老家找铁匠打了个铁环带过来,给孙子当玩具,可惜儿子对这个兴趣不大,也许是年纪还小,也许是玩具太多。记得小时候,没有什么玩具,绝大部分玩具都是自己DIY来的——铁环、陀螺、火柴枪……

 

 

微信订阅号