[转载]网页回拨电话给VoIP市场带来新的机遇

mikel阅读(1005)

[转载]网页回拨电话给VoIP市场带来新的机遇-网络电话资源网.

任何一个企业都是销售产品或者服务的,都离不开客户。面向大众的企业更希望自己的网站能够被频繁点击,每个有效客户都能给企业打电话咨询。企业主明白,来电越多,成功数量才会越高。

网页点击呼叫成为业内一个炙手可热的概念。大家最容易理解,开始也最被人追捧的是webcall,也叫web800。网页上设置有“点击即可呼 叫”之类的按钮,客户要想与该公司联系,直接点击,在下载了控件后,通过点击“联系我们”按钮,即可拨通企业的电话。然而这种方式并没有获得大规模的成 功。原因主要有:

1、 这种业务模型是多个人给一个人打电话,而主叫方(大部分是客户)必须下载插件才能拨通电话,势必造成主叫方嫌麻烦。因为很可能他自认为自己一辈子只会给这家公司打一个电话,为什么非要下载这么大的插件呢?因此他往往选择用手机或者其它电话直接拨打企业的电话!

2、 插件被屏蔽。即使勉强同意下载控件的,要知道,70%的计算机的浏览器现在在屏蔽这些插件。因为互联网上的流氓软件太多了!

3、 语音质量很难保障。大家知道,下载的插件其实是一个软电话,带有DSP,而做编解码转换的是计算机的CPU,并且语音是在互联网上传送的,这种服务质量很难保证。

4、 操作复杂。计算机必须带有耳麦,对很多商业人士,这将显得很复杂。

因此做webcall的企业几乎都以失败而告终,成功者甚少。

随着技术的发展,新型的web800出现了,它就是webcallback

要了解webcallback,我们不得不提到callback。Callback业务,其实是用手机的信令通道将本机和被叫方电话号码传送给软交换,软交换先后拨通被叫和主叫方电话,并将这两者接续,实现通话。

这种方式的资费是:主叫、被叫双方都要向所属电信运营商缴纳被叫的费用(国内目前大部分固网、手机的被叫都是免费),而向回拨运营者交一定数额 的回拨费用。这样,对发起呼叫的用户来说,向电信运营商和向回拨运营者交的钱加起来远远小于直接呼叫被叫方的费用。该业务模型在信息产业部属于违规业务年 从卡市渠道开始入手,全面取缔回拨业务。

而Webcallback则是一种新兴应用。在企业网站上添加一个小区域,这个区域就是简单的几个文本框、验证码和“联系我们”按钮,当感兴趣 的访问者发现需要与该企业沟通咨询,直接把自己的电话号码输入文本框中,输入验证码,并点击“呼叫”按钮,系统将两边的电话拨通。这种方式线路大部分承载 在PSTN网络上,质量有保证,而VoIP只是其中的一段或者可以是很小的一段,这取决于网络部署。

Webcallcallbackwebcallback,分别有着质的区别。先介绍Webcall和weballback的区别:

1、 Webcall需要下载插件,而webcallback不用;

2、 Webcall是采用用户端的CPU来编解码,而webcallback根本不用计算机来接电话,语音大部分都在PSTN上承载,因为无所谓编解码问题;

3、 用户使用习惯上,webcall是计算机连接耳麦,而webcallback是通过用户的固定电话或者手机接听,web上填入的号码只是被送到软交换上去了而已。

4、 Callback客户最接受的是透传对方号码,但是实际上现在国内透传落地很难找。而webcallback很好地解决了这个问题。客户拨打企业电话,把 自己的号码输入空格并点击“呼叫”后,系统把双方接通,之间只要3秒钟以内,即使客户这边显示未知号码,客户也知道肯定是这个企业打过来的。作为企业,通 过web上传的号码信息就被做了记录,也无需要求落地透传客户号码了。

Webcallback和callback的区别:

1、 Callback是一个人买卡,给多个人打电话;而webcallback是企业买服务,让多个人打给它,因此不需要在卡市销售回拨卡。而信产部查抄的正 式卡市的回拨卡,因此从政策风险上来说没有丝毫问题。2、 企业愿意为webcallback支付更高的费用,因为这让企业获取更多的市场有效信息,而callback是个人用途,当然是越便宜客户越喜欢。笔者认 为企业可以接受3毛钱一分钟的被叫。

3、 企业一般是固定电话,如果企业接受网关接入互联网形式的电话接入,比如企业的IP出口带宽充足,那么webcallback运营商的成本又会降低50%。

上海傲威公司的Webcallback模块架构在其软交换AVS上,拥有丰富的用户API,可以让虚拟运营商快速部署市场渠道进行推广。傲威公 司的Webcallback模块在客户端有基于JAVA编写的代码段并配置有详细的说明书,任何虚拟运营商只要将代码段嵌入企业网站首页合适的位置,在 AVS上填入企业电话号码,即可进行运营。一般来说,每个网站每天如果获得60分钟的话务量,每分钟利润1毛钱,每天就是6元,每月180元,如果有 1000个网站选择了webcallback业务,那么每月利润就是18万元!要知道,一台服务器托管的虚拟主机一般都高达500-2000个!

因此,由于webcallback业务利润高、企业接受度高、语音质量好、政策方面不受限制、渠道容易寻找、客户群广泛(每个网站其实都有此需 求)、部署方便快捷(一般一个客户20分钟就能全部完成,不需要到客户现场),将成为近一段时间以来虚拟运营商热捧的主流业务模型。

附:
Webcallback所需设备投入和需要准备的条件
AVS一套,根据并发数价值3-300万不等。
Webcallback模块一套。根据并发数和二次开发需求价值5000-30万不等。
服务器1-2台(200并发以上需要2台)。
寻找支持回拨的落地(隐藏或者透传号码),根据自己的需求定并发数,一般成本是0.10-0.12元6+6(因为是两通呼叫)。
服务器托管费用若干。
有理想的市场推广团队一支。

[转载]Java与Flex通信

mikel阅读(1056)

[转载]Java与Flex通信 – 摇摆的蒜头酥 – 博客园.

提到通信就得面临两个问题,一是通信协议的选择,二是数据协议的定义。通信协议耳熟能详的就有好几种,TCPUDPHTTPFTP等等。数据协议是一种数据交换的格式,像jason,xml,amf3,google protocol都可以用作数据协议,你也可以自己根据通信的效率,安全等因素来定义自己的数据协议。

通信系统的开发是一项很复杂的工作,不要以为往发服务端发一个Hello World!就认为完全掌握了通信系统的开发。概括来说要开发一个健壮的通信系统,必须从这几个方面来着手。

一,通信粘包的处理

这里包的概念是逻辑上的数据包,也就是我们发送的一个完整业务消息包,粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。

为了避免粘包现象,可采取以下几种措施。一是对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令pushTCP软 件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;二是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接 收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;三是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手 段来避免粘包。

以上提到的三种措施,都有其不足之处。总的来说降低了通信系统的吞吐量。我们可以自己设计一个分包算法来处理粘包的问题,该算法的实现是这样的:

  1. 当有数据到达时,将数据压入程序缓冲区。
  2. 循环处理缓冲区,如果缓冲区长度大于包头长度,则取出长度信息n,否则跳出循环,如果缓冲区的长度大于n,则从缓冲区取出一个完整包进行处理,否则跳出循环。

如果你是Java的爱好都可以参考一下Mina和netty2的实现,像Mina和Netty2都提供了粘包处理类可供使用,像Mina的CumulativeProtocolDecoder类,Netty2的LengthFieldBasedFrameDecoder。

二,数据协议选择

现在已经有很多数据协议可供我们选择,像jason,xml,amf3,google protocol等等,这些协议相应的语言都有API来对自身数据做协议处理,我们选择协标准无非就是效率和大小,这里每个人可以根据实际的应用环境选择适合的数据协议。

三,网络系统的安全性

