非常叼的工单系统,工单结束,工作完成;非常叼的权限管控,精细到页面按钮及API

最近在研究工单系统的时候,被我找到一个非常流弊的工单系统,我们都知道工单系统最麻烦的就是流程和模版的维护,并且,在工单处理过程中很可能会添加一些操作,这些操作被称之为钩子。就按我目前调研的结果来说,目前其实没有啥工单系统能实现的这么好的。

这个工单系统就把流程设计,模版设计等等做的非常不错,而且对权限的把控非常详细,包括API接口、菜单、页面按钮权限,都可以灵活的控制,非常的不错。

Demo:http://fdevops.com:8001/#/dashboard

Github:https://github.com/lanyulei/ferry

如果觉得不错就给作者一个star,你的star没准就是作者继续维护下去的动力呢。

功能介绍

系统管理

  • 用户管理不仅仅包括了用户,还有角色、职位、部门的管理,方便后面的工单处理人扩展。
  • 菜单管理,对菜单,页面按钮,甚至是API接口的管理。
  • 登陆日志,对用户登陆和退出的日志记录。

流程中心

  • 流程申请,对流程进行分类管理,方便维护与可视化。
  • 工单列表,拆分了4个分类,包括:我待办的工单,我创建的工单,我相关的工单,还有所有工单。
  • 转交工单,如果你当前有别的事情在处理就可以把工单转交给别人去处理。
  • 结束工单,如果一个工单发展申请不对,权限足够的话,是可以直接结束工单的。
  • 工单处理人的多样化,不仅可以个人处理,还可以是部门、角色、变量。
  • 处理人变量,根据用户数据来自动获得是该谁处理,比如:创建人,创建人leader,HRBP等等。
  • 会签,如果是多个选择人的话,并且勾选了会签功能,那么就需要这些负责人都处理完成后才会通过。
  • 任务管理,可以给任何阶段绑定任务,相当于流程中的钩子操作,实现的效果就是,工单完成,任务也就执行完成了,减少很多的人力成本。
  • 通知方式的灵活性,可以通过任务给每个阶段绑定通知方式,也可以给流程绑定全局通知。
  • 网关,支持排他网关和并行网关,排他网关即通过条件判断,只要有一个条件通过,则可进入下一个阶段;并行网关,即必须所有的阶段都完成处理,才可以进行下一个阶段
  • 后面还会有很多的功能扩展,包括:加签,催办,子流程等等。

等等还有很多功能待研究。

数据结构设计

对于一个完整的工作流系统来说,我们需要有流程、模版、分组、用户、任务等等,并且这些东西都是可以灵活定制的,因为如果不能灵活定制的话,对于普通的使用这来说是非常不方便的,所以对于一个好的工作流系统,是必须要实现灵活性的。

下面直接来展示一下,数据结构的设计图。

工作流数据结构设计图

流程分类

type Classify struct {
    base.Model
    Name    string `gorm:"column:name; type: varchar(128)" json:"name" form:"name"`     // 分类名称
    Creator int    `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"` // 创建者
}

func (Classify) TableName() string {
    return "process_classify"
}

流程

type Info struct {
    base.Model
    Name      string          `gorm:"column:name; type:varchar(128)" json:"name" form:"name"`        // 流程名称
    Structure json.RawMessage `gorm:"column:structure; type:json" json:"structure" form:"structure"` // 流程结构
    Classify  int             `gorm:"column:classify; type:int(11)" json:"classify" form:"classify"` // 分类ID
    Tpls      json.RawMessage `gorm:"column:tpls; type:json" json:"tpls" form:"tpls"`                // 模版
    Task      json.RawMessage `gorm:"column:task; type:json" json:"task" form:"task"`                // 任务ID, array, 可执行多个任务,可以当成通知任务,每个节点都会去执行
    Creator   int             `gorm:"column:creator; type:int(11)" json:"creator" form:"creator"`    // 创建者
}

func (Info) TableName() string {
    return "process_info"
}

模版

type Info struct {
    base.Model
    Name          string          `gorm:"column:name; type: varchar(128)" json:"name" form:"name" binding:"required"`                       // 模板名称
    FormStructure json.RawMessage `gorm:"column:form_structure; type: json" json:"form_structure" form:"form_structure" binding:"required"` // 表单结构
    Creator       int             `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"`                                      // 创建者
    Remarks       string          `gorm:"column:remarks; type: longtext" json:"remarks" form:"remarks"`                                     // 备注
}

func (Info) TableName() string {
    return "tpl_info"
}

工单

type Info struct {
    base.Model
    Title         string          `gorm:"column:title; type:varchar(128)" json:"title" form:"title"`                    // 工单标题
    Process       int             `gorm:"column:process; type:int(11)" json:"process" form:"process"`                   // 流程ID
    Classify      int             `gorm:"column:classify; type:int(11)" json:"classify" form:"classify"`                // 分类ID
    IsEnd         int             `gorm:"column:is_end; type:int(11); default:0" json:"is_end" form:"is_end"`           // 是否结束, 0 未结束,1 已结束
    State         json.RawMessage `gorm:"column:state; type:json" json:"state" form:"state"`                            // 状态信息
    RelatedPerson json.RawMessage `gorm:"column:related_person; type:json" json:"related_person" form:"related_person"` // 工单所有处理人
    Creator       int             `gorm:"column:creator; type:int(11)" json:"creator" form:"creator"`                   // 创建人
}

func (Info) TableName() string {
    return "work_order_info"
}

工单绑定模版

type TplData struct {
    base.Model
    WorkOrder     int             `gorm:"column:work_order; type: int(11)" json:"work_order" form:"work_order"`          // 工单ID
    FormStructure json.RawMessage `gorm:"column:form_structure; type: json" json:"form_structure" form:"form_structure"` // 表单结构
    FormData      json.RawMessage `gorm:"column:form_data; type: json" json:"form_data" form:"form_data"`                // 表单数据
}

