Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

程序员迷茫的未来

$
0
0


我是一名充满热情的程序员,一名开源软件的倡导者,我喜欢用科技提高人们的生活。我喜欢创造东西,喜欢将它们分享给世界。我有大量的开源开发作品, 请看我的GitHub页面。

本文的作者:Ed Finkler本文的作者:Ed Finkler

我是一名充满热情的程序员,一名开源软件的倡导者,

我一直担惊受怕,过去,可能是因为我年轻,但现在,我已经不是那么年轻了,我仍然发现有很多事情让我害怕。

我越来越担心我作为一个程序员的未来。

当年纪越来越大后,我开始变得不能加班。我开始用更多的时间和家人在一起,而不是坐在计算机前(尽管这样,她们仍是抱怨)。我在本地教育委员会社区里提供一些帮助,还组织开源兴趣小组参加活动。

我在思考,为什么以前会把如此多的时间全部用在编程上。大量的编程。那是我渴望深入研究一个类库,一个框架或一门技术。

现在的技术的学习曲线的增加,让我的忍耐性越来越低。各种新技术,因为新奇让人兴奋,但最终变成一场场争论。我越来越无法忍受这些充满市场宣传气息的喧嚣。我对技术看重的是稳定,清晰。

我曾经对JavaScript非常的感兴趣。我曾经用5年时间开发一个开源项目,其中95%都是JavaScript。但我从来没有认为我对各种JS技术有了很好的掌握,我对很多新兴的JS热门技术(ES6, Angular, Ember, Shadow DOM, Module systems等等)的理解都是很浅显的。现在很多公司的招聘都要求会AngularJS技术。我下功夫学过它,但这种技术频繁的升级和不兼容,以及2.0版的计划,让我对这个框架彻底的失望。我对富客户端应用的兴趣彻底的发生了反转。现在,我最感兴趣的是在服务端进行计算,就像是2004年时的样子。

我正式的工作中主要是使用Python,做服务端的处理。我喜欢这种语言,但我仍然觉得并不是真的掌握了它。我有15年的PHP开发经验积累,所以,通常对PHP是轻车熟路,但使用Python时并没有这种感觉。我并不感觉我对模块化系统有了全面的掌握。而且我真的没有弄明白它的Class机制。generator究竟什么东西?它的工作原理是什么?我很迷茫。

你曾经尝试过着在AWS上搭建一个系统吗?里面有近百万个按钮、设置选项、新名词,我都不明白。我根本不知道它里面的系统是怎么运行起来的。

这些困难终究会被克服——只要有足够的时间和热情。但热情决定了我的时间分配。我现在没有足够的热情来利用业余时间来改变这种被动状态。生活中有更多更重要的——跟编程技术不相关的事情需要我去做。

你知道吗?我以前曾是一名平面“设计师”。真的。在1999年,我的技术水平足以在Web上配得上这个称呼。我曾经给一个独立音乐录音室设计过CD封面。2005年左右,当时在网上流传的所谓“优秀设计”是出自我手。几年前,我不再将“设计师”头衔放入我的个人介绍里,因为听起来有点可笑。

早些年,当我还是一个人负责一个web商店时,一切都很容易。我自己配置Apahche,PHP,MySQL,PostgreSQL等等。我自己写服务器端程序。我编写自己的HTML,CSS和JavaScript。所有的这些技术我都上手。我很喜欢它们。

而当处在一个团队中时,事情开始变化。我很想念过去的方式,我想念能够自己折腾HTML,CSS,看着它们从我手里变成软件产品。我想念能够自己折腾服务器配置,看能否让PostgreSQL提高一点点性能。现在,我感觉我的工作太单一了,它专项了,日常工作中适合没有任何机会再去接触那些东西。

我担心这“Web程序员”的工作超越了我的能力,我担心我的技术在萎缩。

10年后我会在哪里?我不知道。我希望我还能够掌握足够的技术来挣一份工资。但事实上我现在接触的只有JavaScript,而我对它们的了解越来越少。

我希望我仍然能有拿得出手的技术。然而,我不知道路在何方。


分享做为独立开发者的一些经验

$
0
0

今天中午在知乎上收到邀请回答 做为独立开发者,有哪些可以分享的经验?, 于是饭后东一榔头西一镐的回答了一通,居然颇受欢迎。我把原文删了一些不必要的再发到这边。

  1. 不要总做外包,要有自己的产品

    外包无论价格多高都还是苦力钱,要形成自己的产品。

  2. 每年给自己制定学习目标和计划

    做独立开发者后,就失去了和同事们交流学习的机会,而作为 Programmer 不停的学习新技术是必须的,所以这方面要特别加强。

  3. 工作计划可以和大众的节奏错开

    做独立开发者最大的好处就是时间的自由。很多地方,例如美术馆,电影,旅游胜地,在周末人满为患,周一到周五则几乎空无一人。 所以我常常是周末工作,周一到周五抽一到两天休息。做独立开发者的时候,黄金周是绝对不出去玩的,都是在家工作,旅游淡季的时候出去玩。

  4. 尽量购买或外包一些非核心工作

    现在网上有很多成熟的各种服务,比如 template monster 的网站模板,可以让你简单填写内容就能搞出一个很漂亮的网站。 这种工作不需要自己做,尽量外包或直接购买。 自己做最核心的东西。 但这个核心并不等同于核心技术, 而是你的核心竞争力。 当你的核心能力是整合能力的时候,甚至所谓的核心技术都可以外包。

  5. 要养成规律的生活习惯

    如果生活没规律,工作也就缺乏计划性,那工作的拖延不可避免,拖延多了,人的状态,心气都会下降,最后形成恶性循环

  6. 养成体育锻炼的习惯

    体育锻炼一个是有助于保持身体健康外,对你保持心理健康,保持积极的心态很有帮助。 我在做独立开发者期间,养成了长跑的习惯,那 9 年的北京马拉松除了有次因为在国外没能参加外,其他全参加了。

  7. 要重视社交生活

    对这 9 年的生活非常满意,如果要说有什么不足的话,就是开始没太重视社交,后来才开始重视。如果有机会重新来过,一定会更积极的参加社交活动。我这个人原先就比较孤僻, 自己一个人工作后,就更有点离群索居了,和客户和朋友都是靠 email,IM 联系。 虽然经常去旅游,甚至一年有半年在路上度过,但大多是自己一个人背包到处转。过了几年才意识到问题,感觉自己脱离开社会太远了,才开始在亲友帮助下重新开始积极参加各种社交活动。

    关于社交活动有几个建议:

    1. 多参加积极向上的群体的活动

      在北京的时候,长期参加了阳光志愿者,后海龙舟队,古逸读书会的活动。这些组织,尤其是阳光志愿者,人们都很有正能量,又都很友善,认识了不少很好的朋友,他们是我这辈子最好的财富。 和积极向上的人多交流有助于自己心态的调整。

    2. 觉得不擅长沟通可以参加培训班

      我不擅长与人当面沟通,于是就总是有意无意躲避与人沟通,结果越来越糟糕,做独立开发者后就更愈演愈烈。 后来听从朋友的建议,参加了一个关于如何沟通的培训班,确实有效果,关键是从此树立了不惧怕当面沟通,重视沟通,积极沟通的态度

    3. 建立自己的 Network(应该叫关系网,但中文的这个词有点贬义)

      不要以为个人开发者不需要 Network,其实应该是更需要,这个道理我今年才明白。

  8. 多参加技术聚会

    一个对学习新技术有帮助,还有就是能认识一些朋友,有助于拓展自己的交际圈。 看楼主是深圳的,我去参加过深圳的 Startup Grind 认识了不少有趣的人,推荐。

本文链接

中国改革户籍制度

$
0
0
国务院公开了《国务院关于进一步推动户籍制改革的意见》的全文。《意见》提出建立城乡统一的户口登记制度,取消农业户口与非农业户口性质区分和由此衍生的蓝印户口等户口类型,统一登记为居民户口。自1958年户籍制度建立以来产生的社会服务、福利、权利的城乡二元化状态有望告终。《意见》将教育划分为基本义务教育、非义务教育、异地高考等不同层次,设置不同获取条件。此次户改,一个重要推进手段是居住证:居住证持有人享有与当地户籍人口同等的基本公共教育权利;以连续居住年限和参加社会保险年限等为条件,逐步享有与当地户籍人口同等的中等职业教育资助等权利,同时结合随迁子女在当地连续就学年限等情况,逐步享有随迁子女在当地参加中考和高考的资格。城区人口500万以上的特大城市则强调严格控制人口规模(如图所示),上海、广州等地已实行积分落户,但落户指标很少。其积分设置方式也受到一些诟病,学历不高但在城市长期就业的农业转移人口很难积足分值。






Thrift入门试用

$
0
0

 

在新的项目中公司在平台内部系统间使用Thrift通讯,都没有听说过。然后听同事说,是跨语言Socket通讯的开源组件。

 

功能及特点

 

1.跨平台和语言的Socket通讯组件。

 

2.根据伪代码的结构语言定义对象和服务结构,然后生成各语言的代码和接口

 

3.各语言根据组件提供的库,编写客户端和服务器端程序。服务器端实现接口并编写业务逻辑。

 

4.服务器端支持多种序列化方式(Binary,Compact,JSON等)和多种服务器实现

 

 

 

太晚了,以后在完善,先贴代码了

 

 

 

本测试使用WINDOW环境和JAVA语言

 

 

 

1.下载和安装

 

下载地址:http://thrift.apache.org

 

下载最新版本,当前0.7.0

 

 

 

 

 

解压 thrift-x.x.x.tar.gz,进入thrift-x.x.x/lib/java,在cmd模式先使用ant编译(ant安装和设置就不说了哈。然后需要上公网,昨天在公司弄不起就是公司上不了公网,热)。

 

 

 

2.建立测试工程

 

普通JAVA工程,目录如下:

 

src

 

+ org.acooly.thrift.demo.client  客户端代码

 

+ org.acooly.thrift.demo.generalcode 通过thrift工具生成的代码

 

+ org.acooly.thrift.demo.server 服务器端代码

 

lib

 

+拷贝前面ant编译后的build/lib下的jar和编译生成的thrift-x.x.x.jar

 

tools

 

+ thrift.exe 前面下载的

 

+ thriftdemo.thrift 伪代码

 

 

 

3.编写伪代码文件*.thrift

 

 

 

Java代码   收藏代码
  1. namespace java org.acooly.thrift.demo.generalcode  
  2.   
  3. struct Contact{  
  4.     1:i32 id  
  5.     2:string name  
  6.     3:i64 birthday  
  7.     4:string phoneNo  
  8.     5:string ipAddress  
  9.     6:map<string,string> props  
  10. }  
  11.   
  12. service ContactManager{  
  13.   void save(1:Contact contact)  
  14.   void remove(1:i32 id)  
  15.   list<Contact> getAll();  
  16.   list<Contact> query(1:map<string,string> conditions)  
  17. }  

 

 

 

4.生成代码

 

 

 

cmd模式进入 tools目录,运行

 

thrift.exe -gen java thriftdemo.thrift

 

 

 

运行成功后,在本目录会生成gen-java目录,拷贝该目录下生成的代码到工程中对应的包。

 

 

 

5.服务器代码和实现业务逻辑

 

 

 

实现业务逻辑

 

Java代码   收藏代码
  1. package org.acooly.thrift.demo.server;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Calendar;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7.   
  8. import org.acooly.thrift.demo.generalcode.Contact;  
  9. import org.acooly.thrift.demo.generalcode.ContactManager;  
  10. import org.apache.thrift.TException;  
  11.   
  12. public class ContactManagerImpl implements ContactManager.Iface{  
  13.   
  14.     public List<Contact> getAll() throws TException {  
  15.         List<Contact> contacts = new ArrayList<Contact>();  
  16.         contacts.add(new Contact(1,"zhangpu",Calendar.getInstance().getTimeInMillis(),"1389612222","192.168.2.1",null));  
  17.         return contacts;  
  18.     }  
  19.   
  20.     public List<Contact> query(Map<String, String> conditions) throws TException {  
  21.         List<Contact> contacts = new ArrayList<Contact>();  
  22.         contacts.add(new Contact(1,"zhangpu",Calendar.getInstance().getTimeInMillis(),"1389612222","192.168.2.1",null));  
  23.         return contacts;  
  24.     }  
  25.   
  26.     public void remove(int id) throws TException {  
  27.         System.out.println("invoke: remove,id = " + id);  
  28.     }  
  29.   
  30.     public void save(Contact contact) throws TException {  
  31.         System.out.println("invoke: save,contact = " + contact);  
  32.           
  33.     }  
  34.   
  35.       
  36.       
  37. }  

 

 编写服务器代码

 

Java代码   收藏代码
  1. package org.acooly.thrift.demo.server;  
  2.   
  3. import org.acooly.thrift.demo.generalcode.ContactManager;  
  4. import org.acooly.thrift.demo.generalcode.ContactManager.Iface;  
  5. import org.apache.thrift.protocol.TCompactProtocol;  
  6. import org.apache.thrift.protocol.TCompactProtocol.Factory;  
  7. import org.apache.thrift.server.TServer;  
  8. import org.apache.thrift.server.TSimpleServer;  
  9. import org.apache.thrift.server.TServer.Args;  
  10. import org.apache.thrift.transport.TServerSocket;  
  11.   
  12. public class ThriftServer {  
  13.   
  14.     public static void main(String[] args) throws Exception{  
  15.         TServerSocket serverSocket = new TServerSocket(8111);  
  16.         ContactManager.Processor<Iface> processor = new ContactManager.Processor<Iface>(new ContactManagerImpl());  
  17.         Factory factory = new TCompactProtocol.Factory();  
  18.         Args ag = new Args(serverSocket);  
  19.         ag.outputProtocolFactory(factory);  
  20.         ag.inputProtocolFactory(factory);  
  21.         ag.processor(processor);  
  22.         TServer server = new TSimpleServer(ag);  
  23.         server.serve();  
  24.     }  
  25.       
  26. }  

 

 

 