网络安全是一个永远的话题,对通信数据加密一般常RSA对byte流加密,FLOOD验证,IP黑名单验证都是必须考虑到的。

以上是做网 络开发必须了解的一些基础知识,在这里我们使用一个具体的实例来加深一下理解,Java与Flex使用AMF3数据协议通信。做过网络开发的一般都会知道 套接字(SOCKET),很多语言都会通SOCKET来提供对网络操作的API,Java的提供的NIO SOCKET是一个高效的异步通信API,当然可以在这个基础上来开发我们的网络应用,但这种Native API需要我们花很多精力来处理网络通信的细节,消弱了我们对业务的关心。为我们开发带来很多不便性,幸好Java有很多现成的NIO SOCKET框架可供使用,MinaNetty2xSocket等等,这些框架处理了很多底层的通信问题,提供了一些易用的API以供使用。在这个实例中我们使用Netty2来做通信框架。

定义消息包,消息包有定长包和不定长包,不定长包无非就是要在消息包中加入长度信息,以对收到的网络字节流进行分界。消息包的定义如下

定义AMF3数据协议的编码和解码器

 /*
  * @(#)AMF3Encoder.java    0.1 05/11/17
  *
  * Copyright 2010 QISI, Inc. All rights reserved.
  * QISI PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  */
 package com.qidea.pushserver.codec;
 import java.io.ByteArrayOutputStream;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
 import com.qidea.pushserver.Constants;
 import com.qidea.pushserver.message.CommandMessage;
 import com.qidea.pushserver.message.PushMessage;
 import flex.messaging.io.SerializationContext;
 import flex.messaging.io.amf.Amf3Output;
 /**
  * 
  * @author sunwei
  * @version 2010-7-21
  * @since JDK1.5
  */
 public class AMF3Encoder extends OneToOneEncoder
 {
     /**
      * 
      */
    @Override
     protected Object encode(ChannelHandlerContext arg0, Channel arg1,
             Object arg2) throws Exception
     {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         SerializationContext serializationContext = new SerializationContext();
         Amf3Output amf3Output = new Amf3Output(serializationContext);
         amf3Output.setOutputStream(stream);
         amf3Output.writeObject(arg2);
         byte[] objSe = stream.toByteArray();
         if (objSe != null && objSe.length > 0)
         {
             ChannelBuffer buffer = ChannelBuffers.buffer(objSe.length + 8);
             if (arg2 instanceof PushMessage)
                 buffer.writeInt(Constants.MAGIC_NUM_PUSH_MSG);
             else if (arg2 instanceof CommandMessage)
                 buffer.writeInt(Constants.MAGIC_NUM_COMMAND_MSG);
             buffer.writeInt(objSe.length);
             buffer.writeBytes(objSe);
             return buffer;
         }
         return null;
     }
 }
 /*
  * @(#)AMF3Decoder.java    0.1 05/11/17
  *
  * Copyright 2010 QISI, Inc. All rights reserved.
  * QISI PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  */
 package com.qidea.pushserver.codec;
 import java.io.ByteArrayInputStream;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import flex.messaging.io.SerializationContext;
 import flex.messaging.io.amf.Amf3Input;
 /**
  * amf3协议解码类
  * 
  * @author sunwei
  * @version 2010-7-21
  * @since JDK1.5
  */
 public class AMF3Decoder extends LengthFieldBasedFrameDecoder
 {
     public static final Logger logger = LoggerFactory
             .getLogger(AMF3Decoder.class);
    /**
      * 
      * @param maxFrameLength
      *            包的最大大小
      * @param lengthFieldOffset
     *            包头信息,长度的偏移位
      * @param lengthFieldLength
      *            包头信息,长度位数
      */
     public AMF3Decoder(int maxFrameLength, int lengthFieldOffset,
             int lengthFieldLength)
     {
         super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
     }
     /**
      * 
      * @param maxFrameLength
      */
     public AMF3Decoder(int maxFrameLength)
     {
         super(maxFrameLength, 4, 4, 0, 0);
     }
     /**
      * 
      */
     @Override
     protected Object decode(ChannelHandlerContext ctx, Channel channel,
             ChannelBuffer buffer) throws Exception
     {
         ChannelBuffer frame = (ChannelBuffer) super
                 .decode(ctx, channel, buffer);
         if (frame == null)
         {
             return null;
         }
         //
         int magicNum = frame.readInt();
         int dataLength = frame.readInt();
         logger.info("magic num={},data length={}", magicNum, dataLength);
         // 读AMF3字节流的内容
         byte[] content = new byte[frame.readableBytes()];
         frame.readBytes(content);
         SerializationContext serializationContext = new SerializationContext();
         Amf3Input amf3Input = new Amf3Input(serializationContext);
         amf3Input.setInputStream(new ByteArrayInputStream(content));
         Object message = amf3Input.readObject();
         return message;
     }
 }
 

构建服务端

public class PushProtocolHandler extends SimpleChannelHandler
 {
     public static Logger log = LoggerFactory
             .getLogger(PushProtocolHandler.class);
     /**
      * 
      */
     @Override
     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
     {
         if (e.getMessage() != null)
         {
             ChannelManager channelManager = PushServerContext
                     .getBean(ChannelManager.class);
             if (e.getMessage() instanceof CommandMessage)
             {
                 channelManager.handleMsg((CommandMessage) e.getMessage(), e
                         .getChannel());
             }
             else if (e.getMessage() instanceof PushMessage)
             {
                 channelManager.handleMsg((PushMessage) e.getMessage(), e
                         .getChannel());
             }
             else
             {
                 log.warn("unkown message {}", e);
             }
         }
     }
 }
import static org.jboss.netty.channel.Channels.*;
 /**
  * 
  * @author sunwei
  * @version 2010-7-22
  * @since JDK1.5
  */
 public class PushServerPipelineFactory implements ChannelPipelineFactory
 {
    @Override
     public ChannelPipeline getPipeline() throws Exception
     {
         ChannelPipeline pipeline = pipeline();
         // 处理日志
         pipeline.addLast("logger", new LoggingHandler());
         // 处理coder
         pipeline.addLast("decoder", new AMF3Decoder(Constants.MAX_OBJECT_SIZE));
         pipeline.addLast("encoder", new AMF3Encoder());
         //
         pipeline.addLast("handler", new PushProtocolHandler());
         //
         return pipeline;
     }
 }
public static main(String[] args)
 {
         // 开始NIO线程
          ChannelFactory factory = new NioServerSocketChannelFactory(Executors
                 .newCachedThreadPool(), Executors.newCachedThreadPool());
         // 服务启始点
     ServerBootstrap bootstrap = new ServerBootstrap(factory);
     // 处理过滤器
     bootstrap.setPipelineFactory(new PushServerPipelineFactory());
     // 设置相关参数
     bootstrap.setOption("child.tcpNoDelay", true);
     // 设置相关参数
     bootstrap.setOption("child.keepAlive", true);
     // 绑定相关端口
     bootstrap.bind(new InetSocketAddress(getPushPort()));
 }

Flex客户端

