Java自定义生成二维码(兼容你所有的需求)
1、概述
作为Java开发人员,说到生成二维码就会想到zxing开源二维码图像处理库,不可否认的是zxing确实很强大,但是实际需求中会遇到各种各样的需求是zxing满足不了的,于是就有了想法自己扩展zxing满足历史遇到的各种需求,经过3周的研究学习+开发,兼容你所有需求的Java二维码生成器孕育而生,接下来我们就看看我写的这个二维码生成器都实现了哪些功能。
2、已实现需求
zxing支持的二维码设置
自定义logo,自动增加白边框、圆形、圆角矩形
最终二维码圆角矩形生成
自定义背景颜色
自定义背景图片,可以设置二维码放置背景图片的位置
自定义设置二维码顶部注释,自定义字体、字体颜色、背景颜色,居中、自动换行
自定义设置二维码底部注释,自定义字体、字体颜色、背景颜色,居中、自动换行
设置码点颜色、形状(普通、原型、方形、三角形、圆角矩形、菱形)
设置码眼外框颜色、形状(方形、圆形-粗边、圆形-细边、圆角矩形-粗边、圆角矩形-中边、圆角矩形-细边)
设置码眼内颜色、形状(方形、圆形、圆角矩形、三角形、菱形)
其他继续扩展中
3、项目配置详细说明
3.1、二维码背景配置
package com.faea.qrcode.config;import lombok.Getter;import java.awt.*;/*** 二维码背景配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeBackgroundConfig {public CodeBackgroundConfig() {this.color = Color.WHITE;}/*** 背景色,默认白色*/public Color color;/*** 二维码绘制在背景图上的X轴*/public int codeDrawX;/*** 二维码绘制在北京图上的Y轴*/public int codeDrawY;/*** 背景图片*/private Image image;public CodeBackgroundConfig color(Color color) {this.color = color;return this;}public CodeBackgroundConfig image(Image image) {this.image = image;return this;}public CodeBackgroundConfig codeDrawX(Integer codeDrawX) {this.codeDrawX = codeDrawX;return this;}public CodeBackgroundConfig codeDrawY(Integer codeDrawY) {this.codeDrawY = codeDrawY;return this;}
}
3.2、二维码码点配置
package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeDotShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码点配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeDotConfig {public CodeDotConfig() {this.color = Color.BLACK;this.shape = CodeDotShapeEnum.NORMAL;}/*** 码点颜色*/private Color color;/*** 形状枚举*/private CodeDotShapeEnum shape;public CodeDotConfig shape(CodeDotShapeEnum shape) {this.shape = shape;return this;}public CodeDotConfig color(Color color) {this.color = color;return this;}
}
3.3、二维码码眼内配置
package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeEyeInsideShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码眼内配置** @author liuchao* @date 2023/2/19*/
@Getter
public class CodeEyeInsideConfig {public CodeEyeInsideConfig() {this(Color.BLACK, CodeEyeInsideShapeEnum.SQUARE);}public CodeEyeInsideConfig(Color color, CodeEyeInsideShapeEnum shape) {this.color = color;this.shape = shape;}/*** 颜色*/private Color color;/*** 形状*/private CodeEyeInsideShapeEnum shape;public CodeEyeInsideConfig color(Color color) {this.color = color;return this;}public CodeEyeInsideConfig shape(CodeEyeInsideShapeEnum shape) {this.shape = shape;return this;}
}
3.4、二维码码眼外配置
package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeEyeOutShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码眼外配置** @author liuchao* @date 2023/2/19*/
@Getter
public class CodeEyeOutConfig {public CodeEyeOutConfig() {this(Color.BLACK, CodeEyeOutShapeEnum.SQUARE);}public CodeEyeOutConfig(Color color, CodeEyeOutShapeEnum shape) {this.color = color;this.shape = shape;}/*** 颜色*/private Color color;/*** 形状*/private CodeEyeOutShapeEnum shape;public CodeEyeOutConfig color(Color color) {this.color = color;return this;}public CodeEyeOutConfig shape(CodeEyeOutShapeEnum shape) {this.shape = shape;return this;}
}
3.5、二维码LOGO配置
package com.faea.qrcode.config;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import lombok.Data;import java.awt.*;
import java.io.File;/*** 二维码LOGO配置** @author liuchao* @date 2023/2/17*/
@Data
public class CodeLogoConfig {public CodeLogoConfig() {this(null);}public CodeLogoConfig(Image image) {this.image = image;this.scale = 0.1F;this.shape = CodeLogoShapeEnum.ROUNDED;this.alpha = 1F;}/*** LOGO 图片*/private Image image;/*** 形状*/private CodeLogoShapeEnum shape;/*** LOGO 透明度*/private Float alpha;/*** 缩放比例。比例大于1时为放大,小于1大于0为缩小*/private float scale;/*** 设置二维码中的Logo文件** @param logoPath 二维码中的Logo路径* @return this;*/public CodeLogoConfig image(String logoPath) {return image(FileUtil.file(logoPath));}/*** 设置二维码中的Logo文件** @param logoFile 二维码中的Logo* @return this;*/public CodeLogoConfig image(File logoFile) {return image(ImgUtil.read(logoFile));}/*** 设置二维码中的Logo** @param image 二维码中的Logo* @return this;*/public CodeLogoConfig image(Image image) {this.image = image;return this;}public CodeLogoConfig shape(CodeLogoShapeEnum shape) {this.shape = shape;return this;}public CodeLogoConfig alpha(Float alpha) {this.alpha = alpha;return this;}public CodeLogoConfig scale(float scale) {this.scale = scale;return this;}
}
3.6、二维码注释配置
package com.faea.qrcode.config;import lombok.Data;import java.awt.*;/*** 二维码注释配置** @author liuchao* @date 2023/2/17*/
@Data
public class CodeNoteConfig {public CodeNoteConfig() {this.rowSpacing = 5;this.size = 14;this.font = "SimHei";this.backColor = Color.DARK_GRAY;this.fontColor = Color.BLACK;}/*** 注释内容*/private String content;/*** 注释字体*/private String font;/*** 字体大小*/private Integer size;/*** 背景颜色*/private Color backColor;/*** 字体颜色*/private Color fontColor;/*** 行间距*/private Integer rowSpacing;public CodeNoteConfig backColor(Color color) {this.backColor = color;return this;}public CodeNoteConfig fontColor(Color color) {this.fontColor = color;return this;}public CodeNoteConfig content(String content) {this.content = content;return this;}public CodeNoteConfig font(String font) {this.font = font;return this;}public CodeNoteConfig size(Integer size) {this.size = size;return this;}public CodeNoteConfig rowSpacing(Integer rowSpacing) {this.rowSpacing = rowSpacing;return this;}
}
3.7、总配置入口类
package com.faea.qrcode.config;import cn.hutool.core.util.CharsetUtil;
import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.Getter;import java.nio.charset.Charset;
import java.util.HashMap;/*** 二维码总配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeConfig {/*** 创建QrConfig** @return QrConfig* @since 4.1.14*/public static CodeConfig create() {return new CodeConfig();}/*** 构造,默认长宽为300*/public CodeConfig() {this(500, 500);}/*** 构造** @param width 宽* @param height 长*/public CodeConfig(int width, int height) {this.width = width;this.height = height;this.margin = 1;this.dot = new CodeDotConfig();this.background = new CodeBackgroundConfig();this.eyeOut = new CodeEyeOutConfig();this.eyeInside = new CodeEyeInsideConfig();this.errorCorrection = ErrorCorrectionLevel.H;this.charset = CharsetUtil.CHARSET_UTF_8;this.arc = 1D;}/*** 宽*/private Integer width;/*** 长*/private Integer height;/*** 边距1~4* 二维码空白区域,最小为0也有白边,只是很小,最小是6像素左右*/private Integer margin;/*** 码眼外*/private CodeEyeOutConfig eyeOut;/*** 码眼内*/private CodeEyeInsideConfig eyeInside;/*** 纠错级别*/private ErrorCorrectionLevel errorCorrection;/*** 编码*/private Charset charset;/*** 前景色配置*/private CodeDotConfig dot;/*** 背景色配置*/private CodeBackgroundConfig background;/*** 圆角弧度,0~1,为长宽占比* 设置最终二维码 圆角弧度* 1:原图* 0.3D:圆角矩形*/private Double arc;/*** logo*/private CodeLogoConfig logo;/*** 顶部注释*/private CodeNoteConfig topNote;/*** 底部注释*/private CodeNoteConfig bottomNote;public CodeConfig logo(CodeLogoConfig logo) {this.logo = logo;return this;}/*** 转换为Zxing的二维码配置** @return 配置*/public HashMap<EncodeHintType, Object> toHints() {// 配置final HashMap<EncodeHintType, Object> hints = new HashMap<>();if (null != this.charset) {hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase());}if (null != this.errorCorrection) {hints.put(EncodeHintType.ERROR_CORRECTION, this.errorCorrection);}if (null != this.margin) {hints.put(EncodeHintType.MARGIN, this.margin);}return hints;}public CodeConfig width(Integer width) {this.width = width;return this;}public CodeConfig height(Integer height) {this.height = height;return this;}public CodeConfig margin(Integer margin) {this.margin = margin;return this;}public CodeConfig eyeOut(CodeEyeOutConfig eyeOut) {this.eyeOut = eyeOut;return this;}public CodeConfig eyeInside(CodeEyeInsideConfig eyeInside) {this.eyeInside = eyeInside;return this;}public CodeConfig errorCorrection(ErrorCorrectionLevel errorCorrection) {this.errorCorrection = errorCorrection;return this;}public CodeConfig charset(Charset charset) {this.charset = charset;return this;}public CodeConfig dot(CodeDotConfig dot) {this.dot = dot;return this;}public CodeConfig background(CodeBackgroundConfig background) {this.background = background;return this;}public CodeConfig topNote(CodeNoteConfig topNote) {this.topNote = topNote;return this;}public CodeConfig bottomNote(CodeNoteConfig bottomNote) {this.bottomNote = bottomNote;return this;}public CodeConfig arc(Double arc) {this.arc = arc;return this;}
}
3.8、核心工具类
package com.faea.qrcode.util;import com.faea.qrcode.config.*;
import com.faea.qrcode.constants.CodeEyePositionEnum;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import com.faea.qrcode.model.CodeDotDrawModel;
import com.faea.qrcode.model.CodeEyeInsideDrawModel;
import com.faea.qrcode.model.CodeEyeOutDrawModel;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import lombok.Data;
import lombok.extern.java.Log;import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;/*** 二维码工具类** @author liuchao* @date 2023/2/17*/
@Log
public class CodeUtil {/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content 内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param logoBase64 logo 图片的 base64 编码* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, String logoBase64) {return generateAsBase64(content, config, imageType, Base64.decode(logoBase64));}/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content 内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param logo logo 图片的byte[]* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, byte[] logo) {return generateAsBase64(content, config, imageType, ImgUtil.toImage(logo));}/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content 内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param logo logo 图片的byte[]* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, Image logo) {config.getLogo().image(logo);return generateAsBase64(content, config, imageType);}/*** 生成 Base64 编码格式的二维码,以 String 形式表示** <p>* 输出格式为: data:image/[type];base64,[data]* </p>** @param content 内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType) {final BufferedImage img = generate(content, config);return ImgUtil.toBase64DateUri(img, imageType);}/*** 生成PNG格式的二维码图片,以byte[]形式表示** @param content 内容* @param width 宽度* @param height 高度* @return 图片的byte[]*/public static byte[] generatePng(String content, int width, int height) {final ByteArrayOutputStream out = new ByteArrayOutputStream();generate(content, width, height, ImgUtil.IMAGE_TYPE_PNG, out);return out.toByteArray();}/*** 生成PNG格式的二维码图片,以byte[]形式表示** @param content 内容* @param config 二维码配置,包括长、宽、边距、颜色等* @return 图片的byte[]*/public static byte[] generatePng(String content, CodeConfig config) {final ByteArrayOutputStream out = new ByteArrayOutputStream();generate(content, config, ImgUtil.IMAGE_TYPE_PNG, out);return out.toByteArray();}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content 文本内容* @param width 宽度* @param height 高度* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, int width, int height, File targetFile) {final BufferedImage image = generate(content, width, height);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content 文本内容* @param width 宽度* @param height 高度* @param logo logo图片* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, int width, int height, Image logo, File targetFile) {CodeConfig config = new CodeConfig(width, height).logo(new CodeLogoConfig(logo));final BufferedImage image = generate(content, config);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码到输出流** @param content 文本内容* @param width 宽度* @param height 高度* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param out 目标流*/public static void generate(String content, int width, int height, String imageType, OutputStream out) {final BufferedImage image = generate(content, width, height);ImgUtil.write(image, imageType, out);}/*** 生成二维码到输出流** @param content 文本内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param out 目标流*/public static void generate(String content, CodeConfig config, String imageType, OutputStream out) {final BufferedImage image = generate(content, config);ImgUtil.write(image, imageType, out);}/*** 生成二维码图片** @param content 文本内容* @param width 宽度* @param height 高度* @return 二维码图片(黑白)*/public static BufferedImage generate(String content, int width, int height) {return generate(content, new CodeConfig(width, height));}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content 文本内容* @param config 二维码配置,包括长、宽、边距、颜色等* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, CodeConfig config, File targetFile) {final BufferedImage image = generate(content, config);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码图片** @param content 二维码内容* @param config 二维码配置* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/17*/public static BufferedImage generate(String content, CodeConfig config) {BitMatrix bitMatrix;try {bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE,config.getWidth(), config.getHeight(), config.toHints());} catch (WriterException e) {throw new RuntimeException(e);}//获取二维码图片尺寸信息CodeImageSizeInfo sizeInfo = convertCodeImageSizeInfo(config);BufferedImage image = toImage(bitMatrix, sizeInfo, config);//绘制背景图片image = drawBackgroundImage(image, config, sizeInfo);//绘制logodrawLogo(image, config);//圆角处理if (config.getArc() < 1D) {image = ImgUtil.toBufferedImage(Img.from(image).round(config.getArc()).getImg());}return image;}/*** 通过内容配置转换为字体** @param note* @return java.awt.FontMetrics* @author liuchao* @date 2023/2/17*/private static FontMetrics convertFont(CodeNoteConfig note) {if (null == note) {return null;}Font font = new Font(note.getFont(), Font.PLAIN, note.getSize());JLabel jLabel = new JLabel(note.getContent());//第一次获取非常慢,建议在应用启动的时候加载一次FontMetrics fontMetrics = jLabel.getFontMetrics(font);return fontMetrics;}/*** BitMatrix转BufferedImage** @param matrix BitMatrix* @param sizeInfo 二维码尺寸信息* @param config* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/17*/private static BufferedImage toImage(BitMatrix matrix, CodeImageSizeInfo sizeInfo, CodeConfig config) {final int width = matrix.getWidth();//创建二维码最终ImageBufferedImage image = new BufferedImage(width, sizeInfo.getCodeImageTotalHeight(), BufferedImage.TYPE_INT_ARGB);Graphics2D graphics2D = ImageUtil.imageToGraphics2D(image);//绘制背景drawBackgroundColor(graphics2D, sizeInfo, config, matrix);// 处理上、下注释、码点、码眼drawNoteAndDotEye(graphics2D, sizeInfo, config, matrix);//添加注释drawNote(graphics2D, sizeInfo, config);graphics2D.dispose();image.flush();return image;}/*** 绘制logo** @param image* @param config* @return void* @author liuchao* @date 2023/2/18*/private static void drawLogo(BufferedImage image, CodeConfig config) {if (null == config.getLogo() || null == config.getLogo().getImage()) {return;}CodeLogoConfig logoConfig = config.getLogo();//圆角弧度,0~1,为长宽占比Double arc = logoConfig.getShape().getArc();//缩放BufferedImage srcImage = ImgUtil.toBufferedImage(Img.from(logoConfig.getImage()).scale(logoConfig.getScale()).round(arc).getImg());//增加边框 非原型 则增加边框if (!logoConfig.getShape().equals(CodeLogoShapeEnum.ORIGINAL)) {srcImage = ImageUtil.roundBorder(srcImage, arc);}ImgUtil.pressImage(image, srcImage, new Rectangle(srcImage.getWidth(), srcImage.getHeight()), logoConfig.getAlpha());}/*** 增加注释** @param graphics2D* @param sizeInfo* @param config* @return void* @author liuchao* @date 2023/2/18*/private static void drawNote(Graphics2D graphics2D, CodeImageSizeInfo sizeInfo, CodeConfig config) {if (sizeInfo.getTopNoteHeight() == 0 && sizeInfo.getBottomNoteHeight() == 0) {return;}int startWriteY = 0;//顶部注释if (sizeInfo.getTopNoteHeight() > 0) {CodeNoteConfig topNode = config.getTopNote();drawHorizontalWords(graphics2D, startWriteY, topNode.getContent(), sizeInfo.topNoteFontMetrics, topNode.getFontColor(),config.getWidth(), sizeInfo.getTopNoteRowSize(),topNode.getRowSpacing());startWriteY += sizeInfo.getTopNoteHeight();}//底部注释if (sizeInfo.getBottomNoteHeight() > 0) {startWriteY += config.getHeight();CodeNoteConfig bottomNote = config.getBottomNote();drawHorizontalWords(graphics2D, startWriteY, bottomNote.getContent(), sizeInfo.bottomNoteFontMetrics, bottomNote.getFontColor(),config.getWidth(), sizeInfo.getTopNoteRowSize(),bottomNote.getRowSpacing());}}/*** 在Graphics2D 写横向文字** @param graphics2D* @param startWriteY* @param content* @param fontMetrics* @param fontColor 字体颜色* @param qrCodeWidth* @param rowSize* @param rowSpacing* @return void* @author liuchao* @date 2023/2/18*/private static void drawHorizontalWords(Graphics2D graphics2D, int startWriteY, String content,FontMetrics fontMetrics, Color fontColor, int qrCodeWidth,int rowSize, int rowSpacing) {graphics2D.setFont(fontMetrics.getFont());graphics2D.setColor(fontColor);int rowHeight = fontMetrics.getHeight();//当前写入Y轴int curWriteY = startWriteY + rowHeight;if (rowSize > 1) {//当前行字符StringBuffer curRowWords = new StringBuffer();for (int i = 0; i < content.length(); i++) {curRowWords.append(content.charAt(i));if (i == (content.length() - 1)) {int wordWidth = fontMetrics.stringWidth(curRowWords.toString());int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(curRowWords.toString(), writeX, curWriteY);} else {//判断当前已累加字符+下一个字符 长度是否大于 总宽度StringBuffer tempWords = new StringBuffer(curRowWords).append(content.charAt(i + 1));if (fontMetrics.stringWidth(tempWords.toString()) > qrCodeWidth) {int wordWidth = fontMetrics.stringWidth(curRowWords.toString());int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(curRowWords.toString(), writeX, curWriteY);curWriteY += rowHeight + rowSpacing;curRowWords.delete(0, curRowWords.length());//最后一个字符了}}}} else {int wordWidth = fontMetrics.stringWidth(content);int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(content, writeX, curWriteY);}}/*** 绘制注释+码点+码眼** @param graphics2D* @param sizeInfo* @param config* @param matrix* @return void* @author liuchao* @date 2023/2/22*/private static void drawNoteAndDotEye(Graphics2D graphics2D,CodeImageSizeInfo sizeInfo, CodeConfig config, BitMatrix matrix) {CodeDotConfig dot = config.getDot();final int height = matrix.getHeight();final int width = matrix.getWidth();Boolean leftTopOut = false;Boolean rightTopOut = false;Boolean leftBottomOut = false;Boolean leftTopInside = false;Boolean rightTopInside = false;Boolean leftBottomInside = false;//上注释if (sizeInfo.getTopNoteHeight() > 0) {CodeNoteConfig topNote = config.getTopNote();graphics2D.setColor(topNote.getBackColor());graphics2D.fillRect(0, 0, width, sizeInfo.getTopNoteHeight());}//下注释if (sizeInfo.getBottomNoteHeight() > 0) {CodeNoteConfig bottomNote = config.getBottomNote();graphics2D.setColor(bottomNote.getBackColor());graphics2D.fillRect(0, sizeInfo.getTopNoteHeight() + height, width, sizeInfo.getBottomNoteHeight());}int multiple = matrix.getMultiple();int leftPadding = matrix.getLeftPadding();int topPadding = matrix.getTopPadding();ByteMatrix input = matrix.getInputMatrix();for (int inputY = 0, outputY = topPadding; inputY < input.getHeight(); inputY++, outputY += multiple) {// Write the contents of this row of the barcodefor (int inputX = 0, outputX = leftPadding; inputX < input.getWidth(); inputX++, outputX += multiple) {if (input.get(inputX, inputY) == 1) {int y = outputY + sizeInfo.getTopNoteHeight();// 左上角码眼,外边框if (input.getEyeOutPoint(CodeEyePositionEnum.LEFT_TOP, inputX, inputY) == 1) {if (!leftTopOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));leftTopOut = true;}//右上角码眼,外边框} else if (input.getEyeOutPoint(CodeEyePositionEnum.RIGHT_TOP, inputX, inputY) == 1) {if (!rightTopOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));rightTopOut = true;}//左下角码眼,外边框} else if (input.getEyeOutPoint(CodeEyePositionEnum.LEFT_BOTTOM, inputX, inputY) == 1) {if (!leftBottomOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));leftBottomOut = true;}// 左上角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.LEFT_TOP, inputX, inputY) == 1) {if (!leftTopInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));leftTopInside = true;}// 右上角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.RIGHT_TOP, inputX, inputY) == 1) {if (!rightTopInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));rightTopInside = true;}// 左下角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.LEFT_BOTTOM, inputX, inputY) == 1) {if (!leftBottomInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));leftBottomInside = true;}} else {// 其他黑色区域dot.getShape().draw(graphics2D, new CodeDotDrawModel(dot.getColor(), outputX, y, multiple, multiple));}}}}}/*** 绘制背景图片** @param codeImage* @param config* @param sizeInfo 尺寸信息* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/22*/private static BufferedImage drawBackgroundImage(BufferedImage codeImage, CodeConfig config, CodeImageSizeInfo sizeInfo) {CodeBackgroundConfig background = config.getBackground();if (ObjectUtil.isEmpty(background.getImage())) {return codeImage;}final int codeWidth = codeImage.getWidth();final int codeHeight = codeImage.getHeight();BufferedImage backgroundImage = ImgUtil.toBufferedImage(background.getImage());int imageW = backgroundImage.getWidth(null);int imageH = backgroundImage.getHeight(null);//背景图的宽高不能小于 二维码宽高int w = imageW < config.getWidth() ? config.getWidth() : imageW;int h = imageH < config.getHeight() ? config.getHeight() : imageH;//如果绘制X\Y都等于0,则直接填充if (background.getCodeDrawX() == 0 && background.getCodeDrawY() == 0) {w = config.getWidth();h = config.getHeight() + sizeInfo.getTopNoteHeight() + sizeInfo.getBottomNoteHeight();}// 背景图缩放if (imageW != w || imageH != h) {BufferedImage newBackgroundImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);newBackgroundImage.getGraphics().drawImage(backgroundImage.getScaledInstance(w, h, Image.SCALE_SMOOTH), 0, 0, null);backgroundImage = newBackgroundImage;}Graphics2D graphics2D = ImageUtil.imageToGraphics2D(backgroundImage);int codeDrawX = (w - codeWidth) >> 1;int codeDrawY = (h - codeHeight) >> 1;//如果设置了开始绘制的坐标,则按照这个坐标绘制if (background.getCodeDrawX() != 0 || background.getCodeDrawY() != 0) {codeDrawX = background.getCodeDrawX();codeDrawY = background.getCodeDrawY();}graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1F));graphics2D.drawImage(codeImage.getScaledInstance(codeWidth, codeHeight, Image.SCALE_SMOOTH), codeDrawX, codeDrawY,null);graphics2D.dispose();backgroundImage.flush();return backgroundImage;}/*** 绘制背景图片** @param graphics2D* @param sizeInfo 尺寸信息* @param config 配置信息* @return void* @author liuchao* @date 2023/2/21*/private static void drawBackgroundColor(Graphics2D graphics2D, CodeImageSizeInfo sizeInfo, CodeConfig config, BitMatrix matrix) {CodeBackgroundConfig background = config.getBackground();//如果设置了背景图片,则忽略背景色if (ObjectUtil.isNotEmpty(background.getImage())) {return;}//设置二维码区域背景颜色 全部为设置的背景色graphics2D.setColor(background.getColor());graphics2D.fillRect(0, sizeInfo.getTopNoteHeight(), matrix.getWidth(), matrix.getHeight());}/*** 转换二维码最终图片尺寸信息** @param config 二维码配置信息* @return com.faea.qrcode.util.CodeUtil.CodeImageSizeInfo* @author liuchao* @date 2023/2/18*/private static CodeImageSizeInfo convertCodeImageSizeInfo(CodeConfig config) {int qrCodeWidth = config.getWidth();int qrCodeHeight = config.getHeight();CodeImageSizeInfo info = new CodeImageSizeInfo();//二维码上注释Integer topNoteHeight = 0;CodeNoteConfig topNote = config.getTopNote();if (null != topNote) {FontMetrics topNoteFont = convertFont(topNote);int topNoteRowHeight = topNoteFont.getHeight();int topNoteWidth = topNoteFont.stringWidth(topNote.getContent());if (topNoteWidth > qrCodeWidth) {int topNoteRowSize = topNoteWidth / qrCodeWidth;if (topNoteWidth % qrCodeWidth != 0) {topNoteRowSize += 1;}info.setTopNoteRowSize(topNoteRowSize);topNoteHeight += (topNote.getRowSpacing() + topNoteRowHeight) * topNoteRowSize;} else {topNoteHeight += (topNote.getRowSpacing() + topNoteRowHeight);}info.setTopNoteHeight(topNoteHeight);info.setTopNoteFontMetrics(topNoteFont);}CodeNoteConfig bottomNote = config.getBottomNote();Integer bottomNoteHeight = 0;if (null != bottomNote) {//二维码下注释FontMetrics bottomNoteFont = convertFont(bottomNote);int bottomNoteRowHeight = bottomNoteFont.getHeight();int bottomNoteWidth = bottomNoteFont.stringWidth(bottomNote.getContent());if (bottomNoteWidth > qrCodeWidth) {int bottomNoteRowSize = bottomNoteWidth / qrCodeWidth;if (bottomNoteWidth % qrCodeWidth != 0) {bottomNoteRowSize += 1;}info.setBottomNoteRowSize(bottomNoteRowSize);bottomNoteHeight += (bottomNote.getRowSpacing() + bottomNoteRowHeight) * bottomNoteRowSize;} else {bottomNoteHeight += (bottomNote.getRowSpacing() + bottomNoteRowHeight);}info.setBottomNoteHeight(bottomNoteHeight);info.setBottomNoteFontMetrics(bottomNoteFont);}info.setCodeImageTotalHeight(topNoteHeight + qrCodeHeight + bottomNoteHeight);return info;}/*** 二维码尺寸信息** @author liuchao* @date 2023/2/18*/@Datastatic class CodeImageSizeInfo {/*** 顶部注释占用高度*/int topNoteHeight;/*** 顶部注释 字体信息*/FontMetrics topNoteFontMetrics;/*** 顶部注释占用行数*/int topNoteRowSize;/*** 底部注释占用高度*/int bottomNoteHeight;/*** 底部注释占用行数*/int bottomNoteRowSize;/*** 底部注释 字体信息*/FontMetrics bottomNoteFontMetrics;/*** 二维码图片总高度*/int codeImageTotalHeight;}}
4、效果查看
4.1、效果单元测试
总体写了14个单元测试,通过单元测试可以看出,使用起来还是很方便的。
package com.faea;import com.faea.qrcode.config.CodeConfig;
import com.faea.qrcode.config.CodeLogoConfig;
import com.faea.qrcode.config.CodeNoteConfig;
import com.faea.qrcode.constants.CodeDotShapeEnum;
import com.faea.qrcode.constants.CodeEyeInsideShapeEnum;
import com.faea.qrcode.constants.CodeEyeOutShapeEnum;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import com.faea.qrcode.util.CodeUtil;
import org.junit.Test;import java.awt.*;
import java.io.File;/*** 二维码测试** @author liuchao* @date 2023/2/23*/
public class CodeTest {/*** 保存二维码地址*/private static final String SAVE_FILE_PATH = "src/test/code/";/*** 二维码内容*/private static final String content = "https://blog.csdn.net/u011837804";/*** 注释*/private static final String note = "Java全栈行动派";/*** 普通二维码*/@Testpublic void test1() {CodeUtil.generate(content, 300, 300,new File(SAVE_FILE_PATH + "gen/test1.png"));}/*** 带logo*/@Testpublic void test2() {CodeUtil.generate(content, 300, 300,ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png")),new File(SAVE_FILE_PATH + "gen/test2.png"));}/*** 圆形logo*/@Testpublic void test3() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))).shape(CodeLogoShapeEnum.CIRCLE));CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test3.png"));}/*** 原型logo*/@Testpublic void test4() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))).shape(CodeLogoShapeEnum.ORIGINAL));CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test4.png"));}/*** 圆角二维码*/@Testpublic void test5() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png")))).arc(0.3D);CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test5.png"));}/*** 背景颜色*/@Testpublic void test6() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.getBackground().color(ImgUtil.getColor(129590));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test6.png"));}/*** 设置背景图片*/@Testpublic void test7() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.getBackground().image(ImgUtil.read(new File(SAVE_FILE_PATH + "background.png")));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test7.png"));}/*** 设置上注释*/@Testpublic void test8() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.topNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test8.png"));}/*** 设置上注释 + 背景颜色+ 字颜色*/@Testpublic void test9() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.topNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test9.png"));}/*** 设置下注释*/@Testpublic void test10() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test10.png"));}/*** 设置码点颜色*/@Testpublic void test11() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test11.png"));}/*** 设置码眼颜色*/@Testpublic void test12() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE);config.getEyeInside().color(Color.BLUE);config.getEyeOut().color(Color.RED);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test12.png"));}/*** 设置码点形状*/@Testpublic void test13() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE).shape(CodeDotShapeEnum.DIAMOND);config.getEyeInside().color(Color.BLUE);config.getEyeOut().color(Color.RED);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test13.png"));}/*** 设置码眼形状*/@Testpublic void test14() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE).shape(CodeDotShapeEnum.TRIANGLE);config.getEyeInside().color(Color.BLUE).shape(CodeEyeInsideShapeEnum.ROUNDED);config.getEyeOut().color(Color.RED).shape(CodeEyeOutShapeEnum.ROUNDED_THICK_EDGE);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test14.png"));}
}
4.2、生成所有的效果二维码
由于文章审核要求,所以做了图片模糊,但是二维码优化效果还是可以看清楚的。

