非常叼的工單系統(tǒng),工單結(jié)束,工作完成;非常叼的權(quán)限管控,精細(xì)到頁(yè)面按鈕及API

最近在研究工單系統(tǒng)的時(shí)候,被我找到一個(gè)非常流弊的工單系統(tǒng),我們都知道工單系統(tǒng)最麻煩的就是流程和模版的維護(hù),并且,在工單處理過(guò)程中很可能會(huì)添加一些操作,這些操作被稱(chēng)之為鉤子。就按我目前調(diào)研的結(jié)果來(lái)說(shuō),目前其實(shí)沒(méi)有啥工單系統(tǒng)能實(shí)現(xiàn)的這么好的。

這個(gè)工單系統(tǒng)就把流程設(shè)計(jì),模版設(shè)計(jì)等等做的非常不錯(cuò),而且對(duì)權(quán)限的把控非常詳細(xì),包括API接口、菜單、頁(yè)面按鈕權(quán)限,都可以靈活的控制,非常的不錯(cuò)。

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

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

如果覺(jué)得不錯(cuò)就給作者一個(gè)star,你的star沒(méi)準(zhǔn)就是作者繼續(xù)維護(hù)下去的動(dòng)力呢。

功能介紹

系統(tǒng)管理

  • 用戶(hù)管理不僅僅包括了用戶(hù),還有角色、職位、部門(mén)的管理,方便后面的工單處理人擴(kuò)展。
  • 菜單管理,對(duì)菜單,頁(yè)面按鈕,甚至是API接口的管理。
  • 登陸日志,對(duì)用戶(hù)登陸和退出的日志記錄。

流程中心

  • 流程申請(qǐng),對(duì)流程進(jìn)行分類(lèi)管理,方便維護(hù)與可視化。
  • 工單列表,拆分了4個(gè)分類(lèi),包括:我待辦的工單,我創(chuàng)建的工單,我相關(guān)的工單,還有所有工單。
  • 轉(zhuǎn)交工單,如果你當(dāng)前有別的事情在處理就可以把工單轉(zhuǎn)交給別人去處理。
  • 結(jié)束工單,如果一個(gè)工單發(fā)展申請(qǐng)不對(duì),權(quán)限足夠的話,是可以直接結(jié)束工單的。
  • 工單處理人的多樣化,不僅可以個(gè)人處理,還可以是部門(mén)、角色、變量。
  • 處理人變量,根據(jù)用戶(hù)數(shù)據(jù)來(lái)自動(dòng)獲得是該誰(shuí)處理,比如:創(chuàng)建人,創(chuàng)建人leader,HRBP等等。
  • 會(huì)簽,如果是多個(gè)選擇人的話,并且勾選了會(huì)簽功能,那么就需要這些負(fù)責(zé)人都處理完成后才會(huì)通過(guò)。
  • 任務(wù)管理,可以給任何階段綁定任務(wù),相當(dāng)于流程中的鉤子操作,實(shí)現(xiàn)的效果就是,工單完成,任務(wù)也就執(zhí)行完成了,減少很多的人力成本。
  • 通知方式的靈活性,可以通過(guò)任務(wù)給每個(gè)階段綁定通知方式,也可以給流程綁定全局通知。
  • 網(wǎng)關(guān),支持排他網(wǎng)關(guān)和并行網(wǎng)關(guān),排他網(wǎng)關(guān)即通過(guò)條件判斷,只要有一個(gè)條件通過(guò),則可進(jìn)入下一個(gè)階段;并行網(wǎng)關(guān),即必須所有的階段都完成處理,才可以進(jìn)行下一個(gè)階段
  • 后面還會(huì)有很多的功能擴(kuò)展,包括:加簽,催辦,子流程等等。

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

數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