public class FlexSocket
 {
  
 //发送包
         public function send(type:int, obj:PushMessage):Boolean
         {
             if (_socket == null)
             {
                 return false;
             }
             //手动限制不给发送的时候用
             if (socketState == socket_state_closed || socketState == socket_state_connecting)
             {
                 return false;
             }
             if (!_socket.connected)
             {
                 return false;
             }
             var byteArr:ByteArray=objToByteaArray(obj);
             var msgHead:MsgHead=new MsgHead(type, byteArr.length);
             sendMsg(msgHead.getType(), msgHead.getSize(), byteArr);
             return true;
         }
 
 //接受包
                 private function getDataHandler(e:ProgressEvent):void
         {
             _timeServerDead.stop();
             _timeServerDead.reset();
             if (_socket.bytesAvailable >= 8 && !_isReadHead)
             {
                 _recvPackageType=_socket.readInt();
                 //同意关闭
 //                if(_recvPackageType == 5)
 //                {
 //                    close();
 //                }
                 _recvPackageSize=_socket.readInt();
                 _isReadHead=true;
             }
             if (_isReadHead && _socket.bytesAvailable >= _recvPackageSize)
             {
                 var byte:ByteArray=new ByteArray();
                 _socket.readBytes(byte, 0, _recvPackageSize);
                 _msgObj=byteArraytoObject(byte);
                 //暂时用上面一种 
                 if (_recvPackageType == packageType.LOGIN_TYPE)
                 {
                     if (_msgObj.ret == bodyType.RECEIVE_OK)
                     {
                        _timerDetectSocket.start();
                        socketState=socket_state_connected;
                         myEventDispatch.Instence().dispatcher(bodyType.INLINE_CURRENTSOCKETSTATE);
                     }
                   else if (_msgObj.ret == bodyType.RECEIVE_ERROR)
                    {
                      close();
                     }

                }
                else if (_recvPackageType == packageType.CHAT_TYPE)
                {
                    myEventDispatch.Instence().dispatcher(selectEventName(_recvPackageType), _msgObj);
               }
                 _recvPackageSize=0;
                 _recvPackageType=0;
              _msgObj=null;
               _isReadHead=false;


             }
         }

 }

 有关Mina的实现,你可以通过本博客向我索取相关代码。

[转载]分层架构中的服务层-服务层的相关模式

mikel阅读(1026)

[转载]分层架构中的服务层-服务层的相关模式 – 软件设计开发 – 博客园.

1 引言

我们把服务层看做是暴露给用户界面的一个服务集合。大多数时候,我们会发现服务层的方法很容易满足用户的行为。在大多数企业应用中,CRUD是常用的操作。有的时候在一次操作中会处理多个实体。

服务层包括角色管理,数据验证,通知,调整返回给用户界面的数据,或者是整合系统可能的需求。

在谈到这些的时候,一些设计模式可能会有帮助。下面是一些在实现服务层的过程中有帮助的模式。

2 远程外观模式Remote Facade Pattern

远程外观模式是用来修改已经实现的方法的粒度的,外观模式没有实现任何新功能。它只是在原有的API之上包装一层不同的外观。为什么会需要外观模式呢?

2.1 使用外观模式的动机

外观模式改变一个已经存在的对象的访问方式。举一个货运商在线服务的例子。每一个货运商对于注册运输的货物、跟踪货物和其他服务都有自己的 API,他们的细节在你的应用中可能都用不着。通过建立一个统一的API,你可以隐藏货运商API的细节,使得你的应用有一个清晰的接口。

换句话说,如果你想要你的接口处理一个简单的接口,这些必要性强迫你创建另外一个外观。实际上,这就是经典的远程外观模式的精确定义。

外观模式的好处就是允许你在一系列定义好的细粒度对象之上,定义粗粒度的接口。

面向对象使得你创建了很多小的对象,职责分离,细粒度对象。但是这些对象不适合分布式。为了是这些对象有效,你需要做出一些调整。你不想改变细 粒度对象的任何实现,但是你需要通过这些对象可以执行一批操作。在这种情况下,你需要创建新的方法,移动更大的数据。当你这么做的时候,实际上就是修改了 已经存在的接口粒度,也就是创建了外观模式。让我们回到货运订单的例子。通过你自己的API你可以发送多个订单,不用使用货运商的API来发送单个的订 单,这里我们假设货运商不支持多个订单的API。

2.2 远程外观模式和服务层

经过设计,服务层本质上拥有了粗粒度的接口,因为它更趋向于抽象一定数量的细小操作,来给客户端使用。在这点上,服务层已经是一种位于业务逻辑层和领域层对象之上的外观模式。

public interface IOrderService
{
void Create(Order o);
List
<Order> FindAll();
Order FindByID(
int orderID);

}

如果你使用WCF,需要在服务层接口上添加协议attribute。

代码

[ServiceContract]
public interface IOrderService
{
[OperationContract]
void Create(Order o);

[OperationContract]
List<Order> FindAll();

[OperationContract]
Order FindByID(int orderID);

}

所有非基本类型的数据都需要添加datacontract标记。在WCF运行的时候,这些标记会为指定的类型自动创建数据传输对象DTO。

[DataContract]
public class Order
{
[DataMember]
int OrderID {get; set;};

[DataMember]
DateTime OrderDate {get; set;};

}

如果你使用ASP.NET xml web service。上面的类不要添加任何标记,只需要在服务层类的方法上添加WebMethod标记就可以了。

在使用外观模式的时候,你可能会需要更粗粒度的接口,你也可能会用到DTO,或者你都会用到。下面的接口就和领域对象解耦了,更加聚焦在和表现层的交互上。

public interface IOrderService
{
void Create(OrderDto o);

List<OrderDto> FindAll();

OrderDto FindByID(int orderID);

List<OrderDto> SearchOrder(QueryByExample query);
}

OrderDto可能是领域类Order的子集或者是包含Order(可能会整合依赖的对象 OrderDetail),QueryByExample是一个专门的类,会传输用户界面的一些条件给服务层,我更喜欢定义写Find类,例如 OrderFind。里面就是一些查询条件,例如:时间、编号、金额等等。

实际上,如果用户界面改动比较大的话,就需要修改服务层。你可能会需要重构服务层,或者是在外面再次的使用外观模式来包装。

3 数据传输对象模式

DTO(Data Transfer Object数据传输对象)仅仅是一个跨越应用边界传输数据的对象,主要的目的是最小化网络的往返。

3.1 使用DTO模式的动机

有两个主要的动机。一个是在你调用远程对象的时候,最小化网络的往返;另外一个是在前端显示和后端的领域模型之间维护一个松散的耦合关系。

在多数情况,DTO都只有属性,没有操作。DTO在领域模型方案中扮演着重要的角色。不是在所有的情况,表现层都可以直接使用本地的领域对象。如果是服务 层和表现层在同一个物理层的话,例如表现层是web网页的形式,可以直接使用。如果服务层和表现层不在同一个物理层,最好不要通过领域对象来交换数据。主 要的原因是你的领域对象之间可能是彼此依赖的,甚至是循环引用的关系,这会严重的影响它们的序列化能力,甚至是序列化失败的问题。

关于为什么需要DTO以及什么时候需要DTO的个人理解,仅供参考:

都在同一层的话,不存在对象通过网络传输的问题,所以可以直接使用领域模型,而且效率还高,没有网络传输和延迟。
不在同一层的话,就存在网络传输的问题,领域模型中既包含了数据,也包含了操作,数据可以在层之间传输,可是操作时不能传输的。而且为了解耦领域模型中 的对象和传输的对象,而且领域模型的粒度较小,传输领域模型的话,可能需要多次调用,才可以满足一次界面的显示,这样会增加网络的往返次数。

同时,界面显示的内容可能来自几个领域模型对象,也可能是一个领域模型对象中的几个属性。所以才单独建立DTO用来在层之间传输数据。

独立出来的话,就可能就会涉及到序列化的问题。

例如,要考虑到WCF和ASP.NET xml web service的xml序列化都不能处理循环引用的情况。同时,如果你使用领域模型,你会发现每一个Customer对象都有多个Order对象,每一个 Order对象都会对应一个Customer对象。在复杂的领域模型中,循环引用很常见。

循环引用:

循环引用就是两个对象相互引用,每个对象的都有对方类型的属性存在,这样就造成了循环引用。也就是下面的情况。

代码


public class Order
{

public string OrderSeqNo
{
get;
set;
}
public Customer Customer
{
get;
set;
}
}

public class Customer
{
public List<Order> Orders
{
get;
set;
}
}

DTO可以帮助你避免这种风险,使你的系统更加整洁。但是,它也引入和新的复杂等级。我们需要额外的层,DTO适配层。

3.2 数据传输对象和服务层

当你在开始的架构会议中讨论DTO的时候,你经常会听到反对使用DTO的声音。数据传输对象可能会浪费开发时间和资源。问题是DTO的存在有他的必要性。可以不使用它们,但是他们在企业级架构中任然扮演重要的角色。

