2015年7月

首页2015年7月
30
Jul
0

DevExpressVCL控件开发浅析

在Windows平台Win32软件开发领域,由于Microsoft的大力推广及不断更新,Visual Studio系列可以说是目前最为流行的开发平台。当然,任何领域都不可能只有一家独大,在软件开发工具的进化历史长河中,有家公司一直与Microsoft竞争,那就是Borland软件公司。但随着Microsoft在Visual Studio系列上的逐渐发力,Borland的竞争逐渐处于下风,最终将开发工具业务出售给现在的Embarcadero,Embarcadero将Delphi和C++ Builder等开发工具放在一起称为RAD Studio。近几年,虽然Embarcadero不断推陈出新,几乎每年升级RAD Studio,无奈市场占有率不断下降,但这并不能否定目前最新版本的RAD Studio XE6是非常优秀的开发工具。
笔者从Delphi 5.0时代开始,一直使用C++ Builder 作为主要开发工具,现在使用的版本是C++ Builder 2010。RAD Studio从2009版本开始,几乎每年一升级,经历了2010、XE、XE2、XE3、XE4,直到目前最新的XE6,最新版本支持原生Android移动平台开发,有很大发展前景。由于目前我的工作基本上只用到Windows平台,所以一直使用的是2010版本。
在Delphi和C++ Builder开发领域,有一款重量级的开发控件,那就是DevExpress公司开发的用于Delphi与C++ Builder的DevExpress VCL Controls。这套控件功能十分强大,涵盖了界面设计、数据表单等方方面面。由于功能的强大,也造成这套控件的使用也非常复杂,该文就从这套控件在C++ Builder中的安装开始,对它在开发使用过程中的一些技巧及问题做一阐述。
1 DevExpress VCL Controls的编译与安装
DevExpress VCL Controls的版本更新很快,每当RAD Studio新版本发布,DevExpress都会及时跟进,使自己的控件能够兼容最新的Delphi和C++ Builder。目前官网上最新的版本是13.2,支持C++ Builder 2007至最新的C++ Builder XE6,而我们能够从网上获取到的最新版本为13.1.4。该控件的很多版本分为多种安装格式,最完整的是第三方制作的带源码、实例及帮助的完整版安装程序,只需要一步步安装即可,非常方便;但另外一种是只有源代码、实例,而没有安装程序。这种只有源代码的包安装起来很复杂,因为整个DevExpress VCL包含超过180个控件,将近30个package,需要在C++ Builder中将每个package的源代码编译安装,且有一定的安装顺序。这个编译安装过程相当繁琐,造成每次需要安装DevExpress VCL的过程经常出现问题。
笔者依据近几年开发的经验,参考Delphi的dcc32编译器的详细使用帮助,采用命令行编译的方式,将每个package用dcc32进行命令行编译,代码格式如下:
dcc32.exe "..\..\ExpressCore Library\Packages\dxCoreRS14.dpk" -JL -B -U"..\..\ExpressCore Library\Sources;..\..\Library\RS14" -R"..\..\ExpressCore Library\Sources" -N"." -LE"." -LN"." -NH"." -NO"."
这条编译命令中所用到的参数说明:
-JL = 生成C++需要的 .lib,.bpi以及所有的.hpp文件
-B = 编译所有单元文件
-U = 单元文件所在目录
-R = 资源文件所在目录(dcr等文件)
-LE = .bpl文件输出目录
-LN = .dcp文件输出目录
-NH = .hpp文件输出目录
-NO = .obj文件输出目录
其中所有的输出文件指定的目录都是“.”,表示该批处理文件运行时所在的目录,方便最后C++ Builder进行开发时,搜索需要的各种文件。
这是单个dpk文件的编译命令,将所有的dpk文件编译命令生成一个批处理,就可以通过运行该批处理文件,一次性将所有的dpk文件编译完成。在批处理中,由于各个dpk之间的相互依赖关系,有一定的先后顺序,经过测试,确定了整个DevExpress VCL控件编译的顺序,保证所有控件都能正常编译,生成的批处理文件如图1所示:
将下载获取的DevExpress VCL控件源码包解压,复制到自己想要存放的目录,笔者参考自动安装程序的目录,存放到Program Files(x86)\DevExpress VCL,并且在该目录下创建Library\RS14,用于RAD Studio 2010版本的编译后的文件存放,这样方便识别,如果想要编译其他版本,就创建RS15等等其他目录。将我自己创建的批处理文件manuinstallRS14.bat复制到RS14,以管理员身份运行command命令,执行该批处理,稍等片刻,所有的控件就编译好了,文件都生成在RS14目录。   运行C++ Builder 2010,进入Install Packages,添加编译生成的所有以dcl开头的bpl控件,这是设计时控件,这样DevExpress VCL就全部安装完成,可以在C++ Builder中看到这些控件。后面还要在Options中设置软件开发中使用这些控件的编译路径,这个一般程序员都应该清楚,就不多说了。
2 DevExpress VCL中Ribbon控件的使用
DevExpress VCL这套控件包中有超过180个控件,几乎涵盖了软件开发过程中界面设计的方方面面,可以说,有了这套控件,基本上可以不需要安装其他第三方控件。其中的Ribbon控件就是用于开发Ribbon界面的有力工具,Microsoft自从2007年发布Office2007后,其标志性的Office Ribbon界面目前已成为流行软件开发的标准UI,现在许多软件的操作界面都采用Ribbon,包括现在最新的Win8/Win8.1系统,很多地方都已经集成Ribbon操作。RAD Studio从2009版本开始也自带了Ribbon控件,但是自带的这个控件使用起来很麻烦,而且功能不强,比起DevExpress VCL中的Ribbon控件有很大的差距。下面以笔者自己写的几个软件为例,简单介绍一下Ribbon控件的开发使用。
Ribbon控件位于ExpressBars分类中,名称显示为TdxRibbon,TdxRibbon要与TdxBarManager配合使用,先将一个TdxBarManager放置于Form上,然后放置TdxRibbon,TdxRibbon会自动与TdxBarManager关联,缺省情况下,Ribbon栏只有一个Tab,且是Office 2007 Ribbon的风格,一般与Ribbon相配合的还有一个TdxRibbonStatusBar,也放在Form上即可。很简单的三步,一个Ribbon界面的Form即设计好了。在缺省的Tab上面点击右键,就会弹出右键菜单,可以增加或者删除Tab,可以增加Group。每个Tab就是Ribbon栏上一个选项卡,每个选项卡下面可以增加多个Group,在Group中创建各种工具按钮。如下图所示,就是一个简单的Ribbon空界面。在Ribbon控件的属性设置中,有个Style属性,可以设置Office 2007、2010、2013这三种风格。2007与2010的设置还不一样,我们选择2010即可。在现在这个阶段,Ribbon界面左边的那个File菜单还没有出现,对于2010的风格,我们需要放置TdxRibbonBackstageView控件,将TdxRibbon属性中ApplicationButton下Menu关联至TdxRibbonBackstageView,Text属性中输入菜单显示的文字,比如File,同时,在TdxBarManager中创建一个ToolBar,将TdxRibbon的QuickAccessToolbar属性下的Toobar属性关联至这个新创建的Toolbar,这样,Ribbon的菜单就出来了。运行一下这个程序,就可以看到如图2的界面了。
这时候,你会发现一个小小的问题,那就是Windows系统自己的标题栏还是存在的,而真正的Ribbon界面程序,是没有这个标题栏的。这个问题笔者是参考Devexpress提供的demo解决的,首先将TdxRibbon的SurpportNonClientDrawing属性设为true,然后修改该Form创建的基类。即将BCB缺省创建的基类class TForm1 : public TForm,修改为class TForm1 : public TdxCustomRibbonForm,重新编译运行,是不是成功了?
由于Ribbon栏上面众多的Group,每个Group中又有众多的按钮,所以,需要我们在TdxBarManaager控件中详细分类设置好每个Tab,每个Group中对应的按钮,这是个细活,不能着急。以上就是TdxRibbon控件的关键几个问题,注意这几个问题,整个Ribbon界面就搭建好了,其他的Tab栏上面各种按钮控件一个个试用一下即可掌握使用方法。
3 DevExpress VCL中TcxGrid控件的使用
用到DevExpress VCL的控件,我们就不得不提到其中的TcxGrid控件,笔者最初就是从使用这个强大的Grid表格控件开始的。
3.1 TcxGrid基础应用
使用TcxGrid控件创建一个表格应用很简单,首先设置好连接数据库的各种控件,根据连接方式的不同,有不同的设置方法,比如,笔者使用的是ADO连接SQL Server,这也是数据库开发常用的一种方式,设置好TADOQuery和TDataSource控件,将TcxGrid放置在Form上,TcxGrid控件中有两个概念需要理清:Level和View。Level相当于TcxGrid自带的PageControl控件,每个Level下链接一个View,这个View就是具体的表格。图3就是基础的TcxGrid形态。
3.2 TcxGrid分组统计功能
TcxGrid自带很多强大的功能,比如汇总、分组查询,可以得到强大的统计功能。图4显示的是以“发货日期”为一级分组,“市县”为二级分组,这样就能显示某一天中,某个县详细的学校清单,县一级有合计,当天那一级也有合计,非常清楚明晰。
4 结束语
采用Devexpress的控件解决方案,极大地提高了软件开发的效率,降低系统维护的复杂性,也同时降低了投资成本和后期管理成本,具有很高的实用性。该文主要针对支持Delphi与C++ Builder的VCL控件进行介绍,Devexpress的控件,尤其是TcxGrid还有深入的开发方法,需要更深层次的使用与挖掘。

