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目录下面

SVN与ReviewBoard联动(3)

前面做测试,生成了很多错误的review request。
但没有找到可以批量删除的工具,于是自己写了一个。

deleterequest.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Created on 2016-11-24
@author: Hansen
获取指定svn文件夹下,指定svn版本的注释
'''

import os
import sys

#获取指定svn版本的注释
def del_rb_request(rid1,rid2):
    for rid in range(rid1,rid2):
        cmd='curl -X DELETE --header "Authorization: token API_TOKEN" http://127.0.0.1/api/review-requests/'+str(rid)+'/'
        print(cmd)
        logs = os.popen(cmd.encode()).readlines()
        for log in logs:
            print(log)

#start here
del_rb_request(0,86)

SVN与ReviewBoard联动(2)

接上一篇。

1、脚本写了没多久,但郁闷的是测通上面的脚本我花了很久,后来发现主要是字符集的问题。

我用的是Debian默认字符集为UTF8,python2.7默认字符集为ASCII。
后面,设置了python的字符集默认为UTF8,第一篇的脚本才测通了。
/usr/lib/python2.7/sitecustomize.py

# install the apport exception handler if available
import sys

sys.setdefaultencoding('UTF8')

try:
    import apport_python_hook
except ImportError:
    pass
else:
    apport_python_hook.install()

2、然后发现RBT貌似不支持UTF8的中文参数,反正我没有测通,好吧,自己写了一个脚本将SVN上的中文注释更新到mysql中。
说明一下,RBT的这个问题,我已经向社区提交Bug了,亲测补丁有效,大家等待下一版本就好了。

#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Created on 2016-11-24
@author: Hansen
用于更新rb的说明
'''

import sys
import datetime
import MySQLdb
import nsvnutils

#记录日志
def nlog(logpath,msg):
    fo = open(logpath,'a+')
    try:
        fo.write(msg+'\n')
    finally:
        fo.close()

#更新rb的reviewrequest说明
def update_rb_comment():
    #获取连接
    try:
        connection=MySQLdb.connect(user='user',passwd="passwd",host='127.0.0.1',port=3306,db='db',charset='utf8')
        subconnection=MySQLdb.connect(user='user',passwd="passwd",host='127.0.0.1',port=3306,db='db',charset='utf8')
        #print('mysql connection ok')
        nlog('/mnt/rb/sql.log','mysql connection ok')
    except Exception as ex:
        #print(str(ex))
        nlog('/mnt/rb/sql.log',str(ex))
        return

    #获取需要处理的数据
    try:
        cursor=connection.cursor()
	subcursor=subconnection.cursor()
        sql="select id,summary from reviews_reviewrequest r where r.description='' or r.description is null"
        cursor.execute(sql)
        #print('mysql select ok')
        nlog('/mnt/rb/sql.log','mysql select ok')
        for row in cursor.fetchall():
            rid=str(row[0])
            ss=row[1].split('_Rev')
	    if len(ss)==2:
                nsvnutils.get_repo_name(ss[0])
                comment=nsvnutils.get_svn_log(ss[1])
		commiter=nsvnutils.get_svn_commiter(ss[1])
                subsql="update reviews_reviewrequest r set r.description='"+commiter+"_"+comment+"' where r.id="+rid
	        #print(subsql)
                nlog('/mnt/rb/sql.log',subsql)
		subcursor.execute(subsql)
            else:
                #print('skiping id='+rid+' summary='+row[1])
                nlog('/mnt/rb/sql.log','skiping id='+rid+' summary='+row[1])
        subconnection.commit()
    except Exception as ex:
        #print(str(ex))
        nlog('/mnt/rb/sql.log',str(ex))
	return
    finally:
        cursor.close()
	subcursor.close()

    connection.close()
    subconnection.close()

#start here
nlog("/mnt/rb/sql.log", 'Starting job on '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
update_rb_comment()
nlog("/mnt/rb/sql.log", 'Ending job on '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))

3、建立定时任务,执行脚本

crontab -l

#10分钟运行一次
*/10 * * * * /home/neohope/scripts/updatedb.py

SVN与ReviewBoard联动(1)

公司的SVN服务器是在Windows上的,ReviewBoard是在Linux虚拟服务器上的。
好吧,主要是历史原因了哦。。。

