在咱們平時的工做中,會有不少時候須要跟其餘同事進行協同辦公,甚至須要跨部門合做,在這種狀況下,咱們就須要同事之間的溝通交流,而溝經過程中一句不合適的話,都會形成爭吵,跨部門合做的時候,更加麻煩,由於對彼此都不熟悉,所以很難進行很好的溝通交流。git
其實不少時候,咱們的工做都是流程化,上一位同事處理完,下一位同事接着進行處理,在這種場景需求下就誕生了工單系統,將一個任務處理過程當中,所涉及到的人,按照優先級排列,而後依次進行處理,處理過程當中可進行註解,方便下一個處理人對以前處理結果的熟悉,而且處理完成後經過郵件或者其餘通信方式通知下一我的處理,依次類推,當處理完成後,通知工單申請人,工單已所有處理完成。github
其實目前有一些現成的工單系統了,有開源的也有收費的,可是廣泛的功能比較單一,不方便管理,固然確定有我不知道的很是流弊的工單系統,只是在說我已知的,若是冒犯到你的話,深感抱歉。sql
本篇文章就給你們詳細的介紹一下如何設計一個靈活,易維護管理的工單系統,在介紹此工單系統以前,首先給你們推薦一個仍是不錯的工單系統,供你們參考,學習。數據庫
Demo: ferry 工單系統(點擊進入演示Demo)json
Github: https://github.com/lanyulei/ferry服務器
Gitee: https://gitee.com/yllan/ferry數據結構
目前大部分公司都會安裝部署一套單點登陸系統,方便維護管理員工的帳號信息,同時實現多系統的用戶登錄,例如Ldap,所以支持主流的單點登陸系統也是頗有必要的。app
對於一個工單系統來講,由於涉及的人會很是多,審批階段會有各層次的領導進行審閱;處理階段會有各部門的人進行處理。所以對於權限的管理是要很是的嚴格。數據庫設計
在工單系統中流程設計應該算是最繁瑣的地方了,要定義好每一個節點的關聯關係、處理人以及各類屬性數據,若是沒有一個好的流程設計工具,對於工單系統的用戶體驗來講,是很是差的。並且對於後續的維護也是很是的不易。tcp
以前就有朋友說,他們的工單系統是寫死的,每一個流程,每一個節點,每一個處理人都是在代碼裏定義好的,這樣的工單系統是很是不易用的,只要有人員變更,流程變更,都須要從新修改代碼進行編譯上線,所以好的流程設計工具,對一個好的工單系統來講,是相當重要的。
咱們在提交工單的時候,須要寫不少的描述數據,例如申請服務器,就須要寫服務器的配置信息,像是CPU、內存、磁盤等信息,這些須要寫的描述數據,並不是是一成不變的,會根據場景的不一樣而進行適當的修改,所以咱們就須要有一個可視化的表單設計工具,來進行表單模版的維護管理,經過將表單模版與流程的關聯從而實現一條完整的工做流。
咱們工單系統內的工單,根據工單歸屬的分類,能夠大概的分爲:我建立的工單、我待辦的工單、我相關的工單以及全部工單。咱們根據這4類工單歸屬類型進行數據的分類展現,方便他人的使用及查看。
在一個工單流程中,一般不少操做都是能夠自動完成的,好比申請數據庫帳號,可讓用戶填寫好數據後,DBA審批經過,則自動去建立帳號,並進行郵件或者其餘通信工具進行通知。這種就能節省不少人爲操做,使這我的能夠處理更多其餘的工做。畢竟20世紀最貴的成本是人力呢。
以上就是大概總結的一些核心的功能,固然還有不少細小的劃分。更精細的設計就須要你們去認真構思了。畢竟一篇文章若是說考慮到方方面面的話,那是不現實的,得須要碼多少字呢。
其實開發一個系統,最耗費時間的不是敲代碼的部分。程序設計,數據結構設計纔是最繁瑣的,要考慮到各類場景、各類關聯。
因此這裏就不藏私了,直接給你們看下個人數據結構設計:
用戶/權限/菜單/系統配置
工單系統
在此將核心功能中代碼給你們展現一下,可是確定不會所有的(由於實在是太多了,貼不完呢),有興趣的能夠去Github上Clone下來詳細的研究一下。
Github: https://github.com/lanyulei/ferry
Gitee: https://gitee.com/yllan/ferry
ldap算是比較經常使用的一個單點登錄系統了,跟同事、同窗、朋友聊天的過程當中,能感受大部分都是在使用ldap。固然有人會說ldap是一個協議,這些就不用糾結這些細節了,你也能夠說,使用了ldap協議單點登陸系統。
集成ldap登錄驗證後,在用戶驗證經過會,會將數據保存至本地數據庫一份,方便維護管理。
鏈接ldap:
package ldap import ( "crypto/tls" "errors" "ferry/pkg/logger" "fmt" "time" "github.com/spf13/viper" "github.com/go-ldap/ldap/v3" ) /* @Author : lanyulei */ var conn *ldap.Conn // ldap鏈接 func ldapConnection() (err error) { var ldapConn = fmt.Sprintf("%v:%v", viper.GetString("settings.ldap.host"), viper.GetString("settings.ldap.port")) conn, err = ldap.Dial( "tcp", ldapConn, ) if err != nil { err = errors.New(fmt.Sprintf("沒法鏈接到ldap服務器,%v", err)) logger.Error(err) return } if viper.GetBool("settings.ldap.tls") { err = conn.StartTLS(&tls.Config{ InsecureSkipVerify: true, }) if err != nil { err = errors.New(fmt.Sprintf("升級到加密方式失敗,%v", err)) logger.Error(err) return } } //設置超時時間 conn.SetTimeout(5 * time.Second) return }
搜索ldap用戶信息:
package ldap import ( "encoding/json" "errors" "ferry/global/orm" "ferry/models/system" "ferry/pkg/logger" "fmt" "github.com/go-ldap/ldap/v3" "github.com/spf13/viper" ) /* @Author : lanyulei */ func getLdapFields() (ldapFields []map[string]string, err error) { var ( settingsValue system.Settings contentList []map[string]string ) err = orm.Eloquent.Model(&settingsValue).Where("classify = 2").Find(&settingsValue).Error if err != nil { return } err = json.Unmarshal(settingsValue.Content, &contentList) if err != nil { return } for _, v := range contentList { if v["ldap_field_name"] != "" { ldapFields = append(ldapFields, v) } } return } func searchRequest(username string) (userInfo *ldap.Entry, err error) { var ( ldapFields []map[string]string cur *ldap.SearchResult ldapFieldsFilter = []string{ "dn", } ) ldapFields, err = getLdapFields() for _, v := range ldapFields { ldapFieldsFilter = append(ldapFieldsFilter, v["ldap_field_name"]) } // 用來獲取查詢權限的用戶。若是 ldap 禁止了匿名查詢,那咱們就須要先用這個賬戶 bind 如下才能開始查詢 if !viper.GetBool("settings.ldap.anonymousQuery") { err = conn.Bind( fmt.Sprintf("cn=%v,%v", viper.GetString("settings.ldap.bindUser"), viper.GetString("settings.ldap.baseDn")), viper.GetString("settings.ldap.bindPwd")) if err != nil { logger.Error("用戶或密碼錯誤。", err) return } } sql := ldap.NewSearchRequest( viper.GetString("settings.ldap.baseDn"), ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, fmt.Sprintf("(cn=%s)", username), ldapFieldsFilter, nil) if cur, err = conn.Search(sql); err != nil { err = errors.New(fmt.Sprintf("在Ldap搜索用戶失敗, %v", err)) logger.Error(err) return } if len(cur.Entries) == 0 { err = errors.New("未查詢到對應的用戶信息。") logger.Error(err) return } userInfo = cur.Entries[0] return }
映射本地數據庫字段:
package ldap import ( "ferry/models/system" "github.com/go-ldap/ldap/v3" ) /* @Author : lanyulei */ func LdapFieldsMap(ldapUserInfo *ldap.Entry) (userInfo system.SysUser, err error) { var ( ldapFields []map[string]string ) ldapFields, err = getLdapFields() if err != nil { return } for _, v := range ldapFields { switch v["local_field_name"] { case "nick_name": userInfo.NickName = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) case "phone": userInfo.Phone = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) case "avatar": userInfo.Avatar = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) case "sex": userInfo.Sex = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) case "email": userInfo.Email = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) case "remark": userInfo.Remark = ldapUserInfo.GetAttributeValue(v["ldap_field_name"]) } } return }
ldap用戶登錄:
package ldap import ( "fmt" "github.com/go-ldap/ldap/v3" ) /* @Author : lanyulei */ func LdapLogin(username string, password string) (userInfo *ldap.Entry, err error) { err = ldapConnection() if err != nil { return } defer conn.Close() userInfo, err = searchRequest(username) if err != nil { return } err = conn.Bind(userInfo.DN, password) if err != nil { return nil, fmt.Errorf("用戶或密碼不正確。") } return }
在一個工單系統中,最核心的部分就是工單流轉,要兼容各類處理人信息,而且根據節點之間的關聯進行節點的流轉,所以,在工單流轉這一部分咱們須要很是認真對待,由於若是你不認真對待,可能會形成工單流轉到一個非此流程相關的人名下,對於用戶體驗是很是差的,甚至會喪失用戶對系統的信用再也不使用此係統。因此這段是重中之重。需認真對待。
package service import ( "encoding/json" "errors" "ferry/global/orm" "ferry/models/base" "ferry/models/process" "ferry/models/system" "ferry/pkg/notify" "ferry/tools" "fmt" "reflect" "time" "github.com/jinzhu/gorm" "github.com/gin-gonic/gin" ) /* @Author : lanyulei @Desc : 處理工單 */ /* -- 節點 -- start: 開始節點 userTask: 審批節點 receiveTask: 處理節點 scriptTask: 任務節點 end: 結束節點 -- 網關 -- exclusiveGateway: 排他網關 parallelGateway: 並行網關 inclusiveGateway: 包容網關 */ type Handle struct { cirHistoryList []process.CirculationHistory workOrderId int updateValue map[string]interface{} stateValue map[string]interface{} targetStateValue map[string]interface{} workOrderData [][]byte workOrderDetails process.WorkOrderInfo endHistory bool flowProperties int circulationValue string processState ProcessState tx *gorm.DB } // 時間格式化 func fmtDuration(d time.Duration) string { d = d.Round(time.Minute) h := d / time.Hour d -= h * time.Hour m := d / time.Minute return fmt.Sprintf("%02d小時 %02d分鐘", h, m) } // 會籤 func (h *Handle) Countersign(c *gin.Context) (err error) { var ( stateList []map[string]interface{} stateIdMap map[string]interface{} currentState map[string]interface{} cirHistoryCount int ) err = json.Unmarshal(h.workOrderDetails.State, &stateList) if err != nil { return } stateIdMap = make(map[string]interface{}) for _, v := range stateList { stateIdMap[v["id"].(string)] = v["label"] if v["id"].(string) == h.stateValue["id"].(string) { currentState = v } } for _, cirHistoryValue := range h.cirHistoryList { if _, ok := stateIdMap[cirHistoryValue.Source]; !ok { break } for _, processor := range currentState["processor"].([]interface{}) { if cirHistoryValue.ProcessorId != tools.GetUserId(c) && cirHistoryValue.Source == currentState["id"].(string) && cirHistoryValue.ProcessorId == int(processor.(float64)) { cirHistoryCount += 1 } } } if cirHistoryCount == len(currentState["processor"].([]interface{}))-1 { h.endHistory = true err = h.circulation() if err != nil { return } } return } // 工單跳轉 func (h *Handle) circulation() (err error) { var ( stateValue []byte ) stateList := make([]interface{}, 0) for _, v := range h.updateValue["state"].([]map[string]interface{}) { stateList = append(stateList, v) } err = GetVariableValue(stateList, h.workOrderDetails.Creator) if err != nil { return } stateValue, err = json.Marshal(h.updateValue["state"]) if err != nil { return } err = h.tx.Model(&process.WorkOrderInfo{}). Where("id = ?", h.workOrderId). Updates(map[string]interface{}{ "state": stateValue, "related_person": h.updateValue["related_person"], }).Error if err != nil { h.tx.Rollback() return } return } // 條件判斷 func (h *Handle) ConditionalJudgment(condExpr map[string]interface{}) (result bool, err error) { var ( condExprOk bool condExprValue interface{} ) defer func() { if r := recover(); r != nil { switch e := r.(type) { case string: err = errors.New(e) case error: err = e default: err = errors.New("未知錯誤") } return } }() for _, data := range h.workOrderData { var formData map[string]interface{} err = json.Unmarshal(data, &formData) if err != nil { return } if condExprValue, condExprOk = formData[condExpr["key"].(string)]; condExprOk { break } } if condExprValue == nil { err = errors.New("未查詢到對應的表單數據。") return } // todo 待優化 switch reflect.TypeOf(condExprValue).String() { case "string": switch condExpr["sign"] { case "==": if condExprValue.(string) == condExpr["value"].(string) { result = true } case "!=": if condExprValue.(string) != condExpr["value"].(string) { result = true } case ">": if condExprValue.(string) > condExpr["value"].(string) { result = true } case ">=": if condExprValue.(string) >= condExpr["value"].(string) { result = true } case "<": if condExprValue.(string) < condExpr["value"].(string) { result = true } case "<=": if condExprValue.(string) <= condExpr["value"].(string) { result = true } default: err = errors.New("目前僅支持6種常規判斷類型,包括(等於、不等於、大於、大於等於、小於、小於等於)") } default: err = errors.New("條件判斷目前僅支持字符串、整型。") } return } // 並行網關,確認其餘節點是否完成 func (h *Handle) completeAllParallel(c *gin.Context, target string) (statusOk bool, err error) { var ( stateList []map[string]interface{} ) err = json.Unmarshal(h.workOrderDetails.State, &stateList) if err != nil { err = fmt.Errorf("反序列化失敗,%v", err.Error()) return } continueHistoryTag: for _, v := range h.cirHistoryList { status := false for i, s := range stateList { if v.Source == s["id"].(string) && v.Target == target { status = true stateList = append(stateList[:i], stateList[i+1:]...) continue continueHistoryTag } } if !status { break } } if len(stateList) == 1 && stateList[0]["id"].(string) == h.stateValue["id"] { statusOk = true } return } func (h *Handle) commonProcessing(c *gin.Context) (err error) { // 若是是拒絕的流轉則直接跳轉 if h.flowProperties == 0 { err = h.circulation() if err != nil { err = fmt.Errorf("工單跳轉失敗,%v", err.Error()) } return } // 會籤 if h.stateValue["assignValue"] != nil && len(h.stateValue["assignValue"].([]interface{})) > 1 { if isCounterSign, ok := h.stateValue["isCounterSign"]; ok { if isCounterSign.(bool) { h.endHistory = false err = h.Countersign(c) if err != nil { return } } else { err = h.circulation() if err != nil { return } } } else { err = h.circulation() if err != nil { return } } } else { err = h.circulation() if err != nil { return } } return } func (h *Handle) HandleWorkOrder( c *gin.Context, workOrderId int, tasks []string, targetState string, sourceState string, circulationValue string, flowProperties int, remarks string, ) (err error) { h.workOrderId = workOrderId h.flowProperties = flowProperties h.endHistory = true var ( execTasks []string relatedPersonList []int cirHistoryValue []process.CirculationHistory cirHistoryData process.CirculationHistory costDurationValue string sourceEdges []map[string]interface{} targetEdges []map[string]interface{} condExprStatus bool relatedPersonValue []byte parallelStatusOk bool processInfo process.Info currentUserInfo system.SysUser sendToUserList []system.SysUser noticeList []int sendSubject string = "您有一條待辦工單,請及時處理" sendDescription string = "您有一條待辦工單請及時處理,工單描述以下" ) defer func() { if r := recover(); r != nil { switch e := r.(type) { case string: err = errors.New(e) case error: err = e default: err = errors.New("未知錯誤") } return } }() // 獲取工單信息 err = orm.Eloquent.Model(&process.WorkOrderInfo{}).Where("id = ?", workOrderId).Find(&h.workOrderDetails).Error if err != nil { return } // 獲取流程信息 err = orm.Eloquent.Model(&process.Info{}).Where("id = ?", h.workOrderDetails.Process).Find(&processInfo).Error if err != nil { return } err = json.Unmarshal(processInfo.Structure, &h.processState.Structure) if err != nil { return } // 獲取當前節點 h.stateValue, err = h.processState.GetNode(sourceState) if err != nil { return } // 目標狀態 h.targetStateValue, err = h.processState.GetNode(targetState) if err != nil { return } // 獲取工單數據 err = orm.Eloquent.Model(&process.TplData{}). Where("work_order = ?", workOrderId). Pluck("form_data", &h.workOrderData).Error if err != nil { return } // 根據處理人查詢出須要會籤的條數 err = orm.Eloquent.Model(&process.CirculationHistory{}). Where("work_order = ?", workOrderId). Order("id desc"). Find(&h.cirHistoryList).Error if err != nil { return } err = json.Unmarshal(h.workOrderDetails.RelatedPerson, &relatedPersonList) if err != nil { return } relatedPersonStatus := false for _, r := range relatedPersonList { if r == tools.GetUserId(c) { relatedPersonStatus = true break } } if !relatedPersonStatus { relatedPersonList = append(relatedPersonList, tools.GetUserId(c)) } relatedPersonValue, err = json.Marshal(relatedPersonList) if err != nil { return } h.updateValue = map[string]interface{}{ "related_person": relatedPersonValue, } // 開啓事務 h.tx = orm.Eloquent.Begin() stateValue := map[string]interface{}{ "label": h.targetStateValue["label"].(string), "id": h.targetStateValue["id"].(string), } switch h.targetStateValue["clazz"] { // 排他網關 case "exclusiveGateway": sourceEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "source") if err != nil { return } breakTag: for _, edge := range sourceEdges { edgeCondExpr := make([]map[string]interface{}, 0) err = json.Unmarshal([]byte(edge["conditionExpression"].(string)), &edgeCondExpr) if err != nil { return } for _, condExpr := range edgeCondExpr { // 條件判斷 condExprStatus, err = h.ConditionalJudgment(condExpr) if err != nil { return } if condExprStatus { // 進行節點跳轉 h.targetStateValue, err = h.processState.GetNode(edge["target"].(string)) if err != nil { return } if h.targetStateValue["clazz"] == "userTask" || h.targetStateValue["clazz"] == "receiveTask" { if h.targetStateValue["assignValue"] == nil || h.targetStateValue["assignType"] == "" { err = errors.New("處理人不能爲空") return } } h.updateValue["state"] = []map[string]interface{}{{ "id": h.targetStateValue["id"].(string), "label": h.targetStateValue["label"], "processor": h.targetStateValue["assignValue"], "process_method": h.targetStateValue["assignType"], }} err = h.commonProcessing(c) if err != nil { err = fmt.Errorf("流程流程跳轉失敗,%v", err.Error()) return } break breakTag } } } if !condExprStatus { err = errors.New("全部流轉均不符合條件,請確認。") return } // 並行/聚合網關 case "parallelGateway": // 入口,判斷 sourceEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "source") if err != nil { err = fmt.Errorf("查詢流轉信息失敗,%v", err.Error()) return } targetEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "target") if err != nil { err = fmt.Errorf("查詢流轉信息失敗,%v", err.Error()) return } if len(sourceEdges) > 0 { h.targetStateValue, err = h.processState.GetNode(sourceEdges[0]["target"].(string)) if err != nil { return } } else { err = errors.New("並行網關流程不正確") return } if len(sourceEdges) > 1 && len(targetEdges) == 1 { // 入口 h.updateValue["state"] = make([]map[string]interface{}, 0) for _, edge := range sourceEdges { targetStateValue, err := h.processState.GetNode(edge["target"].(string)) if err != nil { return err } h.updateValue["state"] = append(h.updateValue["state"].([]map[string]interface{}), map[string]interface{}{ "id": edge["target"].(string), "label": targetStateValue["label"], "processor": targetStateValue["assignValue"], "process_method": targetStateValue["assignType"], }) } err = h.circulation() if err != nil { err = fmt.Errorf("工單跳轉失敗,%v", err.Error()) return } } else if len(sourceEdges) == 1 && len(targetEdges) > 1 { // 出口 parallelStatusOk, err = h.completeAllParallel(c, sourceEdges[0]["target"].(string)) if err != nil { err = fmt.Errorf("並行檢測失敗,%v", err.Error()) return } if parallelStatusOk { h.endHistory = true endAssignValue, ok := h.targetStateValue["assignValue"] if !ok { endAssignValue = []int{} } endAssignType, ok := h.targetStateValue["assignType"] if !ok { endAssignType = "" } h.updateValue["state"] = []map[string]interface{}{{ "id": h.targetStateValue["id"].(string), "label": h.targetStateValue["label"], "processor": endAssignValue, "process_method": endAssignType, }} err = h.circulation() if err != nil { err = fmt.Errorf("工單跳轉失敗,%v", err.Error()) return } } else { h.endHistory = false } } else { err = errors.New("並行網關流程不正確") return } // 包容網關 case "inclusiveGateway": return case "start": stateValue["processor"] = []int{h.workOrderDetails.Creator} stateValue["process_method"] = "person" h.updateValue["state"] = []map[string]interface{}{stateValue} err = h.circulation() if err != nil { return } case "userTask": stateValue["processor"] = h.targetStateValue["assignValue"].([]interface{}) stateValue["process_method"] = h.targetStateValue["assignType"].(string) h.updateValue["state"] = []map[string]interface{}{stateValue} err = h.commonProcessing(c) if err != nil { return } case "receiveTask": stateValue["processor"] = h.targetStateValue["assignValue"].([]interface{}) stateValue["process_method"] = h.targetStateValue["assignType"].(string) h.updateValue["state"] = []map[string]interface{}{stateValue} err = h.commonProcessing(c) if err != nil { return } case "scriptTask": stateValue["processor"] = []int{} stateValue["process_method"] = "" h.updateValue["state"] = []map[string]interface{}{stateValue} case "end": stateValue["processor"] = []int{} stateValue["process_method"] = "" h.updateValue["state"] = []map[string]interface{}{stateValue} err = h.circulation() if err != nil { h.tx.Rollback() return } err = h.tx.Model(&process.WorkOrderInfo{}). Where("id = ?", h.workOrderId). Update("is_end", 1).Error if err != nil { h.tx.Rollback() return } } // 流轉歷史寫入 err = orm.Eloquent.Model(&cirHistoryValue). Where("work_order = ?", workOrderId). Find(&cirHistoryValue). Order("create_time desc").Error if err != nil { h.tx.Rollback() return } for _, t := range cirHistoryValue { if t.Source != h.stateValue["id"] { costDuration := time.Since(t.CreatedAt.Time) costDurationValue = fmtDuration(costDuration) } } // 獲取當前用戶信息 err = orm.Eloquent.Model(¤tUserInfo). Where("user_id = ?", tools.GetUserId(c)). Find(¤tUserInfo).Error if err != nil { return } cirHistoryData = process.CirculationHistory{ Model: base.Model{}, Title: h.workOrderDetails.Title, WorkOrder: h.workOrderDetails.Id, State: h.stateValue["label"].(string), Source: h.stateValue["id"].(string), Target: h.targetStateValue["id"].(string), Circulation: circulationValue, Processor: currentUserInfo.NickName, ProcessorId: tools.GetUserId(c), CostDuration: costDurationValue, Remarks: remarks, } err = h.tx.Create(&cirHistoryData).Error if err != nil { h.tx.Rollback() return } // 獲取流程通知類型列表 err = json.Unmarshal(processInfo.Notice, ¬iceList) if err != nil { return } bodyData := notify.BodyData{ SendTo: map[string]interface{}{ "userList": sendToUserList, }, Subject: sendSubject, Description: sendDescription, Classify: noticeList, ProcessId: h.workOrderDetails.Process, Id: h.workOrderDetails.Id, Title: h.workOrderDetails.Title, Creator: currentUserInfo.NickName, Priority: h.workOrderDetails.Priority, CreatedAt: h.workOrderDetails.CreatedAt.Format("2006-01-02 15:04:05"), } // 判斷目標是不是結束節點 if h.targetStateValue["clazz"] == "end" && h.endHistory == true { sendSubject = "您的工單已處理完成" sendDescription = "您的工單已處理完成,工單描述以下" err = h.tx.Create(&process.CirculationHistory{ Model: base.Model{}, Title: h.workOrderDetails.Title, WorkOrder: h.workOrderDetails.Id, State: h.targetStateValue["label"].(string), Source: h.targetStateValue["id"].(string), Processor: currentUserInfo.NickName, ProcessorId: tools.GetUserId(c), Circulation: "結束", Remarks: "工單已結束", }).Error if err != nil { h.tx.Rollback() return } if len(noticeList) > 0 { // 查詢工單建立人信息 err = h.tx.Model(&system.SysUser{}). Where("user_id = ?", h.workOrderDetails.Creator). Find(&sendToUserList).Error if err != nil { return } bodyData.SendTo = map[string]interface{}{ "userList": sendToUserList, } bodyData.Subject = sendSubject bodyData.Description = sendDescription // 發送通知 go func(bodyData notify.BodyData) { err = bodyData.SendNotify() if err != nil { return } }(bodyData) } } h.tx.Commit() // 提交事務 // 發送通知 if len(noticeList) > 0 { stateList := make([]interface{}, 0) for _, v := range h.updateValue["state"].([]map[string]interface{}) { stateList = append(stateList, v) } sendToUserList, err = GetPrincipalUserInfo(stateList, h.workOrderDetails.Creator) if err != nil { return } bodyData.SendTo = map[string]interface{}{ "userList": sendToUserList, } bodyData.Subject = sendSubject bodyData.Description = sendDescription // 發送通知 go func(bodyData notify.BodyData) { err = bodyData.SendNotify() if err != nil { return } }(bodyData) } // 執行流程公共任務及節點任務 if h.stateValue["task"] != nil { for _, task := range h.stateValue["task"].([]interface{}) { tasks = append(tasks, task.(string)) } } continueTag: for _, task := range tasks { for _, t := range execTasks { if t == task { continue continueTag } } execTasks = append(execTasks, task) } go ExecTask(execTasks) return }
ok, anyway.
加上一段在公司常常聽到英文做爲結束語,若是能實現本文的這些功能,其實一個簡單易用,靈活方便維護管理的工單系統就成型了。
在此給你們說聲抱歉,沒法將完整的代碼貼在這裏(由於實在是太多了),若是你們還有什麼疑問的話,歡迎隨時來個人博客留言,我看到會盡快回復,也能夠加入個人qq羣,一塊兒討論學習。
blog: 蘭玉磊的技術博客
QQ羣:1127401830