在理论上,我们提倡在两个层之间发生通信的时候使用DTO,包括表现层和服务层的通信。另外,我们还提倡在每一个截然不同的用户界面使用不同的DTO,甚至是不同的DTO请求和相应。

在实际的使用中,事情可能有所不同。DTO意味着在服务层添加新的一层代码,随着而来的是复杂性。只要没有更好的选择,这样做是可以接受的。我们在强调DTO的花销的时候很容易低估它的花销。当你拥有上百个领域对象的时候,2-3倍的类就会使噩梦了。

只在使用DTO的好处很明显,而且必要性很明显的时候再用DTO,否则就直接使用领域对象。

使用DTO我们还需要内部的、项目定制的ORM层。

对于ORM层,我们有很多工具可以选择,商业的和开源的。WCF和ASP.NET xml web service在序列化数据的时候生成DTO,但是对于数据格式的控制,它们提供的功能有限。通过 WCF中的[IgnoreDataMember]和 asp.net xml web service中的[XmlIgnore ],你可以将一些属性从DTO中删除,也就是在DTO对象中没有这些属性。但是在请求和相应需要不同的类的时候,或者是不同的用户界面使用不同的DTO的 时候,你没有自动产生特定的DTO的方法。到目前为止,还不存在这样的向导。需要我们手动来完成,自己定义DTO。

4 适配器模式

当你在分层系统中使用DTO的时候,你可能会为不同的接口而调整领域模型。你需要实现适配器模式,一个经典而且很流行的模式。适配器的本质是将一个类的接口转换为用户希望的另外一个接口。

4.1 使用适配器模式的动机

适配器的职责是将数据表现为另外一种格式。例如:适配器会将从数据库中读取的bit格式的列,转换为用户接口中的boolean,来方便使用。

在一个分层的方案中,适配器模式被用于将一个领域对象转换为DTO对象,或者是反过来。在适配器类中没有复杂的逻辑,但是你需要一些适配器类来赋值DTO对象。

4.2 适配器模式和服务层

给每一个DTO配置一个适配器类,必然会增加开发的成本。

在评估DTO的时候,对于将要产生的一大丢类,你应该持续的考虑维护他们的问题。你要记住,在每一个适配器类中, 需要有两个功能,一个是从领域对象到DTO;一个是从DTO到领域对象。

【Blog】http://virusswb.cnblogs.com/

【MSN】jorden008@hotmail.com

【说明】转载请标明出处,谢谢

[转载]SQL Server性能调教系列(6)—Index Structure and Tuning

mikel阅读(891)

[转载]SQL Server性能调教系列(6)—Index Structure and Tuning – 我爱菊花 – 博客园.

一:前言

Index对数据库性能有着举足轻重的作用。Index设计的优劣直接影响到DB执行的效率。所以在做DB Tuning时,一部分会从Index着手处理,SQL Server也提供了很好的工具Database Engine Tuning Advisor,会给出一些建Index和优化方面的建议。

二:Index概述

这方面在各个博客论坛上面已经讲的比较多了,在此大致总结一下:

1. 数据表的基本结构

当建立一个新表时,系统将在磁盘中分配一段以8K为单位的连续空间;当第一个8K用完的时候,SQL Server指针会自动分配8K的空间。每个8K空间成为一个数据页(Page),又称页面或者数据页面,并分配0-7的页号,每个文件的第0页记录引导 信息,叫文件头(File Header);每8个数据页(64K)的组合形成扩展区(Extent),成为扩展。全部的数据页的组合形成堆(Heap)。

2. 索引的基本概念

建立索引的目的就是提高数据检索效率,改善数据库工作性能,提高数据访问速度。系统表sysindexes存储Index的重要信息。以B-Tree为存储结构。

3. 数据表扫描与索引的使用

没有索引时,访问表的数据时按照Table Scan,平均效率比较低。

建立索引时,访问表的数据时按照Index Scan/Seek,平均效率很高。

image

4. 聚集索引和非聚集索引(Clustered Index and Non Clustered Index)

相同点:

  • 以B-Tree为存储结构存放的一组数据页
  • 不同阶的节点包含指向另一个阶的数据页
  • 子节点包含所有的键值
  • 在sysindexes中可以找到索引的大小和内容分布
  • 都会提高数据查询的效率

不同点:

  • 叶子节点存放什么:聚集索引存放实际的数据页;非聚集索引存放指针

image

注意:子叶层级存放的内容不一样。

5. 覆盖索引(Covering Index)

索引覆盖是指建索引的字段正好是覆盖查询条件中所涉及的字段,这里需要注意的是,必须是从第一个开始覆盖。

6. 死锁(DackLock)

请参照

http://www.cnblogs.com/changbluesky/archive/2010/06/10/1753021.html

三:性能简述(Performance)

1. Index碎片

1.1 查询碎片

sys.dm_db_index_physical_stats可以用来检测特定索引、表或索引视图的所有索引、数据库中所有索引或所有数据库中所有索引中的碎片。

捕获2

重要栏位:

avg_fragmentation_in_percent 逻辑碎片(索引中的无序页)的百分比
fragment_count 索引中的碎片(物理上连续的叶页)数量
avg_fragment_size_in_pages 索引中一个碎片的平均页数

1.2. 重建索引与重组索引(rebuild and reorganize)

无论何时对基础数据执行插入、更新或删除操作,SQL Server 数据库引擎都会自动维护索引。随着时间的推移,这些修改可能会导致索引中的信息分散在数据库中(含有碎片)。当索引包含的页中的逻辑排序(基于键值)与数 据文件中的物理排序不匹配时,就存在碎片。碎片非常多的索引可能会降低查询性能,导致应用程序响应缓慢。通过重新组织索引或重新生成索引来修复索引碎片, 提高性能。

  • ALTER INDEX IX_IndexName ON dbo.TableName REBUILD WITH (ONLINE=ON)
  • ALTER INDEX IX_IndexName ON dbo.TableName REORGANIZE

两种方法的区别:

  • 重新组织索引是通过对叶页进行物理重新排序,使其与叶节点的逻辑顺序(从左到右)相匹配,从而对表或视图的聚集索引和非聚集索引的叶级别进行碎片整理。
  • 重新生成索引将删除该索引并创建一个新索引。此过程中将删除碎片,通过使用指定的或现有的填充因子设置压缩页来回收磁盘空间,并在连续页中对索引行重新排序(根据需要分配新页)。这样可以减少获取所请求数据所需的页读取数,从而提高磁盘性能。

建议根据碎片程度,使用修复碎片的最佳方法:

image

2. 选择正确而的Index

2.1 主要的考量

以范围查询

常常需要排序的数据

2.2 次要考量

栏位长度要短

  • 会影响所有的非聚集索引
  • 非聚集索引的子也曾都包含所有聚集索引的键值

数据的类型

3.建立索引的方针

所有SQL语法的优先性

优先建立多个查询语法可以共通使用的索引

建立符合索引时,最佳的栏位顺序

四:总结

与书中的索引一样,数据库中的索引使您可以快速找到表或索引视图中的特定信息。索引包含从表或视图中一个或多个列生成的键,以及映射到指定数据的存 储位置的指针。通过创建设计良好的索引以支持查询,可以显著提高数据库查询和应用程序的性能。索引可以减少为返回查询结果集而必须读取的数据量。索引还可 以强制表中的行具有唯一性,从而确保表数据的数据完整性。

设计良好的索引可以减少磁盘 I/O 操作,并且消耗的系统资源也较少,查询优化器也能够很好的利用索引,提高查询性能。

>>>SQL Server性能调校系列入口地址

[转载]数据库视图详解

mikel阅读(1017)

[转载]数据库视图详解 – luck_net – 博客园.

执行视图:

保护视图:

with check option导致视图where自居不但筛选检索的数据,还在通过视图插入和更新数据时进行检查。即进行双向限制。