解决思路是,利用SVN的post-commit钩子,在提交后,将版本号存到一个文件中。
在Linux服务器中,定时去扫描这些文件,有变动的话,则利用RBTools提交请求。

1、Windows中用post-commit钩子获取版本号
post-commit.bat

@echo off
SET REPOS=%1%
SET REV=%2%
SET RPATH=PATH_TO_STORE_FILE

CALL :getfilename %REPOS%

echo %REV% >> %RPATH%\%FNAME%.txt

:success
exit 0

:getfilename
set FNAME=%~nx1

2、Linux中用python脚本扫描文件变动
runme.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Created on 2016-11-24
@author: Hansen
遍历监视文件夹下所有的txt文件
当文件多余两行时,对于每一行,调用rbt生成review请求
最后,只保留最后一行
'''

import os
import sys
import datetime
import nsvnutils

#记录日志
def nlog(logpath,msg):
    fo = open(logpath,'a+')
    try:
        fo.write(msg)
    finally:
        fo.close()

#创建rbt命令,并调用命令
def create_rbt_command(repo, revision1, revision2, logpath):
    title = repo+"_Rev"+revision1
    comment = nsvnutils.get_svn_log(revision1)
    commiter = nsvnutils.get_svn_commiter(revision1)
    #rbt好像不支持utf8参数,因此用更新db的方式添加注释
    cmd = 'rbt post -p --repository ' +repo+" --repository-type svn --server http://127.0.0.1 --api-token API_TOKEN --summary "+title+' '+revision1 
    fo = open(logpath,'a+')
    try:
        fo.write('adding rbt job on '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+'\n')
	fo.write('PWD is '+os.getcwd()+'\n')
	fo.write('CMD is '+cmd+'\n')
	logs = os.popen(cmd.encode('UTF8')).readlines()
        for log in logs:
	   fo.write(log+'\n')
	fo.write('\n')
    finally:
        fo.close()

#写回最后一行
def write_last_line(txtpath,line):
    fo = open(txtpath,'w+')
    try:
	fo.write(line)
    finally:
        fo.close()

#遍历行
#多于两行则自动生成post review
def enum_line(txtpath,logpath,repo):
    nlog('/mnt/rb/rb.log','Trying repositor '+repo+'\n')
    fi = open(txtpath)
    try:
	lines=fi.readlines()
	linesize=len(lines)
	if linesize <= 1:
            nlog('/mnt/rb/rb.log','Skiping repositor '+repo+'\n')
            return

        for i in range(0, linesize):
            if i+1<linesize:
		rbRepo = nsvnutils.get_repo_name(repo)
                if len(rbRepo)==0 :
                    nlog('/mnt/rb/rb.log','Skiping repositor '+repo+'\n')
                    return

                nlog('/mnt/rb/rb.log','Parsing repository '+rbRepo+'\n')
		create_rbt_command(rbRepo,lines[i].replace('\n','').replace('\r',''),lines[i+1].replace('\n','').replace('\r',''),logpath)
    except Exception as ex:
        nlog('/mnt/rb/rb.log',str(ex))
    finally:
        fi.close()

    write_last_line(txtpath,lines[linesize-1])

#递归遍历文件
def enum_file(targetdir,sufix,logpath):
    for root, dirs, files in os.walk(targetdir, True):
        for fname in files:
            fsufix = os.path.splitext(fname)[1][1:]
	    repo = os.path.splitext(fname)[0]
            if sufix == fsufix:
                txtpath = root+"/"+fname
                #txtpath = root+"\\"+fname
		enum_line(txtpath,logpath,repo)


#start here
nlog("/mnt/rb/rb.log", 'Starting job on '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+'\n')
enum_file("/mnt/rb","txt", "/mnt/rb/rb.log")
#enum_file("D:/MyProducts/Python/ReviewBoard/rb","txt", "D:/MyProducts/Python/ReviewBoard/rb/rb.log")
nlog("/mnt/rb/rb.log", 'Ending job on '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+'\n')

nsvnutils.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Created on 2016-11-24
@author: Hansen
获取指定svn文件夹下,指定svn版本的注释
'''

import os
import sys

#切换路径,并获取repository名称
def get_repo_name(repo):
    mydict = {'REPO1':'REPO1','REPO2':'REPO2'}
    if not mydict.get(repo):
        return '' 
    os.chdir('/home/neohope/repository/'+mydict.get(repo))
    #os.chdir('D:/MyProducts/Python/ReviewBoard/repo/'+mydict(repo))
    return mydict[repo]

