如何找到好的产品?(上)


如何找到好的产品?(上)
周鸿祎

我不知道大家想听什么,比如说有两大部分可以讲,高大上的我们讲转型互联网。今天的主题是产品创新,我就讲讲微观的。我在我们的行业里,战略比不上两位马总,我从骨子里本质上还是一个产品经理,可以分享一些做产品的心得。

最近互联网有一个很不好的风气,猫三狗四开始装教主,说我是如何成功的。我经常讲成功是偶然,失败是必然。大家的愚蠢上是有共性的,很多人在失败上会犯同一个错误。当你了解别人的产品是如何失败的,你才能在自己做产品的过程中避开这些暗礁。

+互联网还是互联网+?

大家最近都在谈互联网+,我理解大概有两种用互联网的方法,一种是+互联网,一种是互联网+。这两个有什么不一样?

+互联网:很多人希望把传统行业跟互联网结合,这是一种术,比如说我在互联网开一个店卖东西,在互联网上打广告,甚至是你在互联网上雇水军黑老周,云计算,大数据,这些做法都叫+互联网。因为没有改变某一个行业或者说产品的本质,你只是利用互联网把它改的更加有效率,不会产生爆炸性的指数级的变化。这种做法是传统企业转型互联网最简单的,所以说今天这不在我们的话题之内。

今天热门的互联网+有什么不一样呢,我的理解就是利用互联网的思维,去指导一个产品或者一个行业去改变它的产品体验,看待用户的方式,它和用户的连接方式,改变它的商业模式,从而产生真正的资源重新配置,产生化学反应甚至是核反映的效果。

连接的力量

过去,很多传统大咖看不上互联网,认为是一群毛孩子忽悠国外VC的钱在国内乱烧。行业里有一位我比较尊重的老朋友丁磊,2000年互联网泡沫破碎,他为了给自己和大家打气弄了一个广告,当年我没有看懂。过了10年我终于理解,那个广告道出了互联网的真谛――网聚人的力量。网络之所以牛,因为网络把很多东西连在一起。

大家今天谈一个词“连接”,你要考虑你的产品如何能够真正把很多东西连接在一起。这个东西可以是人,可以是企业,也可以是信息。只有理解了连接,你才会理解为什么很多行业会被颠覆。UBER为什么这么热,因为它改变了连接关系。为什么微信会比QQ牛,因为他真正把人连在一起。

下一个趋势是什么?

有两种思路创新和做产品,一种是站在过去看现在,一种是站在现在看未来。当你创业,当你要创新,你一定要做未来的事情。未来的事情有两种,要不就是别人没有做过的事情,要不就是把别人做过的事情换一种别人想不到的方式去干。所以我认为只有做一件今天大家可能都不看好,但是明天后天有可能起来的事情,你才可能获得巨大的成功。

未来我觉得有两个趋势,一个趋势IOE(Internet of Everything)或者O2O,我觉得更多的就是面对服务业讲的。利用互联网把很多服务业来进行改造,无论今天的上门服务还是打车、定餐等等所有东西这是一大块。

还有一个趋势IOT(Internet of Things),把今天很多的物理器件变成智能设备,把它们和云端连接在一起,意味着今天你所有看到的东西都可以被智能化,无线化,移动化、云端化。我特别不喜欢一个词“物联网”,被很多人庸俗化成一个传感器的网络。

如果说仅仅是一个传感器没有价值,重要的就是每一个设备都是智能的,它采集数据,做出一些智能的判断,再把数据反馈到云端,然后云端汇总成大数据,大数据再产生一些结果再反馈给各个智能设备。所以说今天在我的眼里,在IOT的世界里面所有的东西都是手机。你们想想未来手表是不是手机,眼镜是不是手机。

前几天我看了一个行业大老,我说智能汽车就是四个轮子的苹果,他一听特别的激动,他说我早就这么认为,但是大家不认同。所有的东西你可以认为都是手机,所以说今天做手机也不一定就要造放在耳朵边上的东西,谁说五年以后手机还是这样呢?手机有可能变成这样的。

今天所谓的车联网,当你坐车的时候,你真的需要再把手机打开吗?可能车本身就变成了智能的系统,车有屏幕,车也可以跟你对话。车子里有全套的通信系统,回到家里把手机一放,家里到处都是智能设备,有摄象头,有电视,有音响各种的家电设备。包括你身上穿戴的各种东西,我一直在想能不能做一个可充电的皮带,在皮带里装满了电池。

我们公司的员工创造性特别高,有一个员工做了一个智能捕鼠器,有特别多的智能功能,比如手机可以遥控,可以把老鼠电死,掐死噪音把它震的口鼻出血。但是有一个缺点,就是需要抓老鼠放在捕鼠器,但是对智能设备来说这不是缺点。

前一段时间GE所有的航空发动机里装上一个智能的设备记录发动机的运转数据。同时,会把数据汇总到GE总部,通过大数据告诉航空公司,你的这个发动机有一点问题,跟其它的数据曲线不一样。

这里不仅意味着很多的设备可以智能化,最重要的就是很多硬件产品的用户体验将会被重新改变,商业模式会被改变。换句话说,以后大部分产品,特别是3C产品,除了苹果以外,卖硬件赚钱的机会都会没有。以后的很多设备只是会变成连接。

用户还是客户?

很多企业转型互联网,他们恨不得在一夜之间引刀自宫,结果最后就流血而死。很多人说我们知道,马总给我们讲了连接,我说连你个头,你连接谁?他们就无语了。还有很多的企业说老周我有大数据,我说您那一个硬盘的数据,不叫大数据。最重要的就是转换一个概念,你们原来心中只知道有客户,而不知道有用户。在座的诸位知道用户和客户的差别吗?其实不是简单付不付钱作为代表。

传统行业我和大家讲,产品业务很复杂,商业模式特简单,谁掏钱就是它的客户。它们心中如果只有客户的概念转型不了互联网,为什么?你要建立用户的概念,用户有几个特征。第一,用户不见得向你掏钱。更重要的就是用户要经常性的用你一个什么服务或者产品,连接,交互,这些才是用户的条件。所以做客户容易,做用户难。