在创建视图的时候,如果在management stdio设计器中进行创建,如果在属性窗口中启用了with schemabinding(绑定到架构)选项。那么我们在修改表的结构的时候,系统就会提示无法修改,系统就会保护视图

我们所创建的视图的代码都可以再系统表中找到:存储在SysComments中

eg:select Text from SysComments

join SysObjects on SysObjects.ID=SysComments.ID

where Name=’ViewName’

即可得到ViewName的源代码

但是如果我们在创建视图的时候加上了with encryption的话,就无法得到源代码 ,而是一堆乱码

不过值得注意的是,如果加了这个限制之后,我们就无法修改这个视图了。

通过视图进行更新:

人们经常抱怨,不能通过视图来更新数据。事实上,除非视图包含的是简单的select语句,否则不能通过它来实现数据的更新

下列因素将导致视图不可更新:

1.只能更新一个表。如果视图使用了连接,引用视图的update语句只能跟新其中的一个表

2.针对视图或基表的instead of触发器将改变数据修改操作,在这种情况下,将执行instead of触发器中的代码,而不执行提交的数据更新

3.如果试图包含聚合函数或group by子句,视图将是不可更新的,因为SQL server无法确定应更新那些被汇总的行

4.如果视图使用自查询来提供派生表,视图输出将不包含该派生表中的任何列。然而可以再用作派生表的子查询中使用聚合函数

5.如果试图包含with check option,则insert和update操作必须满足视图的where子句指定的条件

6.执行insert或update操作时,指定的列必须在基表中是唯一的。如果两个表包含名称相同的列,必须指定列时使用table.column表示法

交叉参考:一种规避视图不可更新限制的方法是创建一个instead of触发器,它检查要更新的数据,并根据这些数据执行合法的update操作

[转载]VelocityEditors-Velocity代码编辑工具

mikel阅读(2084)

[转载]VelocityEditors – Velocity Wiki.

These contributions from the user community can make developing with Velocity easier and more convenient. If you have additional plugins to editors or IDE’s, please add them to the bottom of the list below.

Eclipse

Veloeclipse is a Velocity editor, based on Veloedit, which also provides smart editing of XML and HTML files. http://code.google.com/p/veloeclipse/

JointLogic provides Web Designer plug-in that allows WYSIWYG authoring of HTML and XHTML documents and fragments with embedded Velocity template language elements.

Joe Hudson (joe@binamics.com) has created a plug-in for editing templates with the Eclipse IDE. Features include: content assist (Velocity directives, variables, macros, JavaScript, HTML, java context objects), outline view (HTML & Directive) and syntax highlighting.

Torsten Juergeleit (tjuergeleit@yahoo.de) has created a plugin for editing templates with the Eclipse IDE.

aloba ag has integrated a velocity editor into their Eclipse based opensource GeneratorFramework named QiQu. It is based on Torsten Juergeleit’s veloedit, but extends it with a realtime preview and improved code assistance. Refer to the QiQudocumentation for details.

BBEdit

A Velocity Language Module is provided by Wishingline for the BBEdit Macintosh editor.

Boxer Text Editor

David Hamel has created a color syntax definition file for highlighting Velocity macros in the Boxer Text Editor.

Dreamweaver

Jason Gill (aka Pete the Chop) created a Dreamweaver Extension that gives full Velocity support in the Dreamweaver web editor.

IntelliJ IDEA

Rickard Oberg (rickard@xpedio.com) has contributed templates for the IntelliJ IDEA IDE. View it at IntelliJTemplates.

Dennis Lundberg has contributed a IntelliJSyntaxHighlighting file type configuration for IntelliJ IDEA. It gives you syntax highlighting for .vm files.

UltraEdit Editor

