feat: dbms新增支持工单流程审批

This commit is contained in:
meilin.huang
2024-02-29 22:12:50 +08:00
parent bf75483a3c
commit f93231da61
115 changed files with 3280 additions and 553 deletions

View File

@@ -30,7 +30,7 @@ require (
github.com/sijms/go-ora/v2 v2.8.9
github.com/stretchr/testify v1.8.4
go.mongodb.org/mongo-driver v1.14.0 // mongo
golang.org/x/crypto v0.19.0 // ssh
golang.org/x/crypto v0.20.0 // ssh
golang.org/x/oauth2 v0.17.0
golang.org/x/sync v0.6.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1

View File

@@ -14,6 +14,7 @@ import (
msgapp "mayfly-go/internal/msg/application"
msgdto "mayfly-go/internal/msg/application/dto"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
@@ -49,8 +50,15 @@ func (d *Db) Dbs(rc *req.Ctx) {
}
queryCond.Codes = codes
res, err := d.DbApp.GetPageList(queryCond, page, new([]vo.DbListVO))
var dbvos []*vo.DbListVO
res, err := d.DbApp.GetPageList(queryCond, page, &dbvos)
biz.ErrIsNil(err)
// 填充标签信息
d.TagApp.FillTagInfo(collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource {
return dbvo
})...)
rc.ResData = res
}
@@ -117,7 +125,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
s = stringx.TrimSpaceAndBr(s)
// 多条执行,暂不支持查询语句
if isMulti {
biz.IsTrue(!strings.HasPrefix(strings.ToLower(s), "select"), "多条语句执行暂不不支持select语句")
biz.IsTrue(!strings.HasPrefix(strings.ToLower(s[:10]), "select"), "多条语句执行暂不不支持select语句")
}
execReq.Sql = s
@@ -132,8 +140,10 @@ func (d *Db) ExecSql(rc *req.Ctx) {
}
colAndRes := make(map[string]any)
colAndRes["columns"] = execResAll.Columns
colAndRes["res"] = execResAll.Res
if execResAll != nil {
colAndRes["columns"] = execResAll.Columns
colAndRes["res"] = execResAll.Res
}
rc.ResData = colAndRes
}
@@ -161,6 +171,8 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
clientId := rc.Query("clientId")
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
// 开启流程审批时,执行文件暂时还未处理
biz.IsTrue(dbConn.Info.FlowProcdefKey == "", "该库已开启流程审批,暂不支持该操作")
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
rc.ReqParam = fmt.Sprintf("filename: %s -> %s", filename, dbConn.Info.GetLogDesc())

View File