不可否认的是要实现这些功能,需要对二维码生成原理、zxing源码等非常属性,着实让小编下了很大的功能,从上面小编贴出来的代码可以看出针对每个设置项注释是非常到位的,核心工具类也贴出来了,希望对朋友们的开发有帮助,全部源码下载地址:https://download.csdn.net/download/u011837804/87490526 。
相关文章:

Java自定义生成二维码(兼容你所有的需求)
1、概述作为Java开发人员,说到生成二维码就会想到zxing开源二维码图像处理库,不可否认的是zxing确实很强大,但是实际需求中会遇到各种各样的需求是zxing满足不了的,于是就有了想法自己扩展zxing满足历史遇到的各种需求,…...

Spring事务的隔离级别
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题 事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题 事务的特性: 隔离性:多个事务在并发执行的时候,多个事务执行的一个行为模式,当一个事务执行的时候,另一个事务执行的一个行…...

JVM系统优化实践(4):以支付系统为例
您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~前面说过,JVM会将堆内存划分为年轻代、老年代两个区域。年轻代会将创建和使用完之后马上就要回收的对象放在里面,而老年代则将创建之后需要…...

16- TensorFlow实现线性回归和逻辑回归 (TensorFlow系列) (深度学习)
知识要点 线性回归要点: 生成线性数据: x np.linspace(0, 10, 20) np.random.rand(20)画点图: plt.scatter(x, y)TensorFlow定义变量: w tf.Variable(np.random.randn() * 0.02)tensor 转换为 numpy数组: b.numpy()定义优化器: optimizer tf.optimizers.SGD()定义损失: …...
无自动化测试系统设计方法论
灵活 敏捷 迭代。 自动化测试 辩思 测试必不可少 想想看没有充分测试的代码, 哪一次是一次过的? 哪一次不需要经历下测试的鞭挞? 不要以为软件代码容易改, 就对于质量不切实际的自信—那是自大! 不适用自动化测试的case 遗留系统。太多的依赖方, 不想用过多的mock > …...

