添加生成课程码的功能

This commit is contained in:
2025-06-23 08:36:27 +08:00
parent c04ae851cd
commit 2c25017f0a
24 changed files with 206 additions and 17 deletions

View File

@ -207,6 +207,13 @@
<version>2.6</version>
</dependency>
<!--图片合成-->
<dependency>
<groupId>com.freewayso</groupId>
<artifactId>image-combiner</artifactId>
<version>2.6.9</version>
</dependency>

View File

@ -3,6 +3,8 @@ package com.greenorange.promotion.controller.wechat;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.freewayso.image.combiner.ImageCombiner;
import com.freewayso.image.combiner.enums.OutputFormat;
import com.greenorange.promotion.annotation.RequiresPermission;
import com.greenorange.promotion.common.BaseResponse;
import com.greenorange.promotion.common.ErrorCode;
@ -19,15 +21,18 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@ -78,6 +83,25 @@ public class WechatGetQrcodeController {
}
/**
* 微信小程序获取课程码
* @return
* @throws IOException
*/
// @Hidden
@PostMapping("/get/course/qrcode")
@Operation(summary = "微信小程序获取课程码", description = "参数:无, 权限:所有人, 方法名getCourseQrcode")
// @RequiresPermission(mustRole = UserConstant.DEFAULT_ROLE)
public BaseResponse<String> getCourseQrcode(@Valid @RequestBody CommonStringRequest commonStringRequest) throws Exception {
String inviteCode = commonStringRequest.getTemplateString();
String view = wechatGetQrcodeService.getWxCourseQrCode(inviteCode);
return ResultUtils.success(view);
}

View File

@ -22,4 +22,10 @@ public interface WechatGetQrcodeService {
* 微信小程序获取二维码
*/
String getWxQrCode(String inviteCode) throws IOException;
/**
* 微信小程序获取课程码
*/
String getWxCourseQrCode(String inviteCode) throws Exception;
}

View File

