使用yoeman, bower, grunt等工具开发web。
基于salt开发配置管理工具
需求
T2,是某厂商开发的ESB产品,用于证券公司交易相关系统之间的互相调用。该产品的设计思路是一套核心的组件实现消息通信,
通过不同的插件实现ESB的协议转换、消息路由、访问控制等功能。每个节点基于xml配置不同的插件,即可扮演不同的角色。
恰恰由于这种灵活性,导致其配置非常复杂,人工进行配置修改和检查很容易出错。再加上每个架构角色可能会有多个节点,重复性的工作让人厌烦。
为此,我想通过salt进行自动化的配置。主要需求如下:
- 只定义最基本的、非冗余的数据,从这些数据生成所有的配置
- 支持多个版本的部署
- 自动实现停止服务、部署、启动服务等一系列操作
- 支持升级包的部署
机制
salt适合固定的配置(state)部署多个实例(minion)的场景。尽管可以在pillar、state中使用python脚本,但仍无法满足本场景中需要的复杂度。
所以拟构建python应用,通过salt client API与salt master进行交互。
为了降低对使用者的要求,需要实现一个web界面。拟采用bottle.py。
。
设计
salt的“三驾马车”是 state
、 pillar
、 grains
。其中:
- state是配置项(CI)的集合。配置项即可以是固定的内容,也可以是模板+数据,还可以是命令和脚本
- pillar中定义数据并分配给minion。这些数据可以用于state中模板的上下文(context)数据
- grains记录minion自身的属性。可以作为为minion分配pillar和state的依据,也可以用于模板的上下文
针对本文提到的场景,关系如下:
- CI
在svn中维护各个版本的所有配置项 - datas
在文件中维护部署需要的数据 - pillars.top
根据datas和各节点的grains数据,组合出每个minion的pillars - states.top
根据各节点的grains和pillars数据,将CI分配到minion - pillars
节点数据,其中可以引用grains - states
节点状态,其中可以引用grains, pillars
pillar_roots目录结构
`pillarroots` 是 `/etc/salt/master`
中配置的参数,指定所有可用pillar的保存位置。
salt中可以按照环境划分不同的pillars,比如 `base` , `dev` , `prod`
等, 还可以指定多个pillar, 比如 `extpillar` 。
本文的场景中,不需要在salt中区分各种环境,故只使用 `base`
目录。其默认值为 /srv/pillar
。设计的目录结构如下:
1 | /srv/pillar |
其中,yaml是部署配置,通过工具编辑; top.sls是pillar分配,通过脚本生成。
file_root目录结构
file_root
是 /etc/salt/master
中配置的参数,指定所有可用state的保存位置。
salt中可以按照环境划分不同的states,比如 base
, dev
, prod
等。
本文的场景中,不需要在salt中区分各种环境,故只使用 base
目录。其默认值为 /srv/salt
。设计的目录结构如下:
1 | /srv/salt |
流程
- 从svn中获取需要的各个版本,放入
/srv/salt/
- 编辑
/srv/pillar
中的各个部署配置 - 生成
/srv/pillar/top.sls
和/srv/salt/top.sls
- 调用salt client API进行部署
配置管理规范需要考虑的内容
在拟定《配置管理规范》时,需要考虑很多因素。本文例举一些需要考虑的因素以及相关的可选策略。
开发Camel组件
快速开始
因为 JBOSS FUSE:你必须知道的那些事 ,而ServiceMix的核心组成部分是Apache Camel,所以“用Fuse开发路由”也就是“开发Camel路由”。
Camel提供了大量的开发工具,其中camel-archetype-blueprint 是一个maven
archetype,可以基于 Blueprint,以依赖注入的方式配置CamelContext。下面快速创建一个demo:
1 | mvn archetype:generate \ |
会创建如下结构的一个工程:

