添加生成课程码的功能
This commit is contained in:
7
pom.xml
7
pom.xml
@ -207,6 +207,13 @@
|
|||||||
<version>2.6</version>
|
<version>2.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--图片合成-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.freewayso</groupId>
|
||||||
|
<artifactId>image-combiner</artifactId>
|
||||||
|
<version>2.6.9</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package com.greenorange.promotion.controller.wechat;
|
|||||||
|
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
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.annotation.RequiresPermission;
|
||||||
import com.greenorange.promotion.common.BaseResponse;
|
import com.greenorange.promotion.common.BaseResponse;
|
||||||
import com.greenorange.promotion.common.ErrorCode;
|
import com.greenorange.promotion.common.ErrorCode;
|
||||||
@ -19,15 +21,18 @@ import jakarta.annotation.Resource;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.FileOutputStream;
|
import java.nio.file.Path;
|
||||||
import java.io.IOException;
|
import java.nio.file.Paths;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,4 +22,10 @@ public interface WechatGetQrcodeService {
|
|||||||
* 微信小程序获取二维码
|
* 微信小程序获取二维码
|
||||||
*/
|
*/
|
||||||
String getWxQrCode(String inviteCode) throws IOException;
|
String getWxQrCode(String inviteCode) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序获取课程码
|
||||||
|
*/
|
||||||
|
String getWxCourseQrCode(String inviteCode) throws Exception;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
package com.greenorange.promotion.service.wechat.impl;
|
package com.greenorange.promotion.service.wechat.impl;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import ch.qos.logback.core.util.MD5Util;
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
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.google.gson.Gson;
|
||||||
import com.greenorange.promotion.common.ErrorCode;
|
import com.greenorange.promotion.common.ErrorCode;
|
||||||
import com.greenorange.promotion.common.ResultUtils;
|
|
||||||
import com.greenorange.promotion.config.WxAccessToken;
|
import com.greenorange.promotion.config.WxAccessToken;
|
||||||
import com.greenorange.promotion.exception.BusinessException;
|
import com.greenorange.promotion.exception.BusinessException;
|
||||||
import com.greenorange.promotion.model.entity.FileInfo;
|
import com.greenorange.promotion.model.entity.FileInfo;
|
||||||
@ -19,16 +21,17 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.Base64;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -185,4 +188,145 @@ public class WechatGetQrcodeServiceImpl implements WechatGetQrcodeService {
|
|||||||
fileInfoService.save(fileInfo);
|
fileInfoService.save(fileInfo);
|
||||||
return biz + "-" + view;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,14 @@ package com.greenorange.promotion.utils;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.font.LineBreakMeasurer;
|
||||||
|
import java.awt.font.TextLayout;
|
||||||
import java.awt.geom.Ellipse2D;
|
import java.awt.geom.Ellipse2D;
|
||||||
import java.awt.geom.RoundRectangle2D;
|
import java.awt.geom.RoundRectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
public class QRCodeUtil {
|
public class QRCodeUtil {
|
||||||
|
|
||||||
@ -128,4 +131,9 @@ public class QRCodeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ spring:
|
|||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
port: 6379
|
port: 6379
|
||||||
host: 154.8.193.216
|
host: 27.30.77.229
|
||||||
database: 7
|
database: 7
|
||||||
password: Cksys6509
|
password: Cksys6509
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ spring:
|
|||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
port: 6379
|
port: 6379
|
||||||
host: 154.8.193.216
|
host: 27.30.77.229
|
||||||
database: 9
|
database: 9
|
||||||
password: Cksys6509
|
password: Cksys6509
|
||||||
|
|
||||||
@ -28,8 +28,8 @@ spring:
|
|||||||
|
|
||||||
#文件上传和下载地址
|
#文件上传和下载地址
|
||||||
file:
|
file:
|
||||||
upload-dir: /www/wwwroot/fileUpload_qc/
|
# upload-dir: /www/wwwroot/fileUpload_qc/
|
||||||
# upload-dir: D:/qingcheng/image/
|
upload-dir: D:/qingcheng/image/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ spring:
|
|||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
port: 6379
|
port: 6379
|
||||||
host: 154.8.193.216
|
host: 27.30.77.229
|
||||||
database: 9
|
database: 10
|
||||||
password: Cksys6509
|
password: Cksys6509
|
||||||
|
|
||||||
servlet:
|
servlet:
|
||||||
|
@ -13,7 +13,7 @@ spring:
|
|||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
port: 6379
|
port: 6379
|
||||||
host: 154.8.193.216
|
host: 27.30.77.229
|
||||||
database: 8
|
database: 8
|
||||||
password: Cksys6509
|
password: Cksys6509
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: test
|
active: dev
|
||||||
|
|
||||||
|
BIN
src/main/resources/fonts/SourceHanSansCN-Bold.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Bold.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-ExtraLight.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-Heavy.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Heavy.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-Light.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Light.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-Medium.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Medium.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-Normal.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Normal.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSansCN-Regular.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSansCN-Regular.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-Bold.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-Bold.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-ExtraLight.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-Heavy.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-Heavy.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-Light.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-Light.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-Medium.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-Medium.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-Regular.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-Regular.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/SourceHanSerifCN-SemiBold.ttf
Normal file
BIN
src/main/resources/fonts/SourceHanSerifCN-SemiBold.ttf
Normal file
Binary file not shown.
Reference in New Issue
Block a user