對(duì)于一個(gè)完整的工作流系統(tǒng)來(lái)說(shuō),我們需要有流程、模版、分組、用戶(hù)、任務(wù)等等,并且這些東西都是可以靈活定制的,因?yàn)槿绻荒莒`活定制的話,對(duì)于普通的使用這來(lái)說(shuō)是非常不方便的,所以對(duì)于一個(gè)好的工作流系統(tǒng),是必須要實(shí)現(xiàn)靈活性的。

下面直接來(lái)展示一下,數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)圖。

工作流數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)圖

流程分類(lèi)

type Classify struct {
    base.Model
    Name    string `gorm:"column:name; type: varchar(128)" json:"name" form:"name"`     // 分類(lèi)名稱(chēng)
    Creator int    `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"` // 創(chuàng)建者
}

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

流程

type Info struct {
    base.Model
    Name      string          `gorm:"column:name; type:varchar(128)" json:"name" form:"name"`        // 流程名稱(chēng)
    Structure json.RawMessage `gorm:"column:structure; type:json" json:"structure" form:"structure"` // 流程結(jié)構(gòu)
    Classify  int             `gorm:"column:classify; type:int(11)" json:"classify" form:"classify"` // 分類(lèi)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"`                // 任務(wù)ID, array, 可執(zhí)行多個(gè)任務(wù),可以當(dāng)成通知任務(wù),每個(gè)節(jié)點(diǎn)都會(huì)去執(zhí)行
    Creator   int             `gorm:"column:creator; type:int(11)" json:"creator" form:"creator"`    // 創(chuàng)建者
}

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"`                       // 模板名稱(chēng)
    FormStructure json.RawMessage `gorm:"column:form_structure; type: json" json:"form_structure" form:"form_structure" binding:"required"` // 表單結(jié)構(gòu)
    Creator       int             `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"`                                      // 創(chuàng)建者
    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"`                    // 工單標(biāo)題
    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"`                // 分類(lèi)ID
    IsEnd         int             `gorm:"column:is_end; type:int(11); default:0" json:"is_end" form:"is_end"`           // 是否結(jié)束, 0 未結(jié)束,1 已結(jié)束
    State         json.RawMessage `gorm:"column:state; type:json" json:"state" form:"state"`                            // 狀態(tài)信息
    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"`                   // 創(chuàng)建人
}

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"` // 表單結(jié)構(gòu)
    FormData      json.RawMessage `gorm:"column:form_data; type: json" json:"form_data" form:"form_data"`                // 表單數(shù)據(jù)
}

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

工單流轉(zhuǎn)歷史

type CirculationHistory struct {
    base.Model
    Title        string `gorm:"column:title; type: varchar(128)" json:"title" form:"title"`                         // 工單標(biāo)題
    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"`                         // 工單狀態(tài)
    Source       string `gorm:"column:source; type: varchar(128)" json:"source" form:"source"`                      // 源節(jié)點(diǎn)ID
    Target       string `gorm:"column:target; type: varchar(128)" json:"target" form:"target"`                      // 目標(biāo)節(jié)點(diǎn)ID
    Circulation  string `gorm:"column:circulation; type: varchar(128)" json:"circulation" form:"circulation"`       // 流轉(zhuǎn)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"` // 處理時(shí)長(zhǎng)
    Remarks      string `gorm:"column:remarks; type: longtext" json:"remarks" form:"remarks"`                       // 備注
}

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

任務(wù)

type Info struct {
    base.Model
    Name     string `gorm:"column:name; type: varchar(256)" json:"name" form:"name"`               // 任務(wù)名稱(chēng)
    TaskType string `gorm:"column:task_type; type: varchar(45)" json:"task_type" form:"task_type"` // 任務(wù)類(lèi)型
    Content  string `gorm:"column:content; type: longtext" json:"content" form:"content"`          // 任務(wù)內(nèi)容
    Creator  int    `gorm:"column:creator; type: int(11)" json:"creator" form:"creator"`           // 創(chuàng)建者
    Remarks  string `gorm:"column:remarks; type: longtext" json:"remarks" form:"remarks"`          // 備注
}

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

任務(wù)執(zhí)行歷史

