Perfil de Yun无尽的探索FotosBlogListas Herramientas Ayuda

Blog


26 noviembre

MySQL Reference: 12.4.3. Statements That Cause an Implicit Commit

Each of the following statements (and any synonyms for them) implicitly end a transaction, as if you had done a COMMIT before executing the statement:

  • ALTER TABLE, BEGIN, CREATE INDEX, DROP INDEX, DROP TABLE, LOAD MASTER DATA, LOCK TABLES, LOAD DATA INFILE, RENAME TABLE, SET AUTOCOMMIT=1 (if the value is not already 1), START TRANSACTION, UNLOCK TABLES.

    The BEGIN statement differs from the use of the BEGIN keyword that starts a BEGIN ... END compound statement. The latter does not cause an implicit commit. See Section 21.2.7, “BEGIN ... END Compound Statement Syntax”.

  • Beginning with MySQL 5.0.8, CREATE TABLE, CREATE DATABASE DROP DATABASE, and TRUNCATE TABLE cause an implicit commit.

    Beginning with MySQL 5.0.13, ALTER FUNCTION, ALTER PROCEDURE, CREATE FUNCTION, CREATE PROCEDURE, DROP FUNCTION, and DROP PROCEDURE cause an implicit commit.

    Beginning with MySQL 5.0.15, ALTER VIEW, CREATE TRIGGER, CREATE USER, CREATE VIEW, DROP TRIGGER, DROP USER, DROP VIEW, and RENAME USER cause an implicit commit.

  • UNLOCK TABLES commits a transaction only if any tables currently have been locked with LOCK TABLES. This does not occur for UNLOCK TABLES following FLUSH TABLES WITH READ LOCK because the latter statement does not acquire table-level locks.

  • The CREATE TABLE statement in InnoDB is processed as a single transaction. This means that a ROLLBACK from the user does not undo CREATE TABLE statements the user made during that transaction.

  • CREATE TABLE and DROP TABLE do not commit a transaction if the TEMPORARY keyword is used. (This does not apply to other operations on temporary tables such as CREATE INDEX, which do cause a commit.) However, although no implicit commit occurs, neither can the statement be rolled back. Therefore, use of such statements will violate transaction atomicity: For example, if you use CREATE TEMPORARY TABLE and then roll back the transaction, the table remains in existence.

  • In MySQL 5.0.25 and earlier, LOAD DATA INFILE caused an implicit commit for all storage engines. Beginning with MySQL 5.0.26, it causes an implicit commit only for tables using the NDB storage engine. For more information, see Bug#11151.

Transactions cannot be nested. This is a consequence of the implicit COMMIT performed for any current transaction when you issue a START TRANSACTION statement or one of its synonyms.

Statements that cause an implicit commit cannot be used in an XA transaction while the transaction is in an ACTIVE state.

19 junio

jQuery form提交时中文乱码 [转]

关键字: ajax post 中文 乱码

在使用jsp开始时,一不小心就会出现中文乱码的情况。通常可以使用如下方法解决:
第一种解决方法:
从jsp到servlet统一使用utf-8编码.全部使用utf-8编码能省去很多麻烦,但一点不足是utf-8编码对汉字是使用3-4个字节,会加大网络传输量。
第二种方法:
1.jsp页面使用GBK
2.使用servlet过滤器设置request.setCharacterEncoding("GBK");google一下就能找到很多怎么使用过滤器转换编码。
以上两种方法能解决大部分的乱码问题,特别是第一种方法,能解决使用ajax提交时的中文乱码问题。如果采用第二种方法,那么在使用ajax提交表单时仍然会有中文乱码。这是因为ajax方式提交时js使用的是utf-8的编码,过滤器使用gbk进行转码就不正确了,应该使用utf-8进行转码。要解决这个问题,网上也有很多个版本,其中一个是我曾经采用的在客户端使用encodeURI,然后再在服务器端进行URLDecoder.decode,这种方案在偶尔处理一下中文是可行的,但是如果页面有大量数据录入,那么这种方案是不可行的。有没有好的解决方法呢?在经过新一轮的google之后,还是找到了方法。参考http://www.javaeye.com/topic/157698?page=1。原理就是根据httpheader中的内容来区分是ajax方式请求还是普通的请求。在jquery1.2.6中,ajaxSettings默认设置contentType为"application/x-www-form-urlencoded",在ajax方法中设置xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");,我们只要在servlet中根据这两个参数值来判断是不是通过xmlhttprequest发起的请求。这里值得一提的是jquery在提交form时对参数进行了encodeURIComponent调用,参见param方法

Java代码 复制代码

  1. jQuery.each( a, function(){   
  2.                 s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );   
  3.             }); 
jQuery.each( a, function(){
				s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
			});

所以传给servlet的就是utf-8编码了,那么我们在过滤器中就必须使用utf-8进行转码。
对其中的过滤器做了下修正,在IE下和chrome下,request.getContentType()的值为"application/x-www-form-urlencoded",但是在firefox下,这个值为"application/x-www-form-urlencoded; charset=UTF-8",不是很明白ff为什么会是这个值。
filter代码如下:

Java代码 复制代码

  1. package com.ajax.demo.action;   
  2. import java.io.IOException;   
  3. import javax.servlet.Filter;   
  4. import javax.servlet.FilterChain;   
  5. import javax.servlet.FilterConfig;   
  6. import javax.servlet.ServletException;   
  7. import javax.servlet.ServletRequest;   
  8. import javax.servlet.ServletResponse;   
  9. import javax.servlet.http.HttpServletRequest;   
  10. import javax.servlet.http.HttpServletResponse;   
  11. public class AjaxPostFilter implements Filter {   
  12. private static final String IE_CONTENT_TYPE = "application/x-www-form-urlencoded";   
  13. private static final String FF_AJAX_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=UTF-8";   
  14. private static final String XMLHTTP_REQUEST = "XMLHttpRequest";   
  15. private static final String AJAX_CHARACTER_ENCODING_UTF8 = "UTF-8";   
  16. public void destroy() {   
  17. // TODO Auto-generated method stub
  18.     }   
  19. public void doFilter(ServletRequest servletRequest,   
  20.             ServletResponse servletResponse, FilterChain filterChain)   
  21. throws IOException, ServletException {   
  22.         HttpServletRequest request = (HttpServletRequest) servletRequest;   
  23.         HttpServletResponse response = (HttpServletResponse) servletResponse;   
  24.         String requestedWith = request.getHeader("x-requested-with");   
  25.         String type = request.getContentType();   
  26. if (XMLHTTP_REQUEST.equals(requestedWith)&& (FF_AJAX_CONTENT_TYPE.equals(type)   
  27.                 ||IE_CONTENT_TYPE.equals(type))) {   
  28.             request.setCharacterEncoding(AJAX_CHARACTER_ENCODING_UTF8);   
  29.             response.setCharacterEncoding(AJAX_CHARACTER_ENCODING_UTF8);   
  30. //          request.getParameterMap();
  31.         }   
  32.         filterChain.doFilter(request, response);   
  33.     }   
  34. public void init(FilterConfig arg0) throws ServletException {   
  35. // TODO Auto-generated method stub
  36.     }   
package com.ajax.demo.action;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AjaxPostFilter implements Filter {

	private static final String IE_CONTENT_TYPE = "application/x-www-form-urlencoded";
	private static final String FF_AJAX_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=UTF-8";
	private static final String XMLHTTP_REQUEST = "XMLHttpRequest";
	private static final String AJAX_CHARACTER_ENCODING_UTF8 = "UTF-8";

	public void destroy() {
		// TODO Auto-generated method stub

	}

	public void doFilter(ServletRequest servletRequest,
			ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String requestedWith = request.getHeader("x-requested-with");
		String type = request.getContentType();
		if (XMLHTTP_REQUEST.equals(requestedWith)&& (FF_AJAX_CONTENT_TYPE.equals(type)
				||IE_CONTENT_TYPE.equals(type))) {
			request.setCharacterEncoding(AJAX_CHARACTER_ENCODING_UTF8);
			response.setCharacterEncoding(AJAX_CHARACTER_ENCODING_UTF8);
//			request.getParameterMap();
		}
		filterChain.doFilter(request, response);

	}

	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub

	}

}


web.xml配置,我用的是struts

Java代码 复制代码

  1. <filter>   
  2.         <filter-name>ajaxEncodeFilter</filter-name>   
  3.         <filter-class>com.ajax.demo.action.AjaxPostFilter</filter-class>   
  4.     </filter>   
  5.     <filter-mapping>   
  6.         <filter-name>ajaxEncodeFilter</filter-name>   
  7.         <url-pattern>*.do</url-pattern>   
  8.     </filter-mapping>   
  9.     <filter-mapping>   
  10.         <filter-name>ajaxEncodeFilter</filter-name>   
  11.         <url-pattern>*.jsp</url-pattern>   
  12.     </filter-mapping> 
<filter>
		<filter-name>ajaxEncodeFilter</filter-name>
		<filter-class>com.ajax.demo.action.AjaxPostFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>ajaxEncodeFilter</filter-name>
		<url-pattern>*.do</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>ajaxEncodeFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>

这个filter应该在你的EncodeFilter之后,RoyMax说要在之前,我试了下是不行的。
经过这样设置之后,jsp使用gbk采用ajax提交就不会存在中文乱码了。
在使用chrome进行测试时,还发现chrome的一个奇怪问题,对于返回的结果,使用jquery.ajax处理