@@ -5,6 +5,9 @@ import (
"mayfly-go/internal/db/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/conv"
"strings"
)
type DbSqlExec struct {
@@ -13,6 +16,12 @@ type DbSqlExec struct {
func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage(rc, new(entity.DbSqlExecQuery))
if statusStr := rc.Query("status"); statusStr != "" {
queryCond.Status = collx.ArrayMap[string, int8](strings.Split(statusStr, ","), func(val string) int8 {
return int8(conv.Str2Int(val, 0))
})
}
res, err := d.DbSqlExecApp.GetPageList(queryCond, page, new([]entity.DbSqlExec))
biz.ErrIsNil(err)
rc.ResData = res

View File

@@ -1,12 +1,13 @@
package form
type DbForm struct {
Id uint64 `json:"id"`
Name string `binding:"required" json:"name"`
Database string `json:"database"`
Remark string `json:"remark"`
TagId []uint64 `binding:"required" json:"tagId"`
InstanceId uint64 `binding:"required" json:"instanceId"`
Id uint64 `json:"id"`
Name string `binding:"required" json:"name"`
Database string `json:"database"`
Remark string `json:"remark"`
TagId []uint64 `binding:"required" json:"tagId"`
InstanceId uint64 `binding:"required" json:"instanceId"`
FlowProcdefKey string `json:"flowProcdefKey"`
}
type DbSqlSaveForm struct {

View File

@@ -1,8 +1,13 @@
package vo
import "time"
import (
tagentity "mayfly-go/internal/tag/domain/entity"
"time"
)
type DbListVO struct {
tagentity.ResourceTags
Id *int64 `json:"id"`
Code string `json:"code"`
Name *string `json:"name"`
@@ -16,6 +21,8 @@ type DbListVO struct {
Port int `json:"port"`
Username string `json:"username"`
FlowProcdefKey string `json:"flowProcdefKey"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
@@ -23,3 +30,7 @@ type DbListVO struct {
Modifier *string `json:"modifier"`
ModifierId *int64 `json:"modifierId"`
}
func (d DbListVO) GetCode() string {
return d.Code
}

View File

@@ -34,9 +34,14 @@ func Init() {
panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
}
GetDataSyncTaskApp().InitCronJob()
InitDbFlowHandler()
})()
}
func GetDbSqlExecApp() DbSqlExec {
return ioc.Get[DbSqlExec]("DbSqlExecApp")
}
func GetDbBackupApp() *DbBackupApp {
return ioc.Get[*DbBackupApp]("DbBackupApp")
}

View File

@@ -168,7 +168,12 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
if err := instance.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error())
}
return toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), nil
di := toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...)
if db.FlowProcdefKey != nil {
di.FlowProcdefKey = *db.FlowProcdefKey
}
return di, nil
})
}

View File

@@ -0,0 +1,11 @@
package application
import flowapp "mayfly-go/internal/flow/application"
const (
DbSqlExecFlowBizType = "db_sql_exec_flow" // db sql exec flow biz type
)
func InitDbFlowHandler() {
flowapp.RegisterBizHandler(DbSqlExecFlowBizType, GetDbSqlExecApp())
}

View File

@@ -7,11 +7,14 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
flowapp "mayfly-go/internal/flow/application"
flowentity "mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/jsonx"
"mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
@@ -47,6 +50,8 @@ func (d *DbSqlExecRes) Merge(execRes *DbSqlExecRes) {
}
type DbSqlExec interface {
flowapp.FlowBizHandler
// 执行sql
Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
@@ -58,7 +63,10 @@ type DbSqlExec interface {
}
type dbSqlExecAppImpl struct {
dbApp Db `inject:"DbApp"`
dbSqlExecRepo repository.DbSqlExec `inject:"DbSqlExecRepo"`
flowProcinstApp flowapp.Procinst `inject:"ProcinstApp"`
}
func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.DbSqlExec {
@@ -67,6 +75,7 @@ func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.
dbSqlExecRecord.Db = execSqlReq.Db
dbSqlExecRecord.Sql = execSqlReq.Sql
dbSqlExecRecord.Remark = execSqlReq.Remark
dbSqlExecRecord.Status = entity.DbSqlExecStatusSuccess
dbSqlExecRecord.FillBaseInfo(model.IdGenTypeNone, contextx.GetLoginAccount(ctx))
return dbSqlExecRecord
}
@@ -105,9 +114,9 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
}
var execErr error
if isSelect || strings.HasPrefix(lowerSql, "show") {
execRes, execErr = doRead(ctx, execSqlReq)
execRes, execErr = d.doRead(ctx, execSqlReq)
} else {
execRes, execErr = doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
execRes, execErr = d.doExec(ctx, execSqlReq, dbSqlExecRecord)
}
if execErr != nil {
return nil, execErr
@@ -119,29 +128,76 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
switch stmt := stmt.(type) {
case *sqlparser.Select:
isSelect = true
execRes, err = doSelect(ctx, stmt, execSqlReq)
execRes, err = d.doSelect(ctx, stmt, execSqlReq)
case *sqlparser.Show:
isSelect = true
execRes, err = doRead(ctx, execSqlReq)
execRes, err = d.doRead(ctx, execSqlReq)
case *sqlparser.OtherRead:
isSelect = true
execRes, err = doRead(ctx, execSqlReq)
execRes, err = d.doRead(ctx, execSqlReq)
case *sqlparser.Update:
execRes, err = doUpdate(ctx, stmt, execSqlReq, dbSqlExecRecord)
execRes, err = d.doUpdate(ctx, stmt, execSqlReq, dbSqlExecRecord)
case *sqlparser.Delete:
execRes, err = doDelete(ctx, stmt, execSqlReq, dbSqlExecRecord)
execRes, err = d.doDelete(ctx, stmt, execSqlReq, dbSqlExecRecord)
case *sqlparser.Insert:
execRes, err = doInsert(ctx, stmt, execSqlReq, dbSqlExecRecord)
execRes, err = d.doInsert(ctx, stmt, execSqlReq, dbSqlExecRecord)
default:
execRes, err = doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
execRes, err = d.doExec(ctx, execSqlReq, dbSqlExecRecord)
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
if err != nil {
return nil, err
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
return execRes, nil
}
func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, procinstStatus flowentity.ProcinstStatus, bizKey string) error {
logx.Debugf("DbSqlExec FlowBizHandle -> bizKey: %s, procinstStatus: %s", bizKey, flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
// 流程挂起不处理
if procinstStatus == flowentity.ProcinstSuspended {
return nil
}
dbSqlExec := &entity.DbSqlExec{FlowBizKey: bizKey}
if err := d.dbSqlExecRepo.GetBy(dbSqlExec); err != nil {
logx.Errorf("flow-[%s]关联的sql执行信息不存在", bizKey)
return nil
}
if procinstStatus != flowentity.ProcinstCompleted {
dbSqlExec.Status = entity.DbSqlExecStatusNo
dbSqlExec.Res = fmt.Sprintf("流程%s", flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
}
dbSqlExec.Status = entity.DbSqlExecStatusFail
dbConn, err := d.dbApp.GetDbConn(dbSqlExec.DbId, dbSqlExec.Db)
if err != nil {
dbSqlExec.Res = err.Error()
d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
return err
}
rowsAffected, err := dbConn.ExecContext(ctx, dbSqlExec.Sql)
if err != nil {
dbSqlExec.Res = err.Error()
d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
return err
}
dbSqlExec.Status = entity.DbSqlExecStatusSuccess
dbSqlExec.Res = fmt.Sprintf("执行成功,影响条数: %d", rowsAffected)
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
}
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) {
d.dbSqlExecRepo.DeleteByCond(ctx, condition)
}
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 保存sql执行记录如果是查询类则根据系统配置判断是否保存
func (d *dbSqlExecAppImpl) saveSqlExecLog(isQuery bool, dbSqlExecRecord *entity.DbSqlExec) {
if !isQuery {
@@ -156,15 +212,7 @@ func (d *dbSqlExecAppImpl) saveSqlExecLog(isQuery bool, dbSqlExecRecord *entity.
}
}
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) {
d.dbSqlExecRepo.DeleteByCond(ctx, condition)
}
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
selectExprsStr := sqlparser.String(selectStmt.SelectExprs)
if selectExprsStr == "*" || strings.Contains(selectExprsStr, ".*") ||
len(strings.Split(selectExprsStr, ",")) > 1 {
@@ -189,10 +237,10 @@ func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbS
}
}
return doRead(ctx, execSqlReq)
return d.doRead(ctx, execSqlReq)
}
func doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
dbConn := execSqlReq.DbConn
sql := execSqlReq.Sql
cols, res, err := dbConn.QueryContext(ctx, sql)
@@ -205,7 +253,7 @@ func doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error
}, nil
}
func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
dbConn := execSqlReq.DbConn
tableStr := sqlparser.String(update.TableExprs)
@@ -256,10 +304,10 @@ func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlEx
dbSqlExec.Table = tableName
dbSqlExec.Type = entity.DbSqlExecTypeUpdate
return doExec(ctx, execSqlReq.Sql, dbConn)
return d.doExec(ctx, execSqlReq, dbSqlExec)
}
func doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
dbConn := execSqlReq.DbConn
tableStr := sqlparser.String(delete.TableExprs)
@@ -278,24 +326,47 @@ func doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlEx
dbSqlExec.Table = table
dbSqlExec.Type = entity.DbSqlExecTypeDelete
return doExec(ctx, execSqlReq.Sql, dbConn)
return d.doExec(ctx, execSqlReq, dbSqlExec)
}
func doInsert(ctx context.Context, insert *sqlparser.Insert, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, insert *sqlparser.Insert, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
tableStr := sqlparser.String(insert.Table)
// 可能使用别名,故空格切割
table := strings.Split(tableStr, " ")[0]
dbSqlExec.Table = table
dbSqlExec.Type = entity.DbSqlExecTypeInsert
return doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
return d.doExec(ctx, execSqlReq, dbSqlExec)
}
func doExec(ctx context.Context, sql string, dbConn *dbi.DbConn) (*DbSqlExecRes, error) {
func (d *dbSqlExecAppImpl) doExec(ctx context.Context, execSqlReq *DbSqlExecReq, dbSqlExecRecord *entity.DbSqlExec) (*DbSqlExecRes, error) {
dbConn := execSqlReq.DbConn
flowProcdefKey := dbConn.Info.FlowProcdefKey
if flowProcdefKey != "" {
bizKey := stringx.Rand(24)
// 如果该库关联了审批流程,则启动流程实例即可
_, err := d.flowProcinstApp.StartProc(ctx, flowProcdefKey, &flowapp.StarProcParam{
BizType: DbSqlExecFlowBizType,
BizKey: bizKey,
Remark: dbSqlExecRecord.Remark,
})
if err != nil {
return nil, err
}
dbSqlExecRecord.FlowBizKey = bizKey
dbSqlExecRecord.Status = entity.DbSqlExecStatusWait
return nil, nil
}
sql := execSqlReq.Sql
rowsAffected, err := dbConn.ExecContext(ctx, sql)
execRes := "success"
if err != nil {
execRes = err.Error()
dbSqlExecRecord.Status = entity.DbSqlExecStatusFail
dbSqlExecRecord.Res = err.Error()
} else {
dbSqlExecRecord.Res = fmt.Sprintf("执行成功,影响条数: %d", rowsAffected)
}
res := make([]map[string]any, 0)
resData := make(map[string]any)

View File

@@ -23,6 +23,7 @@ type DbInfo struct {
Params string
Database string
FlowProcdefKey string // 流程定义key
TagPath []string
SshTunnelMachineId int

View File

@@ -7,9 +7,10 @@ import (
type Db struct {
model.Model
Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Database string `orm:"column(database)" json:"database"`
Remark string `json:"remark"`
InstanceId uint64
Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Database string `orm:"column(database)" json:"database"`
Remark string `json:"remark"`
InstanceId uint64
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}

View File

@@ -10,17 +10,17 @@ import (
type DbInstance struct {
model.Model
Name string `json:"name"`
Type string `json:"type"` // 类型mysql oracle等
Host string `json:"host"`
Port int `json:"port"`
Network string `json:"network"`
Sid string `json:"sid"`
Username string `json:"username"`
Password string `json:"-"`
Params string `json:"params"`
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `json:"name"`
Type string `json:"type"` // 类型mysql oracle等
Host string `json:"host"`
Port int `json:"port"`
Network string `json:"network"`
Sid string `json:"sid"`
Username string `json:"username"`
Password string `json:"-"`
Params *string `json:"params"`
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
}
func (d *DbInstance) TableName() string {

View File

@@ -1,6 +1,8 @@
package entity
import "mayfly-go/pkg/model"
import (
"mayfly-go/pkg/model"
)
// 数据库sql执行记录
type DbSqlExec struct {
@@ -13,6 +15,10 @@ type DbSqlExec struct {
Sql string `json:"sql"` // 执行的sql
OldValue string `json:"oldValue"`
Remark string `json:"remark"`
Status int8 `json:"status"` // 执行状态
Res string `json:"res"` // 执行结果
FlowBizKey string `json:"flowBizKey"` // 流程业务key
}
const (
@@ -21,4 +27,9 @@ const (
DbSqlExecTypeDelete int8 = 2 // 删除类型
DbSqlExecTypeInsert int8 = 3 // 插入类型
DbSqlExecTypeQuery int8 = 4 // 查询类型如select、show等
DbSqlExecStatusWait = 1
DbSqlExecStatusSuccess = 2
DbSqlExecStatusNo = -1 // 不执行
DbSqlExecStatusFail = -2
)

View File

@@ -31,12 +31,14 @@ type DbQuery struct {
}
type DbSqlExecQuery struct {
Id uint64 `json:"id" form:"id"`
DbId uint64 `json:"dbId" form:"dbId"`
Db string `json:"db" form:"db"`
Table string `json:"table" form:"table"`
Type int8 `json:"type" form:"type"` // 类型
Id uint64 `json:"id" form:"id"`
DbId uint64 `json:"dbId" form:"dbId"`
Db string `json:"db" form:"db"`
Table string `json:"table" form:"table"`
Type int8 `json:"type" form:"type"` // 类型
FlowBizKey string `json:"flowBizKey" form:"flowBizKey"`
Status []int8
CreatorId uint64
}

View File

@@ -23,6 +23,8 @@ func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pagePa
Eq("`table`", condition.Table).
Eq("type", condition.Type).
Eq("creator_id", condition.CreatorId).
Eq("flow_biz_key", condition.FlowBizKey).
In("status", condition.Status).
RLike("db", condition.Db).WithOrderBy(orderBy...)
return gormx.PageQuery(qd, pageParam, toEntity)
}

View File

@@ -10,7 +10,7 @@ import (
)
func InitDbSqlExecRouter(router *gin.RouterGroup) {
db := router.Group("/dbs/:dbId/sql-execs")
db := router.Group("/dbs/sql-execs")
d := new(api.DbSqlExec)
biz.ErrIsNil(ioc.Inject(d))

View File

@@ -0,0 +1,12 @@
package form
import "mayfly-go/internal/flow/domain/entity"
type Procdef struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"` // 名称
DefKey string `json:"defKey" binding:"required"`
Tasks string `json:"tasks" binding:"required"` // 审批节点任务信息
Status entity.ProcdefStatus `json:"status" binding:"required"`
Remark string `json:"remark"`
}

View File

@@ -0,0 +1,6 @@
package form
type ProcinstTaskAudit struct {
Id uint64 `json:"id" binding:"required"`
Remark string `json:"remark"`
}

View File

@@ -0,0 +1,50 @@
package api
import (
"mayfly-go/internal/flow/api/form"
"mayfly-go/internal/flow/application"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"strconv"
"strings"
)
type Procdef struct {
ProcdefApp application.Procdef `inject:""`
}
func (p *Procdef) GetProcdefPage(rc *req.Ctx) {
cond, page := req.BindQueryAndPage(rc, new(entity.Procdef))
res, err := p.ProcdefApp.GetPageList(cond, page, new([]entity.Procdef))
biz.ErrIsNil(err)
rc.ResData = res
}
func (p *Procdef) GetProcdef(rc *req.Ctx) {
defkey := rc.PathParam("key")
biz.NotEmpty(defkey, "流程定义key不能为空")
procdef := &entity.Procdef{DefKey: defkey}
biz.ErrIsNil(p.ProcdefApp.GetBy(procdef), "该流程定义不存在")
rc.ResData = procdef
}
func (a *Procdef) Save(rc *req.Ctx) {
form := &form.Procdef{}
procdef := req.BindJsonAndCopyTo(rc, form, new(entity.Procdef))
rc.ReqParam = form
biz.ErrIsNil(a.ProcdefApp.Save(rc.MetaCtx, procdef))
}
func (p *Procdef) Delete(rc *req.Ctx) {
idsStr := rc.PathParam("id")
rc.ReqParam = idsStr
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
biz.ErrIsNilAppendErr(p.ProcdefApp.DeleteProcdef(rc.MetaCtx, uint64(value)), "删除失败:%s")
}
}

View File

@@ -0,0 +1,99 @@
package api
import (
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/flow/api/form"
"mayfly-go/internal/flow/api/vo"
"mayfly-go/internal/flow/application"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
)
type Procinst struct {
ProcinstApp application.Procinst `inject:""`
ProcdefApp application.Procdef `inject:""`
ProcinstTaskRepo repository.ProcinstTask `inject:""`
}
func (p *Procinst) GetProcinstPage(rc *req.Ctx) {
cond, page := req.BindQueryAndPage(rc, new(entity.ProcinstQuery))
// 非管理员只能获取自己申请的流程
if laId := rc.GetLoginAccount().Id; laId != consts.AdminId {
cond.CreatorId = laId
}
res, err := p.ProcinstApp.GetPageList(cond, page, new([]entity.Procinst))
biz.ErrIsNil(err)
rc.ResData = res
}
func (p *Procinst) ProcinstCancel(rc *req.Ctx) {
instId := uint64(rc.PathParamInt("id"))
rc.ReqParam = instId
biz.ErrIsNil(p.ProcinstApp.CancelProc(rc.MetaCtx, instId))
}
func (p *Procinst) GetProcinstDetail(rc *req.Ctx) {
pi, err := p.ProcinstApp.GetById(new(entity.Procinst), uint64(rc.PathParamInt("id")))
biz.ErrIsNil(err, "流程实例不存在")
pivo := new(vo.ProcinstVO)
structx.Copy(pivo, pi)
// 流程定义信息
procdef, _ := p.ProcdefApp.GetById(new(entity.Procdef), pi.ProcdefId)
pivo.Procdef = procdef
// 流程实例任务信息
instTasks := new([]*entity.ProcinstTask)
biz.ErrIsNil(p.ProcinstTaskRepo.ListByCond(&entity.ProcinstTask{ProcinstId: pi.Id}, instTasks))
pivo.ProcinstTasks = *instTasks
rc.ResData = pivo
}
func (p *Procinst) GetTasks(rc *req.Ctx) {
instTaskQuery, page := req.BindQueryAndPage(rc, new(entity.ProcinstTaskQuery))
if laId := rc.GetLoginAccount().Id; laId != consts.AdminId {
// 赋值操作人为当前登录账号
instTaskQuery.Assignee = fmt.Sprintf("%d", rc.GetLoginAccount().Id)
}
taskVos := new([]*vo.ProcinstTask)
res, err := p.ProcinstApp.GetProcinstTasks(instTaskQuery, page, taskVos)
biz.ErrIsNil(err)
instIds := collx.ArrayMap[*vo.ProcinstTask, uint64](*taskVos, func(val *vo.ProcinstTask) uint64 { return val.ProcinstId })
insts := new([]*entity.Procinst)
p.ProcinstApp.GetByIdIn(insts, instIds)
instId2Inst := collx.ArrayToMap[*entity.Procinst, uint64](*insts, func(val *entity.Procinst) uint64 { return val.Id })
// 赋值任务对应的流程实例
for _, task := range *taskVos {
task.Procinst = instId2Inst[task.ProcinstId]
}
rc.ResData = res
}
func (p *Procinst) CompleteTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
rc.ReqParam = auditForm
biz.ErrIsNil(p.ProcinstApp.CompleteTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
}
func (p *Procinst) RejectTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
rc.ReqParam = auditForm
biz.ErrIsNil(p.ProcinstApp.RejectTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
}
func (p *Procinst) BackTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
rc.ReqParam = auditForm
biz.ErrIsNil(p.ProcinstApp.BackTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
}

View File

@@ -0,0 +1,45 @@
package vo
import (
"mayfly-go/internal/flow/domain/entity"
"time"
)
type ProcinstVO struct {
Id uint64 `json:"id"`
ProcdefId uint64 `json:"procdefId"` // 流程定义id
ProcdefName string `json:"procdefName"` // 流程定义名称
BizType string `json:"bizType"` // 业务类型
BizKey string `json:"bizKey"` // 业务key
BizStatus int8 `json:"bizStatus"` // 业务状态
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
TaskKey string `json:"taskKey"` // 当前任务key
Remark string `json:"remark"`
Status int8 `json:"status"`
EndTime *time.Time `json:"endTime"`
Duration int64 `json:"duration"` // 持续时间(开始到结束)
Creator string `json:"creator"`
CreateTime *time.Time `json:"createTime"`
UpdateTime *time.Time `json:"updateTime"`
Procdef *entity.Procdef `json:"procdef"`
ProcinstTasks []*entity.ProcinstTask `json:"procinstTasks"`
}
type ProcinstTask struct {
Id uint64 `json:"id"`
ProcinstId uint64 `json:"procinstId"` // 流程实例id
TaskKey string `json:"taskKey"` // 当前任务key
TaskName string `json:"taskName"` // 当前任务名称
Assignee string `json:"assignee"` // 分配到该任务的用户
Status entity.ProcinstTaskStatus `json:"status"` // 状态
Remark string `json:"remark"`
Duration int64 `json:"duration"` // 持续时间(开始到结束)
CreateTime *time.Time `json:"createTime"`
EndTime *time.Time `json:"endTime"`
Procinst *entity.Procinst `json:"procinst"`
}

View File

@@ -0,0 +1,13 @@
package application
import (
"mayfly-go/internal/flow/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(procdefAppImpl), ioc.WithComponentName("ProcdefApp"))
ioc.Register(new(procinstAppImpl), ioc.WithComponentName("ProcinstApp"))
}

View File

@@ -0,0 +1,42 @@
package application
import (
"context"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
)
// 流程业务处理函数(流程结束后会根据流程业务类型获取该函数进行处理)
// @param procinstStatus 流程实例状态
// @param bizKey 业务key可为业务数据对应的主键
// type FlowBizHandlerFunc func(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
// 业务流程处理器(流程状态变更后会根据流程业务类型获取对应的处理器进行回调处理)
type FlowBizHandler interface {
// 业务流程处理函数
// @param procinstStatus 流程实例状态
// @param bizKey 业务key可为业务数据对应的主键
FlowBizHandle(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
}
var (
handlers map[string]FlowBizHandler = make(map[string]FlowBizHandler, 0)
)
// 注册流程业务处理函数
func RegisterBizHandler(flowBizType string, handler FlowBizHandler) {
logx.Infof("flow register biz handelr: bizType=%s", flowBizType)
handlers[flowBizType] = handler
}
// 流程业务处理
func FlowBizHandle(ctx context.Context, flowBizType string, bizKey string, procinstStatus entity.ProcinstStatus) error {
if handler, ok := handlers[flowBizType]; !ok {
logx.Warnf("flow biz handler not found: bizType=%s", flowBizType)
return errorx.NewBiz("业务流程处理器不存在")
} else {
return handler.FlowBizHandle(ctx, procinstStatus, bizKey)
}
}

View File

@@ -0,0 +1,13 @@
package application
// 启动流程实例请求入参
type StarProcParam struct {
BizType string // 业务类型
BizKey string // 业务key
Remark string // 备注
}
type CompleteProcinstTaskParam struct {
TaskId uint64
Remark string // 备注
}

View File

@@ -0,0 +1,75 @@
package application
import (
"context"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
)
type Procdef interface {
base.App[*entity.Procdef]
GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
// 保存流程实例信息
Save(ctx context.Context, def *entity.Procdef) error
// 删除流程实例信息
DeleteProcdef(ctx context.Context, defId uint64) error
}
type procdefAppImpl struct {
base.AppImpl[*entity.Procdef, repository.Procdef]
procinstApp Procinst `inject:"ProcinstApp"`
}
// 注入repo
func (p *procdefAppImpl) InjectProcdefRepo(procdefRepo repository.Procdef) {
p.Repo = procdefRepo
}
func (p *procdefAppImpl) GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return p.Repo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *procdefAppImpl) Save(ctx context.Context, def *entity.Procdef) error {
if err := entity.ProcdefStatusEnum.Valid(def.Status); err != nil {
return err
}
if def.Id == 0 {
if p.GetBy(&entity.Procdef{DefKey: def.DefKey}) == nil {
return errorx.NewBiz("该流程实例key已存在")
}
return p.Insert(ctx, def)
}
// 防止误修改key
def.DefKey = ""
if err := p.canModify(def.Id); err != nil {
return err
}
return p.UpdateById(ctx, def)
}
func (p *procdefAppImpl) DeleteProcdef(ctx context.Context, defId uint64) error {
if err := p.canModify(defId); err != nil {
return err
}
return p.DeleteById(ctx, defId)
}
// 判断该流程实例是否可以执行修改操作
func (p *procdefAppImpl) canModify(prodefId uint64) error {
if activeInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstActive}); activeInstCount > 0 {
return errorx.NewBiz("存在运行中的流程实例,无法操作")
}
if suspInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstSuspended}); suspInstCount > 0 {
return errorx.NewBiz("存在挂起中的流程实例,无法操作")
}
return nil
}