type History struct {
    base.Model
    Task          int    `gorm:"column:task; type: int(11)" json:"task" form:"task"`                                    // 任務(wù)ID
    Name          string `gorm:"column:name; type: varchar(256)" json:"name" form:"name"`                               // 任務(wù)名稱(chēng)
    TaskType      int    `gorm:"column:task_type; type: int(11)" json:"task_type" form:"task_type"`                     // 任務(wù)類(lèi)型, python, shell
    ExecutionTime string `gorm:"column:execution_time; type: varchar(128)" json:"execution_time" form:"execution_time"` // 執(zhí)行時(shí)間
    Result        string `gorm:"column:result; type: longtext" json:"result" form:"result"`                             // 任務(wù)返回
}

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

安裝部署

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

二次開(kāi)發(fā)

后端

# 1. 獲取代碼
git https://github.com/lanyulei/ferry.git

# 2. 進(jìn)入工作路徑
cd ./ferry

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

# 配置信息注意事項(xiàng):
1. 程序的啟動(dòng)參數(shù)
2. 數(shù)據(jù)庫(kù)的相關(guān)信息
3. 日志的路徑

# 4. 初始化數(shù)據(jù)庫(kù)
go run main.go init -c=config/settings.dev.yml

# 5. 啟動(dòng)程序
go run main.go server -c=config/settings.dev.yml

前端

# 1. 獲取代碼
git https://github.com/lanyulei/ferry_web.git

# 2. 進(jìn)入工作路徑
cd ./ferry_web

# 3. 安裝依賴(lài)
npm install

# 4. 啟動(dòng)程序
npm run dev

上線部署

后端

# 1. 進(jìn)入到項(xiàng)目路徑下進(jìn)行交叉編譯(centos)
env GOOS=linux GOARCH=amd64 go build

更多交叉編譯內(nèi)容,請(qǐng)?jiān)L問(wèn) https://www.fdevops.com/2020/03/08/go-locale-configuration

# 2. config目錄上傳到項(xiàng)目根路徑下,并確認(rèn)配置信息是否正確
vi ferry/config/settings.yml

# 配置信息注意事項(xiàng):
1. 程序的啟動(dòng)參數(shù)
2. 數(shù)據(jù)庫(kù)的相關(guān)信息
3. 日志的路徑

# 3. 創(chuàng)建日志路徑及靜態(tài)文件經(jīng)歷
mkdir -p log static/uploadfile

# 4. 初始化數(shù)據(jù)
./ferry init -c=config/settings.yml

# 5. 啟動(dòng)程序,推薦通過(guò)"進(jìn)程管理工具"進(jìn)行啟動(dòng)維護(hù)
nohup ./ferry server -c=config/settings.yml > /dev/null 2>&1 &

前端

# 1. 編譯
npm run build:prod

# 2. 將dist目錄上傳至項(xiàng)目路徑下即可。
mv dist web

# 3. nginx配置,根據(jù)業(yè)務(wù)自行調(diào)整即可
  server {
    listen 8001; # 監(jiān)聽(tīng)端口
    server_name localhost; # 域名可以有多個(gè),用空格隔開(kāi)
  
    #charset koi8-r;
  
    #access_log  logs/host.access.log  main;
    location / {
      root /data/ferry/web;
      index index.html index.htm; #目錄內(nèi)的默認(rèn)打開(kāi)文件,如果沒(méi)有匹配到index.html,則搜索index.htm,依次類(lèi)推
    }
  
    #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地址
    }
  
    # 后端靜態(tài)文件路徑
    location /static/uploadfile {
      proxy_pass http://127.0.0.1:8002; #node api server 即需要代理的IP地址
    }
  
    #error_page  404              /404.html;    #對(duì)錯(cuò)誤頁(yè)面404.html 做了定向配置
  
    # redirect server error pages to the static page /50x.html
    #將服務(wù)器錯(cuò)誤頁(yè)面重定向到靜態(tài)頁(yè)面/50x.html
    #
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
      root html;
    }
  }

自此項(xiàng)目就介紹完了,反正我覺(jué)得還是非常不錯(cuò)的,如果你也覺(jué)得不錯(cuò),就給作者一個(gè)star吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容