This commit is contained in:
2025-09-27 22:57:59 +08:00
parent 8a458ff0a4
commit ed26244cdb
12585 changed files with 1914308 additions and 3474 deletions

View File

@@ -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\":\"识别服务不可用,请稍后重试\"}");
}
}
}