View File

@@ -0,0 +1,295 @@
package application
import (
"context"
"fmt"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
)
type Procinst interface {
base.App[*entity.Procinst]
GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
// 获取流程实例审批节点任务
GetProcinstTasks(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
// 根据流程定义key启动一个流程实例
StartProc(ctx context.Context, procdefKey string, reqParam *StarProcParam) (*entity.Procinst, error)
// 取消流程
CancelProc(ctx context.Context, procinstId uint64) error
// 完成任务
CompleteTask(ctx context.Context, taskId uint64, remark string) error
// 拒绝任务
RejectTask(ctx context.Context, taskId uint64, remark string) error
// 驳回任务(允许重新提交)
BackTask(ctx context.Context, taskId uint64, remark string) error
}
type procinstAppImpl struct {
base.AppImpl[*entity.Procinst, repository.Procinst]
procinstTaskRepo repository.ProcinstTask `inject:"ProcinstTaskRepo"`
procdefApp Procdef `inject:"ProcdefApp"`
}
// 注入repo
func (p *procinstAppImpl) InjectProcinstRepo(procinstRepo repository.Procinst) {
p.Repo = procinstRepo
}
func (p *procinstAppImpl) GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return p.Repo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *procinstAppImpl) GetProcinstTasks(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return p.procinstTaskRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *procinstAppImpl) StartProc(ctx context.Context, procdefKey string, reqParam *StarProcParam) (*entity.Procinst, error) {
procdef := &entity.Procdef{DefKey: procdefKey}
if err := p.procdefApp.GetBy(procdef); err != nil {
return nil, errorx.NewBiz("流程实例[%s]不存在", procdefKey)
}
if procdef.Status != entity.ProcdefStatusEnable {
return nil, errorx.NewBiz("该流程定义非启用状态")
}
procinst := &entity.Procinst{
BizType: reqParam.BizType,
BizKey: reqParam.BizKey,
BizStatus: entity.ProcinstBizStatusWait,
ProcdefId: procdef.Id,
ProcdefName: procdef.Name,
Remark: reqParam.Remark,
Status: entity.ProcinstActive,
}
task := p.getNextTask(procdef, "")
procinst.TaskKey = task.TaskKey
if err := p.Save(ctx, procinst); err != nil {
return nil, err
}
return procinst, p.createProcinstTask(ctx, procinst, task)
}
func (p *procinstAppImpl) CancelProc(ctx context.Context, procinstId uint64) error {
procinst, err := p.GetById(new(entity.Procinst), procinstId)
if err != nil {
return errorx.NewBiz("流程不存在")
}
la := contextx.GetLoginAccount(ctx)
if la == nil {
return errorx.NewBiz("未登录")
}
if procinst.CreatorId != la.Id {
return errorx.NewBiz("只能取消自己创建的流程")
}
procinst.Status = entity.ProcinstCancelled
procinst.BizStatus = entity.ProcinstBizStatusNo
procinst.SetEnd()
return p.Tx(ctx, func(ctx context.Context) error {
return p.cancelInstTasks(ctx, procinstId, "流程已取消")
}, func(ctx context.Context) error {
return p.Save(ctx, procinst)
}, func(ctx context.Context) error {
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
})
}
func (p *procinstAppImpl) CompleteTask(ctx context.Context, instTaskId uint64, remark string) error {
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
if err != nil {
return err
}
// 赋值状态和备注
instTask.Status = entity.ProcinstTaskStatusPass
instTask.Remark = remark
instTask.SetEnd()
procinst := new(entity.Procinst)
p.GetById(procinst, instTask.ProcinstId)
procdef := new(entity.Procdef)
p.procdefApp.GetById(procdef, procinst.ProcdefId)
// 获取下一实例审批任务
task := p.getNextTask(procdef, instTask.TaskKey)
if task == nil {
procinst.Status = entity.ProcinstCompleted
procinst.SetEnd()
} else {
procinst.TaskKey = task.TaskKey
}
return p.Tx(ctx, func(ctx context.Context) error {
return p.UpdateById(ctx, procinst)
}, func(ctx context.Context) error {
return p.procinstTaskRepo.UpdateById(ctx, instTask)
}, func(ctx context.Context) error {
return p.createProcinstTask(ctx, procinst, task)
}, func(ctx context.Context) error {
// 下一审批节点任务不存在,说明该流程已结束
if task == nil {
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
}
return nil
})
}
func (p *procinstAppImpl) RejectTask(ctx context.Context, instTaskId uint64, remark string) error {
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
if err != nil {
return err
}
// 赋值状态和备注
instTask.Status = entity.ProcinstTaskStatusReject
instTask.Remark = remark
instTask.SetEnd()
procinst := new(entity.Procinst)
p.GetById(procinst, instTask.ProcinstId)
// 更新流程实例为终止状态,无法重新提交
procinst.Status = entity.ProcinstTerminated
procinst.BizStatus = entity.ProcinstBizStatusNo
procinst.SetEnd()
return p.Tx(ctx, func(ctx context.Context) error {
return p.UpdateById(ctx, procinst)
}, func(ctx context.Context) error {
return p.procinstTaskRepo.UpdateById(ctx, instTask)
}, func(ctx context.Context) error {
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
})
}
func (p *procinstAppImpl) BackTask(ctx context.Context, instTaskId uint64, remark string) error {
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
if err != nil {
return err
}
// 赋值状态和备注
instTask.Status = entity.ProcinstTaskStatusBack
instTask.Remark = remark
procinst := new(entity.Procinst)
p.GetById(procinst, instTask.ProcinstId)
// 更新流程实例为挂起状态,等待重新提交
procinst.Status = entity.ProcinstSuspended
return p.Tx(ctx, func(ctx context.Context) error {
return p.UpdateById(ctx, procinst)
}, func(ctx context.Context) error {
return p.procinstTaskRepo.UpdateById(ctx, instTask)
}, func(ctx context.Context) error {
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
})
}
// 取消处理中的流程实例任务
func (p *procinstAppImpl) cancelInstTasks(ctx context.Context, procinstId uint64, cancelReason string) error {
// 流程实例任务信息
instTasks := new([]*entity.ProcinstTask)
p.procinstTaskRepo.ListByCond(&entity.ProcinstTask{ProcinstId: procinstId, Status: entity.ProcinstTaskStatusProcess}, instTasks)
for _, instTask := range *instTasks {
instTask.Status = entity.ProcinstTaskStatusCanceled
instTask.Remark = cancelReason
instTask.SetEnd()
p.procinstTaskRepo.UpdateById(ctx, instTask)
}
return nil
}
// 触发流程实例状态改变事件
func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context, procinst *entity.Procinst) error {
err := FlowBizHandle(ctx, procinst.BizType, procinst.BizKey, procinst.Status)
if err != nil {
// 业务处理错误,非完成状态则终止流程
if procinst.Status != entity.ProcinstCompleted {
procinst.Status = entity.ProcinstTerminated
procinst.SetEnd()
p.cancelInstTasks(ctx, procinst.Id, "业务处理失败")
}
procinst.BizStatus = entity.ProcinstBizStatusFail
procinst.BizHandleRes = err.Error()
return p.UpdateById(ctx, procinst)
}
// 处理成功,并且状态为完成,则更新业务状态为成功
if procinst.Status == entity.ProcinstCompleted {
procinst.BizStatus = entity.ProcinstBizStatusSuccess
procinst.BizHandleRes = "success"
return p.UpdateById(ctx, procinst)
}
return err
}
// 获取并校验实例任务
func (p *procinstAppImpl) getAndValidInstTask(ctx context.Context, instTaskId uint64) (*entity.ProcinstTask, error) {
instTask := new(entity.ProcinstTask)
if err := p.procinstTaskRepo.GetById(instTask, instTaskId); err != nil {
return nil, errorx.NewBiz("流程实例任务不存在")
}
la := contextx.GetLoginAccount(ctx)
if instTask.Assignee != fmt.Sprintf("%d", la.Id) {
return nil, errorx.NewBiz("当前用户不是任务处理人,无法完成任务")
}
return instTask, nil
}
// 创建流程实例节点任务
func (p *procinstAppImpl) createProcinstTask(ctx context.Context, procinst *entity.Procinst, task *entity.ProcdefTask) error {
if task == nil {
return nil
}
procinstTask := &entity.ProcinstTask{
ProcinstId: procinst.Id,
Status: entity.ProcinstTaskStatusProcess,
TaskKey: task.TaskKey,
TaskName: task.Name,
Assignee: task.UserId,
}
return p.procinstTaskRepo.Insert(ctx, procinstTask)
}
// 获取下一审批节点任务
func (p *procinstAppImpl) getNextTask(procdef *entity.Procdef, nowTaskKey string) *entity.ProcdefTask {
tasks := procdef.GetTasks()
if len(tasks) == 0 {
return nil
}
if nowTaskKey == "" {
// nowTaskKey为空则说明为刚启动该流程实例
return tasks[0]
}
for index, t := range tasks {
if (t.TaskKey == nowTaskKey) && (index < len(tasks)-1) {
return tasks[index+1]
}
}
return nil
}