如果你有了用户,后续的牌就胡打胡有理。所以用户至上不是一句空话,如果说互联网总结四个字的话我就是用户至上,没有用户的概念怎么连接,所有的连接、大数据等等都是空谈,你就无法建立你的商业模式。

所以很多人一上来就老想说我在互联网里怎么赚钱,我不是不爱钱,我和你们一样的爱钱,但是如果你在互联网上刚开始想怎么赚钱,就想着弄客户,你可能就没有用户。各位想想到底有客户关系还是有用户关系。

很多人讲如何和用户交朋友,如何让用户参与,怎么搞社群,这些都没错。这些方法都很好,但是前提是你得有一个产品或者服务,把用户吸引过来。我举几个例子大家就可以理解用户的价值为什么要高于客户。

滴滴和快的的例子,最早他们做的是打车生意,在打车过程中出租车公司会向滴滴付钱吗?打车的人会向它付钱吗?没有一个人是它的客户,但是它解决了两个问题。打车是不是刚需?打不到车是不是痛点?打车还是比较高频次的业务,解决了一部分用户或者80%的用户高频、刚需和痛点。

跟这些用户建立连接,这些用户和出租车司机原来有连接吗?没有连接。但是现在和打车软件建立连接以后,有了这么多用户,你发现它再下一步往专车走,天下所有小租车公司,或者说有车愿意出租的人,最后都会成为它的客户。前提是因为它连接了很多用户,所以你去看互联网里很多模式到今天的颠覆,都是用户战胜客户。

我们再说互联网电视。今天买了一台电视,买回家以后在历史上和电视厂商还有关系吗?你们家再不换电视,五年之内见不到它。客户价值就是一次性挣了你一笔钱,仅此而已。所以每年对这些企业来说,因为没有连接,所以说当它做了一个新产品以后怎么办?它又得从零做起,又得再打一轮的广告,兄弟们你们的40寸电视过时,我们推出了41寸的电视。周而复始。

所以互联网对电视业的冲击不仅仅是说把电视机加了一个智能设备,最重要的就是电视机以后的销售没有硬件利润。卖电视不再是一个生意,你把电视买回家,服务才刚刚开始。你买电视的决策,取决于里面有没有好玩的游戏,有没有好看的动作片。对传统的电视机厂商是多么大的挑战。你想一下用户和客户的变化。跟传统的行业老板来说,这是一个巨大的变化,不挣钱了。

你可以看到很多企业没有领会到这个本质,还是在做客户关系。只不过在客户关系里加了一些互动,召开客户见面会,这些其实没有改变连接的本质,客户还是觉得你没有给他提供什么持续而有价值的服务。所以我们为什么要从客户转成用户?

道理很简单,因为你只有拥有的用户你才能真正在用户的基础之上,你才能往后走建立粉丝。有了粉丝用户的参与感和用户的社群才能做起来,而且以后的生意,我们说每一个企业都会互联网化,这意味着什么,不仅意味着客户用户化,还意味着会成为一个服务业的企业。

特斯拉最本质的革命不是它前面的那个大Pad电脑,那个操纵特别的不方便。特斯拉改变了车厂和消费者的关系,特斯拉每一个买车的人都是它的客户,这是没有问题。传统车从4S店提走以后,你和车厂还有关系吗?没有。所以你就是客户,但是以后所有的智能汽车就是4个轮子的手机,这个手机时刻和造车公司的服务器连接。

不光有OTA的升级,可能还有各种信息的推送,各种互联网服务。所以将来大家想象一下,每一个造汽车的公司都要变成互联网的导航服务商,可能是一个生活服务指南提供商,或者一个开车时候的音乐电台服务提供商,你会觉得意外吗?一点都不意外,所以这就是连接。

原来我觉得最牛的行业是运营商,为什么说运营商最牛掰呢?因为它又是客户,又是用户,你每一个月交话费买套餐,是不是客户?但是你每天都在打电话发短信、上网,你离不开它的服务,运营商把服务断一分钟你都受不了。你每天都在利用运营商的服务,但是为什么我经常说到微信干掉了运营商呢,你每天在手机里,你大量用的都是微信服务,你和运营商之间的距离越来越远,大家跟运营商之间真的就没有用户关系,还只剩下客户关系。如果以后都像我们设想的那样免费wifi无处不在,你连那个SIM卡都不需要。

所以要通过现象看本质,你就可以理解为什么微信干掉了运营商。将来在这个价值链里面,离用户越近时间越长年度越高的厂商是最有价值的,不然永远是拿利润最微薄的部分。

今天的运营商为什么会出现如此大的变化,因为他们没有搞清楚用户和客户的区别。这两个词一字之差理念非常不一样,所以曾经记得有一段运营商提了一个问题,你的微信收费吗?这是传统的思路,传统思路就是你提供了某一个服务,你就一定要收费,你要收费你就是要把它变成客户。其实马化腾需要收费吗?马化腾现在每年微信投入几十亿,给大家提供免费的通信服务,让你们每个人每天花5个小时在上面,你们都离不开。我想不用都不能,因为你们都用了。有了用户以后互联网的规律是什么呢?胡打胡有理,插根扁担都开花,在上面做什么不好。我几年前演讲的时候就说老马比你老婆还了解你。

Windows搭建Testlink环境

现在团队测试用例是excel管理的,bug追踪用的是mantis,最近准备管理下团队的测试用例,于是打算搭建一下Testlink。

整个搭建过程中,还是遇到几个坑的,最大的原因在于,我以前有一套apache2.2+mysql5.5+php5的环境,结果,下载testlink-1.9.16后发现,这几个环境都太陈旧了,于是全部更新为较新的32位版本。

apache升级到了2.4.27
mysql升级到了5.7.19
php升级到了5.6.31
然后为了这三个程序可以运行,又安装了vc_redist2012、vc_redist2013、dot_net_framework4.0

把testlink-1.9.16解压后,放到apache的www目录下。

1、修改apache配置
httpd.conf

#调整SRVROOT 
Define SRVROOT "C:/Testlink/Apache2.4"

<IfModule dir_module>
    #新增index.php
    DirectoryIndex index.html index.php
</IfModule>

<IfModule mime_module>
    #新增下面一行
    AddType application/x-httpd-php .php .phtml .php3 .php4
</IfModule>

