Refactor PrestashopClient to enhance manual multipart image upload handling and improve error logging

This commit is contained in:
Vincent Guillet
2025-12-03 15:19:26 +01:00
parent 078cef0585
commit 503cbee641

View File

@@ -2,21 +2,22 @@ package fr.gameovergne.api.service.prestashop;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpHeaders;
import org.springframework.http.*; import org.springframework.http.HttpStatus;
import org.springframework.http.client.MultipartBodyBuilder; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException; import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException; import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
@@ -232,8 +233,6 @@ public class PrestashopClient {
/** /**
* Upload d'une image produit vers PrestaShop : * Upload d'une image produit vers PrestaShop :
* POST /api/images/products/{productId} * POST /api/images/products/{productId}
* <p>
* On envoie directement les bytes du fichier avec le bon content-type (image/jpeg, image/png, ...).
*/ */
public ResponseEntity<String> uploadProductImage( public ResponseEntity<String> uploadProductImage(
@@ -242,7 +241,7 @@ public class PrestashopClient {
MultipartFile imageFile MultipartFile imageFile
) { ) {
try { try {
// Construire lURL Presta // Construire lURL Presta (comme avant)
StringBuilder urlBuilder = new StringBuilder(baseUrl) StringBuilder urlBuilder = new StringBuilder(baseUrl)
.append("/images/products/") .append("/images/products/")
.append(productId); .append(productId);
@@ -252,7 +251,7 @@ public class PrestashopClient {
} }
String url = urlBuilder.toString(); String url = urlBuilder.toString();
byte[] bytes = imageFile.getBytes(); byte[] fileBytes = imageFile.getBytes();
String originalFilename = imageFile.getOriginalFilename(); String originalFilename = imageFile.getOriginalFilename();
if (originalFilename == null || originalFilename.isBlank()) { if (originalFilename == null || originalFilename.isBlank()) {
@@ -261,42 +260,86 @@ public class PrestashopClient {
String contentType = imageFile.getContentType(); String contentType = imageFile.getContentType();
if (contentType == null || contentType.isBlank()) { if (contentType == null || contentType.isBlank()) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; contentType = "application/octet-stream";
} }
log.info( log.info(
"[PrestaShop] POST (image multipart) {} (size={} bytes, contentType={}, filename={})", "[PrestaShop] POST (image multipart - manual) {} (size={} bytes, contentType={}, filename={})",
url, bytes.length, contentType, originalFilename url, fileBytes.length, contentType, originalFilename
); );
// Resource avec filename pour que PHP le traite comme un fichier dans $_FILES["image"] // -------- Construction du multipart "à la main" --------
String finalOriginalFilename = originalFilename; String boundary = "----PrestashopBoundary" + System.currentTimeMillis();
ByteArrayResource imageResource = new ByteArrayResource(bytes) { ByteArrayOutputStream baos = new ByteArrayOutputStream();
@Override OutputStreamWriter osw = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
public String getFilename() { PrintWriter writer = new PrintWriter(osw, true);
return finalOriginalFilename;
// Début de la part "image"
writer.append("--").append(boundary).append("\r\n");
writer.append("Content-Disposition: form-data; name=\"image\"; filename=\"")
.append(originalFilename)
.append("\"\r\n");
writer.append("Content-Type: ").append(contentType).append("\r\n");
writer.append("\r\n");
writer.flush();
// Données binaires du fichier
baos.write(fileBytes);
baos.write("\r\n".getBytes(StandardCharsets.UTF_8));
// Fin du multipart
writer.append("--").append(boundary).append("--").append("\r\n");
writer.flush();
byte[] multipartBytes = baos.toByteArray();
// -------- Envoi via HttpURLConnection --------
URL targetUrl = URI.create(url).toURL();
HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("Accept", "application/xml, text/xml, */*;q=0.1");
// Important : pas de chunked, on envoie une taille fixe
conn.setFixedLengthStreamingMode(multipartBytes.length);
try (OutputStream os = conn.getOutputStream()) {
os.write(multipartBytes);
}
int status = conn.getResponseCode();
InputStream is = (status >= 200 && status < 300)
? conn.getInputStream()
: conn.getErrorStream();
String responseBody;
if (is != null) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
responseBody = sb.toString();
} }
} else {
responseBody = "";
}
@Override log.info("[PrestaShop] Image upload response status={}, body={}", status, responseBody);
public long contentLength() {
return bytes.length;
}
};
// body multipart : la clé "image" => part nommée "image" HttpStatus springStatus = HttpStatus.resolve(status);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); if (springStatus == null) {
body.add("image", imageResource); springStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return client.post() return new ResponseEntity<>(responseBody, springStatus);
.uri(URI.create(url))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(body)
.retrieve()
.toEntity(String.class);
} catch (IOException e) { } catch (IOException e) {
log.error("[PrestaShop] Erreur lecture fichier image", e); log.error("[PrestaShop] Erreur lors de l'upload d'image", e);
throw new RuntimeException("Erreur lors de la lecture du fichier image", e); throw new RuntimeException("Erreur lors de l'upload d'image vers PrestaShop", e);
} }
} }
} }