Java代码 复制代码

  1. success:function showResponse(responseText, statusText)  {    
  2. //这里name为input text id,如果"aa"在前,那么name的值会改为aa+返回的结果
  3.                     $('#name').val("aa"+responseText);   
  4. //如果改成$('#name').val(responseText+"aa"),那么name的值仍然是//responseText,“aa”没有加到后面去,不知道是什么原因
success:function showResponse(responseText, statusText)  { 
//这里name为input text id,如果"aa"在前,那么name的值会改为aa+返回的结果
    				$('#name').val("aa"+responseText);
//如果改成$('#name').val(responseText+"aa"),那么name的值仍然是//responseText,“aa”没有加到后面去,不知道是什么原因
}
 
参考:http://jasin2008.javaeye.com/blog/312854

14 noviembre

Linux下修改网卡IP和网关

修改ifcfg-eth0文件内容:

[root@yourmachine ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE=eth0
ONBOOT=yes
BOOTPROTO=static
IPADDR=10.130.40.69
NETMASK=255.255.255.0
GATEWAY=10.130.40.1

修改resolv.conf:

[root@yourmachine ~]# vi /etc/resolv.conf

nameserver 10.130.1.1
nameserver 10.130.1.9
nameserver 202.106.196.115

重新启动网络配置
/etc/init.d/network restart

12 septiembre

Linux下安装Apache httpd + Subversion服务配置

先决条件

为了让你的版本库使用HTTP网络,你基本上需要两个包里的四个部分。你需要Apache httpd2.0和包括的mod_dav DAV模块,Subversion和与之一同分发的mod_dav_svn文件系统提供者模块,如果你有了这些组件,网络化你的版本库将非常简单,如:

  • 配置好httpd 2.0,并且使用mod_dav启动,

  • 为mod_dav安装mod_dav_svn插件,它会使用Subversion的库访问版本库,并且

  • 配置你的httpd.conf来输出(或者说暴露)版本库。

你可以通过从源代码编译httpd和Subversion来完成前两个项目,也可以通过你的系统上的已经编译好的二进制包来安装。最新的使用Apache HTTP的Subversion的编译方法和Apache的配置方式可以看Subversion源代码树根目录的INSTALL文件。

 
基本的 Apache 配置

一旦你安装了必须的组件,剩下的工作就是在httpd.conf里配置Apache,使用LoadModule来加载mod_dav_svn模块,这个指示必须先与其它Subversion相关的其它配置出现,如果你的Apache使用缺省布局安装,你的mod_dav_svn模块一定在Apache安装目录(通常是在/usr/local/apache2)的modules子目录,LoadModule指示的语法很简单,影射一个名字到它的共享库的物理位置:

LoadModule dav_svn_module     modules/mod_dav_svn.so

注意,如果mod_dav是作为共享对象编译(而不是静态链接到httpd程序),你需要为它使用LoadModule语句,一定确定它在mod_dav_svn之前:

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so
基本HTTP认证

最简单的客户端认证方式是通过HTTP基本认证机制,简单的使用用户名和密码来验证一个用户所自称的身份,Apache提供了一个htpasswd工具来管理可接受的用户名和密码,这些就是你希望赋予Subversion特别权限的用户,让我们给Sally和Harry赋予提交权限,首先,我们需要添加他们到密码文件。

$ ### First time: use -c to create the file
$ ### Use -m to use MD5 encryption of the password, which is more secure
$ htpasswd -cm /etc/svn-auth-file harry
New password: *****
Re-type new password: *****
Adding password for user harry
$ htpasswd -m /etc/svn-auth-file sally
New password: *******
Re-type new password: *******
Adding password for user sally
$

下一步,你需要在httpd.confLocation区里添加一些指示来告诉Apache如何来使用这些密码文件,AuthType指示指定系统使用的认证类型,这种情况下,我们需要指定Basic认证系统,AuthName是你提供给认证域一个任意名称,大多数浏览器会在向用户询问名称和密码的弹出窗口里显示这个名称,最终,使用AuthUserFile指示来指定使用htpasswd创建的密码文件的位置。

添加完这三个指示,你的<Location>区块一定像这个样子:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /etc/svn-auth-file
</Location>

这个<Location>区块还没有结束,还不能做任何有用的事情,它只是告诉Apache当需要授权时,要去向Subversion客户端索要用户名和密码。我们这里遗漏的,是一些告诉Apache什么样客户端需要授权的指示。哪里需要授权,Apache就会在哪里要求认证,最简单的方式是保护所有的请求,添加Require valid-user来告诉Apache任何请求需要认证的用户:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /etc/svn-auth-file
  Require valid-user
</Location>
授权选项

此刻,你已经配置了认证,但是没有配置授权,Apache可以要求用户认证并且确定身份,但是并没有说明这个身份的怎样允许和限制,这个部分描述了两种控制访问版本库的策略。

整体访问控制

最简单的访问控制形式是授权特定用户为只读版本库访问或者是读/写访问版本库。

你可以通过在<Location>区块添加Require valid-user指示来限制所有的版本库操作,使用我们前面的例子,这意味着只有客户端只可以是harry或者sally,而且他们必须提供正确的用户名及对应密码,这样允许对Subversion版本库做任何事:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn

  # how to authenticate a user
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /path/to/users/file

  # only authenticated users may access the repository
  Require valid-user
</Location>

有时候,你不需要这样严密,举个例子,Subversion自己在http://svn.collab.net/repos/svn的源代码允许全世界的人执行版本库的只读操作(例如检出我们的工作拷贝和使用浏览器浏览版本库),但是限定只有认证用户可以执行写操作。为了执行特定的限制,你可以使用LimitLimitExcept配置指示,就像Location指示,这个区块有开始和结束标签,你需要在<Location>中添加这个指示。

LimitLimitExcept中使用的参数是可以被这个区块影响的HTTP请求类型,举个例子,如果你希望禁止所有的版本库访问,只是保留当前支持的只读操作,你可以使用LimitExcept指示,并且使用GETPROPFINDOPTIONSREPORT请求类型参数,然后前面提到过的Require valid-user指示将会在<LimitExcept>区块中而不是在<Location>区块。

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn

  # how to authenticate a user
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /path/to/users/file

  # For any operations other than these, require an authenticated user.
  <LimitExcept GET PROPFIND OPTIONS REPORT>
    Require valid-user
  </LimitExcept>
</Location>

这里只是一些简单的例子,想看关于Apache访问控制Require指示的更深入信息,可以查看Apache文档中的教程集http://httpd.apache.org/docs-2.0/misc/tutorials.html中的Security部分。

每目录访问控制

也可以使用Apache的httpd模块mod_authz_svn更加细致的设置访问权限,这个模块收集客户端传递过来的不同的晦涩的URL信息,询问mod_dav_svn来解码,然后根据在配置文件定义的访问政策来裁决请求。

如果你从源代码创建Subversion,mod_authz_svn会自动附加到mod_dav_svn,许多二进制分发版本也会自动安装,为了验证它是安装正确,确定它是在httpd.confLoadModule指示中的mod_dav_svn后面:

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

为了激活这个模块,你需要配置你的Location区块的AuthzSVNAccessFile指示,指定保存路径中的版本库访问政策的文件。(一会儿我们将会讨论这个文件的格式。)

Apache非常的灵活,你可以从三种模式里选择一种来配置你的区块,作为开始,你选择一种基本的配置模式。(下面的例子非常简单;见Apache自己的文档中的认证和授权选项来查看更多的细节。)

最简单的区块是允许任何人可以访问,在这个场景里,Apache决不会发送认证请求,所有的用户作为“匿名”对待。

例 6.1. 匿名访问的配置实例。

<Location /repos>
  DAV svn
  SVNParentPath /usr/local/svn

  # our access control policy
  AuthzSVNAccessFile /path/to/access/file
</Location>
          

在另一个极端,你可以配置为拒绝所有人的认证,所有客户端必须提供证明自己身份的证书,你通过Require valid-user指示来阻止无条件的认证,并且定义一种认证的手段。

例 6.2. 一个认证访问的配置实例。

<Location /repos>
  DAV svn
  SVNParentPath /usr/local/svn

  # our access control policy
  AuthzSVNAccessFile /path/to/access/file

  # only authenticated users may access the repository
  Require valid-user

  # how to authenticate a user
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /path/to/users/file
</Location>

Apache和svnserve都可以给用户赋予(或拒绝)访问许可,通常是对整个版本库:一个用户可以读版本库(或不),而且他可以写版本库(或不)。如果可能,也可以定义细粒度的访问规则。一组用户可以有版本库的一个目录的读写权限,但是没有其它的;另一个目录可以是只对一少部分用户可读。

两种服务器都使用同样的文件格式描述路径为基础的规则,如果是Apache,需要加载mod_authz_svn模块,然后添加AuthzSVNAccessFile指示(在文件httpd.conf中)指明你的规则文件。(完全解释可以看“每目录访问控制”一节。)如果你在使用svnserve,你需要让你的authz-db变量(在svnserve.conf中)指向规则文件。

当你的服务器知道去查找规则文件时,就是需要定义规则的时候了。

访问文件的语法与svnserve.conf和运行中配置文件非常相似,以(#)开头的行会被忽略,在它的简单形式里,每一小节命名一个版本库和一个里面的路径,认证用户名是在每个小节中的选项名,每个选项的值描述了用户访问版本库的级别:r(只读)或者rw(读写),如果用户没有提到,访问是不允许的。

具体一点:这个小节的名称是[repos-name:path]或者[path]的形式,如果你使用SVNParentPath指示,指定版本库的名字是很重要的,如果你漏掉了他们,[/some/dir]部分就会与/some/dir的所有版本库匹配,如果你使用SVNPath指示,因此在你的小节中只是定义路径也很好—毕竟只有一个版本库。

[calc:/branches/calc/bug-142]
harry = rw
sally = r

在第一个例子里,用户harrycalc版本库中/branches/calc/bug-142具备完全的读写权利,但是用户sally只有读权利,任何其他用户禁止访问这个目录。

当然,访问控制是父目录传递给子目录的,这意味着我们可以为Sally指定一个子目录的不同访问策略:

[calc:/branches/calc/bug-142]
harry = rw
sally = r

# give sally write access only to the 'testing' subdir
[calc:/branches/calc/bug-142/testing]
sally = rw

现在Sally可以读取分支的testing子目录,但对其他部分还是只可以读,同时,Harry对整个分支还继续有完全的读写权限。

也可以通过继承规则明确的的拒绝某人的访问,只需要设置用户名参数为空:

[calc:/branches/calc/bug-142]
harry = rw
sally = r

[calc:/branches/calc/bug-142/secret]
harry =

在这个例子里,Harry对bug-142目录树有完全的读写权限,但是对其中的secret子目录没有任何访问权利。

需要记住的是最详细的的路径会被匹配,服务器首先找到匹配自己的目录,然后父目录,然后父目录的父目录,就这样继续下去,更具体的路径控制会覆盖所有继承下来的访问控制。

缺省情况下,没有人对版本库有任何访问,这意味着如果你已经从一个空文件开始,你会希望给所有用户对版本库根目录具备读权限,你可以使用星号(*)实现,用来代表“所有用户”:

[/]
* = r

这是一个普通的设置;注意在小节名中没有提到版本库名称,这让所有版本库对所有的用户可读。当所有用户对版本库有了读权利,你可以赋予特定用户对特定子目录的rw权限。

星号(*)参数需要在这里详细强调:这是匹配匿名用户的唯一模式,如果你已经配置了你的Location区块允许匿名和认证用户的混合访问,所有用户作为Apache匿名用户开始访问,mod_authz_svn会在要访问路径的定义中查找*值;如果找不到,Apache就会要求真实的客户端认证。

访问文件也允许你定义一组的用户,很像Unix的/etc/group文件:

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = harry, sally, joe, frank, sally, jane

组可以被赋予通用户一样的访问权限,使用“at”(@)前缀来加以区别:

[calc:/projects/calc]
@calc-developers = rw

[paint:/projects/paint]
@paint-developers = rw
jane = r

组中也可以定义为包含其它的组:

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = @calc-developers, @paint-developers
 

参考:

02 septiembre

MySQL优化实例

在Apache, PHP, MySQL的体系架构中,MySQL对于性能的影响最大,也是关键的核心部分。对于Discuz!论坛程序也是如此,MySQL的设置是否合理优化,直接影响到论坛的速度和承载量!同时,MySQL也是优化难度最大的一个部分,不但需要理解一些MySQL专业知识,同时还需要长时间的观察统计并且根据经验进行判断,然后设置合理的参数。

下面我们了解一下MySQL优化的一些基础,MySQL的优化我分为两个部分,一是服务器物理硬件的优化;二是MySQL自身(my.cnf)的优化。

(1) 服务器硬件对MySQL性能的影响

a) 磁盘寻道能力(磁盘I/O),以目前高转速SCSI硬盘(7200转/秒)为例,这种硬盘理论上每秒寻道7200次,这是物理特性决定的,没有办法改变。MySQL每秒钟都在进行大量、复杂的查询操作,对磁盘的读写量可想而知。所以,通常认为磁盘I/O是制约MySQL性能的最大因素之一,对于日均访问量在100万PV以上的Discuz!论坛,由于磁盘I/O的制约,MySQL的性能会非常低下!解决这一制约因素可以考虑以下几种解决方案:

使用RAID-0+1磁盘阵列,注意不要尝试使用RAID-5,MySQL在RAID-5磁盘阵列上的效率不会像你期待的那样快; 抛弃传统的硬盘,使用速度更快的闪存式存储设备。经过Discuz!公司技术工程的测试,使用闪存式存储设备可比传统硬盘速度高出6-10倍左右。

b) CPU 对于MySQL应用,推荐使用S.M.P.架构的多路对称CPU,例如:可以使用两颗Intel Xeon 3.6GHz的CPU。

c) 物理内存对于一台使用MySQL的Database Server来说,服务器内存建议不要小于2GB,推荐使用4GB以上的物理内存。

(2) MySQL自身因素当解决了上述服务器硬件制约因素后,让我们看看MySQL自身的优化是如何操作的。对MySQL自身的优化主要是对其配置文件my.cnf中的各项参数进行优化调整。下面我们介绍一些对性能影响较大的参数。

由于my.cnf文件的优化设置是与服务器硬件配置息息相关的,因而我们指定一个假想的服务器硬件环境:

CPU: 2颗Intel Xeon 2.4GHz

内存: 4GB DDR

硬盘: SCSI 73GB

下面,我们根据以上硬件配置结合一份已经优化好的my.cnf进行说明:

# vi /etc/my.cnf

