简单聊聊
在我们平时的工作中,会有很多时候需要跟其他同事进行协同办公,甚至需要跨部门合作,在这种情况下,我们就需要同事之间的沟通交流,而沟通过程中一句不合适的话,都会造成争吵,跨部门合作的时候,更加麻烦,因为对彼此都不熟悉,因此很难进行很好的沟通交流。
其实很多时候,我们的工作都是流程化,上一位同事处理完,下一位同事接着进行处理,在这种场景需求下就诞生了工单系统,将一个任务处理过程中,所涉及到的人,按照优先级排列,然后依次进行处理,处理过程中可进行注解,方便下一个处理人对之前处理结果的熟悉,并且处理完成后通过邮件或者其他通讯方式通知下一个人处理,依次类推,当处理完成后,通知工单申请人,工单已全部处理完成。
其实目前有一些现成的工单系统了,有开源的也有收费的,但是普遍的功能比较单一,不方便管理,当然肯定有我不知道的非常流弊的工单系统,只是在说我已知的,如果冒犯到你的话,深感抱歉。
本篇文章就给大家详细的介绍一下如何设计一个灵活,易维护管理的工单系统,在介绍此工单系统之前,首先给大家推荐一个还是不错的工单系统,供大家参考,学习。
Demo: ferry 工单系统(点击进入演示Demo)
Github: https://github.com/lanyulei/ferry
Gitee: https://gitee.com/yllan/ferry
核心功能介绍
单点登陆
目前大部分公司都会安装部署一套单点登录系统,方便维护管理员工的账号信息,同时实现多系统的用户登陆,例如Ldap,因此支持主流的单点登录系统也是很有必要的。
权限管理
对于一个工单系统来说,因为涉及的人会非常多,审批阶段会有各层次的领导进行审阅;处理阶段会有各部门的人进行处理。因此对于权限的管理是要非常的严格。
流程设计
在工单系统中流程设计应该算是最繁琐的地方了,要定义好每个节点的关联关系、处理人以及各种属性数据,如果没有一个好的流程设计工具,对于工单系统的用户体验来说,是非常差的。而且对于后续的维护也是非常的不易。
之前就有朋友说,他们的工单系统是写死的,每个流程,每个节点,每个处理人都是在代码里定义好的,这样的工单系统是非常不易用的,只要有人员变动,流程变动,都需要重新修改代码进行编译上线,因此好的流程设计工具,对一个好的工单系统来说,是至关重要的。
模版设计
我们在提交工单的时候,需要写很多的描述数据,例如申请服务器,就需要写服务器的配置信息,像是CPU、内存、磁盘等信息,这些需要写的描述数据,并非是一成不变的,会根据场景的不同而进行适当的修改,因此我们就需要有一个可视化的表单设计工具,来进行表单模版的维护管理,通过将表单模版与流程的关联从而实现一条完整的工作流。
多样化的工单列表
我们工单系统内的工单,根据工单归属的分类,可以大概的分为:我创建的工单、我待办的工单、我相关的工单以及所有工单。我们根据这4类工单归属类型进行数据的分类展示,方便他人的使用及查看。
任务管理
在一个工单流程中,通常很多操作都是可以自动完成的,比如申请数据库账号,可以让用户填写好数据后,DBA审批通过,则自动去创建账号,并进行邮件或者其他通讯工具进行通知。这种就能节省很多人为操作,使这个人可以处理更多其他的工作。毕竟20世纪最贵的成本是人力呢。
更多其他功能点
- 加签: 临时将工单转交给他人处理,处理完成后自动返回给原来的处理人。
- 会签: 当一个节点出现多个处理人的时候,则需要所有的处理人都处理完成才可进行下一步。
- 催办: 催促当前节点的处理人尽快处理,当然需要设置时间间隔的。
- 结单: 有的时候我们会提错单子,因为可联系管理员进行手动结单。
- 转交: 在流程中,某一节点的处理人临时有时间,商议后,可转交给他人。
- 主动接单: 当一个节点出现多个处理人的时候,配置主动接单后,需这几个人进行抢单处理。
- 等等,还有很多扩展的功能,有兴趣的人,可以加群一起聊聊:1127401830 。
以上就是大概总结的一些核心的功能,当然还有很多细小的划分。更精细的设计就需要大家去认真构思了。毕竟一篇文章如果说考虑到方方面面的话,那是不现实的,得需要码多少字呢。
数据库设计
其实开发一个系统,最耗费时间的不是敲代码的部分。程序设计,数据结构设计才是最繁琐的,要考虑到各种场景、各种关联。
所以这里就不藏私了,直接给大家看下我的数据结构设计:
用户/权限/菜单/系统配置
工单系统
核心功能代码
在此将核心功能中代码给大家展示一下,但是肯定不会全部的(因为实在是太多了,贴不完呢),有兴趣的可以去Github上Clone下来详细的研究一下。
Github: https://github.com/lanyulei/ferry
Gitee: https://gitee.com/yllan/ferry
Ldap登陆验证
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.
加上一段在公司经常听到英文作为结束语,如果能实现本文的这些功能,其实一个简单易用,灵活方便维护管理的工单系统就成型了。
在此给大家说声抱歉,无法将完整的代码贴在这里(因为实在是太多了),如果大家还有什么疑问的话,欢迎随时来我的博客留言。
本文为原创文章,未经授权禁止转载本站文章。
原文出处:兰玉磊的个人博客
原文链接:https://www.fdevops.com/2020/08/25/cmdb-3336
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。