package com.zteits.pay.util; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.xiaoleilu.hutool.date.DateUtil; import com.xiaoleilu.hutool.lang.Base64; import com.zteits.clouds.api.dto.pay.dto.TdCRechargeCardDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.EnableMBeanExport; import org.springframework.core.io.ClassPathResource; import org.springframework.jmx.support.RegistrationPolicy; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; /** * 充值卡图片生成器(逐字搬自老门户 * {@code zteits-bcp-portal/src/main/java/com/zteits/bcp/portal/util/ChargeCardPicWrapper.java}, * 只改了包名。第二百八十二切片)。 * *

⚠️ 三个字体文件({@code simhei.ttf} / {@code 汉仪书宋一简.ttf} / {@code 长城新艺体.ttf}) * 一并从老门户 {@code src/main/resources} 搬进本模块的 {@code src/main/resources} * —— {@code new ClassPathResource("simhei.ttf")} 是**类路径根**,不是本包的相对路径。 * 少了任何一个,生成图片那一步会在 {@code Font.createFont} 处抛异常(被 catch 吞掉)⇒ * 图片生成失败但接口仍回 {@code true}(存量写法)。

* *

⚠️ 存量写法照搬(红线⑥),不要"顺手修":

*
    *
  1. `@Async("myTaskAsyncPool")` 的三个方法**自己 catch 掉所有异常**、只在 finally * `countDown()` ⇒ 生成失败对调用方完全不可见。
  2. *
  3. 字体是**静态字段 + 懒加载**(首次 `null` 才 `createFont`),多线程下可能重复加载几次。
  4. *
  5. `deleteAnyone` 上也挂了 `@Async` ⇒ 调用方拿到的返回值恒 `false`,删除其实是异步的 * (所以紧接着删 zip 与删目录的顺序在日志里看起来是乱的)。
  6. *
* * @author xiejianpeng(存量作者) */ @Component // 解决jmx重复注册bean的问题 @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) @EnableAsync public class ChargeCardPicWrapper { private final Logger logger = LoggerFactory.getLogger(ChargeCardPicWrapper.class); private static Font fontStaticB = null; private static Font fontStaticH = null; private static Font fontStaticC = null; //文字显示 private static final int QRCOLOR = 0x000000; //二维码颜色 private static final int BGWHITE = 0xFFFFFF; //二维码背景颜色:白色 // 设置QR二维码参数信息 private static Map hints = new HashMap() { private static final long serialVersionUID = 1L; { put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);// 设置QR二维码的纠错级别(H为最高级别) put(EncodeHintType.CHARACTER_SET, "utf-8");// 设置编码方式 put(EncodeHintType.MARGIN, 1);// 白边 } }; public File base64ToFile(String base64) throws IOException { String[] picInfo = base64.split(","); base64 = picInfo[1]; if (base64 == null || "".equals(base64)) { return null; } byte[] buff = Base64.decode(base64); File file = null; FileOutputStream fout = null; try { file = File.createTempFile("tmp", null); fout = new FileOutputStream(file); fout.write(buff); } catch (IOException e) { e.printStackTrace(); } finally { if (fout != null) { try { fout.close(); } catch (IOException e) { e.printStackTrace(); } } } return file; } private String handelPicExtend(String content) { String suffIx = content.replace("data:image/", "").replace(";base64", ""); return suffIx; } private byte[] handelBase64PicContent(String content) throws IOException { java.util.Base64.Decoder decoder = java.util.Base64.getMimeDecoder(); byte[] bytes = decoder.decode(content); for (int i = 0; i < bytes.length; ++i) { // 调整异常数据 if (bytes[i] < 0) { bytes[i] += 256; } } return bytes; } /** * 生成二维码图片+背景+文字描述 * * @param codeFile 生成图地址 * @param bgImgFile 背景图地址 */ @Async("myTaskAsyncPool") public Future CreatQRCodeTopOne(File codeFile, File bgImgFile, TdCRechargeCardDTO cRechargeCardDTO, CountDownLatch countDownLatch) { Integer WIDTH = 320; Integer HEIGHT = 320; String cardNo = cRechargeCardDTO.getCardNo(); try { MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); // 1.参数顺序分别为: 编码内容,编码类型,生成图片宽度,生成图片高度,设置参数 BitMatrix bm = multiFormatWriter.encode(cardNo, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints); BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 2.开始利用二维码数据创建Bitmap图片,分别设为黑(0xFFFFFFFF) 白(0xFF000000)两色 for (int x = 0; x < WIDTH; x++) { for (int y = 0; y < HEIGHT; y++) { image.setRGB(x, y, bm.get(x, y) ? QRCOLOR : BGWHITE); } } /** * 3.添加背景图片 */ BufferedImage backgroundImage = ImageIO.read(bgImgFile); // int qrWidth=image.getWidth(); //距离背景图片x边的距离,居中显示 int disx = 12; //距离y边距离 * * * * int disy = 316; Graphics2D rng = backgroundImage.createGraphics(); // rng.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); // // rng.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_DEFAULT); //消除文字锯齿 rng.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); //消除画图锯 rng.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rng.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP)); rng.drawImage(image, disx, disy, WIDTH, HEIGHT, null); /** * 4. 文字描述参数设置 */ Color textColor = Color.white; rng.setColor(textColor); rng.drawImage(backgroundImage, 0, 0, null); //设置字体类型和大小(BOLD加粗/ PLAIN平常) float size2 = 18; Font font2 = null; if (fontStaticB == null) { fontStaticB = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("simhei.ttf").getInputStream()); } font2 = fontStaticB.deriveFont(size2); rng.setFont(font2); //设置字体颜色 rng.setColor(Color.black); if (2 == cRechargeCardDTO.getCardType()) { //文字1显示位置--兑换码 String cardSecret = "兑换码:" + cRechargeCardDTO.getCardSecret(); rng.drawString(cardSecret, 260, 580);//上下 } //文字1显示位置--兑换码 rng.drawString("扫我充值", 122, 360);//上下 //文字1显示位置--兑换码 String data = "截止日期:" + DateUtil.format(cRechargeCardDTO.getEndTime(), "yyyy年MM月dd日"); rng.drawString(data, 753, 580);//上下 float size = 48; Font font = null; if (fontStaticH == null) { fontStaticH = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("汉仪书宋一简.ttf").getInputStream()); } font = fontStaticH.deriveFont(size); rng.setFont(font); //设置字体颜色 rng.setColor(Color.black); //文字2显示位置 String regex = "(.{4})"; String result = cardNo.replaceAll(regex, "$1 "); rng.drawString(result, 60, 100);//上下 size = 48; font = fontStaticH.deriveFont(size); rng.setFont(font); //设置字体颜色 rng.setColor(Color.black); //文字3显示位置 String no = "NO." + cRechargeCardDTO.getId(); rng.drawString(no, 690, 550);//上下 float size1 = 60; Font font1 = null; if (fontStaticC == null) { fontStaticC = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("长城新艺体.ttf").getInputStream()); } font1 = fontStaticC.deriveFont(size1); font1.deriveFont(Font.ITALIC); rng.setFont(font1); //设置字体颜色 rng.setColor(Color.black); String money = cRechargeCardDTO.getTotalFee() / 100 + "元"; //文字2显示位置 rng.drawString(money, 850, 109);//上下 rng.dispose(); image = backgroundImage; image.flush(); ImageIO.write(image, "png", codeFile); } catch (Exception e) { e.printStackTrace(); logger.error("生成图片错误:{}", e); } finally { countDownLatch.countDown(); } return new AsyncResult<>(cRechargeCardDTO.getCardNo()); } @Async("myTaskAsyncPool") public void creatQRCodeTop(File codeFile, File bgImgFile, TdCRechargeCardDTO cRechargeCardDTO, CountDownLatch countDownLatch) { String cardNo = cRechargeCardDTO.getCardNo(); try { // 1.参数顺序分别为: 编码内容,编码类型,生成图片宽度,生成图片高度,设置参数 BufferedImage image; /** * 3.添加背景图片 */ BufferedImage backgroundImage = ImageIO.read(bgImgFile); //距离背景图片x边的距离,居中显示 Graphics2D rng = backgroundImage.createGraphics(); //消除文字锯齿 rng.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); //消除画图锯 rng.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rng.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP)); /** * 4. 文字描述参数设置 */ Color textColor = Color.white; rng.setColor(textColor); rng.drawImage(backgroundImage, 0, 0, null); //设置字体类型和大小(BOLD加粗/ PLAIN平常) float size = 48; Font font = null; if (fontStaticH == null) { fontStaticH = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("汉仪书宋一简.ttf").getInputStream()); } font = fontStaticH.deriveFont(size); rng.setFont(font); //设置字体颜色 rng.setColor(Color.black); //文字2显示位置 String regex = "(.{4})"; String result = cardNo.replaceAll(regex, "$1 "); rng.drawString(result, 438, 546);//上下 size = 48; font = fontStaticH.deriveFont(size); rng.setFont(font); //设置字体颜色 rng.setColor(Color.black); //文字3显示位置 String no = "NO." + cRechargeCardDTO.getId(); rng.drawString(no, 690, 100);//上下 rng.dispose(); image = backgroundImage; image.flush(); ImageIO.write(image, "png", codeFile); } catch (Exception e) { e.printStackTrace(); logger.error("生成图片错误:{}", e); } finally { countDownLatch.countDown(); } } @Async("myTaskAsyncPool") public void creatQRCodeBottom(File codeFile, File bgImgFile, TdCRechargeCardDTO cRechargeCardDTO, CountDownLatch countDownLatch) { Integer WIDTH = 321; Integer HEIGHT = 321; String cardNo = cRechargeCardDTO.getCardNo(); try { MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); // 1.参数顺序分别为: 编码内容,编码类型,生成图片宽度,生成图片高度,设置参数 BitMatrix bm = multiFormatWriter.encode(cardNo, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints); BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 2.开始利用二维码数据创建Bitmap图片,分别设为黑(0xFFFFFFFF) 白(0xFF000000)两色 for (int x = 0; x < WIDTH; x++) { for (int y = 0; y < HEIGHT; y++) { image.setRGB(x, y, bm.get(x, y) ? QRCOLOR : BGWHITE); } } /** * 3.添加背景图片 */ BufferedImage backgroundImage = ImageIO.read(bgImgFile); // int qrWidth=image.getWidth(); //距离背景图片x边的距离,居中显示 int disx = 28; //距离y边距离 * * * * int disy = 160; Graphics2D rng = backgroundImage.createGraphics(); //消除文字锯齿 rng.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); //消除画图锯 rng.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rng.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP)); rng.drawImage(image, disx, disy, WIDTH, HEIGHT, null); /** * 4. 文字描述参数设置 */ Color textColor = Color.white; rng.setColor(textColor); rng.drawImage(backgroundImage, 0, 0, null); //设置字体类型和大小(BOLD加粗/ PLAIN平常) float size2 = 26; Font font2 = null; if (fontStaticB == null) { fontStaticB = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("simhei.ttf").getInputStream()); } font2 = fontStaticB.deriveFont(size2); rng.setFont(font2); //设置字体颜色 rng.setColor(Color.black); //文字1显示位置--兑换码 if (2 == cRechargeCardDTO.getCardType()) { //文字1显示位置--兑换码 String cardSecret = "兑换码:" + cRechargeCardDTO.getCardSecret(); rng.drawString(cardSecret, 108, 590);//上下 } // String data = "截止日期:" + DateUtil.format(cRechargeCardDTO.getEndTime(), "yyyy年MM月dd日"); // rng.drawString(data, 639, 570);//上下 float size3 = 22; font2 = fontStaticB.deriveFont(size3); rng.setFont(font2); rng.drawString("充值二维码", 133, 505);//上下 float size1 = 60; Font font1 = null; if (fontStaticC == null) { fontStaticC = Font.createFont(Font.TRUETYPE_FONT, new ClassPathResource("长城新艺体.ttf").getInputStream()); } font1 = fontStaticC.deriveFont(size1); font1.deriveFont(Font.ITALIC); rng.setFont(font1); //设置字体颜色 rng.setColor(Color.black); String money = cRechargeCardDTO.getTotalFee() / 100 + "元"; //文字2显示位置 rng.drawString(money, 738, 109);//上下 rng.dispose(); image = backgroundImage; image.flush(); ImageIO.write(image, "png", codeFile); } catch (Exception e) { e.printStackTrace(); logger.error("生成图片错误:{}", e); } finally { countDownLatch.countDown(); } } /** * 判断指定的文件或文件夹删除是否成功 * * @param FileName 文件或文件夹的路径 * @return true or false 成功返回true,失败返回false */ @Async public boolean deleteAnyone(String FileName) { File file = new File(FileName);//根据指定的文件名创建File对象 if (!file.exists()) { //要删除的文件不存在 logger.info("文件" + FileName + "不存在,删除失败!"); return false; } else { //要删除的文件存在 if (file.isFile()) { //如果目标文件是文件 return deleteFile(FileName); } else { //如果目标文件是目录 return deleteDir(FileName); } } } /** * 判断指定的文件删除是否成功 * * @param * @return true or false 成功返回true,失败返回false */ public boolean deleteFile(String fileName) { File file = new File(fileName);//根据指定的文件名创建File对象 if (file.exists() && file.isFile()) { //要删除的文件存在且是文件 if (file.delete()) { logger.info("文件" + fileName + "删除成功!"); return true; } else { logger.info("文件" + fileName + "删除失败!"); return false; } } else { logger.info("文件" + fileName + "不存在,删除失败!"); return false; } } /** * 删除指定的目录以及目录下的所有子文件 * * @param dirName is 目录路径 * @return true or false 成功返回true,失败返回false */ public boolean deleteDir(String dirName) { if (dirName.endsWith(File.separator)) {//dirName不以分隔符结尾则自动添加分隔符 dirName = dirName + File.separator; } File file = new File(dirName);//根据指定的文件名创建File对象 if (!file.exists() || (!file.isDirectory())) { //目录不存在或者 logger.info("目录删除失败" + dirName + "目录不存在!"); return false; } File[] fileArrays = file.listFiles();//列出源文件下所有文件,包括子目录 for (int i = 0; i < fileArrays.length; i++) {//将源文件下的所有文件逐个删除 this.deleteAnyone(fileArrays[i].getAbsolutePath()); } if (file.delete()) {//删除当前目录 logger.info("目录" + dirName + "删除成功!"); } return true; } }