以下只列出my.cnf文件中[mysqld]段落中的内容,其他段落内容对MySQL运行性能影响甚微,因而姑且忽略。

  [mysqld]
  port = 3306
  serverid = 1 
  socket = /tmp/mysql.sock
  skip-locking
  # 避免MySQL的外部锁定,减少出错几率增强稳定性。
  skip-name-resolve

禁止MySQL对外部连接进行DNS解析,使用这一选项可以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求!

back_log = 384

指定MySQL可能的连接数量。当MySQL主线程在很短的时间内接收到非常多的连接请求,该参数生效,主线程花费很短的时间检查连接并且启动一个新线程。

back_log参数的值指出在MySQL暂时停止响应新请求之前的短时间内多少个请求可以被存在堆栈中。 如果系统在一个短时间内有很多连接,则需要增大该参数的值,该参数值指定到来的TCP/IP连接的侦听队列的大小。不同的操作系统在这个队列大小上有它自己的限制。

试图设定back_log高于你的操作系统的限制将是无效的。默认值为50。对于Linux系统推荐设置为小于512的整数。

     key_buffer_size = 256M
  # key_buffer_size指定用于索引的缓冲区大小,增加它可得到更好的索引处理性能。
对于内存在4GB左右的服务器该参数可设置为256M或384M。
注意:该参数值设置的过大反而会是服务器整体效率降低! max_allowed_packet = 4M thread_stack = 256K table_cache = 128K sort_buffer_size = 6M

查询排序时所能使用的缓冲区大小。注意:该参数对应的分配内存是每连接独占!如果有100个连接,那么实际分配的总共排序缓冲区大小为100 × 6 = 600MB。所以,对于内存在4GB左右的服务器推荐设置为6-8M。

 read_buffer_size = 4M

读查询操作所能使用的缓冲区大小。和sort_buffer_size一样,该参数对应的分配内存也是每连接独享!

join_buffer_size = 8M

联合查询操作所能使用的缓冲区大小,和sort_buffer_size一样,该参数对应的分配内存也是每连接独享!

     myisam_sort_buffer_size = 64M
    table_cache = 512
    thread_cache_size = 64
    query_cache_size = 64M

指定MySQL查询缓冲区的大小。可以通过在MySQL控制台执行以下命令观察:

 # > SHOW VARIABLES LIKE '%query_cache%';
 # > SHOW STATUS LIKE 'Qcache%';
 # 如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况;

如果Qcache_hits的值非常大,则表明查询缓冲使用非常频繁,如果该值较小反而会影响效率,那么可以考虑不用查询缓冲;Qcache_free_blocks,如果该值非常大,则表明缓冲区中碎片很多。

     tmp_table_size = 256M
    max_connections = 768

指定MySQL允许的最大连接进程数。如果在访问论坛时经常出现Too Many Connections的错误提 示,则需要增大该参数值。

     max_connect_errors = 10000000
    wait_timeout = 10

指定一个请求的最大连接时间,对于4GB左右内存的服务器可以设置为5-10。

     thread_concurrency = 8

该参数取值为服务器逻辑CPU数量×2,在本例中,服务器有2颗物理CPU,而每颗物理CPU又支持H.T超线程,所以实际取值为4 × 2 = 8

    skip-networking

开启该选项可以彻底关闭MySQL的TCP/IP连接方式,如果WEB服务器是以远程连接的方式访问MySQL数据库服务器则不要开启该选项!否则将无法正常连接!

 

参考:http://kb.discuz.net/index.php?title=MySQL%E4%BC%98%E5%8C%96%E5%AE%9E%E4%BE%8B

21 julio

UserType for persisting a Typesafe Enumeration with a VARCHAR column [转]

Use this Typesafe Enumeration class in your domain model as a simple property type (e.g. of the Comment class in a forum):

public class Rating implements Serializable {

    private String name;

    public static final Rating EXCELLENT = new Rating("Excellent");
    public static final Rating OK = new Rating("OK");
    public static final Rating LOW = new Rating("Low");
    private static final Map INSTANCES = new HashMap();

    static {
        INSTANCES.put(EXCELLENT.toString(), EXCELLENT);
        INSTANCES.put(OK.toString(), OK);
        INSTANCES.put(LOW.toString(), LOW);
    }

    private Rating(String name) {
        this.name=name;
    }

    public String toString() {
        return name;
    }

    private Object readResolve() {
        return getInstance(name);
    }

    public static Rating getInstance(String name) {
        return (Rating)INSTANCES.get(name);
    }
}

Next, write the Hibernate custom mapping type:

public class RatingUserType implements UserType {

    private static final int[] SQL_TYPES = {Types.VARCHAR};

    public int[] sqlTypes() { return SQL_TYPES; }
    public Class returnedClass() { return Rating.class; }
    public boolean equals(Object x, Object y) { return x == y; }
    public Object deepCopy(Object value) { return value; }
    public boolean isMutable() { return false; }

    public Object nullSafeGet(ResultSet resultSet,
                              String[] names,
                              Object owner)
            throws HibernateException, SQLException {

      String name = resultSet.getString(names[0]);
      return resultSet.wasNull() ? null : Rating.getInstance(name);
    }

    public void nullSafeSet(PreparedStatement statement,
                            Object value,
                            int index)
            throws HibernateException, SQLException {

        if (value == null) {
            statement.setNull(index, Types.VARCHAR);
        } else {
            statement.setString(index, value.toString());
        }
    }
}

Finally, in your mapping files, bind it all together:

<hibernate-mapping package="org.hibernate.auction.model">

<class name="Comment"
       table="COMMENTS"
       lazy="true">

    <!-- Common id property. -->
    <id name="id"
        type="long"
        column="COMMENT_ID"
        unsaved-value="null">
        <generator class="native"/>
    </id>

    <!-- Simple property. -->
    <property
        name="rating"
        column="RATING"
        type="org.hibernate.auction.persistence.RatingUserType"
        not-null="true"
        update="false"/>
...
</class>
</hibernate-mapping>

You may also use your Rating enumerated type in Hibernate queries:

Query q = session.createQuery("from Comment c where c.rating = :rating");
q.setParameter("rating",
               Rating.LOW,
               Hibernate.custom(RatingUserType.class));

This is all there is to know about it. You may enhance this strategy with a generic UserType implementation, that knows how to persist different Typesafe Enumerations, this is described here.

The examples and domain model you have just seen can be found in Hibernate in Action.

Christian

Reference:  1. http://www.hibernate.org/172.html

枚举类型的映射和表示 [转]

在项目中常遇到用数字表示一种意思的字段,例如:type ,1 表示正常状态,0表示冻结状态;这是一类典型的问题,即枚举类型的映射和表示。在Hibernate2.1版本中提供了enum的接口类型来处理这类问题,参考这个讨论:

http://forum.javaeye.com/viewtopic.php?t=8114&postdays=0&postorder=asc&highlight=enum&start=0

但是在Hibernate3中取消了enum类型的支持,Hibernate官方推荐使用UserType接口。不过使用UserType接口实际上会比较复杂,下面我将使用一个简单的案例来讲解使用UserType接口来解决这类问题的办法:

 

package com.javaeye.simple.domain;

import java.io.Serializable;

public class UserTitle implements Serializable {

    private int title;

    public static final UserTitle EMPLOYEE = new UserTitle(0);

    public static final UserTitle MANAGER = new UserTitle(1);

    public static final UserTitle DIRECTOR = new UserTitle(2);

    public static final UserTitle CEO = new UserTitle(3);

    private UserTitle(int title) {
        this.title = title;
    }

    public int toInt() {
        return title;
    }

    public static UserTitle fromInt(int title) {
        switch (title) {
        case 0:
            return EMPLOYEE;
        case 1:
            return MANAGER;
        case 2:
            return DIRECTOR;
        case 3:
            return CEO;
        default:
            throw new RuntimeException("Unknown Status");
        }
    }

    public String toString() {
        switch (title) {
        case 0:
            return "雇员";
        case 1:
            return "经理";
        case 2:
            return "总监";
        case 3:
            return "CEO";
        default:
            throw new RuntimeException("Unknown Status");
        }
    }

}

注意,我们需要一个UserTitleType的类,实现UserType接口,定义一个User对象的UserTitle属性是如何向数据库持久化的:

package com.javaeye.simple.domain;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Types;
import java.sql.SQLException;

import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class UserTitleType implements UserType {

    public int[] sqlTypes() {
        int[] types = {Types.INTEGER};       
        return types;
    }

    public Class returnedClass() {
        return UserTitle.class;
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        return x == y;
    }

    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
        int title = rs.getInt(names[0]);
        return rs.wasNull() ? null : UserTitle.fromInt(title);
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.INTEGER);
        } else {
            st.setInt(index, ((UserTitle) value).toInt());
        }
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public boolean isMutable() {
        return false;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) deepCopy(value);
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return deepCopy(cached);
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return deepCopy(original);
    }

}

注意nullSafeGet和nullSafeSet方法的代码,是如何实现UserTitle的字符串值和数据库中保存数字进行转换的。
最后是映射文件:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.javaeye.simple.domain">

    <class name="User">
        <id name="id" unsaved-value="null">
            <generator class="native"/>
        </id>
        <property name="username"/>
        <property name="password"/>
        <property name="gender"/>
        <property name="age"/>
        <property name="department"/>
        <property name="mail"/>
        <property name="birthday"/>
        <property name="title" type="com.javaeye.simple.domain.UserTitleType">
        </property>
    </class>
</hibernate-mapping>

指明title属性是用户自定义的某类型。

在Java程序中指定user的title属性:
user.setTitle(UserTitle.MANAGER);
session.save(user);
在页面输出:
<% user.getTitle() %>
就可以打印出中文字符串表示。


参考:
1. http://www.javaeye.com/topic/15849
2. http://www.hibernate.org/172.html

18 julio

深树

1. deeptree.jsp中定义css为deeptree.css,并定义<div id="deeptree" class="deeptree" CfgXMLSrc="deeptreeconfig.xml">
2. deeptree.css中定义了class=deeptree的behavior为:url(deeptree.htc)
3. deeptree.htc中包含对deeptree的树操作和XML操作和其它页面行为操作的定义。其初始化数据来自deeptreeconfig.xml.使用了deeptree.xsl来处理树相关的操作。
4. 树的信息来自于TreeRoot.xml和Tree688.xml等这样的数据源。

参考:1. Deeptree下载

Apache Lenya2.0中的configure tools

主要类图:

1

主要流程:
ConfigureCommandLine从Configuration中读取选择的default和local的属性,如果在控制台输入新的值,则替换为新值。最后写下这些local参数。  

17 julio

Keycode对照表

字母和数字键的键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
A 65 J 74 S 83 1 49
B 66 K 75 T 84 2 50
C 67 L 76 U 85 3 51
D 68 M 77 V 86 4 52
E 69 N 78 W 87 5 53
F 70 O 79 X 88 6 54
G 71 P 80 Y 89 7 55
H 72 Q 81 Z 90 8 56
I 73 R 82 0 48 9 57

 

数字键盘上的键的键码值(keyCode) 功能键键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
0 96 8 104 F1 112 F7 118
1 97 9 105 F2 113 F8 119
2 98 * 106 F3 114 F9 120
3 99 + 107 F4 115 F10 121
4 100 Enter 108 F5 116 F11 122
5 101 - 109 F6 117 F12 123
6 102 . 110        
7 103 / 111        

 