6.客户端代码

 

Java代码   收藏代码
  1. package org.acooly.thrift.demo.client;  
  2.   
  3. import java.util.Calendar;  
  4. import java.util.List;  
  5.   
  6. import org.acooly.thrift.demo.generalcode.Contact;  
  7. import org.acooly.thrift.demo.generalcode.ContactManager;  
  8. import org.apache.thrift.protocol.TCompactProtocol;  
  9. import org.apache.thrift.protocol.TProtocol;  
  10. import org.apache.thrift.transport.TSocket;  
  11. import org.apache.thrift.transport.TTransport;  
  12.   
  13.   
  14. public class ThriftClient {  
  15.   
  16.     public static void main(String[] args) throws Exception{  
  17.           
  18.         TTransport transport = new TSocket("localhost",8111);  
  19.         TProtocol protocol = new TCompactProtocol(transport);  
  20.         ContactManager.Client client = new ContactManager.Client(protocol);  
  21.         transport.open();  
  22.           
  23.         List<Contact> list = client.getAll();  
  24.         System.out.println(list);  
  25.           
  26.         client.save(new Contact(1,"zhangpu",Calendar.getInstance().getTimeInMillis(),"1389612222","192.168.2.1",null));  
  27.           
  28.         client.remove(1);  
  29.         transport.close();  
  30.     }  
  31. }  

 

 

 

7.启动和测试运行

 

1.运行ThriftServer

 

2.运行ThriftClient

 

 

 

ThriftServer输出:

 

invoke: save,contact = Contact(id:1, name:zhangpu, birthday:1308591769148, phoneNo:1389612222, ipAddress:192.168.2.1, props:null)
invoke: remove,id = 1

 

 

 

ThriftClient输出:

 

[Contact(id:1, name:zhangpu, birthday:1308591769131, phoneNo:1389612222, ipAddress:192.168.2.1, props:null)]

 

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



windows客户端崩溃分析和调试

$
0
0
本文介绍windows上崩溃分析的一些手段,顺便提多进程调试、死锁等。

1.崩溃分析过程
1.1 确认错误码
无论是用windbg还是用vs,首先应该注意的是错误码,而90%以上的崩溃都是非法访问。
在非法访问时,可以看一下访问的目标地址。地址是0,或者离0很近(0x00000008或0xfffffffc),
一般和空指针相关。如果是一个貌似正常的地址,一般是对象已析构后访问其数据,或者堆破坏。

1.2确认崩溃对应的C++操作
什么是确认崩溃对应的C++操作:
比如非法访问,通常得有个mov指令才会触发内存访问,然后导致崩溃。而mov指针对应于C++的哪一步呢?
比如a->b->c->foo();
在看到源码时,会定位于这一行,但是,并不清楚是哪一步访问失败。所以这个时候要查看对应汇编代码。
大概会有好几个mov,简单的分析就知道是哪一步时访问失败。

对编码的影响:
这就要求,不要在单个语句中写太复杂的东西比如
x ? b[i] : y > 0 ? c->member[8] : *ptr;
这样的代码崩溃,要还原到错误的地方很难。

虚函数调用:
通常
mov edx, dword ptr [ecx]
mov edx, dword ptr [edx+0x??]
call edx
意味着虚函数调用,每一行都可能是崩溃位置(在call内崩溃时,vs会标注出下一条语句的位置)。
在第一行崩溃意味着拿到一个非法指针,可能是空,也可以指向非法地址。
在第二行崩溃意味着对象已经析构,ecx指向可以访问,但是值不对,所以拿到的虚函数表不对。
在最后一行崩溃一般还有一个崩溃栈,但是看不到栈帧,在vs中对应的栈桢显示一个地址,没其它内容。
也一般意味着对象析构。

对象及析构函数:
析构函数是经常发生崩溃的地方,如果没有用户提供的析构函数,会定位到几行汇编。所以没事,就写个
析构函数吧,至少能定位到是析构函数。
在析构函数外部还会有一大堆汇编代码,里面是对象成员析构的代码。崩溃在里面的时候,难以确认
是哪个对象析构。
如果用指针,在析构函数中主动调用Release或delete,这样可以显示调用,不用去猜是谁在析构,当然
用指针或值对象,在逻辑上各有其好处,在此不表。
如果崩溃位置是call或jmp到某个A::~A()的位置,可以猜测到析构的对象的类型是A。
对象析构顺序是从后往前,从子类到基类,根据这点,结合崩溃位置,可以猜测谁析构。
利用对象布局,比如成员在对象中的偏移,可能有得于猜测谁析构出问题。
可以人为地在对象布局中引入一些填充的字节,使得能看到this对象(线上崩溃没有堆上的数据,因为
截取fulldump并上报有操作上的难度,所以this指向堆上时可能看不到,而在栈上则有可能),有利于分析。

还原上下文:
线上拿到的dump的信息少,不能调试。所以可以根据崩溃所在模块,崩溃在模块中的偏移量,在本地
调试对应的bin,找到对应的模块,偏移量,打上断点,可以在本地还原出崩溃时的执行环境。当然,
在本地执行到对应位置时不一定崩溃,但是,有了更多上下文信息,可以比较容易确定对应的C++操作。

使用IDA:
可以使用IDA让汇编代码更好看,较容易分析流程。

关闭alsr,指定建议模块加载地址:
这样可能使得更容易分析。但是会使得安全性降低,可以用于小流量版本。

ln指令:
windbg中ln指令可以根据地址,还原出对应的信息,比如该地址是在某个类的某个方法中。有时可能会
还原出几个信息:A::foo() + 0x??, B::foo1() + 0x??,这就需要自己根据上下文判断了。

代码优化:
代码优化使得分析更难,可以尝试改变一些编译选项,降低优化级别,保留栈桢,关闭应用程序全局优化,
使得在release下的分析容易些。

所见并非真实:
windbg和vs看到的栈帧可能是假的:可能在中间某一些可能已经乱了,可能栈桢省略使得vs分析的结果不对。
(一般是vs分析得不对,另外也有windbg杯具的时候)
对于在中间已经乱掉的栈,可以根据返回地址,栈参数,栈桢省略数据等,重新还原出栈。不过在90%的情况
下,即使还原出来,也不知道下一步怎么办。

对象还原:
线上崩溃没有堆,可以将感兴趣的对象拷贝拷贝到栈上(自己得控制深拷贝),然后崩溃上报中就可以看到
对象的状态了。(注意代码优化可能使得拷贝无效)

1.3C++上的逻辑
在确定崩溃和C++操作的关系后,就是自己逻辑上的问题了,基本上能遇到的问题都是对象生命周期管理
不当,进而造成非法访问。
指针的判空能规避一处的非法访问,但是可以把错误进一步扩散。指针判空,且用且珍惜。

在设计或编码时,应当考虑代码的可调试性,比如chromium中的线程池中,添加任务时,会生成当前调用
信息,和task绑定,以使于定位错误。

1.4堆破坏
基本无解,崩溃现场和引入错误的点相差太远。只能尽人事,听天命了。
比如,开一下页堆,存在一定概率使得崩溃出现,看人品。
比如,换一个CRT堆,或者自己写个,增强错误检测。
比如,CRT本身,尤其是调试的堆,堆上有些填充信息,使得在看到的时候或多或少叹口气:大概认识这些
填充信息,想要更多的信息,难啊。。。
比如,可以自己写个调试器,自己插入页堆,或者使用系统的页堆,使得检测自动化,然后通过大规模数据
使之重现。

2.其它
多进程调试:
可以通过在
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
建立关心的进程名的项,填上debugger键值,值为调试器路径,使得进程创建时就attach。(gflags也是
改这里)。但是问题是,有的模块是按需加载的,在这个时候还不能在对应模块中下断点。
另外可以自己在关心的位置加上MessageBox或ATLASSERT之类的代码,等弹出对话框时再attach到对应进程。

activex:
attach方法同理。但是,IE的多进程模型会使得attach不方便。
在IE9及以上,其进程模型是一个主进程,控制多个tab进程,按一定规则创建tab进程,将任务分派到
tab上。同一个网页打开两次,可能分配到不同的进程上,也可能是相同的进程。在同一个进程中,同一
个activex可能有多个实例,而且每个实例对应的主线程还不一定是同一个线程。
一般会控制只开一个tab,使得调试更加容易。另外可以开多tab,然后关闭,然后再开,来测试同一个进程
有多外activex实例的情况。更进一步,可以自己调用IWebBrowser2,来模拟更多的情况。

np插件:
chrome中这个简单些。一个插件一个进程,多个实例,共享主线程。
还有些开源工具将activex适配为np插件,使得可以在chrome中调用ax,调试。

多机调试:
前面说过,windbg,vs都支持。

死锁:
死锁现场不会是线上问题(可以通过一定手段,使得在线上发生死锁时报告,但是基本上没用过,对应的
手段在线下有玩过)。线下问题一般会有现场,或者能拿到full dump。一般使用windbg来看,用~*kb或者
这系列的命令看看线程都在干什么。而重点关注的则是WaitForSingleObjectEx之类的调用。通过分析调用
对应的参数,进一步能还原出,拿到 分发器 对象后不归还的线程是谁。或者也可以!runaway找到占用CPU
高的线程,然后看看该线程在干什么。线程循环等待,则死锁了。线程一直在那里跑,可能是死循环了。

线下的死锁检测,一般可以向主线程发一个消息来实现。

warning:有可能某个线程拿到分发器对象,但是该线程已经挂掉了。
作者:baihacker 发表于2014-7-30 23:01:27 原文链接
阅读:138 评论:0 查看评论

windows客户端开发调试工具

$
0
0
本文介绍windows常用开发与调试工具。


1.windows常用开发与调试工具
1.1 Sysinternals
内核大神打造,含大量windows系统工具,windows开发必备神器,大神被MS招安。
下载地址:http://technet.microsoft.com/en-us/sysinternals


Procmon.exe
监视程序运行过程中的动作,可用于性能监控。


procexp.exe
相当于升级版的任务管理器,可以查看加载模块,模块查找,线程列表(含CPU百分比),
创建dump,查看进程树,进程权限安全token,创建dump,等。


autoruns.exe
查看系统,IE等的加载项。


Dbgview.exe
查看调试端口输出。


1.2 其它工具
windbg:不解释,可用于双机调试,支持pipe,TCP等。
vs:vs也支持双击调试的,只需要拷贝一个东西到目标机上。
IDA:主要用于静态分析。
ollydbg:不解释。
spy++:窗口窥视器。
myspy:也是个窥视器,其中查看IE窗口(IWebBrowser2的宿主窗口)很方便,以及一些窗口,进程,调试输出的窥视功能。
processhacker:作为procexp.exe的补充。
Total Uninstall:观察应用程序对系统配置等的改变,比如对比注册表。
Unlocker:解除文件占用。
Depends.exe:观察模块对DLL的依赖,模块的导出,导入。
PE Explorer:PE工具。
sqliteadmin:可以查看sqlite的数据库。
Cookie Admin:查看cookie。


抓包,网络数据分析:
Microsoft Network Monitor,fiddler,wireshark,httpanalyzer。


1.3 调试必读书目
张银奎《软件调试》


1.4 调试参考书目
《windows高级调试》、《黑客反汇编揭秘》、《c++反汇编与逆向分析技术揭秘》
《windows核心编程》、《深入理解windows操作系统》、《windows内核情景分析》、《逆向工程核心原理》
作者:baihacker 发表于2014-7-30 23:00:35 原文链接
阅读:110 评论:0 查看评论

程序员对索引的误解

$
0
0

1、索引中最常见的就是B树索引,B树索引的实现与二叉查找树相似,但是B的意思不是binary,而是balance(平衡)。 

2、B树索引上的每个结点都是一个块,有叶子块和分支块之分。块中的数据包括各个索引以及一个rowid。走索引查询时,会按照树的分支将需要查询数据路径上的相应的分支块和叶子块读到内存。 

3、B树索引不存在非唯一性条目,在一个非唯一性索引中,Oracle会把rowid作为一个额外的列追加到键上,使得键唯一。非唯一性索引,会先按索引键值排序,然后按rowid升序排序。 

4、B树索引时高度平衡的,大多数情况,B树索引的高度都是2或3,即使索引数百万行记录也是如此。这说明,一般情况,在索引中找到键值只需2到3次IO(2到3个数据块)。 

5、通常有两种使用索引的方法: 
 (1)索引用于访问表中的行,访问表中很少一部分行。 
这是DBA给的经验值: 
小表(记录数小于10000行的表):筛选比例<10%; 
大表:(筛选返回记录数)<(表总记录数*单条记录长度)/10000/16 
      单条记录长度≈字段平均内容长度之和+字段数*2 
