前不久简单设计了个php的工作流程引擎,只有后端部分,前端没有做(前端写着好麻烦,于是偷懒了)。
主要功能是流程创建,可以设置流程使用是单独某人可用还是每个部门可用还是某个角色可用,每个流程可以设置自己的编号规则。流程里分为多个工作节点,每个节点可以设置是审批类型是或签还是会签,固定人审批还是某个部门某个角色的人审批,每个节点可以设置抄送人,可以设置根据申请人提交的表单数据来确定是否经过这个节点(多个字段都满足或者满足其中一个就要经过这个审批节点)。流程创建成功后自动生成该流程对应的数据表。
流程字段表单表:cmf_flowform
流程表:cmf_workflow
流程类型表:cmf_workflowtype
审批项表:cmf_program
审批记录表:cmf_flowlog
其它的一些常用表:
代码目录结构:
外部只调用Workflow.php的方法来实现新建流程、新建审批工作项,审批,编辑流程,编辑审批项等。
整个审批流程怎么走是用一个php数组来判断的,转换成json后保存在流程表的FlowNodes字段下,数组包含键值如下:
$flow = [
[
'type' => // 1:会签 2:或签
'role' => // 审批人角色id
'department => // 审批人部门id
'user'=> // 审批人id
'self' => // 1:本部门对应角色签批
'copy' => // 抄送人id
'need' => [
[
'field' => // 字段
'type' => // 1:值大于 2:值相等 3:值小于
'value' => // 需要时字段条件值
]
[
'field' => // 字段
'type' => // 1:值大于 2:值相等 3:值小于
'value' => // 需要时字段条件值
]
]
'needtype' => // 1:且 2:或
]
];
每个流程还有自己编号规则,编号规则也是用一个数组表示,转json后保存在流程表OrderRule字段下。下面是示例:
$orderRule = [
[
'type' => // 1:标签 2:日期 3:增长值
'value' => // type为1时有值,字符串,例如:HQ
'datetype' => // type为2时有值 1:年 2:年月 3:年月日
'length' => // 增长值最高长度 type为3时有值
]
];
// 下面这个编号规则第一个编号就是HT201803001
$orderRule = [
[
'type' => 1,
'value' => 'HT'
],
[
'type' => 2,
'datetype' => 2
],
[
'type' => 3,
'length'=> 3,
]
];
新建审批项调用Workflow.php的方法createFlow($data)。
$data = [
'Name' => '事假',
'Introduce' => '用户事假申请',
'FlowNodes' => [
[
'type' => 2,
'role' => 2,
'self' => 1,
'need' => 1,
],
[
'type' => 2,
'role' => 4,
'copy' => 4,
'need' => [
[
'field' => 'TimeBetween1Total',
'type' => 1,
'value' => 3,
]
],
'needtype' => 1,
]
],
'DepartmentID' => NULL,
'RoleID' => 1,
'TypeID' => 1,
'UserID' => NULL,
'OrderRule' => [
[
'type' => 1,
'value' => 'SJ'
],
[
'type' => 2,
'datetype' => 2
],
[
'type' => 3,
'length'=> 3,
]
],
'Form' => [
[
'fieldtypeid' => 1,
'FieldName' => 'Content',
'FieldTitle' => '原因',
'Placeholder' => '请输入请假原因',
'Must' => 1,
],
[
'fieldtypeid' => 8,
'FieldName' => 'TimeBetween',
'FieldTitle' => '起止时间',
'Timetype' => 3,
'Must' => 1,
]
]
];
$workflow = new \process\Workflow();
$res = $workflow->createFlow($data);
process\flow\Flow.php负责流程表单的相关操作,process\flow\Flow::$fields定义了常用的一些表单字段,前端编辑创建流程表单的时候需按照这里设定的来。
public static $fields = [
0 => ['fieldname' => 'Title', 'name' => '单行文本', 'type' => 'varchar', 'length' => 255, 'formtype' => 'input', 'inputtype' => 'text', 'config' => ['placeholder']],
1 => ['fieldname' => 'Content', 'name' => '多行文本', 'type' => 'text', 'formtype' => 'textarea', 'config' => ['placeholder']],
2 => ['fieldname' => 'Phone', 'name' => '手机号', 'type' => 'char', 'length' => 11, 'formtype' => 'input', 'inputtype' => 'text', 'pattern' => '/^1(3|4|5|7|8|9)\d{9}$/', 'config' => ['placeholder']],
3 => ['fieldname' => 'Num', 'name' => '整数', 'type' => 'int', 'length' => 11, 'formtype' => 'input', 'inputtype' => 'number', 'config' => ['placeholder']],
4 => ['fieldname' => 'ChooseOne', 'name' => '单选框', 'type' => 'varchar', 'length' => 255, 'formtype' => 'input', 'inputtype' => 'radio', 'config' => ['textarea', 'direction']],
5 => ['fieldname' => 'Checkbox', 'name' => '复选框', 'type' => 'varchar', 'length' => 255, 'formtype' => 'input', 'inputtype' => 'checkbox', 'config' => ['textarea', 'direction']],
6 => ['fieldname' => 'Chooseval', 'name' => '下拉框', 'type' => 'varchar', 'length' => 255, 'formtype' => 'select', 'config' => ['textarea']],
7 => ['fieldname' => 'Time', 'name' => '时间', 'type' => 'int', 'formtype' => 'input', 'inputtype' => 'text', 'config' => ['timetype']],
8 => ['fieldname' => 'TimeBetween', 'name' => '时间区间', 'type' => 'int', 'formtype' => 'input', 'inputtype' => 'text', 'config' => ['timetype']],
9 => ['fieldname' => 'FileName', 'name' => '上传附件', 'type' => 'varchar', 'length' => 255, 'formtype' => 'input', 'inputtype' => 'file', 'config' => ['placeholder']],
10 => ['fieldname' => 'Decimal', 'name' => '小数', 'type' => 'double', 'formtype' => 'input', 'inputtype' => 'text', 'config' => ['placeholder']],
];
Flow.php的createField方法负责创建字段,在创建字段的时候为了防止字段重复,在字段名称后面加了对应的数字键值。但是这种方法在编辑修改流程表单的时候如果要新加字段还是可能会导致字段重复,所以这个方法也不是最好的。
/**
* 生成字段信息数组
*
* @param array $formData 字段信息数据
* @param integer $flowID 流程id
* @return array 生成的数据数组和结果
*/
private static function createField($formData, $flowID)
{
foreach ($formData as $key => $val) {
if (empty($val)) {
$result['errormsg'] = '流程表单未设置';
return $result;
}
/* 表单类型设置数据验证 */
if (!in_array($val['fieldtypeid'], self::$fieldstype)) {
return ['code' => 0, 'errormsg' => '流程表单类型id错误'];
}
if (empty($val['FieldTitle'])) {
return ['code' => 0, 'errormsg' => '表单字段标题不能为空'];
}
if (in_array($val['fieldtypeid'], [4,5,6])) {
if (count(explode("\n", $val['Value'])) < 2) {
return ['code' => 0, 'errormsg' => '选项过少'];
}
}
$data = [];
$data['TypeID'] = intval($val['fieldtypeid']);
$data['Type'] = self::$fields[$data['TypeID']]['type'];
$data['FieldName'] = $val['FieldName'] . $key;
if (!empty(self::$fields[$data['TypeID']]['length']))
$data['FieldLength'] = self::$fields[$data['TypeID']]['length'];
$data['FieldTitle'] = htmlspecialchars(trim($val['FieldTitle']));
$data['FieldNote'] = $data['FieldTitle'];
$data['Must'] = intval($val['Must']);
if (!empty($val['Placeholder']))
$data['Placeholder'] = htmlspecialchars(trim($val['Placeholder']));
if (in_array($data['TypeID'], [4,5]))
$data['Direction'] = intval($val['Direction']);
if (in_array($val['fieldtypeid'], [4,5,6]))
$data['Value'] = htmlspecialchars(trim($val['Value']));
if (in_array($data['Type'], [7,8])) {
$data['Timetype'] = intval($val['Timetype']);
if (!in_array($data['Timetype'], [1,2,3,4,5])) {
return ['code' => 0, 'errormsg' => '时间类型不对'];
}
}
$data['WorkflowID'] = $flowID;
if ($val['fieldtypeid'] == 8) {
$data['FieldName'] = $data['FieldName'] . 'Start';
$form[] = $data;
$data['FieldName'] = $data['FieldName'] . 'End';
$form[] = $data;
$data['FieldName'] = $data['FieldName'] . 'Total';
$data['FieldTitle'] = $data['FieldNote'] = '总时长(天)';
$data['Type'] = 'double';
$form[] = $data;
continue;
}
$form[] = $data;
}
return ['code' => 1, 'data' => $form];
}
/**
* 添加流程表单数据和创建表单表
*
* @param array $formData 表单字段数据
* @param integer $flowID 流程id
* @param string $flowName 流程名称
* @return array 结果
*/
public static function createDbTable($formData, $flowID, $flowName)
{
$result = ['code' => 0];
if (empty($formData)) {
$result['errormsg'] = '流程表单未设置';
return $result;
}
$fieldRes = self::createField($formData, $flowID);
if ($fieldRes['code'] != 1) {
return $fieldRes;
} elseif (empty($fieldRes['data'])) {
$result['errormsg'] = '流程表单生成失败';
return $result;
}
$form = $fieldRes['data'];
$medoo = \process\Workflow::connectdb();
if ($medoo->insert('flowform', $form)->errorCode() !== '00000') {
$medoo->pdo->rollBack();
$result['errormsg'] = '创建流程表单失败';
return $result;
}
/* 创建流程表单表 */
$config = include(__DIR__ . '/../config.php');
$sql = "CREATE TABLE `{$config['dbPrefix']}formtable$flowID` ( "
. "`ID` INT NOT NULL AUTO_INCREMENT , ";
foreach ($form as $val) {
$null = $val['Must'] == 1 ? 'NOT NULL' : 'NULL';
if ($val['Type'] == 'int' || $val['Type'] == 'text') {
$type = $val['Type'];
} else if ($val['Type'] == 'varchar' || $val['Type'] == 'char') {
$type = "{$val['Type']}({$val['FieldLength']})";
}
$sql .= "`{$val['FieldName']}` $type $null COMMENT '{$val['FieldNote']}' ,";
}
$sql .= "PRIMARY KEY (`ID`)) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci COMMENT = '{$flowName}流程表单$flowID';";
$pdoStatement = $medoo->query($sql);
if ($pdoStatement->errorCode() !== '00000') {
$medoo->pdo->rollBack();
$result['errormsg'] = '创建流程表单表失败';
return $result;
}
$result['code'] = 1;
$result['errormsg'] = '';
return $result;
}
上面在执行创建数据表的时候不太对,执行修改表结构,创建表这种非增删改查的操作会自动提交事务,代码里创建表那里的事务其实是起不到作用的。
外部调用的Workflow.php
namespace process;
/**
* 工作流引擎接口类
*
* @author lxj
*/
class Workflow
{
/**
* @var array 配置
*/
public $config;
/**
* 初始化
*
* @access public
*/
public function __construct()
{
$this->config = include(__DIR__ . '/config.php');
}
/**
* 流程进行
*
* @param integer $programID 申请项目id
* @param integer $userID 审批人id
* @param array $data 审批数据
* @return type 处理结果
*/
public function doFlow($programID, $userID, $data)
{
return flow\FlowControl::doflow($programID, $userID, $data);
}
/**
* 新的申请
*
* @param integer $flowID 流程id
* @param integer $userID 申请用户id
* @param array $data 申请表单数据
* @return array 处理结果
*/
public function startNew($flowID, $userID, $data)
{
return flow\FlowControl::startNew($flowID, $userID, $data);
}
/**
* 编辑申请
*
* @param integer $programID 申请id
* @param integer $userID 申请人id
* @param array $data 申请表单数据
* @return array 编辑结果
*/
public function editProgram($programID, $userID, $data)
{
return flow\FlowControl::edit($programID, $userID, $data);
}
/**
* 新建流程
*
* @param array $data 流程数据
* @access public
*/
public function createFlow($data)
{
return flow\FlowControl::createFlow($data);
}
/**
* 撤回申请(仅当没有审核记录时能撤回)
*
* @param integer $programID 申请项id
* @param integer $userID 申请人id
* @return array 处理结果
*/
public function revoke($programID, $userID)
{
return flow\FlowControl::revoke($programID, $userID);
}
public function editFlow($flowID, $data)
{
return flow\FlowControl::editFlow($flowID, $data);
}
/**
* 获取medoo数据库操作类实例
*
* @staticvar object $medoo
* @return object|\process\tool\Medoo medoo实例
*/
public static function connectdb()
{
static $db = '';
if (!empty($db)) {
return $db;
} else {
$config = include(__DIR__ . '/config.php');
$db = new tool\Medoo([
'database_type' => $config['dbType'],
'database_name' => $config['dbName'],
'server' => $config['dbHost'],
'username' => $config['dbUser'],
'password' => $config['dbPassword'],
'charset' => $config['dbCharset'],
'port' => $config['dbPort'],
'prefix' => $config['dbPrefix'],
]);
return $db;
}
}
/**
* 获取流程表单所有可以设置的项
*
* @access public
*/
public function getAllFrom()
{
return flow\Form::$fields;
}
/**
* 创建数据
*
* @param integer $flowID 流程id
* @return mixed 生成数据
* @access public
*/
public static function create($flowID)
{
$data = filter_input_array(INPUT_POST);
if (empty($flowID)) {
return FALSE;
}
$tableName = "formtable$flowID";
$arr = [];
$columns = self::connectdb()->query("SHOW COLUMNS FROM `{$this->config['dbPrefix']}$tableName`")->fetchAll(\PDO::FETCH_ASSOC);
foreach ($columns as $val) {
$keys[] = $val['Field'];
}
foreach ($data as $key => $val) {
if (in_array($key, $keys)) {
$arr[$key] = htmlspecialchars(trim($val)); // 全局转换html元素
}
}
if ($res) {
return $res;
} else {
return FALSE;
}
}
}
使用流程引擎全部从上面那个类进入,数据库用的medoo。以上就是全部了,仅仅是简单做了下简单的设计和基本的几行代码,要使用还要做一些优化调整才能用。有兴趣的可看下源码地址https://github.com/uej/process