28
Jul
0

TClientDataSet和StringStream相互转换

//转出

sStream := TStringStream.Create;
try
cdsServer.SaveToStream(sStream,dfXMLUTF8);
sStream.Position := 0;
(cdsLocal.FieldByName('F_Blob') as TBlobField).LoadFromStream(sStream);
finally
FreeAndNil(sStream);
end;

//转回

sstream := TStringStream.Create();
try
(cdsData.FieldByName('F_Blob') as TBlobField).SaveToStream(sstream);
sstream.Position := 0;
cdsData.Close;
cdsData.CleanupInstance;
cdsData.LoadFromStream(sstream);
cdsData.Open;
finally
FreeAndNil(sstream);
end;

24
Jul
0

解决方案之数据同步

本篇要讲的是数据库数据的同步方案,关于局域网,或者两台数据库IP可见的同步情况,这里不给出方案,因为这种情况数据库本身就提供了有很多种性能卓越的方案,看帮助文档就可以解决。

本文要讲的案例是: 有A,B两台或者更多的数据库服务器,分处于不同的网络,数据库IP不可见,端口不可见,现在需要

 

A中的 t1表 -----> 单向同步到 B中的 t1表

 

A中的 t2表 <-----> 双向同步到 B中的 t2表

 

也就是AB两数据库服务器的单向同步和双向同步应该怎么做?

 

在internet网中必须考虑网络速度,所以应该保证传输的数据量尽量小一点再小一点。 双向同步就是做两次单向同步而已,我们以从A服务器上的t1表单向同步到B服务器的t1表为例子说明同步方案。

我们来看看三种方案,然后比较一下。

方案一:

 

? 将A站的t1表的数据(DateTable类型)直接传送到B站的t1表;

 

? B端修改式插入t1表中,所谓修改式插入就是当不存在就insert当存在就update;

(相当于mysql里面的:insert into t1 ()values() on duplicate key update 语法)

完成。(简单吧)

 

点评:这是最直接最粗暴也最安全的方案,但是如果同步的表数据比较多,这种方案肯定是行不通的。不过当数据量比较少时,比如说100条以内,则这种方案也凸现出了它的优点:安全,简单。所以这种方案也是有用武之地的。

 

方案二:

? 在同步源一端表中(如案例中的A站t1表)增加

is_sync

is_del

两个tinyint类型或者bite类型的字段;

 

? 当作Insert或者Update操作时,同时将is_sync设置成0,等待同步;

 

? 当作Delete操作时,将is_del设置成1,is_sync设置成0,而不是物理删除;

 

? 在A端查询所有is_sync=0的数据,传递到B端;

? B端接收到数据之后将B表中已经存在的数据作物理增删改并

第2/5页

将成功的结果返回给A;

 

? A端收到B操作成功的结果,将is_sync=0且在返回成功中的数据设置is_sync=1,另外如果还is_del=1则物理删除。

点评:这种方案是比较常见但我觉得还不明智的方案,虽然它做到了进行细粒度的同步,但却添加了很多附加操作,如增删改操作都要更改is_sync的值,如果正好项目已经做到收尾阶段,所有的数据层已经实现了, 再要加同步的话改动就比较大了,所以这种方案优点是同步精度提高,传输的数据量减少,但对原本的增删改代码影响较大。

与之相似的方案还有增加相同结构的表(称为它的待同步表),用触发器将增删改操作记录到这种待同步表中,然后按上面的流程实现的同步,那种方案也有同样的许多附加内容,如新增触发器和待同步表,也对现行系统造成增加负担和复杂度的负面影响。

方案三:

 

看方案三之前先介绍一下Rowversion这种字段类型,在mysql中是TIMESTAMP类型。

Long Long ago,MSDN说:“每个数据库都有一个计数器,当对数据库中包含 rowversion 列的表执行插入或更新操作时,该计数器值就会增加。此计数器是数据库行版本。这可以跟踪数据库内的相对时间,而不是时钟相关联的实际时间。一个表只能有一个 rowversion 列。每次修改或插入包含 rowversion 列的行时,就会在 rowversion 列中插入经过增量的数据库行版本值。”

具体的关于Rowversion类型的说明就不详细写出来了,读者可以在msdn上搜一下。总之,这种类型的字段会更着数据库的记录自动改变,不需手工操作,利用这个特性,设计出第三种同步方案。 ? 在同步源一端(A中的t1表)表中增加如下三个字段(以mssql字段类型为例子)

 

row_version rowversion, -- 当前版本

第3/5页

 

sync_version binary(8), -- 已同步版本

is_del bit default 0, -- 是否删除

? 当A中t1表进行增加或修改操作时,row_version字段的值会自动变成当前时间,而sync_version的值不会变,当做删除操作的时候,需要将is_del设置成1,而不做物理删除;

? 查询A中所有row_version!=sync_version的数据,传送到B端;

 

? B接收到数据之后做物理增删改,并将成功的结果返回给A;

? A接收到B返回的结果之后,将sync_version与

row_version的值设置成相同,如果其中有is_del=1的则物理删除;

 

点评:这种方案我觉得是同步大表的最优方案,它只有在做删除操作的时候改成逻辑删除,其他操作不用考虑到同步的问题,对已经存在的系统影响最少,而且也是精确同步,不占用紧张的网络资源,这种方案是本篇的主角,也是我大力推荐的方案。

在mysql中可以用 TIMESTAMP类型实现,mysql的表中只要含有TIMESTAMP字段(且

第4/5页

是无默认值的),则当表有更新或增加时就会改变第一个TIMESTAMP类型字段的值为当前时间,这同样可以应用上述第三套方案。

 

在实际运用中,要看表的大小来选择同步方案,如果表较小则用第一种方案是最好的,如果表较大,则第三种是最佳方案。

 

鉴于这是一个解决方案的文章,索性把方案说到具体的技术实现层面上来。

案例中的A需要将表数据传送给B,这个如何实现呢?

实际中的情形通常是这两个网络都不会开放除了网站80之外的其他端口给外网调用,而且方案中都需要有一个程序每隔一段时间扫描一次数据库看是否有需要同步的数据,综合这些情况,可以很容易想到用win Service+ web Service的方式解决。