@ -1,14 +1,16 @@
package com.greenorange.promotion.service.wechat.impl;
import ch.qos.logback.core.util.MD5Util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.freewayso.image.combiner.ImageCombiner;
import com.freewayso.image.combiner.element.TextElement;
import com.freewayso.image.combiner.enums.BaseLine;
import com.freewayso.image.combiner.enums.OutputFormat;
import com.freewayso.image.combiner.enums.ZoomMode;
import com.google.gson.Gson;
import com.greenorange.promotion.common.ErrorCode;
import com.greenorange.promotion.common.ResultUtils;
import com.greenorange.promotion.config.WxAccessToken;
import com.greenorange.promotion.exception.BusinessException;
import com.greenorange.promotion.model.entity.FileInfo;
@ -19,16 +21,17 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Resource;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Base64;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -185,4 +188,145 @@ public class WechatGetQrcodeServiceImpl implements WechatGetQrcodeService {
fileInfoService.save(fileInfo);
return biz + "-" + view;
}
/**
* 微信小程序获取课程码
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String getWxCourseQrCode(String inviteCode) throws Exception {
String accessToken = (String) redisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
if (accessToken == null) {
accessToken = this.getAccessToken().getAccess_token();
}
Map<String, Object> param = new HashMap<>();
param.put("page", "pages/loginModule/register/register");
param.put("scene", "invitationCode=" + inviteCode);
param.put("width", 430);
param.put("check_path", false);
param.put("env_version", "develop");
String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
String jsonParams = JSONUtil.toJsonStr(param);
byte[] responseBytes = HttpUtil.createPost(url)
.header("Content-Type", "application/json")
.body(jsonParams)
.execute()
.bodyBytes();
String courseTitle = "【早鸟42折】掌握CAD技能实战技能实战技能实战技能实战工作训练营";
String originalPrice = "2680元";
String discountPrice = "1680";
String discountText = "元券后价";
String FontType = "Microsoft YaHei UI";
int FontStyle = Font.PLAIN;
// 加载一张空白图像
String blankUrl = "https://img.picui.cn/free/2025/06/21/6856a072e9eea.png";
ImageCombiner combiner = new ImageCombiner(blankUrl, 341, 391, ZoomMode.WidthHeight, OutputFormat.PNG);
// 加载课程图片
String courseUrl = "https://img.picui.cn/free/2025/06/22/6856ef5908d4b.jpg";
BufferedImage courseImage = ImageIO.read(new URL(courseUrl));
// Graphics2D graphics = courseImage.createGraphics();
// graphics.setColor(Color.decode("#323232"));
// graphics.setFont(new Font("阿里巴巴普惠体", Font.PLAIN, 60));
// graphics.drawString("测试", 0, 80);
// graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
// graphics.drawString("测试", 0, 160);
// graphics.dispose();
// 将二维码数据转换为 BufferedImage
BufferedImage qrImage = ImageIO.read(new ByteArrayInputStream(responseBytes));
// 缩放图片(不失真)
Image scaledImage = qrImage.getScaledInstance(134, 134, Image.SCALE_SMOOTH);
BufferedImage resizedImage = new BufferedImage(134, 134, BufferedImage.TYPE_INT_RGB);
resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
combiner.setQuality(1f);
// 将课程图片合并到组合器中
combiner.addImageElement(courseImage, 18, 16, 306, 158, ZoomMode.WidthHeight).setRoundCorner(5).setCenter(true);
// 将二维码图片合并到组合器中
combiner.addImageElement(resizedImage, 190, 240);
// 绘制文本
TextElement courseTitleElement = new TextElement(courseTitle, FontType, FontStyle, 18, 20, 186);
courseTitleElement.setColor(Color.decode("#323232"))
.setCenter(true)
.setAutoBreakLine(306);
combiner.addElement(courseTitleElement);
TextElement originalPriceElement = new TextElement(originalPrice, FontType, FontStyle, 24, 56, 275);
originalPriceElement.setColor(Color.decode("#8C8C8C")).setStrikeThrough(true);
combiner.addElement(originalPriceElement);
TextElement discountedPriceElement = new TextElement(discountPrice, FontType, Font.BOLD, 28, 31, 343);
discountedPriceElement.setColor(Color.decode("#F84947")).setBaseLine(BaseLine.Base).setSpace(0.01f);
combiner.addElement(discountedPriceElement);
TextElement discountedTextElement = new TextElement(discountText, FontType, FontStyle, 16, discountedPriceElement.getX() + discountedPriceElement.getWidth(), 341);
discountedTextElement.setColor(Color.decode("#F84947")).setBaseLine(BaseLine.Base);
combiner.addElement(discountedTextElement);
combiner.combine();
InputStream resultStream = combiner.getCombinedImageStream();
byte[] resultBytes = resultStream.readAllBytes();
// 创建上传目录,如果不存在
String biz = "default";
String fileName = RandomStringUtils.random(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + "." + "png";
// 获取文件类型
String fileType = FileUtil.getSuffix(fileName);
// 获取view值
String view = RandomStringUtils.random(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
File file = new File(uploadDir + fileName);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();// 如果路径不存在则创建
}
// 将文件上传到目标位置
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE)) {
bos.write(resultBytes);
} catch (IOException e) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "文件上传失败,失败原因:" + e.getMessage());
}
// 获取文件大小
Double fileSize = file.length() / 1024.0;
fileSize = Double.valueOf(String.format("%.2f", fileSize));
// 获取文件哈希值
InputStream inputStream = new FileInputStream(file);
String hashValue = DigestUtils.sha256Hex(inputStream);
// 保存文件
FileInfo fileInfo = FileInfo.builder()
.name(fileName)
.type(fileType)
.size(fileSize)
.fileView(view)
.biz(biz)
.hashValue(hashValue)
.build();
fileInfoService.save(fileInfo);
return biz + "-" + view;
}
public static void main(String[] args) {
// 获取本地图形环境
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
// 列出所有可用字体家族名
String[] families = ge.getAvailableFontFamilyNames();
// 打印全部字体(可选)
System.out.println("=== Available Font Families ===");
Arrays.stream(families).forEach(System.out::println);
}
}

View File

@ -3,11 +3,14 @@ package com.greenorange.promotion.utils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.text.AttributedString;
public class QRCodeUtil {
@ -128,4 +131,9 @@ public class QRCodeUtil {
}
}

View File

@ -13,7 +13,7 @@ spring:
data:
redis:
port: 6379
host: 154.8.193.216
host: 27.30.77.229
database: 7
password: Cksys6509

View File

@ -13,7 +13,7 @@ spring:
data:
redis:
port: 6379
host: 154.8.193.216
host: 27.30.77.229
database: 9
password: Cksys6509
@ -28,8 +28,8 @@ spring:
#文件上传和下载地址
file:
upload-dir: /www/wwwroot/fileUpload_qc/
# upload-dir: D:/qingcheng/image/
# upload-dir: /www/wwwroot/fileUpload_qc/
upload-dir: D:/qingcheng/image/

View File

@ -13,8 +13,8 @@ spring:
data:
redis:
port: 6379
host: 154.8.193.216
database: 9
host: 27.30.77.229
database: 10
password: Cksys6509
servlet:

View File

@ -13,7 +13,7 @@ spring:
data:
redis:
port: 6379
host: 154.8.193.216
host: 27.30.77.229
database: 8
password: Cksys6509

View File

@ -1,4 +1,4 @@
spring:
profiles:
active: test
active: dev

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.