feat: add Go backend
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
type sSysApproval struct{}
|
||||
|
||||
func NewSysApproval() *sSysApproval { return &sSysApproval{} }
|
||||
|
||||
func init() { service.RegisterSysApproval(NewSysApproval()) }
|
||||
|
||||
func (s *sSysApproval) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{FilterTenant: true})
|
||||
}
|
||||
return handler.Model(dao.ApprovalRequest.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysApproval) List(ctx context.Context, in *sysin.ApprovalListInp) (list []*sysin.ApprovalListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx).Fields(sysin.ApprovalListModel{})
|
||||
columns := dao.ApprovalRequest.Columns()
|
||||
if in.ApprovalNo != "" {
|
||||
mod = mod.WhereLike(columns.ApprovalNo, "%"+in.ApprovalNo+"%")
|
||||
}
|
||||
if in.ApprovalType != "" {
|
||||
mod = mod.Where(columns.ApprovalType, in.ApprovalType)
|
||||
}
|
||||
if in.TargetType != "" {
|
||||
mod = mod.Where(columns.TargetType, in.TargetType)
|
||||
}
|
||||
if in.TargetId > 0 {
|
||||
mod = mod.Where(columns.TargetId, in.TargetId)
|
||||
}
|
||||
if in.ApprovalStatus > 0 {
|
||||
mod = mod.Where(columns.ApprovalStatus, in.ApprovalStatus)
|
||||
}
|
||||
if in.RiskLevel > 0 {
|
||||
mod = mod.Where(columns.RiskLevel, in.RiskLevel)
|
||||
}
|
||||
if in.Applicant != "" {
|
||||
ids, queryErr := service.AdminMember().GetIdsByKeyword(ctx, in.Applicant)
|
||||
if queryErr != nil {
|
||||
return nil, 0, queryErr
|
||||
}
|
||||
mod = mod.WhereIn(columns.ApplicantId, ids)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(columns.CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc(columns.Id)
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "获取审批列表失败")
|
||||
}
|
||||
for _, item := range list {
|
||||
item.AllowedActions = s.allowedActions(ctx, item.ApplicantId, item.ApprovalStatus)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysApproval) View(ctx context.Context, in *sysin.ApprovalViewInp) (res *sysin.ApprovalViewModel, err error) {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取审批详情失败")
|
||||
}
|
||||
if res == nil || res.Id == 0 {
|
||||
return nil, gerror.New("审批记录不存在或无权查看")
|
||||
}
|
||||
if err = dao.ApprovalAction.Ctx(ctx).
|
||||
Where(dao.ApprovalAction.Columns().ApprovalRequestId, in.Id).
|
||||
OrderAsc(dao.ApprovalAction.Columns().StepNo).
|
||||
OrderAsc(dao.ApprovalAction.Columns().Id).
|
||||
Scan(&res.Actions); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取审批记录失败")
|
||||
}
|
||||
if res.Actions == nil {
|
||||
res.Actions = []*sysin.ApprovalActionModel{}
|
||||
}
|
||||
res.AllowedActions = s.allowedActions(ctx, res.ApplicantId, res.ApprovalStatus)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysApproval) allowedActions(ctx context.Context, applicantId uint64, status int) []string {
|
||||
userId := uint64(contexts.GetUserId(ctx))
|
||||
actions := make([]string, 0, 2)
|
||||
if userId == applicantId {
|
||||
if status == consts.ApprovalStatusDraft {
|
||||
actions = append(actions, "submit")
|
||||
}
|
||||
if status == consts.ApprovalStatusDraft || status == consts.ApprovalStatusPending || status == consts.ApprovalStatusReviewing {
|
||||
actions = append(actions, "cancel")
|
||||
}
|
||||
return actions
|
||||
}
|
||||
if status == consts.ApprovalStatusPending || status == consts.ApprovalStatusReviewing {
|
||||
actions = append(actions, "approve", "reject")
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
func (s *sSysApproval) Create(ctx context.Context, in *sysin.ApprovalCreateInp) (res *sysin.ApprovalCreateModel, err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if userId <= 0 {
|
||||
return nil, gerror.New("未获取到当前操作人")
|
||||
}
|
||||
approvalNo := "AP" + gtime.Now().Format("YmdHis") + grand.S(6)
|
||||
data := &sysin.ApprovalRequestInsertFields{
|
||||
ApprovalNo: approvalNo,
|
||||
ApprovalType: in.ApprovalType,
|
||||
TargetType: in.TargetType,
|
||||
TargetId: in.TargetId,
|
||||
Action: in.Action,
|
||||
Title: in.Title,
|
||||
PayloadJson: in.PayloadJson,
|
||||
DiffJson: in.DiffJson,
|
||||
RiskLevel: in.RiskLevel,
|
||||
RequiredApprovals: in.RequiredApprovals,
|
||||
ApprovalStatus: consts.ApprovalStatusDraft,
|
||||
ApplicantId: uint64(userId),
|
||||
ExpireAt: in.ExpireAt,
|
||||
Version: 0,
|
||||
Remark: in.Remark,
|
||||
CreatedBy: uint64(userId),
|
||||
}
|
||||
result, err := s.Model(ctx, &handler.Option{}).
|
||||
Fields(sysin.ApprovalRequestInsertFields{}).
|
||||
Hook(hook.SaveTenant).
|
||||
Data(data).Insert()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "创建审批草稿失败")
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取审批ID失败")
|
||||
}
|
||||
return &sysin.ApprovalCreateModel{Id: uint64(id), ApprovalNo: approvalNo}, nil
|
||||
}
|
||||
|
||||
func (s *sSysApproval) Submit(ctx context.Context, in *sysin.ApprovalSubmitInp) error {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
request, err := s.lockRequest(ctx, in.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.ApplicantId != uint64(contexts.GetUserId(ctx)) {
|
||||
return gerror.New("只能提交本人创建的审批")
|
||||
}
|
||||
if request.ApprovalStatus != consts.ApprovalStatusDraft {
|
||||
return gerror.New("仅草稿状态可以提交")
|
||||
}
|
||||
if request.Version != in.Version {
|
||||
return gerror.New("审批数据已更新,请刷新后重试")
|
||||
}
|
||||
now := gtime.Now()
|
||||
if request.ExpireAt != nil && request.ExpireAt.Before(now) {
|
||||
return gerror.New("审批已超过有效期,无法提交")
|
||||
}
|
||||
if err = s.updateStatus(ctx, request, consts.ApprovalStatusPending, now, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.appendAction(ctx, request, 1, consts.ApprovalActionSubmit, "", "提交审批", 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysApproval) Approve(ctx context.Context, in *sysin.ApprovalDecisionInp) error {
|
||||
return s.decide(ctx, in, true)
|
||||
}
|
||||
|
||||
func (s *sSysApproval) Reject(ctx context.Context, in *sysin.ApprovalDecisionInp) error {
|
||||
if in.Comment == "" {
|
||||
return gerror.New("驳回时必须填写审批意见")
|
||||
}
|
||||
return s.decide(ctx, in, false)
|
||||
}
|
||||
|
||||
func (s *sSysApproval) Cancel(ctx context.Context, in *sysin.ApprovalCancelInp) error {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
request, err := s.lockRequest(ctx, in.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.ApplicantId != uint64(contexts.GetUserId(ctx)) {
|
||||
return gerror.New("只能取消本人发起的审批")
|
||||
}
|
||||
if request.Version != in.Version {
|
||||
return gerror.New("审批数据已更新,请刷新后重试")
|
||||
}
|
||||
if request.ApprovalStatus != consts.ApprovalStatusDraft &&
|
||||
request.ApprovalStatus != consts.ApprovalStatusPending &&
|
||||
request.ApprovalStatus != consts.ApprovalStatusReviewing {
|
||||
return gerror.New("当前审批状态不允许取消")
|
||||
}
|
||||
stepNo, err := s.nextStep(ctx, request.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.updateStatus(ctx, request, consts.ApprovalStatusCancelled, gtime.Now(), true); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.appendAction(ctx, request, stepNo, consts.ApprovalActionCancel, "", in.Comment, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysApproval) decide(ctx context.Context, in *sysin.ApprovalDecisionInp, approved bool) error {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
request, err := s.lockRequest(ctx, in.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actorId := uint64(contexts.GetUserId(ctx))
|
||||
if actorId == 0 {
|
||||
return gerror.New("未获取到当前操作人")
|
||||
}
|
||||
if request.ApplicantId == actorId {
|
||||
return gerror.New("申请人不能审批自己的申请")
|
||||
}
|
||||
if request.Version != in.Version {
|
||||
return gerror.New("审批数据已更新,请刷新后重试")
|
||||
}
|
||||
if request.ApprovalStatus != consts.ApprovalStatusPending && request.ApprovalStatus != consts.ApprovalStatusReviewing {
|
||||
return gerror.New("当前审批状态不允许处理")
|
||||
}
|
||||
if request.ExpireAt != nil && request.ExpireAt.Before(gtime.Now()) {
|
||||
return gerror.New("审批已超过有效期")
|
||||
}
|
||||
count, err := dao.ApprovalAction.Ctx(ctx).
|
||||
Where(dao.ApprovalAction.Columns().ApprovalRequestId, request.Id).
|
||||
Where(dao.ApprovalAction.Columns().ActorId, actorId).
|
||||
WhereIn(dao.ApprovalAction.Columns().Action, []int{consts.ApprovalActionApprove, consts.ApprovalActionReject}).Count()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "检查重复审批失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("请勿重复审批")
|
||||
}
|
||||
stepNo, err := s.nextStep(ctx, request.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
action := consts.ApprovalActionReject
|
||||
status := consts.ApprovalStatusRejected
|
||||
decided := true
|
||||
if approved {
|
||||
action = consts.ApprovalActionApprove
|
||||
approvedCount, countErr := dao.ApprovalAction.Ctx(ctx).
|
||||
Where(dao.ApprovalAction.Columns().ApprovalRequestId, request.Id).
|
||||
Where(dao.ApprovalAction.Columns().Action, consts.ApprovalActionApprove).Count()
|
||||
if countErr != nil {
|
||||
return gerror.Wrap(countErr, "统计审批进度失败")
|
||||
}
|
||||
if uint(approvedCount+1) >= request.RequiredApprovals {
|
||||
status = consts.ApprovalStatusApproved
|
||||
} else {
|
||||
status = consts.ApprovalStatusReviewing
|
||||
decided = false
|
||||
}
|
||||
}
|
||||
if err = s.updateStatus(ctx, request, status, gtime.Now(), decided); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.appendAction(ctx, request, stepNo, action, in.ReasonCode, in.Comment, in.MfaVerified)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysApproval) lockRequest(ctx context.Context, id uint64) (*entity.ApprovalRequest, error) {
|
||||
var request *entity.ApprovalRequest
|
||||
err := s.Model(ctx).WherePri(id).LockUpdate().Scan(&request)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "读取审批数据失败")
|
||||
}
|
||||
if request == nil || request.Id == 0 {
|
||||
return nil, gerror.New("审批记录不存在或无权操作")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (s *sSysApproval) updateStatus(ctx context.Context, request *entity.ApprovalRequest, status int, now *gtime.Time, decided bool) error {
|
||||
data := g.Map{
|
||||
dao.ApprovalRequest.Columns().ApprovalStatus: status,
|
||||
dao.ApprovalRequest.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.ApprovalRequest.Columns().Version: request.Version + 1,
|
||||
}
|
||||
if status == consts.ApprovalStatusPending {
|
||||
data[dao.ApprovalRequest.Columns().SubmittedAt] = now
|
||||
}
|
||||
if decided {
|
||||
data[dao.ApprovalRequest.Columns().DecidedAt] = now
|
||||
}
|
||||
result, err := s.Model(ctx).
|
||||
WherePri(request.Id).
|
||||
Where(dao.ApprovalRequest.Columns().Version, request.Version).
|
||||
Data(data).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新审批状态失败")
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "确认审批更新结果失败")
|
||||
}
|
||||
if affected != 1 {
|
||||
return gerror.New("审批数据已更新,请刷新后重试")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sSysApproval) nextStep(ctx context.Context, requestId uint64) (uint, error) {
|
||||
count, err := dao.ApprovalAction.Ctx(ctx).
|
||||
Where(dao.ApprovalAction.Columns().ApprovalRequestId, requestId).Count()
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "获取审批步骤失败")
|
||||
}
|
||||
return uint(count + 1), nil
|
||||
}
|
||||
|
||||
func (s *sSysApproval) appendAction(ctx context.Context, request *entity.ApprovalRequest, stepNo uint, action int, reasonCode, comment string, mfaVerified int) error {
|
||||
snapshot := gjson.New(request)
|
||||
_, err := dao.ApprovalAction.Ctx(ctx).
|
||||
Hook(hook.SaveTenant).
|
||||
Data(g.Map{
|
||||
dao.ApprovalAction.Columns().ApprovalRequestId: request.Id,
|
||||
dao.ApprovalAction.Columns().StepNo: stepNo,
|
||||
dao.ApprovalAction.Columns().ActorId: contexts.GetUserId(ctx),
|
||||
dao.ApprovalAction.Columns().Action: action,
|
||||
dao.ApprovalAction.Columns().ReasonCode: reasonCode,
|
||||
dao.ApprovalAction.Columns().Comment: comment,
|
||||
dao.ApprovalAction.Columns().SnapshotJson: snapshot,
|
||||
dao.ApprovalAction.Columns().MfaVerified: mfaVerified,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "记录审批动作失败")
|
||||
}
|
||||
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{
|
||||
Module: "approval", Action: "approval.action", TargetType: "approval_request",
|
||||
TargetId: request.Id, Before: request, After: g.Map{"action": action, "stepNo": stepNo},
|
||||
ChangeJson: gjson.New(g.Map{"action": action, "stepNo": stepNo, "reasonCode": reasonCode, "comment": comment}),
|
||||
Reason: comment, ApprovalNo: request.ApprovalNo,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user