控制键键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
BackSpace 8 Esc 27 Right Arrow 39 -_ 189
Tab 9 Spacebar 32 Dw Arrow 40 .> 190
Clear 12 Page Up 33 Insert 45 /? 191
Enter 13 Page Down 34 Delete 46 `~ 192
Shift 16 End 35 Num Lock 144 [{ 219
Control 17 Home 36 ;: 186 \| 220
Alt 18 Left Arrow 37 =+ 187 ]} 221
Cape Lock 20 Up Arrow 38 ,< 188 '" 222

多媒体键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
音量加 175            
音量减 174            
停止 179            
静音 173            
浏览器 172            
邮件 180            
搜索 170            
收藏 171            

 

应用:1.限制输入数字

<input style="ime-mode : disabled"  onKeyPress="if ((event.keyCode<48 || event.keyCode>57)) event.returnValue=false">

参考:1. Keycode对照表

09 julio

SSL Configuration HOW-TO

Configuration

Note that JSSE is bundled with Sun's JDK 1.4 and later, so if you're using JDK 1.4 and later, you can skip this step.

Tomcat currently operates with JKS, PKCS11 or PKCS12 format keystores. The JKS format is Java's standard "Java KeyStore" format, and is the format created by the keytool command-line utility. This tool is included in the JDK.

To create a new keystore from scratch, containing a single self-signed Certificate, execute the following from a terminal command line:

%JAVA_HOME%\bin\keytool -genkey -alias tomcat -keyalg RSA
This command will create a new file, in the home directory of the user under which you run it, named ".keystore".
To specify a different location or filename, add the -keystore parameter, followed by the complete pathname to your 
keystore file, to the keytool command shown above.
%JAVA_HOME%\bin\keytool -genkey -alias tomcat -keyalg RSA -keystore \path\to\my\keystore
You MUST use the same password here as was used for the keystore password itself.

Note: your private key password and keystore password should be the same. If they differ, you will get an error along the lines of java.io.IOException: Cannot recover key, as documented in Bugzilla 38217, which contains further references for this issue.

The final step is to configure your secure socket in the $CATALINA_HOME/conf/server.xml file, where $CATALINA_HOME represents the directory into which you installed Tomcat 5. An example <Connector> element for an SSL connector is included in the default server.xml file installed with Tomcat. It will look something like this:

<-- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->
<!--
<Connector 
           port="8443" minProcessors="5" maxProcessors="75"
           enableLookups="true" disableUploadTimeout="true"
           acceptCount="100" debug="0" scheme="https" secure="true";
           clientAuth="false" sslProtocol="TLS"/>
-->

You will need to remove the comment tags around it.Then, you can customize the specified attributes as necessary.If you change the port number here, you should also change the value specified for the redirectPort attribute on the non-SSL connector.

Attribute Description
keystoreFile Add this attribute if the keystore file you created is not in the default place that Tomcat expects (a file named .keystore in the user home directory under which Tomcat is running). You can specify an absolute pathname, or a relative pathname that is resolved against the $CATALINA_BASE environment variable.
keystorePass Add this element if you used a different keystore (and Certificate) password than the one Tomcat expects (changeit).
... ...

After completing these configuration changes, you must restart Tomcat as you normally do, and you should be in business. You should be able to access any web application supported by Tomcat via SSL.

Reference:

  1. The Apache Tomcat 5.5 Servlet/JSP Container SSL Configuration HOW-TO
04 julio

eclipse内置resin的方法

此配置适用于Resin2.x和Resin3.x版本。运行时需要在每个project目录下放置一个resin.conf文件,由于Resin2.x和Resin3.x版本的配置文件不同,所以需要两个文件,resin.conf文件用于启动resin2.x,resin30.conf用于启动resin3.x。

以下说明以配置Resin2.1.16版本为例,具体步骤如下:
1、打开“window->Preferences->Java->Build Path->Classpath Variables”,添加classpath variables,将基配置到Resin的安装目录。比如在我的机器上配置为:Resin_2.1.16 -D:/Program/JavaSoft/resin-2.1.16。最好同时配置一个JDK_TOOLS,指定到java_home下的lib/tools.jar文件上,否则可能会在控制台输出时中文显示成乱码。
2、运行菜单“Run->Run...”,在弹出的窗口中添加一个Java Application的运行项目。
3、项目名称随便添写,只要能区分出不同的应用就可以了。如:resin_2.1.16。在Project一项置为空,如果已经有内容了则将其删除,这要才能保证在每个项目中都可以运行。
4、设置项目的启动类(Main Class),Resin2.x为com.caucho.server.http.HttpServer,Resin3.x为com.caucho.server.http.ResinServer。
5、配置Arguments,在Program arguments中填写-conf "${project_loc}\resin.conf",(如果配置Resin3.x则填写-conf "${project_loc}\resin30.conf")。
6、在VM arguments中为空(如果是Resin3.x则填写-Djava.util.logging.manager=com.caucho.log.LogManagerImpl)
7、指定Working directory为resin的安装目录。
8、配置JRE,保持默认配置即可,如果是resin3.x版本可能需要jre5.0。
9、配置classpath,选择Bootstrap Entries,点击Advanced,选择Add ClasspathVariables,选择Resin_2.1.16,点击Extend,将lib目录下的所有jar文件选中,添加到启动项目中,然后将JDK_TOOLS也加到启动项目中。
10、保存配置。在项目的根目录下放置resin.conf,在eclipse的Run菜单中将Resin_2.1.16加入到收藏中,选择项目或是项目中的某个文件,然后运行Resin_2.1.16即可。

29 mayo

修改Oracle9i中XDB的http和ftp服务端口

安装Oracle 9i,其安装缺省包含了XDB。在启动数据库后,Oracle XDB的http服务将会自动占用了8080端口,使用下面方法修改XDB的http和ftp服务端口:

1.使用dbms_xdb包修改端口设置

使用sys登录sqlplus

sqlplus sys/syspassword as sysdba

执行如下的脚本:

============================ 
-- Change the HTTP/WEBDAV port from 8080 to 8081 
call dbms_xdb.cfg_update(updateXML( 
dbms_xdb.cfg_get() 
, '/xdbconfig/sysconfig/protocolconfig/httpconfig/http-port/text()' 
, 8081)) 
/ 
-- Change the FTP port from 2100 to 2111 
call dbms_xdb.cfg_update(updateXML( 
dbms_xdb.cfg_get() 
, '/xdbconfig/sysconfig/protocolconfig/ftpconfig/ftp-port/text()' 
, 2111)) 
/ 
COMMIT; 
EXEC dbms_xdb.cfg_refresh;
 
参考
1. 轻松解决Oracle XDB的8080端口冲突问题
20 noviembre

Hierarchical Queries

CONNECT BY Example

The following hierarchical query uses the CONNECT BY clause to define the relationship between employees and managers:

SELECT employee_id, last_name, manager_id
   FROM employees
   CONNECT BY PRIOR employee_id = manager_id;

 

START WITH Examples

The next example adds a START WITH clause to specify a root row for the hierarchy and an ORDER BY clause using the SIBLINGS keyword to preserve ordering within the hierarchy:

SELECT last_name, employee_id, manager_id, LEVEL
      FROM employees
      START WITH employee_id = 100
      CONNECT BY PRIOR employee_id = manager_id
      ORDER SIBLINGS BY last_name;
15 agosto

Prototype JavaScript Framework Review (Cont'd)

String.prototype 的扩展:

strip: function() {
  return this.replace(/^\s+/, '').replace(/\s+$/, '');
},

Introduction to Ajax
new Ajax.Request('/some_url', { method:'get' });

16 julio

对对红随机分组的小程序

<html>
    <head>
        <script type="text/javascript">
            function Department(name){
                this.name=name;
                this.persons=new Array();
                this.add=function(person){
                    this.persons.push(person);
                };   
                this.indexOf=function(person){
                    var i;
                    for(i=this.persons.length-1;i>=0;i--){
                        if(this.persons[i]==person) break;   
                    }
                    return i;
                };           
                this.remove=function(person){
                    var i=this.indexOf(person);
                    if(i >= 0){           
                        this.persons.splice(i,1);
                    }
                };
                this.isAllAssigned=function(){
                    return this.persons.length==0;   
                };
            }   
            function Person(name,department){
                this.name=name;
                this.department=department;               
                this.department.add(this);
                this.setAssigned=function(assigned) {
                    if (assigned) {
                        this.department.remove(this);
                    }
                };
            }                   
            function Team(){
                this.departments=new Array();
                this.addAll=function(departments){
                    this.departments=this.departments.concat(departments);
                };
                this.add=function(department){
                    this.departments.push(department);
                };   
                this.size=function(){
                    return this.departments.length;
                }
                this.indexOf=function(department){
                    var i;
                    for(i=this.departments.length-1;i>=0;i--){
                        if(this.departments[i]==department){
                            break;   
                        }   
                    }
                    return i;
                };
                this.remove=function(department){
                    var i=this.indexOf(department);
                    if(i>=0){
                        this.departments.splice(i,1);
                    }
                };
            }
            function Partner(A,B){
                this.A=A;
                this.B=B;
            }
            function PartnerMaker(){
                this.team=new Team();
                this.partners=new Array();
                this.init=function(){
                    var tech=new Department("TECH");
                    new Person('H',tech);
                    new Person('Y',tech);
                    new Person('F',tech);
                    var market = new Department("MARKET");
                    new Person('L',market);
                    new Person('D',market);
                    var edu = new Department("EDU");
                    new Person('F',edu);
                    new Person('Z',edu);
                    new Person('S',edu);
                    new Person('W',edu);
                    var hr = new Department("HR");
                    new Person('Z,hr);                   
                    new Person('W',hr);
                    new Person('Z',hr);
                    var web = new Department("WEB");
                    new Person('Q',web);                   
                    new Person('R',web);
                    var sys = new Department("SYS");
                    new Person("Z",sys);
                    var lesson = new Department("LESSON");
                    new Person("G",lesson);
                    var departments=[tech,market,edu,hr,web,sys,lesson];
                    this.team.addAll(departments);
                };
                this.randomDepartmentNum=function(){                   
                    if(this.team.size()==1){
                        return 0;
                    }
                    var i =    Math.floor(Math.random()*this.team.size());
                    return i;
                };
                this.randomPersonNum=function(persons){
                    if(persons.length==1){
                        return 0;
                    }
                    var i = Math.floor(Math.random()*persons.length);
                    return i;
                };
                this.remainPersons=function(exclusiveDepNum){
                    var persons = new Array();
                    for(var i=0;i<this.team.size();i++){
                        if(i==exclusiveDepNum) continue;
                        persons = persons.concat(this.team.departments[i].persons);   
                    }   
                    return persons;
                };
                this.assignPartners=function(){           
                    while(this.team.size() > 1){                   
                        var depNum = this.randomDepartmentNum();
                        var depA = this.team.departments[depNum];
                        var aNum = this.randomPersonNum(depA.persons);
                        var personA=depA.persons[aNum];
                        var others = this.remainPersons(depNum);
                        var bNum = this.randomPersonNum(others);
                        var personB=others[bNum];
                        var partner = new Partner(personA, personB);
                        this.partners.push(partner);
                        personA.setAssigned(true);
                        personB.setAssigned(true);           
                        if (personA.department.isAllAssigned()) {
                            this.team.remove(personA.department);
                        }
                        if (personB.department.isAllAssigned()) {
                            this.team.remove(personB.department);
                        }
                    }
                };
                this.alertPartners=function() {
                    for (var i = 0; i < this.partners.length; i++) {
                        var a = this.partners[i].A;
                        var b = this.partners[i].B;
                        alert("Partner["+i+"]="+a.name+"@"+ a.department.name+","+b.name+"@"+b.department.name);
                    }
                    for (var j = 0; j < this.team.departments.length; j++) {
                        var dep = this.team.departments[j];
                        for (var k = 0; k < dep.persons.length; k++) {
                            var p = dep.persons[k];
                            alert("落单学员:" + p.name + "@" + p.department.name);
                        }
                    }
                };   
                this.printPartners=function() {
                    var tbody=document.getElementById("TBODY_0");
                    tbody.removeNode(true);
                    tbody = document.createElement("TBODY");
                    tbody.id="TBODY_0";
                    document.getElementById("TABLE_0").appendChild(tbody);
                    for (var i = 0; i < this.partners.length; i++) {
                        var a = this.partners[i].A;
                        var b = this.partners[i].B;
                        var tr = createTR();
                        var td1 = createTD(a.name+"@"+ a.department.name);
                        var td2 = createTD(b.name+"@"+b.department.name);
                        tr.appendChild(td1);
                        tr.appendChild(td2);
                        tbody.appendChild(tr);
                    }
                    for (var j = 0; j < this.team.departments.length; j++) {
                        var dep = this.team.departments[j];
                        for (var k = 0; k < dep.persons.length; k++) {
                            var p = dep.persons[k];
                            var tr = createTR();
                            var td = createTD("落单学员:" + p.name + "@" + p.department.name);
                            tr.appendChild(td);
                            tbody.appendChild(tr);
                        }
                    }
                };               
            }
            function createTR(){
                var tr = document.createElement("TR");
                return tr;
            }
            function createTD(innerText){
                var td = document.createElement("TD");   
                td.innerText=innerText;
                return td;
            }           
            var buttonText=[" 再来一次 "," 事不过三 "," 你逗我玩啊? "," 最后一次! "," 周末休息 :) "];
            var textIndex=0;
            function main(elem){               
                if(!elem){
                    var btn = document.getElementById("BUTTON_0");
                    btn.disabled=false;
                    textIndex=0;
                    btn.value=" 听天由命 ";
                    return;
                }
                elem.value=buttonText[textIndex++];
                if(textIndex==buttonText.length){
                    elem.disabled=true;   
                    setTimeout("main()",2000);                   
                }
                var maker = new PartnerMaker();
                maker.init();
                maker.assignPartners();
                //maker.alertPartners();
                maker.printPartners();
            }
            function doCheat(){
                alert("嘿嘿,C老师不容许!");   
            }
        </script>                   
    </head>
    <body align="center">       
        <table>
            <tr>
                <td width="75%" valign="top">
                    <table width="100%" border="1" cellpadding="0" cellspacing="0" id="TABLE_0">
                        <thead>
                            <tr><th colspan="2">“一帮一 对对红”随机分组情况</th></tr>
                            <tr><th>甲</td><td>乙</th></tr>
                        </thead>
                        <tbody id="TBODY_0">
                        </tbody>
                    </table>
                </td>
                <td>
                    <img src="http://upload.wikimedia.org/wikipedia/en/d/de/Amaryllis_fl.jpg"/>   
                </td>
            </tr>
        </table>
        <input type="button" id="BUTTON_0" value=" 听天由命 " onClick="main(this)"/> <input type="button" value=" 我要作弊 " onClick="doCheat()"/>
    </body>
</html>

20 junio

编写单点登录(SSO)服务

一. 项目中涉及到编写单点登录(SSO)服务。主要涉及两个场景:

  1. 用户在第一个Web应用登录时,保存加密过的用户的认证信息到Cookie中。其中Cookie中token的格式为: username + ":"+Md5Hex(username + ":"+ password + ":" + key),并使用Base64进行编码。
  2. 用户在访问另一个Web应用资源时,这时通过获取Cookie中的token信息来实现自动登录。

二、相关的活动图:

  • 第2个场景

sso1 sso2

三、参考

  1. 编写你自己的单点登录(SSO)服务,王昱, http://blog.csdn.net/javachannel/archive/2006/05/24/752437.aspx 
  2. HTTP State Management Mechanism, RFC2109, http://www.faqs.org/rfcs/rfc2109.html
13 junio

理解ORACLE数据库字符集 [转]

耿立宏
(中国科学院计算机网络信息中心,北京100080)

一.引言

ORACLE数据库字符集,即Oracle全球化支持(Globalization Support),或即国家语言支持(NLS)其作用是用本国语言和格式来存储、处理和检索数据。利用全球化支持,ORACLE为用户提供自己熟悉的数据库母语环境,诸如日期格式、数字格式和存储序列等。Oracle可以支持多种语言及字符集,其中oracle8i支持48种语言、76个国家地域、229种字符集,而oracle9i则支持57种语言、88个国家地域、235种字符集。由于oracle字符集种类多,且在存储、检索、迁移oracle数据时多个环节与字符集的设置密切相关,因此在实际的应用中,数据库开发和管理人员经常会遇到有关oracle字符集方面的问题。本文通过以下几个方面阐述,对oracle字符集做简要分析

二.字符集基本知识

2.1字符集
    实质就是按照一定的字符编码方案,对一组特定的符号,分别赋予不同数值编码的集合。Oracle数据库最早支持的编码方案是US7ASCII。
    Oracle的字符集命名遵循以下命名规则:
    <Language><bit size><encoding>
    即:  <语言><比特位数><编码>
    比如: ZHS16GBK表示采用GBK编码格式、16位(两个字节)简体中文字符集

2.2字符编码方案
2.2.1 单字节编码
    (1)单字节7位字符集,可以定义128个字符,最常用的字符集为US7ASCII
    (2)单字节8位字符集,可以定义256个字符,适合于欧洲大部分国家
    例如:WE8ISO8859P1(西欧、8位、ISO标准8859P1编码)
2.2.2 多字节编码
    (1)变长多字节编码
    某些字符用一个字节表示,其它字符用两个或多个字符表示,变长多字节编码常用于对亚洲语言的支持,   例如日语、汉语、印地语等
    例如:AL32UTF8(其中AL代表ALL,指适用于所有语言)、zhs16cgb231280
    (2)定长多字节编码
    每一个字符都使用固定长度字节的编码方案,目前oracle唯一支持的定长多字节编码是AF16UTF16,也是仅用于国家字符集
2.2.3 unicode编码
    Unicode是一个涵盖了目前全世界使用的所有已知字符的单一编码方案,也就是说Unicode为每一个字符提供唯一的编码。UTF-16是unicode的16位编码方式,是一种定长多字节编码,用2个字节表示一个unicode字符,AF16UTF16是UTF-16编码字符集。
    UTF-8是unicode的8位编码方式,是一种变长多字节编码,这种编码可以用1、2、3个字节表示一个unicode字符,AL32UTF8,UTF8、UTFE是UTF-8编码字符集

2.3 字符集超级
    当一种字符集(字符集A)的编码数值包含所有另一种字符集(字符集B)的编码数值,并且两种字符集相同编码数值代表相同的字符时,则字符集A是字符集B的超级,或称字符集B是字符集A的子集。
    Oracle8i和oracle9i官方文档资料中备有子集-超级对照表(subset-superset pairs),例如:WE8ISO8859P1是WE8MSWIN1252的子集。由于US7ASCII是最早的Oracle数据库编码格式,因此有许多字符集是US7ASCII的超集,例如WE8ISO8859P1、ZHS16CGB231280、ZHS16GBK都是US7ASCII的超集。

2.4 数据库字符集(oracle服务器端字符集)
    数据库字符集在创建数据库时指定,在创建后通常不能更改。在创建数据库时,可以指定字符集(CHARACTER SET)和国家字符集(NATIONAL CHARACTER SET)。
2.4.1字符集
    (1)用来存储CHAR, VARCHAR2, CLOB, LONG等类型数据
    (2)用来标示诸如表名、列名以及PL/SQL变量等
    (3)用来存储SQL和PL/SQL程序单元等
2.4.2国家字符集:
    (1)用以存储NCHAR, NVARCHAR2, NCLOB等类型数据
    (2)国家字符集实质上是为oracle选择的附加字符集,主要作用是为了增强oracle的字符处理能力,因为NCHAR数据类型可以提供对亚洲使用定长多字节编码的支持,而数据库字符集则不能。国家字符集在oracle9i中进行了重新定义,只能在unicode编码中的AF16UTF16和UTF8中选择,默认值是AF16UTF16
2.4.3查询字符集参数
    可以查询以下数据字典或视图查看字符集设置情况
    nls_database_parameters、props$、v$nls_parameters
    查询结果中NLS_CHARACTERSET表示字符集,NLS_NCHAR_CHARACTERSET表示国家字符集
2.4.4修改数据库字符集
    按照上文所说,数据库字符集在创建后原则上不能更改。如果需要修改字符集,通常需要导出数据库数据,重建数据库,再导入数据库数据的方式来转换,或通过ALTER DATABASE CHARACTER SET语句修改字符集,但创建数据库后修改字符集是有限制的,只有新的字符集是当前字符集的超集时才能修改数据库字符集,例如UTF8是US7ASCII的超集,修改数据库字符集可使用ALTER DATABASE CHARACTER SET UTF8。

2.5 客户端字符集(NLS_LANG参数)
2.5.1客户端字符集含义
    客户端字符集定义了客户端字符数据的编码方式,任何发自或发往客户端的字符数据均使用客户端定义的字符集编码,客户端可以看作是能与数据库直接连接的各种应用,例如sqlplus,exp/imp等。客户端字符集是通过设置NLS_LANG参数来设定的。
2.5.2 NLS_LANG参数格式
    NLS_LANG=<language>_<territory>.<client character set>
    Language:显示oracle消息,校验,日期命名
    Territory:指定默认日期、数字、货币等格式
    Client character set:指定客户端将使用的字符集
    例如:NLS_LANG=AMERICAN_AMERICA.US7ASCII 
    AMERICAN是语言,AMERICA是地区,US7ASCII是客户端字符集
2.5.3客户端字符集设置方法
     1)UNIX环境
         $NLS_LANG=“simplified chinese”_china.zhs16gbk
         $export NLS_LANG
         编辑oracle用户的profile文件
    2)Windows环境
         编辑注册表
         Regedit.exe---HKEY_LOCAL_MACHINE---SOFTWARE---ORACLE—HOME0
2.5.4 NLS参数查询
    Oracle提供若干NLS参数定制数据库和用户机以适应本地格式,例如有NLS_LANGUAGE,NLS_DATE_FORMAT,NLS_CALENDER等,可以通过查询以下数据字典或v$视图查看。
    NLS_DATABASE_PARAMETERS--显示数据库当前NLS参数取值,包括数据库字符集取值
    NLS_SESSION_PARAMETERS--显示由NLS_LANG 设置的参数,或经过alter session 改变后的参数值(不包括由NLS_LANG 设置的客户端字符集)
    NLS_INSTANCE_PARAMETE--显示由参数文件init<SID>.ora 定义的参数V$NLS_PARAMETERS--显示数据库当前NLS参数取值
2.5.5修改NLS参数
    使用下列方法可以修改NLS参数
    (1)修改实例启动时使用的初始化参数文件
    (2)修改环境变量NLS_LANG
    (3)使用ALTER SESSION语句,在oracle会话中修改
    (4)使用某些SQL函数
    NLS作用优先级别:Sql function>alter session>环境变量或注册表>参数文件>数据库默认参数

三.导入/导出与字符集转换

3.1 EXP/IMP
    Export 和 Import 是一对读写Oracle数据的工具。Export 将 Oracle 数据库中的数据输出到操作系统文件中, Import 把这些文件中的数据读到Oracle 数据库中,由于使用exp/imp进行数据迁移时,数据从源数据库到目标数据库的过程中有四个环节涉及到字符集,如果这四个环节的字符集不一致,将会发生字符集转换。

EXP                       

IMP                       

    四个字符集是
   (1)源数据库字符集
   (2)Export过程中用户会话字符集(通过NLS_LANG设定)
   (3)Import过程中用户会话字符集(通过NLS_LANG设定)
   (4)目标数据库字符集

3.2导出的转换过程
    在Export过程中,如果源数据库字符集与Export用户会话字符集不一致,会发生字符集转换,并在导出文件的头部几个字节中存储Export用户会话字符集的ID号。在这个转换过程中可能发生数据的丢失。
例:如果源数据库使用ZHS16GBK,而Export用户会话字符集使用US7ASCII,由于ZHS16GBK是16位字符集,而US7ASCII是7位字符集,这个转换过程中,中文字符在US7ASCII中不能够找到对等的字符,所以所有中文字符都会丢失而变成“?? ”形式,这样转换后生成的Dmp文件已经发生了数据丢失。
因此如果想正确导出源数据库数据,则Export过程中用户会话字符集应等于源数据库字符集或是源数据库字符集的超集

3.3导入的转换过程
    (1)确定导出数据库字符集环境
    通过读取导出文件头,可以获得导出文件的字符集设置
    (2)确定导入session的字符集,即导入Session使用的NLS_LANG环境变量
    (3)IMP读取导出文件
    读取导出文件字符集ID,和导入进程的NLS_LANG进行比较
    (4)如果导出文件字符集和导入Session字符集相同,那么在这一步骤内就不需要转换,如果不同,就需要把数据转换为导入Session使用的字符集。可以看出,导入数据到数据库过程中发生两次字符集转换
    第一次:导入文件字符集与导入Session使用的字符集之间的转换,如果这个转换过程不能正确完成,Import向目标数据库的导入过程也就不能完成。
    第二次:导入Session字符集与数据库字符集之间的转换。
    然而,oracle8i的这种转换只能在单字节字符集之间进行,oracle8i导入Session不支持多字节字符集之间的转换,因此为了避免第一次转换,导入Session使用的NLS_LANG与导出文件字符集相同,第二次转换(通过SQL*Net)支持任何两种字符集。以上情况在Oracle9i中略有不同

四.乱码问题

    oracle在数据存储、迁移过程中经常发生字符乱码问题,归根到底是由于字符集使用不当引起。下面以使用客户端sqlplus向数据库插入数据和导入/导出(EXP/IMP)过程为例,说明乱码产生的原因。

4.1使用客户端sqlplus向数据库存储数据
    这个过程存在3个字符集设置
    (1)客户端应用字符集
    (2)客户端NLS_LANG参数设置
    (3)服务器端数据库字符集(Character Set)设置
    客户端应用sqlplus中能够显示什么样的字符取决于客户端操作系统语言环境(客户端应用字符集),但在应用中录入这些字符后,这些字符能否在数据库中正常存储,还与另外两个字符集设置紧密相关,其中客户端NLS_LANG参数主要用于字符数据传输过程中的转换判断。常见的乱码大致有两种情形:
    (1)汉字变成问号“?”;
当从字符集A 转换成字符集B时,如果转换字符之间不存在对应关系,NLS_LANG使用替代字符“?”替代无法映射的字符
    (2)汉字变成未知字符(虽然有些是汉字,但与原字符含义不同)
转换存在对应关系,但字符集A 中的字符编码与字符集B 中的字符编码代表不同含义

4.2发生乱码原因
    乱码产生是由于几个字符集之间转换不匹配造成,分以下几种情况:
    (注:字符集之间如果不存在子集、超集对应关系时的情况不予考虑,因为这种情况下字符集之间转换必产生乱码)   
    1)服务器端数据库字符集与客户端应用字符集相同,与客户端NLS_LANG参数设置不同
    如果客户端NLS_LANG字符集是其它两种字符集的子集,转换过程将出现乱码。
    解决方法:将三种字符集设置成同一字符集,或NLS_LANG字符集是其它两种字符集的超集
    2)服务器端数据库字符集与客户端NLS_LANG参数设置相同,与客户端应用字符集不同
    如果客户端应用字符集是其它两种字符集的超集时,转换过程将出现乱码,但对于单字节编码存储中文问题,可参看本文第5章节的分析
    3)客户端应用字符集、客户端NLS_LANG参数设置、服务器端数据库字符集互不相同
    此种情况较为复杂,但三种字符集之间只要有不能转换的字符,则必产生乱码

4.3导入/导出过程出现乱码原因
    这个过程存在4个字符集设置,在3.1章节中已分析
   (1)源数据库字符集
   (2)EXP过程中NLS_LANG参数
   (3)IMP过程中NLS_LANG参数
   (4)目标数据库字符集
    出现乱码原因
    1)当源数据库字符集不等于EXP过程中NLS_LANG参数,且源数据库字符集是EXP过程中NLS_LANG的子集,才能保证导出文件正确,其他情况则导出文件字符乱码
    2)EXP过程中NLS_LANG字符集不等于IMP过程中NLS_LANG字符集,且EXP过程中NLS_LANG字符集是IMP过程中NLS_LANG字符集的子级, 才能保证第一次转换正常,否则第一次转换中出现乱码。
    3)如果第一次转换正常,IMP过程中NLS_LANG字符集是目标数据库字符集的子集或相同,才能保证第二次转换正常,否则则第二次转换中出现乱码

五.单字节编码存储中文问题

由于历史的原因,早期的oracle没有中文字符集(如oracle6、oracle7、oracle7.1),但有的用户从那时起就使用数据库了,并用US7ASCII字符集存储了中文,或是有的用户在创建数据库时,不考虑清楚,随意选择一个默认的字符集,如WE8ISO8859P1或US7ASCII,而这两个字符集都没有汉字编码,虽然有些时候选用这种字符集好象也能正常使用,但用这种字符集存储汉字信息从原则上说就是错误的,它会给数据库的使用与维护带来一系列的麻烦。
    正常情况下,要将汉字存入数据库,数据库字符集必须支持中文,而将数据库字符集设置为US7ASCII等单字节字符集是不合适的。US7ASCII字符集只定义了128个符号,并不支持汉字。另外,如果在SQL*PLUS中能够输入中文,操作系统缺省应该是支持中文的,但如果在NLS_LANG中的字符集设置为US7ASCII,显然也是不正确的,它没有反映客户端的实际情况。但在实际应用中汉字显示却是正确的,这主要是因为Oracle检查数据库与客户端的字符集设置是同样的,那么数据在客户与数据库之间的存取过程中将不发生任何转换,但是这实际上导致了数据库标识的字符集与实际存入的内容是不相符的。而在SELECT的过程中,Oracle同样检查发现数据库与客户端的字符集设置是相同的,所以它也将存入的内容原封不动地传送到客户端,而客户端操作系统识别出这是汉字编码所以能够正确显示。
    在这个例子中,数据库与客户端都没有设置成中文字符集,但却能正常显示中文,从应用的角度看好象没问题。然而这里面却存在着极大的隐患,比如在应用length或substr等字符串函数时,就可能得到意外的结果。
    对于早期使用US7ASCII字符集数据库的数据迁移到oracle8i/9i中(使用zhs16gbk),由于原始数据已经按照US7ASCII格式存储,对于这种情况,可以通过使用Oracle8i的导出工具,设置导出字符集为US7ASCII,导出后使用UltraEdit等工具打开dmp文件,修改第二、三字符,修改 0001 为0354,这样就可以将US7ASCII字符集的数据正确导入到ZHS16GBK的数据库中。

六.结束语

    为了避免在数据库迁移过程中由于字符集不同导致的数据损失,oracle提供了字符集扫描工具(character set scanner),通过这个工具我们可以测试在数据迁移过程中由于字符集转换可能带来的问题,然后根据测试结果,确定数据迁移过程中最佳字符集解决方案。

参考文献
[1]Biju Thomas , Bob Bryla《oracle9i DBA基础I 学习指南》电子工业出版社 2002
[2]网站 http://www.itpub.net

********************************************************************************

原文:http://www.csdb.cn/viewPaper.jsp?tipid=1148536745101

关于Unicode参考:http://fangyuncn.spaces.live.com/blog/cns!29C7193178944F5A!176.entry

28 mayo

CVSNT Server Configuration [转]

# title : CVSNT SERVER Configuration
#
# @author: H819
#
# @version: 0.5
#
# Copyright: free reference,note author name and the article link.
#
# Date :Dce 14th ,2005
#
# History:
#   Dce 14th ,2005,version 0.5


/*
说明:

1.${CVSNT},表示,CVSNT的安装路径
2.${CVSNTROOT},表示,CNSNT的 ROOT Repository 路径
3.本文提到的 CVSNT 一般指 CVSNT Server
*/


****************
* CVSNT 的安装 *
****************
平台
CVSNT-2.5.03.2151;
windows xp sp2;
/*
注意:
一般需要 ntfs 格式的系统
CVSNT Server 官方站点
http://www.march-hare.com/
http://www.cvsnt.org/archive/?M=A
这个网址可以下载到原始安装文件和手册

*/

安装 CVSNT-2.5.03.2151
过程略

注意:
1.安装完成之后,安装程序会自动把 ${CVSNT} 加入到 path 中
2.cvs.ext 既是 server 又是 client,如果仅作为 client 端,想要运行 cvs 命令,也必须安装它;
3.注意设定 CVSNT Repository.

***********************
* CVSNT的用户验证方式 *
***********************
CVSNT的用户验证方式分两种

1.Windows系统用户与CVSNT用户共存的混合验证方式:

即 windows 操作系统用户和 CVSNT 的 passwd 文件定义的用户都可以作为 CVSNT 的用户。
1.1 作为操作系统的用户登陆:输入操作系统的用户和密码;
1.2 作为 CVSNT 的用户登陆:输入 passwd 文件中定义的用户和密码。
在这种方式,操作系统的管理员帐户同时也是 CVSNT 服务器的管理员帐户。

2.CVSNT用户单一验证方式:

只有 CVSNT passwd 文件定义的用户作为 CVSNT 的用户。
登陆:输入 passwd 文件中定义的用户和密码。

默认工作模式是混合验证方式,但是使用单一验证方式对用户的管理比较方便一点,因此下面的介绍,基于 CVSNT 用户单一验证方式。

***********************
* CVSNT的主要配置文件 *
***********************
CVSNT 服务器安装完成之后,要定义 ROOT Repository (即上文定义的 ${CVSNTROOT} ),在该目录中,定义了服务器的各种配置,在这里可以定义管理选项参数。

本文主要介绍以下几个文件:config,passwd,admin,group,checkoulist,fileattr.xml,cvswrappers

*** config 文件 ***
只关注文中第一、二行注释

#Set this to `no" if pserver shouldn"t check system users/passwords
#SystemAuth=yes