<IfModule alias_module>
    #新增下面一行
    Alias /testlink C:/Testlink/Apache2.4/www/testlink
</IfModule>

#新增一段
<Directory "C:/Testlink/Apache2.4/www/testlink">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

#文件最后新增两行
PHPIniDir "C:/Testlink/PHP"
LoadModule php5_module "C:/Testlink/PHP/php5apache2_4.dll"

2、修改php配置
php.ini

;增加超时时间
session.gc_maxlifetime = 3600
session.cache_expire = 180

;开启以下几个plugin
extension=php_curl.dll
extension=php_gd2.dll
extension=php_mysql.dll
extension=php_mysqli.dll
extension=php_openssl.dll
extension=php_soap.dll

3、新增数据库,字符集utf8

4、新增数据库用户,并设置好权限

5、修改testlink配置
config.inc.php

//默认为中文
$tlCfg->default_language = 'zh_CN';

//日志路径
$tlCfg->log_path = 'C:/Testlink/Apache2.4/logs/';

//上传路径
$g_repositoryPath = 'C:/Testlink/Apache2.4/uploads/';

//SMTP设置
$g_smtp_host        = 'smtp服务器';
$g_tl_admin_email     = '邮箱地址';
$g_from_email         = '邮箱地址'; 
$g_return_path_email  = '邮箱地址';
$g_mail_priority = 5;
$g_phpMailer_method = PHPMAILER_METHOD_SMTP;
$g_smtp_username    = 'smtp用户名';
$g_smtp_password    = 'smtp密码';
$g_smtp_connection_mode = '';
$g_smtp_port = 25;                        
$g_SMTPAutoTLS = false;

6、访问localhost/testlink
选择新安装
查看是否有错误的配置
如果没有则填入数据库相关配置
确认数据库脚本执行成功
安装完毕

7、用admin/admin登录系统

8、删除testlink下的install目录

9、重启PHP

OpenKM6中PDF2SWF中文支持

在使用过程中,发现部分PDF文件是可以正常转换为flash,但部分却只能转换英文,更有甚者直接转换失败。

直接命令行进行转换,发现提示如下:

pdf2swf.exe -f -T 9 -t -s storeallcharacters 1.pdf -o 1.swf 

Error: Unknown character collection 'Adobe-GB1'
Error: Couldn't find 'UniGB-UTF16-H' CMap file for 'Adobe-GB1' collection
Error: Unknown CMap 'UniGB-UTF16-H' for character collection 'Adobe-GB1'
Error: Unknown font tag 'SimSun'
Error: Unknown character collection 'Adobe-GB1'
Error: Couldn't find 'UniGB-UTF16-H' CMap file for 'Adobe-GB1' collection
Error: Unknown CMap 'UniGB-UTF16-H' for character collection 'Adobe-GB1'
Error: Unknown font tag 'SimSun'
...

主要原因是,PDF2SWF中一些字体文件是需要授权的,所以OpenKM6默认是不会带着些文件的。我们配置好就可以使用了:

1、大家可以到网上找这个包xpdf-chinese-simplified。

2、解压到一个路径后,修改包中的add-to-xpdfrc文件,将路径改为正确的路径。

3、用命令行试下,转换是否正常

pdf2swf.exe -f -T 9 -t -s storeallcharacters -s languagedir=X:/OpenKM/extras/xpdf-chinese-simplified/ 1.pdf -o 1.swf 

4、修改OpenKM根目录下openkm.cfg文件中pdf2swf的配置信息

#system.swftools.pdf2swf=X:/OpenKM/Tomcat7/bin/pdf2swf.exe -f -T 9 -t -s storeallcharacters ${fileIn} -o ${fileOut}
system.swftools.pdf2swf=X:/OpenKM/Tomcat7/bin/pdf2swf.exe -f -T 9 -t -s storeallcharacters -s languagedir=X:/OpenKM/extras/xpdf-chinese-simplified/ ${fileIn} -o ${fileOut} 

5、删除flash缓存
X:\OpenKM\tomcat7\repository\cache\swf\*.swf

6、重启OpenKM

看下,是不是已经好了:)

OpenKM6检索功能中文支持

根据OpenKM官方的网站的提示,尝试IK分词的各个版本,都失败了。
没办法,退而求其次,用Lucene自带分词吧。

1、修改OpenKM根目录下openkm.cfg文件,新增下面一行

hibernate.search.analyzer=org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer

2、重启OpenKM
3、到后台,重建Lucene索引
4、现在就可以查询中文了,进行中文搜索时,在前面增加一个”*”号就可以了,比如搜“规范”,就要写“*规范”。

OpenKM6工作流功能浅析

前几天同事和我沟通,希望用工作流类似的功能,对现有的任务进行管理。
说实话,我实在不看好openkm的工作流管理功能(jbpm),但周末还是花了一些时间看了一下。
不看还不知到,看了以后才发现,OpenKM6已经有较长一段时间没有进行框架升级了,还都停留在JDK7早期时代,并且用到了GWt技术。
OpenKM6的工作流,用的是jbpm-3.3.1.GA。看了一下jboss上的jbpm项目,已经进化到jbpm6,好歹开始拥抱drools了吧。
额,扯远了。回到正题。

一、首先是开发环境的搭建,要到这里来下载哦
https://sourceforge.net/projects/openkmportabledev/?source=directory
如果你嫌麻烦,建议就直接解压到指定文件夹就可以用了。

二、打开eclipse,编译jbpm项目
是基于mvn的,有一些jar包,在openkm的仓库里,但下载不到,啥意思吗。自己google咯,还好不算多。

三、编译的时候,建议看下openkm的相关教程,超级简单
https://docs.openkm.com/kcenter/view/wfg/sample-workflow-execution.html

四、介绍一下几个常用节点:
start:工作流的起点,一个流程只允许一个起点
end:工作流的终点,可以有多个,但只要有一个到达终点,流程就结束了
fork、join:就是任务分支与状态同步啦
decision:选择
node:自动处理的任务节点
task node:需要人工干预的任务节点,数据的输入是通过表单form完成的
mail node:自动进行邮件处理的几点
transition:就是两个节点之间的连线