从 pom.xml
来看,这是一个 使用maven-bundle-plugin构建的OSGibundle工程
基于Blueprint装配Camel
工程的`META-INF/blueprint/blueprint.xml’文件是一个Blueprint配置文件:
1 | <?xml version="1.0" encoding="UTF-8"?> |
该配置文件中,定义了一个id为 blueprintContext
的Camel
Context。这个Context中定义了一个路由:
- 入口为一个Timer类型的Endpoint
- 使用预定义的bean为Message设置body
- 记录日志
- 出口为一个Mock类型的Endpoint
如果使用FuseIDE,可以看到图形化的配置界面:

部署到ServiceMix
执行 mvn package
后,得到 route-demo-1.0.0-SNAPSHOT.jar
,这是一个OSGi bundle。可以将jar文件部署到
$SERVICEMIX_HOME/deploy/
目录中。正常情况下,bundle的依赖关系被满足,该bundle会被自动启动。
从ServiceMix到Fuse
上述的过程也适用于JBoss Fuse。
但是Fuse对ServiceMix进行了再次封装,需要使用Fuse对应的版本。比如,=camel-archetype-blueprint=
的版本可能要使用 2.10.0.redhat-60024
这样的“Fuse版本号”,否则在部署到Fuse是可能会发生版本不匹配的问题。
Fuse提供了一个maven仓库,专门提供这种定制版本的组件,需要在maven中配置:
1 | <repository> |
持续集成(CI)工具的作用
个人时代的软件开发
众所周知,软件开发一般需要编码、构建、测试、打包、发布等步骤,如下图:
在“个人英雄”时代,一个人完成所有的事情,一切都不是问题。
团队开发
很快,软件的规模就上升到了个人无法完成的程度。这就需要团队开发。在团队开发模式中,一定要解决代码共享的问题:
图中的“代码库”,通常由版本管理软件负责,比如git,svn。
对于软件的配置信息,开发团队通常不会很十分在意——要么随源代码一起放入版本库,要么由“实施人员”临时配置。
开发和运维
企业应用的环境中,通常开发团队和运维团队是分开的。这就形成了一堵墙:

当软件系统从墙的一端(Dev)传递到另一端(Ops)时,会发生各种各样不可预知的情况发生。这些“异常”通常是由于交付了不正确的东西:
- 开发人员不清楚生成环境,而运维人员又搞不定即没有规范又没有文档的配置
- 开发过程中的不成熟版本进入了生成环境
- ……
在开发团队和运维团队之间,也应该有一个信息交换和缓冲带,主要是:
配置库
有严格的环境(开发、测试、生产环境)区分,有版本管理的配置库,开发人员和运维人员各自维护自己需要的配置信息。
交付库
经过测试,符合要求的软件版本,经过规范的打包后,才允许进入交付库。交付库的软件包是部署生成环境的唯一来源。
自动化工具
有了代码库、配置库和交付库,加上繁杂的管理规范、流程指南,额外配上检查员、管理员,大概就可以完成上述的过程。
但是,我们需要自动化!持续集成(CI)工具就是时上述过程自动化的有力工具——以至于随时可以以很小的代价将上述过程跑一遍。
持续集成工具的主要功能如下图:
CI工具实现了与代码库关联的软件工程管理,能够自动执行构建、测试、打包、发布等操作。
CI工具通常内置了配置库和交付库,在自动构建时可以从配置库获取配置信息,在自动发布时从交付库取得指定的软件版本并部署到服务器。
此外,CI工具还会提供事件通知、生成报告等功能。
常见的CI工具
这个大概是最流行的CI工具了,使用Java开发。

Apache Karaf:OSGi中间件
为什么需要“OSGi中间件”
尽管在OSGi Runtime(Felix, Equinox等)的基础上,OSGi组织又规定了Blueprint规范以实现OSGi环境下的依赖注入,
但这还不够——没有提供类似Web开发框架那样的一些“平台级”的功能。比如日志,控制台,配置文件等。
很难想象没有Tomcat这样的Web中间件,开发Java Web应用的工作量有多大。同样的,OSGi应用也需要一种“中间件”,来实现各应用共性的一些功能,并管理应用的部署。
Apache Karaf就是这样的一个”OSGi中间件”。最早,Karaf只是Apache ServiceMix的Kernel子项目,后来独立出来成为Apache的顶级项目。
目前,Apache Karaf已经用于Apache Geronimo, Apache ServiceMix, Fuse ESB等项目。
Apache Karaf的主要竞争对手是Eclipse Virgo。
Apache Karaf的功能

Apache Karaf提供了如下“开箱即用”的功能:
热部署
尽管OSGi支持热部署,但并不是自动热部署,需要调用一些API去执行插拔的动作。Karaf在运行时可以自动处理
[home]/deploy
文件夹中的OSGi bundle,能够自动加载并在满足依赖关系时自动启动。动态配置
Karaf在
$KARAF_HOME/etc
文件夹中存储配置文件。这些配置内容可以在Karaf运行时动态修改。日志处理
基于Log4J的日志系统,同时支持多种日志API,如JDK 1.4, JCL, SLF4J, Avalon, Tomcat, OSGi等。
系统服务
Karaf可以作为系统服务运行。
控制台
可以在控制台进行服务管理、安装bundle等操作。还可以扩展自己的控制台命令。
可以通过SSH远程访问其他服务器上的Karaf控制台。
多实例管理
一个服务器上可以运行多个Karaf实例。对实例的管理可以在Karaf控制台中进行。
Bundle仓库
Karaf中内置了Pax URL的MVN协议,可以从Maven中央仓库安装bundle。
Bundle集合(Feature)
类似于Eclipse的Feature,Karaf中也支持Feature,即bundle的集合。使用Feature可以简化OSGi应用的部署。
Karaf初体验
安装好Java环境,加压缩Karaf,执行$KARAF_HOME/bin/karaf
,可以看到Karaf的启动界面:

如图的提示,使用<tab>
可以列出所有可用的命令,所有的命令支持--help
参数。
如果执行list
命令,可以列出所有的bundles:

还有很多其他的命令,比如
bundle:install
安装bundlefeature:repo-add
安装feature库feature:install
安装feature
等等。详情可以参考官方的Quick Start。
如果查看Karaf的目录,可以看到如下的目录结构:
/bin
: 启动、停止、登录karaf控制台的脚本/etc
: 配置文件/data
: 存放运行时的工作文件,清空这个目录可以使Karaf回到初始状态/cache
: OSGi framework的bundle缓存/generated-bundles
: 开发用的临时目录/log
: 日志/deploy
: 用于bundle的热部署/instances
: 存放karaf的各个实例用到的数据/lib
: 启动Karaf时需要的一些库/lib/ext
: Karaf需要的扩展库/lib/endorsed
: Karaf对其他API的再封装(背书)的一些jar包/system
: OSGi bundles库,使用Maven 2仓库的结构
Karaf启动模式和实例
- 常规模式(regular mode) 使用命令
bin/karaf
,可以在前端启动Karaf并进入控制台 - 服务模式(server mode) 使用命令
bin/karaf server
,可以在前端启动Karaf,但不进入控制台 - 后台模式(background mode) 使用命令
bin/start
,可以在后台启动Karaf。
如果安装了service-wrapper(可以在Karaf中执行feature:install service-wrapper
),还可以将
Karaf安装为系统服务。
一个Karaf节点可以运行多个”Karaf实例”。实际上,默认情况下Karaf会启动一个名为root
的实例。
实例是包含单独的配置文件、数据文件等信息的一个Karaf副本,每个实例可以单独启动或部署bundle。
Karaf控制台中提供了一些管理实例的命令:
instance:create
创建实例instance:clone
从现有的实例克隆一个新的实例instance:start
启动实例instance:status
查看实例状态instance:connect
连接(切换)到某个实例instance:stop
停止实例instance:destroy
删除实例
ssh、客户端和Web控制台
Karaf内置了一个SSHd server,可以通过ssh远程访问Karaf控制台。
要启动远程控制台服务,需要在控制台中启动bundle: bundle:restart -f org.apache.karaf.shell.ssh
。此时,远端可以使用ssh远程访问控制台,比如: ssh -p 8101 karaf@localhost
。默认karaf用户的密码也是karaf。ssh的用户、密码、端口等都可以在etc/org.apache.karaf.shell.cfg
中配置。
Karaf的客户端’bin/client’即可以连接本地控制台,也可以通过ssh连接远程控制台,甚至可以执行单个的命令。比如:
1 | bin/client -u karaf -p karaf -a 8101 hostname osgi:shutdown |
Karaf还可以安装Web控制台(feature:install webconsole
)。通过Web控制台能够管理
Karaf的bundle、feature、实例、配置以及查看日志。启动web控制台后,默认可以通过http://localhost:8181/system/console
访问web控制台,默认的用户名/密码为karaf/karaf
。Web控制台的配置文件位于etc/org.ops4j.pax.web.cfg
。
bundle URL和bundle仓库
Karaf在安装bundle时,可以使用多种URL来定位bundle,比如:
http
http://repo1.maven.org/maven2/org/apache/servicemix/nmr/org.apache.servicemix.nmr.api/1.0.0-m2/org.apache.servicemix.nmr.api-1.0.0-m2.jar
file
file:base/bundles/org.apache.servicemix.nmr.api-1.0.0-m2.jar
maven库
mvn:org.apache.servicemix.nmr/org.apache.servicemix.nmr.api/1.0.0-m2
其中,maven库方式是使用maven库作为bundle仓库,从其中检索需要的bundle。与maven类似,可以自动解决bundle之间的依赖问题。
Karaf提供了OBR (OSGi Bundle Repository),能够管理bundle仓库。在控制台使用命令feature:install obr
安装OBR之后,就可以:
增加新的bundle仓库
obr:url-add file:///user/.m2/repository/repository.xml
查看已安装的bundle仓库
obr:url-list
刷新仓库
obr:url-refresh
删除仓库
obr:url-remove
列出所有可用的bundle
obr:list
查找bundle
obr:find
OSGi应用,Karaf Feature和KAR
OSGi运行的基本单元是bundle,bundle之间有依赖关系。一组满足依赖关系的bundle集合,加上相关的配置信息,称为一个“OSGi应用”。
将OSGi应用部署到Karaf的行为称为“provisioning”。
为了简化OSGi应用的部署,Karaf定义了feature,用于描述OSGi应用的部署信息,包括:
- 名称
- 版本号
- 描述信息
- bundle集合
- 配置文件信息
- 依赖的其他feature
可以看出,Karaf中的feature与Eclipse Feature非常类似。
Karaf控制台中提供了一系列的feature和feature库的管理命令,包括:
- feature:info
- feature:install
- feature:list
- feature:repo-add
- feature:repo-list
- feature:repo-refresh
- feature:repo-remove
- feature:uninstall
- feature:version-list
由于feature中可能使用远端的bundle,Karaf提出了KAR
格式,可以把一个bundle相关的所有资源打包在一起。
这类似于使用maven定义的web工程,和最终打包的war文件之间的关系。
Karaf控制台中,可以打包(kar:create
)、安装(kar:install
)、卸载(kar:uninstall
)或列出(kar:list
)KAR文件。
企业级特性
Karaf提供了一系列的功能,以支持企业级应用的开发,包括:
日志
Karaf提供了灵活的日志系统,支持
- OSGi Log Service
- Log4j
- Commons Logging
- Logback
- SLF4J
- java Util Logging
等日志框架。不管应用中使用了上述哪种日志框架,Karaf中都可以进行统一的管理,如log level, appender等。
安全
Karaf提供了基于JAAS(Java Authentication and Authorization Service)的安全框架,可以控制对OSGi service、控制台命令、JMX、Web控制台等资源的访问。并且在应用中也可以使用Karaf的安全框架。
Web
Karaf可以作为Web容器使用,完全支持JSP/Servlet规范。
Karaf的Web容器同时支持WAR和WAB的部署。JNDI
Karaf支持OSGi中的JNDI服务。
Graphviz概述
Graphviz是一个强大的绘图工具,使用DOT语言描述图像。
Graphviz是什么
Graphviz是基于dot语言的绘图工具,可以画有向图、无向图、关系图、目录图、流程图等各种你知道的或不知道的图形。可以欣赏Graphviz的Gallery,以及Yifan Hu的gallery of large graphs。
Graphviz使用DOT语言描述图形,并提供一组工具:
dot
将生成的图形转换成多种输出格式,如PostScript,PDF,SVG,PNG,含注解的文本等等。
neato
用于sprint model的生成(在Mac OS版本中称为energy minimized)。
twopi
用于放射状图形的生成
circo
用于圆形图形的生成。
fdp
另一个用于生成无向图的工具。
dotty
一个用于可视化与修改图形的图形用户界面程序。
lefty
一个可编程的控件,它可以显示DOT图形,并允许用户用鼠标在图上执行操作。Lefty可以作为MVC模型的使用图形的GUI程序中的视图部分。
Graphviz使用DOT语言描述图形,而不是依赖于鼠标绘图,这就提供了无限的可能,主要体现在两个方面:
- 自动生成图形描述文件,从而自动生成图形
- 更加Geek的写作方式,比如在Org-mode中嵌入图形代码,在导出时自动生成图形文件
有一些强大的工具依赖或支持Graphviz,比如OmniGraffle
和PlantUML。
DOT语言简介
DOT语言使用离散数学中的Graph描述图形,包括三个基本元素:grahp,node,edge。下面是一个例子:
example.dot
1 | digraph G { |
执行命令dot -Tpng example.dot -o example.png
,就可以生成如下的图形:

DOT通过属性可以设置node和edge的样式,比如:
1 | graph graphname { |
生成的图形如下:

更多的属性设置可以参考官方文档。
应用场景
Graphviz可以有很多玩法,常见的比如:
- 直接使用Graphviz绘图,以代替Visio等绘图工具
- 其他工具生成数据,Graphviz进行图表展示
- 利用脚本动态生成图
- 嵌入轻量级标记语言,如org-mode和markdown中
Apache Camel的核心概念
Apache Camel的整体架构
Apache Camel是一个消息处理引擎,实现了EIP(Enterprise Integration Patterns,企业整合模式)。
Camel能够用来处理来自于不同源的事件和信息,定义规则进行消息的传递和转换等处理,以实现基于消息的应用整合。其整体架构如下图所示:

Message 和 Exchange
org.apache.camel.Message
接口是对“消息”的抽象。消息由head、body、attachment等部分组成。

Camel中提供了一个默认的实现:org.apache.camel.impl.DefaultMessage
,可以适应大部分的应用场景。
不管是请求、响应,或者异常,都可以作为消息在上下文(CamelContext)的消息处理器(Processor)之间进行交换(Exchange)。
org.apache.camel.Exchange
接口就是对“消息交换”的抽象。

其中:
- Exchange ID : 区分每次消息交换的标识
- MEP: (message exchange pattern,消息交换模式),分为单向(InOnly)和请求-应答(InOut)两种
- Exception: 用于记录消息交换时发生的异常
- In message: 上一个节点传入的消息
- Out message: 当MEP 是InOut时,需要传出的消息
Camel提供了默认的org.apache.camel.impl.DefaultExchange
实现。
Camel处理消息时,每个节点都在处理Exchange
。
Endpoint 和 Component
Endpoint(端点),接收或发送消息的通道。通过URI连接消息源或目标。
为了适应各种不同的URI协议,如http,ftp,JMS,smtp等,Camel中提供了多种Endpoint,也支持扩展自己的Endpoint。

Component(不应该叫做组件,而应该是连接器connector)。org.apache.camel.Component
接口只定义了两个方法:
createConfiguration(String)
createEndpoint(String)
通常,客户代码不会直接调用createEndoint()
方法,而是由CamelContext
对象进行调用。
Camel中提供了大量的Component的实现:

Processor
不管是消息路由(Message Routing)、消息转换(Message Transformation)还是消息过滤(Message Filter),都是对消息的某种处理(Process)。
Camel中,抽象出org.apache.camel.Processor
接口,表示对消息的处理。该接口只定义了一个方法:
1 | void process(Exchange exchange) throws Exception; |
从接口定义可以看出,Camel中认为可以处理消息交换(Exchange)的类都是消息处理器(Processor)。
基于Camle的应用可以开发自己的Processor实现,同时Camel提供了大量的内置Processor,以支持EIP(Enterprise Integration Patterns)。

CamelContext

CamelContext是对Camel运行时的抽象,提供了API用于管理Component、Endpoint、Processor等节点:

一般来说,使用Camel的步骤如下:
- 创建一个CamelContext对象。
- 向CamelContext对象中添加Endpoints或者是Components
- 向CamelContext对象中添加路由(routes)规则
- 调用CamelContext的start()方法启动Camel引擎
- 通过Endpoint发送或接收消息
- 调用CamelContext的stop()方法时
定义路由(Route)
每个消息处理流程是由一系列的Processor
连接而成的图(Graph),每个图称为一个路由(Route)。
在开始使用Camel之前,需要在CamelContext中定义一个或多个路由。Camel支持使用DSL或者Spring XML进行配置。比如:
1 | RouteBuilder builder = new RouteBuilder() { |
或者:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
FUSE Mediation Router: 企业级Camel
FuseSource提供Camel的经测试、认证并提供支持的企业级版本,称作FUSE Mediation Router。
命令行界面设计
尽管8年后的1973年出现了图形界面程序,
16年后的1981年出现了图形界面的操作系统,
但是在此之后,至今三十几年过去了,图形界面(GUI)仍无法取代命令行界面(CLI)。
命令行界面是否过时
答案是:不会的!
1965年OS/360的发布标志着与硬件分离的“通用”操作系统的出现。
尽管8年后的1973年出现了图形界面程序,
16年后的1981年出现了图形界面的操作系统,
但是在此之后,至今三十几年过去了,图形界面(GUI)仍无法取代命令行界面(CLI)。
有什么理由可以说,图形界面终将取代命令行界面呢?
不管是传统的linux“神器”,如find, grep, curl, netcat, xargs, rsync,
screen,awk, vi, emacs, 还是让人惊叹的新作,如maven, git,
salt,都有着强大的命令行界面。甚至你会有一种感觉:
为这些软件设计图形界面根本就是画蛇添足。
命令行界面的优势在于:
- 效率高
鼠标操作是最简单,同时也是最笨拙的操作
- 更专注
鼠标操作容易分心
- 速度快
CLI程序需要的资源少,启动和执行速度更快
- 易远程
ssh、telnet的连接比vnc、remote desktop等更容易
- 自动化
CLI不仅可以给人使用,也适用于自动化脚本
- zhuangbility
^o^
命令行界面的类型
按照复杂程度,命令行界面可以分为:
- 非交互式
一次性输入所有的参数,程序执行期间不需要用户的干预。这是最常见的命令行界面形式。
- 基于行的交互
在执行过程中,需要用户输入一些内容,比如确认信息、路径参数等。
由于用户交互会使程序难以用于自动化脚本,所以这种命令行界面并不多见,常用于基于命令行的安装程序。
- 文本用户界面(TUI)
类似于图形用户界面,没有明确的执行流程,完全由用户控制程序的执行步骤。比如vi和emacs。
CLI世界的潜规则
为了使你的CLI程序不会显得格格不入,在设计CLI程序时要遵守一些潜规则。
良好命名
容易理解
名字要短
容易记忆
必备选项
所有的命令行工具都应该提供=-v/–version=和=-h/–help=选项。
- 保持安静
程序的输出要”恰到好处”,让用户/其他程序明确知道必要的信息,又不过分“啰嗦”。过多的输出即会浪费系统资源和带宽,也会让用户感觉不舒服,更重要的是会使得其他程序的处理逻辑变得复杂。以下的原则有利于保持安静:
不要输出无关的信息,比如版本号、作者名——除非用户要求
对于很明确的结果,不需要再提醒用户。只应提示例外(exception)情况
不需要告诉用户输出的是什么东西——用户会知道的
如有必要,可以提供=-v/–verbose=和=-q/–quiet=选项,供用户选择
明确要求
在基于行的交互式CLI中,需要用户输入时要给出明确的提示,比如:
=Do you really want to do this (y/n)?=
=Enter a date (YYYY-MM-DD):=
- 支持管道
程序应该支持从管道或文件重定向中读取数据:
如果文件名作为参数传递给程序,就读取文件的内容作为输入
如果没有提供这样的参数,就从标准输入中读取,一直到 CTRL+D
功能单一
UNIX哲学中重要的一条原则就是:每个程序只做一件事情,并把它做好。
复杂的功能通过程序间的配合完成,而为了与其他的程序配合,要尽量支持管道和重定向。
既然“只做一件事”,就要“做好一件事”。
- 遵循惯例
UNIX中命令行参数会有一些惯例,比如=-=后面的单字母选项可以连用(如=ls -Al=),
=–=后面使用多字母选项等;此外,遵循已经被广泛使用的命令的参数,也会容易被接受。
CLI支持库
CLI是如此重要,以至于很多语言/平台都提供了开发CLI的支持库,比如:
python的optparse和argparse
argparse更先进,旨在替代optparse,但是从python2.7开始才支持。如果希望在比较旧的linux上运行(通常支持python2.4),最好还是使用optparse。
java的Apache Commons CLI
Apache karaf的karaf-command-archetype
python的TUI支持库
subversion安装、配置和管理
尽管svn已经被历史所抛弃,但同样由于历史原因,还要整理一下svn的配置文档。