Herve Guidetti (Herve.Guidetti@linkvest.com) contributed a ‘word.txt’ addition (with tweaks and comments by Wido Hillmann (wido.hillmann@epost.de) for the UltraEdit programmers editor. Find this at UltraEdit.

JEdit

Saimon Moore (smoore@serikat.es) contributed a Velocity mode for the java-based editor JEdit, and can be found here in JEditTemplate.

TextPad

Jeff Linwood (jeff@greenninja.com) created a simple syntax definition for Velocity for the TextPad editor, which can be found here.

Emacs

Brian Leonard (brian@brainslug.org) contributed a minor mode that does font-locking (syntax coloring) of VTL in XEmacs and GNU Emacs. As a minor mode, it insinuates itself into whatever major mode you’re using, so you don’t lose the major mode’s goodness. Read more about it in EmacsVtlMode.

Textmate

Tom Armitage (tom@infovore.org) has produced a bundle for Textmate that provides syntax highlighting and some snippets. It’s in its early stage, so contributions are welcome. You can find it hosted at its Google Code site for now.

[转载]T-SQL操作几个数百万行到上千万行数据的表的过程中提高效率的几点体会

mikel阅读(1069)

[转载]T-SQL操作几个数百万行到上千万行数据的表的过程中提高效率的几点体会 – 数据库相关 – 博客园.

最近做一个OLAP项目,要用T-SQL频繁操作几个大表,数据量大概在四、五百万到一千多万之间。这些T-SQL需要在几个小时内执行完毕。开 发、测试过程遇到过几次T-SQL执行超时的情况。最慢的时候要18个小时才执行完毕。还有时候太慢了就先停止了。经过多次调整,现在整批代码大概4个小 时内可以执行完毕。以下是几点重要的体会:

1:对数据库来说,写比读消耗资源高许多倍。具体多少倍当然和软硬件配置、数据库逻辑设计(表之间的关系)、数据库物理设计(索引、约束、主键等)有密切关系。在这个项目中我觉得大概有100倍。
有 一步操作要把不合格的数据删除。大概会把一个500万条记录的表的数据删掉一半。删除这些数据大概要花三个小时。后来改为不删除而是仅仅做标记。显然后面 用到这个表的时候会话更多的时间去搜索表和索引。但是在这个项目来看这样做是合算的。后面的步骤中的操作时间并没有明显增加。

2:通过限制数据集数量,改一步执行为循环执行的方法,可以有效提高效率。开发中遇到过几次大表之间join导致执行时间超长。最终的解决方案之一 就是用row_number函数加CTE加while循环,让每一次join涉及到的记录集都比较小。虽然需要循环多次,但是最终的时间要比一次执行快了 许多倍。

[转载]无刷新仿google波形扭曲彩色Asp.net验证码

mikel阅读(1179)

[转载]无刷新仿google波形扭曲彩色Asp.net验证码 – 刘岛(非MVP)的Blog – 博客园.

网上关于ASP.NET验证码的示例是在不少,前一段时间我发布的《51aspx实现的Asp.net无刷新中文验证码》受到了广大网站的转载,但是关于其中无刷新及波形扭曲的文章寥寥无几,示例也几乎难寻,于是我搜集了一些资料写了一个示例免费提供给大家,主要特点如下:

效果图:

无刷新:也就是一般网站都有的“看不清,点击更换”、“更换验证码”的那种功能,一段js脚本搞定!
波形扭曲:类似google验证码,这个能更有效的防止验证码被机器人攻破,具体的扭曲程度可以自行设置(正弦曲线Wave扭曲图片产生波形滤镜效果),加加参数也可以改成msn的那种啊,自己举一反三吧!

彩色:字符颜色都是随机的
大小写:也是随机的,当然为了输入方便没有对大小写进行限制,也可以自己修改哦
注释详细:各个参数都有很详细的说明,容易上手

为了显示扭曲效果,默认字体大小为40像素,可以自行修改

关于扭曲的源码:

1 /// <summary>
2 /// 正弦曲线Wave扭曲图片(Edit By 51aspx.com)
3 /// </summary>
4 /// <param name=”srcBmp”>图片路径</param>
5 /// <param name=”bXDir”>如果扭曲则选择为True</param>
6 /// <param name=”nMultValue”>波形的幅度倍数,越大扭曲的程度越高,一般为3</param>
7 /// <param name=”dPhase”>波形的起始相位,取值区间[0-2*PI)</param>
8 /// <returns></returns>

9 public System.Drawing.Bitmap TwistImage(Bitmap srcBmp, bool bXDir, double dMultValue, double dPhase)
10 {
11 System.Drawing.Bitmap destBmp = new Bitmap(srcBmp.Width, srcBmp.Height);
12
13 // 将位图背景填充为白色
14 System.Drawing.Graphics graph = System.Drawing.Graphics.FromImage(destBmp);
15 graph.FillRectangle(new SolidBrush(System.Drawing.Color.White), 0, 0, destBmp.Width, destBmp.Height);
16 graph.Dispose();
17
18 double dBaseAxisLen = bXDir ? (double)destBmp.Height : (double)destBmp.Width;
19
20 for (int i = 0; i < destBmp.Width; i++)
21 {
22 for (int j = 0; j < destBmp.Height; j++)
23 {
24 double dx = 0;
25 dx = bXDir ? (PI2 * (double)j) / dBaseAxisLen : (PI2 * (double)i) / dBaseAxisLen;
26 dx += dPhase;
27 double dy = Math.Sin(dx);
28
29 // 取得当前点的颜色
30 int nOldX = 0, nOldY = 0;
31 nOldX = bXDir ? i + (int)(dy * dMultValue) : i;
32 nOldY = bXDir ? j : j + (int)(dy * dMultValue);
33
34 System.Drawing.Color color = srcBmp.GetPixel(i, j);
35 if (nOldX >= 0 && nOldX < destBmp.Width
36 && nOldY >= 0 && nOldY < destBmp.Height)
37 {
38 destBmp.SetPixel(nOldX, nOldY, color);
39 }

40 }

41 }

42
43 return destBmp;
44 }

45

关于无刷新的问题我找了很多文章都很复杂,后来想到了一个简单的办法

<img id=imgVerify src=VerifyCode.aspx? alt=看不清?点击更换 onclick=this.src=this.src+’?’ />

一段js代码搞定,不知道其他人是不是还有什么高见?欢迎交流

示例源码下载

[转载]谈谈你最熟悉的System.DateTime

mikel阅读(1066)

[转载]谈谈你最熟悉的System.DateTime – Artech – 博客园.

最近一直在负责公司内部框架的升级工作,今天对一个小问题进行了重新思考——时间的处理。具体来说,是如何有效地进行时间的处理以提供对跨时区的支 持。对于一个分布式的应用来说,倘若客户端和服务端部署与不同的地区,在对时间进行处理的时候,就需要考虑时区的问题。以我们现在的一个项目为例,这是一 个为澳大利亚某机构开发的一个基于Smart Client应用(Windows Form客户端),服务器部署与墨尔本,应用的最终用户可能需要跨越不同的州。澳洲地广人稀,不同的州也有可能会跨越不同的时区。假设数据库并不支持对时 区的区分,服务端需要对针对客户端所在的时区对时间进行相应的处理。不过,对该问题解决方案的介绍我会放在后续的文章中,在这里我们先来介绍一些基础性的 内容——谈谈我们熟悉的System.DateTime类型。

一、你是否知道System.DateTimeKind?

System.DateTime类型,我们再熟悉不过。顺便说一下,这个类型不是class,而是一个struct,换言之它是值类型,而不是引用类型。DateTime处理包含我们熟悉的年、月、日、时、分、秒和毫秒等基本属性之外,还具有一个重要的表示时间类型(Kind)的属性:Kind。该属性的类型为System.DateTimeKind枚举。DateTimeKind定义如下,它具有三个枚举值:Unspecified、Utc和Local。后两个分别表示UTC(格林威治时间)和本地时间。Unspecified顾名思义,就是尚未指定具体类型,这是默认值。

   1: [Serializable, ComVisible(true)]

   2: public enum DateTimeKind

   3: {

   4:     Unspecified,

   5:     Utc,

   6:     Local

   7: }

在DateTime类型中,表示时间类型的Kind属性是只读的,只能在构造函数中指定。相关构造函数和Kind属性的定义如下面的代码片断所示:

   1: [Serializable]

   2: public struct DateTime

   3: {

   4:     //Others...

   5:     public DateTimeKind Kind { get; }

   6:

   7:     public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind);

   8:     public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind);

   9:     public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind);

  10: }

虽然,Kind属性是只读的,但是我们还用另外一中设定Kind的方式,那就是调用DateTime的静态方法的SpecifyKind。该方法不会真正去修改一个现有DateTime对象的Kind属性,而是会重新创建一个新的DateTime对象。方法返回的对象具有和指定时间相同的基本属性(年、月、日、时、分、秒和毫秒),该DateTime对象具有你指定的DateTimeKind值。

   1: public struct DateTime

   2: {

   3:     //Others...

   4:     public static DateTime SpecifyKind(DateTime value, DateTimeKind kind);

   5: }

二、几个常用DateTime对象的DateTimeKind

处理直接通过构造函数构建DateTime对象之外,我们还经常用到DateTime的几个静态只读属性去获取一些特殊的时间,比如Now、UtcNow、MinValue和MaxValue等,那么这些DateTime对象的DateTimeKind又是什么呢?

  • 当我们通过构造函数创建一个DateTime对象的时候,Kind默认为DateTimeKind.Unspecified
  • DateTime.Now表示当前系统时间,Kind属性值为DateTimeKind.Local,所以DateTime.Now应该是DateTime.LocalNow;
  • 而DateTime.UtcNow返回以UTC表示的当前时间,毫无疑问,Kind属性自然是DateTimeKind.Utc;
  • DateTime.MinValue和DateTime.MaxValue表示的DateTime所能表示的最大范围,它们的Kind属性为DateTimeKind.Unspecified。

上面列表对几个常用DateTime对象Kind属性的描述可以通过下面的程序来证实:

   1: DateTime endOfTheWorld = new DateTime(2012, 12, 21);

   2: Console.WriteLine("endOfTheWorld.Kind = {0}", endOfTheWorld.Kind);

   3: Console.WriteLine("DateTime.SpecifyKind(endOfTheWorld, DateTimeKind.Utc).Kind = {0}",

   4:     DateTime.SpecifyKind(endOfTheWorld, DateTimeKind.Utc).Kind);

   5: Console.WriteLine("endOfTheWorld.Kind = {0}", endOfTheWorld.Kind);

   6: Console.WriteLine("DateTime.Now.Kind = {0}", DateTime.Now.Kind);

   7: Console.WriteLine("DateTime.UtcNow.Kind = {0}", DateTime.UtcNow.Kind);

   8: Console.WriteLine("DateTime.MinValue.Kind = {0}", DateTime.MinValue.Kind);

   9: Console.WriteLine("DateTime.MaxValue.Kind = {0}", DateTime.MaxValue.Kind);

输出结果:

   1: endOfTheWorld.Kind = Unspecified

   2: DateTime.SpecifyKind(endOfTheWorld, Dat

   3: endOfTheWorld.Kind = Unspecified

   4: DateTime.Now.Kind = Local

   5: DateTime.UtcNow.Kind = Utc

   6: DateTime.MinValue.Kind = Unspecified

   7: DateTime.MaxValue.Kind = Unspecified

三、DateTime的对等性问题

接下来,我们来谈谈另外一个比较有意思的问题——两个DateTime对象对等性。在这之前,我首先提出这样一个问题:“如果两个DateTime对象相等,是否意味着它们表示同一个时间点?”我想有人会认为是。但是答案是“不一定”,我们可以举一个反例。在下面的程序中,我创建了三个DateTime对象,年、月、日、时、分、秒均是相同的,但Kind分分别指定为DateTimeKind.LocalDateTimeKind.Unspecified和DateTimeKind.Utc

   1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local);

   2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified);

   3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc);

   4:

   5: Console.WriteLine("endOfTheWorld1 == endOfTheWorld2 = {0}", endOfTheWorld1 == endOfTheWorld2);

   6: Console.WriteLine("endOfTheWorld2 == endOfTheWorld3 = {0}", endOfTheWorld2 == endOfTheWorld3);