架构初探-学习笔记
1 什么是架构 有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。 1.1 单机架构 所有功能都实现在一个进程里,并部署在一台机器上。 1.2 单体架构 分布式部署单机架构 1.3 垂直应用架构 按应用垂直切分的单体架构 1.4 SOA架构 将…...

在成都想转行IT,选择什么专业比较好?
很多创新型的互联网服务公司的核心其实都是软件,创新的基础、运行的支撑都是软件。例如,软件应用到了出租车行业,就形成了巅覆行业的滴滴;软件应用到了金融领域,就形成互联网金融;软件运用到餐饮行业,就形成美团;软件运…...

【Spark分布式内存计算框架——Spark Streaming】4.入门案例(下)Streaming 工作原理
2.3 Streaming 工作原理 SparkStreaming处理流式数据时,按照时间间隔划分数据为微批次(Micro-Batch),每批次数据当做RDD,再进行处理分析。 以上述词频统计WordCount程序为例,讲解Streaming工作原理。 创…...

2、算法先导---思维能力与工具
题目 碎纸片的拼接复原(2013B) 内容 破碎文件的拼接在司法物证复原、历史文献修复以及军事情报获取等领域都有着重要的应用。传统上,拼接复原工作需由人工完成,准确率较高,但效率很低。特别是当碎片数量巨大,人工拼接很难在短时…...

