添加生成课程码的功能

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

@ -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 {
}
}