而笔者推荐用win service+remoting的方式来做,据测试,使用remoting比web Service方式快7-8倍。不过两者都有传送包大小的限制,所以传送数据的时候最好分批此来传,一次100或者1000条,以免因为数据包超过大小而同步失败。

 

如果熟悉.Net的Remoting(或Webservice)、WinService技术的人,肯定知道

winService如何与remoting通讯,看到这里肯定也知道怎么实现了。由于这是一篇关于解决方案的文章,所以具体技术方面的基础知识就不详述了,望此篇对大家有所帮助。有更好的方案欢迎留言交流。

24
Jul
0

虎口夺食—SQLServer运维

数据是一个企业的命根子,尤其是在当今绝大部分的行业都实现了信息化的管理的时代,企业所有运营的数据、财务信息等都会存放到数据库中,在用户量和数据
量不断变大的情况下,如何保障这些信息的安全、保证随时随地的能被用户访问,同时还需要在用户不间断访问的情况下调整、修改这些数据和结构,用以满足业务的需
求变更,这本身一门很艺术的事了。

这篇文章主要总结了SQLServer数据库运维时,在保证数据安全和DBA操作数据库时保证不影响用户访问方面的一些技巧,之所以将之形容为“虎口夺食”,是因为DBA
是个风险相当高的行业,在高并发、大数据量的系统中,很多小的失误或者操作不当,都将造成严重的后果,不仅影响系统的正常运行,甚至牵连到整个数据的安全,给企业
造成不可估量的损失;我们粗算一笔账,以一个日交易量2000万的网站来说(中型规模),如果因你的操作造成网站当机一个小时,我们按一天20小时交易时间来算(另外
4小时可能没多少交易量),一个小时就是100万的损失,这个只是显性成本,还有因网站故障导致用户流量的隐性损失,如果企业要与员工来算这边账的话,那技术吊丝一
辈子估计都得卖给这个企业了;当然,一般企业不会这么去跟员工算计(否则没人敢干活了),但是一次这种失误发生的话,个人KPI年终奖、部门奖金等就别指望了,而且
因为你的错误,你的顶头上司还会受到牵连,如果你犯错“频繁”(注意DBA一年范两次这样的错就算频繁了),公司不辞你,估计也没脸在公司待下去;我就见过一些犯过类
似错误的DBA,在往后进行某些数据库操作时,口中会念念有词,手还会止不住的发抖(正所谓一朝被蛇咬,十年怕井绳)。

下面将分四个部分,共十个方面来说明我整理的在SQLServer数据库运维的一些技巧:

保障数据访问

这个部分也可以叫在线容灾恢复,就是数据库发生问题后,在不中断用户访问(或者中断时间很短)的前提下,恢复数据库系统。

1.? ?? ??Mirror+Replication?自动切换
这种情况是在为核心数据库做了镜像,同时又做了复制的情况下,在主服务器发生问题时,系统能自动的将数据库切换到镜像机器,复制也同步切换过去(如果单纯只
是做镜像切换的场景比较简单),也就是说核心服务器当机时,数据和同步链都不受影响,如果前端访问时是配置两个IP的(一个核心服务器IP,另一个是Mirror机器IP),
那前端的访问就不会中断,在服务器出现问题时实现了对前端透明的切换过程;这种情况的详细过程请参考:SQLServer 数据库镜像+复制切换方案

2.? ?? ??群集在线添加节点
如果您的数据库服务器做了群集(假设是双A群集),现在群集中的某台机器出现了问题,那另外一台将承受双业务的压力,此种情况下,为防止我们单台服务器压力
过大,或者再出现问题,我们应该尽快的替换掉出现问题的那台服务器,最好的方法是在线添加一个新的节点,步骤如下:

  • 新弄一台服务器,做好必要的配置(加域、加心跳线、配置HBA卡等);
  • 将新服务器添加到目前的Windows群集中;
  • 将新服务器添加到SQLServer群集中;
  • 将原本在出现问题机器上的业务切换到新添加的服务器中;
  • 将出现问题的节点从群集中删除。

这个过程因为要在添加完节点后,将业务切换过来,所以会有短暂的业务暂停(相当于重启了一次SQLServer服务),但一般是可以接受的,这样我们就将损坏的机器做了
在线的替换,基本不影响业务的正常进行。

提高数据库访问能力

3.? ?? ??在线扩充数据库读能力
能在线扩充读能力的,一般都是需要做了数据库复制的环境,很多互联网企业在做企业的促销活动时,经常采取这种方案(尤其是电商);在线扩充读能力一般分两种
情况,第一种是提前准备好了读的服务器,数据也同步了,只需要在读出现瓶颈时,将准备好的服务器添加到负载均衡的缓冲池即可;另外一种是没有提前准备好读的服务
器,而是临时添加,我们就来说下这个过程:

  • 准备好需要添加的服务器;
  • 将其他读的机器上的数据库备份,还原到新的机器;
  • 从分发机器上创建到这台机器的同步链并暂时禁用(可以参考其他读机器同步链);
  • 同步链通过数据库比较工具(BCP、TableDiff等)修复缺失的数据,
  • 启用同步链,跳过数据已经存在的冲突错误;
  • 没有问题后,与其他只读机器比较数据量的情况,如果没问题即添加完成;
  • 将新机器添加的负载均衡缓冲池,提供前端访问。

从整个过程来看,临时扩充系统读能力还是比较麻烦的,需要操作的DBA技能比较熟练,而且数据修复部分可能需要的时间比较长,一般应是提前准备的好,防止临时添加时
时间过紧而犯错。

4.? ?? ??SSB + Replication
SSB是SQLServer数据库的异步通信功能,如果企业对数据实时性要求不是那么高的话,是完全可以采用这项技术的,它的最大好处就是缓解瞬时的高峰压力,将部分操
作异步处理,保证前端用户其他操作的顺畅;举个例子,我们在线预订酒店就可以用SSB技术,当你在网页上面提交订单后,系统不会马上告诉你订单是否成功,这时我们
就用SSB技术传递一个消息给后端去处理,处理完成后再向用户手机发送一条信息,提示他是否预订成功,这样就缓解了网上大量用户在同一时刻下单时,造成网站阻塞的
情况;如果能在结合复制技术,在读的机器上完成下单操作,然后通过SSB技术再到主服务器上去完成事务的处理,效果会更加(因为读的机器比较多,下单压力会分散到
多台机器),这个技术大家可以参考:数据库架构??这篇文章。

5.? ?? ??架构设计扩充系统读能力
架构设计中,加快系统读能力的方案比较多(因为读比较频繁,容易出现瓶颈),比较普遍的是以空间换时间方案,按不同的维度将数据存放多份,在用户进行查找时根
据查找的条件将查询定位到不同的服务器;例如我们将数据按三个维度来存放:普通维度、用户维度和产品维度,当前端写入数据时,就根据规则算法将数据按不同的维度写
入不同的数据库服务器(前端写入的操作会变得复杂),而在用户查找时,就可以按用户选择的条件定位到相应的数据库上面去查找相关的数据了(如果还能结合Lucence等
搜索引擎,效果会更好)。

在线数据库操作

6.? ?? ??在线系统添加非空字段(带默认值)
为在线系统的某个表添加一个非空的字段,这个操作是经常会遇到的,在数据量小,用户访问量不那么频繁时不存在什么问题,一旦表的数据量达到千万级别,而且用户
对这个表访问频繁时,这个看似简单的操作就变得不是那么容易了;如果直接操作往往消耗时间很长,而且其他用户的操作被大量阻塞,甚至出现数据库假死现象;这种情况
下处理这些操作的方式就必须改变,可能的方案如下:

  • 选择业务低峰时间进行操作(如:晚上);
  • 加一个带默认值,且可以为NULL的字段,完成后将为NULL的数据改成默认值;
  • 先加一个为NULL的字段,将数据填充后,改成带默认值的非NULL字段。