五、构建一个自己的工作流
1、新建一个工作流项目
2、会自动新建一个工作流,打开XXX.jpdl.xml文件,就可以进行编辑了
通过编辑器,自己画一个工作流,会自动生成两个文件,加上刚才的文件一共有三个文件:
XXX.jpdl.xml 节点定义及流程描述
.XXX.gpd.xml 元素的位置信息
XXX.jpg 工作流截图
在发布工作流的时候,这三个文件都要带上
JBPM01

六、写Handler
编辑器上,可以直接处理的操作,其实很有限,也没有太多功能,主要还是靠编码实现。
编码的话,主要是实现几类接口:
org.jbpm.graph.def.ActionHandler:
多数的Handler,都用这个即可,包括transition、mail node、task node、node等
org.jbpm.taskmgmt.exe.Assignable:
主要用于任务执行者的分配
org.jbpm.graph.node.DecisionHandler:
主要用于选择节点

Handeler都只需要实现对于的方法即可。最后,通过编辑器进行配置。
JBPM02

六、发布工作流
发布也很简单,就是直接用eclipse上的jbpm菜单就可以发布了,但要把参数配置好才行
JBPM03

七、执行
1、首先要到openkm的后台,将工作流启用
2、然后找到任意一个文件、文件夹,执行工作流即可

八、填坑
遇到的最大的坑,莫过于字符集问题,我这边的方法,只修正了.XXX.gpd.xml的字符集问题,form的字符集问题,并没有修正
找到文件org.jbpm.gd.jpdl_3.4.1.v20120717-1252-H7-GA-SOA.jar,替换掉文件AbstractContentProvider.class即可
AbstractContentProvider.java源码如下:

package org.jbpm.gd.common.editor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.jbpm.gd.common.model.NamedElement;
import org.jbpm.gd.common.model.SemanticElement;
import org.jbpm.gd.common.notation.AbstractNodeContainer;
import org.jbpm.gd.common.notation.BendPoint;
import org.jbpm.gd.common.notation.Edge;
import org.jbpm.gd.common.notation.Label;
import org.jbpm.gd.common.notation.Node;
import org.jbpm.gd.common.notation.NodeContainer;
import org.jbpm.gd.common.notation.NotationElement;
import org.jbpm.gd.common.notation.NotationElementFactory;
import org.jbpm.gd.common.notation.NotationMapping;
import org.jbpm.gd.common.notation.RootContainer;
import org.jbpm.gd.jpdl.Logger;

public abstract class AbstractContentProvider implements ContentProvider {
	protected abstract SemanticElement getEdgeSemanticElement(Node paramNode,
			Element paramElement, int paramInt);

	protected abstract SemanticElement getNodeSemanticElement(
			NodeContainer paramNodeContainer, Element paramElement, int paramInt);

	protected abstract void addNodes(NodeContainer paramNodeContainer,
			Element paramElement);

	protected abstract void addEdges(Node paramNode, Element paramElement);

	protected abstract SemanticElement findDestination(Edge paramEdge,
			Node paramNode);

	protected String getRootNotationInfoElement() {
		return "<root-container/>";
	}

