You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1271 lines
30 KiB
1271 lines
30 KiB
package service |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"fmt" |
|
"reflect" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"go-common/app/admin/main/aegis/model" |
|
"go-common/app/admin/main/aegis/model/business" |
|
"go-common/app/admin/main/aegis/model/common" |
|
"go-common/app/admin/main/aegis/model/net" |
|
"go-common/app/admin/main/aegis/model/resource" |
|
taskmod "go-common/app/admin/main/aegis/model/task" |
|
uprpc "go-common/app/service/main/up/api/v1" |
|
"go-common/library/ecode" |
|
"go-common/library/log" |
|
"go-common/library/sync/errgroup" |
|
"go-common/library/xstr" |
|
) |
|
|
|
// ListBizFlow . |
|
func (s *Service) ListBizFlow(c context.Context, tp int8, bizID []int64, flowID []int64) (list []*business.BizItem, err error) { |
|
// 1. 获取每个类型的业务 |
|
list = []*business.BizItem{} |
|
res, err := s.gorm.BusinessList(c, tp, bizID, true) |
|
if err != nil { |
|
err = ecode.AegisBusinessCfgErr |
|
return |
|
} |
|
if len(res) == 0 { |
|
return |
|
} |
|
bizMap := map[int64]*business.Business{} |
|
accessBiz := []int64{} |
|
for _, item := range res { |
|
bizMap[item.ID] = item |
|
accessBiz = append(accessBiz, item.ID) |
|
} |
|
|
|
//获取指定业务下的可分发流程节点 |
|
var bizFlow map[int64]map[int64]string |
|
if bizFlow, err = s.dispatchFlow(c, accessBiz, flowID); err != nil { |
|
return |
|
} |
|
for bizID, item := range bizFlow { |
|
if bizMap[bizID] == nil { |
|
continue |
|
} |
|
|
|
bizItem := &business.BizItem{ |
|
BizID: bizID, |
|
BizName: bizMap[bizID].Name, |
|
BizType: bizMap[bizID].TP, |
|
Flows: item, |
|
} |
|
list = append(list, bizItem) |
|
} |
|
|
|
sort.Sort(business.BizItemArr(list)) |
|
return |
|
} |
|
|
|
// Next reveive next auditing task |
|
func (s *Service) Next(c context.Context, opt *taskmod.NextOptions) (infos []*model.AuditInfo, err error) { |
|
log.Info("Next opt(%+v)", opt) |
|
if opt.Debug == 1 { |
|
info := model.GetEmptyInfo() |
|
infos = append(infos, info) |
|
return |
|
} |
|
// 1. TODO: 根据业务 动态判定dispatch_count和seize_count |
|
/* |
|
获取任务后,要根据资源状态判断释放需要审核,不用审核的直接关任务。 再次循环获取 |
|
*/ |
|
var ( |
|
tasks []*taskmod.Task |
|
) |
|
for i := 0; i < 3; i++ { |
|
tasks, _, err = s.NextTask(c, opt) |
|
if err != nil { |
|
log.Error("NextTask err(%v)", err) |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
if len(tasks) == 0 { |
|
log.Info("NextTask empty task") |
|
return |
|
} |
|
|
|
for _, task := range tasks { |
|
log.Info("Next task(%+v)", task) |
|
info, err := s.auditInfoByTask(c, task, &opt.BaseOptions) |
|
if err != nil { |
|
err = nil |
|
continue |
|
} |
|
infos = append(infos, info) |
|
} |
|
if len(infos) > 0 { |
|
break |
|
} |
|
} |
|
|
|
return |
|
} |
|
|
|
// InfoTask . |
|
func (s *Service) InfoTask(c context.Context, opt *common.BaseOptions, taskid int64) (info *model.AuditInfo, err error) { |
|
task, err := s.Task(c, taskid) |
|
if err != nil || task == nil { |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
|
|
return s.auditInfoByTask(c, task, opt) |
|
} |
|
|
|
// ListByTask 任务列表 |
|
func (s *Service) ListByTask(c context.Context, opt *taskmod.ListOptions) (list []*model.ListTaskItem, err error) { |
|
log.Info("ListByTask opt(%+v)", opt) |
|
var ( |
|
tasks []*taskmod.Task |
|
count int64 |
|
) |
|
|
|
if opt.Debug == 1 { |
|
list = []*model.ListTaskItem{{}} |
|
opt.Total = 1 |
|
} else { |
|
tasks, count, err = s.ListTasks(c, opt) |
|
if err != nil { |
|
log.Error("ListTasks err(%v)", err) |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
if len(tasks) == 0 { |
|
return |
|
} |
|
opt.Total = int(count) |
|
} |
|
|
|
for _, task := range tasks { |
|
item := &model.ListTaskItem{} |
|
|
|
item.Task = task |
|
if int(time.Since(task.Gtime.Time()).Minutes()) < 10 { |
|
item.GTstr = common.WaitTime(task.Gtime.Time()) |
|
} |
|
item.CTstr = task.Ctime.Time().Format("2006-01-02 15:04:05") |
|
item.MTstr = task.Mtime.Time().Format("2006-01-02 15:04:05") |
|
item.WaitTime = common.WaitTime(task.Ctime.Time()) |
|
ids, _ := xstr.SplitInts(task.Group) |
|
item.UserGroup = s.getUserGroup(c, ids) |
|
list = append(list, item) |
|
} |
|
|
|
// 补充oid,content |
|
s.mulIDtoName(c, list, s.gorm.ListHelperForTask, "RID", "OID", "Content", "Metas") |
|
// 补充user_info,user_group |
|
s.mulIDtoName(c, list, s.listHelpUser, "MID", "UserGroup", "UserInfo") |
|
// 补充uname |
|
s.mulIDtoName(c, list, s.transUnames, "UID", "UserName") |
|
// 将mid替换为昵称 或者 cuser |
|
for _, item := range list { |
|
if item.MID != 0 && item.UserInfo != nil { |
|
item.MidStr = item.UserInfo.Name |
|
} else if item.Metas != nil { |
|
if val, ok := item.Metas["cuser"]; ok { |
|
item.MidStr = fmt.Sprint(val) |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// InfoResource . |
|
func (s *Service) InfoResource(c context.Context, opt *common.BaseOptions) (info *model.AuditInfo, err error) { |
|
if opt.Debug == 1 { |
|
return model.GetEmptyInfo(), nil |
|
} |
|
// 根据oid查资源 |
|
rsc, err := s.gorm.ResByOID(c, opt.BusinessID, opt.OID) |
|
if err != nil || rsc == nil { |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
return s.auditInfoByRsc(c, rsc, opt.NetID) |
|
} |
|
|
|
// ListByResource 资源列表 |
|
func (s *Service) ListByResource(c context.Context, arg *model.SearchParams) (columns []*model.Column, list []*model.ListRscItem, op []*net.TranOperation, err error) { |
|
if arg.Debug == 1 { |
|
arg.Total = 1 |
|
return []*model.Column{}, []*model.ListRscItem{model.EmptyListItem()}, []*net.TranOperation{{}}, nil |
|
} |
|
// 搜索返回资源信息 |
|
var sres *model.SearchRes |
|
if sres, err = s.http.ResourceES(c, arg); err != nil || sres == nil { |
|
err = ecode.AegisSearchErr |
|
return |
|
} |
|
arg.Total = sres.Page.Total |
|
if arg.FilterOff { |
|
list = sres.Resources |
|
} else { |
|
list = s.listParseState(c, arg.State, sres.Resources) |
|
} |
|
|
|
// 补充粉丝数和分组 |
|
if len(list) > 0 { |
|
g, _ := errgroup.WithContext(c) |
|
g.Go(func() error { |
|
s.mulIDtoName(c, list, s.listHelpUser, "MID", "UserGroup", "UserInfo") |
|
return nil |
|
}) |
|
g.Go(func() error { |
|
s.listHightLight(c, arg.BusinessID, list) |
|
return nil |
|
}) |
|
g.Go(func() error { |
|
s.mulIDtoName(c, list, s.listMetas, "ID", "MetaData", "Metas") |
|
return nil |
|
}) |
|
g.Wait() |
|
} |
|
columns = s.getColumns(c, arg.BusinessID) |
|
|
|
// 批量操作项 |
|
op, err = s.fetchBatchOperations(c, arg.BusinessID, 0) |
|
return |
|
} |
|
|
|
// listMetas 补充列表里面的 metadata metas |
|
func (s *Service) listMetas(c context.Context, ids []int64) (res map[int64][]interface{}, err error) { |
|
res = make(map[int64][]interface{}) |
|
var metas map[int64]string |
|
|
|
if metas, err = s.gorm.MetaByRID(c, ids); err != nil { |
|
return |
|
} |
|
for id, meta := range metas { |
|
mmeta := make(map[string]interface{}) |
|
if len(meta) > 0 { |
|
if err = json.Unmarshal([]byte(meta), &mmeta); err != nil { |
|
log.Error("listMetas json.Unmarshal error(%v)", err) |
|
err = nil |
|
} |
|
} |
|
res[id] = []interface{}{meta, mmeta} |
|
} |
|
return |
|
} |
|
|
|
//状态筛选,防止搜索列表更新不及时 |
|
func (s *Service) listParseState(c context.Context, state int64, list []*model.ListRscItem) (hitlist []*model.ListRscItem) { |
|
var arrids []int64 |
|
for _, item := range list { |
|
arrids = append(arrids, item.ID) |
|
} |
|
hitids, err := s.gorm.ResourceHit(c, arrids) |
|
if err != nil { |
|
return list |
|
} |
|
|
|
//搜索待审列表时,过滤掉已被领取的任务,避免提交冲突 |
|
taskhitids, _ := s.gorm.TaskHitAuditing(c, arrids) |
|
for _, item := range list { |
|
if _, ok := taskhitids[item.ID]; ok { |
|
continue |
|
} |
|
if st, ok := hitids[item.ID]; ok { |
|
if state == -12345 || (state == st) { |
|
item.State = st |
|
hitlist = append(hitlist, item) |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// listHelpUser 补充列表里面的 user_info user_group |
|
func (s *Service) listHelpUser(c context.Context, mids []int64) (res map[int64][]interface{}, err error) { |
|
res = make(map[int64][]interface{}) |
|
//mids去零 |
|
for i, v := range mids { |
|
if v > 0 { |
|
continue |
|
} |
|
|
|
if i == len(mids)-1 { |
|
mids = mids[:i] |
|
} else { |
|
mids = append(mids[:i], mids[i+1:]...) |
|
} |
|
} |
|
if len(mids) == 0 { |
|
return |
|
} |
|
|
|
infos, err := s.rpc.UserInfos(c, mids) |
|
if err != nil { |
|
infos = make(map[int64]*model.UserInfo) |
|
} |
|
|
|
upspecials, err := s.rpc.UpsSpecial(c, mids) |
|
if err != nil { |
|
upspecials = make(map[int64]*uprpc.UpSpecial) |
|
} |
|
|
|
for _, mid := range mids { |
|
gids := []int64{} |
|
if gs, ok := upspecials[mid]; ok { |
|
gids = gs.GroupIDs |
|
} |
|
res[mid] = []interface{}{s.getUserGroup(c, gids), infos[mid]} |
|
} |
|
log.Info("listRscHelper res(%+v)", res) |
|
return |
|
} |
|
|
|
/* |
|
避免超时,控制文本长度小于3000 |
|
过滤相同的文本,减少不必要请求 |
|
*/ |
|
func (s *Service) listHightLight(c context.Context, bizid int64, list []*model.ListRscItem) { |
|
|
|
var ( |
|
area string |
|
err error |
|
) |
|
|
|
if area = s.getConfig(c, bizid, business.TypeFiler); len(area) == 0 { |
|
log.Warn("sigleHightLight(%d) 没有文本高亮配置(%v)", bizid, err) |
|
return |
|
} |
|
|
|
arrcontent := []string{} |
|
for _, item := range list { |
|
arrcontent = append(arrcontent, item.Content) |
|
} |
|
arrset := stringset(arrcontent) |
|
hits, err := s.concurrentHightList(c, area, arrset) |
|
if err != nil { |
|
log.Error("listHightLight error(%v)", err) |
|
return |
|
} |
|
|
|
hitset := stringset(hits) |
|
for _, item := range list { |
|
item.Hit = hitset |
|
} |
|
} |
|
|
|
func (s *Service) concurrentHightList(c context.Context, area string, mapset []string) (hits []string, err error) { |
|
var eg errgroup.Group |
|
msgs := joinstr(mapset, "msg=", 3000) |
|
for _, msg := range msgs { |
|
var m = msg |
|
eg.Go(func() error { |
|
var ( |
|
e error |
|
hit []string |
|
) |
|
hit, e = s.http.FilterMulti(context.Background(), area, m) |
|
hits = append(hits, hit...) |
|
return e |
|
}) |
|
} |
|
err = eg.Wait() |
|
return |
|
} |
|
|
|
func (s *Service) sigleHightLight(c context.Context, bizid int64, content string) (hit []string) { |
|
var ( |
|
area string |
|
err error |
|
) |
|
|
|
if area = s.getConfig(c, bizid, business.TypeFiler); len(area) == 0 { |
|
log.Warn("sigleHightLight(%d) 没有文本高亮配置(%v)", bizid, err) |
|
return |
|
} |
|
|
|
hits, err := s.http.FilterMulti(c, area, "msg="+content) |
|
if err != nil { |
|
log.Error("sigleHightLight error(%v)", err) |
|
return |
|
} |
|
return hits |
|
} |
|
|
|
func (s *Service) getColumns(c context.Context, bizid int64) (columns []*model.Column) { |
|
cfg := s.getConfig(c, bizid, business.TypeRscListAdapter) |
|
if len(cfg) == 0 { |
|
log.Warn("getColumns empty config") |
|
return |
|
} |
|
if err := json.Unmarshal([]byte(cfg), &columns); err != nil { |
|
log.Error("getColumns err(%v)", err) |
|
} |
|
return |
|
} |
|
|
|
//Submit . |
|
func (s *Service) Submit(c context.Context, opt *model.SubmitOptions) (err error) { |
|
log.Info("Pre submit(%+v)", opt) |
|
|
|
// 1. 获取flow流转结果 |
|
result, err := s.computeTriggerResult(c, opt.RID, opt.FlowID, opt.Binds) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
esupsert, err := s.submit(c, "submit", opt, result) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
//更新es |
|
s.http.UpsertES(c, []*model.UpsertItem{esupsert}) |
|
return |
|
} |
|
|
|
func (s *Service) submit(c context.Context, action string, opt *model.SubmitOptions, result *net.TriggerResult) (esupsert *model.UpsertItem, err error) { |
|
ormTx, err := s.gorm.BeginTx(c) |
|
if err != nil { |
|
log.Error("tx error(%v)", err) |
|
return nil, ecode.ServerErr |
|
} |
|
|
|
var ( |
|
rsc *resource.Resource |
|
mid, ouid, otaskid int64 |
|
ostate int8 |
|
taskstate int8 |
|
) |
|
|
|
if rsc, err = s.gorm.ResourceByOID(c, opt.OID, opt.BusinessID); err != nil || rsc == nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
if rsc.ID != opt.RID { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
mid = rsc.MID |
|
|
|
// 2. 更新resource 和 resource_result |
|
if err = s.TxUpdateResource(ormTx, opt.RID, result.ResultToken.Values, opt.Result); err != nil { |
|
log.Error("submit TxUpdateResource(%v)", err) |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
log.Info("submit TxUpdateResource success") |
|
|
|
// 3. 更新task |
|
if result.ResultToken.HitAudit && opt.TaskID > 0 { |
|
taskstate = taskmod.TaskStateSubmit |
|
if action == "batch" { // 目前的接口我们区分不了 资源提交还是任务提交 |
|
taskstate = taskmod.TaskStateRscSb |
|
} |
|
|
|
// TODO 看来还是要把资源提交和任务提交分开。 资源提交不能确定要关的是哪个任务 |
|
if ostate, otaskid, ouid, err = s.TxSubmitTask(c, ormTx, &opt.BaseOptions, taskstate); err != nil { |
|
log.Error("submit TxSubmitTask(%v)", err) |
|
ormTx.Rollback() |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
log.Info("submit TxSubmitTask success") |
|
if otaskid != opt.TaskID { |
|
log.Warn("submit different taskid(%d-->%d)", opt.TaskID, otaskid) |
|
opt.TaskID = otaskid |
|
} |
|
} |
|
|
|
if result.ResultToken.HitAudit { |
|
// 4. 更新flow |
|
if err = s.reachNewFlowDB(c, ormTx, result); err != nil { |
|
log.Error("submit reachNewFlowDB(%v)", err) |
|
ormTx.Rollback() |
|
return |
|
} |
|
log.Info("submit reachNewFlowDB success") |
|
|
|
opt.Result.State, _ = strconv.Atoi(fmt.Sprint(result.ResultToken.Values["state"])) |
|
} |
|
|
|
// 5. 更新业务 |
|
if err = s.syncResource(c, opt, mid, result.ResultToken); err != nil { |
|
log.Error("submit syncResource(%v)", err) |
|
ormTx.Rollback() |
|
return |
|
} |
|
log.Info("submit syncResource success") |
|
|
|
if err = ormTx.Commit().Error; err != nil { |
|
ormTx.Rollback() |
|
return |
|
} |
|
|
|
// 6. 任务缓存更新 |
|
if result.ResultToken.HitAudit && opt.TaskID > 0 { |
|
s.submitTaskCache(c, &opt.BaseOptions, ostate, otaskid, ouid) |
|
} |
|
|
|
// 7. 任务流转 |
|
if err = s.afterReachNewFlow(c, result, opt.BusinessID); err != nil { |
|
log.Error("submit afterReachNewFlow(%v)", err) |
|
return |
|
} |
|
log.Info("submit afterReachNewFlow success") |
|
|
|
// 8. 查询下最终数据库的结果,记录 |
|
res, err := s.gorm.ResourceRes(c, opt.RID) |
|
if err != nil || res == nil { |
|
return |
|
} |
|
|
|
//8. 记录日志 |
|
if opt.Result != nil { |
|
opt.Result.State = int(res.State) |
|
} |
|
s.logSubmit(c, action, opt, result) |
|
|
|
//9.更新es |
|
esupsert = &model.UpsertItem{ |
|
ID: res.ID, |
|
State: int(res.State), |
|
Extra1: res.Extra1, |
|
Extra2: res.Extra2, |
|
Extra3: res.Extra3, |
|
Extra4: res.Extra4, |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) logSubmit(c context.Context, action string, opt *model.SubmitOptions, res interface{}) { |
|
s.async.Do(c, func(ctx context.Context) { |
|
// 1. 操作日志 |
|
s.sendAuditLog(ctx, action, opt, res, model.LogTypeAuditSubmit) |
|
// 2. resource日志 |
|
s.sendRscSubmitLog(ctx, action, opt, res) |
|
}) |
|
} |
|
|
|
// JumpFlow 跳流程提交 |
|
func (s *Service) JumpFlow(c context.Context, opt *model.SubmitOptions) (err error) { |
|
var ( |
|
ostate int8 |
|
ouid, otaskid int64 |
|
esupsert *model.UpsertItem |
|
) |
|
|
|
ormTx, err := s.gorm.BeginTx(c) |
|
if err != nil { |
|
log.Error("tx error(%v)", err) |
|
return ecode.ServerErr |
|
} |
|
|
|
// 1. 提交结果到flow |
|
result, err := s.jumpFlow(c, ormTx, opt.RID, opt.FlowID, opt.NewFlowID, opt.Binds) |
|
if err != nil { |
|
ormTx.Rollback() |
|
return |
|
} |
|
|
|
// 2. 更新task |
|
if result.ResultToken.HitAudit && opt.TaskID > 0 { //资源列表直接提交可能没有任务信息 |
|
if ostate, otaskid, ouid, err = s.TxSubmitTask(c, ormTx, &opt.BaseOptions, taskmod.TaskStateClosed); err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
if otaskid != opt.TaskID { |
|
log.Warn("submit different taskid(%d-->%d)", opt.TaskID, otaskid) |
|
opt.TaskID = otaskid |
|
} |
|
} |
|
|
|
if result.SubmitToken != nil { |
|
var ( |
|
rsc *resource.Resource |
|
mid int64 |
|
) |
|
if rsc, err = s.gorm.ResourceByOID(c, opt.OID, opt.BusinessID); err != nil || rsc == nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
if rsc.ID != opt.RID { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
mid = rsc.MID |
|
|
|
// 3. 更新resource 和 resource_result |
|
if err = s.TxUpdateResource(ormTx, opt.RID, result.ResultToken.Values, opt.Result); err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
// 4. 更新业务 |
|
if err = s.syncResource(c, opt, mid, result.ResultToken); err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisBusinessSyncErr |
|
return |
|
} |
|
|
|
} |
|
|
|
if err = ormTx.Commit().Error; err != nil { |
|
return |
|
} |
|
|
|
// 5. 任务缓存更新 |
|
if result.ResultToken.HitAudit && opt.TaskID > 0 { |
|
s.submitTaskCache(c, &opt.BaseOptions, ostate, otaskid, ouid) |
|
} |
|
|
|
// 6. 任务流转 |
|
if result.ResultToken.HitAudit { |
|
s.afterJumpFlow(c, result, opt.BusinessID) |
|
} |
|
|
|
rscr, err := s.gorm.ResourceRes(c, opt.RID) |
|
if err != nil || rscr != nil { |
|
return |
|
} |
|
//7. 记录日志 |
|
if opt.Result != nil { |
|
opt.Result.State = int(rscr.State) |
|
} |
|
s.logSubmit(c, "jump", opt, result) |
|
|
|
//8.更新es |
|
esupsert = &model.UpsertItem{ |
|
ID: rscr.ID, |
|
State: int(rscr.State), |
|
Extra1: rscr.Extra1, |
|
Extra2: rscr.Extra2, |
|
Extra3: rscr.Extra3, |
|
Extra4: rscr.Extra4, |
|
} |
|
s.http.UpsertES(c, []*model.UpsertItem{esupsert}) |
|
|
|
return |
|
} |
|
|
|
// BatchSubmit 批量提交, 超过10个的做异步 |
|
func (s *Service) BatchSubmit(c context.Context, opt *model.BatchOption) (tip *model.Tip, err error) { |
|
tip = &model.Tip{Fail: make(map[int64]string)} |
|
|
|
if len(opt.RIDs) > 10 { |
|
go s.processBatch(context.Background(), opt.RIDs[10:], opt, nil) |
|
rids := opt.RIDs[:10] |
|
s.processBatch(c, rids, opt, tip) |
|
tip.Async = append(tip.Async, opt.RIDs[10:]...) |
|
} else { |
|
s.processBatch(c, opt.RIDs, opt, tip) |
|
} |
|
|
|
return |
|
} |
|
|
|
func (s *Service) processBatch(c context.Context, rids []int64, opt *model.BatchOption, tip *model.Tip) { |
|
var ( |
|
err error |
|
esupdate *model.UpsertItem |
|
esupdates = []*model.UpsertItem{} |
|
) |
|
for _, rid := range rids { |
|
var ( |
|
taskid int64 |
|
oid string |
|
) |
|
if oid, err = s.gorm.OidByRID(c, rid); err != nil { |
|
if tip != nil { |
|
tip.Fail[rid] = err.Error() |
|
} |
|
continue |
|
} |
|
|
|
res, err := s.computeBatchTriggerResult(c, opt.BusinessID, rid, opt.Binds) |
|
if err != nil { |
|
if tip != nil { |
|
tip.Fail[rid] = ecode.Cause(err).Message() |
|
} |
|
continue |
|
} |
|
if task, _ := s.gorm.TaskByRID(c, rid, 0); task != nil { |
|
taskid = task.ID |
|
} |
|
|
|
smtOpt := &model.SubmitOptions{ |
|
EngineOption: model.EngineOption{ |
|
BaseOptions: common.BaseOptions{ |
|
BusinessID: opt.BusinessID, |
|
OID: oid, |
|
UID: opt.UID, |
|
RID: rid, |
|
Uname: opt.Uname, |
|
}, |
|
Result: &resource.Result{ |
|
Attribute: -1, // -1表示不更新 |
|
ReasonID: opt.ReasonID, |
|
RejectReason: opt.RejectReason, |
|
}, |
|
TaskID: taskid, |
|
ExtraData: map[string]interface{}{ |
|
"notify": opt.Notify, |
|
}, |
|
}, |
|
Binds: opt.Binds, |
|
} |
|
if esupdate, err = s.submit(c, "batch", smtOpt, res); err != nil { |
|
if tip != nil { |
|
tip.Fail[rid] = ecode.Cause(err).Message() |
|
} |
|
} else { |
|
if tip != nil { |
|
tip.Success = append(tip.Success, rid) |
|
} |
|
esupdates = append(esupdates, esupdate) |
|
} |
|
} |
|
|
|
//更新es |
|
s.http.UpsertES(c, esupdates) |
|
} |
|
|
|
// Add 业务方添加资源 |
|
func (s *Service) Add(c context.Context, opt *model.AddOption) (err error) { |
|
var res *net.TriggerResult |
|
defer func() { |
|
// 5. 记录资源添加日志 |
|
s.sendRscLog(c, "add", opt, res, nil, err) |
|
}() |
|
|
|
business.AdaptAddOpt(opt, s.getAdapter(c, opt.BusinessID)) |
|
if b, _ := s.gorm.Business(c, opt.BusinessID); b == nil || b.State != 0 { |
|
err = ecode.AegisBusinessSyncErr |
|
return |
|
} |
|
|
|
var rid int64 |
|
ormTx, err := s.gorm.BeginTx(c) |
|
if err != nil { |
|
log.Error("tx error(%v)", err) |
|
return ecode.ServerErr |
|
} |
|
|
|
// 2. 根据net_id,计算是否可添加 |
|
res, err = s.startNet(c, opt.BusinessID, opt.NetID) |
|
if err != nil { |
|
ormTx.Rollback() |
|
return |
|
} |
|
|
|
// 3. 事务新增资源 |
|
rsc := &resource.Result{State: opt.State} |
|
if res.ResultToken != nil { |
|
if state, ok := res.ResultToken.Values["state"]; ok { |
|
rsc.State, _ = strconv.Atoi(fmt.Sprint(state)) |
|
} |
|
} |
|
|
|
rid, err = s.TxAddResource(ormTx, &opt.Resource, rsc) |
|
if err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
res.RID = rid |
|
|
|
// 4. 事务新增flow,根据创建rid |
|
if err = s.reachNewFlowDB(c, ormTx, res); err != nil { |
|
ormTx.Rollback() |
|
return |
|
} |
|
|
|
if err = ormTx.Commit().Error; err != nil { |
|
log.Error("Commit opt(%+v) error(%v)", opt, err) |
|
return |
|
} |
|
|
|
go func() { |
|
s.afterReachNewFlow(context.TODO(), res, opt.BusinessID) |
|
|
|
// 1. 操作日志 |
|
s.sendAuditLog(context.TODO(), "add", &model.SubmitOptions{ |
|
EngineOption: model.EngineOption{ |
|
BaseOptions: common.BaseOptions{ |
|
BusinessID: opt.BusinessID, |
|
FlowID: res.NewFlowID, |
|
RID: rid, |
|
UID: 399, |
|
Uname: "业务方", |
|
}, |
|
Result: rsc, |
|
}, |
|
}, res, model.LogTypeAuditAdd) |
|
}() |
|
return |
|
} |
|
|
|
// Update 业务方修改资源参数 |
|
func (s *Service) Update(c context.Context, opt *model.UpdateOption) (err error) { |
|
var rows int64 |
|
business.AdaptUpdateOpt(opt, s.getAdapter(c, opt.BusinessID)) |
|
for key, val := range opt.Update { |
|
if _, ok := model.UpdateKeys[key]; !ok { |
|
delete(opt.Update, key) |
|
continue |
|
} |
|
switch key { |
|
case "extra1", "extra2", "extra3", "extra4", "extra5", "extra6": |
|
if reflect.TypeOf(val).Kind() != reflect.Float64 && reflect.TypeOf(val).Kind() != reflect.Int { |
|
return ecode.RequestErr |
|
} |
|
case "extra1s", "extra2s", "extra3s", "extra4s": |
|
if reflect.TypeOf(val).Kind() != reflect.String { |
|
return ecode.RequestErr |
|
} |
|
case "extratime1", "octime", "ptime": //时间类型不校验了 |
|
case "metadata": //如果是map,json转化为字符串 |
|
if reflect.TypeOf(val).Kind() == reflect.Map { |
|
var bs []byte |
|
if bs, err = json.Marshal(val); err != nil { |
|
return ecode.RequestErr |
|
} |
|
opt.Update["metadata"] = string(bs) |
|
} |
|
} |
|
} |
|
if len(opt.Update) == 0 { |
|
err = ecode.RequestErr |
|
} else { |
|
rows, err = s.gorm.UpdateResource(c, opt.BusinessID, opt.OID, opt.Update) |
|
} |
|
if rows > 0 || err != nil { |
|
s.sendRscLog(c, "update", &model.AddOption{ |
|
Resource: resource.Resource{ |
|
BusinessID: opt.BusinessID, |
|
OID: opt.OID, |
|
}, NetID: opt.NetID, |
|
}, nil, opt.Update, err) |
|
} |
|
return |
|
} |
|
|
|
//CancelByOper 手动删除任务 |
|
func (s *Service) CancelByOper(c context.Context, businessID int64, oids []string, uid int64, username string) (err error) { |
|
if err = s.Cancel(c, businessID, oids, uid, username); err != nil { |
|
return |
|
} |
|
|
|
//upsert es |
|
go func(businessID int64, oids []string) { |
|
var ( |
|
esupserts []*model.UpsertItem |
|
ctx = context.TODO() |
|
) |
|
if esupserts, err = s.gorm.UpsertByOIDs(ctx, businessID, oids); err != nil || len(esupserts) == 0 { |
|
return |
|
} |
|
s.http.UpsertES(ctx, esupserts) |
|
}(businessID, oids) |
|
return |
|
} |
|
|
|
// Cancel 取消相关资源的所有流程 |
|
func (s *Service) Cancel(c context.Context, businessID int64, oids []string, uid int64, username string) (err error) { |
|
var ( |
|
dstate int |
|
ridFlow map[int64]string |
|
) |
|
defer func() { |
|
// 5. 记录资源注销日志 |
|
s.sendRscCancleLog(c, businessID, oids, uid, username, err) |
|
}() |
|
|
|
ridstr, err := s.gorm.RidsByOids(c, businessID, oids) |
|
if err != nil { |
|
err = nil |
|
return |
|
} |
|
rids, err := xstr.SplitInts(ridstr) |
|
if err != nil || len(rids) == 0 { |
|
err = nil |
|
return |
|
} |
|
|
|
ormTx, err := s.gorm.BeginTx(c) |
|
if err != nil { |
|
log.Error("tx error(%v)", err) |
|
return ecode.ServerErr |
|
} |
|
defer ormTx.Commit() |
|
|
|
// 1. 资源修改为已删除状态 |
|
if dstate, err = s.TxDelResource(c, ormTx, businessID, rids); err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
// 2. 取消相关flow流程 |
|
if ridFlow, err = s.cancelNet(c, ormTx, rids); err != nil { |
|
ormTx.Rollback() |
|
return |
|
} |
|
|
|
// 3. 取消相关task |
|
if err = s.gorm.TxCloseTasks(ormTx, rids, uid); err != nil { |
|
ormTx.Rollback() |
|
err = ecode.AegisTaskErr |
|
return |
|
} |
|
|
|
// 操作日志 |
|
for _, rid := range rids { |
|
s.sendAuditLog(c, "cancel", &model.SubmitOptions{ |
|
EngineOption: model.EngineOption{ |
|
BaseOptions: common.BaseOptions{ |
|
BusinessID: businessID, |
|
RID: rid, |
|
UID: uid, |
|
Uname: username, |
|
}, |
|
Result: &resource.Result{ |
|
State: dstate, |
|
}, |
|
}, |
|
}, &net.TriggerResult{ |
|
OldFlowID: ridFlow[rid], |
|
}, model.LogTypeAuditCancel) |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) closeTask(c context.Context, task *taskmod.Task) (err error) { |
|
if err = s.gorm.CloseTask(c, task.ID); err != nil { |
|
return |
|
} |
|
if task.UID > 0 { |
|
s.submitTaskCache(c, &common.BaseOptions{ |
|
BusinessID: task.BusinessID, |
|
FlowID: task.FlowID, |
|
}, task.State, task.ID, task.UID) |
|
} |
|
return |
|
} |
|
|
|
// Upload to bfs |
|
func (s *Service) Upload(c context.Context, fileName string, fileType string, timing int64, body []byte) (location string, err error) { |
|
if len(body) == 0 { |
|
err = ecode.FileNotExists |
|
return |
|
} |
|
if location, err = s.http.Upload(c, fileName, fileType, timing, body); err != nil { |
|
log.Error("s.upload.Upload() error(%v)", err) |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) auditInfoByTask(c context.Context, task *taskmod.Task, opt *common.BaseOptions) (info *model.AuditInfo, err error) { |
|
var ( |
|
userinfo *model.UserInfo |
|
operhistorys, hit []string |
|
iframeurl string |
|
actions []*model.Action |
|
) |
|
// 1. 未完成的任务信息 |
|
undoStat, _ := s.UnDoStat(c, opt) |
|
|
|
// 2. 获取业务信息 resource |
|
resource, err := s.ResourceRes(c, &resource.Args{RID: task.RID}) |
|
if err != nil || resource == nil || !s.checkaudit(task.FlowID, resource.State) { |
|
log.Error("资源查找失败,删除任务 ResourceRes err(%v)", err) |
|
s.closeTask(c, task) |
|
err = ecode.AegisResourceErr |
|
return |
|
} |
|
|
|
// 3. 获取操作项 flow |
|
flow, err := s.fetchTaskTranInfo(c, task.RID, task.FlowID, opt.NetID) |
|
if err != nil { |
|
log.Error("fetchTransitionInfo(%d,%d) err(%v)", task.RID, task.FlowID, err) |
|
return |
|
} |
|
|
|
g, _ := errgroup.WithContext(c) |
|
|
|
// 4. 获取用户信息 account |
|
g.Go(func() error { |
|
userinfo, err = s.rpc.Profile(c, task.MID) |
|
if err != nil { |
|
log.Error("Profile(%d) err(%v)", task.MID, err) |
|
} |
|
return nil |
|
}) |
|
|
|
// 5. 审核历史 |
|
g.Go(func() error { |
|
operhistorys, err = s.auditLogByRID(c, task.RID) |
|
if err != nil { |
|
log.Error("AuditLog err(%v)", err) |
|
} |
|
return nil |
|
}) |
|
|
|
// 6. iframe url |
|
g.Go(func() error { |
|
iframeurl = s.getConfig(c, task.BusinessID, business.TypeIframe) |
|
actions = s.getActions(c, task.BusinessID) |
|
|
|
var attrcfg map[string]uint |
|
if attrcfg, err = s.AttributeCFG(c, task.BusinessID); len(attrcfg) > 0 { |
|
resource.AttrParse(attrcfg) |
|
} |
|
resource.MetaParse() |
|
return nil |
|
}) |
|
|
|
// 7. filter |
|
g.Go(func() error { |
|
hit = s.sigleHightLight(c, task.BusinessID, resource.Content) |
|
return nil |
|
}) |
|
g.Wait() |
|
|
|
info = &model.AuditInfo{ |
|
UnDoStat: undoStat, |
|
Task: task, |
|
Resource: resource, |
|
UserInfo: userinfo, |
|
Flow: flow, |
|
OperHistorys: operhistorys, |
|
IFrame: iframeurl, |
|
Actions: actions, |
|
Hit: hit, |
|
} |
|
if task.MID > 0 { |
|
s.getUserGroup(c, []int64{task.MID}) |
|
} |
|
|
|
return |
|
} |
|
|
|
func (s *Service) auditInfoByRsc(c context.Context, rsc *resource.Res, netid int64) (info *model.AuditInfo, err error) { |
|
var ( |
|
task *taskmod.Task |
|
userinfo *model.UserInfo |
|
flow *net.TransitionInfo |
|
iframeurl string |
|
attrcfg map[string]uint |
|
operhistorys, hit []string |
|
actions []*model.Action |
|
) |
|
|
|
g, _ := errgroup.WithContext(c) |
|
|
|
// 搜索未指定flow, 则检索task |
|
g.Go(func() error { |
|
task, err = s.gorm.TaskByRID(c, rsc.ID, 0) |
|
if err != nil { |
|
err = nil |
|
task = nil |
|
} |
|
return nil |
|
}) |
|
|
|
// 1. 获取用户信息 account |
|
g.Go(func() error { |
|
if userinfo, err = s.rpc.Profile(c, rsc.MID); err != nil { |
|
log.Error("Profile(%d) err(%v)", rsc.MID, err) |
|
} |
|
return nil |
|
}) |
|
|
|
// 2. 获取操作项 flow |
|
g.Go(func() error { |
|
flow, err = s.fetchResourceTranInfo(c, rsc.ID, rsc.BusinessID, netid) |
|
if err != nil { |
|
log.Error("fetchTransitionInfo(%d,%d,%d) err(%v)", rsc.ID, rsc.BusinessID, netid, err) |
|
} |
|
return nil |
|
}) |
|
|
|
// 3. 审核历史 |
|
g.Go(func() error { |
|
operhistorys, err = s.auditLogByRID(c, rsc.ID) |
|
if err != nil { |
|
log.Error("AuditLog(%d) err(%v)", rsc.ID, err) |
|
} |
|
return nil |
|
}) |
|
|
|
// 4. iframe url |
|
g.Go(func() error { |
|
iframeurl = s.getConfig(c, rsc.BusinessID, business.TypeIframe) |
|
actions = s.getActions(c, rsc.BusinessID) |
|
|
|
if attrcfg, err = s.AttributeCFG(c, rsc.BusinessID); len(attrcfg) > 0 { |
|
rsc.AttrParse(attrcfg) |
|
} |
|
rsc.MetaParse() |
|
return nil |
|
}) |
|
|
|
// 5. filter |
|
g.Go(func() error { |
|
hit = s.sigleHightLight(c, rsc.BusinessID, rsc.Content) |
|
return nil |
|
}) |
|
g.Wait() |
|
|
|
info = &model.AuditInfo{ |
|
Task: task, |
|
Resource: rsc, |
|
UserInfo: userinfo, |
|
Flow: flow, |
|
OperHistorys: operhistorys, |
|
IFrame: iframeurl, |
|
Actions: actions, |
|
Hit: hit, |
|
} |
|
|
|
if rsc.MID > 0 { |
|
if upspecial, _ := s.rpc.UpSpecial(c, rsc.MID); upspecial != nil { |
|
info.UserGroup = s.getUserGroup(c, upspecial.GroupIDs) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) getActions(c context.Context, bizid int64) (actions []*model.Action) { |
|
cfg := s.getConfig(c, bizid, business.TypeAction) |
|
if len(cfg) == 0 { |
|
log.Error("getActions(%d) empty", bizid) |
|
return |
|
} |
|
if err := json.Unmarshal([]byte(cfg), &actions); err != nil { |
|
log.Error("getActions(%d) error(%v)", bizid, err) |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) getAdapter(c context.Context, bizid int64) (adps []*business.Adapter) { |
|
cfg := s.getConfig(c, bizid, business.TypeAdapter) |
|
if len(cfg) == 0 { |
|
return |
|
} |
|
|
|
if err := json.Unmarshal([]byte(cfg), &adps); err != nil { |
|
log.Error("getAdapter cfg(%s) err(%v)", cfg, err) |
|
} |
|
return |
|
} |
|
|
|
// Gray 灰度 |
|
func (s *Service) Gray(opt *model.AddOption) (next bool) { |
|
if opt == nil { |
|
return |
|
} |
|
//未配置,则默认全量 |
|
if len(s.gray[opt.BusinessID]) == 0 { |
|
next = true |
|
return |
|
} |
|
|
|
optval := reflect.ValueOf(opt).Elem() |
|
opttp := optval.Type() |
|
for _, opts := range s.gray[opt.BusinessID] { |
|
//策略与策略之间or, 策略的fields之间and |
|
okcnt := 0 |
|
|
|
for _, item := range opts { |
|
f, exist := opttp.FieldByName(item.Name) |
|
if !exist { |
|
break |
|
} |
|
|
|
v := optval.FieldByIndex(f.Index) |
|
if strings.Contains(item.Value, fmt.Sprintf(",%v,", v.Interface())) { |
|
okcnt++ |
|
continue |
|
} |
|
} |
|
if okcnt > 0 && okcnt == len(opts) { |
|
next = true |
|
return |
|
} |
|
} |
|
|
|
return |
|
} |
|
|
|
//checkaudit 检查任务对应的资源是否需要审核,不需要的不下发 |
|
func (s *Service) checkaudit(flowid int64, state int64) bool { |
|
if len(s.c.Auditstate) > 0 { |
|
if states, ok := s.c.Auditstate[fmt.Sprint(flowid)]; ok { |
|
if !strings.Contains(","+states+",", fmt.Sprint(state)) { |
|
return false |
|
} |
|
} |
|
} |
|
return true |
|
} |
|
|
|
//Auth 用户权限查询 |
|
func (s *Service) Auth(c context.Context, uid int64) (a *model.Auth, err error) { |
|
var ( |
|
roles []*taskmod.Role |
|
) |
|
a = &model.Auth{} |
|
if s.Debug() == "local" { |
|
a.OK = true |
|
return |
|
} |
|
if s.IsAdmin(uid) { |
|
a.OK = true |
|
a.Admin = true |
|
return |
|
} |
|
|
|
//查看业务级别&任务级别的绑定权限 |
|
bidBiz := map[int64]int64{} |
|
//任务级别权限 |
|
for biz, bidFlows := range s.taskRoleCache { |
|
for bid := range bidFlows { |
|
bidBiz[bid] = biz |
|
} |
|
} |
|
//业务级别权限 |
|
for biz, cfgs := range s.bizRoleCache { |
|
if bid, exist := cfgs[business.BizBIDMngID]; exist && bid > 0 { |
|
bidBiz[bid] = biz |
|
} |
|
} |
|
log.Info("Auth bidbiz(%+v) \r\n taskrole(%+v) \r\n bizrole(%+v)", bidBiz, s.taskRoleCache, s.bizRoleCache) |
|
|
|
//用户角色 |
|
if roles, err = s.http.GetUserRoles(c, uid); err != nil { |
|
log.Error("Auth s.http.GetUserRoles(%d) error(%v)", uid, err) |
|
return |
|
} |
|
|
|
a.Business = map[int64]int64{} |
|
for _, item := range roles { |
|
if bidBiz[item.BID] > 0 { |
|
a.Business[item.BID] = bidBiz[item.BID] |
|
} |
|
} |
|
a.OK = len(a.Business) > 0 |
|
return |
|
}
|
|
|