(2)索引用于回答一个查询,索引中的信息足够回答整个查询。

 

6、不能视图加索引,视图只是存储了一种查询,具体的访问还是要访问基表。

 

7、B树索引不会存储null的条目。

 

8、外键上要加索引,否则容易造成死锁。

 

9、组合索引,查询时只使用单一或没有加上全部的索引条件的查询。

 

10、count查询默认是会走B树索引的,但是如果索引建在可能为null的字段上,则不会走索引查询(因为B树索引不能允许null,使用索引查出的count和使用表查询出来的count可能1出现不一致情况)。

 

11、索引列上使用函数或使用数据类型转化函数,将会不走索引查询。

 

12、优化器拒绝使用索引查询,优化器绝大多数时候是英明的,不要贸然强制使用索引。

 

13、没有将最有差别的列放在索引最前面会使索引更小或更有效率的说法,实际上,如果使用索引键压缩,情况恰恰相反(即把没有差别的列放在索引最前面最优的做法)。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



五个Redis使用者需要注意的地方

$
0
0

下面内容来源于Quora上的 一个提问,问题是使用Redis需要避免的五个问题。而回答中超出了五个问题的范畴,描述了五个使用Redis的注意事项。如果你在使用或者考虑使用Redis,可能你可以学习一下下面的一些建议,避免一下提到的问题。

1.使用key值前缀来作命名空间

虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。所以用前缀作为命名空间可能会更明智一点。

另外,在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现,直接在代码里写前缀的做法要严格避免,这样可维护性实在太差了。

2.创建一个类似 ”registry” 的key用于标记key使用情况

为了更好的管理你的key值的使用,比如哪一类key值是属于哪个业务的,你通常会在内部wiki或者什么地方创建一个文档,通过查询这个文档,我们能够知道Redis中的key都是什么作用。

与之结合,一个推荐的做法是,在Redis里面保存一个registry值,这个值的名字可以类似于 __key_registry__ 这样的,这个key对应的value就是你文档的位置,这样我们在使用Redis的时候,就能通过直接查询这个值获取到当前Redis的使用情况了。

3.注意垃圾回收

Redis是一个提供持久化功能的内存数据库,如果你不指定上面值的过期时间,并且也不进行定期的清理工作,那么你的Redis内存占用会越来越大,当有一天它超过了系统可用内存,那么swap上场,离性能陡降的时间就不远了。所以在Redis中保存数据时,一定要预先考虑好数据的生命周期,这有很多方法可以实现。

比如你可以采用Redis自带的过期时间为你的数据设定过期时间。但是自动过期有一个问题,很有可能导致你还有大量内存可用时,就让key过期去释放内存,或者是内存已经不足了key还没有过期。

如果你想更精准的控制你的数据过期,你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,你可以轻松地找到最老的数据,并且从最老的数据开始进行删除,一直删除到你的空间足够为止。

4.设计好你的Sharding机制

Redis目前并不支持Sharding,但是当你的数据量超过单机内存时,你不得不考虑Sharding的事(注意:Slave不是用来做Sharding操作的,只是数据的一个备份和读写分离而已)。

所以你可能需要考虑好数据量大了后的分片问题,比如你可以在只有一台机器的时候就在程序上设定一致性hash机制,虽然刚开始所有数据都hash到一台机器,但是当你机器越加越多的时候,你就只需要迁移少量的数据就能完成了。

5.不要有个锤子看哪都是钉子

当你使用Redis构建你的服务的时候,一定要记住,你只是找了一个合适的工具来实现你需要的功能。而不是说你在用Redis构建一个服务,这是很不同的,你把Redis当作你很多工具中的一个,只在合适使用的时候再使用它,在不合适的时候选择其它的方法。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




Android应用自动更新功能的代码实现

$
0
0

Android应用自动更新功能的代码实现

 

由于Android项目开源所致,市面上出现了N多安卓软件市场。为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量。因此我们有必要给我们的Android应用增加自动更新的功能。

既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息:

  1. <update>  
  2.     <version>2</version>  
  3.     <name>baidu_xinwen_1.1.0</name>  
  4.     <url>http://gdown.baidu.com/data/wisegame/f98d235e39e29031/baiduxinwen.apk</url>  
  5. </update>  

在这里我使用的是XML文件,方便读取。由于XML文件内容比较少,因此可通过DOM方式进行文件的解析:

  1. public class ParseXmlService  
  2. {  
  3.     public HashMap<String, String> parseXml(InputStream inStream) throws Exception  
  4.     {  
  5.         HashMap<String, String> hashMap = new HashMap<String, String>();  
  6.           
  7.         // 实例化一个文档构建器工厂  
  8.         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
  9.         // 通过文档构建器工厂获取一个文档构建器  
  10.         DocumentBuilder builder = factory.newDocumentBuilder();  
  11.         // 通过文档通过文档构建器构建一个文档实例  
  12.         Document document = builder.parse(inStream);  
  13.         //获取XML文件根节点  
  14.         Element root = document.getDocumentElement();  
  15.         //获得所有子节点  
  16.         NodeList childNodes = root.getChildNodes();  
  17.         for (int j = 0; j < childNodes.getLength(); j++)  
  18.         {  
  19.             //遍历子节点  
  20.             Node childNode = (Node) childNodes.item(j);  
  21.             if (childNode.getNodeType() == Node.ELEMENT_NODE)  
  22.             {  
  23.                 Element childElement = (Element) childNode;  
  24.                 //版本号  
  25.                 if ("version".equals(childElement.getNodeName()))  
  26.                 {  
  27.                     hashMap.put("version",childElement.getFirstChild().getNodeValue());  
  28.                 }  
  29.                 //软件名称  
  30.                 else if (("name".equals(childElement.getNodeName())))  
  31.                 {  
  32.                     hashMap.put("name",childElement.getFirstChild().getNodeValue());  
  33.                 }  
  34.                 //下载地址  
  35.                 else if (("url".equals(childElement.getNodeName())))  
  36.                 {  
  37.                     hashMap.put("url",childElement.getFirstChild().getNodeValue());  
  38.                 }  
  39.             }  
  40.         }  
  41.         return hashMap;  
  42.     }  
  43. }  

通过parseXml()方法,我们可以获取服务器上应用的版本、文件名以及下载地址。紧接着我们就需要获取到我们手机上应用的版本信息:

  1. /** 
  2.  * 获取软件版本号 
  3.  *  
  4.  * @param context 
  5.  * @return 
  6.  */  
  7. private int getVersionCode(Context context)  
  8. {  
  9.     int versionCode = 0;  
  10.     try  
  11.     {  
  12.         // 获取软件版本号,  
  13.         versionCode = context.getPackageManager().getPackageInfo("com.szy.update", 0).versionCode;  
  14.     } catch (NameNotFoundException e)  
  15.     {  
  16.         e.printStackTrace();  
  17.     }  
  18.     return versionCode;  
  19. }  

通过该方法我们获取到的versionCode对应AndroidManifest.xml下android:versionCode。android:versionCode和android:versionName两个属性分别表示版本号,版本名称。versionCode是整数型,而versionName是字符串。由于versionName是给用户看的,不太容易比较大小,升级检查时,就可以检查versionCode。把获取到的手机上应用版本与服务器端的版本进行比较,应用就可以判断处是否需要更新软件。

处理流程


处理代码

  1. package com.szy.update;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.net.HttpURLConnection;  
  8. import java.net.MalformedURLException;  
  9. import java.net.URL;  
  10. import java.util.HashMap;  
  11.   
  12. import android.app.AlertDialog;  
  13. import android.app.Dialog;  
  14. import android.app.AlertDialog.Builder;  
  15. import android.content.Context;  
  16. import android.content.DialogInterface;  
  17. import android.content.Intent;  
  18. import android.content.DialogInterface.OnClickListener;  
  19. import android.content.pm.PackageManager.NameNotFoundException;  
  20. import android.net.Uri;  
  21. import android.os.Environment;  
  22. import android.os.Handler;  
  23. import android.os.Message;  
  24. import android.view.LayoutInflater;  
  25. import android.view.View;  
  26. import android.widget.ProgressBar;  
  27. import android.widget.Toast;  
  28.   
  29. /** 
  30.  *@author coolszy 
  31.  *@date 2012-4-26 
  32.  *@blog http://blog.92coding.com 
  33.  */  
  34.   
  35. public class UpdateManager  
  36. {  
  37.     /* 下载中 */  
  38.     private static final int DOWNLOAD = 1;  
  39.     /* 下载结束 */  
  40.     private static final int DOWNLOAD_FINISH = 2;  
  41.     /* 保存解析的XML信息 */  
  42.     HashMap<String, String> mHashMap;  
  43.     /* 下载保存路径 */  
  44.     private String mSavePath;  
  45.     /* 记录进度条数量 */  
  46.     private int progress;  
  47.     /* 是否取消更新 */  
  48.     private boolean cancelUpdate = false;  
  49.   
  50.     private Context mContext;  
  51.     /* 更新进度条 */  
  52.     private ProgressBar mProgress;  
  53.     private Dialog mDownloadDialog;  
  54.   
  55.     private Handler mHandler = new Handler()  
  56.     {  
  57.         public void handleMessage(Message msg)  
  58.         {  
  59.             switch (msg.what)  
  60.             {  
  61.             // 正在下载  
  62.             case DOWNLOAD:  
  63.                 // 设置进度条位置  
  64.                 mProgress.setProgress(progress);  
  65.                 break;  
  66.             case DOWNLOAD_FINISH:  
  67.                 // 安装文件  
  68.                 installApk();  
  69.                 break;  
  70.             default:  
  71.                 break;  
  72.             }  
  73.         };  
  74.     };  
  75.   
  76.     public UpdateManager(Context context)  
  77.     {  
  78.         this.mContext = context;  
  79.     }  
  80.   
  81.     /** 
  82.      * 检测软件更新 
  83.      */  
  84.     public void checkUpdate()  
  85.     {  
  86.         if (isUpdate())  
  87.         {  
  88.             // 显示提示对话框  
  89.             showNoticeDialog();  
  90.         } else  
  91.         {  
  92.             Toast.makeText(mContext, R.string.soft_update_no, Toast.LENGTH_LONG).show();  
  93.         }  
  94.     }  
  95.   
  96.     /** 
  97.      * 检查软件是否有更新版本 
  98.      *  
  99.      * @return 
  100.      */  
  101.     private boolean isUpdate()  
  102.     {  
  103.         // 获取当前软件版本  
  104.         int versionCode = getVersionCode(mContext);  
  105.         // 把version.xml放到网络上,然后获取文件信息  
  106.         InputStream inStream = ParseXmlService.class.getClassLoader().getResourceAsStream("version.xml");  
  107.         // 解析XML文件。 由于XML文件比较小,因此使用DOM方式进行解析  
  108.         ParseXmlService service = new ParseXmlService();  
  109.         try  
  110.         {  
  111.             mHashMap = service.parseXml(inStream);  
  112.         } catch (Exception e)  
  113.         {  
  114.             e.printStackTrace();  
  115.         }  
  116.         if (null != mHashMap)  
  117.         {  
  118.             int serviceCode = Integer.valueOf(mHashMap.get("version"));  
  119.             // 版本判断  
  120.             if (serviceCode > versionCode)  
  121.             {  
  122.                 return true;  
  123.             }  
  124.         }  
  125.         return false;  
  126.     }  
  127.   
  128. /** 
  129.  * 获取软件版本号 
  130.  *  
  131.  * @param context 
  132.  * @return 
  133.  */  
  134. private int getVersionCode(Context context)  
  135. {  
  136.     int versionCode = 0;  
  137.     try  
  138.     {  
  139.         // 获取软件版本号,对应AndroidManifest.xml下android:versionCode  
  140.         versionCode = context.getPackageManager().getPackageInfo("com.szy.update", 0).versionCode;  
  141.     } catch (NameNotFoundException e)  
  142.     {  
  143.         e.printStackTrace();  
  144.     }  
  145.     return versionCode;  
  146. }  
  147.   
  148.     /** 
  149.      * 显示软件更新对话框 
  150.      */  
  151.     private void showNoticeDialog()  
  152.     {  
  153.         // 构造对话框  
  154.         AlertDialog.Builder builder = new Builder(mContext);  
  155.         builder.setTitle(R.string.soft_update_title);  
  156.         builder.setMessage(R.string.soft_update_info);  
  157.         // 更新  
  158.         builder.setPositiveButton(R.string.soft_update_updatebtn, new OnClickListener()  
  159.         {  
  160.             @Override  
  161.             public void onClick(DialogInterface dialog, int which)  
  162.             {  
  163.                 dialog.dismiss();  
  164.                 // 显示下载对话框  
  165.                 showDownloadDialog();  
  166.             }  
  167.         });  
  168.         // 稍后更新  
  169.         builder.setNegativeButton(R.string.soft_update_later, new OnClickListener()  
  170.         {  
  171.             @Override  
  172.             public void onClick(DialogInterface dialog, int which)  
  173.             {  
  174.                 dialog.dismiss();  
  175.             }  
  176.         });  
  177.         Dialog noticeDialog = builder.create();  
  178.         noticeDialog.show();  
  179.     }  
  180.   
  181.     /** 
  182.      * 显示软件下载对话框 
  183.      */  
  184.     private void showDownloadDialog()  
  185.     {  
  186.         // 构造软件下载对话框  
  187.         AlertDialog.Builder builder = new Builder(mContext);  
  188.         builder.setTitle(R.string.soft_updating);  
  189.         // 给下载对话框增加进度条  
  190.         final LayoutInflater inflater = LayoutInflater.from(mContext);  
  191.         View v = inflater.inflate(R.layout.softupdate_progress, null);  
  192.         mProgress = (ProgressBar) v.findViewById(R.id.update_progress);  
  193.         builder.setView(v);  
  194.         // 取消更新  
  195.         builder.setNegativeButton(R.string.soft_update_cancel, new OnClickListener()  
  196.         {  
  197.             @Override  
  198.             public void onClick(DialogInterface dialog, int which)  
  199.             {  
  200.                 dialog.dismiss();  
  201.                 // 设置取消状态  
  202.                 cancelUpdate = true;  
  203.             }  
  204.         });  
  205.         mDownloadDialog = builder.create();  
  206.         mDownloadDialog.show();  
  207.         // 现在文件  
  208.         downloadApk();  
  209.     }  
  210.   
  211.     /** 
  212.      * 下载apk文件 
  213.      */  
  214.     private void downloadApk()  
  215.     {  
  216.         // 启动新线程下载软件  
  217.         new downloadApkThread().start();  
  218.     }  
  219.   
  220.     /** 
  221.      * 下载文件线程 
  222.      *  
  223.      * @author coolszy 
  224.      *@date 2012-4-26 
  225.      *@blog http://blog.92coding.com 
  226.      */  
  227.     private class downloadApkThread extends Thread  
  228.     {  
  229.         @Override  
  230.         public void run()  
  231.         {  
  232.             try  
  233.             {  
  234.                 // 判断SD卡是否存在,并且是否具有读写权限  
  235.                 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))  
  236.                 {  
  237.                     // 获得存储卡的路径  
  238.                     String sdpath = Environment.getExternalStorageDirectory() + "/";  
  239.                     mSavePath = sdpath + "download";  
  240.                     URL url = new URL(mHashMap.get("url"));  
  241.                     // 创建连接  
  242.                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  243.                     conn.connect();  
  244.                     // 获取文件大小  
  245.                     int length = conn.getContentLength();  
  246.                     // 创建输入流  
  247.                     InputStream is = conn.getInputStream();  
  248.   
  249.                     File file = new File(mSavePath);  
  250.                     // 判断文件目录是否存在  
  251.                     if (!file.exists())  
  252.                     {  
  253.                         file.mkdir();  
  254.                     }  
  255.                     File apkFile = new File(mSavePath, mHashMap.get("name"));  
  256.                     FileOutputStream fos = new FileOutputStream(apkFile);  
  257.                     int count = 0;  
  258.                     // 缓存  
  259.                     byte buf[] = new byte[1024];  
  260.                     // 写入到文件中  
  261.                     do  
  262.                     {  
  263.                         int numread = is.read(buf);  
  264.                         count += numread;  
  265.                         // 计算进度条位置  
  266.                         progress = (int) (((float) count / length) * 100);  
  267.                         // 更新进度  
  268.                         mHandler.sendEmptyMessage(DOWNLOAD);  
  269.                         if (numread <= 0)  
  270.                         {  
  271.                             // 下载完成  
  272.                             mHandler.sendEmptyMessage(DOWNLOAD_FINISH);  
  273.                             break;  
  274.                         }  
  275.                         // 写入文件  
  276.                         fos.write(buf, 0, numread);  
  277.                     } while (!cancelUpdate);// 点击取消就停止下载.  
  278.                     fos.close();  
  279.                     is.close();  
  280.                 }  
  281.             } catch (MalformedURLException e)  
  282.             {  
  283.                 e.printStackTrace();  
  284.             } catch (IOException e)  
  285.             {  
  286.                 e.printStackTrace();  
  287.             }  
  288.             // 取消下载对话框显示  
  289.             mDownloadDialog.dismiss();  
  290.         }  
  291.     };  
  292.   
  293.     /** 
  294.      * 安装APK文件 
  295.      */  
  296.     private void installApk()  
  297.     {  
  298.         File apkfile = new File(mSavePath, mHashMap.get("name"));  
  299.         if (!apkfile.exists())  
  300.         {  
  301.             return;  
  302.         }  
  303.         // 通过Intent安装APK文件  
  304.         Intent i = new Intent(Intent.ACTION_VIEW);  
  305.         i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");  
  306.         mContext.startActivity(i);  
  307.     }  
  308. }  

 

