From edd94cdf5ab0260262e1597e1bbf19197dc2acd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 14:18:45 +0000 Subject: [PATCH 1/2] Initial plan From c3e5f0f5fc73891837884d87bafe9184a7c119ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 14:23:43 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=9E=E7=8E=B0OCR=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E6=8E=A5=E5=8F=A3=EF=BC=88cv/ocr/menu?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 WxOcrMenuResult Bean(weixin-java-common) - 在 WxOcrService 接口中添加 menu(String) 和 menu(File) 方法 - 在 WxMpApiUrl.Ocr 枚举中添加 MENU 和 FILE_MENU URL - 在 WxMpOcrServiceImpl 中实现 menu 方法 - 在 WxMaApiUrlConstants.Ocr 中添加菜单识别URL常量 - 在 WxMaOcrServiceImpl 中实现 menu 方法 - 在 WxMpOcrServiceImplTest 中添加 testMenu 和 testMenu2 测试用例 --- .../common/bean/ocr/WxOcrMenuResult.java | 47 +++++++++++++++ .../weixin/common/service/WxOcrService.java | 19 ++++++ .../miniapp/api/impl/WxMaOcrServiceImpl.java | 19 ++++++ .../miniapp/constant/WxMaApiUrlConstants.java | 2 + .../mp/api/impl/WxMpOcrServiceImpl.java | 20 +++++++ .../chanjar/weixin/mp/enums/WxMpApiUrl.java | 12 +++- .../mp/api/impl/WxMpOcrServiceImplTest.java | 58 +++++++++++++++++++ 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java new file mode 100644 index 0000000000..6f5a2bc27e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * OCR菜单识别结果. + * + * @author GitHub Copilot + */ +@Data +public class WxOcrMenuResult implements Serializable { + private static final long serialVersionUID = -8062516251827437945L; + + @SerializedName("img_size") + private WxOcrImgSize imgSize; + @SerializedName("items") + private List items; + + public static WxOcrMenuResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrMenuResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Items implements Serializable { + private static final long serialVersionUID = 3066181677009102792L; + + @SerializedName("text") + private String text; + @SerializedName("pos") + private WxOcrPos pos; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java index d0aeef8491..08524eeafc 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java @@ -7,6 +7,7 @@ import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingLicenseResult; import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingResult; import me.chanjar.weixin.common.bean.ocr.WxOcrIdCardResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrMenuResult; import java.io.File; @@ -130,4 +131,22 @@ public interface WxOcrService { * @throws WxErrorException . */ WxOcrCommResult comm(File imgFile) throws WxErrorException; + + /** + * 菜单OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxOcrMenuResult + * @throws WxErrorException . + */ + WxOcrMenuResult menu(String imgUrl) throws WxErrorException; + + /** + * 菜单OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxOcrMenuResult + * @throws WxErrorException . + */ + WxOcrMenuResult menu(File imgFile) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java index 8faef8e30e..90842242fb 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java @@ -138,4 +138,23 @@ public WxOcrCommResult comm(File imgFile) throws WxErrorException { FILE_COMM, imgFile); return WxOcrCommResult.fromJson(result); } + + @Override + public WxOcrMenuResult menu(String imgUrl) throws WxErrorException { + try { + imgUrl = URLEncoder.encode(imgUrl, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // ignore cannot happen + } + + final String result = this.service.post(String.format(MENU, imgUrl), (String) null); + return WxOcrMenuResult.fromJson(result); + } + + @Override + public WxOcrMenuResult menu(File imgFile) throws WxErrorException { + String result = this.service.execute(OcrDiscernRequestExecutor.create(this.service.getRequestHttp()), + FILE_MENU, imgFile); + return WxOcrMenuResult.fromJson(result); + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java index 815d47c623..81ab9e2508 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java @@ -393,6 +393,8 @@ public interface Ocr { String FILE_BIZ_LICENSE = "https://api.weixin.qq.com/cv/ocr/bizlicense"; String COMM = "https://api.weixin.qq.com/cv/ocr/comm?img_url=%s"; String FILE_COMM = "https://api.weixin.qq.com/cv/ocr/comm"; + String MENU = "https://api.weixin.qq.com/cv/ocr/menu?img_url=%s"; + String FILE_MENU = "https://api.weixin.qq.com/cv/ocr/menu"; } public interface Product { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java index 1c8221338f..3a2928b455 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java @@ -143,4 +143,24 @@ public WxOcrCommResult comm(File imgFile) throws WxErrorException { FILE_COMM.getUrl(this.mainService.getWxMpConfigStorage()), imgFile); return WxOcrCommResult.fromJson(result); } + + @Override + public WxOcrMenuResult menu(String imgUrl) throws WxErrorException { + try { + imgUrl = URLEncoder.encode(imgUrl, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // ignore cannot happen + } + + final String result = this.mainService.post(String.format(MENU.getUrl(this.mainService.getWxMpConfigStorage()), + imgUrl), (String) null); + return WxOcrMenuResult.fromJson(result); + } + + @Override + public WxOcrMenuResult menu(File imgFile) throws WxErrorException { + String result = this.mainService.execute(OcrDiscernRequestExecutor.create(this.mainService.getRequestHttp()), + FILE_MENU.getUrl(this.mainService.getWxMpConfigStorage()), imgFile); + return WxOcrMenuResult.fromJson(result); + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java index dc317bd40e..e6e8b9af48 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java @@ -533,7 +533,17 @@ enum Ocr implements WxMpApiUrl { /** * 通用印刷体OCR识别(文件) */ - FILE_COMM(API_DEFAULT_HOST_URL, "/cv/ocr/comm"); + FILE_COMM(API_DEFAULT_HOST_URL, "/cv/ocr/comm"), + + /** + * 菜单OCR识别 + */ + MENU(API_DEFAULT_HOST_URL, "/cv/ocr/menu?img_url=%s"), + + /** + * 菜单OCR识别(文件) + */ + FILE_MENU(API_DEFAULT_HOST_URL, "/cv/ocr/menu"); private final String prefix; private final String path; diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java index 7e9d477872..c8b44ad290 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java @@ -11,6 +11,7 @@ import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingLicenseResult; import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingResult; import me.chanjar.weixin.common.bean.ocr.WxOcrIdCardResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrMenuResult; import org.testng.annotations.Guice; import org.testng.annotations.Test; @@ -137,6 +138,22 @@ public void testComm2() throws Exception { System.out.println(result); } + @Test + public void testMenu() throws WxErrorException { + final WxOcrMenuResult result = this.service.getOcrService().menu("https://res.wx.qq.com/op_res/apCy0YbnEdjYsa_cjW6x3FlpCc20uQ-2BYE7aXnFsrB-ALHZNgdKXhzIUcrRnDoL"); + assertThat(result).isNotNull(); + System.out.println(result); + } + + @Test + public void testMenu2() throws Exception { + InputStream inputStream = ClassLoader.getSystemResourceAsStream("mm.jpeg"); + File tempFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), TestConstants.FILE_JPG); + final WxOcrMenuResult result = this.service.getOcrService().menu(tempFile); + assertThat(result).isNotNull(); + System.out.println(result); + } + private InputStream getImageStream(String url) { try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); @@ -390,5 +407,46 @@ public void testComm() throws Exception { assertThat(result).isNotNull(); System.out.println(result); } + + @Test + public void testMenu() throws Exception { + String returnJson = "{\n" + + " \"errcode\": 0, \n" + + " \"errmsg\": \"ok\", \n" + + " \"items\": [\n" + + " {\n" + + " \"text\": \"红烧肉\", \n" + + " \"pos\": {\n" + + " \"left_top\": {\n" + + " \"x\": 575, \n" + + " \"y\": 519\n" + + " }, \n" + + " \"right_top\": {\n" + + " \"x\": 744, \n" + + " \"y\": 519\n" + + " }, \n" + + " \"right_bottom\": {\n" + + " \"x\": 744, \n" + + " \"y\": 532\n" + + " }, \n" + + " \"left_bottom\": {\n" + + " \"x\": 573, \n" + + " \"y\": 532\n" + + " }\n" + + " }\n" + + " }\n" + + " ], \n" + + " \"img_size\": {\n" + + " \"w\": 1280, \n" + + " \"h\": 720\n" + + " }\n" + + "}"; + when(wxService.post(anyString(), anyString())).thenReturn(returnJson); + final WxMpOcrServiceImpl wxMpOcrService = new WxMpOcrServiceImpl(wxService); + + final WxOcrMenuResult result = wxMpOcrService.menu("abc"); + assertThat(result).isNotNull(); + System.out.println(result); + } } }