View File

@@ -0,0 +1,52 @@
package entity
import (
"encoding/json"
"mayfly-go/pkg/enumx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
)
// 流程定义信息
type Procdef struct {
model.Model
Name string `json:"name" form:"name"` // 名称
DefKey string `json:"defKey" form:"defKey"` //
Tasks string `json:"tasks"` // 审批节点任务信息
Status ProcdefStatus `json:"status"` // 状态
Remark string `json:"remark"`
}
func (p *Procdef) TableName() string {
return "t_flow_procdef"
}
// 获取审批节点任务列表
func (p *Procdef) GetTasks() []*ProcdefTask {
var tasks []*ProcdefTask
err := json.Unmarshal([]byte(p.Tasks), &tasks)
if err != nil {
logx.ErrorTrace("解析procdef tasks失败", err)
return tasks
}
return tasks
}
type ProcdefTask struct {
Name string `json:"name" form:"name"` // 审批节点任务名称
TaskKey string `json:"taskKey" form:"taskKey"` // 任务key
UserId string `json:"userId"` // 审批人
}
type ProcdefStatus int8
const (
ProcdefStatusEnable ProcdefStatus = 1
ProcdefStatusDisable ProcdefStatus = -1
)
var ProcdefStatusEnum = enumx.NewEnum[ProcdefStatus]("流程定义状态").
Add(ProcdefStatusEnable, "启用").
Add(ProcdefStatusDisable, "禁用")

