// Изменено: 2026-04-11 — добавлена Redis persistence // ChangeMessageVisibilityV1 — меняет visibility timeout сообщения в очереди тенанта. package gosqs import ( "net/http" "strings" "time" "shared-sqs/app/interfaces" "shared-sqs/app/models" "shared-sqs/app/persistence" "shared-sqs/app/utils" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) func ChangeMessageVisibilityV1(req *http.Request) (int, interfaces.AbstractResponseBody) { requestBody := models.NewChangeMessageVisibilityRequest() ok := utils.REQUEST_TRANSFORMER(requestBody, req, false) if !ok { log.Error("Invalid Request - ChangeMessageVisibilityV1") return utils.CreateErrorResponseV1("InvalidParameterValue", true) } t := getTenantFromContext(req) if t == nil { return utils.CreateErrorResponseV1("InvalidClientTokenId", true) } vars := mux.Vars(req) queueUrl := requestBody.QueueUrl queueName := "" if queueUrl == "" { queueName = vars["queueName"] } else { uriSegments := strings.Split(queueUrl, "/") queueName = uriSegments[len(uriSegments)-1] } key := tenantQueueKey(t.AccessKey, queueName) receiptHandle := requestBody.ReceiptHandle visibilityTimeout := requestBody.VisibilityTimeout if visibilityTimeout < 0 || visibilityTimeout > 43200 { return utils.CreateErrorResponseV1("InvalidParameterValue", true) } queue := models.SyncQueues.GetLocked(key) if queue == nil { return utils.CreateErrorResponseV1("QueueNotFound", true) } defer queue.UnlockMu() messageFound := false var changedMsgUuid string var msgRemoved bool // O(1)-поиск по индексу ReceiptHandle (P1.2) вместо скана всего среза. if i, ok := queue.IndexOfReceipt(receiptHandle); ok { if visibilityTimeout == 0 { // Возврат видимости (+ DLQ-перенос при исчерпании MaxReceiveCount) — // единый путь с батчевой версией. changedMsgUuid, msgRemoved = queue.ResetVisibilityAndTryDLQ(i, receiptHandle) } else { queue.Messages[i].VisibilityTimeout = time.Now().Add(time.Duration(visibilityTimeout) * time.Second) changedMsgUuid = queue.Messages[i].Uuid } messageFound = true } // Персистим изменения в Redis под Lock (сетевые записи асинхронны). if messageFound { if msgRemoved { // Сообщение удалено (перемещено в DLQ) — удаляем из Redis persistence.DeleteMessagePersist(key, changedMsgUuid) } else if i, ok := queue.IndexOfUUID(changedMsgUuid); ok { // Сообщение обновлено — перезаписываем в Redis persistence.SaveMessage(key, &queue.Messages[i]) } persistence.SaveQueue(key, queue) } if !messageFound { return utils.CreateErrorResponseV1("MessageNotInFlight", true) } respStruct := models.ChangeMessageVisibilityResult{ Xmlns: models.BaseXmlns, Metadata: models.BaseResponseMetadata, } return http.StatusOK, &respStruct }