From cb767e3746c53798c15f2df53dfd223d81f99505 Mon Sep 17 00:00:00 2001 From: lzx_win <2602107437@qq.com> Date: Wed, 24 Sep 2025 18:02:24 +0800 Subject: [PATCH] 1 --- .../controller/AppletApiBooksController.java | 1 - .../controller/AppletApiPromotionController.java | 8 + .../applet/service/AppletApiWaterService.java | 2 + .../service/impl/AppletApiWaterServiceImpl.java | 218 +++++++++++++++++---- .../src/views/applet/config/AppletConfigList.vue | 10 +- 5 files changed, 190 insertions(+), 49 deletions(-) diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java index cea158a..3fb3d19 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java @@ -135,7 +135,6 @@ public class AppletApiBooksController { * * @return 删除书桌 */ - @IgnoreAuth @Operation(summary = "删除书桌", description = "删除书桌,批量删除用,分割") @PostMapping(value = "/delStand") public Result delStand(@Parameter(description = "书籍id") @RequestParam String id) { diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java index 4a3728f..0fe2df7 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java @@ -62,6 +62,14 @@ public class AppletApiPromotionController { return Result.OK(With); } + + @Operation(summary = "提现成功", description = "参数:提现id") + @PostMapping(value = "/withdrawSuccess") + public Result withdrawSuccess(@RequestParam @Parameter(description = "提现id") String id) { + appletApiWaterService.withdrawSuccess(id); + return Result.OK(); + } + @Operation(summary = "推广统计", description = "推广统计") @GetMapping(value = "/statistics") public Result statistics() { diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java index 4c50edf..e373b5b 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java @@ -37,6 +37,8 @@ public interface AppletApiWaterService { AppletWithdrawal getWithdraw(AppletWithdrawal appletWithdrawal); + void withdrawSuccess(String id); + StatisticsVo statistics(); } diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java index 970403c..38a6636 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java @@ -11,6 +11,8 @@ import org.jeecg.common.api.vo.Result; import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.system.util.AppletUserUtil; import org.jeecg.common.system.vo.AppletUser; +import org.jeecg.common.util.RedisUtil; +import org.jeecg.common.util.oss.OssBootUtil; import org.jeecg.modules.applet.entity.StatisticsVo; import org.jeecg.modules.applet.service.AppletApiWaterService; import org.jeecg.modules.common.IdUtils; @@ -27,6 +29,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -37,12 +40,16 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.InputStream; import java.math.BigDecimal; import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Log4j2 @Service @@ -59,6 +66,11 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { @Autowired private WxHttpUtils wxHttpUtils; + @Autowired + private RedisUtil redisUtil; + + // 创建线程池用于异步处理 + private final ExecutorService asyncExecutor = Executors.newFixedThreadPool(5); @Override public byte[] getInviteCode(){ @@ -79,62 +91,173 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { String xcxSharePage = appletConfigService.getContentByCode("xcxSharePage"); String backgroundImageUrl = appletConfigService.getContentByCode("qr_code_bg"); + // 优化缓存策略:使用更精确的缓存key,包含所有影响因素(包括背景图片URL) + String cacheKey = String.format("inviteCode:final:%s:%s:%s:%s", user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode()); + String imageCacheKey = String.format("inviteCode:image:%s:%s:%s:%s", user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode()); + + // 首先检查是否有最终合成图片的缓存 + byte[] cachedImage = (byte[]) redisUtil.get(imageCacheKey); + if (cachedImage != null) { + log.info("从缓存返回邀请码图片,用户ID: {}", user.getId()); + return cachedImage; + } + + // 检查OSS中是否已存在最终图片 + String finalPath = "invite/final/" + user.getId() + "_" + xcxSharePage + "_" + trial + ".jpg"; + try { + InputStream ossFile = OssBootUtil.getOssFile(finalPath, null); + if (ossFile != null) { + try { + // 使用ByteArrayOutputStream读取InputStream中的所有字节 + java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + while ((nRead = ossFile.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + byte[] ossImageBytes = buffer.toByteArray(); + + // 将OSS中的图片缓存到Redis,设置24小时过期 + redisUtil.set(imageCacheKey, ossImageBytes, 24 * 60 * 60); + log.info("从OSS返回邀请码图片,用户ID: {}", user.getId()); + return ossImageBytes; + } finally { + ossFile.close(); + } + } + } catch (Exception e) { + log.debug("OSS中未找到最终图片,需要重新生成,用户ID: {}", user.getId()); + } + try { + // 检查是否有小程序码的缓存 + String qrCodeCacheKey = String.format("inviteCode:qrcode:%s:%s:%s", user.getId(), trial, xcxSharePage.hashCode()); + byte[] cachedQrCode = (byte[]) redisUtil.get(qrCodeCacheKey); + + byte[] qrCodeBytes; + if (cachedQrCode != null) { + log.info("使用缓存的小程序码,用户ID: {}", user.getId()); + qrCodeBytes = cachedQrCode; + } else { + log.info("生成新的小程序码,用户ID: {}", user.getId()); + qrCodeBytes = generateWxQrCode(user, xcxSharePage, trial); + // 缓存小程序码,设置7天过期(小程序码相对稳定) + redisUtil.set(qrCodeCacheKey, qrCodeBytes, 7 * 24 * 60 * 60); + } - // 准备微信API请求参数 - String key = "shareId=" + user.getId(); - Map param = new HashMap<>(); - param.put("path", xcxSharePage + "?" + key); - param.put("scene", user.getId()); - param.put("width", 150); - param.put("auto_color", false); - param.put("env_version", trial); - - Map line_color = new HashMap<>(); - line_color.put("r", 0); - line_color.put("g", 0); - line_color.put("b", 0); - param.put("line_color", line_color); - - - param.put("is_hyaline", true); - - // 获取微信小程序码 - String accessToken = wxHttpUtils.getAccessToken(); - String url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + accessToken; - - // 请求微信API获取二维码图片数据 - RestTemplate rest = new RestTemplate(); - MultiValueMap headers = new LinkedMultiValueMap<>(); - HttpEntity requestEntity = new HttpEntity(JSON.toJSONString(param), headers); - ResponseEntity entity = rest.exchange(url, HttpMethod.POST, requestEntity, byte[].class, new Object[0]); - byte[] qrCodeBytes = entity.getBody(); - -// return qrCodeBytes; - return this.generateAndCombineImagesFromUrl2(qrCodeBytes, backgroundImageUrl); + // 生成最终合成图片 + byte[] finalImage = this.generateAndCombineImagesFromUrl2(qrCodeBytes, backgroundImageUrl); + + // 异步上传到OSS并缓存 + uploadAndCacheAsync(finalImage, finalPath, imageCacheKey); + + return finalImage; } catch (Exception e) { - e.printStackTrace(); + log.error("生成邀请码失败,用户ID: {}", user.getId(), e); return null; } } + /** + * 生成微信小程序码 + */ + private byte[] generateWxQrCode(AppletUser user, String xcxSharePage, String trial) throws Exception { + // 准备微信API请求参数 + String key = "inviter=" + user.getId(); + Map param = new HashMap<>(); + param.put("path", xcxSharePage + "?" + key); + param.put("scene", user.getId()); + param.put("width", 150); + param.put("auto_color", false); + param.put("env_version", trial); + + Map line_color = new HashMap<>(); + line_color.put("r", 0); + line_color.put("g", 0); + line_color.put("b", 0); + param.put("line_color", line_color); + param.put("is_hyaline", true); + + // 获取微信小程序码 + String accessToken = wxHttpUtils.getAccessToken(); + String url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + accessToken; + + // 请求微信API获取二维码图片数据 + RestTemplate rest = new RestTemplate(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpEntity requestEntity = new HttpEntity(JSON.toJSONString(param), headers); + ResponseEntity entity = rest.exchange(url, HttpMethod.POST, requestEntity, byte[].class, new Object[0]); + + return entity.getBody(); + } + + /** + * 异步上传到OSS并缓存到Redis + */ + private void uploadAndCacheAsync(byte[] imageBytes, String ossPath, String cacheKey) { + // 使用线程池异步执行上传和缓存操作 + CompletableFuture.runAsync(() -> { + try { + // 上传到OSS + OssBootUtil.upload(new ByteArrayInputStream(imageBytes), ossPath); + log.info("异步上传OSS完成,路径: {}", ossPath); + } catch (Exception e) { + log.error("异步上传OSS失败,路径: {}", ossPath, e); + } + }, asyncExecutor).thenRunAsync(() -> { + try { + // 缓存到Redis,设置24小时过期 + redisUtil.set(cacheKey, imageBytes, 24 * 60 * 60); + log.info("异步缓存完成,key: {}", cacheKey); + } catch (Exception e) { + log.error("异步缓存失败,key: {}", cacheKey, e); + } + }, asyncExecutor); + } + public byte[] generateAndCombineImagesFromUrl2(byte[] qrCodeImageByte, String backgroundUrl) { File file = null; try { - - int qr_code_y = appletConfigService.getContentByCodeAsInt("qr_code_y"); - int qr_code_x = appletConfigService.getContentByCodeAsInt("qr_code_x"); + // 缓存背景图片,避免重复下载 + String bgCacheKey = "background_image:" + backgroundUrl.hashCode(); + BufferedImage backgroundImage = (BufferedImage) redisUtil.get(bgCacheKey); + + if (backgroundImage == null) { + // 从URL加载背景图像 + URL backgroundImageUrl = new URL(backgroundUrl); + backgroundImage = ImageIO.read(backgroundImageUrl); + // 缓存背景图片,设置1小时过期 + redisUtil.set(bgCacheKey, backgroundImage, 60 * 60); + log.debug("背景图片已缓存"); + } else { + log.debug("使用缓存的背景图片"); + } // 从字节数组加载小程序码图像 BufferedImage qrCodeImage = ImageIO.read(new ByteArrayInputStream(qrCodeImageByte)); - // 从URL加载背景图像 - URL backgroundImageUrl = new URL(backgroundUrl); - BufferedImage backgroundImage = ImageIO.read(backgroundImageUrl); + // 获取配置参数 + int qr_code_y = appletConfigService.getContentByCodeAsInt("qr_code_y"); + int qr_code_x = appletConfigService.getContentByCodeAsInt("qr_code_x"); - // 创建一个新的BufferedImage来保存合并后的图像 - Graphics2D g2d = backgroundImage.createGraphics(); + // 创建一个新的BufferedImage来保存合并后的图像(复制背景图片) + BufferedImage combinedImage = new BufferedImage( + backgroundImage.getWidth(), + backgroundImage.getHeight(), + BufferedImage.TYPE_INT_RGB + ); + + Graphics2D g2d = combinedImage.createGraphics(); + + // 设置高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 绘制背景图像 + g2d.drawImage(backgroundImage, 0, 0, null); int wh = backgroundImage.getWidth() / 3; @@ -150,10 +273,9 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { // 将合并后的图像保存到临时文件 file = File.createTempFile("combined_", ".png"); - ImageIO.write(backgroundImage, "png", file); + ImageIO.write(combinedImage, "png", file); - // 上传到阿里云OSS -// return this.uploadAliYunOss(Files.readAllBytes(file.toPath()), file.getName()); + // 读取文件字节并返回 return Files.readAllBytes(file.toPath()); } catch (Exception e) { log.error("生成合并图片失败", e); @@ -256,6 +378,16 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { return appletWithdrawal; } + @Override + public void withdrawSuccess(String id) { + AppletWithdrawal appletWithdrawal = appletWithdrawalService.getById(id); + if (appletWithdrawal == null) { + throw new JeecgBootException("提现记录不存在"); + } + appletWithdrawal.setWithdrawStatus("1"); + appletWithdrawalService.updateById(appletWithdrawal); + } + @Override public StatisticsVo statistics() { diff --git a/jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue b/jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue index ec42f34..8f0dbf3 100644 --- a/jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue +++ b/jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue @@ -6,10 +6,10 @@