生成二维码
铁铁们,这两日写了一个导出二维码的接口,要求有一个是在二维码下方生成字体,现在奉上生成二维码的代码:
controller层
@Operation(summary = "导出机构二维码",description = "导出机构二维码")
@GetMapping("/orgCode")
public void getOr(@RequestParam("url") String url,@RequestParam("orgIds") List<Long> orgIds,@RequestParam("channels") List<String> channels, HttpServletResponse response) throws IOException{
//返回二维码生成的数据
byte[] data = orgCodeService.downLoadOrg(url, orgIds, channels);
response.reset();
//指定返回的文件为附件形式,指定文件名为"二维码zip"
response.setHeader(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("二维码.zip","UTF-8"));
//设置返回数据的长度
response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length));
//设置相应类型为二进制流
response.setContentType("application/octet-stream; charset=UTF-8");
//将二维码数据写入响应的输出流中,完成文件下载操作
IoUtil.write(response.getOutputStream(), Boolean.TRUE, data);
}
service层:
byte[] downLoadOrg(String url,List<Long> orgIdList,List<String> channels) throws IOException;
实现类:
@Slf4j
@Service
public class QRGCodeServiceImpl implements QRGCodeService {
// 设置二维码的默认宽度和高度
private final static int width = 756;
private final static int height = 850;
// 设置二维码缩放比例和文件类型
private final static int SCALE = 2;
private final static String fileType = "png";
// 设置字体大小、圆角半径和文字位置
private final static int fontSize = 50;
private final static int roundingRadius = 250;
private final static int pixel = 50;
private final static int FONT_SIZE_BIG = 23 * SCALE;
@Resource
private OrgOrganizationService orgOrganizationService;
/**
* @author dongruipeng
* @date 2024/4/10
* @description 根据URL,机构ID列表和渠道列表生成二维码后压缩并返回给前端
* @param
*/
@Override
public byte[] downLoadOrg(String url, List<Long> orgIdList, List<String> channels) throws IOException {
//参数校验
Objects.requireNonNull(url, "url can not be null");
Objects.requireNonNull(orgIdList, "orgIdList can not be null");
Objects.requireNonNull(channels, "channels cannot be null");
url = URLDecoder.decode(url, StandardCharsets.UTF_8);
log.info("url参数:{}", url);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(stream)) {
for (Long orgId : orgIdList) {
// 获取机构名称
String orgName = getOrgName(orgId);
for (String channel : channels) {
String qrContent = url + "?orgId=" + orgId + "&channel=" + channel;
Color color = getColorByChannel(channel);
byte[] qrCodeBytes = generateQRCode(qrContent, channel.equals(OrgChannelConstant.ONSITE_QR_CODE), orgName, color);
String fileName = orgName + "_" + OrgCodeConstant.getByCode(Integer.parseInt(channel)) + ".png";
ZipEntry entry = new ZipEntry(fileName);
zos.putNextEntry(entry);
zos.write(qrCodeBytes);
zos.closeEntry();
}
}
}
return stream.toByteArray();
}
// 获取机构名称
private String getOrgName(Long orgId) {
OrganizationVo organizationVo = orgOrganizationService.selectByOrgId(orgId);
Assert.notNull(organizationVo, "该机构信息不存在");
return organizationVo.getName();
}
// 生成二维码图片
private byte[] generateQRCode(String qrContent, boolean isSharpCorner, String orgName, Color color) {
try {
//设置二维码参数
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(qrContent, BarcodeFormat.QR_CODE, width, height);
//创建BufferedImage对象,并设置背景颜色
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
graphics.setColor(color);
//将BitMatrix - 二维矩阵 对象转换为BufferedImage对象
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (bitMatrix.get(i, j)) {
graphics.fillRect(i, j, 1, 1);
}
}
}
Font font;
File yuanti = new File("/usr/share/fonts/SIMSUN.TTC");
try {
if (yuanti.exists()) {
font = Font.createFont(Font.TRUETYPE_FONT, new File("/usr/share/fonts/SIMSUN.TTC"));
font = font.deriveFont(Font.BOLD, FONT_SIZE_BIG);
log.info("加载宋体字体文件成功");
} else {
font = new Font("宋体", Font.BOLD, FONT_SIZE_BIG);
}
} catch (IOException | FontFormatException e) {
log.error("加载字体文件时出错: {}", e.getMessage());
font = new Font("宋体", Font.BOLD, FONT_SIZE_BIG); //加载失败时使用默认字体
}
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int orgNameWidth = fontMetrics.stringWidth(orgName);
//计算居中的X坐标
int orgNameX = (width - orgNameWidth) / 2;
//调整像素,文本接近底部
int orgNameY = height - pixel;
//机构名称居中
graphics.drawString(orgName, orgNameX, orgNameY);
//设置二维码边框白色圆角
if (!isSharpCorner) {
//圆角半径
BufferedImage roundedImage = makeRoundedCorner(bufferedImage, roundingRadius);
bufferedImage = roundedImage;
}
// 保存二维码图片到字节数组
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ImageIO.write(bufferedImage, fileType, outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
log.error("保存二维码图片时出错: {}", e.getMessage());
throw new RuntimeException("保存二维码图片时出错", e);
}
} catch (WriterException e) {
log.error("生成二维码时出错: {}", e.getMessage());
throw new RuntimeException("生成二维码时出错", e);
}
}
//创建圆角图片
private static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) {
//获取图像宽高
int width = image.getWidth();
int height = image.getHeight();
//创建新的透明背景的 BufferedImage 对象,存储有圆角的图像
BufferedImage roundedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
//创建 Graphics2D 对象绘制图像
Graphics2D g2 = roundedImage.createGraphics();
//设置图片合成模式为 Src,用于在创建新的图形之前清除现有的
g2.setComposite(AlphaComposite.Src);
//设置抗锯齿,确保图形边缘平滑
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//设置图片颜色为白色,用于设置圆角矩形作为图像的背景
g2.setColor(Color.WHITE);
//绘制圆角矩形,作为图像的背景,cornerRadius参数确定圆角的大小
g2.fill(new RoundRectangle2D.Float(0, 0, width, height, cornerRadius, cornerRadius));
//设置合成模式为 SrcAtop,在现有图形上绘制新内容
g2.setComposite(AlphaComposite.SrcAtop);
//在背景图像上绘制原来的图像,使该图带圆角
g2.drawImage(image, 0, 0, null);
//释放资源
g2.dispose();
return roundedImage;
}
// 根据渠道设置颜色
private Color getColorByChannel(String channel) {
switch (channel) {
case "1":
return Color.BLACK;
case "2":
return new Color(37, 82, 151); //#255297
default:
return Color.BLACK;
}
}
}
上述代码中,可谓是耗费了我十足的精力,在自己本地测试时,因为我们的系统文件,也就是"C:\Windows Fonts文件夹"下面存放的字体文件十分之多,如下图:
所以如果自己本地使用postman测试的话,上述代码中二维码生成的下方文字无论如何也不会乱码,但是一旦发送至我们的服务器上,如果虚拟服务器上的"/usr/share/fonts/"文件夹下面没有字体文件的话,前端一调接口,就会产生乱码问题,那么如何解决呢?
1.首先查看自己引入的字体文件是否已经破坏,最好的测试方法就是:自己在postman上测试,如果生成的字体是系统默认的,那就是坏的,如果生成的字体是我们想要的字体,例如代码所示,使用该方法排除是否字体文件已经破坏
2.如果在确保字体文件没有被破坏的情况下还是无法加载成功,那么这个时候我们就需要在pom文件当中添加防止打包jar包时能够不被java过滤的依赖,如下图:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<!-- 过滤后缀不需要转码的文件后缀名.crt-->
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
<nonFilteredFileExtension>xls</nonFilteredFileExtension>
<nonFilteredFileExtension>zip</nonFilteredFileExtension>
<nonFilteredFileExtension>cer</nonFilteredFileExtension>
<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
<nonFilteredFileExtension>py</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
3.在确保文件没有被破坏和已经加载了依赖的情况下还是无法加载成功的话,这个时候我们就需要查看服务器上的文件夹下是否存在我们需要的字体文件,首先,在DockerFile文件中添加一行Copy外部文件的命令
COPY ./SIMSUN.TTC /usr/share/fonts/SIMSUN.TTC
我存放的目录是这样的:
一定要确保我们的服务器上文件夹下有我们要的字体文件才可以,如果你们遇到了这种情况,一定要排查!