#获取指定svn版本的注释
def get_svn_log(revision):
    cmd = 'svn log -r '+revision
    logs = os.popen(cmd).readlines()
    #for log in logs:
    #    print(log)
    if len(logs)>3:
        print(logs[3].replace('\r','').replace('\n',''))
        return logs[3].replace('\r','').replace('\n','')
    else:
        print('no svn comment') 
        return 'no svn comment'

#获取指定svn版本的提交者
def get_svn_commiter(revision):
    cmd = 'svn log -r '+revision
    logs = os.popen(cmd).readlines()
    if len(logs)>2:
        ss=logs[1].split('|')
        if len(ss)>2:
            print(ss[1].strip().replace('\r','').replace('\n',''))
            return ss[1].strip().replace('\r','').replace('\n','')
        else:
            #print('no svn commiter') 
            return 'no svn commiter'
    else:
        #print('no svn commiter') 
        return 'no svn commiter'

#start here
#get_svn_log('193')
#get_svn_commiter('193')

3、建立定时任务,执行脚本

crontab -l

#半小时运行一次
*/30 * * * * /home/neohope/scripts/runme.py

比较麻烦的是,仍需要在Linux下面,先把SVN的repository检出才可以。
同时要注意,要做好SVN及ReviewBoard之间名称的映射。。。

Debain8安装ReviewBoard

1、安装需要的软件及软件包

#更新apt软件列表
apt-get update

#安装mysql
apt-get install mysql-server

#安装apache2
apt-get install apache2

#安装mod_wsgi
apt-get install libapache2-mod-wsgi

#安装memcached
apt-get install memcached

#安装patch
apt-get install patch

#安排subversion
apt-get install subversion

2、配置MySQL

#修改配置文件
vi /etc/mysql/my.cnf
#添加下面内容
[client]
default-character-set=utf8
[mysqld]
character-set-server=utf8


#重启mysql
/etc/init.d/mysql restart


#新建数据库、用户并授权
mysql -u root -p
mysql> CREATE DATABASE reviewboard CHARACTER SET utf8;
mysql> CREATE USER 'reviewboard'@'localhost' IDENTIFIED BY 'reviewboard';
mysql> GRANT ALL PRIVILEGES ON reviewboard.* to 'reviewboard'@'localhost';

3、配置svn(我的svn和reviewboard不是在一台机器上的,不需要重新配置)

4、配置Python2.7环境

#安装python-setuptools
apt-get install python-setuptools

#安装python-dev
apt-get install python-dev

#安装python-mysqldb
apt-get install python-mysqldb
#替代方案
#easy_install mysql-python

#安装python-svn
apt-get install python-svn

#安装 libffi-dev
apt-get install libffi-dev

#安装ReviewBoard
easy_install ReviewBoard

#你可以用pip安装
#eays_install pip
#pip install pipdeptree
#pip install ReviewBoard

5、安装网站

#这句话会报错,因为依赖的Django版本冲突所导致的
rb-site install /var/www/reviewboard

#查看插件依赖
pipdeptree

#可以看到
#ReviewBoard依赖Django [required: <1.7,>=1.6.11, installed: 1.8]
#django-haystack依赖Django [required: >=1.8, installed: 1.8]
argparse==1.2.1
chardet==2.3.0
lxml==3.4.0
MySQL-python==1.2.3
numpy==1.8.2
pycups==1.9.63
pycurl==7.19.5
pygobject==3.14.0
pysmbc==1.0.15.3
python-apt==0.9.3.11
python-debian==0.1.27
  - six [required: None, installed: 1.8.0]