效果图

检查模拟器SDCARD是否存在下载文件:





已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Spring MVC handler method 参数绑定常用的注解

$
0
0
参考链接:http://csjava.blog.163.com/blog/static/1904700332012102742025948/?COLLCC=3184617125&COLLCC=1892771493&COLLCC=1691444901

请求路径上有个id的变量值,可以通过@PathVariable来获取  @RequestMapping(value = "/page/{id}", method = RequestMethod.GET)
@RequestParam用来获得静态的URL请求入参     spring注解时action里用到。
简介:

handler method 参数绑定常用的注解,我们根据他们处理的Request的不同内容部分分为四类:(主要讲解常用类型)

A、处理requet uri 部分(这里指uri template中variable,不含queryString部分)的注解:   @PathVariable;

B、处理request header部分的注解:   @RequestHeader, @CookieValue;

C、处理request body部分的注解:@RequestParam,  @RequestBody;

D、处理attribute类型的注解: @SessionAttributes, @ModelAttribute;


1、 @PathVariable

当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。

示例代码:

[java] view plaincopyprint?

    @Controller 
    @RequestMapping("/owners/{ownerId}") 
    public class RelativePathUriTemplateController { 
     
      @RequestMapping("/pets/{petId}") 
      public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {     
        // implementation omitted  
      } 
    } 

上面代码把URI template 中变量 ownerId的值和petId的值,绑定到方法的参数上。若方法参数名称和需要绑定的uri template中变量名称不一致,需要在@PathVariable("name")指定uri template中的名称。

2、 @RequestHeader、@CookieValue

@RequestHeader 注解,可以把Request请求header部分的值绑定到方法的参数上。

示例代码:

这是一个Request 的header部分:

    Host                    localhost:8080 
    Accept                  text/html,application/xhtml+xml,application/xml;q=0.9 
    Accept-Language         fr,en-gb;q=0.7,en;q=0.3 
    Accept-Encoding         gzip,deflate 
    Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7 
    Keep-Alive              300 

    @RequestMapping("/displayHeaderInfo.do") 

    public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, 
                                  @RequestHeader("Keep-Alive") long keepAlive)  { 
    } 

上面的代码,把request header部分的 Accept-Encoding的值,绑定到参数encoding上了, Keep-Alive header的值绑定到参数keepAlive上。


@CookieValue 可以把Request header中关于cookie的值绑定到方法的参数上。

例如有如下Cookie值:

    JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

参数绑定的代码:

    @RequestMapping("/displayHeaderInfo.do") 
    public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie)  { 
    } 

即把JSESSIONID的值绑定到参数cookie上。


3、@RequestParam, @RequestBody

@RequestParam

A) 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String--> 简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值;

B)用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST;

C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定;

示例代码:

    @Controller 
    @RequestMapping("/pets") 
    @SessionAttributes("pet") 
    public class EditPetForm { 
        @RequestMapping(method = RequestMethod.GET) 
     public String setupForm(@RequestParam("petId") int petId, ModelMap model) { 
           Pet pet = this.clinic.loadPet(petId); 
       model.addAttribute("pet", pet); 
       return "petForm"; 
       } 

@RequestBody

该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等;

它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。

因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api;

示例代码:

[java] view plaincopyprint?

    @RequestMapping(value = "/something", method = RequestMethod.PUT) 
    public void handle(@RequestBody String body, Writer writer) throws IOException { 
      writer.write(body); 
    } 
    

4、@SessionAttributes, @ModelAttribute

@SessionAttributes:

该注解用来绑定HttpSession中的attribute对象的值,便于在方法中的参数里使用。

该注解有value、types两个属性,可以通过名字和类型指定要使用的attribute 对象;

示例代码:

[java] view plaincopyprint?

    @Controller 
    @RequestMapping("/editPet.do") 
    @SessionAttributes("pet") 
    public class EditPetForm { 
        // ...  
    } 

@ModelAttribute

该注解有两个用法,一个是用于方法上,一个是用于参数上;

用于方法上时:  通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;

用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于:

A) @SessionAttributes 启用的attribute 对象上;

B) @ModelAttribute 用于方法上时指定的model对象;

C) 上述两种情况都没有时,new一个需要绑定的bean对象,然后把request中按名称对应的方式把值绑定到bean中。


用到方法上@ModelAttribute的示例代码:

[java] view plaincopyprint?

    // Add one attribute  
    // The return value of the method is added to the model under the name "account"  
    // You can customize the name via @ModelAttribute("myAccount")  
     
    @ModelAttribute 
    public Account addAccount(@RequestParam String number) { 
        return accountManager.findAccount(number); 
    }
    这种方式实际的效果就是在调用@RequestMapping的方法之前,为request对象的model里put(“account”, Account);


用在参数上的@ModelAttribute示例代码:

[java] view plaincopyprint?

    @RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) 
    public String processSubmit(@ModelAttribute Pet pet) { 
        
    } 

首先查询 @SessionAttributes有无绑定的Pet对象,若没有则查询@ModelAttribute方法层面上是否绑定了Pet对象,若没有则将URI template中的值按对应的名称绑定到Pet对象的各属性上。

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Cookiel劫持测试工具 – Cookie Injecting Tools

$
0
0

‍‍

‍Cookie Injecting Tools 是一款简单的开源cookie利用工具,是Chrome浏览器上开发的一个扩展插件,能够灵活地进行SQL注入测试,编辑以及添加删除COOKIE,界面简洁,易于使用‍‍。‍‍‍‍

‍‍

1.下载和安装

‍‍‍‍下载

可以直接下载打包好的CRX文件,源码就包含其中,当然也可以直接下载源码运行‍‍
https://github.com/lfzark/cookie-injecting-tools/

‍‍‍‍ 有两种方式‍‍

‍‍‍‍‍‍1.1 把.crx改成.rar,这样你就得到一个rar压缩文件,然后右键解压这个压缩文件,你会得到一个文件夹,可以看到里面的源码
然后打开chrome://settings/extensions,点击上方的“载入正在开发的扩展程序”,选中你刚刚解压出来的那一整个文件夹(不要选里面的子文件夹)然后点确定,就安装成功了。‍‍‍‍‍‍

‍‍1.2 ‍‍浏览器地址栏输入chrome://extensions/ 进入扩展程序界面,然后将 cookie_injecting_tools 1.0.0.crx拖进去‍‍

2.使用

‍‍‍‍2.1.伪造Cookie‍‍

将劫持到的Cookie粘贴到如下图文本框中,单击 按钮,如果成功 提示Inject Success. 如图中所示。恭喜你,伪造成功。

‍‍注意:支持的cookie格式是以 分号 ; 为分隔符,请确保,否则会失败。‍‍‍‍

Alt text


‍‍‍‍2.2. 查看Cookie ‍‍

输入你想查看cookie所在域名的关键字,比如google,会自动列出相关的cookie信息‍‍

Alt text

‍‍‍‍2.3.编辑,添加 和删除Cookies‍‍

单击Edit编辑按钮 跳转到编辑页面如下图‍‍

Alt text


参数解释


url 

string 

与待设置cookie相关的URL。该值影响所创建cookie的默认域名与路径值。如果清单文件中没有设置这个URL对应的主机权限,那么这个API调用会失败

必须

name 

string 

cookie名称,默认为空值

可选

value 

string 

cookie的值,默认为空值

可选

domain 

string 

cookie的域名。如果未指定,则该cookie是host-only cookie。

可选

path 

string 

cookie的路径。默认是url参数的路径部分。

可选

secure 

boolean 

是否cookie标记为保密。默认为false。

可选

httpOnly 

boolean 

cookie被标记为HttpOnly。默认为false。

可选

expirationDate 

number 

cookie的过期时间,用从UNIX epoch开始计的秒数表示。如果未指定,该cookie是一个会话cookie。

可选

‍‍‍‍‍‍‍‍如果要删除,只需填前两项 url和name;‍‍

成功会出现提示delete successfully‍‍‍‍‍‍

demo4.png

‍‍‍‍‍‍

‍‍原创 by Ark  https://github.com/lfzark/cookie-injecting-tools/‍

freebuf首发,转载请注明出处‍‍‍‍‍‍

jQuery插件编写之三部曲

$
0
0

1、选择一个jQuery框架,如:

/*!
 * jQuery.myPlugin
 *
 * @version  1.0.0
 * @date     2014/07/16
 * @author   Lime
 * @license  
 */
(function (jQuery) {

	//定义你的属性名myPlugin
    jQuery.fn.myPlugin = function (options) { 
		
		//替换默认参数
        var options = jQuery.extend({}, jQuery.fn.myPlugin.defaults, options);
        
		//使用return this.each运用在多个控件上并实现链式操作 
        return this.each(function () {
              //在这里实现你的方法
        });
    };

    //使用暴露方式设置插件默认参数,这对于让插件的使用者更容易用较少的代码覆盖和修改插件默认设置
    jQuery.fn.myPlugin.defaults = {
    };

})(jQuery);

 2、添加引用编写好的插件

 首先给你的插件起个名,推荐命名方法为:jquery.[插件名].js,如上面可以命名为jquery.myPlugin.js

 然后在你的项目里添加引用<script src="jquery.myPlugin.js" type="text/javascript"></script>,当然在这之前你必须要引用jquery库。

 

