Как отправить изображение внутри формы в переменной состояния в react на бэкенд django
Я пытаюсь отправить изображение для хранения в бэкенде. Изображение хранится в react useState, который обновляется, когда пользователь изменяет форму ввода, и отправляется в django backend api. Этот api принимает и другие входные данные, не только изображение, но и строки. Поэтому для отправки запроса я храню все в useState и выполняю запрос в async-функции. Апи проверен на работу на postman, проблема в том, что я не очень хорошо знаю, как сделать запрос. Изображение является необязательным параметром, поэтому оно прекрасно работает только с другими входными данными, без изображения.
Ранее я игнорировал изображение и выполнял запрос вместе с другими входными данными, что прекрасно работало. Я сделал это, поместив все в объект. Однако когда я помещаю изображение в тот же объект, оно перестает работать. Я осмотрелся и выяснил, что для отправки изображений нужно использовать конструктор FormData, что я и сделал, но столкнулся с несколькими проблемами.
Вот как выглядит мой компонент формы:
export default function Component() {
const [ image, setImage ] = useState<File>();
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
console.log('file', file)
setImage(file);
}
const handleSubmit = async (e: any) => {
const dummy_info = {
name: "hello",
start_date: "2024-01-01"
}
e.preventDefault();
const formData = new FormData();
formData.append('name', info.name);
formData.append('start_date', info.start_date);
if (image) formData.append('event_picture', image);
const res = await apiService.post('<backend_api_url>', formData, {
"Content-Type": "multipart/form-data"
});
}
return (
<form onSubmit={handleSubmit}>
<input
// value={info.event_picture || ''}
className="self-center" id="image" type="file" accept="image/*"
onChange={handleImageChange}
/>
<button type="submit">Submit</button>
</form>
)
}
У меня есть следующий объект apiService, который я использую для надежного выполнения этих вызовов, хотя я думаю, что здесь может быть одна из проблем:
const apiService = {
post: async function(url: string, data?: any, extraHeaders?: any): Promise<any> {
console.log('post', url, data);
try {
const token = await getAccessToken();
const headers = {
'Authorization': `Bearer ${token}`,
"Content-Type": "application/json",
'Accept': 'application/json',
};
if (extraHeaders) {
Object.assign(headers, extraHeaders);
}
return new Promise((resolve, reject) => {
fetch(`${process.env.NEXT_PUBLIC_API_HOST}${url}`, {
method: 'POST',
body: data,
headers: headers
})
.then(response => response.json())
.then((json) => {
console.log('Response:', json);
resolve(json);
})
.catch((error => {
reject(error);
}))
})
} catch (e) {
console.error('error fetching data', e);
}
},
}
При добавлении изображения и выполнении этого запроса я получаю в ответ следующее {detail: 'Multipart form parse error - Invalid boundary in multipart: None'}
Я знаю, что объект FormData не пуст, так как я console.log и он возвращает значения (для изображения: ['event_picture', File]
)
примечание: я использую Nextjs и заранее благодарю вас
Для отправки изображения на ваш сервер у вас есть несколько вариантов:
- Отправка на сервер изображения в виде строки base64. (не рекомендуется)
- Отправка изображения на ваш сервер и сохранение его непосредственно на вашем сервере. (не рекомендуется)
- Загрузка на внешнее хранилище S3 на AWS и отправка url изображения на ваш сервер. (рекомендуется)
Я не рекомендую использовать первый и второй способ, потому что это увеличивает сложность проекта. первый способ увеличивает размер базы данных из-за огромного количества длинных строк, сохраняемых в таблицах. Второй способ не увеличивает размер базы данных, но вам придется написать больше функциональности для управления изображениями, добавления метаданных к изображениям, преобразования их в различные размеры (например, 64x64, 128x128, 256x256 и т. д.), поиска между изображениями, приватных и публичных изображений, указания, кто может получить доступ к изображению, и многое другое.
Я настоятельно рекомендую использовать внешний сервер S3 для обработки всего вышеперечисленного и использовать Ant design или любой другой front-end фреймворк, который значительно упрощает работу по получению изображения и его загрузке на сервер S3. В основном вы загружаете изображение на сервер AWS S3 и можете отправить ссылку на него в ваш бэкенд Django, если изображение общедоступно, или, если изображение приватно, вы можете просто отправить его id в ваш бэкенд Django и сохранить в базе данных, когда пользователь с доступом приходит, вы создаете пользовательскую ссылку на таймер для этого пользователя.
Вы можете узнать об Ant.design по этому URL https://ant.design/
Подробнее о S3 в AWS можно узнать по этому адресу https://aws.amazon.com/pm/serv-s3/
Но если вы хотите пойти по первому пути, вы можете легко преобразовать ваше изображение в Base64String и отправить эту строку на ваш сервер как входной текст. Вот как это можно сделать: Как преобразовать изображение в строку Base64 с помощью JavaScript?
или Если вы хотите воспользоваться вторым способом и загрузить изображение непосредственно на свой сервер, вы можете посмотреть эту ссылку: https://omarshishani.com/how-to-upload-images-to-server-with-react-and-express/
При загрузке файлов, например изображений, необходимо внимательно относиться к объекту FormData. В React/Next.js важно позволить браузеру автоматически управлять 'content-type'.
Ручная установка Content-Type
в "multipart/form-data"
может привести к проблемам, таким как ошибка "Invalid boundary in multipart", с которой вы столкнулись, потому что браузер не будет включать необходимую границу, которая разделяет различные части данных формы.
Чтобы решить эту проблему, необходимо удалить ручную настройку 'content-type' в Component
и Service
.
Сообщите мне, если у вас возникнут другие вопросы после рассмотрения моего мнения.