docxTemplaterWorker/worker/worker.go

396 lines
11 KiB
Go
Raw Normal View History

2022-12-30 18:52:16 +00:00
package worker
import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
"time"
2022-12-30 18:52:16 +00:00
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/templategen/amo"
"penahub.gitlab.yandexcloud.net/backend/templategen/dal"
"penahub.gitlab.yandexcloud.net/backend/templategen/dal/model"
"penahub.gitlab.yandexcloud.net/backend/templategen/gdisk"
"penahub.gitlab.yandexcloud.net/backend/templategen/penadisk"
2022-12-30 18:52:16 +00:00
"penahub.gitlab.yandexcloud.net/backend/templategen/templategen"
"penahub.gitlab.yandexcloud.net/backend/templategen/yadisk"
2022-12-30 18:52:16 +00:00
)
const (
WorkerLifetime = time.Minute * 15
DebounceDuration = time.Millisecond * 165
CacheSize = 10
TaskTimeout = 5 * time.Minute
2022-12-30 18:52:16 +00:00
)
2023-08-11 15:39:43 +00:00
type WorkerDeps struct {
AmoID string
Dal *dal.MongoDAL
YaDisk *yadisk.ClientApp
GDisk *gdisk.ClientApp
Amo *amo.ClientApp
Logger *zap.Logger
}
2022-12-30 18:52:16 +00:00
type Worker struct {
AmoID string
2022-12-30 18:52:16 +00:00
Tube chan *model.WorkerTask // Буферизированный канал с размером CacheSize
lifeTimer *time.Timer
dal *dal.MongoDAL
yaDisk *yadisk.ClientApp
gDisk *gdisk.ClientApp
amo *amo.ClientApp
logger *zap.Logger
}
2023-08-11 15:39:43 +00:00
func NewWorker(deps WorkerDeps) (*Worker, error) {
if deps.AmoID == "" {
return nil, errors.New("empty WorkerDeps.AmoID")
}
if deps.Dal == nil {
return nil, errors.New("nil WorkerDeps.Dal")
}
if deps.YaDisk == nil {
return nil, errors.New("nil WorkerDeps.YaDisk")
}
if deps.GDisk == nil {
return nil, errors.New("nil WorkerDeps.GDisk")
}
if deps.Amo == nil {
return nil, errors.New("nil WorkerDeps.Amo")
}
if deps.Logger == nil {
return nil, errors.New("nil WorkerDeps.Logger")
}
2022-12-30 18:52:16 +00:00
return &Worker{
2023-08-11 15:39:43 +00:00
AmoID: deps.AmoID,
2022-12-30 18:52:16 +00:00
Tube: make(chan *model.WorkerTask, CacheSize),
lifeTimer: time.NewTimer(WorkerLifetime),
2023-08-11 15:39:43 +00:00
dal: deps.Dal,
yaDisk: deps.YaDisk,
gDisk: deps.GDisk,
amo: deps.Amo,
logger: deps.Logger.With(zap.String("amo_id", deps.AmoID)),
}, nil
2022-12-30 18:52:16 +00:00
}
func (w *Worker) Start(ctx context.Context, stopWorker chan string) {
w.logger.Info("worker started")
2022-12-30 18:52:16 +00:00
go func() {
2023-01-06 18:05:56 +00:00
select {
case <-ctx.Done():
2023-01-06 18:05:56 +00:00
w.logger.Info("worker stopped - context done")
stopWorker <- w.AmoID
2023-01-06 18:05:56 +00:00
return
case <-w.lifeTimer.C:
w.logger.Info("worker stopped - timeout")
stopWorker <- w.AmoID
2023-01-06 18:05:56 +00:00
return
2022-12-30 18:52:16 +00:00
}
}()
go func() {
for task := range w.Tube {
if task == nil {
w.logger.Error("worker got empty task")
continue
}
2022-12-30 18:52:16 +00:00
w.logger.Info("new task", zap.String("_id", task.ID))
2023-08-11 15:39:43 +00:00
var err error
if err = w.dal.WorkerTask.UpdateStatus(ctx, task.ID, model.WorkerTaskStatusProcessing); err != nil {
w.logger.Error("cannot update workerTask", zap.String("_id", task.ID))
}
2022-12-30 18:52:16 +00:00
task.Status = model.WorkerTaskStatusDone
if !w.DoTask(ctx, task) {
task.Status = model.WorkerTaskStatusFailed
w.logger.Info("task failed", zap.String("_id", task.ID))
} else {
w.logger.Info("task done", zap.String("_id", task.ID))
}
2022-12-30 18:52:16 +00:00
2023-08-11 15:39:43 +00:00
if err = w.dal.WorkerTask.UpdateByID(ctx, task); err != nil {
w.logger.Error("cannot update workerTask", zap.String("_id", task.ID))
}
2022-12-30 18:52:16 +00:00
}
}()
2022-12-30 18:52:16 +00:00
}
func (w *Worker) DoTask(ctx context.Context, task *model.WorkerTask) bool {
2022-12-30 18:52:16 +00:00
logger := w.logger.With(zap.String("_id", task.ID))
// сбрасываем таймер
w.lifeTimer.Stop()
w.lifeTimer.Reset(WorkerLifetime)
2023-08-11 15:39:43 +00:00
var err error
2022-12-30 18:52:16 +00:00
// находим историю к которой привязана задача
history, err := w.dal.History.GetByWorkerTaskID(ctx, task.ID)
2022-12-30 18:52:16 +00:00
if err != nil {
logger.Error("get history", zap.Error(err))
}
defer func() {
err = w.dal.History.UpdateByID(ctx, history)
if err != nil {
w.logger.Error("cannot update history", zap.Error(err))
}
}()
2022-12-30 18:52:16 +00:00
// проверяем чтобы все поля были на месте
if task.LeadID <= 0 {
2022-12-30 18:52:16 +00:00
err = errors.New("got bad lead_id")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.PenaID == "" {
err = errors.New("got bad pena_id")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.Source.File == "" {
err = errors.New("got bad source.file")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.Source.StorageID == "" {
err = errors.New("got bad source.storage_id")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.Source.StorageType == "" {
err = errors.New("got bad source.storage_type")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.Target.StorageID == "" {
err = errors.New("got bad target.storage_id")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
if task.Target.StorageType == "" {
err = errors.New("got bad target.storage_type")
return w.checkErrTask(ctx, logger, history, "bad data", err)
2022-12-30 18:52:16 +00:00
}
// amo client
amoData, err := w.dal.Amo.GetByID(ctx, task.AmoID)
2022-12-30 18:52:16 +00:00
if err != nil {
return w.checkErrTask(ctx, logger, history, "cannot get amo data", err)
2022-12-30 18:52:16 +00:00
}
// Check privileges
if len(amoData.Privileges) == 0 {
return w.checkErrTask(ctx, logger, history, "check privileges", errors.New("user has no tariff"))
}
// По количеству оставшихся генераций
var privilegeAmount int64
privilege, privilegeCountExists := amoData.Privileges[model.PrivilegeTemplateCount]
if privilegeCountExists {
privilegeAmount = privilege.Amount
if privilegeAmount < 1 {
return w.checkErrTask(ctx, logger, history, "check privileges", errors.New("tariff ended - not enough count"))
}
}
// По дате
privilege, privilegeUnlimTimeExists := amoData.Privileges[model.PrivilegeTemplateUnlimTime]
if privilegeUnlimTimeExists {
if privilege.CreatedAt.AddDate(0, 0, int(privilege.Amount)).After(time.Now()) {
return w.checkErrTask(ctx, logger, history, "check privileges", errors.New("tariff ended - by time"))
}
}
amoClient, err := w.amo.NewClient(ctx, amoData.Referer, amoData.Token(), "")
2022-12-30 18:52:16 +00:00
if err != nil {
return w.checkErrTask(ctx, logger, history, "cannot create amo client", err)
2022-12-30 18:52:16 +00:00
}
lead, err := DebounceWrapper(amoClient.GetLeadByID)(ctx, strconv.FormatInt(task.LeadID, 10))
2022-12-30 18:52:16 +00:00
if err != nil {
return w.checkErrTask(ctx, logger, history, "cannot get lead", err)
2022-12-30 18:52:16 +00:00
}
if lead == nil {
err = errors.New("empty lead data")
return w.checkErrTask(ctx, logger, history, "cannot get lead", err)
2022-12-30 18:52:16 +00:00
}
dataTemplate := map[string]interface{}{}
// Добавляем Инфо Лида
for k, v := range templategen.AmoLeadFieldsToRuMap(lead) {
dataTemplate[k] = v
}
// Добавялем инфо контактов
contacts := []amo.Contact{}
for _, data := range lead.Embedded.Contacts {
var contact *amo.Contact
contact, err = DebounceWrapper(amoClient.GetContactByID)(ctx, strconv.Itoa(data.ID))
2022-12-30 18:52:16 +00:00
if err == nil {
contacts = append(contacts, *contact)
} else {
return w.checkErrTask(ctx, logger, history, "cannot get contact", err)
2022-12-30 18:52:16 +00:00
}
}
dataTemplate["Контакты"] = templategen.AmoContactsFieldsToRuMap(contacts)
// Добавляем инфо компаний
companies := []amo.Company{}
for _, data := range lead.Embedded.Companies {
var company *amo.Company
company, err = DebounceWrapper(amoClient.GetCompanyByID)(ctx, strconv.Itoa(data.ID))
2022-12-30 18:52:16 +00:00
if err == nil {
companies = append(companies, *company)
} else {
return w.checkErrTask(ctx, logger, history, "cannot get company", err)
2022-12-30 18:52:16 +00:00
}
}
dataTemplate["Компании"] = templategen.AmoCompaniesFieldsToRuMap(companies)
err = w.Generate(ctx, task, lead.Name, dataTemplate)
if err != nil {
return w.checkErrTask(ctx, logger, history, "cannot generate", err)
}
if privilegeCountExists {
err = w.dal.Amo.UpdateAmountPrivilege(ctx, amoData.ID, model.PrivilegeTemplateCount, privilegeAmount-1)
}
if err != nil {
return w.checkErrTask(ctx, logger, history, "cannot update amo.privilege.amount", err)
}
history.Target.File = task.Target.File
history.Target.StorageID = task.Target.StorageID
history.Target.StorageType = task.Target.StorageType
history.DownloadURL = task.DownloadURL
history.PublicURL = task.PublicURL
return true
2022-12-30 18:52:16 +00:00
}
// checkErrTask - если найдена ошибка, пишет лог, добавляет в историю и возвращает false.
func (w *Worker) checkErrTask(ctx context.Context, logger *zap.Logger, history *model.History, msg string, err error) bool {
if err != nil {
logger.Error("task failed: "+msg, zap.Error(err))
if len(history.Errors) > 0 {
history.Errors = append(history.Errors, err.Error())
} else {
history.Errors = []string{err.Error()}
}
2023-08-11 15:39:43 +00:00
if err = w.dal.History.UpdateByID(ctx, history); err != nil {
logger.Error("cannot update history", zap.Error(err))
}
return false
}
return true
}
2022-12-30 18:52:16 +00:00
// Generate - генерирует файл и отправляет его на указанный в task.source диск
// TODO: !!!ВНИМАНИЕ!!! После написания тестов сделать чтобы отправлял на диск task.target!!!
func (w *Worker) Generate(ctx context.Context, task *model.WorkerTask, name string, data any) error {
2022-12-30 18:52:16 +00:00
switch task.Source.StorageType {
case "gdisk":
gdiskData, err := w.dal.GDisk.GetByID(ctx, task.Source.StorageID)
2022-12-30 18:52:16 +00:00
if err != nil {
return err
}
client, err := w.gDisk.NewClient(ctx, gdiskData.Token())
2022-12-30 18:52:16 +00:00
if err != nil {
return err
}
task.Target.File, task.DownloadURL, err = templategen.GDiskGenerateDoc(task.Source.File, name,
gdiskData.SaveFolderID, client, data)
2022-12-30 18:52:16 +00:00
if err != nil {
return err
}
case "yadisk":
yaDiskData, err := w.dal.YaDisk.GetByID(ctx, task.Source.StorageID)
2022-12-30 18:52:16 +00:00
if err != nil {
return err
}
2023-04-17 23:24:26 +00:00
if yaDiskData == nil {
return fmt.Errorf("no such yadisk")
}
client, err := w.yaDisk.NewClient(ctx, yaDiskData.Token(), "")
2022-12-30 18:52:16 +00:00
if err != nil {
return err
}
task.Target.File, task.DownloadURL, err = templategen.YaDiskGenerateDoc(ctx, task.Source.File, name,
2022-12-30 18:52:16 +00:00
yaDiskData.SaveFolder, client, data)
if err != nil {
return err
}
2023-08-11 15:39:43 +00:00
if err = client.PublishResource(ctx, task.Target.File); err != nil {
return err
}
res, err := client.GetResources(ctx, task.Target.File, 0, 0)
if err != nil {
return err
}
task.PublicURL = res.PublicURL
case "penadisk":
penadiskData, err := w.dal.PenaDisk.GetByPenaID(ctx, task.PenaID)
if err != nil {
return err
}
client := penadisk.NewClient(task.PenaID)
task.Target.File, task.DownloadURL, err = templategen.PenaDiskGenerateDocBytes(ctx, task.Source.File, name,
penadiskData.SaveFolder, client, data)
if err != nil {
return err
}
task.PublicURL = task.DownloadURL
2022-12-30 18:52:16 +00:00
}
return nil
}
func (w *Worker) Stop() {
close(w.Tube)
w.lifeTimer.Stop()
}
func DebounceWrapper[T any](function T) T {
time.Sleep(DebounceDuration) // debounce
v := reflect.MakeFunc(reflect.TypeOf(function), func(in []reflect.Value) []reflect.Value {
f := reflect.ValueOf(function)
return f.Call(in)
})
return v.Interface().(T)
}