从这三种方式来看,第二种方式是比较好的,因为它既能满足后面不断增加数据为非NULL值的需要,也不会造成大面积的阻塞,而且能满足时间上的需求(这种修改一
般可能要当场就进行,而不是等上一天);当然在做NULL值数据修复时,需要分批进行(一次性操作又会遇到大量的阻塞和操作时间过长的问题),如按下面的代码形式
修改:

declare?@rowcount?int
if?OBJECT_ID('tempdb.dbo.#temp1')?is?not?null? ??? ?
? ?drop?table?dbo.#temp1?

--更新临时表?
create?table?dbo.#temp1(id?int?primary?key?not?null)??

while?1=1
??begin
? ? --每次修改条记录
insert?into?dbo.#temp1(id)??
? ??select?top?1000?id?from?dbo.Table1?where?IsUpdate?is?null

? ? update? ? dbo.Table1??set?a.IsUpdate=0??where?ID?in(select?ID?from?#temp1)? ?? ?
? ? set?@rowcount=@@rowcount
if?@rowcount=0
break;??
? ???else
? ?? ?? ? waitfor?delay?'00:00:00.500'?--暂停.5秒
? ?truncate?table?#temp1
end

这类型的操作还有:添加索引,删除一个大表的部分数据等,操作的时候都需要使用一定的技巧才顺利完成。

7.? ?? ??数据库收缩
数据库变大,磁盘空间不足时,DBA往往都会采取收缩数据库或者数据文件的操作,但是这个操作在数据库很大时往往是一个相当耗时的过程,我们在对数据库(或者
文件)收缩前最好先看一下最多能释放多少空间(下图的最小值)

然后选择能释放空间比较大的文件进行收缩,而且收缩时一定要分部进行,不要一次性收缩,可以每次选择收缩5G;刚开始收缩时是比较快的,越往后面收缩需要的
时间也越长(因为系统需要挪动的数据更多),如果觉得收缩时间过长了,我们可以终止掉,这样就不会有一次性收缩时造成数据库假死的现象;收缩的同时我们还需要查看
下阻塞的情况,如果有用户进程被收缩进程阻塞了,而且一定的时间内不能释放,应该马上终止收缩进程,防止影响业务的运行:
select?*?from?sys.sysprocesses?with(nolock)?where?blocked<>0

8.? ???在线CPU调整
在线系统如果CPU变得比较高了,我们在做调整时往往要对症下药,不能盲目处理,否则可能造成更严重的CPU性能问题;CPU 突然走高,70%的可能是因为新进来
的一些语句缺乏相关的索引,尤其是一些有GroupBy、 OrderBy 这类型的语句,那我们如何来找到这些语句呢?一个常见的方法是查找DMV,找出当时消耗CPU最多的
语句,如下:

--总耗CPU最多的前个20条SQL:
SELECT TOP 20
total_worker_time/1000 AS [总消耗CPU 时间(ms)],execution_count [运行次数],
qs.total_worker_time/qs.execution_count/1000 as [平均消耗CPU 时间(ms)],
last_execution_time as [最后一次执行时间],max_worker_time /1000 as [最大执行时间(ms)],
SUBSTRING(qt.text,qs.statement_start_offset/2+1,
(case when qs.statement_end_offset = -1
then DATALENGTH(qt.text)
else qs.statement_end_offset end -qs.statement_start_offset)/2 + 1)
as [使用CPU的语法], qt.text [完整语法],
qt.dbid, dbname=db_name(qt.dbid),
qt.objectid,object_name(qt.objectid,qt.dbid) ObjectName
FROM sys.dm_exec_query_stats qs with(nolock)
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
where execution_count>1
ORDER BY??total_worker_time DESC


--平均耗CPU最多的前个20个SQL:
SELECT TOP 20
total_worker_time/1000 AS [总消耗CPU 时间(ms)],execution_count [运行次数],
qs.total_worker_time/qs.execution_count/1000 as [平均消耗CPU 时间(ms)],
last_execution_time as [最后一次执行时间],min_worker_time /1000 as [最小执行时间(ms)],
max_worker_time /1000 as [最大执行时间(ms)],
SUBSTRING(qt.text,qs.statement_start_offset/2+1,
(case when qs.statement_end_offset = -1
then DATALENGTH(qt.text)
else qs.statement_end_offset end -qs.statement_start_offset)/2 + 1)
as [使用CPU的语法], qt.text [完整语法],
qt.dbid, dbname=db_name(qt.dbid),
qt.objectid,object_name(qt.objectid,qt.dbid) ObjectName
FROM sys.dm_exec_query_stats qs with(nolock)
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
where??execution_count>1
ORDER BY (qs.total_worker_time/qs.execution_count/1000) DESC

不过DMV记录的是一些瞬时或者部分时间内系统收录的语句,往往不够全面,如果我们能每天在服务器上开一个Trace文件,通过Trace文件来分析就会比较全面了,
如下:

如果你平时有对Trace文件进行分析和格式化并生成相关图表的话,那优化起来就更加有针对性了(格式化后,前十位消耗CPU的语句):

找到了语句,就可以来优化语句了,该加索引的加索引,该做调整的做调整,但是如果我们分析出来的语句该有的索引都有,该改进的地方也都改进了,它因为数据量
或者访问频繁等原因暂用CPU的资源依然相当高,那怎么办?我们还有其他的方法处理吗?还是有的,主要的途径有如下几种:

  • 查找等待信息,如果CPU类型的等待排在前面(主要是CXPACKET和SOS_SCHEDULER_YIELD两类),我们就可以认为是CPU的处理能力不足,此时为不影响其他的用户进程,可以将CPU的并行开销(调大cost threshold for parallelism)和并行度做调整(调小max degree of parallelism);
  • 限制相关的语句使用CPU的数量(加上Option参数),如限制语句最多使用两颗CPU:select * from Products with(nolock) option(maxdop 2)
  • 看看数据库使用的内存是否还可以做调整,或者系统内存是不是大部分被数据库使用;
  • 查看一下索引的碎片是否过大,过大的话需要做索引的重新整理(这个操作需要在业务低峰时进行);
  • 查看下统计信息是否是最新的,如果不是,更新统计信息(这个操作往往比较有效);
  • 判断语句是否使用了正确的执行计划,如果不是,删除已有的执行计划,再重新执行;如果是存储过程可以加 WITH RECOMPILE。

相信采取了这些措施后,应该能让服务器的CPU压力有所缓解,如果还是不能解决问题,那就需要更换更好的CPU了(或者是添加更多的内存)。

数据安全

9.? ?? ??备份与还原
备份与还原是DBA一个很重要的任务,再怎么强调都不过分,就像国家花大价钱培养军人一样,养兵千日,用兵一时,它是关键时刻最后的救命稻草;备份还原也有一些
使用的技巧,如:备份计划和频率的制定,需不需要远程和本地双重备份,备份时我们是否要加“CheckSum”参数,是否需要做压缩备份,是否需要定期做还原测试,还原
有问题时加“CONTINUE_AFTER_ERROR”尽量挽救数据,是否只需要做页还原,以及尾日志的备份和还原等等,如果要保证备份的数据绝对的安全可靠的话,做远程和本
地双备份,做定期的还原测试这些都是必要的;如果还有人问做了Mirror或者LogShipping之后,还需要做备份吗?那请想下,如果某个重要的表被某个不小心的人删除了,
你能通过Mirror或者LogShipping找回吗?

10.核心数据多层保障
核心的数据做多重的保障是必要的(虽然成本会变高),毕竟核心的数据很大程度上对一个企业能否继续运营起着关键作用,在出现问题时,如果一套保护方案出现问
题,我们还有其他的方案来恢复数据,这些保护的投入是值得的,下图罗列了常用的保护方案,DBA可以根据企业的实际情况(主要是成本因素),选取其中的某些技术方
案,来保障数据的安全。

以上十点只是我个人在做DBA的过程中总结的一些技巧,如大家有其他的一些方面的技巧,欢迎留言交流。

20
Jul
0

复杂的时间调度

活动功能本身很简单。问题就在于,时间配置上,什么时间段开发活动。

配置多种多样。比如,没做星期2,4,6活动。每周的周六,周日,活动。指定月份和日期的活动。配置简直是天花乱坠了。。。这尼玛怎么搞????

那么有么有万能的配置方式呢?首先,我能想到的配置肯定是字符串格式的。

必须包含,年月日星期时间,这尼玛不就犯难了嘛????我们C#的 DateTime 类型格式化只支持年月日,时分秒啊。星期怎么控制???例如,我要每个星期的星期天搞一个活动,拉一拉充值消费我擦。。。

我自己都把自己绕晕了。。好吧。。。

后来想到一种方式,

[年][月][日][星期][时间]

[*][*][*][*][*]

这样利于分割,配置,清晰。

然后就是验证,时间在不在配置的时间开发内?

当然想到的*肯定是默认支持所有的

[2015][7][*][*][10:00-11:59]

这个格式,表示2015年的7月每一天的10点到12点为配置开启时间

[2015][7-9][*][*][10:00-11:59]

这个格式,表示2015年的7月1日到9月30的每一天的10点到12点为配置开启时间

[2015][7/9][*][*][10:00-11:59]

这个格式,表示2015年的7月或者9月的每一天的10点到12点为配置开启时间

[2015][*][*][2/4/6][10:00-11:59]
这个格式,表示2015年的每一个星期2,星期4,星期6?的每一天的10点到12点为配置开启时间

接下来,就是验证这个时间格式。

复制代码
 1         #region 验证当前时间 年,月,日,星期,是否符合 static bool VerifyDate(int nowItem, string items)
 2         /// <summary>
 3         /// 验证当前时间 年,月,日,星期,是否符合
 4         /// </summary>
 5         /// <param name="items">1-7;表示 1 到 7 , 1/7 表示 1 或者 7</param>
 6         /// <returns></returns>
 7         static bool VerifyDate(int nowItem, string items)
 8         {
 9             string nowItemStr = nowItem.ToString();
10             if ("*".Equals(items) || nowItemStr.Equals(items)) { return true; }
11             else if (items.IndexOf("-") > 0)
12             {//区间划分
13                 string[] itemSplit = items.Split('-');
14                 int item1 = 9999;
15                 int.TryParse(itemSplit[0], out item1);
16                 int item2 = 9999;
17                 int.TryParse(itemSplit[1], out item2);
18 
19                 if (item1 <= nowItem && nowItem <= item2) { return true; }
20             }
21             else if (items.IndexOf("/") > 0)
22             {//或划分
23                 string[] weeksSplit = items.Split('/');
24                 foreach (var item in weeksSplit)
25                 {
26                     if (nowItemStr.Equals(item)) { return true; }
27                 }
28             }
29             return false;
30         }
31         #endregion
32 
33         #region 验证当期时间格式 static bool VerifyTime(DateTime date, string itemTime)
34         /// <summary>
35         /// 验证当期时间格式
36         /// </summary>
37         /// <param name="date"></param>
38         /// <param name="itemTime"></param>
39         /// <returns></returns>
40         static bool VerifyTime(DateTime date, string itemTime)
41         {
42             bool ret = false;
43             if (!"*".Equals(itemTime))
44             {
45                 var items = Regex.Split(itemTime, @"/");
46                 foreach (var item in items)
47                 {
48                     string[] itemTimes = item.Split('-');
49                     var hhmm = Regex.Split(itemTimes[0], @":|:");
50                     int hh = 24;
51                     int.TryParse(hhmm[0], out hh);
52                     int mm = 60;
53                     int.TryParse(hhmm[1], out mm);
54                     if (date.Hour > hh || (date.Hour == hh && date.Minute >= mm))
55                     {
56                         if (itemTimes.Length > 1)
57                         {
58                             var hhmm1 = Regex.Split(itemTimes[1], @":|:");
59                             int hh1 = 24;
60                             int.TryParse(hhmm1[0], out hh1);
61                             int mm1 = 60;
62                             int.TryParse(hhmm1[1], out mm1);
63                             if (date.Hour < hh1 || (date.Hour == hh1 && date.Minute < mm1)) { ret = true; }
64                             else { ret = false; }
65                         }
66                         else { ret = true; }
67                     }
68                     else { ret = false; }
69                     if (ret)
70                     {
71                         break;
72                     }
73                 }
74             }
75             else { ret = true; }
76             return ret;
77         }
78         #endregion
复制代码

 

看看结果

这样挺万能的吧?

如果每一天有两档活动开放

[2015][7][*][*][10:00-11:59/14:00-16:59]

 

 

当我完成了这个需求的时候,,新的需求又来了,,,妈蛋,,,需要倒计时,,活动还有多长时间开始。。

卧槽。。

好吧,,有需求就是大爷。。只能苦逼的程序去搞定了,需求一句话,我却花了一天时间啊。。。

我的思路是分解出,所包含的年月日,时间段开始。

复制代码
 1         #region 获取配置的年月日星期等信息 static List<int> GetConfigDate(DateTime date, int p1, string p3)
 2         /// <summary>
 3         /// 获取配置的年月日星期等信息
 4         /// </summary>
 5         /// <param name="date"></param>
 6         /// <param name="p1"></param>
 7         /// <param name="p3"></param>
 8         /// <returns></returns>
 9         static List<int> GetConfigDate(DateTime date, int p1, string p3)
10         {
11             List<int> rets = new List<int>();
12             string p1Str = p1.ToString();
13             if ("*".Equals(p3) || p1Str.Equals(p3))
14             {
15                 rets.Add(p1);
16                 rets.Add(p1 + 1);
17             }
18             else if (p3.IndexOf("-") > 0)
19             {
20                 string[] weekSplit = p3.Split('-');
21                 int week1 = 9999;
22                 int.TryParse(weekSplit[0], out week1);
23 
24                 int week2 = 9999;
25                 int.TryParse(weekSplit[1], out week2);
26                 for (int i = week1; i < week2 + 1; i++)
27                 {
28                     rets.Add(i);
29                 }
30             }
31             else if (p3.IndexOf("/") > 0)
32             {
33                 string[] weeksSplit = p3.Split('/');
34                 foreach (var item in weeksSplit)
35                 {
36                     int temp = 0;
37                     if (int.TryParse(item, out temp))
38                     {
39                         rets.Add(temp);
40                     }
41                 }
42             }
43             else
44             {
45                 int temp = 0;
46                 if (int.TryParse(p3, out temp))
47                 {
48                     rets.Add(temp);
49                 }
50             }
51             return rets;
52         }
53         #endregion
54 
55         #region 获取配置的时间字符串 static List<string> GetConfigTimeStr(string itemTime)
56         /// <summary>
57         /// 获取配置的时间字符串 
58         /// </summary>
59         /// <param name="itemTime">必须类似的格式 单条 00:00-23:59  多条00:00-23:59/00:00-23:59</param>
60         /// <returns></returns>
61         static List<string> GetConfigTimeStr(string itemTime)
62         {
63             List<string> retObjs = new List<string>();
64             // 00:00-23:59
65             if (!"*".Equals(itemTime))
66             {
67                 var items = Regex.Split(itemTime, @"/");
68                 foreach (var item in items)
69                 {
70                     string[] itemTimes = item.Split('-');
71                     retObjs.Add(itemTimes[0]);
72                 }
73             }
74             else
75             {
76                 retObjs.Add("00:00");
77             }
78             return retObjs;
79         }
80         #endregion
复制代码

这里有一个蛋疼的问题,就是包含了星期,那么无疑与日期就要牵涉跨星期跨月,跨年了,

在这里特别鸣谢兄弟帮忙的分析。。

所以我分析出来,检查包含的年月日来构造datetime 然后完成7天构造器。来分析时间段。

如果是本月那么就从当前的日期开始,如果不是本月那么就从1号开始,

复制代码
 1         #region 处理星期包含的日期 日  static void ActionWeekDay(int weekmin, int weekmax, ref List<int> days, ref List<int> months, ref List<int> years)
 2         /// <summary>
 3         /// 处理星期包含的日期 日
 4         /// </summary>
 5         /// <param name="weekmin"></param>
 6         /// <param name="weekmax"></param>
 7         /// <param name="days"></param>
 8         /// <param name="months"></param>
 9         /// <param name="years"></param>
10         static void ActionWeekDay(int weekmin, int weekmax, ref List<int> days, ref List<int> months, ref List<int> years)
11         {
12             DateTime nowWeekDate = DateTime.Now;
13             List<int> tempDays, tempMonths, tempYears;
14             tempYears = years.ToList();
15             tempMonths = months.ToList();
16             tempDays = days.ToList();
17             foreach (var itemYear in tempYears)
18             {
19                 foreach (var itemMonth in tempMonths)
20                 {
21                     int itemDay = 1;
22                     if (nowWeekDate.Month == itemMonth)
23                     {
24                         itemDay = nowWeekDate.Day;
25                     }
26                     DateTime date = new DateTime(itemYear, itemMonth, itemDay);
27                     for (int i = 0; i < 7; i++)
28                     {
29                         int week = (int)date.DayOfWeek;
30                         if (week == 0)
31                         {
32                             week = 7;
33                         }
34                         if (weekmin <= week && week <= weekmax)
35                         {
36                             if (!days.Contains(date.Day))
37                             {
38                                 days.Add(date.Day);
39                             }
40                             if (!months.Contains(date.Month))
41                             {
42                                 months.Add(date.Month);
43                             }
44                             if (!years.Contains(date.Year))
45                             {
46                                 years.Add(date.Year);
47                             }
48                         }
49                         date = date.AddDays(1);
50                     }
51                 }
52             }
53         }
54         #endregion
复制代码

验证器

复制代码
  1    #region 验证时间:[*][*][20/22][*][10:00-11:59/16:00-17:59] static public long VerifyDateTime(string timeStr)
  2         /// <summary>
  3         /// 验证时间:[*][*][20/22][*][10:00-11:59/16:00-17:59]
  4         /// <para>第一个是年,,第二个是月,第三个是日期,第四个是星期,第五个是时间,</para>
  5         /// <para>每一个参数,"-" 表示 到 如:“2015-2017”表示 2015 到 2017, "/"  表示 或者 如: “2015/2017”表示2015 或者 2017</para>
  6         /// <para>返回值 -1 表示永久过期,0 表示在时间规则内,大于 0 表示倒计时</para>
  7         /// </summary> 
  8         static public long VerifyDateTime(string timeStr)
  9         {
 10             var items = Regex.Split(timeStr, @";|;");
 11             items.Reverse();
 12             long ret = -1;
 13             DateTime date = DateTime.Now;
 14             foreach (var item in items)
 15             {
 16                 //验证时间匹配
 17                 if (VerifyConfigTimeStr(date, item))
 18                 {
 19                     ret = 0;
 20                     goto Lab_Exit;
 21                 }
 22                 //未通过时间匹配,检查返回剩余时间
 23                 List<string> strs = new List<string>();
 24                 string[] timeStrs = item.Split(new char[] { ']' });
 25                 for (int i = 0; i < timeStrs.Length - 1; i++)
 26                 {
 27                     string time = timeStrs[i].Replace("[", "");
 28                     strs.Add(time);
 29                 }
 30 
 31                 string times = strs[4];
 32                 string weeks = strs[3];
 33                 string days = strs[2];
 34                 string months = strs[1];
 35                 string years = strs[0];
 36 
 37                 int hour = 0, minute = 0, second = 0;
 38                 var tempYears = GetConfigDate(date, date.Year, years);
 39                 var tempMonths = GetConfigDate(date, date.Month, months);
 40                 var tempDays = GetConfigDate(date, date.Day, days);
 41                 //由于星期比较特殊所以获取与星期相关的日期的时候有点诡异。
 42                 if (!"*".Equals(weeks))
 43                 {
 44                     if (weeks.IndexOf("-") > 0)
 45                     {
 46                         //星期的间隔模式
 47                         string[] weekSplit = weeks.Split('-');
 48                         int weekmin = 9999;
 49                         int.TryParse(weekSplit[0], out weekmin);
 50                         int weekmax = 9999;
 51                         int.TryParse(weekSplit[1], out weekmax);
 52                         ActionWeekDay(weekmin, weekmax, ref tempDays, ref tempMonths, ref tempYears);
 53                     }
 54                     else if (weeks.IndexOf("/") > 0)
 55                     {
 56                         //星期的或模式
 57                         string[] weeksSplit = weeks.Split('/');
 58                         int tempWeek;
 59                         if (int.TryParse(weeksSplit[0], out tempWeek))
 60                         {
 61                             if (0 <= tempWeek && tempWeek <= 7)
 62                             {
 63                                 ActionWeekDay(tempWeek, tempWeek, ref tempDays, ref tempMonths, ref tempYears);
 64                             }
 65                         }
 66                     }
 67                     else
 68                     {
 69                         //特定星期的模式
 70                         int tempweek = 0;
 71                         if (int.TryParse(weeks, out tempweek))
 72                         {
 73                             ActionWeekDay(tempweek, tempweek, ref tempDays, ref tempMonths, ref tempYears);
 74                         }
 75                     }
 76                 }
 77                 else
 78                 {
 79                     //未指定星期的模式
 80                     ActionWeekDay(1, 7, ref tempDays, ref tempMonths, ref tempYears);
 81                 }
 82 
 83                 var tempHHMMs = GetConfigTimeStr(times);
 84 
 85                 //进行简单的排序
 86                 tempYears.Sort();
 87                 tempMonths.Sort();
 88                 tempDays.Sort();
 89                 tempHHMMs.Sort();
 90 
 91                 //接下来这里是天坑,就是构造时间器比较,然后计算出倒计时
 92                 for (int y = 0; y < tempYears.Count; y++)
 93                 {
 94                     for (int m = 0; m < tempMonths.Count; m++)
 95                     {
 96                         for (int d = 0; d < tempDays.Count; d++)
 97                         {
 98                             for (int h = 0; h < tempHHMMs.Count; h++)
 99                             {
100                                 string[] hhmm = Regex.Split(tempHHMMs[h], ":|:");
101                                 if (int.TryParse(hhmm[0], out hour) && int.TryParse(hhmm[1], out minute))
102                                 {
103                                     DateTime actionTime = new DateTime(tempYears[y], tempMonths[m], tempDays[d], hour, minute, second);
104                                     if (actionTime > date)
105                                     {
106                                         if (VerifyConfigTimeStr(actionTime, item))
107                                         {
108                                             Console.WriteLine(actionTime.ToString("yyyy-MM-dd HH:mm:ss"));
109                                             TimeSpan ts = (actionTime - date);
110                                             ret = ts.Days * 24 * 60 * 60 + ts.Hours * 60 * 60 + ts.Minutes * 60 + ts.Seconds;
111                                             ret *= 1000;
112                                             ret += ts.Milliseconds;
113                                             goto Lab_Exit;
114                                         }
115                                     }
116                                 }
117                             }
118                         }
119                     }
120                 }
121             }
122         Lab_Exit:
123             return ret;
124         }
125         #endregion
复制代码

验证活动配置时间

[2016][2][*][*][10:00-11:59/14:00-16:59]

 

接下来我们在测试一下性能问题,

大家都懂的,在控制台输出打印是比较耗时的,这种情况下2000次也才1秒;效率是不必是说的。

全部完整代码。。

复制代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Text.RegularExpressions;
  6 using System.Threading.Tasks;
  7 
  8 /**
  9  * 
 10  * @author 失足程序员
 11  * @Blog http://www.cnblogs.com/ty408/
 12  * @mail 492794628@qq.com
 13  * @phone 13882122019
 14  * 
 15  */
 16 namespace Sz
 17 {
 18     /// <summary>
 19     /// 时间验证器
 20     /// </summary>
 21     public static class TimeUtil
 22     {
 23 
 24         //static void Main(string[] args)
 25         //{
 26         //    Console.WriteLine("验证当期时间是否满足活动开放时间:[*][*][20/22][*][10:00-11:59/16:00-17:59]");
 27         //    System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
 28         //    watch.Restart();
 29         //    for (int i = 0; i < 5; i++)
 30         //    {
 31         //        long ticks = TimeUtil.VerifyDateTime("[2014/2016][9][*][*][10:00-11:59/16:00-22:59]");
 32         //        Console.WriteLine(ticks + " 倒计时:" + (ticks / 1000) + "秒");
 33         //    }
 34         //    watch.Stop();
 35         //    Console.WriteLine(watch.ElapsedMilliseconds);
 36         //    Console.ReadLine();
 37         //}
 38 
 39 
 40         /// <summary>
 41         /// 验证时间:[*][*][20/22][*][10:00-11:59/16:00-17:59]
 42         /// <para>第一个是年,,第二个是月,第三个是日期,第四个是星期,第五个是时间,</para>
 43         /// <para>每一个参数,"-" 表示 到 如:“2015-2017”表示 2015 到 2017, "/"  表示 或者 如: “2015/2017”表示2015 或者 2017</para>
 44         /// <para>返回值 -1 表示永久过期,0 表示在时间规则内,大于 0 表示倒计时</para>
 45         /// </summary> 
 46         static public long VerifyDateTime(string timeStr)
 47         {
 48             var items = Regex.Split(timeStr, @";|;");
 49             items.Reverse();
 50             long ret = -1;
 51             DateTime date = DateTime.Now;
 52             foreach (var item in items)
 53             {
 54                 //验证时间匹配
 55                 if (VerifyConfigTimeStr(date, item))
 56                 {
 57                     ret = 0;
 58                     goto Lab_Exit;
 59                 }
 60                 //未通过时间匹配,检查返回剩余时间
 61                 List<string> strs = new List<string>();
 62                 string[] timeStrs = item.Split(new char[] { ']' });
 63                 for (int i = 0; i < timeStrs.Length - 1; i++)
 64                 {
 65                     string time = timeStrs[i].Replace("[", "");
 66                     strs.Add(time);
 67                 }
 68 
 69                 string times = strs[4];
 70                 string weeks = strs[3];
 71                 string days = strs[2];
 72                 string months = strs[1];
 73                 string years = strs[0];
 74 
 75                 int hour = 0, minute = 0, second = 0;
 76                 var tempYears = GetConfigDate(date, date.Year, years);
 77                 var tempMonths = GetConfigDate(date, date.Month, months);
 78                 var tempDays = GetConfigDate(date, date.Day, days);
 79                 //由于星期比较特殊所以获取与星期相关的日期的时候有点诡异。
 80                 if (!"*".Equals(weeks))
 81                 {
 82                     if (weeks.IndexOf("-") > 0)
 83                     {
 84                         //星期的间隔模式
 85                         string[] weekSplit = weeks.Split('-');
 86                         int weekmin = 9999;
 87                         int.TryParse(weekSplit[0], out weekmin);
 88                         int weekmax = 9999;
 89                         int.TryParse(weekSplit[1], out weekmax);
 90                         ActionWeekDay(weekmin, weekmax, ref tempDays, ref tempMonths, ref tempYears);
 91                     }
 92                     else if (weeks.IndexOf("/") > 0)
 93                     {
 94                         //星期的或模式
 95                         string[] weeksSplit = weeks.Split('/');
 96                         int tempWeek;
 97                         if (int.TryParse(weeksSplit[0], out tempWeek))
 98                         {
 99                             if (0 <= tempWeek && tempWeek <= 7)
100                             {
101                                 ActionWeekDay(tempWeek, tempWeek, ref tempDays, ref tempMonths, ref tempYears);
102                             }
103                         }
104                     }
105                     else
106                     {
107                         //特定星期的模式
108                         int tempweek = 0;
109                         if (int.TryParse(weeks, out tempweek))
110                         {
111                             ActionWeekDay(tempweek, tempweek, ref tempDays, ref tempMonths, ref tempYears);
112                         }
113                     }
114                 }
115                 else
116                 {
117                     //未指定星期的模式
118                     ActionWeekDay(1, 7, ref tempDays, ref tempMonths, ref tempYears);
119                 }
120 
121                 var tempHHMMs = GetConfigTimeStr(times);
122 
123                 //进行简单的排序
124                 tempYears.Sort();
125                 tempMonths.Sort();
126                 tempDays.Sort();
127                 tempHHMMs.Sort();
128 
129                 //接下来这里是天坑,就是构造时间器比较,然后计算出倒计时
130                 for (int y = 0; y < tempYears.Count; y++)
131                 {
132                     for (int m = 0; m < tempMonths.Count; m++)
133                     {
134                         for (int d = 0; d < tempDays.Count; d++)
135                         {
136                             for (int h = 0; h < tempHHMMs.Count; h++)
137                             {
138                                 string[] hhmm = Regex.Split(tempHHMMs[h], ":|:");
139                                 if (int.TryParse(hhmm[0], out hour) && int.TryParse(hhmm[1], out minute))
140                                 {
141                                     DateTime actionTime = new DateTime(tempYears[y], tempMonths[m], tempDays[d], hour, minute, second);
142                                     if (actionTime > date)
143                                     {
144                                         if (VerifyConfigTimeStr(actionTime, item))
145                                         {
146                                             Console.WriteLine(actionTime.ToString("yyyy-MM-dd HH:mm:ss"));
147                                             TimeSpan ts = (actionTime - date);
148                                             ret = ts.Days * 24 * 60 * 60 + ts.Hours * 60 * 60 + ts.Minutes * 60 + ts.Seconds;
149                                             ret *= 1000;
150                                             ret += ts.Milliseconds;
151                                             goto Lab_Exit;
152                                         }
153                                     }
154                                 }
155                             }
156                         }
157                     }
158                 }
159             }
160         Lab_Exit:
161             return ret;
162         }
163 
164         #region 处理星期包含的日期 日  static void ActionWeekDay(int weekmin, int weekmax, ref List<int> days, ref List<int> months, ref List<int> years)
165         /// <summary>
166         /// 处理星期包含的日期 日
167         /// </summary>
168         /// <param name="weekmin"></param>
169         /// <param name="weekmax"></param>
170         /// <param name="days"></param>
171         /// <param name="months"></param>
172         /// <param name="years"></param>
173         static void ActionWeekDay(int weekmin, int weekmax, ref List<int> days, ref List<int> months, ref List<int> years)
174         {
175             DateTime nowWeekDate = DateTime.Now;
176             List<int> tempDays, tempMonths, tempYears;
177             tempYears = years.ToList();
178             tempMonths = months.ToList();
179             tempDays = days.ToList();
180             foreach (var itemYear in tempYears)
181             {
182                 foreach (var itemMonth in tempMonths)
183                 {
184                     int itemDay = 1;
185                     if (nowWeekDate.Month == itemMonth)
186                     {
187                         itemDay = nowWeekDate.Day;
188                     }
189                     DateTime date = new DateTime(itemYear, itemMonth, itemDay);
190                     for (int i = 0; i < 7; i++)
191                     {
192                         int week = (int)date.DayOfWeek;
193                         if (week == 0)
194                         {
195                             week = 7;
196                         }
197                         if (weekmin <= week && week <= weekmax)
198                         {
199                             if (!days.Contains(date.Day))
200                             {
201                                 days.Add(date.Day);
202                             }
203                             if (!months.Contains(date.Month))
204                             {
205                                 months.Add(date.Month);
206                             }
207                             if (!years.Contains(date.Year))
208                             {
209                                 years.Add(date.Year);
210                             }
211                         }
212                         date = date.AddDays(1);
213                     }
214                 }
215             }
216         }
217         #endregion
218 
219         #region 验证时间:[*][*][20/22][*][10:00-11:59/16:00-17:59] static bool VerifyConfigTimeStr(DateTime date, string timeStr)
220         /// <summary>
221         /// 验证时间:[*][*][20/22][*][10:00-11:59/16:00-17:59]
222         /// <para>第一个是年,,第二个是月,第三个是日期,第四个是星期,第五个是时间,</para>
223         /// <para>每一个参数,"-" 表示 到 如:“2015-2017”表示 2015 到 2017, "/"  表示 或者 如: “2015/2017”表示2015 或者 2017</para>
224         /// </summary>        
225         /// <returns></returns>
226         static bool VerifyConfigTimeStr(DateTime date, string timeStr)
227         {
228             List<string> strs = new List<string>();
229             string[] timeStrs = timeStr.Split(new char[] { ']' });
230             for (int i = 0; i < timeStrs.Length - 1; i++)
231             {
232                 string time = timeStrs[i].Replace("[", "");
233                 strs.Add(time);
234             }
235             if (VerifyDate(date.Year, strs[0]))
236             {
237                 if (VerifyDate(date.Month, strs[1]))
238                 {
239                     int week = (int)date.DayOfWeek;
240                     if (week == 0) { week = 7; }//星期天
241                     if (VerifyDate(week, strs[3]))
242                     {
243                         if (VerifyDate(date.Day, strs[2]))
244                         {
245                             if (VerifyTime(date, strs[4])) { return true; }
246                         }
247                     }
248                 }
249             }
250             return false;
251         }
252         #endregion
253 
254         #region 验证当前时间 年,月,日,星期,是否符合 static bool VerifyDate(int nowItem, string items)
255         /// <summary>
256         /// 验证当前时间 年,月,日,星期,是否符合
257         /// </summary>
258         /// <param name="nowItem">参数</param>
259         /// <param name="items">1-7;表示 1 到 7 , 1/7 表示 1 或者 7</param>
260         /// <returns></returns>
261         static bool VerifyDate(int nowItem, string items)
262         {
263             string nowItemStr = nowItem.ToString();
264             if ("*".Equals(items) || nowItemStr.Equals(items)) { return true; }
265             else if (items.IndexOf("-") > 0)
266             {//区间划分
267                 string[] itemSplit = items.Split('-');
268                 int item1 = 9999;
269                 int.TryParse(itemSplit[0], out item1);
270                 int item2 = 9999;
271                 int.TryParse(itemSplit[1], out item2);
272 
273                 if (item1 <= nowItem && nowItem <= item2) { return true; }
274             }
275             else if (items.IndexOf("/") > 0)
276             {//或划分
277                 string[] weeksSplit = items.Split('/');
278                 foreach (var item in weeksSplit)
279                 {
280                     if (nowItemStr.Equals(item)) { return true; }
281                 }
282             }
283             return false;
284         }
285         #endregion
286 
287         #region 验证当期时间格式 static bool VerifyTime(DateTime date, string itemTime)
288         /// <summary>
289         /// 验证当期时间格式
290         /// </summary>
291         /// <param name="date"></param>
292         /// <param name="itemTime"></param>
293         /// <returns></returns>
294         static bool VerifyTime(DateTime date, string itemTime)
295         {
296             bool ret = false;
297             if (!"*".Equals(itemTime))
298             {
299                 var items = Regex.Split(itemTime, @"/");
300                 foreach (var item in items)
301                 {
302                     string[] itemTimes = item.Split('-');
303                     var hhmm = Regex.Split(itemTimes[0], @":|:");
304                     int hh = 24;
305                     int.TryParse(hhmm[0], out hh);
306                     int mm = 60;
307                     int.TryParse(hhmm[1], out mm);
308                     if (date.Hour > hh || (date.Hour == hh && date.Minute >= mm))
309                     {
310                         if (itemTimes.Length > 1)
311                         {
312                             var hhmm1 = Regex.Split(itemTimes[1], @":|:");
313                             int hh1 = 24;
314                             int.TryParse(hhmm1[0], out hh1);
315                             int mm1 = 60;
316                             int.TryParse(hhmm1[1], out mm1);
317                             if (date.Hour < hh1 || (date.Hour == hh1 && date.Minute < mm1)) { ret = true; }
318                             else { ret = false; }
319                         }
320                         else { ret = true; }
321                     }
322                     else { ret = false; }
323                     if (ret)
324                     {
325                         break;
326                     }
327                 }
328             }
329             else { ret = true; }
330             return ret;
331         }
332         #endregion
333 
334         #region 获取配置的年月日星期等信息 static List<int> GetConfigDate(DateTime date, int p1, string p3)
335         /// <summary>
336         /// 获取配置的年月日星期等信息
337         /// </summary>
338         /// <param name="date"></param>
339         /// <param name="p1"></param>
340         /// <param name="p3"></param>
341         /// <returns></returns>
342         static List<int> GetConfigDate(DateTime date, int p1, string p3)
343         {
344             List<int> rets = new List<int>();
345             string p1Str = p1.ToString();
346             if ("*".Equals(p3) || p1Str.Equals(p3))
347             {
348                 rets.Add(p1);
349                 rets.Add(p1 + 1);
350             }
351             else if (p3.IndexOf("-") > 0)
352             {
353                 string[] weekSplit = p3.Split('-');
354                 int week1 = 9999;
355                 int.TryParse(weekSplit[0], out week1);
356 
357                 int week2 = 9999;
358                 int.TryParse(weekSplit[1], out week2);
359                 for (int i = week1; i < week2 + 1; i++)
360                 {
361                     rets.Add(i);
362                 }
363             }
364             else if (p3.IndexOf("/") > 0)
365             {
366                 string[] weeksSplit = p3.Split('/');
367                 foreach (var item in weeksSplit)
368                 {
369                     int temp = 0;
370                     if (int.TryParse(item, out temp))
371                     {
372                         rets.Add(temp);
373                     }
374                 }
375             }
376             else
377             {
378                 int temp = 0;
379                 if (int.TryParse(p3, out temp))
380                 {
381                     rets.Add(temp);
382                 }
383             }
384             return rets;
385         }
386         #endregion
387 
388         #region 获取配置的时间字符串 static List<string> GetConfigTimeStr(string itemTime)
389         /// <summary>
390         /// 获取配置的时间字符串 
391         /// </summary>
392         /// <param name="itemTime">必须类似的格式 单条 00:00-23:59  多条00:00-23:59/00:00-23:59</param>
393         /// <returns></returns>
394         static List<string> GetConfigTimeStr(string itemTime)
395         {
396             List<string> retObjs = new List<string>();
397             // 00:00-23:59
398             if (!"*".Equals(itemTime))
399             {
400                 var items = Regex.Split(itemTime, @"/");
401                 foreach (var item in items)
402                 {
403                     string[] itemTimes = item.Split('-');
404                     retObjs.Add(itemTimes[0]);
405                 }
406             }
407             else
408             {
409                 retObjs.Add("00:00");
410             }
411             return retObjs;
412         }
413         #endregion
414     }
415 }
复制代码

View Code

好了谢谢大家的收看。。。我搬砖去了。。。

忘了告诉大家,"-"表示间隔,"/"表示包含的意思 ? 支持多个时间配置器哦;

[2015][7][20-31][*][10:00-11:59/14:00-16:59];[2015][8][1-5][*][10:00-11:59/14:00-16:59]

这样配置表示2015年的7月20号到8月5号的每天两档活动~!