diff --git a/code/base-project/base-springboot/ocr_result_0.jpg b/code/base-project/base-springboot/ocr_result_0.jpg new file mode 100644 index 00000000..ec5dd4dc Binary files /dev/null and b/code/base-project/base-springboot/ocr_result_0.jpg differ diff --git a/code/base-project/base-springboot/pom.xml b/code/base-project/base-springboot/pom.xml index e1cfc957..e5fc5fb9 100644 --- a/code/base-project/base-springboot/pom.xml +++ b/code/base-project/base-springboot/pom.xml @@ -46,6 +46,11 @@ + + com.squareup.okhttp3 + okhttp + + org.springframework.boot spring-boot-starter-test diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082017A007.jpg b/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082017A007.jpg new file mode 100644 index 00000000..4171ae79 Binary files /dev/null and b/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082017A007.jpg differ diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082413A013.jpg b/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082413A013.jpg new file mode 100644 index 00000000..6b6d19c0 Binary files /dev/null and b/code/base-project/base-springboot/src/main/java/cn/g3soft/20241004082413A013.jpg differ diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/Test.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/Test.java new file mode 100644 index 00000000..47433f49 --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/Test.java @@ -0,0 +1,143 @@ +package cn.g3soft; + +import okhttp3.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Base64; + +public class Test { + + public static void main(String[] args) throws IOException { + String API_URL = "http://localhost:8866/ocr"; + String imagePath = "D:\\code\\python-project\\boxocr\\conno\\6ffd244b-1d27-4084-998b-e4d7ac076c5f.jpg"; + + // 压缩并限制最大500KB大小 + long maxSizeBytes = 500 * 1024; + + long compressStart = System.currentTimeMillis(); + byte[] compressedBytes = compressImageToMaxSize(imagePath, maxSizeBytes); + long compressEnd = System.currentTimeMillis(); + System.out.println("Image compression time: " + (compressEnd - compressStart) + " ms"); + System.out.println("Compressed image size: " + compressedBytes.length + " bytes"); + + String base64Image = Base64.getEncoder().encodeToString(compressedBytes); + + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode payload = objectMapper.createObjectNode(); + payload.put("file", "http://117.72.182.135:9000/api/v1/download-shared-object/aHR0cDovLzEyNy4wLjAuMTo5MDAxL3Rlc3QvMjAyNDEwMDUwNDA3MzVBMjk0LmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUZZTEUwQ0NJOTJQOUYyQ0ozTUc5JTJGMjAyNTA4MTIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwODEyVDA2MDAwMVomWC1BbXotRXhwaXJlcz00MzE5OSZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSkdXVXhGTUVORFNUa3lVRGxHTWtOS00wMUhPU0lzSW1WNGNDSTZNVGMxTlRBeU1USTBPU3dpY0dGeVpXNTBJam9pWVdSdGFXNGlmUS5rcWFORTVfalRBOWlVZTl3Um1acjVhemxCbzQzeGVOWnYyb09GYWhjbjhXQ25UZzlqREt3bktVekJiemkxVWZEQUlqenhCaXlLN19GSXZaNjFSeXNrQSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmdmVyc2lvbklkPW51bGwmWC1BbXotU2lnbmF0dXJlPTk0MjQyZDIwMTgwNWM4NWM1ZWZjZTg5MzgxYWU5M2M4YjE0OWQ3N2EzNTZhYWEzMGJlYWVmNTlmODMxOTk5M2Y"); + payload.put("fileType", 1); + + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(0, java.util.concurrent.TimeUnit.MILLISECONDS) + .readTimeout(0, java.util.concurrent.TimeUnit.MILLISECONDS) + .writeTimeout(0, java.util.concurrent.TimeUnit.MILLISECONDS) + .build(); + + MediaType JSON = MediaType.get("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(JSON, payload.toString()); + + long ocrStart = System.currentTimeMillis(); + Request request = new Request.Builder() + .url(API_URL) + .post(body) + .build(); + + try (Response response = client.newCall(request).execute()) { + long ocrEnd = System.currentTimeMillis(); + System.out.println("OCR request elapsed time: " + (ocrEnd - ocrStart) + " ms"); + + if (response.isSuccessful()) { + String responseBody = response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + JsonNode result = root.get("result"); + + JsonNode ocrResults = result.get("ocrResults"); + for (int i = 0; i < ocrResults.size(); i++) { + JsonNode item = ocrResults.get(i); + + JsonNode prunedResult = item.get("prunedResult"); + System.out.println("Pruned Result [" + i + "]: " + prunedResult.toString()); + + // 提取 rec_texts 和 rec_scores + JsonNode recTextsNode = prunedResult.get("rec_texts"); + JsonNode recScoresNode = prunedResult.get("rec_scores"); + + if (recTextsNode != null && recScoresNode != null && recTextsNode.isArray() && recScoresNode.isArray()) { + System.out.println("rec_texts:"); + for (JsonNode textNode : recTextsNode) { + System.out.println(" " + textNode.asText()); + } + System.out.println("rec_scores:"); + for (JsonNode scoreNode : recScoresNode) { + System.out.println(" " + scoreNode.asDouble()); + } + } else { + System.out.println("rec_texts or rec_scores not found or invalid format."); + } + + // 保存 OCR 图片为文件 + String ocrImageBase64 = item.get("ocrImage").asText(); + byte[] ocrImageBytes = Base64.getDecoder().decode(ocrImageBase64); + String ocrImgPath = "ocr_result_" + i + ".jpg"; + + try (FileOutputStream fos = new FileOutputStream(ocrImgPath)) { + fos.write(ocrImageBytes); + System.out.println("Saved OCR image to: " + ocrImgPath); + } + } + } else { + System.err.println("Request failed with HTTP code: " + response.code()); + } + } + } + + + + /** + * 压缩图片,最大限制文件大小,不超过maxFileSizeBytes + * 通过递减压缩质量尝试实现大小限制,最低质量0.1 + */ + public static byte[] compressImageToMaxSize(String imagePath, long maxFileSizeBytes) throws IOException { + File file = new File(imagePath); + BufferedImage image = ImageIO.read(file); + if (image == null) { + throw new IOException("Failed to read image from " + imagePath); + } + + ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next(); + ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam(); + jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + + float quality = 0.9f; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + while (quality >= 0.1f) { + baos.reset(); + jpgWriteParam.setCompressionQuality(quality); + + try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { + jpgWriter.setOutput(ios); + jpgWriter.write(null, new IIOImage(image, null, null), jpgWriteParam); + } + + if (baos.size() <= maxFileSizeBytes) { + jpgWriter.dispose(); + return baos.toByteArray(); + } + quality -= 0.05f; + } + // 质量降至最低仍未达到限制,返回最后压缩结果 + jpgWriter.dispose(); + return baos.toByteArray(); + } +} diff --git a/code/go-project/base-farmework/cmd/app/main.go b/code/go-project/base-farmework/cmd/app/main.go index 9af23fdc..4e218a1f 100644 --- a/code/go-project/base-farmework/cmd/app/main.go +++ b/code/go-project/base-farmework/cmd/app/main.go @@ -16,16 +16,14 @@ func main() { log.Fatalf("加载配置失败: %v", err) } - log.Printf("服务器启动在端口 %d", appConfig.Server.Port) - - r := app.InitAppRouter() - err = appConfig.InitDBConfig("D:\\db.yaml") if err != nil { log.Fatalf("加载数据库配置失败: %v", err) return } + r := app.InitAppRouter() + err = http.ListenAndServe(":"+strconv.Itoa(appConfig.Server.Port), r) if err != nil { log.Fatalf("server start failed: %v", err) diff --git a/code/go-project/base-farmework/internal/app/api/test.go b/code/go-project/base-farmework/internal/app/api/test.go index fc94bb44..a52ef3f8 100644 --- a/code/go-project/base-farmework/internal/app/api/test.go +++ b/code/go-project/base-farmework/internal/app/api/test.go @@ -6,6 +6,6 @@ import ( ) func InitTest(r *router.Router) { - - r.GET("/test", handle.Test) + group := r.Group("/test") + group.GET("/", handle.Test) } diff --git a/code/go-project/base-farmework/internal/app/handle/test.go b/code/go-project/base-farmework/internal/app/handle/test.go index 5c9cb043..a97a4c8f 100644 --- a/code/go-project/base-farmework/internal/app/handle/test.go +++ b/code/go-project/base-farmework/internal/app/handle/test.go @@ -2,11 +2,9 @@ package handle import ( "base-framework/pkg/router" - "net/http" + "base-framework/pkg/utils/response" ) func Test(c *router.Context) { - c.JSON(http.StatusOK, map[string]string{ - "user_id": c.Param("id"), - }) + response.Success(c).Send() } diff --git a/code/go-project/base-farmework/internal/app/router.go b/code/go-project/base-farmework/internal/app/router.go index 99c416b8..ad167d3a 100644 --- a/code/go-project/base-farmework/internal/app/router.go +++ b/code/go-project/base-farmework/internal/app/router.go @@ -10,9 +10,8 @@ func InitAppRouter() *router.Router { r := router.NewRouter() r.Use(middleware.Recover()) - r.Use(middleware.Auth()).ExcludePaths("/login") + //r.Use(middleware.Auth()).ExcludePaths("/login") r.Use(middleware.Logger()) - initApi(r) return r diff --git a/code/go-project/base-farmework/pkg/middleware/logger.go b/code/go-project/base-farmework/pkg/middleware/logger.go index eb72aad2..cec9d7e2 100644 --- a/code/go-project/base-farmework/pkg/middleware/logger.go +++ b/code/go-project/base-farmework/pkg/middleware/logger.go @@ -2,14 +2,18 @@ package middleware import ( "base-framework/pkg/router" - "log" "time" ) func Logger() router.HandlerFunc { return func(c *router.Context) { start := time.Now() + // 执行后续中间件和处理器 c.Next() - log.Printf("[%s] %s in %v", c.Request.Method, c.Request.URL.Path, time.Since(start)) + elapsed := time.Since(start) + // 即使业务中断,这里也能执行,打印耗时 + path := c.Request.URL.Path + method := c.Request.Method + println(method, path, "耗时:", elapsed.String()) } } diff --git a/code/go-project/base-farmework/pkg/utils/jwt.go b/code/go-project/base-farmework/pkg/utils/jwt.go index 0543d3b0..13388c13 100644 --- a/code/go-project/base-farmework/pkg/utils/jwt.go +++ b/code/go-project/base-farmework/pkg/utils/jwt.go @@ -24,7 +24,6 @@ func CreateToken(orgID, userID string) (string, error) { StandardClaims: jwt.StandardClaims{ ExpiresAt: expireTime, IssuedAt: time.Now().Unix(), - Issuer: "your-app-name", // 可以自定义 }, } diff --git a/code/go-project/base-farmework/pkg/utils/response/response.go b/code/go-project/base-farmework/pkg/utils/response/response.go new file mode 100644 index 00000000..50f2f194 --- /dev/null +++ b/code/go-project/base-farmework/pkg/utils/response/response.go @@ -0,0 +1,100 @@ +package response + +import ( + "base-framework/pkg/router" + "net/http" +) + +// 统一响应结构体 +type Result struct { + Code string `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// 常量状态码 +const ( + CodeSuccess = "0000" + CodeFail = "9999" +) + +// code 对应默认提示 +var codeMessages = map[string]string{ + CodeSuccess: "请求成功", + CodeFail: "请求失败", + + "1001": "缺少用户ID", + "1002": "未授权", + "1003": "无权限访问", + "1004": "资源不存在", + + "9000": "系统内部错误", +} + +// 兜底提示 +const defaultMessage = "未知错误" + +// Builder 链式构造器 +type Builder struct { + c *router.Context + result Result +} + +// Success 构造成功响应 +func Success(c *router.Context) *Builder { + return &Builder{ + c: c, + result: Result{ + Code: CodeSuccess, + Message: getMessage(CodeSuccess), + }, + } +} + +// Error 构造失败响应 +func Error(c *router.Context) *Builder { + return &Builder{ + c: c, + result: Result{ + Code: CodeFail, + Message: getMessage(CodeFail), + }, + } +} + +// Code 设置状态码(自动填充默认提示,除非后面手动改) +func (b *Builder) Code(code string) *Builder { + b.result.Code = code + if b.result.Message == "" || b.result.Message == getMessage(b.result.Code) { + b.result.Message = getMessage(code) + } + return b +} + +// Message 设置提示消息(覆盖默认的) +func (b *Builder) Message(msg string) *Builder { + b.result.Message = msg + return b +} + +// Data 设置返回数据 +func (b *Builder) Data(data interface{}) *Builder { + b.result.Data = data + return b +} + +// Send 发送响应 +func (b *Builder) Send() { + if b.result.Data == nil { + b.result.Data = "" + } + b.c.JSON(http.StatusOK, b.result) +} + +// getMessage 获取状态码对应提示,兜底 defaultMessage +func getMessage(code string) string { + if msg, ok := codeMessages[code]; ok { + return msg + } + return defaultMessage +} diff --git a/notes/后端开发/paddlepaddle/paddlex.md b/notes/后端开发/paddlepaddle/paddlex.md index 04bb5b8c..3c9458ce 100644 --- a/notes/后端开发/paddlepaddle/paddlex.md +++ b/notes/后端开发/paddlepaddle/paddlex.md @@ -263,24 +263,39 @@ POST /ocr **Java调用实例** ```java +package cn.g3soft; + import okhttp3.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +import java.awt.image.BufferedImage; +import java.io.*; import java.util.Base64; -public class Main { - public static void main(String[] args) throws IOException { - String API_URL = "http://localhost:8080/ocr"; - String imagePath = "./demo.jpg"; +public class Test { - File file = new File(imagePath); - byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath()); - String base64Image = Base64.getEncoder().encodeToString(fileContent); + public static void main(String[] args) throws IOException { + String API_URL = "http://localhost:8866/ocr"; + String imagePath = "D:\\code\\python-project\\boxocr\\100-2\\20241004082017A007.jpg"; + + // 压缩并限制最大500KB大小 + long maxSizeBytes = 500 * 1024; + + long compressStart = System.currentTimeMillis(); + byte[] compressedBytes = compressImageToMaxSize(imagePath, maxSizeBytes); + long compressEnd = System.currentTimeMillis(); + System.out.println("Image compression time: " + (compressEnd - compressStart) + " ms"); + System.out.println("Compressed image size: " + compressedBytes.length + " bytes"); + + String base64Image = Base64.getEncoder().encodeToString(compressedBytes); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode payload = objectMapper.createObjectNode(); @@ -289,14 +304,18 @@ public class Main { OkHttpClient client = new OkHttpClient(); MediaType JSON = MediaType.get("application/json; charset=utf-8"); - RequestBody body = RequestBody.create(JSON, payload.toString()); + RequestBody body = RequestBody.create(JSON, payload.toString()); + long ocrStart = System.currentTimeMillis(); Request request = new Request.Builder() .url(API_URL) .post(body) .build(); try (Response response = client.newCall(request).execute()) { + long ocrEnd = System.currentTimeMillis(); + System.out.println("OCR request elapsed time: " + (ocrEnd - ocrStart) + " ms"); + if (response.isSuccessful()) { String responseBody = response.body().string(); JsonNode root = objectMapper.readTree(responseBody); @@ -309,9 +328,28 @@ public class Main { JsonNode prunedResult = item.get("prunedResult"); System.out.println("Pruned Result [" + i + "]: " + prunedResult.toString()); + // 提取 rec_texts 和 rec_scores + JsonNode recTextsNode = prunedResult.get("rec_texts"); + JsonNode recScoresNode = prunedResult.get("rec_scores"); + + if (recTextsNode != null && recScoresNode != null && recTextsNode.isArray() && recScoresNode.isArray()) { + System.out.println("rec_texts:"); + for (JsonNode textNode : recTextsNode) { + System.out.println(" " + textNode.asText()); + } + System.out.println("rec_scores:"); + for (JsonNode scoreNode : recScoresNode) { + System.out.println(" " + scoreNode.asDouble()); + } + } else { + System.out.println("rec_texts or rec_scores not found or invalid format."); + } + + // 保存 OCR 图片为文件 String ocrImageBase64 = item.get("ocrImage").asText(); byte[] ocrImageBytes = Base64.getDecoder().decode(ocrImageBase64); String ocrImgPath = "ocr_result_" + i + ".jpg"; + try (FileOutputStream fos = new FileOutputStream(ocrImgPath)) { fos.write(ocrImageBytes); System.out.println("Saved OCR image to: " + ocrImgPath); @@ -322,6 +360,54 @@ public class Main { } } } + + /** + * 压缩图片,最大限制文件大小,不超过maxFileSizeBytes + * 通过递减压缩质量尝试实现大小限制,最低质量0.1 + */ + public static byte[] compressImageToMaxSize(String imagePath, long maxFileSizeBytes) throws IOException { + File file = new File(imagePath); + BufferedImage image = ImageIO.read(file); + if (image == null) { + throw new IOException("Failed to read image from " + imagePath); + } + + ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next(); + ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam(); + jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + + float quality = 0.9f; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + while (quality >= 0.1f) { + baos.reset(); + jpgWriteParam.setCompressionQuality(quality); + + try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { + jpgWriter.setOutput(ios); + jpgWriter.write(null, new IIOImage(image, null, null), jpgWriteParam); + } + + if (baos.size() <= maxFileSizeBytes) { + jpgWriter.dispose(); + return baos.toByteArray(); + } + quality -= 0.05f; + } + // 质量降至最低仍未达到限制,返回最后压缩结果 + jpgWriter.dispose(); + return baos.toByteArray(); + } } + ``` + + +# 警告/报错 + +## Please use PaddlePaddle with GPU version. + +原因:当前 PaddlePaddle 版本不是GPU的,而是CPU的。 + +解决方法:安装GPU版本的PaddlePaddle