Welcome to loonflow’s documentation!¶
r0.x.x之前版本见 https://github.com/blackholll/loonflow/wiki
loonflow是什么¶
a workflow engine base django 基于django的工作流引擎系统,通过http接口调用。 可以作为企业内部统一的工作流引擎, 提供诸如权限申请、资源申请、发布申请、请假、报销、it服务等所有工作流场景的服务。如果有一定的开发能力建议只使用后端引擎功能, 前端根据场景定制开发可分散于各个内部后台管理系统(如人事、运维、监控、cmdb等等)
前言¶
本人2011年开始接触工作流,2013年开始开发工作流第一版本,至今经历了多个版本。目前着手开发一个开源版本,致力于提供企业统一工作流引擎方案 欢迎加入qq群一起交流工作流相关技术: 558788490
操作系统支持¶
建议使用Centos,Redhat,Ubuntu这类linux操作系统 因为celery4以后不支持windows,所以状态脚本和通知脚本执行会无法使用。 可以参考此文档兼容下: 参考文档
如何获取代码¶
推荐使用最新的版本.可以直接通过此链接下载release版本, 或者使用以下命令
# loonflow
git clone git@github.com:blackholll/loonflow.git
git checkout vx.x.x #(具体的版本号,如v0.3.15)拉取代码
# howflow-open: vue.js版本前端+django后端的调用方,基于钉钉生态,移动端审批
https://gitee.com/shihow/howflow-open
# shutongflow: vue.js版本前端+django后端的调用方demo
https://github.com/youshutong2080/shutongFlow
# workflowdemo: bootstrap版本前后端(前后端为分离的调用方demo)
https://github.com/jimmy201602/workflowdemo
# 其他版本demo: 欢迎有兴趣的同学提供react版本调用方demo
# loonflow-helper: 一些脚本的demo,如通知脚本、执行脚本、用户同步脚本,欢迎大家一起完善(直接提pr)
https://github.com/blackholll/loonflow-helper
如何运行¶
开发环境¶
将settings/dev.py.sample在settings目录下复制一份并重命名为config.py
创建数据库并修改settings/config.py中相应配置(数据库配置、redis地址配置、日志路径配置等等)
创建python虚拟环境: python3.10.x(python3.10最新稳定版)
安装依赖包: pip install -r requirements/dev.txt
启动redis(用于生成唯一的工单流水号+celery异步任务[执行脚本、状态hook、通知hook])
初始化数据库
python manage.py makemigrations
python manage.py migrate
# 如果只是本地测试,无需二次开发,也可以直接参考生产环境部署中直接导入初始sql(初始sql中包含admin用户)
创建初始账户: python manage.py createsuperuser
启动开发环境: python manage.py runserver 6060
安装node v14
安装前端依赖 进入到frontend目录下执行npm i
启动前端开发环境 进入到frontend目录下执行npm run dev
启动celery任务: celery -A tasks worker -l info -Q loonflow (用于执行任务脚本、触发任务hook、通知hook。本地开发二次开发如果不需要这些功能时可以不启动)
访问http://127.0.0.1:8000即可
生产环境源码部署¶
生产环境建议使用nginx+uwsgi的方式部署
将settings/pro.py.sample在settings目录下复制一份并重命名为config.py
创建数据库并修改settings/pro.py中相应配置(数据库配置、redis地址配置、日志路径配置等等)
创建python虚拟环境: python3.10.x(python3.10最新稳定版)
安装依赖包: pip install -r requirements/pro.txt
编译前端
进入frontend目录下,依次执行npm i 和npm run build (主要需要事先安装好node,建议安装node v14版本)
启动redis(用于生成唯一的工单流水号+celery异步任务[执行脚本、状态hook、通知hook],centos7下redis service配置文件可参考https://github.com/blackholll/loonflow-helper/tree/master/deploy)
初始化数据库,导入初始化sql, 命令如下
mysql -uroot -p loonflow_2_0 < loonflow2.0.0.sql # 生产环境不建议使用migrate. 用户名及数据库需要根据你的实际情况也即config.py中的配置做相应修改
初始admin账号密码为admin/123456
启动celery任务: celery multi start -A tasks worker -l info -c 8 -Q loonflow –logfile=xxx.log –pidfile=xxx.pid # -c参数为启动的celery进程数,注意logfile和pidfile前面是两个-, logfile为日志文件路径, pidfile为pid文件路径,可自行视情况调整
如需优雅停止celery服务:
celery multi stopwait -A tasks worker -l info -c 8 -Q loonflow --logfile=xxx.log --pidfile=xxx.pid
如需优雅重启celery服务:
celery multi restart -A tasks worker -l info -c 8 -Q loonflow --logfile=xxx.log --pidfile=xxx.pid
启动uwsgi uwsgi配置文件参考docker_compose_deploy/loonflow-web目录下uwsgi.ini
启动nginx nginx配置文件参考docker_compose_deploy/loonflow-web目录下nginx.conf
生产环境docker compose方式部署¶
准备工作:
准备一台linux服务器
安装好python3(请自行百度或者google)
安装好docker-compose(请自行百度或者google)
配置容器镜像加速(请自行百度或者google)
启动/停止服务
# 启动服务
cd docker_compose_deploy
docker compose -f docker-compose.yml up -d
# 停止服务
docker compose -f docker-compose.yml stop
启动/停止服务(使用已有mysql)
# 进入mysql后创建数据库并授权
mysql> create database loonflow character set utf8mb4; # 注意要使用utf8mb4字符集
mysql> grant all privileges on loonflow.* to loonflow@'%' identified by '123456';
# 移除及更新docker-compose.yml中mysql相关配置
移除loonflow-mysql服务
移除loonflow-task和loonflow-web服务中depends_on的oonflow-mysql
修改loonflow-task和loonflow-web服务中的mysql地址、用户、密码
# 启动服务
cd docker_compose_deploy
docker compose -f docker-compose.yml up -d
# 停止服务
docker compose -f docker-compose.yml stop
访问服务
http://{service’s ip}
常见问题¶
部署完后访问用户及密码是多少
docker compose方式会自动导入初始数据,用户及密码为admin/123456,其他已经用户的密码应该也是123456。
docker-compose方式如何修改mysql的密码
容器mysql服务的root密码:docker_compose_deploy/docker-compose.yml中MYSQL_ROOT_PASSWORD(需要第一次启动服务之前修改)
容器mysql服务loonflow的密码: docker_compose_deploy/loonflow-mysql/init/create_database.sql(需要第一次启动服务之前修改)
docker-compose方式如何修改redis的密码
docker_compose_deploy/docker-compose.yml中loonflow-redis中requirepass及其他服务环境变量中的密码
docker-compose方式支持ARM架构下启动么
别折腾了,找个linux服务器吧, 我搞了一整天没成功build所有的arm image。
版本升级¶
r0.1.x-r0.2.x¶
从r0.1.x-r.2.x升级。需要一些DDL操作
workflow.models.Transition新增字段timer,新增字段attribute_type_id,condition_expression
ticket.modles.TicketRecord新增script_run_last_result字段,新增is_end字段,新增is_rejected字段,新增multi_all_person字段
删除ticket.modles.TicketStateLastMan
workfow.models.State新增remember_last_man_enable字段
account.models.AppToken新增字段workflow_ids字段,用于给每个app授权可以访问的工作流资源(对应工作及对应的工单,升级后需要修改此配置).新增ticket_sn_prefix字段,用于支持配置工单流水号前缀
workflow.models.Workflow新增字段limit_expression,用于新建工单权限的限制
workflow.models.workflow新增字段notices,用于关联通知方式
workflow.models新增表CustomNotice 用于支持自定义通知方式
workflow.models.CustomField新增label字段用于调用方自行扩展
r0.2.x-r0.3.x¶
因为v0.3版本中username参数改成从header中获取,所以接口调用时需要将username通过header方式传递
为了脚本安全考虑,当状态的参与人类型为脚本时,参与人需要设置为脚本记录的id。 迁移时需要将这些状态的参与人从脚本名称改成脚本记录的id
r0.3.x-r1.0.x¶
如果你对升级过程不熟悉,强烈建议你将生产环境数据(数据库)导入到测试环境,按照下面的操作演练过且没问题后再在生产环境操作
准备好通知的hook服务端(如果你当前有用到通知脚本,你需要改成hook方式,如果没用到通知功能,可忽略此步)
准备好通知的hook的服务端(可以在服务端提供短信、钉钉、企业微信、邮件等通知服务),服务端给loonflow分配一个token用于生签名,服务器端以此token使用相同的算法生成签名用于校验loonflow。校验通过后根据hook请求的数据来发送通知消息。
下载loonflow v1.0.x版本到新的服务器或者新的目录
将0.3.x版本的media目录下目录及文件全部复制到v1.0.x版本的media目录下
创建新的python3.6虚拟环境,并安装好requirement/pro.txt中的依赖
停止调用方服务
停止loonflow 0.3版本服务(包括web服务及task任务,task任务可优雅停止,命令:xxxx)
celery multi stopwait -A tasks worker -l info -c 8 -Q loonflow --logfile=xxx.log --pidfile=xxx.pid
备份好0.3.x版本数据库(为了在发现问题时快速回退)
执行升级sql
https://github.com/blackholll/loonflow-helper/tree/master/update/0.3.xto1.0.x/ddl.sql
https://github.com/blackholll/loonflow-helper/tree/master/update/0.3.xto1.0.x/dml.py ## 将文件中的数据库配置修改为你的0.3.x版本使用的数据库
将代码中settings/pro.py中复制并重命名为settings/config.py,将config.py中数据库就redis配置修改为当前使用的地址,临时修改config.py中的DEBUG参数=True,进入新的虚拟环境尝试使用python manage.py runserver 0.0.0.0:9999 启动loonflow 1.0.x,观察是否有报错,排查错误
访问http://$serverip:9999, 在“工作流管理”-“通知管理”中新增好需要用到的通知
访问http://$serverip:9999,在“工作流管理”–“工作流配置”中逐个编辑需要发送通知的工作流,选择对应的通知,并设置标题模板和通知模板
修改config.py中的DEBUG参数=False,使用uwsgi+nginx启动loonflow r1.0.x,
启动task服务
启动调用方服务
r1.0.x-r2.0.x¶
待提供
相关术语¶
工单:具体的待处理事项,用户新建的是工单,工单按照工作流的设计来实现不同状态不同处理人之间的流转
工作流:即工作流的设计,定义了工单的审批链、各状态的处理人、各状态可以执行的操作(提交、保存,处理完成,退回,关闭等等)、 每个状态下显示哪些字段、哪些字段可以在哪些编辑
子工单:主要用于工单流转存在子集的情况,如在项目开发周期中存在项目周期和应用周期两个层级, 当项目处于开发中时, 项目的多个涉及应用在项目开发中可能正处于不同的阶段(代码编写、静态扫描、单元测试、完成开发等状态)。 当应用状态都完成开发时将触发项目的状态到提测中。在这个场景中应用的工单即为项目工单的子工单。 应用工单的父状态即为项目的“开发中”
子工作流:工作流的父子层级不体现在工作流记录中,而体现在状态记录中。在配置工作流时,可以给某个工作流的某个状态设置一个子工作流。 可以在工作流的不同状态设置不同的子工作流。
流程图:为了方便用户了解工作流的流转规则,可以通过流程图的方式展示给用户,如下

转交:正常情况下工单的流转都是按照其对应工作流设定的规则来流转(状态、处理人类型、处理人等).在实际操作中,比如A提交了个工单, 到达运维处理中状态,B接单处理,B在处理过程中发现自己其实处理不了,需要C才能处理。于是将工单转交给C。
加签:加签与转交不同。正常情况下工单的流转都是按照其对应工作流设定的规则来流转(状态、处理人类型、处理人等).在实际操作中, 比如A提交了个工单,到达运维处理中状态,B接单处理,B在处理过程中发现需要C做些操作或者提供些信息,才能处理,于是将工单加签给C. C处理完成后工单处理人会回到B.于是B可以继续处理
工单自定义字段与工作流自定义字段的区别: workflow里面自定义字段规定工作流有哪些自定义的字段。比如配置一个请假的工作流。 需要有请假天数这个字段。工单里面的自定义字段 存的是自定义字段具体的值。 比如现在用于新建了一个请假工单,填写了请假天数。 那么工单的自定义字段表中会保存这个值
工作流处理过程可以理解为工单状态的变化,如一个工作流处理过程中可以有:发起人新建中、发起人编辑中、部门经理审核中、技术人员处理中、 发起人验证中、结束等状态,每个状态对应相应的处理人(如部门经理审核中这个状态下只有部门经理才可以处理该工单)。 如用户在新建工单的时候处于“发起人新建中”,(用户)提交后工单处于“部门经理审核中”, 部门经理(即“部门经理审核中”状态的处理人)审批通过后, 工单的状态变更为“技术人员处理中”。 注意:”转交”和”加签”使用场景不同,使用时前端需要做必要的说明,避免用户使用错误
代码结构¶
.
├── apps
│ ├── account # 用户应用
│ ├── manage # 管理后台应用
│ ├── ticket # 工单应用
│ └── workflow # 工作流应用
├── docker_compose_deploy # docker compose方式部署相关文件
│ ├── loonflow-task # 异步任务服务相关配置,dcokerfile等
│ ├── loonflow-web # web服务,dcokerfile,nginx配置,uwsgi配置等
│ ├── docker-compose.yaml # docker compose配置
│ ├── README.md # 一些说明
│ └── run.py # docker compose方式部署主程序
├── frontend # ant design实现的前端部分(包括管理后台及新建、处理工单等页面)
│ ├── config # 前端相关配置
│ │ ├── config.ts # 前端路由菜单配置
│ │ └── proxy.ts # 代理配置,本地开发时,将后端请求代理到后端服务的地址
│ ├── src # 前端主要代码
│ │ ├── pages # 相关前端页面
│ │ └── services # 服务层,请求后端的接口的逻辑
├── loonflow
│ └── __init__.py
| └── url.py # url路由主入口
| └── wsgi.py # wsgi配置
├── media # 静态文件目录
│ ├── flowchart # 工作流流程图,用户上次的流程图,后续将弃用
│ ├── notice_script # 通知脚本目录
│ └── workflow_script # 工作流执行脚本目录
├── requirements # 依赖文件目录
│ ├── common.txt # 通用依赖
│ ├── dev.txt # 开发环境依赖
│ ├── pro.txt # 生产环境依赖
│ ├── test.txt # 测试环境依赖
├── service # 服务层
│ ├── account # 用户相关服务
│ ├── common # 通用服务
│ ├── manage # 管理后台相关服务
│ ├── permission # 权限相关服务
│ ├── ticket # 工单相关服务
│ └── workflow # 工作流相关服务
├── settings # 配置文件目录
│ └── __init__.py
│ └── common.py #通用配置
│ └── dev.py # 开发环境配置
│ └── prod.py # 生产环境配置
│ └── test.py # 测试环境配置
├── static # 静态文件,管理后台页面使用
│ ├── bower_components
│ ├── dist
│ └── plugins
├── templates # 模板文件,管理后台页面使用,因为管理后台未前后端分离,所以有模板文件
│ ├── admin
│ ├── doc
│ ├── user_and_permission
│ └── workflow
└── tests # 单元测试目录
├── test_models # model层测试
├── test_services # service层测试
└── test_views # view层测试
登录管理后台¶
使用部署过程中创建的(python manage.py creatsuperuser)用户名密码 登录http://host_ip:port
同步用户信息¶
同步账户中用户、角色、用户角色(用户具有的角色)、部门信息。 loonflow中工单的流转过程中需要根据用户的相关信息来确定新的处理人, 因为不同公司用户组织架构信息保存方式各不一样(如ldap、AD或者直接保存在企业微信、钉钉等等),需要你自己编写用户组织信息的脚本, 定时将你司的最新用户组织信息同步到loonflow中。可参考从AD中同步。 非常欢迎大家将自己的脚本pr到https://github.com/blackholll/loonflow-helper。
注意:
loonflow中的用户只是用于流转的时候确定新的处理人,无需要用户登录loonflow的管理后台,所以同步脚本中往loonflow插入用户记录时,密码随便插入。当然超级管理员除外(超级管理员可以通过python manage.py createsuperuser命令来创建)
用户表中 dept_id为loonflow的部门表中主键id, 非你司用户信息中的部门id。 同步部门时可以将你司部门id保存在loonflow部门表中的label字段中来关联.label字段建议使用字段的json格式,方便以后扩展。如{“source_dept_id”:11}
工作流配置¶
自定义通知¶
自定义通知的管理权限仅限loonflow管理员,即用户表中is_admin为true的用户。通知hook添加后,工作流管理员在配置工作流时可以选择这些通知。 其中hook_url为提供通知服务的地址,token作为计算签名的密钥,当工作流选择了通知,那么通过此工作流创建的工单状态发送变化时都会触发hook。 loonflow将会对这个hook_url发送一个post请求,请求头中包括签名,计算方式方式同loonflow校验api请求的签名算法。请求内容中包括工单最新 的处理人信息,被请求方可以根据这个处理人信息发送通知消息。hook服务方建议校验请求头中的签名后再响应请求。
签名算法
def gen_signature_by_token(cls, token: str)->tuple:
md5_key = token
timestamp = str(int(time.time()))
ori_str = timestamp + md5_key
tar_str = hashlib.md5(ori_str.encode(encoding='utf-8')).hexdigest()
return True, dict(signature=tar_str, timestamp=timestamp)
loonflow发送hook请求时,header头中将包含signature 和timestamp。
请求内容中将包含以下参数
{
'title_result': title_result, # 通知标题,在工作流配置中会配置标题模板,此处将根据工单的信息生成实际的标题
'content_result': content_result, # 通知内容,在工作流配置中会配置内容模板,此处将根据工单的信息生成实际的内容
'participant_type_id': ticket_obj.participant_type_id, # 当前参与人类型, 类型定义见文档中"常量说明", hook服务方需根据类型决定是否发送消息
'ticket_value_info': ticket_value_info, # 该字段中将包含工单所有字段的值,一般情况下发送消息时候根据标题和内容发送即可,
'last_flow_log': last_flow_log, # 工单的最近一条处理记录,发送通知消息时可以根据自己需要决定消息中是否包含这个信息
'participant_info_list': participant_info_list # 工单的当前处理人信息列表, 是一个数组,每个记录中都包含处理人的username, alias, email, phone
}
新建自定义通知
工作流¶
创建一个工作流包括四个部分,创建工作流基础信息、添加工作流的自定义字段、添加状态、添加流转。 在创建和编辑工作流时可以指定对应的管理员, 只有工作流对应的管理员、工作流创建人、loonflow超级管理员才有工作流的编辑权限。拥有工作流管理员权限的用户可以新增和维护自己相关 (自己创建的、作为管理员的)的工作流
选择通知:loonflow超级管理员在通知管理中添加通知后,工作流管理员即可在新建或编辑工作流时候选择这些通知。
工单查看权限校验:开启后,以工单的关联人(创建人、曾经的处理人)的名义调用工单详情接口才可以返回详情信息
限制表达式:默认”{}”,限制周期({“period”:24} 24小时), 限制次数({“count”:1}在限制周期内只允许提交1次), 限制级别({“level”:1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({“allow_persons”:”zhangsan,lisi”}只允许张三提交工单, {“allow_depts”:”1,2”}只允许部门id为1和2的用户提交工单,{“allow_roles”:”1,2”}只允许角色id为1和2的用户提交工单)。符合限制 表达式规则时调用创建工单接口会返回code=-1,同时msg中将有相应的说明
标题模板:用于发送通知时,确定通知的标题
内容模板:用于发送通知,确定通知的内容
管理员: 工作流的创建人和这里的管理员有权限修改配置此工作流, 另外loonflow的超级管理员拥有对所有工作流的配置查看权限
每个类型的工作流有各自不同的字段, 通过自定义字段可以为工作流新增独有的字段
字段标识:自定义字段需要有个标识,需要为英文字母+下划线,如reason、problem_description
字段名称:自定义字段的名称,如请假理由、问题描述等
字段类型:支持字符串、整形、浮点型、布尔、日期、日期时间、单选框、多选框、下拉列表、多选下拉列表、文本域、用户名、多选用户名、附件。 其中单选、多选、下拉、多选下拉默认支持给定可选范围的情况,如果你需要可选项通过接口获取,请使用字符串类型,然后在”标签“中填写前后端约定好的信息。 由前端针对这个字段特殊处理。附件类型需要调用方自己处理上传逻辑后将附件路径随其他字段一起提交,loonflow只保存附件路径。关于用户名类型字段, 调用方前端可以显示一个搜索用户的可选框,然后提交被选择的username或者逗号隔开的多个被选择username(多选用户名类型)。
顺序ID:用于指定工单详情界面中字段的布局顺序,调用方前端需要根据这个id来布局字段
默认值:调用方前端当用户未填写内容时,可以根据这个信息给定默认值显示
布尔显示定义:当字段类型是布尔类型时,可以通过定义来显示不同的文字,如{“1”:”是”,”0”:”否”}或{“1”:”需要”,”0”:”不需要”},注意数字也需要引号
选项:用于指定单选、多选、下拉框、多选下拉框字段的可选项目,格式为json如:{“1”:”中国”, “2”:”美国”},{“ch”:”中国”, “us”:”美国”}注意数字也需要引号
标签:用于特殊字段,通过与调用方约定标签内容,可以实现任意的字段表现形式,格式为json,如{“table”:”http://xxx.com/table”} 可以表示从http://xxx.com/table 获取信息,然后显示为一个表格
模板: 用于文本域类型字段前端根据此模板显示默认值

状态¶
工单会存在各种状态,工单的处理过程也就是工单的状态的变化,loonflow对于工作流的状态支持各种灵活的配置
名称: 状态的名称,如创建中、编辑中、tl审批中、结束等等
是否隐藏: 开启后,获取工单步骤接口将不返回此状态(工单当前处于隐藏状态除外)。因为step图是线性的, 而loonflow工作流状态是支持环状关联的,所以在step图需要将一些旁支状态隐藏掉
顺序id: 用于指定“获取工单步骤”接口中返回的状态id,因为step图是线性的, 而loonflow工作流状态是支持环状关联的,所以在step图显示时需要给定每个状态的显示顺序
状态类型: 状态分为三个状态,初始状态、普通状态、结束状态,一个工作必须包含一个初始状态、一个结束状态、0-N个普通状态, 初始状态用于创建工单时根据这个状态及状态关联的流转来确定可以做的提交操作(提交、新建、保存等等)。结束状态表示工单到此状态后 将无法被继续处理,建议只设置一个结束状态,否则 “强制关闭工单”接口将无法正常关闭工单(无法确定结束状态是哪个)
是否记忆最后处理人: 开启后,到达此状态时会先检查之前是否有人在此状态处理过,如果有则处理人为最后一次处理的人, 使用场景如:运维处理中状态下处理人有A、B、C,其中A处理了工单,然后到达发起人确认状态时,发起人发现处理的有问题, 那么发起人可能是希望将工单退回到之前处理的A。而不是A、B、C都收到工单
参与人类型: 参与人类型包括个人、多人、部门、角色、工单字段、父工单字段、hook、外部获取、无。 注意:如果需要在此状态创建子工单,需要将参与人类型设置为个人,参与人使用loonrobot
参与人:参与人信息根据参与人类型不同而不同,如果参与人类型是个人,那么参与人需要填写用户的username。 如果参与人类型是多人, 那么参与人需要填写多个username,用逗号隔开,如zhansan,lisi。如果参与人类型是部门,则参与人填写loonflow部门记录中的部门id(需要多个部门处理时, 逗号隔开部门的id,如1,3)。如果参与人类型是角色,则参与人填写loonflow角色记录中的id.如果参与人类型是脚本(建议使用hook方式替代), 则填写“工作流配置”-“执行脚本”中的脚本id。如果参与人类型是工单字段,则参与人填写工作流基础字段或者此工作流定义的自定义字段的字段标识 如username, agent。如果参与人类型是父工单字段,则填写工单的对应字段的标识。如果参会人类型填写无,那么处理人信息留空。 如果参与人类型是hook,那么参与人填写{“hook_url”:”http://xxx.com/xxx”, “hook_token”:”xxxx”, “wait”:true, “extra_info”:”xxx”}, 其中hook_url是要触发hook的目标地址,hook_token用于签名计算,你的hook服务端需要保存此hook_token,hook签名算法如下方代码区, 触发hook请求时候会将timestamp和signature添加到请求头中,hook服务端应该在收到请求后按照相同的签名算法先校验签名的有效性然后才响应hook请求。 wait的值可以是true或者false,如果wait的值是false那么工单触发hook成功(hook服务端返回json类型数据,其中code=0)后直接进入新的状态(触发失败的话 即code=-1 或者服务端无响应或者http status非200工单会标记script_run_last_result为False,你可以调用“重试工单脚本/任务”重新触发hook), 如果wait的值是true那么工单触发hook后会停留在当前状态,直到hook方回调(回调逻辑见文档中“工单相关接口”-“工单hook回调”)loonflow成功 (请求参数中result=True)后工单的状态才继续流转。extra_info(非必填)可以用于传一些额外的信息,loonflow会将这个信息连同工单信息传给hook服务端。 如果参与人类型是’外部获取’,参与人信息需要填写{“external_url”:”http://www.xx.com”, “external_token”:”xxx”, “extra_info”:””}, 系统将根据你配置的地址发起post请求,将结果中的内容作为参与人。
import time
timestamp = str(time.time())[:10]
ori_str = timestamp + token
signature = hashlib.md5(ori_str.encode(encoding='utf-8')).hexdigest()
hook触发时loonflow向hook_url服务端post请求时带的数据如下(外部获取类型post请求数据格式也如此):
{
"id":11,
"sn":"loonflow202002020001",
"title":"xxx",
"state_id":1,
...., //等等工单的所有字段的值
"extra_info": "xxxx", // 此处如果你配置hook的时候指定了extra_info那么会有这个字段,如果没配置就没这个信息
}
对于hook类型你的hook服务端需要response status code 200, 内容为json格式,包含code,msg字段,code为0 表示hook服务端已成功收到请求,并正常处理。 如{“code”:200, “msg”:”ok”},其中msg会被记录到工单的flowlog中,你可以在你处理出错时将出错原因填充到msg字段。 对于外部获取类型,你的external_url的服务端需要response status code 200,内容为json格式,包含code,msg, data字段,如{“code”:0, “msg”:”xxx”, “data”:”zhansgan,lisi”}, 其中data字段中包含的是你需要将此工单的处理人设置为哪些username, 多个username使用逗号隔开
分配方式: 分配方式包括直接处理、主动接单、随机分配、全部处理。如果设置为直接处理,工单的当前处理人可以直接点击配置的流 转(如同意、拒绝、完成)来处理。如果设置为主动接单,则当前处理人需要先接单,然后才可以按照配置的流转来处理(表现形式为获取用 户可执行的操作接口只会返回接单这个流转,具体参考关于接单接口的纤细描述)。 如果设置为随机分配,那么系统会自动将工单处理人设置 为有处理权限的人之间的一个,只有这个人可以处理。如果设置为全部处理,则需要此状态的所有参与人都处理完且处理操作一致,才会改变 工单的状态。
表单字段: 状态的表单字段用于定义当用户有对该工单处理权限时,当前状态工单详情中应该显示哪些字段以及这些字段是否可编辑。其中 1表示只读,2表示必填,3表示可选,示例:{“gmt_created”:1,”title”:2, “sn”:1}, 内置特殊字段 participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名, workflow.workflow_name:工作流名称,
状态标签:json格式,可以使用此配置实现不同状态各种定制化需求,如在服务器申请工单的tl审批阶段显示发起人拥有的所有服务器权限列表, 那么状态标签可以设置为{“display_server”:1},标签具体内容与调用方约定好即可,

流转¶
流转用于定义每个状态之间的变化途径。
名称:定义流转的名称,表现为工单详情中用户可以点击的操作,如“提交”、“保存”、“同意”、“拒绝”、“完成”、“关闭”等等
流转类型:流转类型包括常规流转和定时器流转。如果选择定时器流转,需要设定定时器的时间。
定时器:单位是秒,如果流转配置了定时器,当工单处于这个流转的源状态停留对应定时器的时间后未做任何操作,那么定时器会触发工单自动流转到下个状态。 使用场景如:客服类工单超过SLA无人处理,自动流转到客服团队leader处理
源状态:流转的源状态,即这个流转是在某个状态下可以执行
目标状态:触发流转后工单的状态将改变为这个目标状态。当流转类型为条件流转时,这里的目标状态将无效,以条件表达式中设定的条件目标状态来流转
条件表达式:流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{“expression”:”{days} > 3 and {days} ≤10”, “target_state_id”:11},{“expression”:”{days} >10”, “target_state_id”:12}] 其中{}用于填充工单的字段key, 运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算. loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突
属性类型:因为别的审批系统中对于每个操作可能只有同意还是拒绝,所以此处加个属性用于与其他审批系统对接,另外也根据根据这个属性来判断工单是否被拒绝 (工单列表查询支持是否被拒绝这个条件)
是否校验必填项: 默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如”退回”属性的操作,不需要填写表单内容
点击弹窗提示: 可以用于当用户在处理工单时做特定操作时,弹窗提示信息。 如用户点击”拒绝”时弹窗提示要求用户确认是否真的拒绝,避免点错
弹窗内容: 当启用”点击弹窗提示“时可以设置弹窗中的内容

用户及权限相关管理只有超级管理员用户才有权限查看或者编辑
用户管理¶
loonflow管理后台提供的用户管理功能适用于开始的功能测试,或者账户信息微调。建议通过定时任务程序实现企业账户信息往loonflow的同步 (直接操作loonflow的数据库,非管理员用户无需登录loonflow,密码随便设置)。管理人员可同步后重置登录密码。管理分为两类: 超级管理员、工作流管理员
超级管理员: 拥有loonflow管理后台所有功能的权限, 包括工作流管理员的所有权限
工作流管理员:允许登录loonflow管理后台拥有工作流配置及对应有权限工作流的相关管理功能

角色管理¶
此处角色管理可以定义一系列角色来作为工作流配置中状态的处理人。如可以定义一线处理人、pc故障维修人等等角色。如果你需要这个角色与公司 的用户体系一致,建议通过定时任务脚本直接操作loonflow的数据库来实现

调用权限¶
loonflow给每个调用方应用分配一个token,用于生成调用时请求头中的签名。同时可以限定每个应用可以操作的工作流列表, 以及定义每个应用创建工单时,工单流水号的规则。
调用应用:建议用英文字母,如客服系统调用可以用kefu,oa系统调用可以用oa
工单前缀: 用于指定生成工单的流水号规则,如设定为kefu,则生成的流水号为kefu_202003080001
工作流权限: 指定使用此应用调用loonflow接口时,可以操作的工作流列表,创建工单时只能选择这些工作流,查看工单列表时也只能选择这些类型的工单

接口调用鉴权¶
Loonflow作为工作流引擎,正确是的使用姿势是各个系统的后端通过http api调用按照各自的需求来完成工单展示、工单新建、工单处理逻辑 在loonflow的管理后台中”账户-调用token”中新新增记录.填写调用方app_name新增后会生成一个签名token.调用方将签名信息写到 http header中来调用具体的api 签名算法如下:
import time
timestamp = str(time.time())[:10]
ori_str = timestamp + token
signature = hashlib.md5(ori_str.encode(encoding='utf-8')).hexdigest()
api调用:
import requests
headers = dict(signature=signature, timestamp=timestamp, appname=app_name, username=username)
# get
get_data = dict(per_page=20, category='all')
r = requests.get('http://127.0.0.1:8000/api/v1.0/tickets', headers=headers, params=get_data)
result = r.json()
# post
data = dict(target_username='lisi', suggestion='请协助提供更多信息')
r = requests.post('http://127.0.0.1:8000/api/v1.0/tickets/{ticket_id}/add_node', headers=headers, json=data)
result = r.json()
# patch
requsts.patch,传参同post
# put
requests.put, 传参同post
注意: 开发阶段如果需要在postman中测试接口,避免每次都需要重新生成签名,可以将service.permission.api_permission.ApiPermissionCheck中签名有效期改长些
工作流相关接口¶
获取工作流列表¶
url
api/v1.0/workflows
method
get
使用场景
获取到工作流列表后,用户选择对应的工作流来新建对应的工单。如果需要多级类型,可以在调用方系统保存对应关系。 如调用方的“权限申请-VPN权限申请“对应loonflow中id为1的workflow,调用方的“权限申请-服务器权限申请“对应loonflow中id为2的workflow
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
page |
int |
否 |
页码,默认1 |
per_page |
int |
否 |
每页个数,默认10 |
name |
varchar |
否 |
支持根据workflow name模糊查询 |
返回数据
{
"code": 0,
"data": {
"total": 2,
"page": 1,
"per_page": 10,
"value": [{
"name": "请假申请",
"creator": "admin",
"description": "请假申请",
"gmt_created": "2018-04-23 20:49:32"
}, {
"name": "vpn申请",
"creator": "admin",
"description": "vpn权限申请",
"gmt_created": "2018-05-06 12:32:36"
}]
},
"msg": ""
}
获取工作流初始状态¶
url
api/v1.0/workflows/{workflow_id}/init_state
method
get
请求参数
无
使用场景
用于获取创建工单时对应工作流的初始状态信息,返回内容包括创建工单时需要填写的表单内容,可以执行的提交操作
返回数据
{
"msg": "",
"code": 0,
"data": {
"order_id": 0,
"workflow_id": 1,
"name": "新建中",
"participant_type_id": 1,
"distribute_type_id": 1,
"participant": "wangfei",
"is_hidden": false,
"type_id": 1,
"gmt_created": "2018-04-23 20:53:33",
"id": 1,
"transition": [{
"transition_id": 1,
"transition_name": "提交"
}, {
"transition_id": 2,
"transition_name": "保存"
}],
"sub_workflow_id": 0,
"creator": "admin",
"label": {},
"field_list": [{
"order_id": 20,
"field_key": "title",
"field_attribute": 2,
"value": null,
"name": "标题",
"field_type_id": 5
}, {
"order_id": 35,
"field_key": "leave_proxy",
"field_attribute": 2,
"field_type_id": 60,
"field_value": null,
"field_name": "代理人",
"field_choice": {}
}, {
"order_id": 25,
"field_key": "leave_end",
"field_attribute": 2,
"field_type_id": 30,
"field_value": null,
"field_name": "结束时间",
"field_choice": {}
}, {
"order_id": 20,
"field_key": "leave_start",
"field_attribute": 2,
"field_type_id": 30,
"field_value": null,
"field_name": "开始时间",
"field_choice": {}
}, {
"order_id": 40,
"field_key": "leave_type",
"field_attribute": 2,
"field_type_id": 40,
"field_value": null,
"field_name": "请假类型",
"field_choice": {
"1": "年假",
"2": "调休",
"3": "病假",
"4": "婚假"
}
}, {
"order_id": 45,
"field_key": "leave_reason",
"field_attribute": 2,
"field_type_id": 55,
"field_value": null,
"field_name": "请假原因及相关附件",
"field_choice": {}
}, {
"order_id": 30,
"field_key": "leave_days",
"field_attribute": 2,
"field_type_id": 5,
"field_value": null,
"field_name": "请假天数(0.5的倍数)",
"field_choice": {}
}]
}
}
获取工作流状态详情¶
url
api/v1.0/workflows/states/{state_id}
method
get
请求参数
无
使用场景
略
返回数据
{
"code": 0,
"data": {
"id": 1,
"name": "\u65b0\u5efa\u4e2d",
"workflow_id": 1,
"sub_workflow_id": 0,
"distribute_type_id": 1,
"is_hidden": false,
"order_id": 0,
"type_id": 1,
"participant_type_id": 1,
"participant": "wangfei",
"state_field": {
"title": 2,
"leave_start": 2,
"leave_end": 2,
"leave_days": 2,
"leave_proxy": 2,
"leave_type": 2,
"leave_reason": 2
},
"label": {},
"creator": "admin",
"gmt_created": "2018-04-23 20:53:33"
},
"msg": ""
}
获取工作流状态列表¶
url
api/v1.0/workflows/{workflow_id}/states
method
get
使用场景
可用于用户查询工单列表时选择工作流类型后,显示该工作流类型拥有的状态,然后可以再根据工单当前状态来查询。 另外可用于管理员干预工单强制修改状态时 允许选择的目标状态
返回数据
{
"code": 0,
"data": {
"value": [{
"id": 1,
"creator": "admin",
"gmt_created": "2018-04-23 20:53:33",
"gmt_modified": "2018-05-13 11:42:11",
"is_deleted": false,
"name": "\u65b0\u5efa\u4e2d",
"workflow_id": 1,
"sub_workflow_id": 0,
"is_hidden": false,
"order_id": 0,
"type_id": 1,
"remember_last_man_enable": false,
"participant_type_id": 1,
"participant": "wangfei",
"distribute_type_id": 1,
"state_field_str": {
"title": 2,
"leave_start": 2,
"leave_end": 2,
"leave_days": 2,
"leave_proxy": 2,
"leave_type": 2,
"leave_reason": 2
},
"label": {},
"participant_info": {
"participant": "wangfei",
"participant_name": "wangfei",
"participant_type_id": 1,
"participant_type_name": "\u4e2a\u4eba",
"participant_alias": "wangfei"
}
}, {
"id": 2,
"creator": "admin",
"gmt_created": "2018-04-30 15:45:48",
"gmt_modified": "2018-05-14 06:44:10",
"is_deleted": false,
"name": "\u53d1\u8d77\u4eba-\u7f16\u8f91\u4e2d1",
"workflow_id": 1,
"sub_workflow_id": 2,
"is_hidden": true,
"order_id": 2,
"type_id": 0,
"remember_last_man_enable": false,
"participant_type_id": 5,
"participant": "creator",
"distribute_type_id": 1,
"state_field_str": {
"leave_end": 3,
"leave_days": 3,
"sn": 1,
"state.state_name": 1,
"leave_proxy": 3,
"title": 3,
"gmt_created": 1,
"creator": 1,
"leave_start": 3,
"leave_reason": 3,
"leave_type": 3
},
"label": {},
"participant_info": {
"participant": "creator",
"participant_name": "creator",
"participant_type_id": 5,
"participant_type_name": "\u53d8\u91cf",
"participant_alias": "\u5de5\u5355\u521b\u5efa\u4eba"
}
}, {
"id": 3,
"creator": "admin",
"gmt_created": "2018-04-30 15:46:42",
"gmt_modified": "2018-11-27 07:20:33",
"is_deleted": false,
"name": "TL\u5ba1\u6279\u4e2d",
"workflow_id": 1,
"sub_workflow_id": 0,
"is_hidden": false,
"order_id": 3,
"type_id": 0,
"remember_last_man_enable": true,
"participant_type_id": 5,
"participant": "creator_tl",
"distribute_type_id": 3,
"state_field_str": {
"leave_reason": 1,
"leave_start": 1,
"leave_type": 1,
"creator": 1,
"gmt_created": 1,
"title": 1,
"leave_proxy": 1,
"sn": 1,
"leave_end": 1,
"leave_days": 1
},
"label": {
"tech_er_in": "qa"
},
"participant_info": {
"participant": "creator_tl",
"participant_name": "creator_tl",
"participant_type_id": 5,
"participant_type_name": "\u53d8\u91cf",
"participant_alias": "\u5de5\u5355\u521b\u5efa\u4eba\u7684tl"
}
}, {
"id": 4,
"creator": "admin",
"gmt_created": "2018-04-30 15:47:58",
"gmt_modified": "2018-05-13 11:42:59",
"is_deleted": false,
"name": "\u4eba\u4e8b\u90e8\u95e8-\u5904\u7406\u4e2d",
"workflow_id": 1,
"sub_workflow_id": 0,
"is_hidden": false,
"order_id": 4,
"type_id": 0,
"remember_last_man_enable": false,
"participant_type_id": 1,
"participant": "admin",
"distribute_type_id": 1,
"state_field_str": {
"sn": 1,
"title": 1,
"leave_start": 1,
"leave_end": 1,
"leave_days": 1,
"leave_proxy": 1,
"leave_type": 1,
"creator": 1,
"gmt_created": 1,
"leave_reason": 1
},
"label": {},
"participant_info": {
"participant": "admin",
"participant_name": "admin",
"participant_type_id": 1,
"participant_type_name": "\u4e2a\u4eba",
"participant_alias": "\u8d85\u7ea7\u7ba1\u7406\u5458"
}
}, {
"id": 5,
"creator": "admin",
"gmt_created": "2018-04-30 15:51:41",
"gmt_modified": "2018-05-11 06:52:39",
"is_deleted": false,
"name": "\u7ed3\u675f",
"workflow_id": 1,
"sub_workflow_id": 0,
"is_hidden": false,
"order_id": 6,
"type_id": 2,
"remember_last_man_enable": false,
"participant_type_id": 0,
"participant": "",
"distribute_type_id": 1,
"state_field_str": {},
"label": {},
"participant_info": {
"participant": "",
"participant_name": "",
"participant_type_id": 0,
"participant_type_name": "",
"participant_alias": ""
}
}],
"per_page": 10,
"page": 1,
"total": 5
},
"msg": ""
}
工单相关接口¶
获取工单列表¶
url
api/v1.0/tickets
method
get
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
sn |
varchar |
否 |
流水号,支持根据sn的前几位模糊查询 |
title |
varchar |
否 |
工单标题,模糊查询 |
create_start |
varchar |
否 |
创建时间起,如果创建时间起 和创建时间止 都不提供,则只返回最近一年的数据 |
create_end |
varchar |
否 |
创建时间止,如果创建时间起 和创建时间止 都不提供,则只返回最近一年的数据 |
workflow_ids |
varchar |
否 |
工作流ids,逗号隔开多个工作流id, 如”1,2,3” |
state_ids |
varchar |
否 |
状态ids,逗号隔开多个状态id,如”1,2,3” |
ticket_ids |
varchar |
否 |
工单ids, 逗号隔开多个id,如”1,2,3” |
reverse |
varchar |
否 |
是否按照创建时间倒序,”0”或者”1”,默认倒序 |
page |
int |
否 |
页码,默认1 |
per_page |
varchar |
否 |
每页个数,默认10 |
act_state_id |
int |
否 |
工单的进行状态, 0草稿中(也就是初始状态)、1进行中 2被退回 3被撤回 4已完成 5已关闭 |
parent_ticket_id |
int |
否 |
父工单id |
parent_ticket_state_id |
int |
否 |
父工单状态id |
category |
varchar |
是 |
类型(‘all’:所有工单, ‘owner’:我创建的工单, ‘duty’:我的待处理工单, ‘relation’:我的关联工单[包括我新建的、我处理过的、曾经需要我处理过的工单。注意这里只考虑历史状态,工单将来状态的处理人不考虑], ‘worked’:我处理过的工单) |
返回数据
{
"msg": "",
"code": 0,
"data": {
"value": [{
"participant_info": {
"participant_type_id": 1,
"participant": "1",
"participant_name": "zhangsan",
"participant_type_name": "个人",
"participant_alias": "张三"
},
"gmt_created": "2018-05-15 07:16:38",
"parent_ticket_state_id": 0,
"state": {
"state_name": "发起人-确认中",
"state_id": 10
},
"creator": "lilei",
"parent_ticket_id": 0,
"title": "vpn申请",
"gmt_modified": "2018-05-22 07:26:54",
"workflow": {
"workflow_name": "vpn申请",
"workflow_id": 2
},
"sn": "loonflow_201805150001",
"id": 17
}],
"total": 1,
"page": 1,
"per_page": 10
}
}
新建工单¶
url
api/v1.0/tickets
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
workflow_id |
int |
是 |
工作流id(工单关联的工作流的id) |
transition_id |
int |
是 |
新建工单时候的流转id(通过workflows/{id}/init_state接口可以获取新建工单时允许的transition) |
parent_ticket_id |
int |
否 |
父工单的id(用于子工单的逻辑,如果新建的工单是某个工单的子工单需要填写父工单的id) |
parent_ticket_state_id |
int |
否 |
父工单的状态(子工单是和父工单的某个状态关联的),如果提供parent_ticket_id,则此参数也必须 |
suggestion |
varchar |
否 |
处理意见(与处理工单类型,用户在处理工单的时候点击了按钮操作 可以填写附加的一些意见如:麻烦尽快处理) |
其他必填字段 |
N/A |
否 |
其他必填字段或可选字段(在配置工作流过程中,会配置工作流初始状态必填和可选的字段。在新建工单时候必须提供必填字段。如请假申请工单,配置了自定义字段请假天数days,工单初始状态也设置了days为必填,那么新建此类工单时候就必选提供days) |
返回数据
{
"msg": "",
"code": 0,
"data": {
"ticket_id": 1
}
}
获取工单详情¶
url
api/v1.0/tickets/{ticket_id}
method
get
请求参数
无
返回数据
{
"code": 0,
"msg": "",
"data": {
"value": {
"workflow_id": 2,
"in_add_node": true,
"gmt_created": "2018-05-15 07:16:38",
"id": 17,
"relation": "guiji,wangwu,lilei",
"title": "vpn\u7533\u8bf72",
"sn": "loonflow_201805150001",
"parent_ticket_id": 0,
"creator": "lilei",
"script_run_last_result": true,
"gmt_modified": "2018-05-22 07:26:54",
"act_state_id": 1,
"multi_all_person": "{}",
"creator_info": {
"email": "lilei@163.com",
"alias": "\u674e\u78ca",
"dept_info": {
"creator_info": {
"creator_id": 1,
"creator_alias": "\u8d85\u7ea7\u7ba1\u7406\u5458"
},
"leader": "lilei",
"parent_dept_info": {
"parent_dept_name": "\u603b\u90e8",
"parent_dept_id": 1
},
"approver_info": [],
"parent_dept_id": 1,
"name": "\u6280\u672f\u90e8",
"is_deleted": false,
"creator": "admin",
"gmt_modified": "2018-05-09 06:45:27",
"label": "",
"id": 2,
"approver": "",
"gmt_created": "2018-04-14 23:37:06",
"leader_info": {
"leader_alias": "\u674e\u78ca",
"leader_username": "lilei"
}
},
"username": "lilei",
"phone": "13888888888",
"is_active": true
},
"participant_type_id": 3,
"state_id": 10,
"is_end": false,
"is_deleted": false,
"field_list": [{
"field_value": "loonflow_201805150001",
"label": {},
"boolean_field_display": {},
"field_type_id": 5,
"field_template": "",
"field_choice": {},
"field_key": "sn",
"field_attribute": 1,
"description": "\u5de5\u5355\u7684\u6d41\u6c34\u53f7",
"default_value": null,
"order_id": 10,
"field_name": "\u6d41\u6c34\u53f7"
}, {
"field_value": "\u53d1\u8d77\u4eba-\u786e\u8ba4\u4e2d",
"label": {},
"boolean_field_display": {},
"field_type_id": 5,
"field_template": "",
"field_choice": {},
"field_key": "state.state_name",
"field_attribute": 1,
"description": "\u5de5\u5355\u5f53\u524d\u72b6\u6001\u7684\u540d\u79f0",
"default_value": null,
"order_id": 41,
"field_name": "\u72b6\u6001\u540d"
}, {
"field_value": "\u603b\u90e8",
"label": {},
"boolean_field_display": {},
"field_type_id": 5,
"field_template": "",
"field_choice": {},
"field_key": "participant_info.participant_name",
"field_attribute": 1,
"description": "\u5de5\u5355\u7684\u5f53\u524d\u5904\u7406\u4eba",
"default_value": null,
"order_id": 50,
"field_name": "\u5f53\u524d\u5904\u7406\u4eba"
}, {
"field_value": "vpn\u7533\u8bf7",
"label": {},
"boolean_field_display": {},
"field_type_id": 5,
"field_template": "",
"field_choice": {},
"field_key": "workflow.workflow_name",
"field_attribute": 1,
"description": "\u5de5\u5355\u6240\u5c5e\u5de5\u4f5c\u6d41\u7684\u540d\u79f0",
"default_value": null,
"order_id": 60,
"field_name": "\u5de5\u4f5c\u6d41\u540d\u79f0"
}],
"parent_ticket_state_id": 0,
"add_node_man": "zhangsan",
"participant": "1",
"state_info": {
"id": 10,
"creator": "admin",
"gmt_created": "2018-04-30 15:47:58",
"gmt_modified": "2018-05-13 11:42:59",
"is_deleted": false,
"name": "\u4eba\u4e8b\u90e8\u95e8-\u5904\u7406\u4e2d",
"workflow_id": 1,
"is_hidden": false,
"order_id": 4,
"type_id": 0,
"enable_retreat": false,
"remember_last_man_enable": false,
"participant_type_id": 1,
"participant": "admin",
"distribute_type_id": 1,
"state_field_str": {
"sn": 1,
"title": 1,
"leave_start": 1,
"leave_end": 1,
"leave_days": 1,
"leave_proxy": 1,
"leave_type": 1,
"creator": 1,
"gmt_created": 1,
"leave_reason": 1
},
"label": {}
}
}
}
}
获取工单可以做的操作¶
url
api/v1.0/tickets/{ticket_id}/transitions
method
get
请求参数
无
返回数据
{
"msg": "",
"data": {
"value": [
{
"transition_name": "提交",
"field_require_check": true, # 默认为ture,如果此为否时, 不校验表单必填内容
"transition_id": 1,
"is_accept": false, # 不是接单,
"in_add_node": false, # 不处于加签状态下
"enable_alert": false, # 是否弹窗告警,可用于当用户点击此操作的时确定是否弹窗信息
"alert_text": "" # 弹窗中的消息内容
},
{
"transition_name": "保存",
"field_require_check": true, # 默认为ture,如果此为否时, 不校验表单必填内容
"transition_id": 2,
"is_accept": false, # 不是接单,
"in_add_node": false, # 不处于加签状态下
"enable_alert": false, # 是否弹窗告警,可用于当用户点击此操作的时确定是否弹窗信息
"alert_text": "" # 弹窗中的消息内容
}
]
},
"code": 0
}
如果当前处理人超过一个人(处理人类型为多人,部门、角色都有可能实际为多个人),且当前状态的分配方式为主动接单,则会要求先接单,返回数据如下。 处理时需要处理人先接单(点击接单按钮时 调用接单接口).
{
"msg": "",
"code": 0,
"data": {
"value": [
{
"transition_id": 0,
"transition_name": "接单",
"is_accept": true, # 接单,
"in_add_node": false,
"field_require_check": false
}
]
}
}
当工单当前处于加签状态下,返回格式如下。 则用户点击“完成”按钮时,需要调用完成加签操作接口
{
"msg": "",
"code": 0,
"data": {
"value": [
{
"transition_id": 0,
"transition_name": "完成",
"is_accept": false,
"in_add_node": true, # 处于加签状态
"field_require_check": false
}
]
}
}
接单¶
url
api/v1.0/tickets/{ticket_id}/accept
method
post
请求参数
无
使用场景
使用接口获取工单当前可以做的的操作后,如果data.value.is_accept==true,则需要用户先接单才能处理,即页面显示接单按钮, 用户点击后调用接单接口,将工单的当前处理人设置该用户
返回数据
{
"data": {},
"code": 0,
"msg": ""
}
转交¶
url
api/v1.0/tickets/{ticket_id}/deliver
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
target_username |
varchar |
是 |
转交对象的用户名 |
suggestion |
varchar |
否 |
转交意见 |
from_admin |
boolß |
否 |
是否管理员强制转交,此参数用于对应工作流管理员或者超级管理员强制转交工单,传了from_admin,loonflow会校验用户是否是超级管理员或者该工作流的管理员 |
使用场景
在工单处理界面可以显示一个按钮“转交”,当用户认为当前工单自己处理不了时,可以将工单转交给合适的人处理。 另外作为管理员可以强制(即非工单当前处理人的情况下)将工单转交给别人ß
返回数据
{
"data": true,
"code": 0,
"msg": ""
}
加签¶
url
api/v1.0/tickets/{ticket_id}/add_node
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
target_username |
varchar |
是 |
加签对象的用户名 |
suggestion |
varchar |
否 |
加签意见 |
使用场景
当用户A提交了一个权限申请工单,达到运维人员处理人中状态,作为运维人员的B在处理过程中发现需要C先处理或者提供一些必要的信息,B才能处理。 那么B在处理工单界面可以点击”加签“按钮,弹窗中选择C。 系统调用loonflow的加签接口将工单加签给C。C处理完后点击”完成“按钮, 系统调用loonflow的加签完成接口, 工单处理人将回到B. 那么B就可以按照之前既定流程正常流转下去
返回数据
{
"data": {},
"code": 0,
"msg": ""
}
加签处理完成¶
url
api/v1.0/tickets/{ticket_id}/add_node_end
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
suggestion |
varchar |
否 |
加签完成意见 |
使用场景
使用场景 当A将工单加签给B.B在处理工单时候,界面将只显示“完成“按钮,点击后后端调用此接口,将工单基础表中的is_add_node设置为false
返回数据
{
"data": {},
"code": 0,
"msg": ""
}
处理工单¶
url
api/v1.0/tickets/{ticket_id}
method
patch
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
transition_id |
int |
是 |
流转id |
suggestion |
varchar |
否 |
处理意见(与处理工单类型,用户在处理工单的时候点击了按钮操作 可以填写附加的一些意见如:麻烦尽快处理) |
其他必填字段 |
N/A |
否 |
其他必填字段或可选字段(在配置工作流过程中,会配置工作流每个状态的必填和可选的字段。在处理工单时候必须提供必填字段。如请假申请工单,配置了自定义字段请假天数days,工单初始状态也设置了days为必填,那么处理此类工单时候就必选提供days)。工单详情接口中有当前处理是时必选的字段 |
返回数据
{
"msg": "",
"data": {},
"code": 0
}
获取工单流转记录¶
url
api/v1.0/tickets/{ticket_id}/flowlogs
method
get
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
ticket_data |
string |
否 |
是否返回每个操作时工单的所有字段信息,取值”1”/”0”,默认”0”(否) |
desc |
string |
否 |
是否按照时间降序返回记录,取值”1”/”0”,默认”1”(是) |
返回数据(ticket_data未传或ticket_data传0)
{
"msg": "",
"data": {
"total": 4,
"value": [
{
"state": {
"state_name": "发起人-确认中",
"state_id": 5
},
"transition": {
"transition_name": "确认完成",
"transition_id": 5,
"attribute_type_id": 3
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"suggestion": "已经生效,感谢"
},
{
"state": {
"state_name": "技术人员-处理中",
"state_id": 4
},
"transition": {
"transition_name": "处理完成",
"transition_id": 4
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:14",
"gmt_created": "2018-04-30 15:55:32",
"suggestion": "处理完成"
},
{
"state": {
"state_name": "TL审批中",
"state_id": 3
},
"transition": {
"transition_name": "同意",
"transition_id": 3
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:00",
"gmt_created": "2018-04-30 15:53:19",
"suggestion": "同意处理"
},
{
"state": {
"state_name": "新建中",
"state_id": 1
},
"transition": {
"transition_name": "提交",
"transition_id": 1
},
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:52:35",
"gmt_created": "2018-04-10 17:39:33",
"suggestion": "请尽快处理,谢谢"
}],
"page": 1,
"per_page": 10
},
"code": 0
}
返回数据(ticket_data传1)
{
"msg": "",
"data": {
"total": 4,
"value": [{
"state": {
"state_name": "发起人-确认中",
"state_id": 5
},
"transition": {
"transition_name": "确认完成",
"transition_id": 5,
"attribute_type_id": 3
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"suggestion": "已经生效,感谢",
"ticket_data": {
"title": "xxx",
"sn": "xxxxx",
"state_id": 1,
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"xxxx": "....."
}
},
{
"state": {
"state_name": "技术人员-处理中",
"state_id": 4
},
"transition": {
"transition_name": "处理完成",
"transition_id": 4
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:14",
"gmt_created": "2018-04-30 15:55:32",
"suggestion": "处理完成",
"ticket_data": {
"title": "xxx",
"sn": "xxxxx",
"state_id": 1,
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"xxxx": "....."
}
},
{
"state": {
"state_name": "TL审批中",
"state_id": 3
},
"transition": {
"transition_name": "同意",
"transition_id": 3
},
"ticket_id": 1,
"participant_info": {
"participant_email": "lilei@163.com",
"participant_alias": "李磊",
"participant_phone": "13888888888",
"participant": "lilei",
"participant_type_id": 1
},
"gmt_modified": "2018-04-30 15:57:00",
"gmt_created": "2018-04-30 15:53:19",
"suggestion": "同意处理",
"ticket_data": {
"title": "xxx",
"sn": "xxxxx",
"state_id": 1,
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"xxxx": "....."
}
},
{
"state": {
"state_name": "新建中",
"state_id": 1
},
"transition": {
"transition_name": "提交",
"transition_id": 1
},
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:52:35",
"gmt_created": "2018-04-10 17:39:33",
"suggestion": "请尽快处理,谢谢",
"ticket_data": {
"title": "xxx",
"sn": "xxxxx",
"state_id": 1,
"ticket_id": 1,
"gmt_modified": "2018-04-30 15:57:26",
"gmt_created": "2018-04-30 15:56:02",
"xxxx": "....."
}
}
],
"page": 1,
"per_page": 10
},
"code": 0
}
工单处理步骤记录¶
url
api/v1.0/tickets/{ticket_id}/flowsteps
method
get
请求参数
无
返回数据
{
"data": {
"current_state_id": 2 //工单当前状态id
"value": [{
"state_id": 17,
"state_flow_log_list": [],
"order_id": 0,
"state_name": "test11111"
}, {
"state_id": 18,
"state_flow_log_list": [],
"order_id": 0,
"state_name": "2233222"
}, {
"state_id": 6,
"state_flow_log_list": [{
"gmt_created": "2018-05-15 07:16:38",
"participant_info": {
"participant_alias": "李磊",
"participant_type_id": 1,
"participant": "lilei",
"participant_phone": "13888888888",
"participant_email": "lilei@163.com"
},
"suggestion": "",
"participant": "lilei",
"state_id": 6,
"participant_type_id": 1,
"transition": {
"transition_name": "提交",
"transition_id": 7
},
"id": 32,
"intervene_type_id": 0
}],
"order_id": 1,
"state_name": "发起人-新建中"
}, {
"state_id": 7,
"state_flow_log_list": [{
"gmt_created": "2018-05-15 07:20:40",
"participant_info": {
"participant_alias": "李磊",
"participant_type_id": 1,
"participant": "lilei",
"participant_phone": "13888888888",
"participant_email": "lilei@163.com"
},
"suggestion": "同意申请",
"participant": "lilei",
"state_id": 7,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 8
},
"id": 33,
"intervene_type_id": 0
}],
"order_id": 2,
"state_name": "发起人tl-审批中"
}, {
"state_id": 8,
"state_flow_log_list": [{
"gmt_created": "2018-05-16 06:42:00",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "接单处理",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "未知操作",
"transition_id": 0
},
"id": 36,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 06:49:55",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 37,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 06:57:31",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "接单处理",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "未知操作",
"transition_id": 0
},
"id": 38,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 06:57:36",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 39,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 06:58:41",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 40,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:01:53",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 41,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:03:34",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 43,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:04:45",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 45,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:31:29",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 47,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:21:00",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 49,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:24:03",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 51,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:24:44",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 53,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:33:26",
"participant_info": {
"participant_alias": "轨迹",
"participant_type_id": 1,
"participant": "guiji",
"participant_phone": "13888888888",
"participant_email": "guiji@163.com"
},
"suggestion": "同意",
"participant": "guiji",
"state_id": 8,
"participant_type_id": 1,
"transition": {
"transition_name": "同意",
"transition_id": 9
},
"id": 55,
"intervene_type_id": 0
}],
"order_id": 3,
"state_name": "运维人员-审批中"
}, {
"state_id": 9,
"state_flow_log_list": [{
"gmt_created": "2018-05-16 07:01:54",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "False\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 42,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:03:34",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "False\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 44,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:04:45",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "False\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 46,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 07:31:29",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "lilei\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 48,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:21:00",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "lilei\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 50,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:24:03",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "lilei\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 52,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:24:44",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "lilei\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 54,
"intervene_type_id": 0
}, {
"gmt_created": "2018-05-16 23:33:26",
"participant_info": {
"participant_phone": "",
"participant_alias": "demo_script.py",
"participant_email": "",
"participant_type_id": 6,
"participant": "demo_script.py"
},
"suggestion": "lilei\n",
"participant": "demo_script.py",
"state_id": 9,
"participant_type_id": 6,
"transition": {
"transition_name": "脚本执行完成",
"transition_id": 10
},
"id": 56,
"intervene_type_id": 0
}],
"order_id": 4,
"state_name": "授权脚本-自动执行中"
}, {
"state_id": 10,
"state_flow_log_list": [{
"gmt_created": "2018-05-17 06:45:58",
"participant_info": {
"participant_alias": "李磊",
"participant_type_id": 1,
"participant": "lilei",
"participant_phone": "13888888888",
"participant_email": "lilei@163.com"
},
"suggestion": "请处理",
"participant": "lilei",
"state_id": 10,
"participant_type_id": 1,
"transition": {
"transition_name": "转交操作",
"transition_id": 0
},
"id": 57,
"intervene_type_id": 1
}, {
"gmt_created": "2018-05-17 06:47:46",
"participant_info": {
"participant_alias": "张三",
"participant_type_id": 1,
"participant": "zhangsan",
"participant_phone": "13888888888",
"participant_email": "zhangsan@163.com"
},
"suggestion": "请协助处理",
"participant": "zhangsan",
"state_id": 10,
"participant_type_id": 1,
"transition": {
"transition_name": "加签操作",
"transition_id": 0
},
"id": 58,
"intervene_type_id": 2
}],
"order_id": 6,
"state_name": "发起人-确认中"
}, {
"state_id": 11,
"state_flow_log_list": [],
"order_id": 7,
"state_name": "结束"
}]
},
"msg": "",
"code": 0
}
修改工单状态¶
url
api/v1.0/tickets/{ticket_id}/state
method
put
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
state_id |
int |
是 |
目标状态id |
suggestion |
varchar |
否 |
处理意见 |
使用场景
用于干预工单的当前状态,可以直接将工单状态修改为指定状态,系统会根据state_id获取对应的处理人信息
返回格式
{
"msg": "",
"data": {},
"code": 0
}
批量获取工单状态¶
url
api/v1.0/tickets/states
method
get
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
ticket_ids |
varchar |
是 |
工单ids,逗号隔开的字符串 |
使用场景
调用方自己保存工单基础信息 并根据loonflow中工单id关联,在显示工单列表时直接从自己后端获取工单列表。 但是工单状态需要实时从loonflow中获取,那么可以 通过此接口获取一页工单列表每个工单的状态
返回数据
{
"code": 0,
"data": {
"1": {
"state_id": 1,
"state_name": "发起人-编辑中"
},
2: {
"state_id": 2,
"state_name": "新建中"
}
},
"msg": ""
}
修改工单字段的值¶
url
api/v1.0/tickets/{ticket_id}/fields
method
patch
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
需要修改值的字段的key1 |
varchar |
是 |
如需要修改标题,则就是title |
需要修改值的字段的key2 |
varchar |
是 |
如需要修改标题,则就是title |
其他需要修改的字段的字段标识 |
varchar |
是 |
如需要修改标题,则就是title |
返回数据
{
"msg": "",
"data": {},
"code": 0
}
重试工单脚本/hook任务¶
url
api/v1.0/tickets/{ticket_id}/retry_script
method
post
请求参数
无
使用场景
当工单的脚本(或者hook[v0.3.17版本支持])执行失败后,工单详情接口中获取的数据中script_run_last_result为false. 这时可以在工单详情界面 step图中此状态下显示有个”重试按钮“,用户点击此按钮后,可以调用此接口重新执行或重新触发hook
返回数据
{
"msg": "Ticket script or hook retry start successful",
"data": {},
"code": 0
}
新增工单评论/注释¶
url
api/v1.0/tickets/{ticket_id}/comments
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
suggestion |
varchar |
是 |
处理意见(与处理工单类型,用户在处理工单的时候点击了按钮操作 可以填写附加的一些意见如:麻烦尽快处理) |
返回数据
{
"code": 0,
"msg": "",
"data": {}
}
工单hook回调¶
url
api/v1.0/tickets/{ticket_id}/hook_call_back
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
result |
boolean |
是 |
hook任务执行是否成功, false, true |
msg |
varchar |
是 |
hook执行输出信息,可留空’’ |
field_value |
dict object |
否 |
需要修改值的字段. 这些字段需要在状态表单设置中为可选或者必填 |
使用场景
当工作流状态设置处理人类型为hook,工单到达此状态时,会触发hook请求,被请求方可以执行一些操作,执行完成后回调用loonflow, 告知loonflow任务执行结果,以触发loonflow中工单状态的流转(当hook配置中wait为false时,无需回调,hook发出后会立即触发流转,wait为true会等待 回调)。回调参数如果result为false,那么loonflow会标记该工单的script_run_last_result为False(获取工单详情接口也会返回此标识,前端可以根据这 个标识来显示一个重试的按钮,用户点击这个重试按钮后调用”重试工单脚本/hook任务”接口),同时也会将msg(你可以传失败的原因)中的内容记录到工单流转记录中。
返回数据
{
"code": 0,
"msg": "",
"data": {}
}
工单当前的参与人详情¶
url
api/v1.0/tickets/{ticket_id}/participant_info
method
get
使用场景
此接口将返回该工单当前的参与人详细信息,如果是部门或角色会返回对应部门角色下所有用户。调用方可基于此提供工单催办的功能。 用户在前端点击催办按钮,前端弹窗要求用户选择通知的类型:短信、邮件、微信、钉钉等等 以及需要的备注信息, 然后调用方后端发送相应的通知消息给工单的当前处理人
返回数据
{
"msg": "",
"data": {
"participant_info_list": [{
"alias": "\u8d85\u7ea7\u7ba1\u7406\u5458",
"username": "admin",
"phone": "13888888888",
"email": "blackholll@163.com"
}, {
"alias": "\u8f68\u8ff9",
"username": "guiji",
"phone": "13888888888",
"email": "guiji@163.com"
}, {
"alias": "\u674e\u78ca",
"username": "lilei",
"phone": "13888888888",
"email": "lilei@163.com"
}, {
"alias": "\u5f20\u4e09",
"username": "zhangsan",
"phone": "13888888888",
"email": "zhangsan@163.com"
}, {
"alias": "\u674e\u56db",
"username": "lisi",
"phone": "13888888888",
"email": "lisi@163.com"
}, {
"alias": "\u738b\u4e94",
"username": "wangwu",
"phone": "13888888888",
"email": "wangwu@163.com"
}, {
"alias": "\u6770\u514b",
"username": "jack",
"phone": "13888888888",
"email": "jack@163.com"
}],
"participant_username_list": ["admin", "guiji", "lilei", "zhangsan", "lisi", "wangwu", "jack"]
},
"code": 0
}
强制关闭工单¶
url
api/v1.0/tickets/{ticket_id}/close
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
suggestion |
varchar |
否 |
关闭原因 |
使用场景
超级管理员在查看工单详情时,可以在界面上显示一个强制关闭工单的按钮,点击后调用关闭工单按钮,实现强制关闭工单。 另外工单创建人在工单处于初始状态下(创建人撤回、退回到初始状态等情况工单状态会处于初始状态)也可以强制关闭工单。
返回数据
{
"code": 0,
"msg": "",
"data": {}
}
撤回工单¶
url
api/v1.0/tickets/{ticket_id}/retreat
method
post
请求参数
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
suggestion |
varchar |
否 |
撤回原因 |
使用场景
在配置工作流状态时,可以指定某些状态下允许创建人撤回工单,那么当工单处于这些状态时,创建人可以撤回该工单(调用方前端在这个情况下显示一个撤回按钮)
返回数据
{
"code": 0,
"msg": "",
"data": {}
}
相关常量¶
状态类型¶
分配方式¶
处理人类型¶
流转类型¶
自定义字段类型¶
字段属性¶
工单权限类别¶
常见问题¶
为什么不使用外键
关于是否使用数据库外键,网上各有说法,各有利弊。本人基于如下几个原因所以没使用外键:1.外键属于业务逻辑,不应该吧这种 逻辑放到数据库层而加重数据库的负担 2. 使用外键会有数据的强校验,导致日常维护时会有些麻烦(后来发现其实可以使用 db_constraint=False, on_delete=False参数来关闭强校验,通知也保留外键本来的便利性, 1.0版本也有开始少量应用) 3.有些公司db规范里面就是不允许使用外键的
为什么没使用django rest framework
drf在未使用外键的情况下,无法发挥其价值。无显式外键时,自定义序列话返回数据需要逐条记录查询关联信息,效率非常低
为什么使用http api方式提供服务 而不是python三方包
1.loonflow的理念是:工单应该是嵌入到各个系统中(如oa,cmdb,运维平台、客服系统等等), 这些系统通过后端api调用loonflow。 所以loonflow只有管理界面(v0.1版本直接使用django admin,后面会重写管理界面)。后续会提供几个调用方demo供大家参考。 感谢@youshutong帮忙写的调用方demo(vue+django): https://github.com/youshutong2080/shutongFlow 另外帮忙jimmy201602写的demo(bootstrap+django): https://github.com/jimmy201602/workflowdemo 2.loonflow引擎功能较复杂功能较复杂不适合使用包的方式
为何不建议调用方前端直接调用loonflow
调用方和loonflow之前需要做权限验证,签名算法考虑到安全只能写在调用方后端;作为引擎,loonflow不提供用户登录验证功能, 只校验调用方的合法性,所以登录验证需要做在调用方自己的后端;每个调用方除了纯粹的工单的功能,还会需要一些额外的功能, 比如根据自定义字段筛选工单列表,loonflow提供了工单列表的接,但是因为loonflow的自定义字段是纵表形式存储的, 无法提供根据这些字段来筛选工单列表。如果需要自定义字段的筛选,需要调用方自己保存一份工单数据,用于筛选; 比如需要做一个项目全生命周期管理的系统,需要用到工作流。 但是还有比如发布,获取人员信息、和其他系统交互、 日志查看、项目数据统计等等功能。这些需要做在自己的后端
调用方是否需要保存工单的基础数据
根据情况而定,如果调用方在显示工单数据的时候需要显示更多相关信息,可以本地保存一份附属信息与loonflow中对应关系。 针对本地保存的情况,如果涉及工单流转的字段(如参与人等),在本地修改时需要同时调用loonflow修改loonflow中保存的字段 的值(r0.2以后版本中提供了修改工单字段值的接口)
如何限制用户查看工单权限
默认会限制工单的查看权限(通过api获取工单详情时,只有username参数是工单相关人员时才能获取到数据)。如果需要放开限制, 可以修改工作流配置中的“查看权限校验”为否。权限配置只针对工作流的,多个类型的工作流需要单独配置
为何需要同步用户及部门信息到loonflow
因为工单流转涉及到较多的用户信息获取,所以需要将用户信息(包括部门)同步到loonflow的账户系统中。同步部门信息的时候, 如果发现部门被删除,建议修改部门名字,如前面加个 “已废弃:”,否则如果该部门存在某个工单的当前处理人的时候会有问题。 用户离职的情况设置is_active=0.另外用户密码请随便填写(为了不允许普通用户登录)。管理员账户请通过 python manage.py creatsuperuser来创建。只需要管理员实现一个同步脚本定时执行即可,其他调用方不用考虑此问题
为什么调用方demo中要保存用户信息
调用方demo可以理解为你的cmdb、客服系统、运维系统、oa系统。这些系统你可以自己管理用户信息,也可以在涉及用户方面的地方都调用接口来获取。 不同公司提供用户信息的方式不同,所以demo里面自己实现了个简单的用户管理供大家参考。
为什么调用方demo中的用户,也需要存在于loonflow的用户中
loonflow的工单在流转过程中,如目标状态的处理人是创建人TL,那么loonflow需要在自己的用户体系中找到这个用户所在部门, 然后获取这个部门的审批人或者tl信息
如何支持根据工单的自定义字段查询
loonflow只提供工单基础字段的查询,如果需要针对自定义字段的查询,请在自己系统中保存一份工单数据(注意工单处理过程中, 如果有字段修改,也需要更新自己系统中的数据)
工单列表支持排序
只支持根据创建时间排序。其他字段排序可以在调用方系统中保存一份数据来自己实现排序,然后只有在获取工单详情的时候调用loonflow接口
工单类型需要支持多级
比如需要支持“运维-权限申请-vpn权限申请”。 因为loonflow的工作流只有一级,如果需要支持多级类型,需要在调用方保存一份工单类型 与loonflow工作流关联的数据。表字段可以如下:type_id, type_name, up_type_id, loonflow_workflow_id
如何实现工单的评分和处理优先级功能
因为不同公司对于评分的需求不同,如评分有1星、2星、3星,满意、及格、不合格。 如优先级有高、中、低,紧急、中等、不急等等。 因此loonflow不提供通用功能。用户可以针对不同的工作流定义不同的自定义字段以表示评分或者优先级,自定义字段可以选择checkbox类型, 也可以通过字段的标签灵活处理(前端根据约定好的标签,特殊显示)。 当然如果你这边的需求非常统一,你可以给loonflow的基础表中添加一个字段, 以实现公用。不过修改此逻辑后,后续loonflow更新时需要特别注意下
为什么为提供拖拽生成工作流的功能
loonflow目前非商业化系统,面向用户是有一定计算机基础的人。完全拖拽只能实现非常基础的工作流的配置,复杂点的还是需要填写一些个性化的如标签来实现 定制功能。涉及定制功能的工作流配置使用当前方式效率不比拖拽低。因此拖拽生成工作流的功能优先级较低,会等其他功能都比较完善后再考虑支持
为什么不用migration维护表结构
django提供的migration功能建议只用于自己的开发环境。生产环境直接操作执行风险较大,在实际生产环境中涉及数据库表结构的变更一般需要dba审核, 这种审核是基于DDL语句来审核的(没理由要求DBA查看你的django modal)
遇到问题如何自行排查
loonflow在出现报错时会记录详细的堆栈日志,在遇到问题时,请第一时间查看日志。如果日志中没有异常,请查看浏览器控制台中各请求的返回结果
如何查看日志
日志默认是记录在启动loonflow进程的用户家目录中的loonflow.log, 配置在settings/config.py(从settings目录下其他simple文件复制出来)。 使用uwsgi生产环境运行时候,当前无法打印日志文件(该问题后面有空会解决下),可以在uwsgi日志中查看。
celery任务日志路径取决于你启动celery任务时候指定的日志文件路径
配置工作流时,提示”DataTables warning, unkown parameter”
此问题一般是因为配置错误导致,如状态的参与人类型选择的是部门,参与人填写的部门id实际不存在
工作流修改后流程图无法展示
可能出现的原因是删除了某些状态,但是某些流转中的源目标状态还引用了这些状态导致
配置好工作流后,调用方无法列出相应的工作流
配置完工作流后需要给调用应用授权,“用户及权限-调用权限”中配置
工单详情中显示的信息字段很少或者没有字段信息
详见: https://github.com/blackholll/loonflow/issues/37
部署相关问题
详见文档”如何运行”章节
有问题请优先查文档及在github上提issue,再考虑到群里(qq群:558788490)咨询, 群内问题我会每天晚上21:00-第二天8点之间答复(可能会遗漏)。 github 上issue不会遗漏。常见问题我会定期整理添加到此文档中
欢迎捐助¶
您的支持是我最大的动力,欢迎支付宝扫码捐助,捐助满300元。即可享受VIP服务,权益包括:
发布说明¶
r2.0.18¶
修复: 【用户管理】添加用户时候必填项标记缺失
优化:【部署】docker compose部署新增media目录配置
优化: 【管理后台】svg背景文件迁移到本地
修复:【用户管理】部门管理页面新增记录时上级部门只能选择当前页列表中的部门
修复: 【用户管理】用户角色页面无法切换当前页
修复: 【工单详情】当日期或日期时间字段为空时直接退回工单,会导致后续此字段无法编辑
修复: 【工单详情】处理工单时文件类型字段的必填校验不生效
r2.0.17¶
修复: 【工单详情】日期类型字段有值且可编辑状态下工单详情页面报错问题
修复: 【工单详情】文件类型字段无值时且当前配置展示时工单详情页面报错问题
优化: 【文档】readme文档更新
r2.0.16¶
优化:【部署】简化docker compose部署方式,image已传docker hub,不再需要本地build image,支持容器启动mysql
修复:【接口】工作流列表接口中admin列表信息返回错误
优化:【新功能】支持配置hook_url的host可信列表,提供安全性
修复:【管理后台】状态列表及自定义字段列表前端代码中per_page参数typo(实际不影响功能)
修复:【管理后台】工作流配置相关页面在第二页新增配置后列表页码显示错误
修复:【代码逻辑】工单标题模板未支持内置字段(creator、sn等)
修复:【代码逻辑】新建工单后,工单列表页面需要1分钟后才显示最新工单记录
修复:【管理后台】登录失败页面没有提示信息
修复:【代码逻辑】工单执行状态hook时,如果成功执行则没有记录操作日志问题
修复:【代码逻辑】新建工单的限制表达式中count参数实际未生效问题
r2.0.15¶
修复: 升级python版本后导致的docker-compose方式部署无法启动问题
修复: 处理人类型使用外部获取时计算最终处理人处理出错问题
修复: 第二个状态的处理人类型为外部获取,出发hook时丢失工单信息问题
r2.0.14¶
优化: 工单当前所处状态被删除,工单详情api不报错
优化: 升级python版本到3.10, 使用pymysql替换mysqlclient降低安装复杂度
优化: 将前端icon文件迁移到本地,避免用户无法链接外网导致页面打开非常缓慢
优化: 初始化sql更新
优化: 更新文档中的截图到最新版本
修复: 工单详情接口返回内容中创建人所属部门id信息内容错乱问题
修复: redis server未配置密码导致创建工单提示”Client sent AUTH, but no password is set”问题
新增: 工单处理人新增支持在流程过程中实时从外部获取
新增: 英文版readme
r2.0.13¶
修复: 新建工单界面错误的显示了工单列表(子工单列表没处理好导致的)
修复: 生产环境部署后,错误日志未被记录问题
修复: 当工单状态的处理人是全部处理,且按照不同的处理路径处理完后,再强制修改状态,会导致工单无法继续处理问题
r2.0.12¶
优化: 管理员工单列表支持删除工单
优化: 工单详情页支持创建人关闭及撤回工单
优化: 工单详情页处理工单按钮支持点击后弹窗提示
优化: 工单列表接口支持查询子工单
优化: 工单详情页面显示子工单列表
修复: 日期字段未填写无法提交工单问题
修复: 强制修改工单状态,未触发通知hook问题
修复: 工作流配置时流转操作的开启弹窗switch无法生效问题
r2.0.11¶
后端首页去除登录认证
修复:表单中包含时间或日期类型只读时无法提交或处理工单问题
docker compose部署使用utf8mb4字符集
修复:流程图界面未import message导致出错是页面崩溃问题
修复: 开发模式下提交或处理工单无法上传附件问题
优化: 移除工单内置字段的描述信息,避免在工单详情页面显示
修复: 新建流程时,配置了条件表达式,未选择目标状态会无法保存问题
修复: 开发环境下工单详情中附件无法访问问题
优化: 工单详情中下载文件保存为原有文件名
优化: 未配置工作流展示表单导致工单详情页面报错问题
部署: docker compose部署方式的一些备注信息
修复: 新增用户时所在部门只能选择前10个问题
修复: 配置了查看或干预权限但是工单查看、工单干预页面列表返回空问题
r2.0.10¶
兼容:【流程图】前端ant design 的chart版本自动升级后导致流程图页面打开报错问题兼容
兼容:【部署】docker compose部署方式,因为下载nodejs的源证书问题,导致无法下载的兼容
r2.0.9¶
新增:【详情页面】支持加签完成操作
优化:【接口】工单状态删除后,不影响列表接口的数据返回(不抛异常,已’未知状态’代替)
修复:【接口】hook操作后未记录操作类型,导致操作日志接口中hook操作的名称为’未知’问题
优化:【代码逻辑】提高eval的安全性
r2.0.8¶
修复:【代码逻辑】应用接口调用权限添加后无法通过权限校验问题
r2.0.7¶
修复:【代码逻辑】计算用户上级部门逻辑错误
修复:【管理后台】工作流调用权限无法添加成功问题
修复:【管理后台】角色用户管理中无法添加角色用户问题
修复:【管理后台】工作流基础配置中添加应用授权时无法搜索应用问题
优化:【管理后台】工作流状态列表中显示状态id,方便些条件表达式
优化:【管理后台】应用调用权限记录新增或者编辑是应用名不允许重复
优化:【接口】工单操作记录中使用的流转操作被删除后,操作记录接口报错问题优化
优化:【接口】新建工单时如果redis未启动时提示信息优化
优化:【文档】新增对前端部分代码接口的描述说明
优化:【代码】移除已废弃的前端页面及相关图片
r2.0.6¶
修复:【管理后台】工作流列表分页无效问题
修复:【管理后台】工作流无法删除问题
修复:【管理后台】参与人类型选择无, 参与人还是必填问题
修复:【管理后台】部门新增不选择审批人时,无法添加问题
修复:【管理后台】工作流编辑时,删除api授权应用后无法再次添加回来问题
优化:【管理后台】部门列表中显示部门的审批人姓名及leader的姓名
修复:【部署】docker compose方式无法停止服务,即执行python3 run.py stop报错问题
优化:【部署】docker compose部署支持升级
r2.0.5¶
修复:【管理后台】工作流编辑时选择通知的回显异常问题
修复:【管理后台】工作流状态流转等多次编辑时json被重复转换导致内容异常问题
修复:【管理后台】用户所属部门无法被删除问题
修复:【工单详情】多选类型字段无法提交问题
修复:【管理后台】选择类型字段只读状态显示异常问题
新增:【工单详情】新增支持富文本的显示及回显
优化:【API】工单列表中工单处理人为多人时的性能优化
r2.0.4¶
修复:【部署】初始sql中有个字段写错了导致无法添加调用权限记录问题
优化:【管理后台】流程图显示优化
修复:【管理后台】部门列表只能显示第一页问题
修复:【管理后台】添加部门弹窗中默认的审批人信息错误导致无法直接添加部门问题
修复:【管理后台】添加调用权限记录弹窗中默认工作流选择初始值为空导致无法直接添加问题
r2.0.3¶
修复:【管理后台】工作流管理页面查询条件不生效问题
修复:【管理后台】工单列表筛选条件(创建人、创建时间不生效)问题
修复:【管理后台】流程图中条件表达式无法显示显示问题
修复:【管理后台】定时器没有配置入口问题
修复:【管理后台】工作流创建时部分人员信息未保存问题
修复:【管理后台】流转记录编辑后会新增一条记录问题
修复:【管理后台】hook类型前端显示错误问题
修复:【部署】docker compose部署时,数据库导入失败未停止部署进程问题
修复:【部署】docker compose部署,导如初始化数据时,数据库端口指定不生效问题
r2.0.2¶
修复: 创建工单时填写内容不合法时会跳转到报错页面问题
修复: 新创建的用户无法登录问题
修复: 普通用户无法被重置密码问题
修复: 不同用户类型的权限不生效问题
修复: 超级管理员无法管理所有工单问题
修复: 普通用户可以查看干预工单菜单问题
修复: 无法删除角色问题
修复: 工单列表中查询条件工作流类型不生效问题
修复: 标题模板配置后不生效问题
优化: 未选择工作流时,点击新建工单按钮,弹窗报错问题
r2.0.1¶
修复: docker compose方式部署时,提示权限问题
修复: 管理后台新增工作流报错问题
r2.0.0¶
自带工单创建、查看、处理、管理界面(本次最大改动)
支持用户同时属于多个部门(本次较大改动)
flowlog接口支持指定顺序或者倒序
工作流配置界面支持查看每天新增工单统计
工单详情中支持管理员干预工单
支持用户自行修改密码
其他若干优化
r1.0.13¶
修复: 创建人关闭工单功能异常
修复: 多人处理工单逻辑异常
修复: hook处理未正常记录hook执行状态
修复: 处理工单时,在操作记录中记录的所有字段值信息格式错误
r1.0.12¶
修复: 管理后台工作流列表查询不生效
修复: 管理后台无法新增部门
修复: 撤回工单后,未出现在创建人的待办列表中
修复: 处理人类型为hook时 状态无法流转
修复: 处理人类型为hook,当处理失败时未成功保存工单所有字段信息
修复: 多人处理是去重逻辑问题
优化: 管理后台支持回车登录
r1.0.11¶
修复: 当用户无处理权限时,获取用户可执行操作接口返回结果格式不合理
修复: 工单被撤回时,工单进行状态字段值未被更新,导致此状态无法被查询
修复: 工作流编辑时,标题模板及通知模板被修改后,前端未更新显示
修复: 角色用户无法被成功删除
修复: 工单操作记录中处理意见无法被成功保存
r1.0.10¶
修复: 当参与人类型为部门,且参与人设置了多个部门id(逗号隔开)时,无法正确流转到对应的人
修复: 管理后台中配置流转时,”点击弹窗提示 “属性无法成功保存问题
r1.0.9¶
修复: 工单加签完成后,当前处理人待办列表中无该工单问题
修复: 多人全部处理完成后,下个状态处理人类型如果是工单字段时,无法获取到当前处理人问题
优化: 修改工单基础表中当前参与人字段的长度,修改工单处理记录中处理意见字段的长度, 修改工作流状态中参与人字段长度
r1.0.8¶
修复: 当工单当前状态需要接单时,获取用户可以做的流转接口报错
修复: 无法删除角色的用户记录
优化: 不请求favicon.ico
r1.0.7¶
修复: 状态分配方式为全部处理,且参与人设置为工单字段情况下,其中一个人处理就直接到下个状态问题
修复: 状态hook回调时 result传false后, 实际工单脚本hook执行状态未更新问题
修复: 状态参与人设置为父工单字段时,功能不正常
新增: 工单状态参与人类型变量、工单字段、父工单字段支持设置多个(逗号隔开)
r1.0.6¶
优化: hook流转suggestion获取方式调整
优化: sphinx文档新增几个常见问题及解答
优化: readme中调用方demo相关信息修改
r1.0.5¶
修复: 状态参与人为多人,且分配方式为全部处理时,参与人没有处理完就流转到下个状态的问题
修复: 状态参与人为hook, wait=false(即不等待回调,直接流转)情况下,无法正常流转问题
新增: 新增基于钉钉生态的移动端调用方开源审批系统项目,https://gitee.com/shihow/howflow-open
r1.0.4¶
修复: 工单自定义字段的值不能被正常更新问题
修复: 处理人为多部门时,处理人计算错误问题
修复: 撤回工单未更新工单状态问题
r1.0.3¶
修复: 强制修改工单状态后处理人异常问题
修复: 撤回工单条件判断逻辑错误问题
新增: 新增docker compose方式部署loonflow_shutongflow(仅供演示用)
r1.0.2¶
修复: 获取工作流状态详情接口报错问题
修复: 还没有配置工作流时,工单管理界面报错问题
修复: 部门编辑时未选择部门审批人无法保存问题修复
修复: 编辑工作流时候标题模板,内容模板未成功保存问题
修复: 处理人类型为工单字段时, 获取处理人信息错误问题
修复: 配置流转时候目标状态不选时,导致流转列表出不来问题
修复: 管理后台中强制修改工单状态导致工单无法被继续处理问题
修复: 状态强制修改为初始状态或者结束状态时, 处理人错误问题
修复: 调用权限编辑后再新增记录时,表单中遗留了上次编辑的内容问题
修复: readthedoc文档中允许启动命令中中两个-被转成了一个–问题说明
修复: 使用uwsgi部署后,日志文件没有内容问题(临时改成打印日志到控制台,可取uwsgi日志中查看日志)
优化: 新增工作流后提示用户去添加调用权限
优化: 配置工作流 选择通知的地方,加个提示 如何新增通知
r1.0.1¶
修复: 生产环境依赖包uwsgi版本更新
修复: 工单列表查询条件创建起止时间处理逻辑错误
修复: 评论工单接口逻辑错误
修复: 强制关闭后工单的进行状态属性未更新问题
修复: 状态参与人类型是角色时导致处理人异常问题
修复: 部分情况下工单列表接口查询我的待办工单返回数据错误
新增功能: 工单列表支持我处理过的工单查询
新增功能: 工单列表查询api的状态属性条件支持“已关闭”查询
优化: 管理后台中工单管理异常情况提示信息优化及一些其他细节优化
r1.0.0¶
升级python3.6
配置文件统一修改为config.py
新增接口:撤回工单
工单详情接口新增返回当前状态的详细信息
允许工单创建人在工单的初始状态直接关闭工单
工单列表接口性能优化
flowstep接口中新增返回当前状态信息,并且记录按照state的顺序id排序
工单列表查询接口新增支持查询条件: 草稿中、进行中、被撤回、被退回、完成
自定义通知由脚本修改为hook方式
管理后台首页新增工单数量分类统计
管理后台显示当前详细版本号
管理后台支持用户、部门、角色编辑
管理后台配置状态时,初始及结束状态隐藏处理人输入框信息
管理后台支持对工单干预处理: 直接关闭、转交、修改工单状态、删除
状态参与人类型是部门时,支持设置多个部门
流转操作支持目标状态为初始状态:不再需要额外配置一个”发起人编辑中“这样的中间状态
工作流状态hook,支持配置额外参数信息
管理后台权限控制细化:分为超级管理员和工作流管理员
使用readthedoc管理项目文档
静态文件由cdn移到本地,避免内网部署无外网访问权限时无法正常使用
代码结构及内部逻辑优化(去除冗余代码、单例模式减少内存占用、数据库操作语句优化、type hints、view参数强校验等)
r0.x.x¶
见github release https://github.com/blackholll/loonflow/releases