logarchiverestorelogic.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Code scaffolded by goctl. Safe to edit.
  2. // goctl 1.10.2
  3. package systemlog
  4. import (
  5. "context"
  6. "database/sql"
  7. "fmt"
  8. "os"
  9. "udeServer/internal/apierr"
  10. "udeServer/internal/audit"
  11. "udeServer/internal/log"
  12. udeModel "udeServer/internal/model"
  13. "udeServer/internal/svc"
  14. "udeServer/internal/types"
  15. "github.com/zeromicro/go-zero/core/logx"
  16. "github.com/zeromicro/go-zero/core/stores/sqlx"
  17. )
  18. type LogArchiveRestoreLogic struct {
  19. logx.Logger
  20. ctx context.Context
  21. svcCtx *svc.ServiceContext
  22. }
  23. // 归档恢复
  24. func NewLogArchiveRestoreLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogArchiveRestoreLogic {
  25. return &LogArchiveRestoreLogic{
  26. Logger: logx.WithContext(ctx),
  27. ctx: ctx,
  28. svcCtx: svcCtx,
  29. }
  30. }
  31. // restoreResult carries the outcome of one restore goroutine.
  32. type restoreResult struct {
  33. RecordId int64
  34. Success bool
  35. ErrMsg string
  36. }
  37. // LogArchiveRestore launches the restore of the selected archive runs in
  38. // background goroutines (bounded to 5 concurrent) and answers immediately;
  39. // progress is tracked through t_log_restore_record.
  40. func (l *LogArchiveRestoreLogic) LogArchiveRestore(req *types.LogArchiveRestoreReq) (*types.LogArchiveRestoreResp, error) {
  41. if len(req.Ids) == 0 {
  42. return nil, apierr.BadRequest("ids 不能为空")
  43. }
  44. if l.svcCtx.MinioClient == nil {
  45. return nil, apierr.New(500, "MinIO客户端未加载,无法恢复(检查t_config的file_*配置)")
  46. }
  47. log.RecordDebug(l.ctx, "开始日志归档恢复!")
  48. list, err := l.svcCtx.LogArchiveRecordModel.FindByIds(l.ctx, req.Ids)
  49. if err != nil {
  50. log.RecordError(l.ctx, err)
  51. return nil, apierr.New(500, "查询归档记录失败")
  52. }
  53. if len(list) == 0 {
  54. return nil, apierr.New(404, "归档记录不存在")
  55. }
  56. operator := operatorFromContext(l.ctx)
  57. maxConcurrent := 5
  58. // 同时可支持5个协程的信号量通道,满位后阻塞
  59. sem := make(chan struct{}, maxConcurrent)
  60. // 为每一个归档日志记录一个结果
  61. resultChan := make(chan restoreResult, len(list))
  62. for i := range list {
  63. logArchiveRecord := &list[i]
  64. // 占位,满位后阻塞
  65. sem <- struct{}{}
  66. go func(r *udeModel.TLogArchiveRecord) {
  67. defer func() {
  68. <-sem
  69. if rec := recover(); rec != nil {
  70. log.RecordError(l.ctx, fmt.Errorf("[%d]的归档恢复Panic!原因:%v", r.Id, rec))
  71. }
  72. }()
  73. // 恢复执行在后台协程,不携带 HTTP 请求上下文
  74. resultChan <- runRestore(context.Background(), l.svcCtx, r, operator)
  75. }(logArchiveRecord)
  76. }
  77. // 后台汇总结果日志
  78. go func() {
  79. var success, failed int
  80. for i := 0; i < len(list); i++ {
  81. r := <-resultChan
  82. if r.Success {
  83. success++
  84. } else {
  85. failed++
  86. log.RecordDebug(l.ctx, "[%d]的归档恢复失败:%s", r.RecordId, r.ErrMsg)
  87. }
  88. }
  89. log.RecordDebug(l.ctx, "归档恢复任务全部完成,总数:%d,成功:%d,失败:%d", len(list), success, failed)
  90. }()
  91. msg := "归档恢复任务已启动,正在后台处理中,请稍候刷新恢复记录查看进度!"
  92. log.RecordDebug(l.ctx, "%s", msg)
  93. return &types.LogArchiveRestoreResp{RestoreMsg: msg}, nil
  94. }
  95. // operatorFromContext resolves the operator name (no auth layer -> admin).
  96. func operatorFromContext(ctx context.Context) string {
  97. name := audit.GetRequestMeta(ctx).UserName
  98. if name == "" {
  99. return "admin"
  100. }
  101. return name
  102. }
  103. // runRestore executes one archive restore run and books its record row.
  104. func runRestore(localCtx context.Context, svcCtx *svc.ServiceContext, r *udeModel.TLogArchiveRecord, operator string) restoreResult {
  105. result := restoreResult{RecordId: r.Id}
  106. // 1.构造恢复记录
  107. logRestoreRecord := &udeModel.TLogRestoreRecord{
  108. ArchiveId: r.Id,
  109. RestoreStatus: audit.RestoreProcessing,
  110. RestoreMessage: sql.NullString{Valid: false},
  111. RestoredBy: operator,
  112. }
  113. insertResult, err := svcCtx.LogRestoreRecordModel.Insert(localCtx, logRestoreRecord)
  114. if err != nil {
  115. log.RecordError(localCtx, err)
  116. result.Success, result.ErrMsg = false, "新增恢复记录失败:"+err.Error()
  117. return result
  118. }
  119. recordId, err := insertResult.LastInsertId()
  120. if err != nil {
  121. log.RecordError(localCtx, err)
  122. result.Success, result.ErrMsg = false, "获取恢复记录ID失败:"+err.Error()
  123. return result
  124. }
  125. // 状态更新辅助函数
  126. update := func(status int64, message sql.NullString) {
  127. if uErr := svcCtx.LogRestoreRecordModel.UpdateResult(localCtx, recordId, status, message); uErr != nil {
  128. log.RecordError(localCtx, fmt.Errorf("更新[%d]的恢复记录状态失败!原因:%v", recordId, uErr))
  129. }
  130. }
  131. fail := func(errMsg string) {
  132. result.Success, result.ErrMsg = false, errMsg
  133. update(audit.RestoreFail, sql.NullString{String: errMsg, Valid: true})
  134. }
  135. // 2.下载归档文件
  136. filePath, err := svcCtx.MinioClient.DownloadFile(localCtx, r.ArchiveFileKey)
  137. if err != nil || filePath == "" {
  138. fail(fmt.Sprintf("下载归档文件失败:%v", err))
  139. return result
  140. }
  141. // 3.流式恢复(每批100条语句一个事务)
  142. err = audit.RestoreSQLStream(localCtx, filePath, 100, func(ctx context.Context, batch []string) error {
  143. return svcCtx.DB.TransactCtx(localCtx, func(ctx context.Context, session sqlx.Session) error {
  144. for _, stmt := range batch {
  145. if _, err := session.ExecCtx(ctx, stmt); err != nil {
  146. return err
  147. }
  148. }
  149. return nil
  150. })
  151. })
  152. // 4.清理下载的临时文件
  153. if rErr := os.Remove(filePath); rErr != nil {
  154. log.RecordDebug(localCtx, "删除临时文件失败(%s):%v", filePath, rErr)
  155. }
  156. // 5.更新恢复记录状态
  157. if err != nil {
  158. fail("恢复执行失败:" + err.Error())
  159. return result
  160. }
  161. result.Success, result.ErrMsg = true, ""
  162. update(audit.RestoreSuccess, sql.NullString{Valid: false})
  163. return result
  164. }