2
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package com.example.demo.barcode;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/barcode")
|
||||
public class BarcodeProxyController {
|
||||
|
||||
private final PythonBarcodeProperties properties;
|
||||
private final RestTemplate restTemplate;
|
||||
private static final Logger log = LoggerFactory.getLogger(BarcodeProxyController.class);
|
||||
|
||||
public BarcodeProxyController(PythonBarcodeProperties properties) {
|
||||
this.properties = properties;
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout((int) Duration.ofSeconds(2).toMillis());
|
||||
factory.setReadTimeout((int) Duration.ofSeconds(8).toMillis());
|
||||
this.restTemplate = new RestTemplate(factory);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/scan", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<String> scan(@RequestPart("file") MultipartFile file) throws IOException {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body("{\"success\":false,\"message\":\"文件为空\"}");
|
||||
}
|
||||
long maxBytes = (long) properties.getMaxUploadMb() * 1024L * 1024L;
|
||||
if (file.getSize() > maxBytes) {
|
||||
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
|
||||
.body(String.format("{\"success\":false,\"message\":\"文件过大(> %dMB)\"}", properties.getMaxUploadMb()));
|
||||
}
|
||||
String url = String.format("http://%s:%d/api/barcode/scan", properties.getHost(), properties.getPort());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("转发条码识别请求: url={} filename={} size={}B", url, file.getOriginalFilename(), file.getSize());
|
||||
}
|
||||
|
||||
// 构建 multipart/form-data 请求转发
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
HttpHeaders fileHeaders = new HttpHeaders();
|
||||
String contentType = file.getContentType();
|
||||
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
if (contentType != null && !contentType.isBlank()) {
|
||||
try {
|
||||
mediaType = MediaType.parseMediaType(contentType);
|
||||
} catch (Exception ignored) {
|
||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
}
|
||||
fileHeaders.setContentType(mediaType);
|
||||
String originalName = file.getOriginalFilename();
|
||||
if (originalName == null || originalName.isBlank()) {
|
||||
originalName = file.getName();
|
||||
}
|
||||
if (originalName == null || originalName.isBlank()) {
|
||||
originalName = "upload.bin";
|
||||
}
|
||||
fileHeaders.setContentDisposition(ContentDisposition.builder("form-data")
|
||||
.name("file")
|
||||
.filename(originalName)
|
||||
.build());
|
||||
final String finalFilename = originalName;
|
||||
ByteArrayResource resource = new ByteArrayResource(file.getBytes()) {
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return finalFilename;
|
||||
}
|
||||
};
|
||||
HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(resource, fileHeaders);
|
||||
body.add("file", fileEntity);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
HttpEntity<MultiValueMap<String, Object>> req = new HttpEntity<>(body, headers);
|
||||
|
||||
try {
|
||||
long t0 = System.currentTimeMillis();
|
||||
ResponseEntity<String> resp = restTemplate.postForEntity(url, req, String.class);
|
||||
long cost = System.currentTimeMillis() - t0;
|
||||
if (log.isDebugEnabled()) {
|
||||
String bodyStr = resp.getBody();
|
||||
if (bodyStr != null && bodyStr.length() > 500) {
|
||||
bodyStr = bodyStr.substring(0, 500) + "...";
|
||||
}
|
||||
log.debug("转发完成: status={} cost={}ms resp={}", resp.getStatusCodeValue(), cost, bodyStr);
|
||||
}
|
||||
return ResponseEntity.status(resp.getStatusCode())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(resp.getBody());
|
||||
} catch (HttpStatusCodeException ex) {
|
||||
String bodyStr = ex.getResponseBodyAsString();
|
||||
if (bodyStr != null && bodyStr.length() > 500) {
|
||||
bodyStr = bodyStr.substring(0, 500) + "...";
|
||||
}
|
||||
log.warn("Python 服务返回非 2xx: status={} body={}", ex.getStatusCode(), bodyStr);
|
||||
MediaType respType = ex.getResponseHeaders() != null
|
||||
? ex.getResponseHeaders().getContentType()
|
||||
: MediaType.APPLICATION_JSON;
|
||||
if (respType == null) {
|
||||
respType = MediaType.APPLICATION_JSON;
|
||||
}
|
||||
return ResponseEntity.status(ex.getStatusCode())
|
||||
.contentType(respType)
|
||||
.body(ex.getResponseBodyAsString());
|
||||
} catch (Exception ex) {
|
||||
// Python 服务不可用或超时等异常
|
||||
log.warn("转发到 Python 服务失败: {}:{} path=/api/barcode/scan, err={}", properties.getHost(), properties.getPort(), ex.toString());
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body("{\"success\":false,\"message\":\"识别服务不可用,请稍后重试\"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user