ps:SpringBoot
-
ZXing:
是一个用于生成和解析多种条形码和二维码格式的库。支持多种编码格式,包括QR码、Code 128、UPC等。可用于Android、Java(SE)、C++、JavaScript、.NET、Python等平台和语言的应用。允许开发者在应用程序中集成条形码和二维码的扫描、生成和解析功能。ZXing项目提供了一个完成度高的Android应用示例,它展示了如何使用库在移动设备上扫描和生成码。开源并在Apache 2.0许可下发布。 -
QR码: 是一种由日本Denso Wave公司创建的特定二维码格式。能够编码文本信息,并因其快速读取能力而得名 “快速反应”(Quick Response)码。通常用于存储URL、联系信息、纯文本、电话号码等。拥有较高的容错率,即使码部分被遮挡也可以识别。常见于广告、产品跟踪、物品标识和手机应用中。不是开源项目,但Denso Wave对QR码的专利并未穷追用户费用,因此它被广泛采用。
当我们说ZXing与QR码的比较时,我们实际上是在比较一个库(ZXing)和它能处理的一种数据格式(Q码)。你可以使用ZXing这个库来生成QR码或解析QR码内容,而QR码本身是一种信息存储格式。
pxm
<!--zxing生成二维码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
QRCodeUtils
/**
* @ClassName : QRCodeUtil
* @Description : 二维码工具类
*/
@Slf4j
public class QRCodeGeneratorUtils {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "png";
// 二维码尺寸
private static final int QRCODE_SIZE = 400;
// LOGO宽度
private static final int WIDTH = 60;
// LOGO高度
private static final int HEIGHT = 60;
/**
* 生成包含 标题说明 LOGO 的二维码图片
*
* @param mainTitle 主标题 底部显示
* @param subTitle 副标题 底部显示
* @param content 二维码内容
* @param imgPath LOGO文件路径
* @param needCompress LOGO 是否压缩
* @return 二维码图片流
* @throws Exception
*/
private static BufferedImage createImage(String mainTitle, String subTitle, String content, String imgPath, boolean needCompress) throws Exception {
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 5);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE + 100, hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int tempHeight = height;
// 二维码图片大小 有文字放大
boolean notEmptyMainTitle = StringUtils.isNotEmpty(mainTitle);
if (notEmptyMainTitle) {
// tempHeight += 200;
}
BufferedImage image = new BufferedImage(width, tempHeight, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
// 插入图片LOGO
if (StringUtils.isNotEmpty(imgPath)) {
QRCodeGeneratorUtils.insertImage(image, imgPath, needCompress);
}
//主标题:插入文字
if (StringUtils.isNotEmpty(subTitle)) {
Font font = new Font("微软雅黑", Font.BOLD, 24);
addFontImage(image, mainTitle, 20, 50, font);
}
//副标题:插入文字
if (notEmptyMainTitle) {
Font font = new Font("微软雅黑", Font.PLAIN, 16);
addFontImage(image, subTitle, 70, 20, font);
}
return image;
}
/**
* 添加 底部图片文字
*
* @param source 图片源
* @param declareText 文字本文
*/
private static void addFontImage(BufferedImage source, String declareText, int offsetY, int fontHeight, Font font) {
//设置文本图片宽高
BufferedImage textImage = strToImage(declareText, QRCODE_SIZE, fontHeight, font);
Graphics2D graph = source.createGraphics();
//开启文字抗锯齿
graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int width = textImage.getWidth(null);
int height = textImage.getHeight(null);
Image src = textImage;
//画图 文字图片最终显示位置 在Y轴偏移量 从上往下算
graph.drawImage(src, 0, QRCODE_SIZE - 20, width, height, null);
//graph.drawImage(src, 0, offsetY, width, height, null);
graph.dispose();
}
/**
* 处理文字大小 生成文字图片
*
* @param str
* @param width
* @param height
* @return 文本图片
*/
private static BufferedImage strToImage(String str, int width, int height, Font font) {
BufferedImage textImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) textImage.getGraphics();
//开启文字抗锯齿
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setBackground(Color.WHITE);
g2.clearRect(0, 0, width, height);
g2.setPaint(Color.BLACK);
FontRenderContext context = g2.getFontRenderContext();
// Font font = new Font("微软雅黑", Font.BOLD, FONT_SIZE);
g2.setFont(font);
LineMetrics lineMetrics = font.getLineMetrics(str, context);
FontMetrics fontMetrics = FontDesignMetrics.getMetrics(font);
float offset = (width - fontMetrics.stringWidth(str)) / 2;
float y = (height + lineMetrics.getAscent() - lineMetrics.getDescent() - lineMetrics.getLeading()) / 2;
g2.drawString(str, (int) offset, (int) y);
return textImage;
}
/**
* 插入LOGO
*
* @param source 二维码图片
* @param imgPath LOGO图片地址
* @param needCompress 是否压缩
* @throws Exception
*/
private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
File file = new File(imgPath);
if (!file.exists()) {
System.err.println("" + imgPath + " 该文件不存在!");
return;
}
Image src = ImageIO.read(new File(imgPath));
int width = src.getWidth(null);
int height = src.getHeight(null);
if (needCompress) { // 压缩LOGO
if (width > WIDTH) {
width = WIDTH;
}
if (height > HEIGHT) {
height = HEIGHT;
}
Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null); // 绘制缩小后的图
g.dispose();
src = image;
}
// 插入LOGO
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graph.drawImage(src, x, y, width, height, null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
graph.setStroke(new BasicStroke(3f));
graph.draw(shape);
graph.dispose();
}
/**
* 生成二维码(内嵌LOGO) 本地保存
*
* @param content 内容
* @param imgPath LOGO地址
* @param destPath 存放目录
* @param needCompress 是否压缩LOGO
* @throws Exception
*/
public static String encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
BufferedImage image = QRCodeGeneratorUtils.createImage(null, null, content, imgPath, needCompress);
mkdirs(destPath);
// 随机生成二维码图片文件名
String file = UUID.randomUUID() + ".jpg";
ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
return destPath + file;
}
/**
* 创建二维码 底部有标题 LOGO
*
* @param mainTitle 主标题
* @param subTitle 副标题
* @param content 二维码内容
* @param imgPath 图片地址
* @param filePath 临时存储本地图片地址
* @param needCompress LOGO是否压缩 推荐为True
*/
public static File createTitleImage(String mainTitle, String subTitle, String content, String imgPath, boolean needCompress, String filePath) {
try {
//创建二维码
BufferedImage image = QRCodeGeneratorUtils.createImage(mainTitle, subTitle, content, imgPath, needCompress);
// 创建目录
File directory = new File(filePath).getParentFile();
if (!directory.exists()) {
directory.mkdirs();
}
// 将图片数据写入文件
File outputfile = new File(filePath);
ImageIO.write(image, FORMAT_NAME, outputfile);
// 返回文件对象
return outputfile;
} catch (Exception e) {
log.error("创建二维码异常,{}", filePath);
throw new RuntimeException(e);
}
}
/**
* 创建二维码 底部有标题 LOGO
*
* @param mainTitle 主标题
* @param subTitle 副标题
* @param content 二维码内容
* @param imgPath 图片地址
* @param needCompress LOGO是否压缩 推荐为True
* @param response 响应流
*/
public static void createAndDownTitleImage(String mainTitle, String subTitle, String content, String imgPath, boolean needCompress, HttpServletResponse response) {
BufferedImage image = null;
ImageOutputStream imageOutput = null;
long length = 0;
try {
//创建二维码
image = QRCodeGeneratorUtils.createImage(mainTitle, subTitle, content, imgPath, needCompress);
//步骤一:BufferedImage 转 InputStream
/*ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
imageOutput = ImageIO.createImageOutputStream(byteArrayOutputStream);
ImageIO.write(image, "png", imageOutput);*/
String filePath = "C:\\Users\\123\\Downloads\\22222.png";
File barcodeFile = new File(filePath);
ImageIO.write(image, "png", barcodeFile);
System.out.println("QR码已生成并保存到: " + filePath);
//步骤二:获得文件长度
/*length = imageOutput.length();
// 文件名 类型需要注明
String fileName = "6S-【" + title + "】点检.png";
//设置文件长度
response.setContentLength((int) length);
//输入流
InputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
//步骤三:传入文件名、输入流、响应
fileDownload(fileName, inputStream, response);*/
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//文件下载方法,工具类
public static void fileDownload(String filename, InputStream input, HttpServletResponse response) {
try {
byte[] buffer = new byte[input.available()];
input.read(buffer);
input.close();
// 清空response
response.reset();
// 设置response的Header
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("utf-8"), "ISO-8859-1"));
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
toClient.write(buffer);
toClient.flush();
//关闭,即下载
toClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
*
* @param destPath 存放目录
* @author lanyuan Email: mmm333zzz520@163.com
* @date 2013-12-11 上午10:16:36
*/
public static void mkdirs(String destPath) {
File file = new File(destPath);
// 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
if (!file.exists() || !file.isDirectory()) {
file.mkdirs();
}
}
/**
* 文件压缩
*
* @param filePaths 压缩文件路径
* @param zipPath zip文件路径
*/
public static void zip(List<String> filePaths, String zipPath) {
ZipOutputStream zipOutput = null;
try {
File zipFile = new File(zipPath);
// 判断文件是否存在,如文件不存在创建一个新文件
if (!zipFile.exists()) {
zipFile.createNewFile();
}
// 创建一个zip文件输出流
zipOutput = new ZipOutputStream(new FileOutputStream(zipFile));
for (String filePath : filePaths) {
File file = new File(filePath);
// 判断文件是否存在,如不存在直接跳过
if (!file.exists()) {
continue;
}
/**
* 创建一个缓冲读取流,提高读取效率
* 也可以直接创建一个 FileInputStream 对象,BufferedInputStream内部维护了一个8KB的缓冲区,BufferedInputStream本身不具备读取能力
* BufferedInputStream 可以手动指定缓冲区大小 单位为字节例如:new BufferedInputStream(new FileInputStream(file), 10240)
*/
BufferedInputStream bufferedInput = new BufferedInputStream(new FileInputStream(file));
// 设置压缩条目名称
zipOutput.putNextEntry(new ZipEntry(file.getName()));
byte[] bytes = new byte[1024];
int len = -1;
// 读取file内的字节流,写入到zipOutput内
while ((len = bufferedInput.read(bytes)) != -1) {
zipOutput.write(bytes, 0, len);
}
// 关闭输入流
// 无需关闭new FileInputStream(file)的输入流 因为BufferedInputStream.close()方法内部已经调用了FileInputStream.close()方法
bufferedInput.close();
// 写入完毕后关闭条目
zipOutput.closeEntry();
}
} catch (Exception e) {
log.info("文件压缩失败,{}", e.getMessage(), e);
} finally {
try {
if (zipOutput != null) {
// 写入完毕后关闭条目
zipOutput.close();
}
} catch (Exception e) {
log.error("文件压缩:关闭流异常,{}", e.getMessage(), e);
}
}
}
}
IZXService
/**
* @title IZXService
*/
public interface IZXService {
/**
* 生成包含 标题说明 LOGO 的二维码图片
*
* @param mainTitle 主标题 底部显示
* @param subTitle 副标题 底部显示
* @param fileName 文件名称
* @param content 二维码内容
* @param imgPath LOGO文件路径
* @param needCompress LOGO 是否压缩
* @return 二维码图片流
* @throws Exception
*/
String createTitleImage(String mainTitle,String subTitle,String fileName, String content, String imgPath, boolean needCompress);
}
IZXServiceImpl
/**
* @title ZXServiceImpl
*/
@Service
@Slf4j
public class ZXServiceImpl implements IZXService {
@Value("${qr-code.filePath:}")
private String filePath;
@Resource
private OssTemplate ossTemplate;
/**
* 生成包含 标题说明 LOGO 的二维码图片
*
* @param mainTitle 主标题 底部显示
* @param subTitle 副标题 底部显示
* @param subTitle 文件名称
* @param content 二维码内容
* @param imgPath LOGO文件路径
* @param needCompress LOGO 是否压缩
* @return 二维码图片流
* @throws Exception
*/
@Override
public String createTitleImage(String mainTitle, String subTitle,String fileName, String content, String imgPath, boolean needCompress) {
File file = null;
SystemOssFile systemOssFile;
try {
file = QRCodeGeneratorUtils.createTitleImage(mainTitle, subTitle, content, imgPath, true,filePath+fileName);
systemOssFile = ossTemplate.putFileReturnOriginalName(file);
Assert.notNull(systemOssFile,"上传文件失败");
}catch (Exception e){
log.error("生成二维码异常,{}",e.getMessage(),e);
throw new RuntimeException("生成二维码异常");
}finally {
if(ObjectUtils.isNotEmpty(file)){
file.delete();
}
}
return systemOssFile.getLink();
}
}
使用
@Override
public void save(LandInfoForm form) {
// 保存
// 生成二维码并更新qrCode
String qrCodeName =""; // 区县/镇街/村/地块名称
String titleImage = izxService.createTitleImage("", qrCodeName, qrCodeName + "-" + landInfo.getId() + ".png", String.valueOf(landInfo.getCode()), "", true);
landInfo.setQrCode(titleImage);
baseMapper.updateById(landInfo);
}
zip包下载
一
@Override
public void export(LandInfoForm form, HttpServletResponse response) {
File codeFile = null;
FileInputStream fileInputStream = null;
List<String> filepath = Collections.synchronizedList(new ArrayList<>());
if (CollectionUtil.isNotEmpty(form.getIdList()) && form.getIdList().size() > 0) {
List<LandInfo> landInfoList = baseMapper.selectBatchIds(form.getIdList());
if (CollectionUtil.isNotEmpty(landInfoList)) {
// 生成所有二维码
codeFile = getFile(filepath, landInfoList);
// 一次性打包
packIntoPackage(response, codeFile, fileInputStream, filepath);
}
}
}
private File getFile(List<String> filepath, List<LandInfo> list) {
//zip文件保存在服务器上的地址
String codePath = zipPath;
File codeFile;
//判断是否为文件
codeFile = new File(codePath);
// 判断文件夹是否存在,如文件不存在创建一个新文件
if (!codeFile.exists()) {
codeFile.mkdir();
}
//生成二维码文件
list.parallelStream().forEach(i -> {
filepath.add(getFilePath(i));
});
return codeFile;
}
private String getFilePath(LandInfo landInfo) {
String[] split = landInfo.getAreaCodes().split(",");
if (split.length < 5) throw new RuntimeException("地块区域信息不完整");
String qrCodeName = getSysArea(split[2]) + getSysArea(split[3]) + getSysArea(split[4]) + "-" + landInfo.getName(); // 区县/镇街/村/地块名称
return izxService.createQRCodeStream("", qrCodeName, qrCodeName + "-" + landInfo.getId() + ".png", String.valueOf(landInfo.getCode()), "", true);
}
private void packIntoPackage(HttpServletResponse response, File codeFile, FileInputStream fileInputStream, List<String> filepath) {
//zip文件保存在服务器上的地址
String codePath = zipPath;
try {
//打包成zip文件
String fileName = new StringBuilder(System.currentTimeMillis() + "ms").append("_code.zip").toString();
String zip = new StringBuilder(codePath).append("\\").append(fileName).toString();
QRCodeGeneratorUtils.zip(filepath, zip);
//从该应用服务器下载文件
fileInputStream = new FileInputStream(zip);
downFile(fileInputStream, response, fileName);
} catch (Exception e) {
log.error("下载失败,{}", e.getMessage(), e);
throw new RuntimeException("下载失败");
} finally {
//删除服务器中的文件
try {
if (fileInputStream != null) {
fileInputStream.close();
}
//删除二维码
FileUtils.forceDelete(codeFile);
} catch (Exception e) {
log.error("删除二维码zip异常,{}", e.getMessage(), e);
}
}
}
public static void downFile(InputStream inputStream, HttpServletResponse response, String fileName) {
try {
response.reset();
response.setContentType(getContentType(fileName));
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
byte[] buf = new byte[1024];
int length = 0;
//不断的读取了文件的资源
while ((length = inputStream.read(buf)) != -1) {
//向浏览器输出
out.write(buf, 0, length);
}
//关闭 资源
out.flush();
out.close();
inputStream.close();
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getContentType(String returnFileName) {
String contentType = "application/octet-stream";
if (returnFileName.lastIndexOf(".") < 0) {
return contentType;
}
returnFileName = returnFileName.toLowerCase();
returnFileName = returnFileName.substring(returnFileName.lastIndexOf(".") + 1);
if ("html".equals(returnFileName) || "htm".equals(returnFileName) || "shtml".equals(returnFileName)) {
contentType = "text/html";
} else if ("xls".equals(returnFileName)) {
contentType = "application/vnd.ms-excel";
} else if ("zip".equals(returnFileName)) {
contentType = "application/zip";
}
return contentType;
}
二
文件下载导出解压
@PostMapping("/getOssFile")
@ApiOperationSupport(order = 8)
@ApiOperation(value = "下载", notes = "下载")
public R<String> getOssFile(HttpServletRequest req, HttpServletResponse response, @RequestParam("fileName") String fileName){
List<Household> households = householdService.getBaseMapper().selectList(Wrappers.lambdaQuery(Household.class)
.eq(Household::getTenantId, "331003200205")
.eq(Household::getIsDeleted, 0));
ArrayList<String> keyList = new ArrayList<>();
Map<String, String> hashMap = new HashMap<>();
for (Household household : households) {
if (StringUtil.isNotBlank(household.getQrCodeImg())){
String substring = household.getQrCodeImg().substring(47);
hashMap.put(substring,household.getHouseholdNo()+".png");
keyList.add(substring);
}
}
//特别注意 这个文件路径 只需要文件路径 不需要带域名之类的 并且文件全路径要和桶名称对应上
//keyList.add("upload/20220915/18768cb97623e8e0f36b5f7980ed1720.png");
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
String accessKeyId = "LTAI4FeddqmzyunKGQmt36Vx";
String accessKeySecret = "8xpmbrDgDbunlfjtxA1JsgCAciXKMz";
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 创建临时文件
File zipFile = null;
try {//临时文件名称
zipFile = File.createTempFile("test", ".zip");
FileOutputStream f = new FileOutputStream(zipFile);
/**
* 作用是为任何OutputStream产生校验和
* 第一个参数是制定产生校验和的输出流,第二个参数是指定Checksum的类型 (Adler32(较快)和CRC32两种)
*/
CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
// 用于将数据压缩成Zip文件格式
ZipOutputStream zos = new ZipOutputStream(csum);
for (String ossFile : keyList) {
// 获取Object,返回结果为OSSObject对象
OSSObject ossObject = ossClient.getObject("meiyun-mng", ossFile);
// 读去Object内容 返回
InputStream inputStream = ossObject.getObjectContent();
// 对于每一个要被存放到压缩包的文件,都必须调用ZipOutputStream对象的putNextEntry()方法,确保压缩包里面文件不同名
//String name=ossFile.substring(ossFile.lastIndexOf("/")+1);
String name = "";
if (hashMap.containsKey(ossFile)){
String householdNo = hashMap.get(ossFile);
name = householdNo;
}
zos.putNextEntry(new ZipEntry(name));
int bytesRead = 0;
// 向压缩文件中输出数据
while ((bytesRead = inputStream.read()) != -1) {
zos.write(bytesRead);
}
inputStream.close();
zos.closeEntry(); // 当前文件写完,定位为写入下一条项目
}
zos.close();
String header = req.getHeader("User-Agent").toUpperCase();
if (header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE")) {
fileName = URLEncoder.encode(fileName, "utf-8");
//IE下载文件名空格变+号问题
fileName = fileName.replace("+", "%20");
} else {
fileName = new String(fileName.getBytes(), "ISO8859-1");
}
response.reset();
response.setContentType("text/plain");
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Location", fileName);
response.setHeader("Cache-Control", "max-age=0");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
FileInputStream fis = new FileInputStream(zipFile);
BufferedInputStream buff = new BufferedInputStream(fis);
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
byte[] car = new byte[1024];
int l = 0;
while (l < zipFile.length()) {
int j = buff.read(car, 0, 1024);
l += j;
out.write(car, 0, j);
}
// 关闭流
fis.close();
buff.close();
out.close();
ossClient.shutdown();
// 删除临时文件
zipFile.delete();
} catch (IOException e1) {
e1.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
return R.status(true);
}