3、插件的使用

<script type="text/javascript"> 
	$(document).ready(function () { 
		$("#div1").myPlugin(); 
	}); </script> 

 有木有发现简直简单到爆啊,是的,就这么简单!

 

附:select下拉绑定jQuery插件

/*!
 * jQuery.selectDataBind
 *
 * @version  1.0.0
 * @date     2014/07/16
 * @author   Lime<zhrjin@163.com>
 * @license  
 */
(function (jQuery) {
    jQuery.fn.selectDataBind = function (options) {
        var options = jQuery.extend({}, jQuery.fn.selectDataBind.defaults, options);
        return this.each(function () {
            var jOption = options.options;
            var jVaule = options.values;
            var bEmpty = options.empty;
            var j = 0;
            if (bEmpty) {
                this.options[0] = new Option("", "");
                j++;
            }
            for (var i = 0; i < jOption.length; i++) {
                var objOption = new Option(jOption[i], jVaule[i]);
                this.options[j++] = objOption;
            }
        });
    };
    jQuery.fn.selectDataBind.defaults = {
        options: [],
        values: [],
        empty: true
    };
})(jQuery);

 

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



铁路控制系统初探

$
0
0

在探讨控制系统前,先说说控制系统的需求:


1> 控制铁路轨道上各种设备的工作

2> 协调各设备之间协作


这2点有些过于抽象了,任何一个系统都有这些特性。


从系统设计的角度, 要做2方面的事情。

1> 确定控制系统与外部设备间的通信接口与协议

2> 确定内部系统构成与功能。


1.1 关于通信协议与接口, 行业上通用的是modbus接口, 参考网站: http://libmodbus.org/

另外一些协议均是与公司有关,设计到保密协议,无法深入讨论。个人感觉最深的是, 均是udp协议,这颠覆了我一直认为tcp更安全的偏见。

通过学习这些接口,也学会了如何利用sequence ID 与Time设计一个稳定安全的内部通讯协议。


1.2 关于数据,整个控制系统使用的均是开关信号, 将它看成一个BitTable, 数学上理解就很简单了。


2.1 关于控制系统

基本上分为2个系统,一个是监控与log系统, 一个是逻辑控制系统。

第一个系统, 和通用系统没啥区别,用linux构建,完成系统状态的监控与显示。

第2个是逻辑控制系统, 基本是基于PLC设计原理。

基本思路: 定周期Timer触发处理流程

                     读取各系统状态与输入设备缓冲区状态

                     依次执行PLC控制逻辑

                    读取输出状态,通过外设继电器控制各种设备状态。


难点: 如何设计控制规则

了解下来,基本工具类似Ladder, 也就是常说的梯形图, 关于Ladder参考 (http://www.ladder-logic.com/hello-world/)

通过工具设计好控制逻辑后,将控制逻辑导出,并通过通信协议,传输到控制系统。

这样,整个控制系统就是可编程的了。


系统从技术上来说就2个难点, 1, 如何设计控制逻辑, 2 如何通过GUI,显示整个控制系统,并提供良好的人机交互。


2.2 安全

整个系统的稳定性是靠系统冗余来解决的。即所有设备和通信网络均由2个, 这样,假设单线的故障概率为0.001, 整个系的故障概率就为 (0.001)的4次方,

很低了。


这里有个难点是,主从系统如何切换,如何处理冗余系统, 这就又回到第一个问题,通信协议的设计了。




作者:zlf_jack 发表于2014-8-2 19:23:41 原文链接
阅读:73 评论:0 查看评论

三种Div高度自适应的方法

$
0
0

让DIV高度自适应,这是在网页设计中常遇到的问题,为了给大家提供参考,这里提供3种div高度自适应的方法:一是JS法、二是背景图填充法、三是“补丁大法”(比较变态)。

1、JS法

代码如下。原理:用JS判断左右DIV的高度,若不一致则设为一致。 框架资源分享

Java代码   收藏代码
  1. <div style="width:500px;background:#cccccc;height:0px;">  
  2. <div id="right" style="width:380%;height:100%;float:left;border:1px solid #265492;">left</div>  
  3. <div id="left" style="width:60%;;float:left;background:#376037;">  
  4. right<br>  
  5. right<br>  
  6. right<br>  
  7. right<br>  
  8. right<br>  
  9. right<br>  
  10. right<br>  
  11. </div>  
  12. </div>  
  13. <script type="text/javascript">  
  14. <!--   
  15. var a=document.getElementById("left");  
  16. var b=document.getElementById("right");  
  17. if(a.clientHeight<b.clientHeight){  
  18.   a.style.height=b.clientHeight+"px";  
  19. }else{   
  20.   b.style.height=a.clientHeight+"px";  
  21. }  
  22. -->  
  23. </script>  

 

 

这是大站用得比较多的方法,如163等,研究了一下,结果如下。2、背景图填充法

这里是给父DIV设置了背景图片填充,所有DIV都不设高度。 前端资源分享

HTML代码:

Java代码   收藏代码
  1. <div class="endArea">  
  2. <div class="col1">第一列 左边正文</div>  
  3. <div class="col3">第二列 右边<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />字字</div>  
  4. <div class="col2">第三列 中间图片</div>  
  5. <div class="clear"></div>  
  6. </div>  

 

CSS代码:

Java代码   收藏代码
  1. .endArea{margin:0 auto; width:960px; background:url(http://cimg2.163.com/cnews/img07/end_n_bg1.gif); clear:both;}  
  2. .endArea .col1{float:left; width:573px; }  
  3. .endArea .col2{float:left; width:25px; }  
  4. .endArea .col3{float:right; width:362px;}  

 

3、补丁大法

就是“隐藏容器溢出”和“正内补丁”和“负外补丁”结合的方法。比较另类的方法,在IE6、IE7、FF3下测试通过。要点:

1、父DIV设置 overflow:hidden; 框架资源分享

2、对要高度自适应的DIV设置 padding-bottom:100000px;margin-bottom:-100000px; 两列或多列同理。

代码如下:

Java代码   收藏代码
  1. <html>  
  2. <head>  
  3. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />  
  4. <title>Div高度自适应</title>  
  5. <style type="text/css">  
  6. #wrap{overflow:hidden;}  
  7. #sidebar_left,#sidebar_right{padding-bottom:100000px;margin-bottom:-100000px;}  
  8. </style>  
  9. </head>  
  10. <body>  
  11. <div id="wrap" style="width:300px; background:#FFFF00;">  
  12. <div id="sidebar_left" style="float:left;width:100px; background:#777;">居左</div>  
  13. <div id="sidebar_mid" style="float:left;width:100px; background:#999;">  
  14. 居中<br />  
  15. 居中<br />  
  16. 居中<br />  
  17. 居中<br />  
  18. 居中<br />  
  19. 居中<br />  
  20. 居中<br />  
  21. </div><div id="sidebar_right" style="float:right;width:100px; background:#888;">居右</div></div>  
  22. </body>  
  23. </html>  

 

以上三种方法都可以解决Div高度自适应,请根据你自己的需要,三选一


作者:ariss123 发表于2014-8-2 15:20:34 原文链接
阅读:93 评论:0 查看评论

谈谈网站静态化

$
0
0

写在前头

静态化是解决减轻网站压力,提高网站访问速度的常用方案,但在强调交互的We2.0 时代,对静态化提出了更高的要求,静态不仅要能静,还要能动,下面我通过一个项目,谈谈网站静态化后的架构设计方案,同时和大家探讨一下,在开源产品大行其道,言架构必称MemberCache, Nginx,的时代,微软技术在网站架构设计中的运用.

静态化的设计原则和步骤

静态化是解决减轻网站压力,但是静态化也会带来一系列的问题,包括开发上复杂度的增加,维护难度的增加,运用不的当,更可能适得其反,而许多替代方案,比如页面缓存,如果运用得当,也能起到很好的效果,所以在开始之前,必须进行详细的考察,确定是否适合静态化,并制定适合的静态化方式,下面先介绍一下

l          考查读写比:

读写比,准确的说是读写负荷比,是否值得静态化的最终考虑,由于一般写入的压力明显大于读出的压力,如果写入太频繁,或者每次写入消耗的资源太多,都不能达到效果,我觉得读写比例10:1应该是个上限.具体情况需要根据自己的业务逻辑判断

 

l          确定页面呈现的内容是否适合静态化:

在设计方案时,必须详细考虑每个原型页面,找到页面上展示的信息,和他的更新方式,更新时机,更新频率,一定要注意那些不起眼的信息,他们可能左右你的设计,

比如:我们以CSDN的论坛的任意一篇帖子为例,进行分析



 

 

上面的帖子中呈现的内容主要是这样几块,帖子内容,回复内容,发帖人回复人的用户信息

n         帖子内容和回复内容在发帖时更新,发帖后用户可以修改其内容,更新频率高

n         用户信息,用户修改个人信息时可能会发生更改,用户等级增加时也可能发生更改,比如加星,更新频率低

n         回复数将每次回复后都要更改,更新频率高

n         设计时要注意细节,如上图中圈出来的部分,这些部分是怎么修改的,频率有多大,一个都不能放过.

l          确定生成方式:

在上面帖子一例中.每次更改都重新生成页面是不可取的,一篇比回复数多的帖子,需要的数据量是巨大的(每层楼的用户信息,回复内容),任何修改,都需要重新取出数据进行生成是不能允许的.一般除非你的页面基本不用更新,或者更新开销极小,(比如一段嵌入的广告代码)才能采用整体更新的方式,不然就需要我们找到合适的更新页面局部区域的方法:

一般有下面两个方法:

1)      正则修改法:

        比如,如果帖子中的回复数,html代码是这样
        <label>回复数<var id="replyCount">34</var></label>
        我们可以通过用下面正则来查找并替换计数
         (?<=id="replyCount">)\d{1,}

2)      页面区域分块:

把页面分成很多小块,在显示时组装起来,比如DotText就采用这个方法



 

 

这是一篇典型的Dottext blog页面,其中红色标定部分是一个独立的文件,而黄色框内的是脚本动态加载,这些部分在最终显示的时候组合起来,最终构成了一篇Blog,具体的组合方法也有多种,可以使用Include,也可以自己来实现.DotText就自己实现了一套加载机制

 

上面的两种方法并不孤立,并可以根据需要,配合使用

 

l          确定需要动态加载的信息:

页面上总有一些内容看起来不太适合静态化,最典型的是一些统计结果,比如如果你在做一个图书介绍页面,可能就会需要展示图书的当天综合评分,或者书籍排名,这些内容需要用脚本进行动态加载

既然做了静态化,就是希望减少服务器负载,动态加载的数据总是不得已而为之,有的时候在需求允许的情况下,我们在数据在实时性和性能方面做一些妥协,比如上面帖子中的用户星级和昵称,从数据实时性上说,当用户的星级增长,他发言的所有帖子都应该发生变化,所以应该用动态加载.然而其实上这些信息如果不发生变化,也无伤大雅,用户反而能够看到自己在多年前发帖时的级别和昵称.

现实中的项目

X网站是大型的电影资讯,电影社区,向外提供电影相关信息服务,以及用户社区,其中信息服务部分, 其中大部分页面属于信息呈现页,读取量比较大,百万级别pv,信息主要由编辑在后台发布,更新较少,但其页面上有大量的交互性的内容,比如评论,收藏列表,同时许多内容允许用户创造,比如上传图片,添加注释.交互内容的数量和交互的频繁程度,都超过了普通的咨询页面,这次调整,准备将其中访问量最大的几块:电影资料页,影人资料页,进行静态化,如果成功,还将运用到更多的频道,基本实现全站静态化

 

通过对页面设计和前一版本的分析,下面是具有挑战性的地方.这些特点基本使用于大多数web2.0的站点,很具有典型意义

 

l          页面生成的触发条件复杂

一般论坛中的帖子或者blog,更新方式比较单一:主要是由回复进行触发还有少数的修改动作,然而该网站一个页面上需要根据不同触发条件就有20多个, 比如光二级菜单:用户发布图片,删除图片,发布或者删除影片信息,发布或者修改视频,后台修改电影信息,都有可能触发

 

l          一个动作触发生成的页面可能很多而且相互交叠

每一个动作都会触发一系列的生成,并且不同动作可能都会涉及同一个页面或者区域的生成.

比如:用户给一步电影评分,需要生成评分更多页,评分统计更多页,首页右侧谁还关注此影片小区域,等等.用户收藏一个影片,也需要更新首页右侧谁还关注此影片小区域

 

l          触发频繁:

虽然不及某些更大规模的网站,但是由于涉及众多用户参与的内容,评论,收藏等等,触发点多,发生频度相当频繁

 

l          页面多,结构复杂,空间占用大:

通常,需要生成的页面规模是这样粗略估算的,Rn*P,Rn为资源数,P为每个资源的页面数,所谓资源,可以看做一个生成单位,其页面数可以简单看做发布一个资源,就需要生成其所有相关页面数量,比如:发布一个blog,就需要生成一个Blog页,同时还需要生成或者更新个人主页的blog列表,算上个人主页右侧的分类文章数的小块,也就是最多10来个页面或者区域,但是发布一个电影,其相关的页面至少有50个以上,而且有的页面还带有分页,一个信息比较丰富的电影,其页面竟可以达到千个以上,空间10~20M,而且资源总数也不少,电影80000左右,电影人虽然P值较少,但是总量确有几十万之巨,估计静态页面磁盘占用量几百个G

 

l          向下兼容

这是一个已有系统,旧系统的框框需要突破,但又没有时间,或者不能完全突破,比如Url,已经被收录到搜索引擎,就不能随便调整,还有一些地方,原本没有为静态生成考虑,另一些地方又需要兼容旧的设计.

 

l          多台前端Web

这种结构要求生成的文件可能需要分布到多个服务器(另一个方案是放在几台专用的机器上,等前端来取)

 

l          任务紧迫

架构讨论结束仪式六月初,离奥运开幕上线只有两月,也就是说所有底层框架实现,页面模板开发,调试测试,动作的整理,必须在7月底全部完成,按我原来估计,光实现这几块的上百个页面模板和填充方法,也需要那么长的时间

 

综合考虑上述因素,架构必须要有以下几个方面的特点

l         动作可以灵活扩展配置,某个动作对应哪些生成,应该可以配置,并且可以分组

l         文件必须有分发机制

l         分发和生成必须独立出来,并且支持分布式

l         各种的动作,必须转化为消息,发送到生成和分发服务器进行处理

l         针对同意资源频繁动作,在变量相同的情况下能够具有合并的能力

l         动作必须有记录

l         尽量考虑使用已有成熟技术,节省开发时间

下面是设计的第一个架构



 

 

用户的动作经过MSMQ [1]传入到生成分发中心(途中绿色箭头)进行处理,,处理中心接受到消息后,负责生成对应的页面或者页面区域,并将页面分发到各个服务器,负载均衡沿用以前的架构,采用微软的NLB [2]

 

之所以用MSMQ,就是看上了他提供的完整的消息存储恢复机制,这样我们能确保即使服务器down掉重启后,消息依然能正常处理,碰巧我们cms组的同事MSMQ非常熟悉,并且真准备在另外一个项目中使用类似的架构—于是一拍即合

 

页面采用分块存储,这样能保证生成时目标小,开销小,也能重用性,然后再藉由SSI [3](shtml include)进行整合,之所以采取这样的方案,而不采用Dottext的整合方式,是因为如果采用Dottext的方式,就必须走IIS和.Net的管道 [4],而据测试,经过管道和直接返回html性能有非常大的差异,而使用ssi,在性能上是一个折中,并且可以Light HTTPd等高性能web服务器

 

模板生成方式,采用了XSLT和另外一种自定义的模板(我的同事开发的机制,很有趣, 理论上能把传统模板替换的性能开销全部消除),生成的最终产物是shtml,之所以生成shtml是为了使用其ssi(Server Side Include)的特性,保证一定的灵活性,并实现热点数据的分离:某些页面上的部分可能会频繁更新和生成,而其它地方不变,或者某个部分是所有页面通用的(比如页头和页脚),较之php下常常使用smarty,生成php文件,虽然灵活性不如php,但是性能上不相上下,还略高.

 

但是这个设计的问题是动态内容和静态内容没有分开,生成的html页面,和动态页面都放在前端服务器上,通过负载均衡访问,也造成了分发服务器需要分发到多台服务器,网络IO效率较低,而且静态内容需要的磁盘空间很大,且小文件非常多,和动态页面混在一起不便于优化,所以第二个方案对生成的静态内容与动态内容使用不同的服务器

 

方案二:



 

 

我们把生成的静态文件单独放置,可以看到,前端增加Nginx,作为跳转,把电影,影人资料库的页面转向静态服务器,其他的调用转向动态服务器,这样我们就可以单独为静态服务器进行优化,比如采用更高效的服务器等等.

 

同时减少了文件分发的次数(甚至可以只分发到本机),提高生成分发的处理能力

 

更进一步,可以把图片服务分到另外一组机器上,使用独立的域名,比如img.xxx.com,这样可以有效的减少带宽

 

最终完整架构:

 



  

 

文件生成分发中心

下图是文件生成分发中心的工作流程图

 



 
生成服务对外只有一个输入,就是消息,一个输出:静态文件,内部根据消息,从配置文件中找到对应的生成方法,取出相应的模板,进行数据填充

 

分发服务主要吧生成服务产生的文件进行分发,分发到前端的N台服务器上,开始考虑得比较复杂,希望分发服务可以跨越协议(本地文件系统,局域网,http协议),跨越多种存储介质(文件系统,数据库),实际最后定下来基本是本地文件系统或者局域网传输

 

:上图中文件分发的部分也可以通过定制MogileFS,来实现分布式文件系统

 

马后炮:

总结起来,静态化除了对架构方面的影响,对开发和测试流程也有影响

对测试提出更高的要求:

因为一旦上线后,某个页面发现问题,即使是文字的修改,也需要重新生成许多页面,所以测试人员必须非常仔细,测试周期也需要延长

 

开发人员需要掌握模板语言

需要掌握一种模板预言,无论是Xslt还是自己开发的模板语言,都需要花一定的时间掌握

 

需要给第一次生成腾出足够时间:

如果不是新系统,那么数据迁移和生成的过程就比较痛苦,由于页面众多,第一次生成的过程可能需要以天来计算,在制定上线方案是就需要考虑到这个方面

 

Nginx作为前端的跳转,根据其他网站的经验,应该可以达到2-3万并发连接,但是使用之后,常常有卡壳的情况发生,具体症状为在浏览器中访问页面时,连接超时,或者一直不响应,此时Nginx连接数并不高,好在还有第一套方案可以备用,让我们有时间去解决这个问题,如果大家对这个问题有什么心得,欢迎交流

我的联系方式

MSN:yizhu2000@hotmail.com

Gtalk:yizhu2005@gmail.com

 

篇后:

在大型web开发上,我感到微软产品结构(包括微软开源社区的成果)在某些方面还存在一些不足:

 

高性能服务器选择太少

Linux下可以采用Light HTTPd,Nginx等诸多服务器,这些服务器在很多方面的表现会让Windows下唯一的选择--IIS相形见绌

 

分布式文件系统

微软及其社区没有比较著名的产品出现,Linux下有MogileFS

 

微软架构下,文件系统选择太少:

在Linux下我们可以选择诸如Ext3,ReiserFS,而Windows环境下,NTFS是唯一的选择,不过值得称道的是.NTFS的效率和稳定性都相当不错.

 

开源技术对windows版本的支持态度不积极

诸多在Linux下名声卓著的开源产品,又懒于为Windows提供相应的版本,或者提供的windows版本效果差强人意.使得采用微软服务器的厂商少了很多选择

 

现在的Web开发已经进入了各种技术大混合,大整合的时代,任何一个厂商都不可能涵盖所有方面,在后端架构和逻辑方面.Net和Java严谨,良好的编程风格,清晰的设计思路,较高的运行效率,以及稳定的配套服务支持,是其最大的优势,对主要擅长微软技术的Web工程师和架构师而言,应该增进对Linux及开源社区的了解,才能根据需求设计出合理的架构

 



[1] Message Queuing: A Scalable, Highly Available Load-Balancing Solution

http://msdn.microsoft.com/en-us/library/ms811052.aspx

[2] 网络负载平衡(NLB)详解,注意文章后给出的参考链接

http://blog.chinaitlab.com/user1/563173/archives/2007/132713.html

[3] 怎样使用ssi,及其语法:

http://blog.csdn.net/dadou2007/archive/2008/06/08/2521365.aspx

Nginx下的ssimodule

http://www.nginx.cn/NginxChsHttpSsiModule

[4] asp.net的处理机制http://www.microsoft.com/china/msdn/library/webservices/asp.net/dnvs05Internals.mspx?mfr=true

html.asp.aspx运行效率比较

http://iamlibai.blogbus.com/logs/2017870.html

 
 


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




SSI技术

$
0
0
1.       SSI,通常称为“服务器端包含”技术。使用了SSI技术的文件默认的后缀名为.shtml,SSI技术通过在html文件中加入SSI指令让web服务器在输出标准HTML代码之前先解释SSI指令,并把解释完后的输出结果和HTML代码一起返回给客户端。

2.       SSI技术的优点:SSI技术是通用技术,它不受限于运行环境,在java、dotnet、CGI、ASP、PHP下都可以使用SSI技术;解释SSI的效率比解释JSP的效率快很多,因为JSP规范提供了太多的功能,这些功能都需要servlet引擎一一进行解释,所以效率比较低

3.       解析SSI文件最佳的服务器:Apache Http Server

4.       Tomcat5.5下配置使用SSI:

①:把$CATALINA_HOME/server/lib/servlets-ssi.renametojar的名称改为servlets-ssi.jar

②:设置$CATALINA_HOME/conf/context.xml文件,在<Context>节点添加privileged="true"属性,该属性指定我们发布的web应用有权限使用容器的servlet。当你如果需要在$CATALINA_HOME/conf/server.xml添加其它web应用时,最好也为<Context>加上privileged="true"属性,如下:

<Context debug="0" path="" docBase="E:/website" privileged="true"/>

③:在$CATALINA_HOME/ conf/web.xml开启SSI的功能,Tomcat提供了两种开启SSI的方式:一种是servlet,另一种是filter。这里我们使用Servlet开启SSI功能。

在$CATALINA_HOME/ conf/web.xml文件中找到<servlet-name>ssi</servlet-name> ,把对该Servlet的注释去掉,然后根据shtml文件的编码格式指定inputEncoding 和outputEncoding 属性值

5.       SSI指令基本格式:<!--#指令名称="指令参数“ --> 

注:<!--与#号间无空格,只有SSI指令与参数间存在空格。上面的标点="",一个也不能少。SSI指令是大小写敏感的,因此参数必须是小写才会起作用

6.       SSI指令:

1)#include:<!--#include file="文件名称"--> file 文件名是一个相对路径,该路径相对于使用 #include 指令的文档所在的目录;<!--#include virtual="文件名称"--> virtual 文件名是 Web 站点上的虚拟目录的完整路径