WordPress 函数:add_theme_support() 开启主题自定义功能(全面)
add_theme_support() 用于在我们的当前使用的主题添加一些特殊的功能,函数一般写在主题的functions.php文件中,当然也可以再插件中使用钩子来调用该函数,如果是挂在钩子上,那他必须挂在after_setup_theme钩子上,因为 i…...

Winform控件开发(16)——Timer(史上最全)
前言: Timer控件的作用是按用户定义的时间间隔引发事件的计时器,说的直白点就是,他就像一个定时炸弹一样到了一定时间就爆炸一次,区别在于定时炸弹炸完了就不会再次爆炸了,但是Timer这个计时器到了下一个固定时间还会触发一次,上面那张图片就是一个典型的计时器,该定时器…...

游戏高度可配置化:通用数据引擎(data-e)及其在模块化游戏开发中的应用构想图解
游戏高度可配置化:通数据引擎在模块化游戏开发中的应用构想图解 ygluu 码客 卢益贵 目录 一、前言 二、模块化与插件 1、常规模块化 2、插件式模块化(插件开发) 三、通用数据引擎理论与构成 1、名字系统(数据类型…...

CountDownLatch与CyclicBarrier原理剖析
1.CountDownLatch 1.1 什么是CountDownLatch CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。 CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之…...

NLP中的对话机器人——预训练基准模型
引言 本文是七月在线《NLP中的对话机器人》的视频笔记,主要介绍FAQ问答型聊天机器人的实现。 场景二 上篇文章中我们解决了给定一个问题和一些回答,从中找到最佳回答的任务。 在场景二中,我们来实现: 给定新问题,从…...
C语言学习及复习笔记-【14】C文件读写
14 C文件读写 14.1打开文件 您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE包含了所有用来控制流的必要的信息。下面是这个函数调用的原型: FILE *fopen( const char…...
模拟退火算法优化灰色
clc; clear; close all; warning off; %% tic T01000; % 初始温度 Tend1e-3; % 终止温度 L200; % 各温度下的迭代次数(链长) q0.9; %降温速率 X[16.4700 96.1000 16.4700 94.4400 20.0900 92.5400 22.3900 93.3700 25.…...

Pandas怎么添加数据列删除列
Pandas怎么添加数据列 1、直接赋值 # 1、直接赋值df.loc[:, "最高气温"] df["最高气温"].str.replace("℃", "").astype("int32")df.loc[:, "最低气温"] df["最低气温"].str.replace("℃"…...

C++类和对象:构造函数和析构函数
目录 一. 类的六个默认成员函数 二. 构造函数 2.1 什么是构造函数 2.2 编译器自动生成的默认构造函数 2.3 构造函数的特性总结 三. 析构函数 3.1 什么是析构函数 3.2 编译器自动生成的析构函数 3.3 析构函数的特性总结 一. 类的六个默认成员函数 对于任意一个C类&…...

【Stata】从入门到精通.零基础小白必学的教程,一学就fei
视频教程移步:https://www.bilibili.com/video/BV1hK4y1d714/?p4&spm_id_frompageDriver&vd_sourcecc8074e9c81a225f214226065db53d32P3 第二讲 Stata处理数据全流程(上) P3 - 01:37内置数据 file example datasets使用…...
【RuoYi优化】调整JVM启动内存
📔 笔记介绍 大家好,千寻简笔记是一套全部开源的企业开发问题记录,毫无保留给个人及企业免费使用,我是作者星辰,笔记内容整理并发布,内容有误请指出,笔记源码已开源,前往Gitee搜索《chihiro-notes》,感谢您的阅读和关注。 作者各大平台直链: GitHub | Gitee | CSD…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...