View File

@@ -0,0 +1,100 @@
package entity
import (
"mayfly-go/pkg/enumx"
"mayfly-go/pkg/model"
"time"
)
// 流程实例信息 -> 根据流程定义信息启动一个流程实例
type Procinst struct {
model.Model
ProcdefId uint64 `json:"procdefId"` // 流程定义id
ProcdefName string `json:"procdefName"` // 流程定义名称
BizType string `json:"bizType"` // 业务类型
BizKey string `json:"bizKey"` // 业务key
BizStatus ProcinstBizStatus `json:"bizStatus"` // 业务状态
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
TaskKey string `json:"taskKey"` // 当前任务key
Status ProcinstStatus `json:"status"` // 状态
Remark string `json:"remark"`
EndTime *time.Time `json:"endTime"`
Duration int64 `json:"duration"` // 持续时间(开始到结束)
}
func (a *Procinst) TableName() string {
return "t_flow_procinst"
}
// 设置流程终止结束的一些信息
func (a *Procinst) SetEnd() {
nowTime := time.Now()
a.EndTime = &nowTime
a.Duration = int64(time.Since(*a.CreateTime).Seconds())
}
type ProcinstStatus int8
const (
ProcinstActive ProcinstStatus = 1 // 流程实例正在执行中,当前有活动任务等待执行或者正在运行的流程节点
ProcinstCompleted ProcinstStatus = 2 // 流程实例已经成功执行完成,没有剩余任务或者等待事件
ProcinstSuspended ProcinstStatus = -1 // 流程实例被挂起,暂停执行,可能被驳回等待修改重新提交
ProcinstTerminated ProcinstStatus = -2 // 流程实例被终止,可能是由于某种原因如被拒绝等导致流程无法正常执行
ProcinstCancelled ProcinstStatus = -3 // 流程实例被取消,通常是用户手动操作取消了流程的执行
)
var ProcinstStatusEnum = enumx.NewEnum[ProcinstStatus]("流程状态").
Add(ProcinstActive, "执行中").
Add(ProcinstCompleted, "完成").
Add(ProcinstSuspended, "挂起").
Add(ProcinstTerminated, "终止").
Add(ProcinstCancelled, "取消")
type ProcinstBizStatus int8
const (
ProcinstBizStatusWait ProcinstBizStatus = 1 // 待处理
ProcinstBizStatusSuccess ProcinstBizStatus = 2 // 成功
ProcinstBizStatusNo ProcinstBizStatus = -1 // 不处理
ProcinstBizStatusFail ProcinstBizStatus = -2 // 失败
)
//----------流程实例关联任务-----------
// 流程实例关联的审批节点任务
type ProcinstTask struct {
model.Model
ProcinstId uint64 `json:"procinstId"` // 流程实例id
TaskKey string `json:"taskKey"` // 当前任务key
TaskName string `json:"taskName"` // 当前任务名称
Assignee string `json:"assignee"` // 分配到该任务的用户
Status ProcinstTaskStatus `json:"status"` // 状态
Remark string `json:"remark"`
EndTime *time.Time `json:"endTime"`
Duration int64 `json:"duration"` // 持续时间(开始到结束)
}
func (a *ProcinstTask) TableName() string {
return "t_flow_procinst_task"
}
// 设置流程任务终止结束的一些信息
func (p *ProcinstTask) SetEnd() {
nowTime := time.Now()
p.EndTime = &nowTime
p.Duration = int64(time.Since(*p.CreateTime).Seconds())
}
type ProcinstTaskStatus int8
const (
ProcinstTaskStatusProcess ProcinstTaskStatus = 1 // 处理中
ProcinstTaskStatusPass ProcinstTaskStatus = 2 // 通过
ProcinstTaskStatusReject ProcinstTaskStatus = -1 // 拒绝
ProcinstTaskStatusBack ProcinstTaskStatus = -2 // 驳回
ProcinstTaskStatusCanceled ProcinstTaskStatus = -3 // 取消
)

View File

@@ -0,0 +1,20 @@
package entity
type ProcinstQuery struct {
ProcdefId uint64 `json:"procdefId" form:"procdefId"` // 流程定义id
ProcdefName string `json:"procdefName"` // 流程定义名称
BizType string `json:"bizType" form:"bizType"` // 业务类型
BizKey string `json:"bizKey"` // 业务key
Status ProcinstStatus `json:"status" form:"status"` // 状态
CreatorId uint64
}
type ProcinstTaskQuery struct {
ProcinstId uint64 `json:"procinstId"` // 流程实例id
ProcinstName string `json:"procinstName"` // 流程实例名称
BizType string `json:"bizType" form:"bizType"`
Assignee string `json:"assignee"` // 分配到该任务的用户
Status ProcinstTaskStatus `json:"status" form:"status"` // 状态
}

View File

@@ -0,0 +1,13 @@
package repository
import (
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/model"
)
type Procdef interface {
base.Repo[*entity.Procdef]
GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
}

View File

@@ -0,0 +1,19 @@
package repository
import (
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/model"
)
type Procinst interface {
base.Repo[*entity.Procinst]
GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
}
type ProcinstTask interface {
base.Repo[*entity.ProcinstTask]
GetPageList(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
}

View File

@@ -0,0 +1,11 @@
package persistence
import (
"mayfly-go/pkg/ioc"
)
func Init() {
ioc.Register(newProcdefRepo(), ioc.WithComponentName("ProcdefRepo"))
ioc.Register(newProcinstRepo(), ioc.WithComponentName("ProcinstRepo"))
ioc.Register(newProcinstTaskRepo(), ioc.WithComponentName("ProcinstTaskRepo"))
}

View File

@@ -0,0 +1,24 @@
package persistence
import (
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/model"
)
type procdefImpl struct {
base.RepoImpl[*entity.Procdef]
}
func newProcdefRepo() repository.Procdef {
return &procdefImpl{base.RepoImpl[*entity.Procdef]{M: new(entity.Procdef)}}
}
func (p *procdefImpl) GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Procdef)).
Like("name", condition.Name).
Like("def_key", condition.DefKey)
return gormx.PageQuery(qd, pageParam, toEntity)
}

View File

@@ -0,0 +1,37 @@
package persistence
import (
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/model"
)
type procinstImpl struct {
base.RepoImpl[*entity.Procinst]
}
func newProcinstRepo() repository.Procinst {
return &procinstImpl{base.RepoImpl[*entity.Procinst]{M: new(entity.Procinst)}}
}
func (p *procinstImpl) GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Procinst)).WithCondModel(condition)
return gormx.PageQuery(qd, pageParam, toEntity)
}
//-----------procinst task--------------
type procinstTaskImpl struct {
base.RepoImpl[*entity.ProcinstTask]
}
func newProcinstTaskRepo() repository.ProcinstTask {
return &procinstTaskImpl{base.RepoImpl[*entity.ProcinstTask]{M: new(entity.ProcinstTask)}}
}
func (p *procinstTaskImpl) GetPageList(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.ProcinstTask)).WithCondModel(condition)
return gormx.PageQuery(qd, pageParam, toEntity)
}