2)<!--#flastmod file="文件名称"--> 文件最近更新日期;<!--#fsize file="文件名称"-->

文件的长度

3)<!--#echo var="变量名称"-->  将环境变量插入到页面中

示例:

本文档名称:<!--#echo var="DOCUMENT_NAME"-->

你的IP地址:<!--#echo var="REMOTE_ADDR"-->

<!--#config timefmt="%Y-%m-%d %a %H:%M:%S"-->

现在时间:<!--#echo var="DATE_LOCAL"-->

4)<!--#set var="变量名" value="变量值"-->  给变量赋值

5)<!--#if expr="$变量名=/"变量值A/""-->

  显示内容

<!--#elif expr="$变量名=/"变量值B/""-->

  显示内容

<!--#else-->

  显示内容

<!--#endif"-->  //创建可以改变数据的页面,这些数据根据使用if语句时计算的要求予以显示

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



PostgreSQL学习手册(常用数据类型)

$
0
0

一、数值类型:

    下面是PostgreSQL所支持的数值类型的列表和简单说明:

名字存储空间描述范围
smallint2 字节小范围整数-32768 到 +32767
integer4 字节常用的整数-2147483648 到 +2147483647
bigint8 字节大范围的整数-9223372036854775808 到 9223372036854775807
decimal变长用户声明精度,精确无限制
numeric变长用户声明精度,精确无限制
real4 字节变精度,不精确6 位十进制数字精度
double8 字节变精度,不精确15 位十进制数字精度
serial4 字节自增整数1 到 +2147483647
bigserial8 字节大范围的自增整数1 到 9223372036854775807

   1. 整数类型:
    类型smallint、integer和bigint存储各种范围的全部是数字的数,也就是没有小数部分的数字。试图存储超出范围以外的数值将导致一个错误。常用的类型是integer,因为它提供了在范围、存储空间和性能之间的最佳平衡。一般只有在磁盘空间紧张的时候才使用smallint。而只有在integer的范围不够的时候才使用bigint,因为前者(integer)绝对快得多。 

    2. 任意精度数值:
    类型numeric可以存储最多1000位精度的数字并且准确地进行计算。因此非常适合用于货币金额和其它要求计算准确的数量。不过,numeric类型上的算术运算比整数类型或者浮点数类型要慢很多。
    numeric字段的最大精度和最大比例都是可以配置的。要声明一个类型为numeric的字段,你可以用下面的语法:
    NUMERIC(precision,scale)
    比如数字23.5141的精度为6,而刻度为4。
    在目前的PostgreSQL版本中, decimalnumeric是等效的。
    
    3. 浮点数类型:
    数据类型real和double是不准确的、牺牲精度的数字类型。不准确意味着一些数值不能准确地转换成内部格式并且是以近似的形式存储的,因此存储后再把数据打印出来可能显示一些缺失。
    
   4. Serial(序号)类型:
    serial和bigserial类型不是真正的类型,只是为在表中设置唯一标识做的概念上的便利。
    CREATE TABLE tablename (
        colname  SERIAL
    );
    等价于
    CREATE SEQUENCE tablename_colname_seq;
    CREATE TABLE tablename(
        colname integer DEFAULT  nextval('tablename_colname_seq') NOT NULL
    );
    这样,我们就创建了一个整数字段并且把它的缺省数值安排为从一个序列发生器取值。应用了一个NOT NULL约束以确保空值不会被插入。在大多数情况下你可能还希望附加一个UNIQUE或者PRIMARY KEY约束避免意外地插入重复的数值,但这个不是自动发生的。因此,如果你希望一个序列字段有一个唯一约束或者一个主键,那么你现在必须声明,就像其它数据类型一样。
    还需要另外说明的是,一个serial类型创建的序列在其所属字段被删除时,该序列也将被自动删除,但是其它情况下是不会被删除的。因此,如果你想用同一个序列发生器同时给几个字段提供数据,那么就应该以独立对象的方式创建该序列发生器。 

