diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java index 45a869216e..bf8176718a 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java @@ -11,7 +11,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.WxChannelBasicService; import me.chanjar.weixin.channel.bean.address.AddressCodeResponse; @@ -28,6 +27,7 @@ import me.chanjar.weixin.channel.util.ResponseUtils; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; import me.chanjar.weixin.common.util.http.RequestExecutor; /** @@ -76,12 +76,11 @@ public QualificationFileResponse uploadQualificationFile(File file) throws WxErr @Override public ChannelImageResponse getImg(String mediaId) throws WxErrorException { - String appId = shopService.getConfig().getAppid(); ChannelImageResponse rs; try { String url = GET_IMG_URL + "?media_id=" + mediaId; RequestExecutor executor = ChannelMediaDownloadRequestExecutor.create(shopService, - Files.createTempDirectory("wxjava-channel-" + appId).toFile()); + FileUtils.getTmpDir()); rs = shopService.execute(executor, url, null); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java index 855897f223..ea7f8add0f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java @@ -16,6 +16,7 @@ * @author Daniel Qian */ public class FileUtils { + private static volatile File tmpDir; /** * 创建临时文件. @@ -41,14 +42,39 @@ private static void copyToFile(final InputStream source, final File destination) } /** - * 创建临时文件. + * 创建临时文件,文件位于 {@link #getTmpDir()} 返回的目录中. * * @param inputStream 输入文件流 * @param name 文件名 * @param ext 扩展名 */ public static File createTmpFile(InputStream inputStream, String name, String ext) throws IOException { - return createTmpFile(inputStream, name, ext, Files.createTempDirectory("wxjava-temp").toFile()); + return createTmpFile(inputStream, name, ext, getTmpDir()); + } + + /** + * 获取 SDK 共用的临时目录. + *

+ * 目录在首次调用时创建,之后的调用都复用它,不会每次调用都新建一个永远不会被删除的临时目录; + * 目录被外部清理掉后,下次调用会重新创建。 + *

+ * + * @return 临时目录 + * @throws IOException 无法创建临时目录时抛出 + */ + public static File getTmpDir() throws IOException { + File dir = tmpDir; + if (dir == null || !dir.isDirectory()) { + synchronized (FileUtils.class) { + dir = tmpDir; + if (dir == null || !dir.isDirectory()) { + dir = Files.createTempDirectory("wxjava-temp").toFile(); + dir.deleteOnExit(); + tmpDir = dir; + } + } + } + return dir; } /** diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java index 23e761efd9..a4b2d59df8 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java @@ -32,6 +32,34 @@ public void testCreateTmpFile() throws IOException { public void testTestCreateTmpFile() { } + @Test + public void testCreateTmpFileReusesOneTmpDir() throws IOException { + File first = FileUtils.createTmpFile(new ByteArrayInputStream("a".getBytes()), "first", "txt"); + File second = FileUtils.createTmpFile(new ByteArrayInputStream("b".getBytes()), "second", "txt"); + try { + // 每次调用都新建一个临时目录的话,调用方删除文件后目录仍会一直残留 + assertThat(second.getParentFile()).isEqualTo(first.getParentFile()); + } finally { + assertThat(first.delete()).isTrue(); + assertThat(second.delete()).isTrue(); + } + } + + @Test + public void testGetTmpDirIsRecreatedAfterDeletion() throws IOException { + File tmpDir = FileUtils.getTmpDir(); + assertThat(FileUtils.getTmpDir()).isEqualTo(tmpDir); + + org.apache.commons.io.FileUtils.deleteDirectory(tmpDir); + assertThat(tmpDir).doesNotExist(); + + File recreated = FileUtils.getTmpDir(); + assertThat(recreated).isDirectory(); + File tmpFile = FileUtils.createTmpFile(new ByteArrayInputStream("c".getBytes()), "recreated", "txt"); + assertThat(tmpFile.getParentFile()).isEqualTo(recreated); + assertThat(tmpFile.delete()).isTrue(); + } + @Test public void testImageToBase64ByStream() { byte[] original = new byte[5000]; diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index 67c5d585a6..a2ce1f6211 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -12,6 +12,7 @@ + diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java index d43589595f..96c1a7f757 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java @@ -742,8 +742,12 @@ public WxCpProductAlbumResult getProductAlbum(String productId) throws WxErrorEx @Override public WxMediaUploadResult uploadAttachment(String mediaType, String fileType, Integer attachmentType, InputStream inputStream) throws WxErrorException, IOException { - return uploadAttachment(mediaType, attachmentType, FileUtils.createTmpFile(inputStream, - UUID.randomUUID().toString(), fileType)); + File tmpFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType); + try { + return uploadAttachment(mediaType, attachmentType, tmpFile); + } finally { + tmpFile.delete(); + } } @Override diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java index a128a35ccb..a3a0e37744 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java @@ -48,7 +48,12 @@ public class WxCpMediaServiceImpl implements WxCpMediaService { @Override public WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException { - return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); + File tmpFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType); + try { + return this.upload(mediaType, tmpFile); + } finally { + tmpFile.delete(); + } } @Override diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java index 326c9dd667..d3ebf2b861 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java @@ -31,7 +31,12 @@ public class WxCpTpMediaServiceImpl implements WxCpTpMediaService { @Override public WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream, String corpId) throws WxErrorException, IOException { - return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType), corpId); + File tmpFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType); + try { + return this.upload(mediaType, tmpFile, corpId); + } finally { + tmpFile.delete(); + } } @Override diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTmpFileCleanupTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTmpFileCleanupTest.java new file mode 100644 index 0000000000..23d737a13d --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTmpFileCleanupTest.java @@ -0,0 +1,84 @@ +package me.chanjar.weixin.cp.api.impl; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.impl.WxCpTpMediaServiceImpl; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +/** + * 验证通过输入流上传时,SDK 内部创建的临时文件在上传后被删除. + */ +public class WxCpTmpFileCleanupTest { + + @Test + public void mediaUploadFromStreamDeletesTmpFile() throws Exception { + WxCpMediaServiceImpl mediaService = spy(new WxCpMediaServiceImpl(mock(WxCpService.class))); + WxMediaUploadResult result = new WxMediaUploadResult(); + List uploaded = new ArrayList<>(); + doAnswer(invocation -> { + File file = invocation.getArgument(1); + assertTrue(file.isFile()); + uploaded.add(file); + return result; + }).when(mediaService).upload(eq("image"), any(File.class)); + + assertSame(mediaService.upload("image", "jpg", new ByteArrayInputStream(new byte[]{1, 2, 3})), result); + + assertEquals(uploaded.size(), 1); + assertFalse(uploaded.get(0).exists()); + } + + @Test + public void tpMediaUploadFromStreamDeletesTmpFile() throws Exception { + WxCpTpMediaServiceImpl mediaService = spy(new WxCpTpMediaServiceImpl(mock(WxCpTpService.class))); + WxMediaUploadResult result = new WxMediaUploadResult(); + List uploaded = new ArrayList<>(); + doAnswer(invocation -> { + File file = invocation.getArgument(1); + assertTrue(file.isFile()); + uploaded.add(file); + return result; + }).when(mediaService).upload(eq("image"), any(File.class), eq("CORP_ID")); + + assertSame(mediaService.upload("image", "jpg", new ByteArrayInputStream(new byte[]{1, 2, 3}), "CORP_ID"), + result); + + assertEquals(uploaded.size(), 1); + assertFalse(uploaded.get(0).exists()); + } + + @Test + public void externalContactAttachmentFromStreamDeletesTmpFile() throws Exception { + WxCpExternalContactServiceImpl contactService = spy(new WxCpExternalContactServiceImpl(mock(WxCpService.class))); + WxMediaUploadResult result = new WxMediaUploadResult(); + List uploaded = new ArrayList<>(); + doAnswer(invocation -> { + File file = invocation.getArgument(2); + assertTrue(file.isFile()); + uploaded.add(file); + return result; + }).when(contactService).uploadAttachment(eq("image"), eq(1), any(File.class)); + + assertSame(contactService.uploadAttachment("image", "jpg", 1, new ByteArrayInputStream(new byte[]{1, 2, 3})), + result); + + assertEquals(uploaded.size(), 1); + assertFalse(uploaded.get(0).exists()); + } +} diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml index 6e586686fd..d0ea5067d2 100644 --- a/weixin-java-cp/src/test/resources/testng.xml +++ b/weixin-java-cp/src/test/resources/testng.xml @@ -11,6 +11,7 @@ + diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java index b4f92e6e30..7b1124b298 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java @@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.json.GsonHelper; @@ -42,11 +43,10 @@ public void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException @Override public byte[] getQrCode(String path) throws WxErrorException { - String appId = this.service.getWxMaConfig().getAppid(); Path qrCodeFilePath = null; try { RequestExecutor executor = BaseMediaDownloadRequestExecutor - .create(this.service.getRequestHttp(), Files.createTempDirectory("wxjava-ma-" + appId).toFile()); + .create(this.service.getRequestHttp(), FileUtils.getTmpDir()); final StringBuilder url = new StringBuilder(GET_QRCODE_URL); if (StringUtils.isNotBlank(path)) { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java index c534bd6938..7b767585da 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java @@ -90,10 +90,16 @@ public String uploadResponseImage(File imageFile) throws WxErrorException, IOExc @Override public String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException { + File tmpFile = null; try { - return this.uploadResponseImage(FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileName)); + tmpFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileName); + return this.uploadResponseImage(tmpFile); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); + } finally { + if (tmpFile != null) { + tmpFile.delete(); + } } } } \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMediaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMediaServiceImpl.java index 0310cd0994..c922c8f571 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMediaServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMediaServiceImpl.java @@ -14,7 +14,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; import java.util.UUID; import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Media.MEDIA_GET_URL; @@ -29,10 +28,16 @@ public class WxMaMediaServiceImpl implements WxMaMediaService { @Override public WxMediaUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException { + File tmpFile = null; try { - return this.uploadMedia(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); + tmpFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType); + return this.uploadMedia(mediaType, tmpFile); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); + } finally { + if (tmpFile != null) { + tmpFile.delete(); + } } } @@ -48,7 +53,7 @@ public WxMediaUploadResult uploadMedia(String mediaType, File file) throws WxErr public File getMedia(String mediaId) throws WxErrorException { try { RequestExecutor executor = BaseMediaDownloadRequestExecutor - .create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("wxma").toFile()); + .create(this.wxMaService.getRequestHttp(), FileUtils.getTmpDir()); return this.wxMaService.execute(executor, MEDIA_GET_URL, "media_id=" + mediaId); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaTmpFileCleanupTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaTmpFileCleanupTest.java new file mode 100644 index 0000000000..348c127cfc --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaTmpFileCleanupTest.java @@ -0,0 +1,115 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.sun.net.httpserver.HttpServer; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +/** + * 验证 SDK 内部创建的临时文件用完即删除,且不会每次调用都新建一个临时目录. + */ +public class WxMaTmpFileCleanupTest { + + @Test + public void getQrCodeReusesOneTmpDir() throws Exception { + byte[] image = {1, 2, 3, 4}; + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + server.createContext("/wxa/get_qrcode", exchange -> { + exchange.getResponseHeaders().add("Content-Type", "image/jpeg"); + exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=\"qrcode.jpg\""); + exchange.sendResponseHeaders(200, image.length); + try (OutputStream out = exchange.getResponseBody()) { + out.write(image); + } + }); + server.start(); + WxMaServiceImpl service = spy(new WxMaServiceImpl()); + try { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + config.setAppid("FAKE_APP_ID"); + config.setSecret("FAKE_SECRET"); + config.setApiHostUrl("http://127.0.0.1:" + server.getAddress().getPort()); + config.updateAccessToken("FAKE_ACCESS_TOKEN", 7200); + service.setWxMaConfig(config); + + List downloaded = new ArrayList<>(); + doAnswer(invocation -> { + Object result = invocation.callRealMethod(); + downloaded.add((File) result); + return result; + }).when(service).execute(any(RequestExecutor.class), anyString(), isNull()); + + WxMaCodeServiceImpl codeService = new WxMaCodeServiceImpl(service); + assertEquals(codeService.getQrCode(null), image); + assertEquals(codeService.getQrCode("pages/index"), image); + + assertEquals(downloaded.size(), 2); + assertFalse(downloaded.get(0).exists()); + assertFalse(downloaded.get(1).exists()); + assertEquals(downloaded.get(1).getParentFile(), downloaded.get(0).getParentFile()); + } finally { + service.getRequestHttpClient().close(); + server.stop(0); + } + } + + @Test + public void uploadMediaFromStreamDeletesTmpFile() throws Exception { + WxMaMediaServiceImpl mediaService = spy(new WxMaMediaServiceImpl(mock(WxMaService.class))); + WxMediaUploadResult result = new WxMediaUploadResult(); + List uploaded = new ArrayList<>(); + doAnswer(invocation -> { + File file = invocation.getArgument(1); + assertTrue(file.isFile()); + uploaded.add(file); + return result; + }).when(mediaService).uploadMedia(eq("image"), any(File.class)); + + assertSame(mediaService.uploadMedia("image", "jpg", new ByteArrayInputStream(new byte[]{1, 2, 3})), result); + assertSame(mediaService.uploadMedia("image", "jpg", new ByteArrayInputStream(new byte[]{4, 5, 6})), result); + + assertEquals(uploaded.size(), 2); + assertFalse(uploaded.get(0).exists()); + assertFalse(uploaded.get(1).exists()); + assertEquals(uploaded.get(1).getParentFile(), uploaded.get(0).getParentFile()); + } + + @Test + public void uploadResponseImageFromStreamDeletesTmpFile() throws Exception { + WxMaComplaintServiceImpl complaintService = spy(new WxMaComplaintServiceImpl(mock(WxMaService.class))); + List uploaded = new ArrayList<>(); + doAnswer(invocation -> { + File file = invocation.getArgument(0); + assertTrue(file.isFile()); + uploaded.add(file); + return "MEDIA_ID"; + }).when(complaintService).uploadResponseImage(any(File.class)); + + assertEquals(complaintService.uploadResponseImage(new ByteArrayInputStream(new byte[]{1, 2, 3}), "jpg"), + "MEDIA_ID"); + + assertEquals(uploaded.size(), 1); + assertFalse(uploaded.get(0).exists()); + } +} diff --git a/weixin-java-miniapp/src/test/resources/testng.xml b/weixin-java-miniapp/src/test/resources/testng.xml index 8d38decfe7..3a546027fb 100644 --- a/weixin-java-miniapp/src/test/resources/testng.xml +++ b/weixin-java-miniapp/src/test/resources/testng.xml @@ -6,6 +6,7 @@ + diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java index 8a202ed27b..84874f0d67 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java @@ -7,6 +7,7 @@ import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.open.executor.CommonUploadMultiRequestExecutor; +import me.chanjar.weixin.common.util.fs.FileUtils; import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.open.api.WxOpenMaIcpService; @@ -16,7 +17,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; /** * @author xzh @@ -209,7 +209,7 @@ public WxOpenQueryIcpSubjectTypeResult queryIcpSubjectTypes() throws WxErrorExce public File getIcpMedia(String mediaId) throws WxErrorException { try { RequestExecutor executor = BaseMediaDownloadRequestExecutor - .create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("wxma").toFile()); + .create(this.wxMaService.getRequestHttp(), FileUtils.getTmpDir()); return this.wxMaService.execute(executor, GET_ICP_MEDIA, "media_id=" + mediaId); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index 2dcb418cd0..1e8b902919 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -33,6 +33,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxRuntimeException; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.apache.http.entity.ContentType; @@ -1201,9 +1202,10 @@ private WxPayBillResult handleBill(String billType, String responseContent) { } private String handleGzipBill(String url, String requestStr) throws WxPayException { + Path tempDirectory = null; try { byte[] responseBytes = this.postForBytes(url, requestStr, false); - Path tempDirectory = Files.createTempDirectory("bill"); + tempDirectory = Files.createTempDirectory("bill"); Path path = Paths.get(tempDirectory.toString(), System.currentTimeMillis() + ".gzip"); Files.write(path, responseBytes); try { @@ -1219,6 +1221,10 @@ private String handleGzipBill(String url, String requestStr) throws WxPayExcepti } } catch (Exception e) { throw new WxPayException("解析对账单文件时出错!", e); + } finally { + if (tempDirectory != null) { + FileUtils.deleteQuietly(tempDirectory.toFile()); + } } } @@ -1252,9 +1258,10 @@ public WxPayFundFlowResult downloadFundFlow(WxPayDownloadFundFlowRequest request } private String handleGzipFundFlow(String url, String requestStr) throws WxPayException { + Path tempDirectory = null; try { byte[] responseBytes = this.postForBytes(url, requestStr, true); - Path tempDirectory = Files.createTempDirectory("fundFlow"); + tempDirectory = Files.createTempDirectory("fundFlow"); Path path = Paths.get(tempDirectory.toString(), System.currentTimeMillis() + ".gzip"); Files.write(path, responseBytes); @@ -1273,6 +1280,10 @@ private String handleGzipFundFlow(String url, String requestStr) throws WxPayExc throw wxPayException; } catch (Exception e) { throw new WxPayException("解压zip文件出错", e); + } finally { + if (tempDirectory != null) { + FileUtils.deleteQuietly(tempDirectory.toFile()); + } } } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayGzipBillTmpFileTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayGzipBillTmpFileTest.java new file mode 100644 index 0000000000..001943f3dc --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayGzipBillTmpFileTest.java @@ -0,0 +1,64 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.WxPayDownloadBillRequest; +import com.github.binarywang.wxpay.config.WxPayConfig; +import org.testng.annotations.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; +import java.util.zip.GZIPOutputStream; + +import static org.testng.Assert.assertEquals; + +/** + * 验证下载 GZIP 对账单后不会在系统临时目录中残留临时目录和文件. + */ +public class WxPayGzipBillTmpFileTest { + + @Test + public void gzipBillDownloadLeavesNoTmpFiles() throws Exception { + String bill = "交易时间,公众账号ID,商户号\n`2026-11-01 10:00:00,`wx0000000000000000,`1900000000\n总交易单数\n`1"; + byte[] gzipBill = gzip(bill); + WxPayServiceApacheHttpImpl payService = new WxPayServiceApacheHttpImpl() { + @Override + public byte[] postForBytes(String url, String requestStr, boolean useKey) { + return gzipBill; + } + }; + WxPayConfig config = new WxPayConfig(); + config.setAppId("wx0000000000000000"); + config.setMchId("1900000000"); + config.setMchKey("0123456789abcdef0123456789abcdef"); + payService.setConfig(config); + + File tmpDir = new File(System.getProperty("java.io.tmpdir")); + Set before = billTmpEntries(tmpDir); + + WxPayDownloadBillRequest request = new WxPayDownloadBillRequest(); + request.setBillDate("20261101"); + request.setBillType("ALL"); + request.setTarType("GZIP"); + assertEquals(payService.downloadRawBill(request), bill); + assertEquals(payService.downloadRawBill(request), bill); + + assertEquals(billTmpEntries(tmpDir), before); + } + + private static Set billTmpEntries(File tmpDir) { + String[] names = tmpDir.list((dir, name) -> name.startsWith("bill")); + return names == null ? new TreeSet<>() : new TreeSet<>(Arrays.asList(names)); + } + + private static byte[] gzip(String content) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { + gzip.write(content.getBytes(StandardCharsets.UTF_8)); + } + return out.toByteArray(); + } +} diff --git a/weixin-java-pay/src/test/resources/testng.xml b/weixin-java-pay/src/test/resources/testng.xml index b44e6f3870..1138faa426 100644 --- a/weixin-java-pay/src/test/resources/testng.xml +++ b/weixin-java-pay/src/test/resources/testng.xml @@ -55,6 +55,7 @@ +