SystemAuth: 定义验证方式,默认是 SystemAuth=yes ,即混合验证方式。
这项设置默认是注释掉的,相当于 SystemAuth=yes

如果想要修改为 CVSNT 用户单一验证方式,修改为 SystemAuth=no
注意,如果修改为  SystemAuth=no ,操作系统用户就不能登陆 CVSNT 服务器了,所以要在定义好 CVSNT 服务器的管理员之后(即修改好 passwd,admin,checkoutlist 三个文件之后),再修改它。

*** passwd 文件 ***

该文件中定义了 CVSNT 用户的用户名、密码、操作系统用户别名;
默认该文件是不存在的,只有管理员有权限创建和修改它;
该文件不能被 checkout。

格式:
cvsadmin:fqr1fS4gDghrt:administrator
用户名:密码:操作系统用户别名

密码是CVS使用UNIX标准加密函数对密码进行加密后的结果;
这个用户名和密码和操作系统用户没有关系,修改它们不会对操作系统产生任何影响;
每个用户必须要有一个操作系统用户别名,这个别名必须在操作系统中真实存在,并且是管理员身份,否则该用户无法进行 cvs 操作。(为什么必须是管理员?)
/*
实际上, CVSNT 服务器是以该操作系统用户的身份进行文件的读写操作的,如果该操作系统用户为 guest ,则不能 checkout ,ls 等。
不知道为为什么 CVSNT 服务器没有把这项设置对使用者透明化,因为每个用户都要进行  checkout ,ls 等基本的操作,如果连这些基本的操作都不能进行的话,这个用户就没有存在的必要了。
按我的理解,格式就要用户名和密码,后面的操作系统别名默认为管理员,就像 servu 一样易于使用。
(也可能有其他的原因)

附:
cvsnt manual
The cvs passwd command can be used to add or delete new users. Only an administrator can do this.
Note that deleting a user does not remove them from any user permissions.
*/