二、字符类型:

    下面是PostgreSQL所支持的字符类型的列表和简单说明:

名字描述
varchar(n)变长,有长度限制
char(n)定长,不足补空白
text变长,无长度限制

    SQL 定义了两种基本的字符类型,varchar(n)和char(n),这里的n是一个正整数。两种类型都可以存储最多n个字符长的字串,试图存储更长的字串到这些类型的字段里会产生一个错误,除非超出长度的字符都是空白,这种情况下该字串将被截断为最大长度。如果没有长度声明,char等于char(1),而varchar则可以接受任何长度的字串。
     MyTest=> CREATE TABLE testtable(first_col varchar(2));
    CREATE TABLE
     MyTest=> INSERT INTO testtable VALUES('333');   --插入字符串的长度,超过其字段定义的长度,因此报错。
    ERROR:  value too long for type character varying(2)
    --插入字符串中,超出字段定义长度的部分是空格,因此可以插入,但是空白符被截断。
     MyTest=> INSERT INTO testtable VALUES('33 ');   
    INSERT 0 1
    MyTest=> SELECT * FROM testtable;
     first_col
    -----------
     33
    (1 row)
    这里需要注意的是,如果是将数值转换成char(n)或者varchar(n),那么超长的数值将被截断成n个字符,而不会抛出错误。
    MyTest=> select 1234::varchar(2);
     varchar
    ---------
     12
    (1 row)
    最后需要提示的是,这三种类型之间没有性能差别,只不过是在使用char类型时增加了存储尺寸。虽然在某些其它的数据库系统里,char(n)有一定的性能优势,但在PostgreSQL里没有。在大多数情况下,应该使用text或者varchar。
    
三、日期/时间类型:

    下面是PostgreSQL所支持的日期/时间类型的列表和简单说明:

名字存储空间描述最低值最高值分辨率
timestamp[无时区]8字节包括日期和时间4713 BC5874897AD1毫秒/14位
timestamp[含时区]8字节日期和时间,带时区4713 BC5874897AD1毫秒/14位
interval12字节时间间隔-178000000年178000000年1毫秒/14位
date4字节只用于日期4713 BC32767AD1天
time[无时区]8字节只用于一日内时间00:00:0024:00:001毫秒/14位


    1. 日期/时间输入:
    任何日期或者时间的文本输入均需要由单引号包围,就象一个文本字符串一样。
    1). 日期:
    以下为合法的日期格式列表:

例子描述
January 8, 1999在任何datestyle输入模式下都无歧义
1999-01-08ISO-8601格式,任何方式下都是1999年1月8号,(建议格式)
1/8/1999歧义,在MDY下是1月8号;在 DMY模式下读做8月1日
1/18/1999在MDY模式下读做1月18日,其它模式下被拒绝
01/02/03MDY模式下的2003年1月2日;DMY模式下的2003年2月1日;YMD 模式下的2001年2月3日
1999-Jan-08任何模式下都是1月8日
Jan-08-1999任何模式下都是1月8日
08-Jan-1999任何模式下都是1月8日
99-Jan-08在YMD模式下是1月8日,否则错误
08-Jan-991月8日,除了在YMD模式下是错误的之外
Jan-08-991月8日,除了在YMD模式下是错误的之外
19990108ISO-8601; 任何模式下都是1999年1月8日
990108ISO-8601; 任何模式下都是1999年1月8日

    2). 时间:
    以下为合法的时间格式列表:

例子描述
04:05:06.789ISO 8601
04:05:06ISO 8601
04:05ISO 8601
040506ISO 8601
04:05 AM与04:05一样;AM不影响数值
04:05 PM与16:05一样;输入小时数必须 <= 12
04:05:06.789-8ISO 8601
04:05:06-08:00ISO 8601
04:05-08:00ISO 8601
040506-08ISO 8601

    3). 时间戳:
    时间戳类型的有效输入由一个日期和时间的联接组成,后面跟着一个可选的时区。因此,1999-01-08 04:05:06和1999-01-08 04:05:06 -8:00都是有效的数值。
   
   2. 示例:
    1). 在插入数据之前先查看datestyle系统变量的值:
    MyTest=> show  datestyle;
     DateStyle
    -----------
     ISO, YMD
    (1 row)
    2). 创建包含日期、时间和时间戳类型的示例表:
    MyTest=> CREATE TABLE testtable (id integer, date_col date, time_col time, timestamp_col timestamp);
    CREATE TABLE
    3). 插入数据:
    MyTest=> INSERT INTO testtable(id,date_col) VALUES(1,  DATE'01/02/03');  --datestyle为YMD
    INSERT 0 1
    MyTest=> SELECT id, date_col FROM testtable;
     id   |  date_col
    ----+------------
      1  | 2001-02-03
    (1 row)
    
    MyTest=> set datestyle =  MDY;
    SET
    MyTest=> INSERT INTO testtable(id,date_col) VALUES(2,  DATE'01/02/03');  --datestyle为MDY
    INSERT 0 1
    MyTest=> SELECT id,date_col FROM testtable;
     id   |  date_col
    ----+------------
      1  | 2001-02-03
      2  | 2003-01-02  

    MyTest=> INSERT INTO testtable(id,time_col) VALUES(3,  TIME'10:20:00');  --插入时间。
    INSERT 0 1
    MyTest=> SELECT id,time_col FROM testtable WHERE time_col IS NOT NULL;
     id   | time_col
    ----+----------
      3   | 10:20:00
    (1 row)

     MyTest=> INSERT INTO testtable(id,timestamp_col) VALUES(4,  DATE'01/02/03'); 
    INSERT 0 1
    MyTest=> INSERT INTO testtable(id,timestamp_col) VALUES(5,  TIMESTAMP'01/02/03 10:20:00');
    INSERT 0 1
    MyTest=> SELECT id,timestamp_col FROM testtable WHERE timestamp_col IS NOT NULL;
     id   |    timestamp_col
    ----+---------------------
      4  | 2003-01-02 00:00:00
      5  | 2003-01-02 10:20:00
    (2 rows)

四、布尔类型:

    PostgreSQL支持标准的SQL boolean数据类型。boolean只能有两个状态之一:真(True)或 假(False)。该类型占用1个字节。
    "真"值的有效文本值是:
    TRUE
    't'
    'true'
    'y'
    'yes'
    '1'
    而对于"假"而言,你可以使用下面这些:
    FALSE
    'f'
    'false'
    'n'
    'no'
    '0'
  见如下使用方式:
    MyTest=> CREATE TABLE testtable (a boolean, b text);
    CREATE TABLE
    MyTest=> INSERT INTO testtable VALUES(TRUE, 'sic est');
    INSERT 0 1
    MyTest=> INSERT INTO testtable VALUES(FALSE, 'non est');
    INSERT 0 1
    MyTest=> SELECT * FROM testtable;
     a  |    b
    ---+---------
     t  | sic est
     f  | non est
    (2 rows)    
    MyTest=> SELECT * FROM testtable WHERE a;
     a  |    b
    ---+---------
     t  | sic est
    (1 row)    
    MyTest=> SELECT * FROM testtable WHERE a = true;
     a  |    b
    ---+---------
     t  | sic est
    (1 row)
    
五、位串类型:

    位串就是一串1和0的字串。它们可以用于存储和视觉化位掩码。我们有两种类型的SQL位类型:bit(n)和bit varying(n); 这里的n是一个正整数。bit类型的数据必须准确匹配长度n; 试图存储短些或者长一些的数据都是错误的。类型bit varying数据是最长n的变长类型;更长的串会被拒绝。写一个没有长度的bit等效于bit(1),没有长度的bit varying相当于没有长度限制。
    针对该类型,最后需要提醒的是,如果我们明确地把一个位串值转换成bit(n),那么它的右边将被截断或者在右边补齐零,直到刚好n位,而不会抛出任何错误。类似地,如果我们明确地把一个位串数值转换成bit varying(n),如果它超过n位,那么它的右边将被截断。 见如下具体使用方式:    
    MyTest=> CREATE TABLE testtable (a bit(3), b bit varying(5));
    CREATE TABLE
    MyTest=> INSERT INTO testtable VALUES ( B'101',  B'00');
    INSERT 0 1
    MyTest=> INSERT INTO testtable VALUES ( B'10',  B'101');
    ERROR:  bit string length 2 does not match type bit(3)
    MyTest=> INSERT INTO testtable VALUES ( B'10':: bit(3),  B'101');
    INSERT 0 1
    MyTest=> SELECT * FROM testtable;
      a  |  b
    -----+-----
     101 | 00
     100 | 101
    (2 rows)
     MyTest=> SELECT  B'11':: bit(3);
     bit
    -----
     110
    (1 row)


六、数组:

    1. 数组类型声明:
    1). 创建字段含有数组类型的表。
    CREATE TABLE sal_emp (
        name            text,
        pay_by_quarter  integer [] --还可以定义为 integer[4]integer ARRAY[4]
    );
    2). 插入数组数据:
    MyTest=# INSERT INTO sal_emp VALUES ('Bill', '{11000, 12000, 13000, 14000}');
    INSERT 0 1
    MyTest=# INSERT INTO sal_emp VALUES ('Carol',  ARRAY[21000, 22000, 23000, 24000]);
    INSERT 0 1
    MyTest=# SELECT * FROM sal_emp;
     name  |      pay_by_quarter
    --------+---------------------------
     Bill     | {11000,12000,13000,14000}
     Carol  | {21000,22000,23000,24000}
    (2 rows)    

    2. 访问数组:
    和其他语言一样,PostgreSQL中数组也是通过下标数字(写在方括弧内)的方式进行访问,只是PostgreSQL中数组元素的下标是从1开始n结束。
    MyTest=# SELECT pay_by_quarter[3] FROM sal_emp;
     pay_by_quarter
    ----------------
              13000
              23000
    (2 rows)
    MyTest=# SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];
     name
    ------
     Bill
     Carol
    (2 rows)
    PostgreSQL中还提供了访问数组范围的功能,即ARRAY[脚标下界:脚标上界]。
    MyTest=# SELECT name,pay_by_quarter [1:3] FROM sal_emp;
     name  |   pay_by_quarter
    --------+---------------------
     Bill     | {11000,12000,13000}
     Carol  | {21000,22000,23000}
    (2 rows)
    
    3. 修改数组:
    1). 代替全部数组值:
     --UPDATE sal_emp SET pay_by_quarter =  ARRAY[25000,25000,27000,27000] WHERE name = 'Carol'; 也可以。
    MyTest=# UPDATE sal_emp SET pay_by_quarter = '{31000,32000,33000,34000}' WHERE name = 'Carol';
    UPDATE 1
    MyTest=# SELECT * FROM sal_emp;
     name  |      pay_by_quarter
    --------+---------------------------
     Bill     | {11000,12000,13000,14000}
     Carol  | {31000,32000,33000,34000}
    (2 rows)
    2). 更新数组中某一元素:
    MyTest=# UPDATE sal_emp SET pay_by_quarter[4] = 15000 WHERE name = 'Bill';
    UPDATE 1
     MyTest=# SELECT * FROM sal_emp;
     name  |      pay_by_quarter
    --------+---------------------------
     Carol  | {31000,32000,33000,34000}
     Bill     | {11000,12000,13000,15000}
    (2 rows)
    3). 更新数组某一范围的元素:
    MyTest=# UPDATE sal_emp SET pay_by_quarter[1:2] = '{37000,37000}' WHERE name = 'Carol';
    UPDATE 1
    MyTest=# SELECT * FROM sal_emp;
     name  |      pay_by_quarter
    --------+---------------------------
     Bill     | {11000,12000,13000,15000}
     Carol  | {37000,37000,33000,34000}
    (2 rows)
    4). 直接赋值扩大数组:
    MyTest=# UPDATE sal_emp SET pay_by_quarter[5] = 45000 WHERE name = 'Bill';
    UPDATE 1
    MyTest=# SELECT * FROM sal_emp;
     name  |         pay_by_quarter
    --------+---------------------------------
     Carol  | {37000,37000,33000,34000}
     Bill     | {11000,12000,13000,15000,45000}
    (2 rows)

    4. 在数组中检索:
    1). 最简单直接的方法:
    SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 OR
                            pay_by_quarter[2] = 10000 OR
                            pay_by_quarter[3] = 10000 OR
                            pay_by_quarter[4] = 10000;    
    2). 更加有效的方法:
    SELECT * FROM sal_emp WHERE 10000 =  ANY (pay_by_quarter);  --数组元素中有任何一个等于10000,where条件将成立。
    SELECT * FROM sal_emp WHERE 10000 =  ALL (pay_by_quarter);  --只有当数组中所有的元素都等于10000时,where条件才成立。

