需求:用户领取证书功能。起初做法是将证书内容保存至富文本编辑器,再将富文本内容返回,由前端组成证书页面。但是前端因为屏幕尺寸、拉伸等问题,导致展示的效果不尽人意,无法满足业务要求。所以选择了使用Graphics2D在后端生成证书图片,再将图片返回的方式。
目录
- 一、使用类
- Graphics2D
- BufferedImage
- ByteArrayOutputStream
- 二、生成证书图片
- 1.准备工作:
- 2、工具类
- (1)引入依赖
- (2)绘图工具类
- (3)上传文件工具类
- 3、绘制证书
- 结果图展示:
- 三、保存证书文件
- 1.上传至服务器
- 2.上传至minio
一、使用类
Graphics2D
Graphics类,提供了对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制。用于在Java平台上呈现二维形状、文本和图像的基础类。Graphics2D对象相当于一块画布,我们可以使用Graohics2D提供的方法在画布上进行绘制。
BufferedImage
BufferedImage是带缓冲区图像类,主要作用是将图片加载到内存中,用于对图片进行一些绘图操作。
ByteArrayOutputStream
ByteArrayOutputStream是一个输出流,用于将数据输出到字节数组中,可以通过toByteArray()方法获取写日的字节数组,也可以通过toString()方法将字节数组转换为字符串。
二、生成证书图片
1.准备工作:
(1)模板底图(包含固定文字信息)
(2)填入的信息(比如:姓名、编号等),记录底图中需要填写信息位置的坐标轴(x,y)。
[{name:"Java123",x:"374",y:"298"},{name:"小李",x:"570",y:"299"},{name:"Graphics杯",x:"320",y:"351"}]
2、工具类
(1)引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.14</version>
</dependency>
(2)绘图工具类
import cn.hutool.core.codec.Base64;
import cn.hutool.core.img.FontUtil;
import cn.hutool.core.img.GraphicsUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.img.ImgUtil;
import org.springframework.core.io.ClassPathResource;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
public class GraphicsUtils {
private static final String prefix = "/graphics.background/";
private static Font baseFont;
private static Font normalFont;
private static Font ltBoldFont;
static {
//基础字体
//每次Font.createFont都会生成一个临时文件,多了磁盘就爆了
try {
// 宋体字资源
ClassPathResource songFontResource = new ClassPathResource(prefix + "simsun.ttc");
baseFont = Font.createFont(Font.TRUETYPE_FONT, songFontResource.getInputStream());
normalFont = Font.createFont(Font.TRUETYPE_FONT, songFontResource.getInputStream());
// 黑体字资源
ClassPathResource ltFontResource = new ClassPathResource(prefix + "Lantinghei.ttc");
ltBoldFont = Font.createFont(Font.TRUETYPE_FONT, ltFontResource.getInputStream());
} catch (FontFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 写字
* @param graphics
* @param text
* @param font
* @param x
* @param y
* @param align
*/
public static void drawString(Graphics2D graphics, String text, Font font, int x, int y, int align)
{
// 获得字体对应字符串的长宽信息
Dimension dimension = FontUtil.getDimension(graphics.getFontMetrics(font), text);
int newX = x;
if(align == 0) {
newX = (int) (x-dimension.getWidth()/2);
}
else if(align == -1){
newX = x;
}
else{
newX = (int) (x-dimension.getWidth());
}
GraphicsUtil.drawString(graphics,text, font,Color.BLACK,new Point(newX, y));
}
public static int drawStringWithMultiLine(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int startY, int heightSpace,int indent)
{
return drawStringWithMultiLine(graphics2D, text, font, maxWidth, startX, startY, heightSpace, indent, true);
}
/**
* 输出整行字符串,超过换行
* @param graphics2D
* @param text
* @param font
* @param maxWidth
* @param startX
* @param startY
* @param heightSpace
* @param indent
* @return
*/
public static int drawStringWithMultiLine(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int startY, int heightSpace,int indent,boolean draw)
{
int lastY = startY;
// 获取指定字体的字体度量。
FontMetrics fontMetrics = graphics2D.getFontMetrics(font);
Dimension dimension = FontUtil.getDimension(fontMetrics, text);
StringBuilder sb = new StringBuilder();
for(int i=0;i<indent;i++)
{
//全角空格
sb.append('\u3000');
}
sb.append(text);
text = sb.toString();
int textLength = text.length();
int totalWidth = (int) dimension.getWidth(); //一行的总长度,用于判断是否超出了范围
int textHeight = (int) dimension.getHeight(); //计算一行的高度
int rightOffset = maxWidth - startX;
if (totalWidth > rightOffset) {
//总长度超过了整个长度限制
int alreadyWriteLine = 0; //已经写了多少行
int nowWidth = 0; //目前一行写的长度
for (int i = 0; i < textLength; i++) {
int oneWordWidth = fontMetrics.charWidth(text.charAt(i)); //获取单个字符的长度
int tempWidth = oneWordWidth + nowWidth; //判断目前的一行加上这个字符的长度是否超出了总长度
if (tempWidth > rightOffset) {
//如果超出了一行的总长度,则要换成下一行
nowWidth = 0;
alreadyWriteLine++;
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
if(draw)
{
GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK,new Point(startX + nowWidth, writeY));
}
nowWidth = oneWordWidth;
} else {
//当前行长度足够,可以直接画
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
if(draw)
{
GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK,new Point(startX + nowWidth, writeY));
}
nowWidth = tempWidth;
}
}
lastY = startY + (alreadyWriteLine+1) * (textHeight + heightSpace);
} else {
//没有超过限制,直接画
if(draw)
{
GraphicsUtil.drawString(graphics2D,text, font,Color.BLACK,new Point(startX , startY));
}
lastY = startY + textHeight + heightSpace;
}
return lastY;
}
/**
*
* @param graphics2D
* @param text
* @param font
* @param maxWidth
* @param startX
* @param currentX
* @param startY
* @param heightSpace
* @param indent
* @return
*/
public static int[] drawAndAppendString(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int currentX, int startY, int heightSpace,int indent)
{
return drawAndAppendString(graphics2D, text, font, maxWidth, startX, currentX, startY, heightSpace, indent, 0, true);
}
public static int[] drawAndAppendString(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int currentX, int startY, int heightSpace,int indent, int nextIndent)
{
return drawAndAppendString(graphics2D, text, font, maxWidth, startX, currentX, startY, heightSpace, indent, nextIndent, true);
}
public static int[] drawAndAppendString(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int currentX, int startY, int heightSpace,int indent, boolean draw)
{
return drawAndAppendString(graphics2D, text, font, maxWidth, startX, currentX, startY, heightSpace, indent, 0, draw);
}
/**
* 追加字符串,超过自动换行
* @param graphics2D 地图对象
* @param text 文本
* @param font 字体
* @param maxWidth 文本最宽x轴
* @param startX 起始x轴
* @param currentX 当前x轴(上一个文本的结尾坐标)
* @param startY 起始y轴
* @param heightSpace 行间距
* @param indent 开头空格数
* @return
*/
public static int[] drawAndAppendString(Graphics2D graphics2D, String text,Font font, int maxWidth, int startX, int currentX, int startY, int heightSpace,int indent,int nextIndent,boolean draw)
{
int lastY = startY;
int lastX = 0;
StringBuilder sb = new StringBuilder();
if(currentX == startX)
{
for(int i=0;i<indent;i++)
{
//全角空格
sb.append('\u3000');
}
}
sb.append(text);
text = sb.toString();
FontMetrics fontMetrics = graphics2D.getFontMetrics(font);
Dimension dimension = FontUtil.getDimension(fontMetrics, text);
int textLength = text.length();
int totalWidth = (int) dimension.getWidth(); //一行的总长度,用于判断是否超出了范围
int textHeight = (int) dimension.getHeight(); //计算一行的高度
int rightOffset = maxWidth - currentX;
if (totalWidth > rightOffset) {
//总长度超过了整个长度限制
int alreadyWriteLine = 0; //已经写了多少行
int nowWidth = 0; //目前一行写的长度
for (int i = 0; i < textLength; i++) {
int nextIndentWidth = nextIndent * fontMetrics.charWidth('\u3000');
int oneWordWidth = fontMetrics.charWidth(text.charAt(i)); //获取单个字符的长度
int tempWidth = oneWordWidth + nowWidth; //判断目前的一行加上这个字符的长度是否超出了总长度
if (tempWidth > rightOffset) {
//如果超出了一行的总长度,则要换成下一行
nowWidth = 0;
alreadyWriteLine++;
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
if(draw)
{
GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK,new Point(startX + nowWidth + nextIndentWidth, writeY));
}
nowWidth = oneWordWidth;
currentX = startX + nextIndentWidth;
rightOffset = maxWidth - currentX;
} else {
//当前行长度足够,可以直接画
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
if(draw)
{
GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK,new Point(currentX + nowWidth, writeY));
}
nowWidth = tempWidth;
}
}
lastY = startY + alreadyWriteLine * (textHeight + heightSpace);
lastX = currentX + nowWidth;
} else {
//没有超过限制,直接画
if(draw)
{
GraphicsUtil.drawString(graphics2D,text, font,Color.BLACK,new Point(currentX , startY));
}
// lastY = startY + textHeight + heightSpace;
lastX = currentX + totalWidth;
}
return new int[]{lastX, lastY};
}
/**
* 追加字符串,超过自动换行
* @param graphics2D
* @param as
* @param font
* @param maxWidth
* @param startX
* @param currentX
* @param startY
* @param heightSpace
* @param indent
* @return
*/
public static int[] drawAndAppendString(Graphics2D graphics2D, AttributedString as,Font font, int maxWidth, int startX, int currentX, int startY, int heightSpace,int indent)
{
int lastY = startY;
int lastX = 0;
AttributedCharacterIterator iter = as.getIterator();
int limit = iter.getRunLimit();
//构造字符串
StringBuilder textBuilder = new StringBuilder();
while (iter.getIndex() < limit)
{
textBuilder.append(iter.current());
iter.next();
}
String text = textBuilder.toString();
StringBuilder sb = new StringBuilder();
if(currentX == startX)
{
for(int i=0;i<indent;i++)
{
//全角空格
sb.append('\u3000');
}
}
sb.append(text);
text = sb.toString();
FontMetrics fontMetrics = graphics2D.getFontMetrics(font);
Dimension dimension = FontUtil.getDimension(fontMetrics, text);
int textLength = text.length();
int totalWidth = (int) dimension.getWidth(); //一行的总长度,用于判断是否超出了范围
int textHeight = (int) dimension.getHeight(); //计算一行的高度
//距离右边
int rightOffset = maxWidth - currentX;
//从头遍历
iter.setIndex(0);
if (totalWidth > rightOffset) {
//总长度超过了整个长度限制
int alreadyWriteLine = 0; //已经写了多少行
int nowWidth = 0; //目前一行写的长度
//遍历字符
while (iter.getIndex() < limit)
{
//获取单个富文本
AttributedString tempAs = new AttributedString(String.valueOf(iter.current()));
AttributedCharacterIterator tempIter = tempAs.getIterator();
tempAs.addAttributes(iter.getAttributes(), tempIter.getBeginIndex(), tempIter.getEndIndex());
int oneWordWidth = fontMetrics.charWidth(iter.current()); //获取单个字符的长度
int tempWidth = oneWordWidth + nowWidth; //判断目前的一行加上这个字符的长度是否超出了总长度
if (tempWidth > rightOffset) {
//如果超出了一行的总长度,则要换成下一行
nowWidth = 0;
alreadyWriteLine++;
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
// GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK,new Point(startX + nowWidth, writeY));
graphics2D.drawString(tempAs.getIterator(), startX + nowWidth, writeY);
nowWidth = oneWordWidth;
currentX = startX;
rightOffset = maxWidth;
} else {
//当前行长度足够,可以直接画
int writeY = startY + alreadyWriteLine * (textHeight + heightSpace);
// GraphicsUtil.drawString(graphics2D,text.charAt(i)+"", font,Color.BLACK, new Point(currentX + nowWidth, writeY));
graphics2D.drawString(tempAs.getIterator(), currentX + nowWidth, writeY);
nowWidth = tempWidth;
}
iter.next();
}
lastY = startY + alreadyWriteLine * (textHeight + heightSpace);
lastX = currentX + nowWidth;
} else {
//没有超过限制,直接画
graphics2D.drawString(iter, currentX, startY);
lastX = currentX + totalWidth;
}
return new int[]{lastX, lastY};
}
/**
* 计算
* @param graphics2D
* @param text
* @param font
* @param maxWidth
* @param currentX
* @return
*/
public static double calculatePercentOfMargin(Graphics2D graphics2D, String text, Font font, int maxWidth, int currentX)
{
FontMetrics fontMetrics = graphics2D.getFontMetrics(font);
Dimension dimension = FontUtil.getDimension(fontMetrics, text);
int totalWidth = (int) dimension.getWidth();
int rightOffset = maxWidth - currentX;
BigDecimal bigDecimal = new BigDecimal((float)totalWidth / rightOffset).setScale(2, BigDecimal.ROUND_DOWN);
return bigDecimal.doubleValue();
}
/**
* 生成证书base64
*
*/
public static String createCertificateImageBase64(BufferedImage bufferedImage) {
if (null == bufferedImage) {
return "";
}
String result = "";
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", outputStream);
result = Base64.encode(outputStream.toByteArray());
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
(3)上传文件工具类
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件上传工具类
*
* @author ruoyi
*/
public class FileUploadUtils
{
/**
* 默认大小 100M
*/
public static final long DEFAULT_MAX_SIZE = 100 * 1024 * 1024;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
/**
* 默认上传的地址
*/
private static String defaultBaseDir = RuoYiConfig.getProfile();
public static void setDefaultBaseDir(String defaultBaseDir)
{
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}
public static String getDefaultBaseDir()
{
return defaultBaseDir;
}
/**
* 以默认配置进行文件上传
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String upload(MultipartFile file) throws IOException
{
try
{
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 二维码专用
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upCodeload(String baseDir, MultipartFile file) throws IOException
{
try
{
return uploadCode(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
public static final String uploadCode(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = file.getOriginalFilename();
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(UUID.randomUUID().toString()), Seq.getId(Seq.uploadSeqType), getExtension(file));
// return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
// FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
{
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.exists())
{
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
}
return desc;
}
public static final String getPathFileName(String uploadDir, String fileName) throws IOException
{
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}
/**
* 文件大小校验
*
* @param file 上传的文件
* @return
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws InvalidExtensionException
*/
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
long size = file.getSize();
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension
* @param allowedExtension
* @return
*/
public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
{
for (String str : allowedExtension)
{
if (str.equalsIgnoreCase(extension))
{
return true;
}
}
return false;
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
/**
* 根据url下载文件到本地
* @param url
* @param localFilePath 含文件名
* @return 1:成功 0:失败
*/
public static int downloadFileFromUrl(String url, String localFilePath) {
try {
URL httpUrl = new URL(url);
File f = new File(localFilePath);
FileUtils.copyURLToFile(httpUrl, f);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
return 1;
}
public static int fileExists(String path)
{
File file = new File(path);
if(file.isFile()) {
return 1; // 是文件
} else if(file.isDirectory()) {
if(Objects.requireNonNull(file.list()).length>0)
return 2; // 非空文件夹
else
return 3; // 空文件夹
}
return 0; // 路径不存在
}
/**
* 根据url下载文件到本地
* @param url
* @param localFilePath 含文件名
* @return 1:成功 0:失败
*/
public static int downloadFileFromUrl2(String url, String localFilePath) {
int res = 0;
int fileExists = FileUploadUtils.fileExists(localFilePath);
// 下载文件,先查询文件是否已存在
if (StringUtils.isNotBlank(url) && fileExists == 0) {
res = FileUploadUtils.downloadFileFromUrl(url, localFilePath);
}
return res;
}
}
3、绘制证书
public static void main(String args[]) {
final int alignRight = 1; // 对齐右侧
final int alignCenter = 0; // 居中
// 背景图输入流
InputStream backgroundInputStream = null;
try {
// 获取背景
String path = "C:\\Users\\Dell\\Desktop\\图片\\证书模板.jpg";
// 构造背景
backgroundInputStream = new FileInputStream(path);
// 按需压缩
int backgroundWidth = 1000; //宽度像素
Img backGround = Img.from(backgroundInputStream);
Img backgroudScale = backGround.scale(backgroundWidth, 700);// 按照目标宽度、高度缩放图像
BufferedImage backgroudBuffer = ImgUtil.copyImage(backgroudScale.getImg(), BufferedImage.TYPE_INT_ARGB); // 表示包含8位RGBA颜色组件的图像整数像素
// 以背景图作为画布
Graphics2D graphics = GraphicsUtil.createGraphics(backgroudBuffer, null);
// 设置字体大小
baseFont = baseFont.deriveFont(20f);
float originNormalFontSize = 20f;
normalFont = normalFont.deriveFont(originNormalFontSize); // 正文字体
ltBoldFont = ltBoldFont.deriveFont(originNormalFontSize); //加粗字体
graphics.setFont(baseFont);
String variableCoordinates = "[{name:\"Java123\",x:\"374\",y:\"298\"},{name:\"小李\",x:\"570\",y:\"299\"},{name:\"Graphics杯\",x:\"320\",y:\"351\"}]";
// 遍历把字全部填到地图上
List<VariableCoordinatesVo> variableCoordinatesList = JSONUtil.toList(JSONUtil.toJsonStr(variableCoordinates), VariableCoordinatesVo.class);
for (VariableCoordinatesVo variableCoordinatesVo : variableCoordinatesList) {
int align = alignCenter; // 居中写
Integer x = variableCoordinatesVo.getX();
Integer y = variableCoordinatesVo.getY();
String name = variableCoordinatesVo.getName();
// 根据x,y轴居中写
HuGraphics.drawString(graphics, name, ltBoldFont, x, y, align);
}
// 收尾
graphics.dispose();
// 比例压缩到一半
backgroudBuffer = Thumbnails.of(backgroudBuffer).scale(0.5f).asBufferedImage();
// 图片base64码
String baseImg = createCertificateImageBase64(backgroudBuffer);
} catch (Exception e) {
e.printStackTrace();
}
}
结果图展示:
三、保存证书文件
生成结束后,可以一次性将图片的base64返回给前端,但是绘制图片是一个比较占用内存的操作,当并发量较大时应该减少绘制的频率。可以将生成的证书图片转为文件保存至服务器,再返回文件的url给到前端。
public static void main(String args[]) {
// ...
// 比例压缩到一半
backgroudBuffer = Thumbnails.of(backgroudBuffer).scale(0.5f).asBufferedImage();
// 1.图片base64码
String baseImg = createCertificateImageBase64(backgroudBuffer);
// 2.将图片写入ByteArrayOutputStream流,转为字节数组
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(backgroudBuffer, "png", byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
}
1.上传至服务器
使用图片base64作为入参
String localFilePath = "C:\\Users\\Dell\\Desktop\\图片\\生成证书.png";
FileUploadUtils.downloadFileFromUrl2(URLDecoder.decode(srcUrl), localFilePath);
2.上传至minio
使用字节数组作为入参
/**
* 将输出流转为输入流,上传至minio
*
* @param bytes
* @param bizPath 文件夹路径
* @param fileName 文件名
*/
public static String outPutStreamUpload(byte[] bytes, String bizPath, String customBucket, String fileName) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InternalException, NoResponseException, InvalidBucketNameException, XmlPullParserException, ErrorResponseException, RegionConflictException, InvalidArgumentException {
// 创建inputStream流
InputStream inputStream = new ByteArrayInputStream(bytes);//将inputStream流写成文件(查看文件内容是否正确)
// 上传至minio
String fileUrl = "";
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
// 初始化minio
initMinio(minioUrl, minioName, minioPass);
if (!minioClient.bucketExists(bucketName)) {
// 创建一个名为ota的存储桶
minioClient.makeBucket(bucketName);
}
String objectName = bizPath + "/" + fileName;
minioClient.putObject(newBucket, bizPath + "/" + fileName, inputStream, inputStream.available(), "application/octet-stream");
inputStream.close();
fileUrl = minioUrl + newBucket + "/" + objectName;
// 获取文件大小
ObjectStat objectStat = minioClient.statObject(newBucket, objectName);
long fileSize = objectStat.length();
// log.info("读取minio文件:桶名:{},文件名:{},获取文件大小:{}", minioBucketName, relativePath, fileSize);
String ofileName = fileName;
String suffix = "";
if (StringUtils.isNotEmpty(ofileName)) {
suffix = ofileName.substring(ofileName.lastIndexOf(".") + 1);
ofileName = ofileName.substring(0, ofileName.lastIndexOf("."));
}
return fileUrl;
}