由于我们处于东8区,基于DateTimeKind.Local的endOfTheWorld1和基于DateTimeKind.Utc的 endOfTheWorld3,不可能表示的是同一个时刻。但是从下面的输出结果来看,它们却是“相等的”,不但如此,Kind为Unspecified 的endOfTheWorld2也和这两个时间对象相等。

   1: endOfTheWorld1 == endOfTheWorld2 = True

   2: endOfTheWorld2 == endOfTheWorld3 = True

由此可见,DateTimeKind对等性判断和DateTimeKind无关,那么在内部是如何进行判断的呢?要回答这个问题,这就要谈谈DateTime另外一个重要的属性——Ticks了。该属性定义如下,是DateTime的只读属性,类型为长整型,表示该DateTime对象通过日期和时间体现出来的计时周期数。每个计时周期表示一百纳秒,即一千万分之一秒。1 毫秒内有 10,000 个计时周期。此属性的值表示自公元元年( 0001 年) 1 月 1 日午夜 12:00:00(表示 DateTime.MinValue)以来经过的以100 纳秒为间隔的间隔数。

   1: public struct DateTime

   2: {

   3:     //Others...

   4:     public long Ticks { get; }

   5: }

注意,这里的基准时间0001 年 1 月 1 日午夜 12:00:00,并没有说是一定是UTC时间,所以Ticks和DateTimeKind无关,这里通过下面的实例看出来:

   1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local);

   2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified);

   3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc);

   4:

   5: Console.WriteLine("endOfTheWorld1.Ticks = {0}", endOfTheWorld1.Ticks);

   6: Console.WriteLine("endOfTheWorld2.Ticks = {0}", endOfTheWorld2.Ticks);

   7: Console.WriteLine("endOfTheWorld3.Ticks = {0}", endOfTheWorld3.Ticks);

从下面的输出结果我们不难看出,上面创建的具有不同DateTimeKind的三个DateTime的Ticks属性的值都是相等的。实际上,DateTime的对等性判断就是通过Ticks的大小来判断的

   1: endOfTheWorld1.Ticks = 634917312000000000

   2: endOfTheWorld2.Ticks = 634917312000000000

   3: endOfTheWorld3.Ticks = 634917312000000000

我们经常说的UTC时间和本地时间之间的相互转化,实际上指的就是将一个具有某种DateTimeKinde的DateTime对象转化成具有另外一种DateTimeKind的DateTime对象,并且确保两个DateTime对象对象表示相同的时间点。关于时间转换的实现,我们有很多不同的选择。

四、通过DateTime类型的ToLocalTime和ToUniversalTime方法实现UTC和Local的转换

对基于三种不同DateTimeKind的DateTime对象之间的转化,最方便的就是直接采用DateTime类型的两个对应的方法:ToLocalTimeToUniversalTime,这两个方法的定义如下。

   1: public struct DateTime

   2: {

   3:     //Others...

   4:     public DateTime ToLocalTime();

   5:     public DateTime ToUniversalTime();

   6: }

实际上我们所说的不同DateTimeKind之间的DateTime之间的转化主要包括两个方面:将一个 DateTimeKind.Local(或者DateTimeKind.Unspecified)时间转换成DateTimeKind.Utc时间,或者 将DateTimeKind.Utc(或者DateTimeKind.Unspecifed时间)转换成DateTimeKind.Local时间。为了 深刻地理解两种不同转换采用的转化规则,我写了如下一段程序:

   1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local);

   2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified);

   3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc);

   4:

   5: Console.WriteLine("endOfTheWorld1.ToLocalTime() = {0}",endOfTheWorld1.ToLocalTime());

   6: Console.WriteLine("endOfTheWorld2.ToLocalTime() = {0}", endOfTheWorld2.ToLocalTime());

   7: Console.WriteLine("endOfTheWorld3.ToLocalTime() = {0}\n", endOfTheWorld3.ToLocalTime());

   8:

   9: Console.WriteLine("endOfTheWorld1.ToUniversalTime() = {0}", endOfTheWorld1.ToUniversalTime());

  10: Console.WriteLine("endOfTheWorld2.ToUniversalTime() = {0}", endOfTheWorld2.ToUniversalTime());

  11: Console.WriteLine("endOfTheWorld3.ToUniversalTime() = {0}", endOfTheWorld3.ToUniversalTime());

对于DataTimeKind为Utc和Local之间的转化,没有什么可以说得,就是一个基于时差的换算而已。大家容易忽视的是DataTimeKind.Unspecifed时间分别向其他两种DateTimeKind时间的转换问题。从下面的输出我们可以看出,当DateTimeKind.Unspecifed时间向DateTimeKind.Local转换的时候,实际上是当成DateTimeKind.Utc时间而向DateTimeKind.Utc转换的时候,则当成是DateTimeKind.Local。 顺便补充一下:不论被转换的时间属于怎么的DateTimeKind,调用ToLocalTime和ToUniversalTime方法的返回的时间的 Kind属性总是DateTimeKind.Local和DateTimeKind.Utc,两者之间的转换并不只是年月日和时分秒的改变。

   1: endOfTheWorld1.ToLocalTime() = 12/21/2012 12:00:00 AM

   2: endOfTheWorld2.ToLocalTime() = 12/21/2012 8:00:00 AM

   3: endOfTheWorld3.ToLocalTime() = 12/21/2012 8:00:00 AM

   4:

   5: endOfTheWorld1.ToUniversalTime() = 12/21/2012 4:00:00 PM

   6: endOfTheWorld2.ToUniversalTime() = 12/21/2012 4:00:00 PM

   7: endOfTheWorld3.ToUniversalTime() = 12/21/2012 12:00:00 AM

五、通过TimeZoneInfo实现Utc和Local的转换

上面提供的方式虽然简单,但是功能上确有局限,因为转换的过程是基于本机当前的时区。这解决不了我在开篇介绍的应用场景:服务端根据访问者所在的时区(而不是本机的时区)进行时间的转换。换句话说,我们需要能够基于任意时区的时间转换方式,这就可以通过System.TimeZoneInfo

TimeZoneInfo实际上对原来System.TimeZone类型的一个改进。它是一个可序列化的类型(这一点在分布式场景中进行基于时区的时间处理实现非常重要),表示具体某个时区的信息。它提供了一系列静态方法供我们对某个DateTime对象进行基于指定TimeZoneInfo的时间转换, 在这我们介绍我们常用的2个:ConvertTimeFromUtc和ConvertTimeToUtc。前者将一个DateTimeKind.Utc或 者Unspecified的DateTime时间转换成基于指定时区的DateTimeKind.Local时间;后者则将一个基于指定时区的 DateTimeKind.Local或者DateTimeKind.Unspecified时间象转化成一DateTimeKind.Utc时间。此 外,TimeZoneInfo还提供了两个静态属性Local和Utc表示本地时区和格林威治时区。

   1: [Serializable]

   2: public sealed class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback

   3: {

   4:     //Others...

   5:     public static DateTime ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone);

   6:     public static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfo sourceTimeZone);

   7:

   8:     public static TimeZoneInfo Local { get; }

   9:     public static TimeZoneInfo Utc { get; }

  10: }

我们照例来做个试验。还是刚才创建的三个DateTime对象,现在我们分别调用ConvertTimeFromUtc将 DateTimeKind.Utc或者DateTimeKind.Unspecified时间转换成DateTimeKind.Local时间;然后将调 用ConvertTimeToUtc将DateTimeKind.Local或者DateTimeKind.Unspecified时间转换成 DateTimeKind.Utc时间。

   1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local);

   2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified);

   3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc);

   4:

   5: Console.WriteLine("TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = {0}",

   6:     TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2, TimeZoneInfo.Local));

   7: Console.WriteLine("TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3,TimeZoneInfo.Local) = {0}\n",

   8:    TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3, TimeZoneInfo.Local));

   9:

  10: Console.WriteLine("TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,TimeZoneInfo.Local) = {0}",

  11:     TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1, TimeZoneInfo.Local));

  12: Console.WriteLine("TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2,TimeZoneInfo.Local) = {0}",

  13:    TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2, TimeZoneInfo.Local));