*** admin 文件 ***

文件中定义了 CVSNT 服务器的管理员列表

格式:
user1
user2
...

该文件中的用户同样必须出现在 passwd 文件中;
该文件中的用户,拥有 CVSNT 服务器管理员的所有权限;
该文件默认是不存在的,需要手工建立;
该文件要加入到 checkoutlist 文件中。

实际上,cvsnt server 把这个文件当作一个特殊的 group 来对待的。

*** group 文件 ***

这个文件用来定义组,我们可以将同样性质的用户归入一个组,然后用给用户赋权限的方式给组赋权限,这样,一个组的用户就会具有同样的权限。

格式:
group1:user1 user2 user3
group2:user4 user5 user6
group3:user7 user8 user9
组的名称+冒号+用户名+空格+用户名+空格...

多个用户名之间用空格来进行分割;
组中的用户,要在 passwd 文件中存在。


*** checkoutlist 文件 ***

# The "checkoutlist" file is used to support additional version controlled
# administrative files in $CVSROOT/CVSROOT, such as template files.
#
# File format:
#
# [<whitespace>]<filename><whitespace><error message><end-of-line>
#
# comment lines begin with '#'

该文件用来支持附加的版本控制操作,在 CVSROOT 中添加的文件,要在此文件中声明.
目前只知道,admin 文件需要声明.
格式举例:
 admin err msg by Apollo
 