七、复合类型:

    PostgreSQL中复合类型有些类似于C语言中的结构体,也可以被视为Oracle中的记录类型,但是还是感觉复合类型这个命名比较贴切。它实际上只是一个字段名和它们的数据类型的列表。PostgreSQL允许像简单数据类型那样使用复合类型。比如,表字段可以声明为一个复合类型。
    1. 声明复合类型:
    下面是两个简单的声明示例:
    CREATE TYPE complex  AS (
        r double,
        i double
    );   
    CREATE TYPE inventory_item  AS (
        name           text,
        supplier_id   integer,
        price            numeric
    );
    和声明一个数据表相比,声明类型时需要加AS关键字,同时在声明TYPE时不能定义任何约束。下面我们看一下如何在表中指定复合类型的字段,如:
    CREATE TABLE on_hand (
        item       inventory_item,
        count    integer
    );
    最后需要指出的是,在创建表的时候,PostgreSQL也会自动创建一个与该表对应的复合类型,名字与表字相同,即表示该表的复合类型。
    
    2. 复合类型值输入:
    我们可以使用文本常量的方式表示复合类型值,即在圆括号里包围字段值并且用逗号分隔它们。你也可以将任何字段值用双引号括起,如果值本身包含逗号或者圆括号,那么就用双引号括起,对于上面的inventory_item复合类型的输入如下:
    '("fuzzy dice",42,1.99)'
    如果希望类型中的某个字段为NULL,只需在其对应的位置不予输入即可,如下面的输入中price字段的值为NULL,
    '("fuzzy dice",42,)'
    如果只是需要一个空字串,而非NULL,写一对双引号,如:
    '("",42,)'
    在更多的场合中PostgreSQL推荐使用ROW表达式来构建复合类型值,使用该种方式相对简单,无需考虑更多标识字符问题,如:
    ROW('fuzzy dice', 42, 1.99)
    ROW('', 42, NULL)
    注:对于ROW表达式,如果里面的字段数量超过1个,那么关键字ROW就可以省略,因此以上形式可以简化为:
    ('fuzzy dice', 42, 1.99)
    ('', 42, NULL)
    
    3. 访问复合类型:
    访问复合类型中的字段和访问数据表中的字段在形式上极为相似,只是为了对二者加以区分,PostgreSQL设定在访问复合类型中的字段时,类型部分需要用圆括号括起,以避免混淆,如:
    SELECT (item).name FROM on_hand WHERE (item).price > 9.99;
    如果在查询中也需要用到表名,那么表名和类型名都需要被圆括号括起,如:
    SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;
    
    4. 修改复合类型:
    见如下几个示例:
    --直接插入复合类型的数据,这里是通过ROW表达式来完成的。
    INSERT INTO on_hand(item) VALUES(ROW("fuzzy dice",42,1.99));
    --在更新操作中,也是可以通过ROW表达式来完成。
    UPDATE on_hand SET item = ROW("fuzzy dice",42,1.99) WHERE count = 0;
    --在更新复合类型中的一个字段时,我们不能在SET后面出现的字段名周围加圆括号,
    --但是在等号右边的表达式里引用同一个字段时却需要圆括号。
    UPDATE on_hand SET item.price = (item).price + 1 WHERE count = 0;
    --可以在插入中,直接插入复合类型中字段。
    INSERT INTO on_hand (item.supplier_id, item.price) VALUES(100, 2.2);



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



MySQL表结构优化

$
0
0
前言

     很多人都将<数据库设计范式>作为数据库表结构设计“圣经”,认为只要按照这个范式需求设计,就能让设计出来的表结构足够优化,既能保证性能优异同时还能满足扩展性要求。殊不知,在N年前被奉为“圣经”的数据库设计3范式早就已经不完全适用了。这里我整理了一些比较常见的数据库表结构设计方面的优化技巧,希望对大家有用。

     由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作 IO 的时候是以 page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相同行数的数据,需要访问的 page 就会减少,也就是 IO 操作次数降低,直接提升性能。此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。



数据类型选择

  数据库操作中最为耗时的操作就是 IO 处理,大部分数据库操作 90% 以上的时间都花在了 IO 读写上面。所以尽可能减少 IO 读写量,可以在很大程度上提高数据库操作的性能。

  我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题:

  1.数字类型:非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开 TINYINT / INT / BIGINT 的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。

  2.字符类型:非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。

  3.时间类型:尽量使用TIMESTAMP类型,因为其存储空间只需要 DATETIME 类型的一半。对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。不建议通过INT类型类存储一个unix timestamp 的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。

  4.ENUM & SET:对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。

  5.LOB类型:强烈反对在数据库中存放 LOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。在数据库中存储 LOB 数据就像让一个多年前在学校学过一点Java的营销专业人员来写 Java 代码一样。



字符编码

  字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。

  1.纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间。

  2.如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费。

  3.MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率。



适当拆分

  有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。

  当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。

  上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少 IO 操作次数,提高缓存命中率。下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。



适度冗余

  为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?

  确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:

  1.被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段。

  2.这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。



尽量使用 NOT NULL

  NULL 类型比较特殊,SQL 难优化。虽然 MySQL NULL类型和 Oracle 的NULL 有差异,会进入索引中,但如果是一个组合索引,那么这个NULL 类型的字段会极大影响整个索引的效率。此外,NULL 在索引中的处理也是特殊的,也会占用额外的存放空间。

  很多人觉得 NULL 会节省一些空间,所以尽量让NULL来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保 DEFAULT 值不是 NULL,也是一个很好的表结构设计优化习惯。(此文为转载)

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



温州有线是怎么被黑的?

$
0
0

hacker昨夜 消息,温州地区有线电视被大面积黑入,出现了一段时间的画(敏)面(感)太(内)美(容)。

有线电视被黑的方式主要包括大量的图片和敏感消息推送。那么N年不遇的有线电视黑入到底是怎么回事呢?

03
一位业内同学给了一个比较简单的解释:

广电的技术架构为Client/Server架构,即客户端向服务器端发送请求,而服务器端返回结果(在这里就是内容)。因此这样的架构只要服务器端被侵入,就能注入一些内容。

关于具体的入侵细节上,有网友猜测:

被黑的不是播出部分,而是运营商的EPG(电子节目单)服务器或者BOSS系统的相关服务器。

所谓信号源,目前的IPTV/iTV都是MPEG2或者H.264的TS over UDP的组播流,是由各个地方台推送到省级/国家级播控平台,再由播控平台下发的,期间都是加扰过,并且电视台和地方级的广电网络公司或者电信运营商是没办法更改内容本身的。

至于片源,除了直播信号之外,大部分成品节目都是从媒资出库到播出,一般出库之前都有校验,人为篡改片源文件的可能性基本已经被杜绝了。

从入侵的照片来看,这种情况是运营商的EPG(电子节目单)服务器或者BOSS计费系统服务器被入侵。因为这两种系统有办法在节目之外推送一些消息到机顶盒终端(比如有些付费节目,可以先看5分钟,然后提示需要付费),或者通过EPG系统可以推送一些实时消息之类的。

至于上面说什么漏洞很搞笑之类的,我倒觉得这没什么可笑的,毕竟这类系统也不外乎就是运行在Linux+MySQL或者11g之类的环境下,要攻击也大多是利用SQL注入或者Linux的一些0day漏洞(已知漏洞一般都会在第一时间封堵,毕竟是涉及到播出的东西)。

而对于入侵的技术难度,有网友如此表示:

这并不是很了不起的技术活,并不需要入侵或者破解信号源,完全可以是一个“内鬼”或者是破解了相关人员的账号密码(很多系统在运营阶段甚至还保留很简单的测试密码或者默认密码)——乌云等爆的“漏洞”不算是新鲜事儿,但温州这事儿实在不必那么麻烦,更不必上升到APT层面。

因为行业问题(软件行业细分很多,但往往是很不专业的“编码爱好者”在做一些细分领域的工作,加上众所周知的原因竞争不够充分)以及后续使用的安全意识薄弱,满大街的各种“新媒体”、电子屏都很容易被入侵。

只是因为真正的黑客对此不屑一顾,而黑产的骇客也未必有兴趣:相对于风险,无法快速获得足够的利益。

比如:

A. 黑客告诉对方有漏洞,对方不一定关心,甚至为了保护自己的利益故意忽略最多悄悄修补;
B. 骇客不告诉对方有漏洞,自己利用?——怎么用?

典型的例子,比如公交地铁系统的那些卡,难道你帮别人充值赚钱?还没赚够你就被捕了。
典型的例子,比如满大街的Taxi、公交车上的LED广告,难道你帮别人发广告赚钱?一样的,第一个广告出现后,追查广告主一定找到你,还没赚够你就被捕了。
所以,只有一种可能:被轮子等利用。

所以,类似的事情其实很容易爆发的,只是还没有罢了。

最后总体而言,纵观此事的利弊:

对安全行业和安全产品,此次的事件是利好消息;而对广电机顶盒,产品安全的管控必然会更加严格。至于OTT盒子呢?

文章内容来自网络投稿,雷锋网登载此文为出于传递更多信息目的,作者观点不代表雷锋网
责任编辑: 吴德新

您可能也喜欢:

温州有线被黑,OTT盒子的神经收紧

黑莓衰亡幕后故事(三):为什么黑莓不鸟中国

狗币交易系统被黑,损失3000万狗币

苹果开发者中心被黑仍未修复

黑莓衰亡幕后故事(一):“iPhone杀手”该怎么做
无觅

spring roo 入门

$
0
0
  • 准备工作:

  1. Spring官网下载STS(如果没有STS)。

     

  • 体验步骤:

  1. 创建Spring Roo基础项目

    wKioL1LyZdeS797iAAOg4XolFMY114.jpgwKiom1LyZjSSmRGzAAbnuauFugA722.jpg

  2. 生成持久层

    根 据ROO的提示输入jpa setup再按ctrl+space,很遗憾这个快捷键已经被输入法切换占用,不能借助提示输入命令,但我们可以打开ROO命令向导,这里我们输入jpa 可以查到这条命令的用法,根据提示增加provider和database选项来完成命令。wKiom1LyZ73gQ0_2AAM7ajzGgSk278.jpg

     

    1
    jpa setup --provider HIBERNATE --database H2_IN_MEMORY

    命令执行后我们可以看到新增加或更新以下内容:

    1)增加database.properties文件

    2)增加persistence.xml文件

    3)更新applicationContext.xml问价

    4)更新Maven依赖

    接 着再次输入hint,ROO会提示我们创建实体,我们再次打开命令向导,敲入entity,向导提示class参数是必须的,这里我们输入Card作为我 们要创建的Class,然后按Finish,ROO就会自动帮我们在顶层包下生成Card实体类,同时还会生成AspectJ的相关类,这些类主要给 ROO使用,可以先不必理会。

     

    我们假设一张卡有卡号,客户号和余额三个属性,目前Card类没有任何属性,显然不能 满足我们的需求,再次输入hint命令,ROO提示使用field命令给实体增加属性,我们依赖使用命令向导来添加这三个属性,值得注意的是可选项,有些 验证的可选项我们可以直接使用。糟糕,field命令执行不成功,原来comment的值需要加双引号.field命令很强大,如果表有关联,我们可以使 用field reference命令向导来完成。

    wKioL1LyZoiCl3PYAANKQoc2GDk282.jpg

    接 着使用repository jpa命令向导来创建一个实现CRUD操作的简单Repository,这个命令很神奇,ROO会自动使用AspectJ和Spring Data来完成基本的CRUD操作实现,而CardRepository类只是加简单的注解@RooJpaRepository(domainType = Card.class),不再需要任何实现,虽然简单方便,但也可以看出项目已经跟ROO绑定。

     

    至此我们完成了一个简单的持久层,使用命令向导还是挺方便的。

  3. 生成服务层

    再 次使用hint命令,ROO可以增加更多实体或者field,或者创建Web MVC Controller等,但没有服务层相关提示,我们可以使用命令向导,输入service,选择service type来创建服务接口CardService和实现类CardServiceImpl,命令执行后ROO也会自动生成相关的AspectJ类来维护 Repositoryde的CRUD调用,所以我们只需要再增加其它特殊操作就可以了。

  4. 生成展示层

    使用刚 才ROO提示的“web mvc setup”命令来初始化Web MVC,该命令会增加Spring MVC的支持,以及实体的操作页面、国际化、主题等支持。当然该命令要结合Controller的创建命令使用,打开命令向导,输入"web",选 择"web mvc all"命令来为所有实体生成Controller。至此一个基本的MVC应用已经成形,可以马上“Run on server”试试效果。 wKiom1LyZwmTyveuAAIURs7q_-8075.jpg

    wKiom1LyZvLwNN-KAAFaolfoN5Y650.jpg

    wKioL1LyZs6QNa5gAAFHhuVMEvg611.jpg

    太棒了,基本的CRUD都支持了。

  5. 生成安全支持

    在从命令向导输入"security",选择"security setup",然后点击Finish,该命令将为我们生成一个登陆页面和基本的验证信息,太神奇了,赶快也试试吧!

    wKioL1LyZvODMkBSAAFYF19xH-M863.jpg

  • 小结:

第 一次使用还是蛮顺手,非常适合创建简单测试项目,ROO脚本可以保存成文件,这样下次直接执行就可以自动创建一个同样的项目了,另外ROO还提供了很多插 件可以帮忙简化更多开发工作,有待进一步探索,还有一点就是项目维护问题,既然代码都是自动生成的,免不了重构,不知道重构支持如何,继续考察。

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>