View File

@@ -0,0 +1,12 @@
package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/flow/application"
"mayfly-go/internal/flow/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -0,0 +1,30 @@
package router
import (
"mayfly-go/internal/flow/api"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
func InitProcdefouter(router *gin.RouterGroup) {
p := new(api.Procdef)
biz.ErrIsNil(ioc.Inject(p))
reqGroup := router.Group("/flow/procdefs")
{
reqs := [...]*req.Conf{
req.NewGet("", p.GetProcdefPage),
req.NewGet("/:key", p.GetProcdef),
req.NewPost("", p.Save).Log(req.NewLogSave("流程定义-保存")).RequiredPermissionCode("flow:procdef:save"),
req.NewDelete(":id", p.Delete).Log(req.NewLogSave("流程定义-删除")).RequiredPermissionCode("flow:procdef:del"),
}
req.BatchSetGroup(reqGroup, reqs[:])
}
}

View File

@@ -0,0 +1,36 @@
package router
import (
"mayfly-go/internal/flow/api"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
func InitProcinstRouter(router *gin.RouterGroup) {
p := new(api.Procinst)
biz.ErrIsNil(ioc.Inject(p))
reqGroup := router.Group("/flow/procinsts")
{
reqs := [...]*req.Conf{
req.NewGet("", p.GetProcinstPage),
req.NewGet("/:id", p.GetProcinstDetail),
req.NewPost("/:id/cancel", p.ProcinstCancel).Log(req.NewLogSave("流程-取消")),
req.NewGet("/tasks", p.GetTasks),
req.NewPost("/tasks/complete", p.CompleteTask).Log(req.NewLogSave("流程-任务完成")),
req.NewPost("/tasks/reject", p.RejectTask).Log(req.NewLogSave("流程-任务拒绝")),
req.NewPost("/tasks/back", p.BackTask).Log(req.NewLogSave("流程-任务驳回")),
}
req.BatchSetGroup(reqGroup, reqs[:])
}
}

View File

@@ -0,0 +1,8 @@
package router
import "github.com/gin-gonic/gin"
func Init(router *gin.RouterGroup) {
InitProcdefouter(router)
InitProcinstRouter(router)
}

View File

@@ -10,6 +10,7 @@ import (
"mayfly-go/internal/machine/config"
"mayfly-go/internal/machine/domain/entity"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
@@ -43,14 +44,20 @@ func (m *Machine) Machines(rc *req.Ctx) {
}
condition.Codes = codes
res, err := m.MachineApp.GetMachineList(condition, pageParam, new([]*vo.MachineVO))
var machinevos []*vo.MachineVO
res, err := m.MachineApp.GetMachineList(condition, pageParam, &machinevos)
biz.ErrIsNil(err)
if res.Total == 0 {
rc.ResData = res
return
}
for _, mv := range *res.List {
// 填充标签信息
m.TagApp.FillTagInfo(collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.ITagResource {
return mvo
})...)
for _, mv := range machinevos {
if machineStats, err := m.MachineApp.GetMachineStats(mv.Id); err == nil {
mv.Stat = collx.M{
"cpuIdle": machineStats.CPU.Idle,

View File

@@ -1,6 +1,7 @@
package vo
import (
tagentity "mayfly-go/internal/tag/domain/entity"
"time"
)
@@ -12,6 +13,8 @@ type AuthCertBaseVO struct {
}
type MachineVO struct {
tagentity.ResourceTags
Id uint64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
@@ -35,6 +38,10 @@ type MachineVO struct {
Stat map[string]any `json:"stat" gorm:"-"`
}
func (m *MachineVO) GetCode() string {
return m.Code
}
type MachineScriptVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`

View File

@@ -67,6 +67,9 @@ func checkClientAvailability(interval time.Duration) {
continue
}
cli := v.Value.(*Cli)
if cli.Info == nil {
continue
}
if _, _, err := cli.sshClient.Conn.SendRequest("ping", true, nil); err != nil {
logx.Errorf("machine[%s] cache client is not available: %s", cli.Info.Name, err.Error())
DeleteCli(cli.Info.Id)

View File

@@ -4,9 +4,11 @@ import (
"context"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/mongo/api/form"
"mayfly-go/internal/mongo/api/vo"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/domain/entity"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
@@ -36,8 +38,15 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
}
queryCond.Codes = codes
res, err := m.MongoApp.GetPageList(queryCond, page, new([]entity.Mongo))
var mongovos []*vo.Mongo
res, err := m.MongoApp.GetPageList(queryCond, page, &mongovos)
biz.ErrIsNil(err)
// 填充标签信息
m.TagApp.FillTagInfo(collx.ArrayMap(mongovos, func(mvo *vo.Mongo) tagentity.ITagResource {
return mvo
})...)
rc.ResData = res
}

View File

@@ -0,0 +1,20 @@
package vo
import (
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/model"
)
type Mongo struct {
model.Model
tagentity.ResourceTags
Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Uri string `orm:"column(uri)" json:"uri"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
}
func (m *Mongo) GetCode() string {
return m.Code
}

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/redis/domain/entity"
"mayfly-go/internal/redis/rdm"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
@@ -37,8 +38,15 @@ func (r *Redis) RedisList(rc *req.Ctx) {
}
queryCond.Codes = codes
res, err := r.RedisApp.GetPageList(queryCond, page, new([]vo.Redis))
var redisvos []*vo.Redis
res, err := r.RedisApp.GetPageList(queryCond, page, &redisvos)
biz.ErrIsNil(err)
// 填充标签信息
r.TagApp.FillTagInfo(collx.ArrayMap(redisvos, func(rvo *vo.Redis) tagentity.ITagResource {
return rvo
})...)
rc.ResData = res
}

View File

@@ -1,10 +1,14 @@
package vo
import "time"
import (
tagentity "mayfly-go/internal/tag/domain/entity"
"time"
)
type Redis struct {
tagentity.ResourceTags
Id *int64 `json:"id"`
Code *string `json:"code"`
Code string `json:"code"`
Name *string `json:"name"`
Host *string `json:"host"`
Db string `json:"db"`
@@ -19,6 +23,10 @@ type Redis struct {
ModifierId *int64 `json:"modifierId"`
}
func (r *Redis) GetCode() string {
return r.Code
}
type Keys struct {
Cursor map[string]uint64 `json:"cursor"`
Keys []string `json:"keys"`

View File

@@ -13,6 +13,7 @@ import (
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/conv"
"mayfly-go/pkg/utils/cryptox"
"strconv"
"strings"
@@ -119,13 +120,29 @@ func (a *Account) UpdateAccount(rc *req.Ctx) {
// @router /accounts [get]
func (a *Account) Accounts(rc *req.Ctx) {
condition := &entity.Account{}
condition := &entity.AccountQuery{}
condition.Username = rc.Query("username")
condition.Name = rc.Query("name")
res, err := a.AccountApp.GetPageList(condition, rc.GetPageParam(), new([]vo.AccountManageVO))
biz.ErrIsNil(err)
rc.ResData = res
}
func (a *Account) SimpleAccounts(rc *req.Ctx) {
condition := &entity.AccountQuery{}
condition.Username = rc.Query("username")
condition.Name = rc.Query("name")
idsStr := rc.Query("ids")
if idsStr != "" {
condition.Ids = collx.ArrayMap[string, uint64](strings.Split(idsStr, ","), func(val string) uint64 {
return uint64(conv.Str2Int(val, 0))
})
}
res, err := a.AccountApp.GetPageList(condition, rc.GetPageParam(), new([]vo.SimpleAccountVO))
biz.ErrIsNil(err)
rc.ResData = res
}
// @router /accounts
func (a *Account) SaveAccount(rc *req.Ctx) {
form := &form.AccountCreateForm{}

View File

@@ -15,6 +15,12 @@ type AccountManageVO struct {
OtpSecret string `json:"otpSecret"`
}
type SimpleAccountVO struct {
Id uint64 `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
}
// 账号角色信息
type AccountRoleVO struct {
RoleId uint64 `json:"roleId"`

View File

@@ -16,7 +16,7 @@ import (
type Account interface {
base.App[*entity.Account]
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
Create(ctx context.Context, account *entity.Account) error
@@ -34,7 +34,7 @@ func (a *accountAppImpl) InjectAccountRepo(repo repository.Account) {
a.Repo = repo
}
func (a *accountAppImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
func (a *accountAppImpl) GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return a.GetRepo().GetPageList(condition, pageParam, toEntity)
}

View File

@@ -1,5 +1,11 @@
package entity
type AccountQuery struct {
Ids []uint64 `json:"ids"`
Name string `json:"name" form:"name"`
Username string `json:"code" form:"code"`
}
type SysLogQuery struct {
CreatorId uint64 `json:"creatorId" form:"creatorId"`
Type int8 `json:"type" form:"type"`

View File

@@ -9,5 +9,5 @@ import (
type Account interface {
base.Repo[*entity.Account]
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
}

View File

@@ -16,9 +16,10 @@ func newAccountRepo() repository.Account {
return &AccountRepoImpl{base.RepoImpl[*entity.Account]{M: new(entity.Account)}}
}
func (m *AccountRepoImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
func (m *AccountRepoImpl) GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Account)).
Like("name", condition.Name).
Like("username", condition.Username)
Like("username", condition.Username).
In("id", condition.Ids)
return gormx.PageQuery(qd, pageParam, toEntity)
}

View File

@@ -34,6 +34,9 @@ func InitAccountRouter(router *gin.RouterGroup) {
// 获取所有用户列表
req.NewGet("", a.Accounts),
// 获取用户列表信息(只包含最基础信息)
req.NewGet("/simple", a.SimpleAccounts),
req.NewPost("", a.SaveAccount).Log(req.NewLogSave("保存账号信息")).RequiredPermission(addAccountPermission),
req.NewPut("change-status/:id/:status", a.ChangeStatus).Log(req.NewLogSave("修改账号状态")).RequiredPermission(addAccountPermission),

View File

@@ -47,6 +47,9 @@ type TagTree interface {
// 账号是否有权限访问该标签关联的资源信息
CanAccess(accountId uint64, tagPath ...string) error
// 填充资源的标签信息
FillTagInfo(resources ...entity.ITagResource)
}
type tagTreeAppImpl struct {
@@ -231,6 +234,26 @@ func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath ...string) error {
return errorx.NewBiz("您无权操作该资源")
}
func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) {
if len(resources) == 0 {
return
}
// 资源编号 -> 资源
resourceCode2Resouce := collx.ArrayToMap(resources, func(rt entity.ITagResource) string {
return rt.GetCode()
})
// 获取所有资源code关联的标签列表信息
var tagResources []*entity.TagResource
p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceCodes: collx.MapKeys(resourceCode2Resouce)}, &tagResources)
for _, tr := range tagResources {
// 赋值标签信息
resourceCode2Resouce[tr.ResourceCode].SetTagInfo(entity.ResourceTag{TagId: tr.TagId, TagPath: tr.TagPath})
}
}
func (p *tagTreeAppImpl) Delete(ctx context.Context, id uint64) error {
accountId := contextx.GetLoginAccount(ctx).Id
tag, err := p.GetById(new(entity.TagTree), id)

View File

@@ -13,3 +13,35 @@ type TagResource struct {
ResourceCode string `json:"resourceCode"` // 资源标识
ResourceType int8 `json:"resourceType"` // 资源类型
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
type ITagResource interface {
// 获取资源code
GetCode() string
// 赋值标签基本信息
SetTagInfo(rt ResourceTag)
}
// 资源关联的标签信息
type ResourceTag struct {
TagId uint64 `json:"tagId" gorm:"-"`
TagPath string `json:"tagPath" gorm:"-"` // 标签路径
}
func (r *ResourceTag) SetTagInfo(rt ResourceTag) {
r.TagId = rt.TagId
r.TagPath = rt.TagPath
}
// 资源标签列表
type ResourceTags struct {
Tags []ResourceTag `json:"tags" gorm:"-"`
}
func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
if r.Tags == nil {
r.Tags = make([]ResourceTag, 0)
}
r.Tags = append(r.Tags, rt)
}

View File

@@ -4,6 +4,7 @@ import (
_ "mayfly-go/internal/auth/init"
_ "mayfly-go/internal/common/init"
_ "mayfly-go/internal/db/init"
_ "mayfly-go/internal/flow/init"
_ "mayfly-go/internal/machine/init"
_ "mayfly-go/internal/mongo/init"
_ "mayfly-go/internal/msg/init"

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.7.3"
Version = "v1.7.4"
)
func GetAppInfo() string {

View File

@@ -128,3 +128,18 @@ func ArrayRemoveFunc[T any](arr []T, isDeleteFunc func(T) bool) []T {
}
return newArr
}
// 数组元素去重
func ArrayDeduplicate[T comparable](arr []T) []T {
encountered := map[T]bool{}
result := []T{}
for v := range arr {
if !encountered[arr[v]] {
encountered[arr[v]] = true
result = append(result, arr[v])
}
}
return result
}

View File

@@ -6,6 +6,14 @@ import (
"text/template"
)
// 逻辑空字符串由于gorm更新结构体只更新非零值所以使用该值最为逻辑空字符串方便更新结构体
const LogicEmptyStr = "-"
// 是否为逻辑上空字符串
func IsLogicEmpty(str string) bool {
return str == "" || str == LogicEmptyStr
}
// 可判断中文
func Len(str string) int {
return len([]rune(str))

View File

@@ -635,3 +635,26 @@ func Case2Camel(name string) string {
name = strings.Title(name)
return strings.Replace(name, " ", "", -1)
}
// 结构体转为map
func ToMap(input any) map[string]any {
result := make(map[string]any)
v := Indirect(reflect.ValueOf(input))
if v.Kind() != reflect.Struct {
return result
}
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
zeroValue := reflect.Zero(field.Type()).Interface()
fieldValue := field.Interface()
if !reflect.DeepEqual(fieldValue, zeroValue) {
result[v.Type().Field(i).Name] = fieldValue
} else {
result[v.Type().Field(i).Name] = zeroValue
}
}
return result
}

View File

@@ -11,10 +11,10 @@ import (
)
type Src struct {
Id *int64 `json:"id"`
Id int64 `json:"id"`
Username string `json:"username"`
CreateTime time.Time `json:"time"`
UpdateTime time.Time
UpdateTime *time.Time
Inner *SrcInner
}
@@ -194,3 +194,12 @@ func TestTemplateResolve(t *testing.T) {
fmt.Println(resolve)
}
func TestToMap(t *testing.T) {
mapRes := ToMap(&Src{
Id: 0,
Username: "哈哈哈",
CreateTime: time.Now(),
})
fmt.Println(mapRes)
}

View File

@@ -836,6 +836,13 @@ INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight,
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES (155, 150, 'Jra0n7De/PigmSGVg/', 2, 1, '日志', 'db:sync:log', 1704266866, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-01-03 15:27:47', '2024-01-03 15:27:47', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES (160, 49, 'dbms23ax/xleaiec2/3NUXQFIO/', 2, 1, '数据库备份', 'db:backup', 1705973876, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:37:56', '2024-01-23 09:37:56', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES (161, 49, 'dbms23ax/xleaiec2/ghErkTdb/', 2, 1, '数据库恢复', 'db:restore', 1705973909, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:38:29', '2024-01-23 09:38:29', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208354, 1708911264, '6egfEVYr/fw0Hhvye/b4cNf3iq/', 2, 1, '删除流程', 'flow:procdef:del', 1709208354, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:54', '2024-02-29 20:05:54', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208339, 1708911264, '6egfEVYr/fw0Hhvye/r9ZMTHqC/', 2, 1, '保存流程', 'flow:procdef:save', 1709208339, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:40', '2024-02-29 20:05:40', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709103180, 1708910975, '6egfEVYr/oNCIbynR/', 1, 1, '我的流程', 'procinsts', 1708911263, '{"component":"flow/ProcinstList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstList"}', 1, 'admin', 1, 'admin', '2024-02-28 14:53:00', '2024-02-29 20:36:07', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709045735, 1708910975, '6egfEVYr/3r3hHEub/', 1, 1, '我的任务', 'procinst-tasks', 1708911263, '{"component":"flow/ProcinstTaskList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstTaskList"}', 1, 'admin', 1, 'admin', '2024-02-27 22:55:35', '2024-02-27 22:56:35', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708911264, 1708910975, '6egfEVYr/fw0Hhvye/', 1, 1, '流程定义', 'procdefs', 1708911264, '{"component":"flow/ProcdefList","icon":"List","isKeepAlive":true,"routeName":"ProcdefList"}', 1, 'admin', 1, 'admin', '2024-02-26 09:34:24', '2024-02-27 22:54:32', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, type, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708910975, 0, '6egfEVYr/', 1, 1, '工单流程', '/flow', 60000000, '{"icon":"List","isKeepAlive":true,"routeName":"flow"}', 1, 'admin', 1, 'admin', '2024-02-26 09:29:36', '2024-02-26 15:37:52', 0, NULL);
-- Table: t_sys_role
CREATE TABLE IF NOT EXISTS "t_sys_role" (

View File

@@ -34,21 +34,23 @@ CREATE TABLE `t_db_instance` (
-- ----------------------------
DROP TABLE IF EXISTS `t_db`;
CREATE TABLE `t_db` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'code',
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`database` varchar(3000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
`remark` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注,描述等',
`instance_id` bigint(20) NOT NULL COMMENT '数据库实例 ID',
`create_time` datetime DEFAULT NULL,
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`is_deleted` tinyint(8) NOT NULL DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`name` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`database` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`remark` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`instance_id` bigint unsigned NOT NULL,
`flow_procdef_key` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '审批流-流程定义key有值则说明关键操作需要进行审批执行',
`create_time` datetime DEFAULT NULL,
`creator_id` bigint DEFAULT NULL,
`creator` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint DEFAULT NULL,
`modifier` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库资源信息表';
-- ----------------------------
@@ -86,6 +88,9 @@ CREATE TABLE `t_db_sql_exec` (
`sql` varchar(5000) NOT NULL COMMENT '执行sql',
`old_value` varchar(5000) DEFAULT NULL COMMENT '操作前旧值',
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
`status` tinyint DEFAULT NULL COMMENT '执行状态',
`flow_biz_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '流程关联的业务key',
`res` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '执行结果',
`create_time` datetime NOT NULL,
`creator` varchar(36) NOT NULL,
`creator_id` bigint(20) NOT NULL,
@@ -94,7 +99,8 @@ CREATE TABLE `t_db_sql_exec` (
`modifier_id` bigint(20) NOT NULL,
`is_deleted` tinyint(8) NOT NULL DEFAULT 0,
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
PRIMARY KEY (`id`),
KEY `idx_flow_biz_key` (`flow_biz_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库sql执行记录表';
-- ----------------------------
@@ -797,6 +803,13 @@ INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(150, 36, 'Jra0n7De/', 1, 1, '数据同步', 'sync', 1693040707, '{"component":"ops/db/SyncTaskList","icon":"Coin","isKeepAlive":true,"routeName":"SyncTaskList"}', 12, 'liuzongyang', 12, 'liuzongyang', '2023-12-22 09:51:34', '2023-12-27 10:16:57', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(160, 49, 'dbms23ax/xleaiec2/3NUXQFIO/', 2, 1, '数据库备份', 'db:backup', 1705973876, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:37:56', '2024-01-23 09:37:56', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(161, 49, 'dbms23ax/xleaiec2/ghErkTdb/', 2, 1, '数据库恢复', 'db:restore', 1705973909, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:38:29', '2024-01-23 09:38:29', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208354, 1708911264, '6egfEVYr/fw0Hhvye/b4cNf3iq/', 2, 1, '删除流程', 'flow:procdef:del', 1709208354, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:54', '2024-02-29 20:05:54', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208339, 1708911264, '6egfEVYr/fw0Hhvye/r9ZMTHqC/', 2, 1, '保存流程', 'flow:procdef:save', 1709208339, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:40', '2024-02-29 20:05:40', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709103180, 1708910975, '6egfEVYr/oNCIbynR/', 1, 1, '我的流程', 'procinsts', 1708911263, '{"component":"flow/ProcinstList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstList"}', 1, 'admin', 1, 'admin', '2024-02-28 14:53:00', '2024-02-29 20:36:07', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709045735, 1708910975, '6egfEVYr/3r3hHEub/', 1, 1, '我的任务', 'procinst-tasks', 1708911263, '{"component":"flow/ProcinstTaskList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstTaskList"}', 1, 'admin', 1, 'admin', '2024-02-27 22:55:35', '2024-02-27 22:56:35', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708911264, 1708910975, '6egfEVYr/fw0Hhvye/', 1, 1, '流程定义', 'procdefs', 1708911264, '{"component":"flow/ProcdefList","icon":"List","isKeepAlive":true,"routeName":"ProcdefList"}', 1, 'admin', 1, 'admin', '2024-02-26 09:34:24', '2024-02-27 22:54:32', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708910975, 0, '6egfEVYr/', 1, 1, '工单流程', '/flow', 60000000, '{"icon":"List","isKeepAlive":true,"routeName":"flow"}', 1, 'admin', 1, 'admin', '2024-02-26 09:29:36', '2024-02-26 15:37:52', 0, NULL);
COMMIT;
-- ----------------------------
@@ -1002,4 +1015,73 @@ BEGIN;
INSERT INTO `t_team_member` VALUES (7, 3, 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
COMMIT;
DROP TABLE IF EXISTS `t_flow_procdef`;
-- 工单流程相关表
CREATE TABLE `t_flow_procdef` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`def_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程定义key',
`name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '流程名称',
`status` tinyint DEFAULT NULL COMMENT '状态',
`tasks` varchar(3000) COLLATE utf8mb4_bin NOT NULL COMMENT '审批节点任务信息',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程定义';
DROP TABLE IF EXISTS `t_flow_procinst`;
CREATE TABLE `t_flow_procinst` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`procdef_id` bigint NOT NULL COMMENT '流程定义id',
`procdef_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程定义名称',
`task_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前任务key',
`status` tinyint DEFAULT NULL COMMENT '状态',
`biz_type` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务类型',
`biz_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务key',
`biz_status` tinyint DEFAULT NULL COMMENT '业务状态',
`biz_handle_res` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '流程持续时间(开始到结束)',
`create_time` datetime NOT NULL COMMENT '流程发起时间',
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '流程发起人',
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_procdef_id` (`procdef_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程实例(根据流程定义开启一个流程)';
DROP TABLE IF EXISTS `t_flow_procinst_task`;
CREATE TABLE `t_flow_procinst_task` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`procinst_id` bigint NOT NULL COMMENT '流程实例id',
`task_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '任务key',
`task_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务名称',
`assignee` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '分配到该任务的用户',
`status` tinyint DEFAULT NULL COMMENT '状态',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '任务持续时间(开始到结束)',
`create_time` datetime NOT NULL COMMENT '任务开始时间',
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_procinst_id` (`procinst_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程实例任务';
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,87 @@
begin;
-- 新增工单流程相关菜单
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208354, 1708911264, '6egfEVYr/fw0Hhvye/b4cNf3iq/', 2, 1, '删除流程', 'flow:procdef:del', 1709208354, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:54', '2024-02-29 20:05:54', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709208339, 1708911264, '6egfEVYr/fw0Hhvye/r9ZMTHqC/', 2, 1, '保存流程', 'flow:procdef:save', 1709208339, 'null', 1, 'admin', 1, 'admin', '2024-02-29 20:05:40', '2024-02-29 20:05:40', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709103180, 1708910975, '6egfEVYr/oNCIbynR/', 1, 1, '我的流程', 'procinsts', 1708911263, '{"component":"flow/ProcinstList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstList"}', 1, 'admin', 1, 'admin', '2024-02-28 14:53:00', '2024-02-29 20:36:07', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1709045735, 1708910975, '6egfEVYr/3r3hHEub/', 1, 1, '我的任务', 'procinst-tasks', 1708911263, '{"component":"flow/ProcinstTaskList","icon":"Tickets","isKeepAlive":true,"routeName":"ProcinstTaskList"}', 1, 'admin', 1, 'admin', '2024-02-27 22:55:35', '2024-02-27 22:56:35', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708911264, 1708910975, '6egfEVYr/fw0Hhvye/', 1, 1, '流程定义', 'procdefs', 1708911264, '{"component":"flow/ProcdefList","icon":"List","isKeepAlive":true,"routeName":"ProcdefList"}', 1, 'admin', 1, 'admin', '2024-02-26 09:34:24', '2024-02-27 22:54:32', 0, NULL);
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1708910975, 0, '6egfEVYr/', 1, 1, '工单流程', '/flow', 60000000, '{"icon":"List","isKeepAlive":true,"routeName":"flow"}', 1, 'admin', 1, 'admin', '2024-02-26 09:29:36', '2024-02-26 15:37:52', 0, NULL);
-- 工单流程相关表
CREATE TABLE `t_flow_procdef` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`def_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程定义key',
`name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '流程名称',
`status` tinyint DEFAULT NULL COMMENT '状态',
`tasks` varchar(3000) COLLATE utf8mb4_bin NOT NULL COMMENT '审批节点任务信息',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程定义';
CREATE TABLE `t_flow_procinst` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`procdef_id` bigint NOT NULL COMMENT '流程定义id',
`procdef_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程定义名称',
`task_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前任务key',
`status` tinyint DEFAULT NULL COMMENT '状态',
`biz_type` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务类型',
`biz_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务key',
`biz_status` tinyint DEFAULT NULL COMMENT '业务状态',
`biz_handle_res` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '流程持续时间(开始到结束)',
`create_time` datetime NOT NULL COMMENT '流程发起时间',
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '流程发起人',
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_procdef_id` (`procdef_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程实例(根据流程定义开启一个流程)';
CREATE TABLE `t_flow_procinst_task` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`procinst_id` bigint NOT NULL COMMENT '流程实例id',
`task_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '任务key',
`task_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务名称',
`assignee` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '分配到该任务的用户',
`status` tinyint DEFAULT NULL COMMENT '状态',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '任务持续时间(开始到结束)',
`create_time` datetime NOT NULL COMMENT '任务开始时间',
`creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_procinst_id` (`procinst_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程-流程实例任务';
-- 新增工单流程相关字段
ALTER TABLE t_db_sql_exec ADD status tinyint NULL COMMENT '执行状态';
ALTER TABLE t_db_sql_exec ADD flow_biz_key varchar(64) NULL COMMENT '工单流程定义key';
ALTER TABLE t_db_sql_exec ADD res varchar(1000) NULL COMMENT '执行结果';
ALTER TABLE t_db ADD flow_procdef_key varchar(64) NULL COMMENT '审批流-流程定义key有值则说明关键操作需要进行审批执行';
-- 历史执行记录调整为成功状态
UPDATE t_db_sql_exec SET status = 2
commit;