注意:不要忘记文件名称之前的空格。

*** fileattr.xml 文件 ***

在每一个 Repository 建立之后,都会在 Repository 的 ROOT 中建立一个名字叫做 CVS 目录,该目录中只有一个文件 fileattr.xml;
fileattr.xml 文件中定义了访问该 Repository 的用户的权限;
对于新建立的 Repository ,默认是所有人拥有所有权限(CVSNT ROOT 除外);
CVSNT ROOT 的 fileattr.xml:默认是只有管理员拥有其所有权限。


*** cvswrappers 文件 ***
refers to
http://www.mobilefish.com/developer/cvsnt/cvsnt.html

Before using CVS, it is recommended to update the cvswrappers file. The cvswrapper file contains file extensions which determines which file should be stored as binary files in the CVS Repository. To update this file do the following:

Add the following file extensions in the cvswrappers file, e.g.:

*.cab -k 'b'
*.class -k 'b'
*.doc -k 'b'
*.dll -k 'b'
*.ear -k 'b'
*.exe -k 'b'
*.exp -k 'b'
*.fla -k 'b'
*.gif -k 'b'
*.gz -k 'b'
*.jar -k 'b'
*.jpg -k 'b'
*.jpeg -k 'b'
*.lib -k 'b'
*.msi -k 'b'
*.mso -k 'b'
*.pdf -k 'b'
*.pfw -k 'b'
*.png -k 'b'
*.ppt -k 'b'
*.sit -k 'b'
*.swf -k 'b'
*.tar -k 'b'
*.tlb -k 'b'
*.vsd -k 'b'
*.xls -k 'b'
*.war -k 'b'
*.wmz -k 'b'
*.zip -k 'b'


******************
* CVSNT 配置实验 *
******************

*** 实验目的 ***

1.修改 CVSNT 服务器验证方式为 CVSNT 用户单一验证方式;
2.定义 CVSNT 服务器的管理员为 cvsadmin,密码为 cvsadmin;
3.定义两个 Repository(testa,testb) ,添加四个普通用户,每个 Repository 有两个用户.
test1:test1 , test2:test2 为 Repository testa 的用户,test1,只能读(checkout),test2 能读、写、添加文件(read,write,create).
test3:test3 , test4:test4 为 Repository testb 的用户,test3,只能读(checkout),test4 能读、写、添加文件(read,write,create).

*** 完成效果 ***

cvsadmin 能设置 CVSNT 服务器的配置,并且能对所有的 Repository 拥有全部权限;
test1,test2 只能看见 testa,并且 test1 只能对 teata 进行 checkout(read),test2 能对 testa 进行 checkout,commit,add (read,write,create);
test3,test4 只能看见 testb,并且 test3 只能对 teatb 进行 checkout(read),test4 能对 testb 进行 checkout,commit,add (read,write,create);

/*
说明:
权限参数有 read,write,add,tag,control. 这里只举例说明最常要的 read,write,add(参见帮助文档);
control 没有应用成功。

*/

*** 开始配置 ***

/*
说明:
1. 以下从命令行进行配置,注意步骤地顺序;
2. 设置权限等,都要把 Repository checkout 出来;
3. 对于 CVSNT 服务器的设置,实际上就是对 CVSROOT 这个特殊的 Repository 进行设置,所以首先也要把 CVSROOT checkout 出来.注意 checkout CVSROOT 需要用管理员身份;
4. 刚安装完成之后的 CVSNT server 默认的管理员是操作系统的管理员,所以用操作系统管理员身份 checkout CVSROOT;

*/

1 *** 进入命令行,建立一个临时目录,用来放置 checkout 出来的 Repository ,修改完成之后,再提交到 CVSNT server 上去。

d:\ md 00   # 在 d: 建立一个临时目录,进入
d:\00\

2 ***  登陆  CVSNT server

d:\00\set cvsroot=:pserver:administrator:123456@localhost/cvsroot

/*
说明:
set cvsroot 是设置一个临时的环境变量,变量值为 = 右边部分;
pserver     为登陆协议,是一种明文协议,用户名和密码可以被监测到。本次实验用这种协议,其他的协议以后再总结;
administrator:123456    为本机操作系统的管理员用户和密码;
localhost   为登陆的机器地址,可以用 IP;
/cvsroot    cvsroot 是安装 CVSNT server 的时候,指定 CVSROOT 的别名。
*/

3 ***  列表
d:\00\ cvs ls
/*
说明:
列出 CVSNT server 的 CVSROOT 下面的所有 Repository;
此时应该只有一个 CVSROOT ,它是服务器的根,服务器的所有配置都在这里;
此时登陆的身份是管理员,前文说过,CVSNT ROOT 的 fileattr.xml 已经定义,默认是只有管理员拥有其所有权限;
列表命令成功,同时也说明登陆成功。
*/

4 *** checkout CVSNTROOT 到 d:\00\
d:\00\cvs co cvsroot
/*
说明:
co 是 commit 命令的简写,具体简写的命令,见帮助;
该命令会把  CVSNTROOT checkout 到 d:\00\ ,并且会在 d:\00\ 建立一个 cvsroot 目录;
d:\00\cvsroot 里面是 CVSNTROOT 中的所有配置信息;
修改配置信息之后,再 commit 上去, CVSNT server 的配置随之改变了。
*/

5 ***  添加用户信息

d:\00\cvs passwd -r administrator -a cvsadmin
d:\00\cvs passwd -r administrator -a test1
d:\00\cvs passwd -r administrator -a test2
d:\00\cvs passwd -r administrator -a test3
d:\00\cvs passwd -r administrator -a test4
/*
说明:
该命令会直接在 CVSNT server 的 server 端直接建立一个 passwd 文件,而不会在已经 checkout 的 d:\00\cvsroot 目录中建立此文件;
该文件只能由管理员建立,并且不能被非管理员修改,不能被 checkout;

-r 是管理的操作系统用户的别名
注意:
该命令不会影响操作系统用户;
此操作系统用户必须为管理员。

-a 添加用户

添加用户的时候,会提示输入密码。(本次实验,用户名和密码取相同)。

建立的 passwd 文件内容应如下内容:

cvsadmin:oVaaG5hcPSOQY:administrator
test1:eXetD79lHrTgw:administrator
test2:zX3nQkEmS/Se2:administrator
test3:cZgugWplhpBd.:administrator
test4:pZ5M7WTmCHMmI:administrator

The cvs passwd command can be used to add or delete new users. Only an administrator can do this.
Note that deleting a user does not remove them from any user permissions.

参考上文 passwd 文件说明。
*/

6 ***  定义 cvsadmin 为  CVSNT server 管理员
在 d:\00\cvsroot 建立文件名为 admin 的文件(注意,无扩展名)
内容为 :

cvsadmin

即定义了 cvsadmin 为 CVSNT Server 管理员;
如果还想加入其他的用户作为管理员,依次加入用户名即可,每个用户名一行;
实际上 CVSNT 把这个文件中的所有用户,当作是管理员组。

7 ***  注册文件 admin 到 checkoutlist 文件

打开 d:\00\cvsroot 中 checkoutlist 文件,加入这一句:

 admin err msg by jh

注意 admin 前后的空格,具体的格式信息,参照前文 checkoutlist 文件 说明部分;
只有添加的 admin 文件需要注册到 checkoutlist 中,其他的添加文件不用。

8 ***  定义 group 组

在  d:\00\cvsroot 建立文件名为 group 的文件(注意,无扩展名)
内容为 :

#testall :hava all permissions
testaall:test1 cvsadmin
#testar :read only
testar:test2
#testbll :hava all permissions
testball:test3 cvsadmin
#testbr :read only
testbr:test4

testaall,testball 有完全权限;
testar,testbr     只读权限;
该文件不用注册到 checkoutlist。


9 ***  修改 cvswrappers 文件
参考上文。

10 ***  修改验证方式

修改   d:\00\cvsroot 下面的 config 文件
修改前面第二行为:
SystemAuth=no

即修改验证方式为 CVSNT用户单一验证方式。


11 ***  提交对 CVSROOT 的修改

11.1 进入  d:\00\cvsroot

11.2 d:\00\cvsroot\cvs add admin group
由于 admin 和 group 文件是新产生的,所以提交之前要添加到 commit 的计划之中;
运行完该命令之后,会提示运行 cvs commit 命令提交该文件。

11.3 d:\00\cvsroot\cvs commit
运行该命令,会提交 d:\00\cvsroot\ 目录中的所有文件,包括新建立的文件;
中间过程会弹出来一个提示文件,说明哪些文件被修改了,哪些文件是添加的,该文件的信息如下:

CVS: ----------------------------------------------------------------------
CVS: Enter Log.  Lines beginning with `CVS:' are removed automatically
CVS:
CVS: Committing in .
CVS:
CVS: Modified Files:
CVS:  checkoutlist cvswrappers config
CVS: Added Files:
CVS:  admin group
CVS: ----------------------------------------------------------------------

即提示修改了 checkoutlist config cvswrappers 三个文件,添加了 admin group 两个文件;
这个提示文件可以添加内容,所有信息都会被提交并且被 CVSNT Server 记录;
关掉该文件,选择继续提交。

/*
至此,完成工作如下:
CVSNT server 已经设置为 CVSNT用户单一验证方式;
CVSNT server 管理员已经设置为 cvsadmin;
test1,test2,test3,test4 已经添加。

此时,如果再运行命令 cvs ls ,则会提示该用户不存在。
因为我们最开始登陆的时候,设置的是 set cvsroot=:pserver:administrator:123456@localhost/cvsroot
所以此时运行  cvs ls 是在 set cvsroot=:pserver:administrator:123456@localhost/cvsroot 环境下面进行的,而我们已经改变了验证方式为  CVSNT用户单一验证方式,administrator 用户不在 passwd 文件中,所以 cvs ls 命令不会被执行了。

以 cvsadmin 用户登陆:
关掉命令窗口,打开一个新的命令窗口

进入临时目录 d:\00\
d:\00\set cvsroot=:pserver:cvsadmin:cvsadmin@localhost/cvsroot
d:\00\cvs ls

就可以看到 CVSROOT 下面的所有 Repository 了。

测试其他的用户为非管理员,即看不到 CVSROOT:
关掉命令窗口,打开一个新的命令窗口
进入临时目录 d:\00\
d:\00\set cvsroot=:pserver:test1:test1@localhost/cvsroot
d:\00\cvs ls
会提示
Listing modules on server

空白,即不能看到 CVSROOT。

前文提到过,CVSNT ROOT 默认是只有管理员拥有其所有权限,它是靠 fileattr.xml 文件这一句
<all deny="1" />
来实现的,具体的详细解释见下文。
*/

12 ***  至此,对 CVSRT Server 的 CVSROOT 的修改已经完成,为了安全起见,删除临时 checkout 出来的  CVSROOT
d:/00/rd cvsroot /s
dos 命令,删除根目录下面的所有文件及文件夹。
/*
dos 命令
1. help  # 列出所有命令
2. help xxx  #列出 xxx 的帮助
*/

13 ***  以 test1 用户身份 import 一个 testa 工程,做如下权限限制:
只有 cvsadmin,test1,test2 能看见 testa,;
test1 只能对 teata 进行 checkout(read);
cvsadmin,test2 能对 testa 进行 checkout,commit,add (read,write,create).

关闭窗口,建立临时目录 d:/00/test/,在 test 中放入几个文件,准备 import 到 CVSNT Server 上面去。
重新以 test1 用户登陆:
d:/00/test/set cvsroot=:pserver:test1:test1@129.9.200.253/CVSNTROOT

把 d:/00/test/目录中的所有文件 import 进入 CVSNT Server,并且命名为 testa:
d:/00/test/cvs import -m "Imported sources test1 "  testa start

-m 为导入时的日志信息
cvs --help import 可以查看 import 参数

设置权限:
必须导出来才可以定制权限;
只有 CVSNT Server 的管理员才可以设置权限,项目的建立者(owner)也不能更改权限(是这样吗,麻烦!);
新建立的项目:默认是所有人拥有所有权限;
cvs lsacl :查看当前 Repository 权限。

/*

关于权限的说明:

CVSNT 2.5 对 Repository 的访问控制设置,是使用命令 chacl,chown 来进行的;
当新建立一个 Repository 之后,会在其中建立一个 cvs 文件夹,里面有一个 fileattr.xml 文件,该文件定义了其所在 Repository 的访问控制权限,实际上 chacl,chown 命令就是对该文件的操作;
chacl,chown 命令,只有管理员能够执行,就是 Repository 的创建者(owner)也不能执行!(管理员累死了!怎么会有这种管理方法!)
执行 chacl 命令,必须先把  Repository 导出来,以下执行命令,都是导出来之后执行的。

*/

13.1 一个新的 Repository 创建之后,默认是对所有人开放所有权限的。一个新建立的 Repository 的 fileattr.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<fileattr>
  <directory>
    <owner>test1</owner>
  </directory>
</fileattr>


仅仅表明,该 Repository 的拥有者 owner (即创建者) 是 test1.

CVSNT 2.5 权限管理的原则是:如果不显示的指出否定权限,则赋予肯定权限。(这一点个人觉得非常不方便,安全是第一位的。)
例如上面的 fileattr.xml 是所有人,拥有所有权限

13.2 如果要把默认的权限关闭,要用 chacl 命令
以管理员身份执行
cvs chacl -a none

执行完毕之后,应该是这个样子:

<?xml version="1.0" encoding="UTF-8"?>
<fileattr>
  <directory>
    <owner>test1</owner>
    <acl>
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.00.04</modified_date>
      <all deny="1" />
    </acl>
  </directory>
</fileattr>

modified_by,modified_date 是注释信息;
<all deny="1" /> 一句,把默认得权限都关闭了,即使是管理员、创建者都没有任何权限了;
并且,如果不显示的指出肯定权限,都是默认的否定权限(和默认的权限规则相反)。

13.3 开通管理员的所有权限
cvs chacl -u testaall -a all
即,testaall 组对 testa 拥有全部权限;
-u 可以是组名称,也可以是单个用户名称。

<?xml version="1.0" encoding="UTF-8"?>
<fileattr>
  <directory>
    <acl>
      <all deny="1" />
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.00.04</modified_date>
    </acl>
    <owner>test1</owner>
    <acl user="testaall">
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.00.41</modified_date>
      <all />
    </acl>
  </directory>
</fileattr>

这样,testaall 组中的用户拥有所有权限了,其他的用户无任何权限;
如果想要让一个用户拥有 所有权限,加入到 testaall 组即可。

13.4 添加 testar 组用户只读权限
cvs chacl -u testar -a read
说明:
经过 cvs chacl -a none ,如果不显示的指出肯定权限,都是默认的否定权限(和默认的权限规则相反);
相当于 cvs chacl -u testar -a read,nowrite,nocreate,no...(所有的参数加上前缀 no).


<?xml version="1.0" encoding="UTF-8"?>
<fileattr>
  <directory>
    <acl user="testaall">
      <all />
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.00.41</modified_date>
    </acl>
    <acl>
      <all deny="1" />
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.00.04</modified_date>
    </acl>
    <owner>test1</owner>
    <acl user="testar">
      <modified_by>cvsadmin</modified_by>
      <modified_date>2005.12.11.11.09.33</modified_date>
      <read />
    </acl>
  </directory>
</fileattr>


/*

对于 CVSNTROOT Repository 的说明

刚建立 CVSNTROOT 的时候 fileattr.xml 是这样的(CVSNT 自动建立的)

<?xml version="1.0" encoding="UTF-8"?>
<fileattr>
  <directory>
    <owner>administrator</owner>
    <acl user="administrator">
      <all />
    </acl>
    <acl user="admin">
      <all />
    </acl>
    <acl>
      <all deny="1" />
    </acl>
  </directory>
</fileattr>


即只有 administrator 用户和 admin 组的用户拥有全部权限;
而 安装 CVSNT 的时候,是以操作系统管理员 administrator 安装的,所以 administrator 默认给了所有权限,当更改了验证方式之后,administrator 用户就无效了。
admin 也是 CVSNT 自动加上的,即默认 admin组中的用户也拥有全部权限.

实际上,我们可以直接用编辑工具按照格式更改 fileattr.xml 文件,而不用 chacl 命令,效果是一样的。


我们可以测试:
test1,cvsadmin 用户可以 read,write,create ...
而test2 用户只能 read

*/


14  ***  testb 工程
同 11 testa 的过程

建立以后,我们会发现:
test1,tes2 用户运行 cvs ls 的时候,只能看见 testa
test3,tes4 用户运行 cvs ls 的时候,只能看见 testb
而 cvsadmin 用户,可以看见全部的 Repository(cvsroot,testa,testb)

15   ***  完成,可以测试实验的结果了

********
* 备注*
*******

1. 基本概念介绍
refers to
http://www.guoly.com/wincvs-guide.html

    CVS是很早的时候在Unix下发展起来的,它使用的术语比较特殊,需要先熟悉和理解,这是使用CVS的第一步。
1.1 Repository: 中文名称:仓库。它是 CVS服务器(可能在远程,也可能在本地)的根目录,我们所有的工作都保存在这个仓库中,包括源代码和这些代码的全部历史。你可以把Repository想像成一个仓库,仓库中有许多“木桶”,每个“木桶”就
是我们的一个让CVS管理起来的工程。对于CVS来说,这些“木桶”之间是没有什么关联的,删除一个“木桶”不会影响别的“木桶”。我们所想像的木桶,在CVS术语中,又叫模块(Module)。
1.2 Module:中文名称:模块。就是上面我们所想像的仓库中的“木桶”,里面放的是一个项目的所有文件(包括源代码,文档文件,资源文件等等)。在物理上,Module是CVS服务器根目录下的第一级子目录。
1.3 Import:中文名称:导入。我们本地有一个软件项目,里面有许多各种类型的文件,都需要用CVS进行版本管理,那么第一步就是把这个软件项目的整个目录结构都Import到CVS的仓库中去。经过这种导入,CVS将为你的项目创建一个新的“木桶”----Module,即模块。
1.4 Checkout:中文名称:导出。指将仓库中的一个“木桶”(Module, 模块)中的东西导出到本地的工作目录下,然后我们可以在WinCvs的管理下,进行工作,修改其中的内容。
1.5 Commit:中文名称:提交修改。我们在本地的工作目录下,对工程中的文件进行修改,这些修改,需要提交给CVS的仓库,这个过程,就叫Commit。你可以Commit一个文件,也可以Commit整个目录。
1.6 Update:中文名称:同步。它与Commit相对应,是从仓库中的“木桶”(模块)中下载你同事修改过的文件(别忘记你的项目有许多人共同参与),如果这个文件在你本地也有,就会更新本地的拷贝,如果你本地没有,就会把新文件下载到你的本地。
1.7 Revision:中文名称:文件版本。这是CVS中一个需要特别注意的概念,它指的是单个文件的版本,而不是整个项目的版本。基本上,单个文件每次的修改,经过Commit之后,它的Revision都要改变一次,比如从1.1到1.2到1.3等等。特别要注意,单个文件的版本(Revision)与整个工程产品的版本(Version,或者Release)可以没有任何关系。例如,整个产品现在发行1.0版本(Version 1.0)了,但是产品的源代码文件中,有的文件版本(Revision)可能是1.9, 有的是2.1,等等。这很容易理解,因为为了发行产品V1.0,我们需要对源代码进行多次修改编译。
1.8 Release:中文名称:发行版本。整个产品的版本,例如VC5.0, VC6.0等。
1.9 Tag:中文名称:标签。在一个开发的特定期,对一个文件或者多个文件给的符号名,一般是有意义的字符串,如“stable”,“release_1_0”等。比如,我们对某个文件的1.5版本加上标签:“memory_bug_fixed”,借助这个有意义的标签,我们可以理解1.5版本解决了内存Bug,所以说Tag赋予了版本一些文字含义。
上述这些基本概念,与其它的版本控制工具(例如Microsoft Source Safe)有一定的差异,建议您仔细体会。


2. cvsroot 文件介绍
举例 :exmple
.#exmple   以 .# 开头的文件是备份文件,不能被 checkout
exmple,v   以 ,v 结尾的文件:此文件记录了文件的各次版本变化的历史记录,cvs 根据此文件,进行版本控制。
exmple     该文件记录真正的文件内容,在进行 cvs 命令操作的时候,和 exmple,v 一起作为 cvs 版本控制的依据。

3. cvs 命令
cvs help
# 列出所有命令
cvs --help command
# 查看某个命令的语法

命令简写
http://www.mobilefish.com/developer/cvsnt/cvsnt.html


*************
* refers to *
*************


http://blog.csdn.net/lonelyegg/
http://www.mobilefish.com/developer/cvsnt/cvsnt.html
http://blog.csdn.net/jdskyy/archive/2005/10/18/508567.aspx
http://www.8848software.com/scmforum/forum.asp?FORUM_ID=49&CAT_ID=9
(一个讨论论坛)

*******
* faq *
*******
1.
关于登陆CVSNT server 出现信息 :
Error reading from server xxxx: -1: Unknown error
的解释

一般是无法链接到 CVSNT server ,是通讯受到阻止的原因。
排查一下原因:
查看本机的防火墙;
局域网中的端口限制。

***********
* 遗留问题*
***********

1. 其他的登陆协议的登陆方法

2. 访问权限控制,如何让 owner 拥有其所在 Repository 的 fileattr.xml 修改权限?
这样,Repository 的创建者就拥有其维护权限了,减轻了 CVSNT Server 管理员的权限。如果 Repository 有很多个的话,仅靠管理员来维护是不现实的。

参考:http://blog.csdn.net/H819/archive/2005/12/14/552052.aspx

21 mayo

Project Summary (To be continued)

1. VSS Plugin for Eclipse  
 
2. Web Calendar:  PopCalXP9_Lite

<input type="text" name="startTime" value="<%=startTime%>" readonly size="16"
onclick="if(self.gfPop)gfPop.fPopCalendar(this);return false;" />

<iframe width=174 height=189 name="gToday:normal:agenda.js:gfPop:plugins_time.js"
id="gToday:normal:agenda.js:gfPop:plugins_time.js" src="js/PopCalXP/ipopeng.htm" scrolling="no"
frameborder="0" style="visibility:visible; z-index:999; position:absolute; left:-500px; top:-500px;">
</iframe>
 
 
3. Struts使用
3.1  一个Module运行时配置:controller
  •  nocache: Set to "true" if you want the controller to add HTTP headers for defeating caching to every response from this module. [false]
3.2  使用PlugIn
  • 方式:实现org.apache.struts.action.PlugIn接口.
  • 例如:使用ConvertUtils.register(new SqlDateConverter(null), Date.class);对模块注册自己的Convert. 注意在PlugIn的destroy()时,要恢复原有设置。

 

4. 项目质量要求

4.1 页面:

  • 查询条件保持。
  • 提交不成功时,保持所填写内容。
  • 避免重复提交

 

5.  遇到的问题及解决:

5.1 AJAX中文问题:
使用:
    res.setContentType("text/html; charset=UTF-8");
    PrintWriter out = res.getWriter();

5.2 定时器
使用:org.springframework.scheduling.timer包解决.

5.3 两个日期之间的天数
使用:(Date.getTime()-Date.getTime())/(24*60*60*1000)