func (TplData) TableName() string {
    return "work_order_tpl_data"
}

工单流转历史

type CirculationHistory struct {
    base.Model
    Title        string `gorm:"column:title; type: varchar(128)" json:"title" form:"title"`                         // 工单标题
    WorkOrder    int    `gorm:"column:work_order; type: int(11)" json:"work_order" form:"work_order"`               // 工单ID
    State        string `gorm:"column:state; type: varchar(128)" json:"state" form:"state"`                         // 工单状态
    Source       string `gorm:"column:source; type: varchar(128)" json:"source" form:"source"`                      // 源节点ID
    Target       string `gorm:"column:target; type: varchar(128)" json:"target" form:"target"`                      // 目标节点ID
    Circulation  string `gorm:"column:circulation; type: varchar(128)" json:"circulation" form:"circulation"`       // 流转ID
    Processor    string `gorm:"column:processor; type: varchar(45)" json:"processor" form:"processor"`              // 处理人
    ProcessorId  int    `gorm:"column:processor_id; type: int(11)" json:"processor_id" form:"processor_id"`         // 处理人ID
    CostDuration string `gorm:"column:cost_duration; type: varchar(128)" json:"cost_duration" form:"cost_duration"` // 处理时长
    Remarks      string `gorm:"column:remarks; type: longtext" json:"remarks" form:"remarks"`                       // 备注
}

func (CirculationHistory) TableName() string {
    return "work_order_circulation_history"
}

任务

type Info struct {
    base.Model
    Name     string `gorm:"column:name; type: varchar(256)" json:"name" form:"name"`               // 任务名称
    TaskType string `gorm:"column:task_type; type: varchar(45)" json:"task_type" form:"task_type"` // 任务类型
    Content  string `gorm:"column:content; type: longtext" json:"content" form:"content"`          // 任务内容
    Creator  int    `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"`           // 创建者
    Remarks  string `gorm:"column:remarks; type: longtext" json:"remarks" form:"remarks"`          // 备注
}

func (Info) TableName() string {
    return "task_info"
}

任务执行历史

type History struct {
    base.Model
    Task          int    `gorm:"column:task; type: int(11)" json:"task" form:"task"`                                    // 任务ID
    Name          string `gorm:"column:name; type: varchar(256)" json:"name" form:"name"`                               // 任务名称
    TaskType      int    `gorm:"column:task_type; type: int(11)" json:"task_type" form:"task_type"`                     // 任务类型, python, shell
    ExecutionTime string `gorm:"column:execution_time; type: varchar(128)" json:"execution_time" form:"execution_time"` // 执行时间
    Result        string `gorm:"column:result; type: longtext" json:"result" form:"result"`                             // 任务返回
}

func (History) TableName() string {
    return "task_history"
}

安装部署

go >= 1.14
vue >= 2.6
npm >= 6.14

二次开发

后端

# 1. 获取代码
git https://github.com/lanyulei/ferry.git

# 2. 进入工作路径
cd ./ferry

# 3. 修改配置 ferry/config/settings.dev.yml
vi ferry/config/settings.dev.yml

# 配置信息注意事项:
1. 程序的启动参数
2. 数据库的相关信息
3. 日志的路径

# 4. 初始化数据库
go run main.go init -c=config/settings.dev.yml

# 5. 启动程序
go run main.go server -c=config/settings.dev.yml

前端

# 1. 获取代码
git https://github.com/lanyulei/ferry_web.git

# 2. 进入工作路径
cd ./ferry_web

# 3. 安装依赖
npm install

# 4. 启动程序
npm run dev

上线部署

后端

# 1. 进入到项目路径下进行交叉编译(centos)
env GOOS=linux GOARCH=amd64 go build

更多交叉编译内容,请访问 https://www.fdevops.com/2020/03/08/go-locale-configuration

# 2. config目录上传到项目根路径下,并确认配置信息是否正确
vi ferry/config/settings.yml

# 配置信息注意事项:
1. 程序的启动参数
2. 数据库的相关信息
3. 日志的路径

# 3. 创建日志路径及静态文件经历
mkdir -p log static/uploadfile

# 4. 初始化数据
./ferry init -c=config/settings.yml

# 5. 启动程序,推荐通过"进程管理工具"进行启动维护
nohup ./ferry server -c=config/settings.yml > /dev/null 2>&1 &

前端

# 1. 编译
npm run build:prod

# 2. 将dist目录上传至项目路径下即可。
mv dist web

# 3. nginx配置,根据业务自行调整即可
  server {
    listen 8001; # 监听端口
    server_name localhost; # 域名可以有多个,用空格隔开
  
    #charset koi8-r;
  
    #access_log  logs/host.access.log  main;
    location / {
      root /data/ferry/web;
      index index.html index.htm; #目录内的默认打开文件,如果没有匹配到index.html,则搜索index.htm,依次类推
    }
  
    #ssl配置省略
    location /api {
      # rewrite ^.+api/?(.*)$ /$1 break;
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
      proxy_redirect off;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  
    # 登陆
    location /login {
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
    }
  
    # 刷新token
    location /refresh_token {
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
    }
  
    # 接口地址
    location /swagger {
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
    }
  
    # 后端静态文件路径
    location /static/uploadfile {
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
    }
  
    #error_page  404              /404.html;    #对错误页面404.html 做了定向配置
  
    # redirect server error pages to the static page /50x.html
    #将服务器错误页面重定向到静态页面/50x.html
    #
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
      root html;
    }
  }

自此项目就介绍完了,反正我觉得还是非常不错的,如果你也觉得不错,就给作者一个star吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342