Я пытаюсь обновить документ, когда размер группы изображений изменяется, просто чтобы показать прогресс и отслеживать, не удалось ли преобразовать какие-либо изображения.
у нас есть несколько изображений в каталоге, где dir — это идентификатор документа. используя идентификатор документа, мы должны обновить этот документ. мы должны установить поле статуса, статус содержит объекты для преобразованной или не удалось преобразовать информацию. {изображение: путь, обработка: строка}
это будет выглядеть как { ... положение дел : [ имя_файла1:{...}, имя_файла2:{...} ] }
Но когда я загружаю от 5 до 10 изображений, в поле статуса устанавливается только информация о первых 4-5 изображениях. он приостанавливается (или выглядит как пауза или перезаписывается небольшая информация) перед записью последней информации об изображении в статус.
Хотя я пытался просто заменить старые данные новыми в статусе, это означает, что здесь будет только один объект и он будет изменен соответствующим образом. и это работает большую часть времени. я видел редкий случай, когда это тоже не сработало, но в большинстве случаев это работает.
Также пытался создать массив объектов, но при этом также пропускалось несколько записей.
async function updateDoc({filePath, metadata}) {
const [docId, fileName] = filePath.split('/');
const db = admin.firestore();
const collectionRef = db.collection(COLLECTION_PACKS).doc(docId);
const stickersRef = collectionRef.collection(COLLECTION_STICKERS);
let attempts = 0;
const maxAttempts = 5;
while (attempts < maxAttempts) {
try {
await db.runTransaction(async (transaction) => {
const updatedStatus = {};
updatedStatus[fileName] = metadata;
// Update the document with the new status and progress
transaction.update(collectionRef, {
status: updatedStatus,
});
});
console.info("Updated document for", filePath);
break; // Exit the loop if successful
} catch (error) {
attempts++;
console.error(`Error updating document (attempt ${attempts}):`, error);
if (attempts >= maxAttempts) {
throw error; // Re-throw the error after max attempts
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempts)); // Exponential backoff
}
}
}
Я также попробовал этот простой фрагмент, который добавляет новый объект в массив статусов, но это также не позволило сохранить интеграцию данных.
const db = admin.firestore();
const docRef = db.collection(COLLECTION_PACKS).doc(docId);
await db.runTransaction(async (transaction) => {
const docSnapshot = await transaction.get(docRef);
if (docSnapshot.exists) {
docRef.update({
status: FieldValue.arrayUnion(metadata),
});
}
});
Будь то вложенные объекты или массив объектов, любой из них приемлем.
Обновлено: Я наблюдал, как данные перезаписываются немногие объекты вставляются раньше и позже быстро заменяются новыми объектами. на несколько секунд это выглядит так
status : [
object1, object2
]
тогда это становится
status : [
object3, object4, object5
]
объект, который быстро вставляется при ранних триггерах, заменяется позже, и кажется, что новое триггерное событие не знает об этих вставленных данных. это расовая проблема.
Дополнительная деталь кода: вот как вызывается метод функции триггера события updateDoc()
exports.resize = onObjectFinalized({
bucket:'sticker-app-2ad48',
region:'us-central1',
timeoutSeconds: 450,
memory:'1GiB',
cpu:2
},
transform
)
async function transform(event){
...
let metadatainfo = await bucket.file(filePath).getMetadata()[0];
// Early return;
if (metadatainfo?.metadata?.process === "done"){
console.info("previously done processing | ",fileName)
return;
}
try {
if (valid){
console.info("image is valid ✅ |",filePath)
await updateMeta({filePath, valid:true, process:"done"});
return;
}
... // invoke image processing with sharpjs
if (buffer?.length > 0){
// save
log("✔️ transformed ", fileName)
await bucket.file(`${filePath}`).save(buffer);
await updateMeta({filePath, valid:true, process:"done"});
}else{
// leave image data as it is. set metadata
log("❌ failed to transform ", fileName)
await updateMeta({filePath, valid:false, process:"done"});
}
buffer = null;
} catch (e) {
console.error('Error_> ', e)
await updateMeta({filePath, valid:false, process:"done", error: e.message});
}
}
async function updateMeta({filePath, process, valid, error}){
// console.info("updateMeta ",filePath)
let metadata = { process, valid }
if (error) { metadata.error = error; }
await bucket.file(filePath).setMetadata({
metadata: metadata
})
await updateDoc({ filePath, metadata})
}
Обновлено: добавление кода на стороне клиента, который может быть причиной проблемы. Код клиентской стороны:
const promises = selectedFiles.map(..) => fetch(`${baseUrl}/upload`, ...) )
// Uploads images which triggers the onObjectFinalized
// to prcoess and add the info to the doc
Promise.all(promises)
.then((responses) => {
const jsonPromises = responses.map((response) => response.json());
return Promise.all(jsonPromises);
})
.then(uploadedImages=>{
// structure the stickers objects with download url
...
})
.then(stickerObjects=>{
...
// Populate subcollection "stickers"
return fetch(`${baseUrl}/addStickerPack/${docId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: body
})
})
.then(result=>{
console.info("Populated subcollection 'stickers' of the Doc ", {docId})
......
})
.catch(...)
.finally(...)
последний блок «тогда», который печатает «заполненную подколлекцию...», помог мне найти основную причину. Когда эта строка печатает поле статуса документа, для которого установлено значение неопределенное, что стирает поле статуса, и после этого журнала все, что добавляется в статус, остается интегрированным без каких-либо проблем. удаление метода выборки предотвращает любые изменения в подколлекции документов, устраняет проблему потери данных. Не знаю, почему это происходит.
вот внутренний код, который создает и обновляет подколлекцию.
app.put('/addStickerPack/:stickerPackId', async (req, res) => {
try {
const stickerPackDocId = req.params.stickerPackId;
const {
...
name,
publisher,
stickers
} = req.body;
const stickerPackData = {
name,
publisher,
...
};
const stickerPackRef = admin.firestore().collection(COLLECTION_PACKS).doc(stickerPackDocId);
await stickerPackRef.set(stickerPackData);
// Create sticker documents within the pack
const stickerPromises = stickers.map(async (sticker) => {
const stickerRef = stickerPackRef.collection(COLLECTION_STICKERS).doc();
const stickerData = {
name: sticker.name, // Replace with actual image storage logic
image_file: sticker.image_file, // Replace with actual image storage logic
emojis: sticker.emojis,
filePath: sticker.filePath,
valid:false,
};
console.info(sticker.image_file)
return stickerRef.set(stickerData);
});
// Wait for all sticker writes to complete
await Promise.all(stickerPromises);
console.info('Sticker pack added successfully:', stickerPackData);
const stickerPackId = stickerPackRef.id;
res.status(201).send({message:'Sticker pack added successfully', id:stickerPackId});
} catch (error) {
console.error('Error adding sticker pack:', error);
res.status(500).send('Internal Server Error');
}
});
Использование db.runTransaction()
не требуется, поскольку вы не получаете более старых значений для использования в обновлении, поэтому это просто добавляет больше ненужной обработки.
Если ваша структура данных на самом деле { ... status : { filename1:{...}, filename2:{...} } }
(обратите внимание, что я заменил квадратные скобки фигурными скобками, я полагаю, вы имели в виду объект, а не массив), то вы можете просто обновить вложенный путь без использования транзакции. Повторные попытки также не требуются:
// this is a docRef, not a colRef as in your example
const docRef = db.collection(COLLECTION_PACKS).doc(docId);
const update = {
[`status.${fileName}`]: metadata,
};
await docRef.update(update);
===== Обновлено:
Просто чтобы объяснить дальше, когда вы говорите:
Но когда я загружаю от 5 до 10 изображений, в поле статуса устанавливается только информация о первых 4-5 изображениях. он делает паузу перед записью информации о последнем изображении в статус.
Я не думаю, что это остановится. Я считаю, что у вас состояние гонки. Обратите внимание, что вы перезаписываете весь объект status
в каждом файле. Это означает, что если файлы запускаются в этом порядке 1-2-3-4, но завершают обработку 1-2-4-3, вы получите информацию, которая была у вас в третьем файле, а не в последнем.
Это не соответствует коду, который вы показываете, но, судя по тому, что вы говорите, это возможно, если вы намеревались получить весь объект status
перед обновлением.
===== Обновлено еще раз:
Что касается последнего добавленного вами кода, который используется на стороне клиента: этот код перезаписывает весь документ, в результате чего свойство status
становится неопределенным. Вот почему вы добавляете 1,2,очистить от клиента,3,4,5 и в итоге получаете только 3,4,5.
Измените строку, которая устанавливает документ, очищая остальную часть документа. Вместо:
await stickerPackRef.set(stickerPackData);
скорее используйте
await stickerPackRef.set(stickerPackData, {merge:true});
OR
await stickerPackRef.update(stickerPackData);
окей, я обнаружил, что это не пауза, а перезапись. возможно, любой вызванный экземпляр не знает о новых данных в массиве/вложенных объектах. он принимает пустой и помещает данные и обновляет документ, который фактически заменяет ранее добавленные данные.
изображения, размер которых не изменяется или не становится маленьким, поэтому их выполнение не занимает много времени, сначала они устанавливают данные, но позже они заменяются новыми изображениями, поэтому я видел, как он помещает 3 объекта, а затем заменяет их 7 новыми объектами, поэтому мы пропустили первые 3 изображения. поэтому я обновил заголовок вопроса. он не останавливается, а всегда устанавливает документ, и я думаю, что проблема заключается в перезаписи.
@CrackerKSR "так что, если я использую массив, все будет в порядке". На самом деле, нет. Если вы используете массив status
объектов file-status
, вам необходимо использовать транзакцию (чтобы получить текущий массив status
и добавить свой собственный file-status
объект). Использование объекта status
(вместо массива) решает эту проблему: вам не понадобится транзакция, и вы можете обновлять по вложенному пути, как в примере в моем ответе (await docRef.update({ 'status.whateverName': whateverStatus });
).
Но все же у меня возникает та же проблема с вложенным объектом без транзакции. Я видел журнал на стороне клиента, который прослушивал изменения документа с помощью onSnapshot. он добавляет 1 или 2 объекта, а затем показывает неопределенное значение. после этого будут добавлены результаты объектов. поэтому мы потеряли значения перед установкой неопределенного. и заметил, что это происходит, когда я получаю ответ от метода выборки, который добавлял информацию в подколлекцию того же документа. Я удалил его, и теперь он работает нормально. Я добавил код, который отправляет изображения на сервер, а также обновляет подколлекцию документа.
Так вы говорите, что сейчас это работает? Это был другой процесс очистки значения status
?
да. я добавил коды. Кажется, что оператор, который обновляет документ, устанавливает совершенно новые свежие значения для всего документа, которые также стирают поле статуса. Итак, наконец-то нашли причину и устранили проблему. Спасибо за ответ..
О, я только что увидел. Вы используете set()
, который, да, очищает все остальные значения. Вам необходимо либо: A) использовать update()
, если вы уверены, что документ уже существует, либо B) использовать set()
с merge: true
, чтобы другие значения не очищались. Я рад, что вы решили это. Если мой ответ помог, не могли бы вы пометить его как принятый? Спасибо
Спасибо за ответ,. так что, если я использую массив, все будет в порядке. в моем существующем коде я также попытался поместить новый элемент в массив статуса и столкнулся с той же проблемой. Я использовал вложенный объект, потому что замена объекта каждый раз без сохранения предыдущих значений в большинстве случаев работает нормально (иногда этот метод также пропускает последние 1 или 2 записи).