	protected String createInitialNotationInfo() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		buffer.append("\n\n");
		buffer.append(getRootNotationInfoElement());
		return buffer.toString();
	}

	public String getNotationInfoFileName(String semanticInfoFileName) {
		return ".gpd." + semanticInfoFileName;
	}

	public String getDiagramImageFileName(String semanticInfoFileName) {
		int index = semanticInfoFileName.indexOf(".xml");
		String result = index > -1 ? semanticInfoFileName.substring(0, index)
				: semanticInfoFileName;
		return result + ".jpg";
	}

	protected void processRootContainer(RootContainer rootContainer,
			Element notationInfo) {
		addDimension(rootContainer, notationInfo);
		addNodes(rootContainer, notationInfo);
		postProcess(rootContainer);
	}

	protected void addNodes(NodeContainer nodeContainer,
			SemanticElement[] semanticElements, Element notationInfo) {
		List notationInfoElements = notationInfo == null ? new ArrayList()
				: notationInfo.elements();
		for (int i = 0; i < semanticElements.length; i++) {
			Element notationInfoElement = null;
			String nodeName = ((NamedElement) semanticElements[i]).getName();
			for (int j = 0; j < notationInfoElements.size(); j++) {
				Element element = (Element) notationInfoElements.get(j);
				String elementName = element.attributeValue("name");
				if (((elementName != null) && (elementName.equals(nodeName)))
						|| ((elementName == null) && (nodeName == null))) {
					notationInfoElement = element;
				}
			}

			addNode(nodeContainer, semanticElements[i], notationInfoElement);
		}
	}

	protected void addEdges(Node node, SemanticElement[] semanticElements,
			Element notationInfo) {
		List notationInfoElements = notationInfo == null ? new ArrayList()
				: notationInfo.elements();
		for (int i = 0; i < semanticElements.length; i++) {
			Element notationInfoElement = null;
			if (notationInfoElements.size() >= i + 1) {
				notationInfoElement = (Element) notationInfoElements.get(i);
			}
			addEdge(node, semanticElements[i], notationInfoElement);
		}
	}

	protected void addNode(NodeContainer nodeContainer,
			SemanticElement semanticElement, Element notationInfoElement) {
		String notationElementId = NotationMapping
				.getNotationElementId(semanticElement.getElementId());
		Node notationElement = (Node) nodeContainer.getFactory().create(
				notationElementId);
		notationElement.setSemanticElement(semanticElement);
		notationElement.register();
		nodeContainer.addNode(notationElement);
		semanticElement.addPropertyChangeListener(notationElement);
		processNode(notationElement, notationInfoElement);
		if ((notationElement instanceof NodeContainer)) {
			addNodes((NodeContainer) notationElement, notationInfoElement);
		}
	}

	protected void addEdge(Node node, SemanticElement semanticElement,
			Element notationInfoElement) {
		NotationElement notationElement = node
				.getRegisteredNotationElementFor(semanticElement);
		if (notationElement == null) {
			String notationElementId = NotationMapping
					.getNotationElementId(semanticElement.getElementId());
			notationElement = node.getFactory().create(notationElementId);
			notationElement.setSemanticElement(semanticElement);
			notationElement.register();
			node.addLeavingEdge((Edge) notationElement);
			semanticElement.addPropertyChangeListener(notationElement);
		}
		processEdge((Edge) notationElement, notationInfoElement);
	}

	protected void addDimension(RootContainer processDefinitionNotationElement,
			Element processDiagramInfo) {
		String width = processDiagramInfo.attributeValue("width");
		String height = processDiagramInfo.attributeValue("height");
		Dimension dimension = new Dimension(width == null ? 0 : Integer
				.valueOf(width).intValue(), height == null ? 0 : Integer
				.valueOf(height).intValue());
		processDefinitionNotationElement.setDimension(dimension);
	}

	protected void processNode(Node node, Element notationInfoElement) {
		addConstraint(node, notationInfoElement);
		addEdges(node, notationInfoElement);
	}

	protected void processEdge(Edge edge, Element edgeInfo) {
		processLabel(edge, edgeInfo);
		addBendpoints(edge, edgeInfo);
	}

	protected void addBendpoints(Edge edge, Element edgeInfo) {
		if (edgeInfo != null) {
			List list = edgeInfo.elements("bendpoint");
			for (int i = 0; i < list.size(); i++) {
				addBendpoint(edge, (Element) list.get(i), i);
			}
		}
	}

	protected BendPoint addBendpoint(Edge edge, Element bendpointInfo, int index) {
		BendPoint result = new BendPoint();
		processBendpoint(result, bendpointInfo);
		edge.addBendPoint(result);
		return result;
	}

	protected void processBendpoint(BendPoint bendPoint, Element bendpointInfo) {
		int w1 = Integer.valueOf(bendpointInfo.attributeValue("w1")).intValue();
		int h1 = Integer.valueOf(bendpointInfo.attributeValue("h1")).intValue();
		int w2 = Integer.valueOf(bendpointInfo.attributeValue("w2")).intValue();
		int h2 = Integer.valueOf(bendpointInfo.attributeValue("h2")).intValue();
		Dimension d1 = new Dimension(w1, h1);
		Dimension d2 = new Dimension(w2, h2);
		bendPoint.setRelativeDimensions(d1, d2);
	}

	private void processLabel(Edge edge, Element edgeInfo) {
		Element label = null;
		if (edgeInfo != null) {
			label = edgeInfo.element("label");
		}
		if (label != null) {
			Point offset = new Point();
			offset.x = Integer.valueOf(label.attributeValue("x")).intValue();
			offset.y = Integer.valueOf(label.attributeValue("y")).intValue();
			edge.getLabel().setOffset(offset);
		}
	}

	private void addConstraint(Node node, Element nodeInfo) {
		Rectangle constraint = node.getConstraint().getCopy();
		Dimension initialDimension = NotationMapping.getInitialDimension(node
				.getSemanticElement().getElementId());
		if (initialDimension != null) {
			constraint.setSize(initialDimension);
		}
		if (nodeInfo != null) {
			constraint.x = Integer.valueOf(nodeInfo.attributeValue("x"))
					.intValue();
			constraint.y = Integer.valueOf(nodeInfo.attributeValue("y"))
					.intValue();
			constraint.width = Integer
					.valueOf(nodeInfo.attributeValue("width")).intValue();
			constraint.height = Integer.valueOf(
					nodeInfo.attributeValue("height")).intValue();
		}
		node.setConstraint(constraint);
	}

	protected void postProcess(NodeContainer nodeContainer) {
		List nodes = nodeContainer.getNodes();
		for (int i = 0; i < nodes.size(); i++) {
			Node node = (Node) nodes.get(i);
			List edges = node.getLeavingEdges();
			for (int j = 0; j < edges.size(); j++) {
				Edge edge = (Edge) edges.get(j);
				SemanticElement destination = findDestination(edge, node);
				Node target = (Node) edge.getFactory()
						.getRegisteredNotationElementFor(destination);
				if ((target != null) && (edge.getTarget() == null)) {
					target.addArrivingEdge(edge);
				}
			}
			if ((node instanceof NodeContainer)) {
				postProcess((NodeContainer) node);
			}
		}
	}

	public boolean saveToInput(IEditorInput input, RootContainer rootContainer) {
		boolean result = true;
		try {
			IFile file = getNotationInfoFile(((IFileEditorInput) input)
					.getFile());
			InputStreamReader reader = new InputStreamReader(file.getContents(), "UTF-8");
			Element notationInfo = new SAXReader().read(reader)
					.getRootElement();
			if (upToDateCheck(notationInfo)) {
				getNotationInfoFile(((IFileEditorInput) input).getFile())
						.setContents(
								new ByteArrayInputStream(toNotationInfoXml(
										rootContainer).getBytes("UTF-8")),
								true, true, null);
			} else {
				result = false;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	protected String toNotationInfoXml(RootContainer rootContainer) {
		StringWriter writer = new StringWriter();
		write(rootContainer, writer);
		return writer.toString();
	}

	protected void write(RootContainer rootContainer, Writer writer) {
		try {
			Document document = DocumentHelper.createDocument();
			Element root = document.addElement("root-container");
			write(rootContainer, root);
			XMLWriter xmlWriter = new XMLWriter(writer,
					OutputFormat.createPrettyPrint());
			xmlWriter.write(document);
		} catch (IOException e) {
			e.printStackTrace(new PrintWriter(writer));
		}
	}

	protected void write(RootContainer rootContainer, Element element) {
		addAttribute(element, "name",
				((NamedElement) rootContainer.getSemanticElement()).getName());
		addAttribute(element, "width",
				Integer.toString(rootContainer.getDimension().width));
		addAttribute(element, "height",
				Integer.toString(rootContainer.getDimension().height));
		Iterator iter = rootContainer.getNodes().iterator();
		while (iter.hasNext()) {
			write((Node) iter.next(), element);
		}
	}

	protected void write(Node node, Element element) {
		Element newElement = null;
		if ((node instanceof AbstractNodeContainer)) {
			newElement = addElement(element, "node-container");
		} else {
			newElement = addElement(element, "node");
		}
		addAttribute(newElement, "name",
				((NamedElement) node.getSemanticElement()).getName());
		addAttribute(newElement, "x", String.valueOf(node.getConstraint().x));
		addAttribute(newElement, "y", String.valueOf(node.getConstraint().y));
		addAttribute(newElement, "width",
				String.valueOf(node.getConstraint().width));
		addAttribute(newElement, "height",
				String.valueOf(node.getConstraint().height));
		if ((node instanceof AbstractNodeContainer)) {
			Iterator nodes = ((AbstractNodeContainer) node).getNodes()
					.iterator();
			while (nodes.hasNext()) {
				write((Node) nodes.next(), newElement);
			}
		}
		Iterator edges = node.getLeavingEdges().iterator();
		while (edges.hasNext()) {
			Edge edge = (Edge) edges.next();
			write(edge, addElement(newElement, "edge"));
		}
	}

	protected void write(Edge edge, Element element) {
		Point offset = edge.getLabel().getOffset();
		if (offset != null) {
			Element label = addElement(element, "label");
			addAttribute(label, "x", String.valueOf(offset.x));
			addAttribute(label, "y", String.valueOf(offset.y));
		}
		Iterator bendpoints = edge.getBendPoints().iterator();
		while (bendpoints.hasNext()) {
			write((BendPoint) bendpoints.next(),
					addElement(element, "bendpoint"));
		}
	}

	protected void write(BendPoint bendpoint, Element bendpointElement) {
		addAttribute(bendpointElement, "w1",
				String.valueOf(bendpoint.getFirstRelativeDimension().width));
		addAttribute(bendpointElement, "h1",
				String.valueOf(bendpoint.getFirstRelativeDimension().height));
		addAttribute(bendpointElement, "w2",
				String.valueOf(bendpoint.getSecondRelativeDimension().width));
		addAttribute(bendpointElement, "h2",
				String.valueOf(bendpoint.getSecondRelativeDimension().height));
	}

	protected Element addElement(Element element, String elementName) {
		Element newElement = element.addElement(elementName);
		return newElement;
	}

	protected void addAttribute(Element e, String attributeName, String value) {
		if (value != null) {
			e.addAttribute(attributeName, value);
		}
	}

	private void createNotationInfoFile(IFile notationInfoFile) {
		try {
			notationInfoFile.create(new ByteArrayInputStream(
					createInitialNotationInfo().toString().getBytes("UTF-8")),
					true, null);
		} catch (CoreException e) {
			Logger.logError(e);
		} catch (UnsupportedEncodingException e) {
			Logger.logError(e);
		}
	}

	protected IFile getNotationInfoFile(IFile semanticInfoFile) {
		IProject project = semanticInfoFile.getProject();
		IPath semanticInfoPath = semanticInfoFile.getProjectRelativePath();
		IPath notationInfoPath = semanticInfoPath.removeLastSegments(1).append(
				getNotationInfoFileName(semanticInfoFile.getName()));
		IFile notationInfoFile = project.getFile(notationInfoPath);
		if (!notationInfoFile.exists()) {
			createNotationInfoFile(notationInfoFile);
		}
		return notationInfoFile;
	}

	public void addNotationInfo(RootContainer rootContainer, IEditorInput input) {
		try {
			IFile file = getNotationInfoFile(((FileEditorInput) input)
					.getFile());
			if (file.exists()) {
				InputStreamReader reader = new InputStreamReader(
						file.getContents(),"UTF-8");
				Element notationInfo = new SAXReader().read(reader)
						.getRootElement();
				boolean changed = convertCheck(notationInfo);
				processRootContainer(rootContainer, notationInfo);
				if (changed) {
					file.setContents(
							new ByteArrayInputStream(toNotationInfoXml(
									rootContainer).getBytes("UTF-8")), true,
							true, null);
				}
			} else {
				file.create(new ByteArrayInputStream(
						createInitialNotationInfo().toString()
								.getBytes("UTF-8")), true, null);
			}
		} catch (DocumentException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (CoreException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private boolean convertCheck(Element notationInfo) {
		boolean changed = false;
		if (("process-diagram".equals(notationInfo.getName()))
				|| ("pageflow-diagram".equals(notationInfo.getName()))) {
			MessageDialog dialog = new MessageDialog(
					null,
					"Convert To 3.1.x Format",
					null,
					"A file created with an older GPD version was detected. If you open this file it will be converted to the 3.1.x format and overwritten.\nDo you want to continue?",
					3, new String[] { "Convert And Open",
							"Continue Without Converting" }, 0);
			if (dialog.open() == 0) {
				convertToRootContainer(notationInfo);
				changed = true;
			}
		}
		return changed;
	}

	private boolean upToDateCheck(Element notationInfo) {
		if (("process-diagram".equals(notationInfo.getName()))
				|| ("pageflow-diagram".equals(notationInfo.getName()))) {
			MessageDialog dialog = new MessageDialog(
					null,
					"GPD 3.0.x Format Detected",
					null,
					"The file you are trying to save contains GPD 3.0.x information.Saving the file will result in an automatic conversion into the 3.1.x format.It will be impossible to open it with the old GPD.\nDo you want to continue?",
					3, new String[] { "Save And Convert", "Cancel" }, 0);
			return dialog.open() == 0;
		}
		return true;
	}

	private void convertToRootContainer(Element notationInfo) {
		notationInfo.setName("root-container");
		convertChildrenToEdge(notationInfo);
	}

	private void convertChildrenToEdge(Element element) {
		List list = element.elements();
		for (int i = 0; i < list.size(); i++) {
			convertToEdge((Element) list.get(i));
		}
	}

	private void convertToEdge(Element element) {
		if ("transition".equals(element.getName())) {
			element.setName("edge");
		}
		convertChildrenToEdge(element);
	}
}

结语:
1、jbpm如果要用起来,还是要有很大的工作量的,而且早期版本对中文支持并不友好
2、openkm的工作流,整体来说太弱了,不够假单和直观
3、这种流程式管理方式,并不符合我们的管理方式
4、先用openkm的文档管理功能就好了
5、如果openkm再不升级框架,估计后面我们要找其他文档管理工具了。

OpenKM6搭建说明

近期发现,公司的文档管理十分的混乱,于是准备找一个文档管理工具来。

主要找了几类工具,第一类是Wiki,第二类是内容管理系统CMS,第三类是文档管理系统DMS,第四类是企业文档管理系统EDMS,第五类为在线协作软件。

我们的要求有:
1、可以兼容大量的历史文档
2、文档要在本地,不能在云端
3、最好可以直接版本管理和检索功能
4、最好免费

查看了这些软件的主流厂商,发现只有DMS文档比较适合我们:
1、wiki、多数的CMS希望大家用在线工具进行写作,我们有大量的word和execel文档,很难直接导入
2、在线协作软件要把文档托管在云端,也不适合我们
3、EDMS系统看了几个,发现功能太多,也不适合我们

最后选用了DMS,找了Alfresco免费版、LogicalDOC免费版和OpenKM免费版。周末自己进行了搭建测试,发现我的思路和Alfresco格格不入,设计理念太逆天了。LogicalDOC和OpenKM都不错,最后感觉OpenKM操作更顺畅一些,选用了OpenKM。

OpenKM有这几种搭建方式:
1、通过OKMInstaller.jar
https://sourceforge.net/projects/openkm/files/common/
2、通过打好的安装包,包括linux和windows版本,推荐
https://sourceforge.net/projects/openkm/files/6.3.2/
3、通过bundle
https://sourceforge.net/projects/openkm/files/6.3.2/
4、纯手工,从war包开始
https://sourceforge.net/projects/openkm/files/6.3.4/

本文介绍第3种方式。因为第1、2种方式相对简单,第4种方式和第3种方式基本一样(一定要到上面提到的common目录下下载tomcat)。

1、首先,下载最新的bundle,选择openkm-6.3.2-community-tomcat-bundle.zip,并解压
https://sourceforge.net/projects/openkm/files/6.3.2/

2、下载extra,并解压
https://sourceforge.net/projects/openkm/files/common/

3、安装mysql,新建数据库,新建用户,授权

4、修改解压后bundle文件夹中的OpenKM.cfg文件

# OpenKM Hibernate configuration values
#把方言改为MySQL
hibernate.dialect=org.hibernate.dialect.MySQLDialect
#第一次运行,一定要改为create
#运行后,系统会自动改为null
hibernate.hbm2ddl=none

# Initial configuration - Linux
#system.imagemagick.convert=/usr/bin/convert
#system.openoffice.path=/usr/lib/libreoffice
#system.swftools.pdf2swf=/opt/openkm/bin/pdf2swf -f -T 9 -t -s storeallcharacters ${fileIn} -o ${fileOut}

# Initial configuration - Windows
# 按extra文件的位置,改一下路径
system.imagemagick.convert=C:/NeoECM/OpenKM/Tomcat7/bin/convert.exe
system.openoffice.path=C:/NeoECM/OpenKM/extras/ApacheOpenOffice_4.1.1/Bin/OpenOffice 4
system.swftools.pdf2swf=C:/NeoECM/OpenKM/Tomcat7/bin/pdf2swf.exe -f -T 9 -t -s storeallcharacters ${fileIn} -o ${fileOut}

5、修改解压后bundle文件夹中conf/server.xml,修改数据库连接方式为mysql

    <Resource name="jdbc/OpenKMDS" auth="Container" type="javax.sql.DataSource"
            maxActive="100" maxIdle="30" maxWait="10000" validationQuery="select 1"
            username="openkm" password="openkm" driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/openkm?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF8"/>
                
    <!--Resource name="jdbc/OpenKMDS" auth="Container" type="javax.sql.DataSource"
            maxActive="100" maxIdle="30" maxWait="10000" validationQuery="select 1 from INFORMATION_SCHEMA.SYSTEM_USERS"
            username="sa" password="" driverClassName="org.hsqldb.jdbcDriver"
            url="jdbc:hsqldb:${catalina.base}/repository/okmdb"/-->

6、运行startup.bat

7、如果有错误,请查看日志。如果没有错误,就可以用okmadmin/admin进行登录了。

8、关掉控制台

9、将bin/win-x64下两个文件,拷贝到bin下面

10、命令行注册为服务

service install OpenVM

11、启动服务编辑界面

tomcat7w //ES//OpenVM

12、点击启动服务即可

13、如果遇到问题,请排查一下内容
A、JVM版本和tomcat的tomcat7版本是否同为32或同为64
B、tomcat7w界面中jvm.dll选择是否正确
C、如果还报错,可以把jdk/jre下的msvc*.dll拷贝到bin目录下面

Webmin搭建说明

1、下载安装包

wget http://prdownloads.sourceforge.net/webadmin/webmin_1.840_all.deb

2、安装依赖包

apt-get install perl libnet-ssleay-perl openssl libauthen-pam-perl libpam-runtime libio-pty-perl apt-show-versions python libapt-pkg-perl

3、安装webmin

dpkg --install webmin_1.840_all.deb

4、访问地址http://localhost:10000/
用户名:root,密码:系统root密码

OpenStack搭建私有云10

本节介绍对象存储的基本操作,仅在CT01进行操作

. user01-openrc
#查看状态
swift stat
#新建container
openstack container create container01
#文件上传
openstack object create container01 hi.txt
#文件ls
openstack object list container01
#查看文件信息
openstack object show container01 hi.txt
#设置tag
openstack object set --property owner=neohope container01 hi.txt
#查看文件信息
openstack object show container01 hi.txt
#取消tag
openstack object unset --property owner container01 hi.txt
#查看文件信息
openstack object show container01 hi.txt
#取回文件
mv hi.txt hi.txt.bak
openstack object save container01 hi.txt
#删除文件
openstack object delete container01 hi.txt

PS:
如果遇到权限问题,可以尝试将/srv/node安全级别降到最低

#chcon -R system_u:object_r:swift_data_t:s0 /srv/node

OpenStack搭建私有云09

本节开始安装swift,用于对对象存储进行管理,需要在CT01、OS01、OS02进行操作
一、在CT01安装对应模块
1、新建用户及endpoint

. admin-openrc
openstack user create --domain default --password-prompt swift
openstack role add --project serviceproject --user swift admin
openstack service create --name swift --description "OpenStack Object Storage" object-store

openstack endpoint create --region Region01 object-store public http://CT01:8080/v1/AUTH_%\(tenant_id\)s
openstack endpoint create --region Region01 object-store internal http://CT01:8080/v1/AUTH_%\(tenant_id\)s
openstack endpoint create --region Region01 object-store admin http://CT01:8080/v1

2、安装

apt-get install swift swift-proxy python-swiftclient python-keystoneclient python-keystonemiddleware memcached

3、修改配置文件
3.1、新建目录/etc/swift,并下载文件

curl -o /etc/swift/proxy-server.conf https://git.openstack.org/cgit/openstack/swift/plain/etc/proxy-server.conf-sample?h=stable/newton

3.2修改配置文件
/etc/swift/proxy-server.conf

[DEFAULT]
bind_port = 8080
user = swift
swift_dir = /etc/swift

[pipeline:main]
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk ratelimit authtoken keystoneauth container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server

[app:proxy-server]
use = egg:swift#proxy
account_autocreate = True

[filter:keystoneauth]
use = egg:swift#keystoneauth
operator_roles = admin,user

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
auth_uri = http://CT01:5000
auth_url = http://CT01:35357
memcached_servers = CT01:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = serviceproject
username = swift
password = swift
delay_auth_decision = True

[filter:cache]
use = egg:swift#memcache
memcache_servers = CT01:11211

二、在OS01、OS02安装对应模块
1、硬盘初始化(每台虚拟机分配两块硬盘)

apt-get install xfsprogs rsync
mkfs.xfs /dev/sdb
mkfs.xfs /dev/sdc
mkdir -p /srv/node/sdb
mkdir -p /srv/node/sdc

2、修改/etc/fstab

/dev/sdb /srv/node/sdb xfs noatime,nodiratime,nobarrier,logbufs=8 0 2
/dev/sdc /srv/node/sdc xfs noatime,nodiratime,nobarrier,logbufs=8 0 2

3、挂载硬盘

mount /srv/node/sdb
mount /srv/node/sdc

4、修改/etc/rsyncd.conf

uid = swift
gid = swift
log file = /var/log/rsyncd.log
pid file = /var/run/rsyncd.pid
address = 10.0.3.13

[account]
max connections = 2
path = /srv/node/
read only = False
lock file = /var/lock/account.lock

[container]
max connections = 2
path = /srv/node/
read only = False
lock file = /var/lock/container.lock

[object]
max connections = 2
path = /srv/node/
read only = False
lock file = /var/lock/object.lock

5、修改/etc/default/rsync

RSYNC_ENABLE=true

6、重启rsync

service rsync start

7、软件安装

apt-get install swift swift-account swift-container swift-object
curl -o /etc/swift/account-server.conf https://git.openstack.org/cgit/openstack/swift/plain/etc/account-server.conf-sample?h=stable/newton
curl -o /etc/swift/container-server.conf https://git.openstack.org/cgit/openstack/swift/plain/etc/container-server.conf-sample?h=stable/newton
curl -o /etc/swift/object-server.conf https://git.openstack.org/cgit/openstack/swift/plain/etc/object-server.conf-sample?h=stable/newton

8、修改/etc/swift/account-server.conf

[DEFAULT]
bind_ip = 10.0.3.13
bind_port = 6202
user = swift
swift_dir = /etc/swift
devices = /srv/node
mount_check = True

[pipeline:main]
pipeline = healthcheck recon account-server

[filter:recon]
use = egg:swift#recon
recon_cache_path = /var/cache/swift

9、修改/etc/swift/container-server.conf

[DEFAULT]
bind_ip = 10.0.3.13
bind_port = 6201
user = swift
swift_dir = /etc/swift
devices = /srv/node
mount_check = True

[pipeline:main]
pipeline = healthcheck recon container-server

[filter:recon]
use = egg:swift#recon
recon_cache_path = /var/cache/swift

10、修改/etc/swift/object-server.conf

[DEFAULT]
bind_ip = 10.0.3.13
bind_port = 6200
user = swift
swift_dir = /etc/swift
devices = /srv/node
mount_check = True

[pipeline:main]
pipeline = healthcheck recon object-server

[filter:recon]
use = egg:swift#recon
recon_cache_path = /var/cache/swift
recon_lock_path = /var/lock

11、授权

chown -R swift:swift /srv/node
mkdir -p /var/cache/swift
chown -R root:swift /var/cache/swift
chmod -R 775 /var/cache/swift

三、在CT01进行配置
1、创建配置文件

cd /etc/swift

swift-ring-builder account.builder create 10 3 1
swift-ring-builder account.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6202 --device sdb --weight 100
swift-ring-builder account.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6202 --device sdc --weight 100
swift-ring-builder account.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6202 --device sdb --weight 100
swift-ring-builder account.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6202 --device sdc --weight 100
swift-ring-builder account.builder
swift-ring-builder account.builder rebalance

swift-ring-builder container.builder create 10 3 1
swift-ring-builder container.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6201 --device sdb --weight 100
swift-ring-builder container.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6201 --device sdc --weight 100
swift-ring-builder container.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6201 --device sdb --weight 100
swift-ring-builder container.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6201 --device sdc --weight 100
swift-ring-builder container.builder
swift-ring-builder container.builder rebalance

swift-ring-builder object.builder create 10 3 1
swift-ring-builder object.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6200 --device sdb --weight 100
swift-ring-builder object.builder add --region 1 --zone 1 --ip 10.0.3.13 --port 6200 --device sdc --weight 100
swift-ring-builder object.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6200 --device sdb --weight 100
swift-ring-builder object.builder add --region 1 --zone 2 --ip 10.0.3.14 --port 6200 --device sdc --weight 100
swift-ring-builder object.builder
swift-ring-builder object.builder rebalance

2、拷贝配置文件
将account.ring.gz、container.ring.gz和object.ring.gz拷贝到OS02和OS02的目录/etc/swift

3、下载配置文件

sudo curl -o /etc/swift/swift.conf https://git.openstack.org/cgit/openstack/swift/plain/etc/swift.conf-sample?h=stable/newton

4、编辑/etc/swift/swift.conf

[swift-hash]
swift_hash_path_suffix = neohope
swift_hash_path_prefix = neohope

[storage-policy:0]
name = Policy-0
default = yes

5、拷贝配置文件swift.conf,到所有节点的/etc/swift

6、在非对象存储节点运行

chown -R root:swift /etc/swift
service memcached restart
service swift-proxy restart

7、在对象存储节点运行

chown -R root:swift /etc/swift
swift-init all start