同上面进行的转换方式一样,在向DateTimeKind.Utc时间进行转换的时候,DateTimeKind.Unspecifed时间被当成 DateTimeKind.Local;而在向DateTimeKind.Local时间转换的时候,DateTimeKind.Unspecifed则 被当成DateTimeKind.Utc时间。

   1: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/22/2012 8:00:00 AM

   2: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3,TimeZoneInfo.Local) = 12/22/2012 8:00:00 AM

   3:

   4: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,TimeZoneInfo.Local) = 12/21/2012 4:00:00 PM

   5: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/21/2012 4:00:00 PM

image ConvertTimeFromUtc和ConvertTimeToUtc方法在转换的时候,如果发现被转换的时间和需要转化时间具有相同的 DateTimeKind会抛出异常。也就是说,我们不能调用ConvertTimeFromUtc方法并传入DateTimeKind.Local时 间,也不能调用ConvertTimeToUtc方法并传入DateTimeKind.Urc时间。如右图所式,我们将一个 DateTimeKind.Utc时间(DateTime.UtcNow)传入ConvertTimeToUtc方法,结果抛出一个 ArgumentException异常。错误消息为:“The conversion could not be completed because the supplied DateTime did not have the Kind property set correctly.  For example, when the Kind property is DateTimeKind.Local, the source time zone must be TimeZoneInfo.Local.
Parameter name: sourceTimeZone”。

TimeZoneInfo是我们解决在开篇提出的“如何在分布式应用中处理跨时区时间处理问题”的关键,具体的解决方案,请听下回分解。

[转载]Visual Studio 2010 所有版本 下载,激活,序列号,注册码,下载地址(全集)

mikel阅读(984)

[转载]Visual Studio 2010 所有版本 下载,激活,序列号,注册码,下载地址(全集) – 醒了又睡的鱼.net的空间 – 博客园.

Visual Studio 2010 下载,安装,激活,序列号,注册码,下载地址(全集) 附Visual Studio 2008下载地址 激活 sp1下载

Visual Studio 2010 激活码 Visual Studio 2010注册码
YCFHQ-9DWCY-DKV88-T2TMH-G7BHP
破 解方式:Product Key为YCFHQ9DWCYDKV88T2TMHG7BHP,用这个key替换安装盘中setup目录中的setup.sdb中[Product Key]的内容,重新保存即可,这是针对Visual Studio 2010 Ultimate版,其他版本的应该也适用,之后的安装就一路顺风了

custom fabric

1、Microsoft Visual Studio 2010 Ultimate的安装与下载

标题:Microsoft Visual Studio 2010 Ultimate – ISO

文件名:X16-42552VS2010UltimTrial1.iso 容量:2283.0 MB SHA-1: 0×8371f6a8d090063fcc320617e94854374633df3c

网页地址:http://www.microsoft.com/downloads/details.aspx?FamilyID=06a32b1c-80e9-41df-ba0c-79d56cb823f7&displaylang=en

下载地址:http://download.microsoft.com/download/2/4/7/24733615-AA11-42E9-8883-E28CDCA88ED5/X16-42552VS2010UltimTrial1.iso

2、Microsoft Visual Studio 2010 Ultimate的注册与破解

Help->Register Product-> 注册码 YCFHQ-9DWCY-DKV88-T2TMH-G7BHP

Visual Studio 2010 下载地址 Visual Studio 2010 下载 Visual Studio 2010 中文下载

专业版(Professional)
http://download.microsoft.com/download/4/0/E/40EFE5F6-C7A5-48F7-8402-F3497FABF888/X16-42555VS2010ProTrial1.iso

高级版(Premium)
http://download.microsoft.com/download/F/F/8/FF8C8AF1-D520-4027-A844-8EC7BC0FB27C/X16-42546VS2010PremTrial1.iso
旗舰版(Ultimate)
http://download.microsoft.com/download/2/4/7/24733615-AA11-42E9-8883-E28CDCA88ED5/X16-42552VS2010UltimTrial1.iso
测试专业版(Test Professional)
LicenseKey注册码=6F0510EB-FF8F-47ec-96E1-78FB4B955330

Visual Studio 2010 Professional 官方试用版内置序列号:
[Product Key]序列号
Q49M2-C79JF-QP93Q-WX9GJ-6TKKD
Visual Studio 2010 Professional 正式序列号:(已安装测试可用。Professional版与Ultimate版均测试通过!)
YCFHQ-9DWCY-DKV88-T2TMH-G7BHP

Visual Studio 2010 Ultimate 官方试用版内置序列号:
[Product Key]序列号
YR3W8-FCM2B-7BKF9-HMQFT-CH7WK

下载了官方试用版的同学,安装后可以通过添加删除程序对Visual Studio 2010进行序列号更换。
或者修改安装光盘中setup目录下的setup.sdb文件,做一次安装激活。

MSDN官网上公布的Visual Studio 2010 SHA1及CRC:
File Name: en_visual_studio_2010_ultimate_x86_dvd_509116.iso
Date Published (UTC): 4/12/2010 8:53:48 AM
Last Updated (UTC): 4/12/2010 10:44:43 AM
SHA1: 84C90BB02AF92347010FF9058E0A2BA28F19CC53
ISO/CRC: B22E6250

File Name: en_visual_studio_2010_premium_x86_dvd_509357.iso
Date Published (UTC): 4/12/2010 8:53:23 AM
Last Updated (UTC): 4/12/2010 10:44:31 AM
SHA1: A4E69966496AD0F704FE9D97FAC2A723EF75A99B
ISO/CRC: D314471B

File Name: en_visual_studio_2010_professional_x86_dvd_509727.iso
Date Published (UTC): 4/12/2010 8:53:35 AM
Last Updated (UTC): 4/12/2010 10:44:19 AM
SHA1: F0ED50712D83BF0EDA7D284DA76DF49E4C88CEF7
ISO/CRC: 6D4DCAE4

Visual Studio 2008

1、Visual Studio 2008 Team Suite Edition的安装与下载

标题:Visual Studio Team System 2008 Team Suite (90-day Trial)

文件名:VS2008TeamSuiteENU90DayTrialX1429235.iso  容量:3921.2 MB

网页地址:http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=d95598d7-aa6e-4f24-82e3-81570c5384cb

下载地址:http://download.microsoft.com/download/d/8/9/d89c9839-ac45-4a6c-b25f-2f60b190e356/VS2008TeamSuiteENU90DayTrialX1429235.iso

2、Visual Studio 2008 Team Suite Edition的注册与破解

方法1:安装完90天试用版后,在“添加或删除应用程序”,删除vs 2008,点击“卸载”,在卸载里有让输入序列号的地方,把正式版的序列号PYHYP-WXB3B-B2CCM-V9DX9-VDY8T输入进去就行了。

方法2:把iso文件中的setup文件夹中的setup.sdb文件中的[Product Key]下的一行的原来的序列号换为能用的正式版的序列号就行了。
[Product Key]序列号
PYHYPWXB3BB2CCMV9DX9VDY8T
3、Microsoft Visual Studio 2008 Service Pack 1安装与下载

标题:Microsoft Visual Studio 2008 Service Pack 1 (iso)

文件名:VS2008SP1ENUX1512962.iso 容量:831.3 MB

网页地址:http://www.microsoft.com/downloads/details.aspx?familyid=27673C47-B3B5-4C67-BD99-84E525B5CE61&displaylang=en

下载地址:http://download.microsoft.com/download/a/3/7/a371b6d1-fc5e-44f7-914c-cb452b4043a9/VS2008SP1ENUX1512962.iso