SpringBoot集成ZXing實(shí)現(xiàn)二維碼的生成與讀取功能
一、概述
本教程將詳細(xì)講解如何在 Spring Boot 項(xiàng)目中集成 ZXing 庫(kù)實(shí)現(xiàn)二維碼的生成(返回 Base64 編碼)和讀取(解析圖片的二維碼)功能,并覆蓋常見異常處理、參數(shù)優(yōu)化等實(shí)戰(zhàn)要點(diǎn),適合 Java 開發(fā)新手快速上手。
二、環(huán)境準(zhǔn)備
2.1 技術(shù)棧
- 框架:Spring Boot 2.x/3.x(本人測(cè)試SpringBoot版本影響較小)
- 核心依賴:ZXing(二維碼處理)
- 開發(fā)工具:IDEA/Eclipse、Maven
2.2 依賴引入
在 pom.xml 中添加 ZXing 核心依賴:
<!-- SpringBoot啟動(dòng)器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- SpringBoot的web啟動(dòng)器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ZXing 二維碼核心依賴 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version> <!-- 推薦使用最新穩(wěn)定版 -->
</dependency>
<!-- ZXing JavaSE 擴(kuò)展(處理圖片) -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.2</version>
</dependency>三、工具類封裝
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
//注意JDK11以上javax包名需要修改為jakarta
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 二維碼工具類(生成+讀?。?
* 包含異常處理、參數(shù)優(yōu)化、Base64 轉(zhuǎn)換等功能
*/
public class QRCodeUtil {
// 默認(rèn)二維碼寬度/高度
private static final int DEFAULT_SIZE = 300;
// 默認(rèn)字符編碼
private static final String DEFAULT_CHARSET = "UTF-8";
// 二維碼顏色(黑色)
private static final int QR_CODE_COLOR = 0xFF000000;
// 二維碼背景色(白色)
private static final int QR_CODE_BACKGROUND = 0xFFFFFFFF;
/**
* 生成二維碼 BufferedImage 對(duì)象
* @param content 二維碼內(nèi)容(必填)
* @return BufferedImage 二維碼圖片
* @throws WriterException 生成失敗異常
*/
public static BufferedImage createQRCode(String content) throws WriterException {
return createQRCode(content, DEFAULT_SIZE, DEFAULT_SIZE);
}
/**
* 自定義尺寸生成二維碼
* @param content 二維碼內(nèi)容
* @param width 寬度
* @param height 高度
* @return BufferedImage
* @throws WriterException 內(nèi)容為空/尺寸非法時(shí)拋出
*/
public static BufferedImage createQRCode(String content, int width, int height) throws WriterException {
// 前置校驗(yàn)
if (content == null || content.isEmpty()) {
throw new WriterException("二維碼內(nèi)容不能為空");
}
if (width <= 0 || height <= 0) {
throw new WriterException("二維碼尺寸必須大于0");
}
// 配置二維碼參數(shù)(關(guān)鍵:解決中文亂碼、提升容錯(cuò)率)
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, DEFAULT_CHARSET); // 字符編碼
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 最高容錯(cuò)率(30%)
hints.put(EncodeHintType.MARGIN, 1); // 邊距(0=無(wú)白邊)
// 生成二維碼矩陣
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
// 轉(zhuǎn)換為 BufferedImage(自定義顏色)
MatrixToImageConfig config = new MatrixToImageConfig(QR_CODE_COLOR, QR_CODE_BACKGROUND);
return MatrixToImageWriter.toBufferedImage(bitMatrix, config);
}
/**
* 將 BufferedImage 轉(zhuǎn)換為字節(jié)數(shù)組
* @param image 二維碼圖片
* @return 字節(jié)數(shù)組
* @throws IOException 圖片轉(zhuǎn)換失敗
*/
public static byte[] imageToBytes(BufferedImage image) throws IOException {
if (image == null) {
throw new IOException("圖片對(duì)象不能為空");
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", outputStream);
return outputStream.toByteArray();
}
/**
* 將字節(jié)數(shù)組轉(zhuǎn)換為 Base64 編碼字符串(純編碼,無(wú)前綴)
* @param bytes 圖片字節(jié)數(shù)組
* @return Base64 字符串
*/
public static String imageToBase64(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
throw new IllegalArgumentException("字節(jié)數(shù)組不能為空");
}
return Base64.getEncoder().encodeToString(bytes);
}
/**
* 讀取圖片流中的二維碼內(nèi)容
* @param inputStream 圖片輸入流(如文件流、網(wǎng)絡(luò)流)
* @return 二維碼內(nèi)容
* @throws NotFoundException 未識(shí)別到二維碼
* @throws IOException 圖片讀取失敗
* @throws ChecksumException 二維碼數(shù)據(jù)校驗(yàn)失敗
* @throws FormatException 二維碼格式錯(cuò)誤
*/
public static String readQRCode(InputStream inputStream) throws NotFoundException, IOException, ChecksumException, FormatException {
if (inputStream == null) {
throw new IOException("圖片輸入流不能為空");
}
BufferedImage image = ImageIO.read(inputStream);
if (image == null) {
throw new IOException("無(wú)法解析圖片,請(qǐng)檢查文件格式");
}
// 配置解析參數(shù)(提升識(shí)別率)
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); // 嘗試更高精度解析
hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); // 僅解析二維碼
hints.put(DecodeHintType.CHARACTER_SET, DEFAULT_CHARSET); // 字符編碼
// 解析二維碼
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(image)));
QRCodeReader qrCodeReader = new QRCodeReader();
Result result = qrCodeReader.decode(binaryBitmap, hints);
return result.getText();
}
/**
* 讀取 Base64 編碼中的二維碼內(nèi)容
* @param base64Str Base64 字符串(支持帶/不帶 data:image/png;base64, 前綴)
* @return 二維碼內(nèi)容
* @throws Exception 解析失敗
*/
public static String readQRCodeFromBase64(String base64Str) throws Exception {
if (base64Str == null || base64Str.isEmpty()) {
throw new IllegalArgumentException("Base64 字符串不能為空");
}
// 移除 Base64 前綴(如果有)
String pureBase64 = base64Str.replace("data:image/png;base64,", "");
// 解碼為字節(jié)數(shù)組并轉(zhuǎn)換為輸入流
byte[] bytes = Base64.getDecoder().decode(pureBase64);
try (InputStream inputStream = new java.io.ByteArrayInputStream(bytes)) {
return readQRCode(inputStream);
}
}
}
四、接口實(shí)現(xiàn)(生成 + 讀?。?/h2>
4.1 二維碼生成接口(支持顯示圖片/Base64)
import com.google.zxing.NotFoundException;
import com.google.zxing.WriterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
//注意JDK11以上javax包名需要修改為jakarta
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
/**
* 二維碼生成接口
*/
@RestController
@RequestMapping("/qrcode")
public class QRCodeGenerateController {
/**
* 生成二維碼(直接在瀏覽器顯示圖片)
* @param content 二維碼內(nèi)容(必填)
* @param width 寬度(默認(rèn)300)
* @param height 高度(默認(rèn)300)
*/
@GetMapping("/img")
public void generate( @RequestParam(required = true) String content,
@RequestParam(defaultValue = "300") int width,
@RequestParam(defaultValue = "300") int height, HttpServletResponse response) throws Exception {
try {
response.setContentType("image/png");
BufferedImage image = QRCodeUtil.createQRCode(content, width, height);
com.example.demo.d1.QRCodeUtil.imageToBytes(image);
ImageIO.write(image, "png", response.getOutputStream());
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 生成二維碼并返回 Base64 編碼(前端可直接用于 img 標(biāo)簽)
* @param content 二維碼內(nèi)容(必填)
* @param width 寬度(默認(rèn)300)
* @param height 高度(默認(rèn)300)
* @return 結(jié)構(gòu)化響應(yīng)
*/
@GetMapping("/generate")
public Map<String, Object> generateQRCode(
@RequestParam(required = true) String content,
@RequestParam(defaultValue = "300") int width,
@RequestParam(defaultValue = "300") int height) {
Map<String, Object> result = new HashMap<>();
try {
// 生成二維碼圖片
BufferedImage qrImage = QRCodeUtil.createQRCode(content, width, height);
// 轉(zhuǎn)換為字節(jié)數(shù)組
byte[] qrBytes = QRCodeUtil.imageToBytes(qrImage);
// 轉(zhuǎn)換為 Base64(帶前端可直接使用的前綴)
String base64 = "data:image/png;base64," + QRCodeUtil.imageToBase64(qrBytes);
// 響應(yīng)結(jié)果
result.put("code", 200);
result.put("msg", "二維碼生成成功");
result.put("data", base64);
} catch (WriterException e) {
// 處理生成失敗異常(內(nèi)容為空/尺寸非法等)
result.put("code", 400);
result.put("msg", "二維碼生成失?。? + e.getMessage());
result.put("data", null);
} catch (Exception e) {
// 處理其他未知異常
result.put("code", 500);
result.put("msg", "服務(wù)器異常:" + e.getMessage());
result.put("data", null);
}
return result;
}
}
4.2 二維碼讀取接口(支持文件 / Base64)
import com.google.zxing.NotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
/**
* 二維碼讀取接口
*/
@RestController
@RequestMapping("/qrcode")
public class QRCodeReadController {
/**
* 解析上傳的圖片文件中的二維碼
* @param file 圖片文件(支持 png/jpg/jpeg)
* @return 解析結(jié)果
*/
@PostMapping("/file")
public Map<String, Object> readQRCodeFromFile(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
// 前置校驗(yàn)
if (file.isEmpty()) {
result.put("code", 400);
result.put("msg", "上傳的文件不能為空");
return result;
}
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
result.put("code", 400);
result.put("msg", "請(qǐng)上傳圖片文件(png/jpg/jpeg)");
return result;
}
// 解析二維碼
String content = QRCodeUtil.readQRCode(file.getInputStream());
result.put("code", 200);
result.put("msg", "二維碼解析成功");
result.put("data", content);
} catch (NotFoundException e) {
// 核心異常:未識(shí)別到二維碼
result.put("code", 400);
result.put("msg", "未識(shí)別到二維碼:圖片中無(wú)有效二維碼或二維碼模糊/破損");
result.put("data", null);
} catch (Exception e) {
result.put("code", 500);
result.put("msg", "解析失?。? + e.getMessage());
result.put("data", null);
}
return result;
}
/**
* 解析 Base64 編碼中的二維碼
* @param base64Str Base64 字符串(支持帶/不帶前綴)
* @return 解析結(jié)果
*/
@PostMapping("/base64")
public Map<String, Object> readQRCodeFromBase64(@RequestParam("base64") String base64Str) {
Map<String, Object> result = new HashMap<>();
try {
String content = QRCodeUtil.readQRCodeFromBase64(base64Str);
result.put("code", 200);
result.put("msg", "解析成功");
result.put("data", content);
} catch (NotFoundException e) {
result.put("code", 400);
result.put("msg", "未識(shí)別到二維碼");
} catch (IllegalArgumentException e) {
result.put("code", 400);
result.put("msg", "Base64 格式錯(cuò)誤:" + e.getMessage());
} catch (Exception e) {
result.put("code", 500);
result.put("msg", "解析失?。? + e.getMessage());
}
return result;
}
}
五、常見異常與解決方案
5.1 核心異常列表
| 異常類型 | 異常說(shuō)明 | 解決方案 |
|---|---|---|
| com.google.zxing.NotFoundException | 未識(shí)別到二維碼 | 1. 檢查圖片是否包含有效二維碼 2. 確保二維碼清晰、無(wú)遮擋、分辨率≥200px 3. 解析時(shí)啟用 TRY_HARDER 參數(shù)4. 避免二維碼角度傾斜過(guò)大 |
| com.google.zxing.WriterException | 二維碼生成失敗 | 1. 檢查內(nèi)容是否為空 2. 確保尺寸參數(shù)大于 0 3. 內(nèi)容過(guò)長(zhǎng)時(shí)縮短(或提升容錯(cuò)級(jí)別) |
| java.io.IOException | 圖片讀取 / 轉(zhuǎn)換失敗 | 1. 檢查文件格式是否為 png/jpg 2. 確保輸入流未提前關(guān)閉 3. 驗(yàn)證 Base64 編碼是否完整 |
| IllegalArgumentException | 參數(shù)非法 | 1. 校驗(yàn) Base64 字符串是否為空 2. 檢查文件是否為空 3. 驗(yàn)證尺寸 / 編碼參數(shù)合法性 |
5.2 通用優(yōu)化建議
- 提升生成容錯(cuò)率:使用 ErrorCorrectionLevel.H(最高級(jí)別),即使二維碼被遮擋 30% 仍可識(shí)別;
- 解決中文亂碼:生成 / 解析時(shí)統(tǒng)一設(shè)置 CHARACTER_SET 為 UTF-8;
- 優(yōu)化解析成功率:
- 解析時(shí)啟用 TRY_HARDER 參數(shù);
- 對(duì)圖片進(jìn)行預(yù)處理(灰度化、縮放至合適尺寸);
- 使用 MultiFormatReader 替代 QRCodeReader(支持多碼制解析)
- Base64 兼容性:返回時(shí)拼接 data:image/png;base64, 前綴,前端可直接用于 ;
- 參數(shù)校驗(yàn):所有接口必須校驗(yàn)入?yún)ⅲ▋?nèi)容、文件、尺寸),避免空指針 / 非法參數(shù)異常。
六、測(cè)試
可以直接使用如下測(cè)試頁(yè)面進(jìn)行測(cè)試,如修改請(qǐng)求url請(qǐng)根據(jù)修改內(nèi)容同步修改頁(yè)面測(cè)試地址
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>二維碼識(shí)別&生成工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
padding: 30px;
font-size: 16px;
}
.container {
max-width: 500px;
margin: 0 auto;
}
h2 {
margin-bottom: 20px;
text-align: center;
}
/* 新增:生成和識(shí)別模塊的分隔 */
.module {
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.module:last-child {
border-bottom: none;
}
.upload-box {
border: 2px dashed #ccc;
padding: 40px;
text-align: center;
margin-bottom: 20px;
cursor: pointer;
}
.upload-box:hover {
border-color: #409eff;
}
#preview {
max-width: 100%;
max-height: 300px;
margin: 20px 0;
display: none;
}
/* 新增:生成二維碼的預(yù)覽樣式 */
#qrcode-preview {
max-width: 200px;
max-height: 200px;
margin: 20px auto;
display: none;
}
#result {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 6px;
white-space: pre-wrap;
}
button {
padding: 10px 20px;
background: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
}
/* 新增:生成二維碼的輸入框樣式 */
.generate-input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin: 15px 0;
resize: vertical;
min-height: 80px;
}
</style>
</head>
<body>
<div class="container">
<!-- 新增:二維碼生成模塊 -->
<div class="module">
<h2>二維碼生成</h2>
<textarea class="generate-input" id="qrcode-text" placeholder="請(qǐng)輸入要生成二維碼的文本內(nèi)容(如網(wǎng)址、文字、手機(jī)號(hào)等)"></textarea>
<div style="text-align: center; margin: 10px 0;">
<button id="generateBtn">生成二維碼</button>
</div>
<!-- 生成的二維碼預(yù)覽 -->
<div style="text-align: center;">
<img id="qrcode-preview" alt="生成的二維碼">
</div>
</div>
<!-- 原有:二維碼識(shí)別模塊 -->
<div class="module">
<h2>二維碼圖片識(shí)別</h2>
<div class="upload-box" onclick="document.getElementById('file').click()">
點(diǎn)擊或拖拽上傳二維碼圖片
</div>
<input type="file" id="file" accept="image/*" style="display: none;">
<img id="preview" alt="預(yù)覽圖">
<div style="text-align: center; margin: 10px 0;">
<button id="recognizeBtn" disabled>開始識(shí)別</button>
</div>
<div id="result"></div>
</div>
</div>
<script>
// ========== 原有:二維碼識(shí)別功能 ==========
const fileInput = document.getElementById('file');
const preview = document.getElementById('preview');
const recognizeBtn = document.getElementById('recognizeBtn');
const result = document.getElementById('result');
// 選擇圖片
fileInput.onchange = function (e) {
const file = e.target.files[0];
if (!file) return;
// 預(yù)覽
const url = URL.createObjectURL(file);
preview.src = url;
preview.style.display = 'block';
recognizeBtn.disabled = false;
// 識(shí)別
recognizeBtn.onclick = async function () {
result.innerText = "識(shí)別中...";
recognizeBtn.disabled = true;
const formData = new FormData();
formData.append("file", file);
try {
const res = await fetch("http://localhost:8080/qrcode/read", {
method: "POST",
body: formData
});
const jsonData = await res.json();
result.innerText = "識(shí)別結(jié)果:\n" + jsonData.data;
} catch (err) {
result.innerText = "識(shí)別失?。? + err.message;
} finally {
recognizeBtn.disabled = false;
}
};
}
// ==========二維碼生成功能 ==========
const generateBtn = document.getElementById('generateBtn');
const qrcodeText = document.getElementById('qrcode-text');
const qrcodePreview = document.getElementById('qrcode-preview');
// 生成二維碼按鈕點(diǎn)擊事件
generateBtn.onclick = async function () {
const text = qrcodeText.value.trim();
if (!text) {
alert("請(qǐng)輸入要生成二維碼的內(nèi)容!");
return;
}
generateBtn.disabled = true;
generateBtn.innerText = "生成中...";
qrcodePreview.style.display = 'none';
try {
// 調(diào)用后端生成二維碼接口(需后端配合實(shí)現(xiàn)/qrcode/generate接口)
const res = await fetch("http://localhost:8080/qrcode/generate?content="+text, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
if (!res.ok) throw new Error("生成失敗");
const jsonData = await res.json();
// 將后端返回的二維碼圖片轉(zhuǎn)為URL顯示
// const blob = await res.blob();
// const qrUrl = URL.createObjectURL(blob);
qrcodePreview.src = jsonData.data;
qrcodePreview.style.display = 'block';
} catch (err) {
alert("二維碼生成失敗:" + err.message);
} finally {
generateBtn.disabled = false;
generateBtn.innerText = "生成二維碼";
}
}
</script>
</body>
</html>以上就是SpringBoot集成ZXing實(shí)現(xiàn)二維碼的生成與讀取功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot ZXing二維碼生成與讀取的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot靜態(tài)資源映射規(guī)則的使用小結(jié)
本文主要介紹了springboot靜態(tài)資源映射規(guī)則,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-12-12
Java設(shè)計(jì)模式——工廠設(shè)計(jì)模式詳解
這篇文章主要介紹了Java設(shè)計(jì)模式——工廠設(shè)計(jì)模式詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
JVM線上調(diào)優(yōu)參數(shù)配置實(shí)踐完全指南
JVM調(diào)優(yōu)的基本思路涉及多個(gè)方面,旨在提升Java應(yīng)用的性能、穩(wěn)定性和響應(yīng)速度,下面這篇文章主要介紹了JVM線上調(diào)優(yōu)參數(shù)配置的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-08-08
java.nio.file.InvalidPathException異常解決
本人在ubuntu22.04的操作系統(tǒng)上,運(yùn)行java程序時(shí)創(chuàng)建一個(gè)文件,由于文件名稱中包含了中文,所以導(dǎo)致了程序拋出了java.nio.file.InvalidPathException的異常,感興趣的可以了解一下2025-09-09
java實(shí)現(xiàn)多層級(jí)zip解壓的示例代碼
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)多層級(jí)zip解壓的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-12-12
關(guān)于java自定義線程池的原理與實(shí)現(xiàn)
本文介紹了如何自定義線程池和阻塞隊(duì)列,包括阻塞隊(duì)列的實(shí)現(xiàn)方法,線程池的構(gòu)建以及拒絕策略的應(yīng)用,詳細(xì)闡述了線程池中任務(wù)的提交和執(zhí)行流程,以及如何處理任務(wù)超出隊(duì)列容量的情況2022-04-04
Windows安裝Apache?Kafka保姆級(jí)詳細(xì)教程(圖文+可視化管理工具)
本文詳細(xì)介紹了在Windows系統(tǒng)上安裝和配置Apache?Kafka的步驟,適合初學(xué)者,從環(huán)境準(zhǔn)備到啟動(dòng)服務(wù),再到測(cè)試功能和設(shè)置為Windows服務(wù),每個(gè)步驟都提供了詳細(xì)的圖文教程,最后,還介紹了如何使用KafkaTool進(jìn)行可視化管理,感興趣的朋友跟隨小編一起看看吧2025-11-11

