主管理员 1 month ago
parent
commit
cb767e3746
5 changed files with 190 additions and 49 deletions
  1. +0
    -1
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java
  2. +8
    -0
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java
  3. +2
    -0
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java
  4. +175
    -43
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java
  5. +5
    -5
      jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue

+ 0
- 1
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiBooksController.java View File

@ -135,7 +135,6 @@ public class AppletApiBooksController {
*
* @return 删除书桌
*/
@IgnoreAuth
@Operation(summary = "删除书桌", description = "删除书桌,批量删除用,分割")
@PostMapping(value = "/delStand")
public Result<?> delStand(@Parameter(description = "书籍id") @RequestParam String id) {


+ 8
- 0
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java View File

@ -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<StatisticsVo> statistics() {


+ 2
- 0
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java View File

@ -37,6 +37,8 @@ public interface AppletApiWaterService {
AppletWithdrawal getWithdraw(AppletWithdrawal appletWithdrawal);
void withdrawSuccess(String id);
StatisticsVo statistics();
}

+ 175
- 43
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java View File

@ -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<String, Object> 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<String, Object> 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<String, String> headers = new LinkedMultiValueMap<>();
HttpEntity requestEntity = new HttpEntity(JSON.toJSONString(param), headers);
ResponseEntity<byte[]> 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<String, Object> 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<String, Object> 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<String, String> headers = new LinkedMultiValueMap<>();
HttpEntity requestEntity = new HttpEntity(JSON.toJSONString(param), headers);
ResponseEntity<byte[]> 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() {


+ 5
- 5
jeecgboot-vue3/src/views/applet/config/AppletConfigList.vue View File

@ -6,10 +6,10 @@
<template #tableTitle>
<a-button type="primary" v-auth="'appletConfig:applet_config:add'" @click="handleAdd"
preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'appletConfig:applet_config:exportXls'"
preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button type="primary" v-auth="'appletConfig:applet_config:importExcel'"
preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<!-- <a-button type="primary" v-auth="'appletConfig:applet_config:exportXls'"-->
<!-- preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>-->
<!-- <j-upload-button type="primary" v-auth="'appletConfig:applet_config:importExcel'"-->
<!-- preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>-->
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
@ -265,4 +265,4 @@ function getDropDownAction(record) {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
}
</style>
</style>

Loading…
Cancel
Save