python-debianbts==1.11
pyxdg==0.25
reportbug==6.6.3
ReviewBoard==2.5.1
  - Django [required: <1.7,>=1.6.11, installed: 1.8]
  - django-evolution [required: <=0.7.999,>=0.7.5, installed: 0.7.6]
    - Django [required: <1.7.0,>=1.4.10, installed: 1.8]
  - django-haystack [required: >=2.3.1, installed: 2.5.0]
    - Django [required: >=1.8, installed: 1.8]
    - Django [required: <1.10, installed: 1.8]
  - django-multiselectfield [required: None, installed: 0.1.4]
    - django [required: >=1.4, installed: 1.8]
  - Djblets [required: <=0.9.999,>=0.9, installed: 0.9.3]
    - Django [required: >=1.6.11,<1.8.999, installed: 1.8]
    - django-pipeline [required: <1.3.9999,>=1.3.23, installed: 1.3.27]
      - futures [required: >=2.1.3, installed: 3.0.5]
    - feedparser [required: >=5.1.2, installed: 5.2.1]
    - pillowfight [required: None, installed: 0.2]
      - Pillow [required: None, installed: 2.6.1]
    - pytz [required: None, installed: 2016.7]
  - docutils [required: None, installed: 0.12]
  - markdown [required: <2.4.999,>=2.4.0, installed: 2.4.1]
  - mimeparse [required: >=0.1.3, installed: 0.1.3]
  - paramiko [required: >=1.12, installed: 2.0.2]
    - cryptography [required: >=1.1, installed: 1.5.2]
      - cffi [required: >=1.4.1, installed: 1.8.3]
        - pycparser [required: None, installed: 2.14]
      - enum34 [required: None, installed: 1.1.6]
      - idna [required: >=2.0, installed: 2.1]
      - ipaddress [required: None, installed: 1.0.17]
      - pyasn1 [required: >=0.1.8, installed: 0.1.9]
      - setuptools [required: >=11.3, installed: 28.6.0]
      - six [required: >=1.4.1, installed: 1.8.0]
    - pyasn1 [required: >=0.1.7, installed: 0.1.9]
  - pycrypto [required: >=2.6, installed: 2.6.1]
  - Pygments [required: >=1.6, installed: 2.0.1]
  - python-dateutil [required: ==1.5, installed: 1.5]
  - python-memcached [required: None, installed: 1.58]
    - six [required: >=1.4.0, installed: 1.8.0]
  - pytz [required: None, installed: 2016.7]
  - recaptcha-client [required: None, installed: 1.0.6]
  - Whoosh [required: >=2.6, installed: 2.7.4]
roman==2.0.0
SOAPpy==0.12.22
  - defusedxml [required: None, installed: 0.4.1]
  - wstools [required: None, installed: 0.4.3]
    - docutils [required: None, installed: 0.12]
wsgiref==0.1.2

#所以降级django-haystack就好了
easy_install -m django
easy_install -m django-haystack
easy_install django-haystack==2.3.1
easy_install reviewboard

6、安装网站,并修改网站权限

#安装网站,按提示输入
rb-site install /var/www/reviewboard
chown -R www-data /var/www/reviewboard/htdocs/media/uploaded
chown -R www-data /var/www/reviewboard/data
chown -R www-data /var/www/reviewboard/logs
chown -R www-data /var/www/reviewboard/htdocs/media/ext
chown -R www-data /var/www/reviewboard/htdocs/static/ext

7、配置访问权限

#配置访问权限
vi /var/www/reviewboard/conf/settings_local.py

#修改下面一行,这是不限制任何访问
ALLOW_HOSTS=['*']

8、配置apache2虚拟目录

cp /var/www/reviewboard/conf/apache-wsgi.conf /etc/apache2/sites-available/reviewboard.conf
#按实际需要的部署情况,编辑reviewboard.conf
cd /etc/apache2/sites-enabled
ln -s ../sites-avaiable/reviewboard.conf

9、重启apache2

/etc/init.d/apache2 restart

10、打开浏览器就可以登录啦

11、另外,我是在虚拟机中部署的reviewboard,我尝试了用nginx反向代理reviewboard,但没有成功。
用firebug看到,json中的ip地址没有改写,最后发现是因为django没有正确的处理absolute_path。
好像增加一些django的配置就好了,但实在是没时间处理了。
最后用bridege方式将虚拟机映射出来,完成部署。

搭建DokuWiki(IIS)

1、下载dokuwiki的安装包
dokuwiki

2、解压文件

3、IIS安装GCI模块
(控制面板-》添加删除Windows功能)

4、IIS安装PHP模块
http://php.iis.net/

5、在IIS上添加网站,路径指向解压目录

6、IIS上将程序池修改为
“No Managed Code”

7、调整权限,让下面几个目录对IUSR可写
data
conf

8、浏览install.php

9、进行设置

10、对网站添加”IIS Request Filtering”,让下面几个目录,不可以通过HTTP进行访问
/data/
/conf/
/bin/
/inc/

11、搞定