Java 字符串根據寬度(像素)換行
在一些場景下,我們經常會通過判斷字符串的長度,比如個數來實現換行,但是中文、英文、數字、其實在展示的時候同樣長度的字符串,其實它的寬度是不一樣的,這也是們我通俗意義上說的寬度(像素)
根據像素寬度進行換行
需求:
/** * 10、自己做圖片 ,根據文本寬度進行換行 */ @Test public void creatMyImage(){ //整體圖合成 BufferedImage bufferedImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); //設置圖片的背景色 Graphics2D main = bufferedImage.createGraphics(); main.fillRect(0, 0, 500, 500); String text = "111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111"; Graphics2D textG = bufferedImage.createGraphics() ; textG.setColor(new Color(37,37,37)); Font hualaoContentFont = new Font("PingFang SC", Font.PLAIN, 20); textG.setFont(hualaoContentFont); textG.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); drawString(textG,text,30,100,4,10,50,true,false); //存儲到本地 String saveFilePath = "/Users/healerjean/Desktop/new.png"; saveImageToLocalDir(bufferedImage, saveFilePath); } /** * * @param g * @param text 文本 * @param lineHeight 行高(注意字體大小的控制哦) * @param maxWidth 行寬 * @param maxLine 最大行數 * @param left 左邊距 //整段文字的左邊距 * @param top 上邊距 //整頓文字的上邊距 * @param trim 是否修剪文本(1、去除首尾空格,2、將多個換行符替換為一個) * @param lineIndent 是否首行縮進 */ public static void drawString(Graphics2D g, String text, float lineHeight, float maxWidth, int maxLine, float left, float top, boolean trim, boolean lineIndent) { if (text == null || text.length() == 0) return; if(trim) { text = text.replaceAll("\\n+", "\n").trim(); } if(lineIndent) { text = " " + text.replaceAll("\\n", "\n "); } drawString(g, text, lineHeight, maxWidth, maxLine, left, top); } /** * * @param g * @param text 文本 * @param lineHeight 行高 * @param maxWidth 行寬 * @param maxLine 最大行數 * @param left 左邊距 * @param top 上邊距 */ private static void drawString(Graphics2D g, String text, float lineHeight, float maxWidth, int maxLine, float left, float top) { if (text == null || text.length() == 0) return; FontMetrics fm = g.getFontMetrics(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); sb.append(c); int stringWidth = fm.stringWidth(sb.toString()); if (c == '\n' || stringWidth > maxWidth) { if(c == '\n') { i += 1; } if (maxLine > 1) { g.drawString(text.substring(0, i), left, top); drawString(g, text.substring(i), lineHeight, maxWidth, maxLine - 1, left, top + lineHeight); } else { g.drawString(text.substring(0, i - 1) + "…", left, top); } return; } } g.drawString(text, left, top); }
Java字符串根據寬度(像素)進行換行及Pdf合并
實際開發中,我們經常會通過判斷字符串的長度,比如個數來實現換行,但是中文、英文、數字在展示的時候同樣長度的字符串,其實它的寬度是不一樣的,這也是們我通俗意義上說的寬度(像素)。
下面結合jasper套打打印業務來說明。
1、工具類最終版前奏
package main; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; public class SubstringStr { /** * 1、自己做圖片,根據文本寬度進行換行 */ public void creatMyImage(String text) { // 整體圖合成 BufferedImage bufferedImage = new BufferedImage(595, 842, BufferedImage.TYPE_INT_RGB); // 設置圖片的背景色 Graphics2D main = bufferedImage.createGraphics(); main.fillRect(0, 0, 1500, 2600); Graphics2D graphics2d = bufferedImage.createGraphics(); graphics2d.setColor(new Color(37, 37, 37)); Font hualaoContentFont = new Font("宋體", Font.PLAIN, 20); graphics2d.setFont(hualaoContentFont); graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); drawString1(graphics2d, text, 30, 500, 28, 71, 20, false, false, 0); // 存儲到本地 String saveFilePath = "D:\\new.png"; saveImageToLocalDir(bufferedImage, saveFilePath); } private void saveImageToLocalDir(BufferedImage img, String saveFilePath) { try { //bufferedimage 轉換成 inputstream ByteArrayOutputStream bs = new ByteArrayOutputStream(); ImageOutputStream imOut = ImageIO.createImageOutputStream(bs); ImageIO.write(img, "jpg", imOut); InputStream inputStream = new ByteArrayInputStream(bs.toByteArray()); long length = imOut.length(); OutputStream outStream = new FileOutputStream(saveFilePath); //輸出流 byte[] bytes = new byte[1024]; long count = 0; while(count < length){ int len = inputStream.read(bytes, 0, 1024); count +=len; outStream.write(bytes, 0, len); } outStream.flush(); inputStream.close(); outStream.close(); } catch (Exception e) { e.printStackTrace(); } } /** * @param graphics2d * @param text 文本 * @param lineHeight 行高(注意字體大小的控制哦) * @param maxWidth 行寬 * @param maxLine 最大行數 * @param left 左邊距 //整段文字的左邊距 * @param top 上邊距 //整段文字的上邊距 * @param trim 是否修剪文本(1、去除首尾空格,2、將多個換行符替換為一個) * @param lineIndent 是否首行縮進 */ public static void drawString1(Graphics2D graphics2d, String text, float lineHeight, float maxWidth, int maxLine, float left, float top, boolean trim, boolean lineIndent, int resultLine) { if (text == null || text.length() == 0) return; if (trim) { text = text.replaceAll("\\n+", "\n").trim(); System.err.println("執行了。。。。"); } if (lineIndent) { text = " " + text.replaceAll("\\n", "\n "); System.err.println("執行了====="); } drawString2(graphics2d, text, lineHeight, maxWidth, maxLine, left, top, resultLine); } /** * * @param graphics2d * @param text 文本 * @param lineHeight 行高 * @param maxWidth 行寬 * @param maxLine 最大行數 * @param left 左邊距 * @param top 上邊距 */ private static void drawString2(Graphics2D graphics2d, String text, float lineHeight, float maxWidth, int maxLine, float left, float top, int resultLine) { if (text == null || text.length() == 0) { return; } FontMetrics fm = graphics2d.getFontMetrics(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char strChar = text.charAt(i); sb.append(strChar); int stringWidth = fm.stringWidth(sb.toString()); if (strChar == '\n' || stringWidth > maxWidth) { if (strChar == '\n') { i += 1; System.out.println("\n字符串內容為:" + text.substring(0, i)); } if (maxLine > 1) { resultLine = resultLine + 1; System.err.println("\nif中字符串內容為:" + text.substring(0, i)); graphics2d.drawString(text.substring(0, i), left, top); drawString2(graphics2d, text.substring(i), lineHeight, maxWidth, maxLine - 1, left, top + lineHeight, resultLine); } else { System.err.println("\nelse中字符串內容為:" + text.substring(0, i)); graphics2d.drawString(text.substring(0, i - 1) + "…", left, top); } return; } } System.err.println("最后字符串內容為:" + text + "===行數為:" + resultLine); graphics2d.drawString(text, left, top); } public static void main(String[] args) { String text = "111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111111122223所以比傳統紙巾更環保3334441比傳統紙巾" + System.getProperty("line.separator") + "更環66666666"+ System.getProperty("line.separator") + "所以比傳統紙巾更環保3334441比傳統紙巾更環。。。。。。。" + "111111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111"; SubstringStr test = new SubstringStr(); test.creatMyImage(text); System.out.println("輸入圖片完成:D:/new.png"); } }
2、工具類最終版
package com.sinosoft.emr.util; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SubstrUtil { private static int index = 0; /** * 1、自己做圖片 ,根據文本寬度進行換行 */ public static Map<String, Object> creatMyImage(String text, Font font, int appointRow, Map<String, Object> map, int rows) { index = 0; // 整體圖合成 BufferedImage bufferedImage = new BufferedImage(595, 842, BufferedImage.TYPE_INT_RGB); Graphics2D graphics2d = bufferedImage.createGraphics(); graphics2d.setColor(new Color(37, 37, 37)); graphics2d.setFont(font); graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); map = myDrawString(graphics2d, text, 30, 500, appointRow, 71, 20, map, rows); return map; } /** * 2、根據寬度截取字符串 * @param graphics2d * @param text 文本 * @param lineHeight 行高 * @param maxWidth 行寬 * @param maxLine 最大行數 * @param left 左邊距 * @param top 上邊距 */ private static Map<String, Object> myDrawString(Graphics2D graphics2d, String text, int lineHeight, int maxWidth, int maxLine, int left, int top, Map<String, Object> map, int rows) { if (text == null || text.length() == 0) { return map; } FontMetrics fm = graphics2d.getFontMetrics(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char strChar = text.charAt(i); sb.append(strChar); int stringWidth = fm.stringWidth(sb.toString()); int strLength = text.substring(0, i).length(); if (strLength >= 53 || strChar == '\n' || stringWidth >= maxWidth) { if (strChar == '\n') { i += 1; // System.out.println("字符串內容為:" + text.substring(0, i)); } if (maxLine > 1) { rows = rows + 1; // System.err.println("if中字符串內容為:" + text.substring(0, i)); char value = ' ', value1 = ' '; try { value = text.substring(i).charAt(0); value1 = text.substring(i - 1).charAt(0); } catch (Exception e) { /*e.printStackTrace();*/ System.err.println("----" + e.getMessage()); } if (isChinesePunctuation(value) && checkCharCN(value1)) { map.put("row" + rows, text.substring(0, i - 1)); myDrawString(graphics2d, text.substring(i - 1), lineHeight, maxWidth, maxLine - 1, left, top + lineHeight, map, rows); } else { map.put("row" + rows, text.substring(0, i)); myDrawString(graphics2d, text.substring(i), lineHeight, maxWidth, maxLine - 1, left, top + lineHeight, map, rows); } index = index + i; map.put("index", index); // System.err.println("---下標" + index); } else { rows = rows + 1; map.put("row" + rows, text.substring(0, i)); // System.err.println("else中字符串內容為:" + text.substring(0, i)); index = index + i; map.put("index", index); } return map; } } return map; } // 2.1 判斷字符是否是中文 public static boolean checkCharCN(char c) { String s = String.valueOf(c); String regex = "[\u4e00-\u9fa5]"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(s); return m.matches(); } // 2.2 判斷字符是否是中文標點符號 public static boolean isChinesePunctuation(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS || ub == Character.UnicodeBlock.VERTICAL_FORMS) { return true; } else { return false; } } /** * 3、針對jasper中寬度較短的文本 * @author wanglong * @date 2020年11月10日下午7:12:14 */ public static Map<String, Object> creatShortImage(String text, Font font, int appointRow, Map<String, Object> map, int rows) { index = 0; // 整體圖合成 BufferedImage bufferedImage = new BufferedImage(595, 842, BufferedImage.TYPE_INT_RGB); // 設置圖片的背景色 Graphics2D main = bufferedImage.createGraphics(); main.fillRect(0, 0, 595, 842); Graphics2D graphics2d = bufferedImage.createGraphics(); graphics2d.setColor(new Color(37, 37, 37)); graphics2d.setFont(font); graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); map = myDrawShortString(graphics2d, text, 30, 230, appointRow, 71, 20, map, rows); return map; } // 3.1 private static Map<String, Object> myDrawShortString(Graphics2D graphics2d, String text, int lineHeight, int maxWidth, int maxLine, int left, int top, Map<String, Object> map, int rows) { if (text == null || text.length() == 0) { return map; } FontMetrics fm = graphics2d.getFontMetrics(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char strChar = text.charAt(i); sb.append(strChar); int stringWidth = fm.stringWidth(sb.toString()); if (strChar == '\n' || stringWidth > maxWidth) { if (strChar == '\n') { i += 1; //System.out.println("字符串內容為:" + text.substring(0, i)); } if (maxLine > 1) { rows = rows + 1; //System.err.println("if中字符串內容為:" + text.substring(0, i)); map.put("row" + rows, text.substring(0, i)); map.put("row_len" + rows, stringWidth); myDrawShortString(graphics2d, text.substring(i), lineHeight, maxWidth, maxLine - 1, left, top + lineHeight, map, rows); } else { rows = rows + 1; map.put("row" + rows, text.substring(0, i)); map.put("row_len" + rows, stringWidth); //System.err.println("else中字符串內容為:" + text.substring(0, i)); } return map; } } return map; } public static void main(String[] args) { Map<String, Object> map = new LinkedHashMap<>(); int rows = 0; int appointRow = 4; Font font = new Font("宋體", Font.PLAIN, 12); String text = "111122223所以比傳統紙巾更環保3334441比傳統紙巾更環11111111111111122223所以比傳統紙巾更環保3334441測試數據" + System.getProperty("line.separator") + "在仿蘋果官網垃圾桶時, 設計出的UI使用PingFang SC 字體"+ System.getProperty("line.separator") + "天津市防控指揮部消息稱,經排查,濱海新區中新天津紙巾更環保3334441比傳統紙巾更環666所以比傳統紙巾更環保3334441比傳統紙巾更環8888"; int length = text.length(); map = creatMyImage(text, font, appointRow, map, rows); if (length > index) { System.out.println("還剩余文本:" + text.substring(index)); } System.out.println("\n<<--->>>rows=" + rows + ", map="+map); } }
說明:此工具類我們經理進行了改造,生成的文本內容符合中國人的書寫習慣:行首不能是標點符號。如果行首是標點符號,上一行的最后一個文字會挪至本行。
3、項目中具體使用
3.1 controller層
/** * 4、打印 病程記錄 * @author wanglong * @date 2020年3月4日 下午5:22:57 */ @RequestMapping(value = "printEmrCourse", method = RequestMethod.GET, produces = MediaType.APPLICATION_PDF_VALUE) public void printEmrCourse(@RequestParam(value = "id", required = true) String id, @RequestParam(value = "courseType", required = true) String courseType, @RequestParam(value = "token", required = true) String token, HttpServletResponse response) { List<String> filePathList = new ArrayList<String>(); Map<String, Map<String, Object>> map = iEmrCourseSelectBusi.selectEmrCourseData(id, courseType, token); try { if (map.size() > 0) { for (int i = 0, len = map.size(); i < len; i++) { if (i % 2 == 0) { String path1 = iEmrCourseSelectBusi.getCoursePdf(map.get("map" + i), "emrCourseLeft.jasper"); if (!"".equals(path1)) { filePathList.add(path1); } } else { String path2 = iEmrCourseSelectBusi.getCoursePdf(map.get("map" + i), "emrCourseRight.jasper"); if (!"".equals(path2)) { filePathList.add(path2); } } } iEmrCourseSelectBusi.printEmrCoursePdf(filePathList, response); } } catch (Exception e) { e.printStackTrace(); } }
3.2 serviceImpl層
@Service("iEmrCourseSelectBusi") public class EmrCourseBusiSelectImpl extends EmrCourseSelectServiceImpl implements IEmrCourseSelectBusi{ private static final long serialVersionUID = 1L; private Font font = new Font("宋體", Font.PLAIN, 12); // 讀取配置文件里的jasper路徑 @Value("${jasperPath}") private String jasperPath; /** * 4、打印 病程記錄 * @author wanglong * @date 2020年11月9日下午3:49:23 */ @Override public Map<String, Map<String, Object>> selectEmrCourseData(String id, String courseType, String token) { Map<String, Map<String, Object>> resultMap = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>(); int rows = 0, appointRow = 28; StringBuffer bufferStr = new StringBuffer(); String lineBreak = System.getProperty("line.separator"); // 換行符 try { EmrCourse record = emrCourseMapper.selectByPrimaryKey(id); if (record != null) { map.put("patientName", record.getPatientName()); map.put("fkHosRegisterHosNo", record.getFkHosRegisterHosNo()); String recordTime = DateUtil.dateToString(record.getCourseRecordTime(), "yyyy-MM-dd HH:mm"); String doctorName = record.getDoctorRealName(); if ("1".equals(courseType)) { bufferStr.append(recordTime + lineBreak); String tempStr = record.getGeneralRecords() == null ? "" : record.getGeneralRecords(); String blankStr = ""; if (!"".equals(tempStr)) { blankStr = StringDealUtil.getSubString(blankStr, tempStr); } String generalRecords = record.getGeneralRecords() == null ? "" : (blankStr + lineBreak); bufferStr.append(generalRecords); } String updateName = record.getUpdateOprName(); String opraterName = updateName == null ? record.getAddOprName() : updateName; bufferStr.append("\t\t\t\t\t\t\t\t\t簽 名:" + opraterName + lineBreak); String text = bufferStr.toString(); int length = text.length(); map = SubstrUtil.creatMyImage(text, font, appointRow, map, rows); map.put("pageNum", "1"); resultMap.put("map0", map); int index = (int) map.get("index"); if (length > index) { String _text = StringDealUtil.getSurplusText(text, index); resultMap = this.getOtherMap(_text, appointRow, resultMap, 1); } } } catch (Exception e) { e.printStackTrace(); } return resultMap; } // 4.1 截取文本獲取其余map private Map<String, Map<String, Object>> getOtherMap(String text, int appointRow, Map<String, Map<String, Object>> resultMap, int i) { int length = text.length(); Map<String, Object> map = new LinkedHashMap<>(); map = SubstrUtil.creatMyImage(text, font, appointRow, map, 0); map.put("pageNum", i + 1 + ""); resultMap.put("map"+ i, map); int index = (int) map.get("index"); if (length > index) { String _text = StringDealUtil.getSurplusText(text, index); resultMap = this.getOtherMap(_text, appointRow, resultMap, i+1); } return resultMap; } /** * 5、生成pdf * @author wanglong * @date 2020年11月9日下午1:52:42 */ @Override public String getCoursePdf(Map<String, Object> map, String jasperName) { String fileInfo = ""; Instant timestamp = Instant.now(); try { JRDataSource jrDataSource = new JRBeanCollectionDataSource(null); File reportFile = new File(jasperPath + jasperName); String exportFileName = timestamp.toEpochMilli() + ".pdf"; fileInfo = jasperPath + exportFileName; //調用工具類 JasperHelper.exportPdf2File(jasperPath + exportFileName, reportFile, map, jrDataSource); } catch (Exception e) { e.printStackTrace(); } return fileInfo; } /** * 5.1 合成pdf進行打印 * @author wanglong * @date 2020年11月9日下午1:52:58 */ @Override public void printEmrCoursePdf(List<String> filePathList, HttpServletResponse response) { List<File> files = new ArrayList<File>(); try { if (filePathList != null && filePathList.size() > 0) { for (int i = 0, length = filePathList.size(); i < length; i++) { String pathName = filePathList.get(i); files.add(new File(pathName)); } String newFileName = Instant.now().toEpochMilli() + "病程記錄mul2one.pdf"; String targetPath = jasperPath + newFileName; File newFile = PdfMergeUtil.mulFile2One(files, targetPath); if (newFile.exists()) { // 先將單個的pdf文件刪除 for (File file : files) { if (file.exists() && file.isFile()) { file.delete(); } } newFile.getName(); PdfUtil.downloadFile(response, targetPath, newFileName); } } } catch (Exception e) { e.printStackTrace(); } } }
4、工具類補充
package utils; import net.sf.jasperreports.engine.*; import net.sf.jasperreports.engine.util.JRLoader; import java.io.*; import java.util.Map; public class JasperHelper { /** * 1、直接導出pdf文件 * @author wanglong * @date 2020年10月31日下午9:10:41 */ public static void exportPdf2File(String defaultFilename, File is, Map<String, Object> parameters, JRDataSource conn) { try { JasperReport jasperReport = (JasperReport) JRLoader.loadObject(is); JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn); exportPdfToFile(jasperPrint, defaultFilename); } catch (Exception e) { e.printStackTrace(); } } // 1.1 導出Pdf private static void exportPdfToFile(JasperPrint jasperPrint, String defaultFilename) throws IOException, JRException { FileOutputStream ouputStream = new FileOutputStream(new File(defaultFilename)); JasperExportManager.exportReportToPdfStream(jasperPrint, ouputStream); ouputStream.flush(); ouputStream.close(); } }
package utils; import java.io.File; import java.io.IOException; import java.util.List; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.multipdf.PDFMergerUtility; /** * @name: PdfMergeUtil * @Description: pdf合并拼接工具類 * @author wanglong * @date 2020年2月25日下午4:33:13 * @version 1.0 */ public class PdfMergeUtil { /** * @Description: pdf合并拼接 * @param files 文件列表 * @param targetPath 合并到 * @throws IOException */ public static File mulFile2One(List<File> files,String targetPath) throws IOException{ // pdf合并工具類 PDFMergerUtility mergePdf = new PDFMergerUtility(); for (File f : files) { if(f.exists() && f.isFile()){ // 循環添加要合并的pdf mergePdf.addSource(f); } } // 設置合并生成pdf文件名稱 mergePdf.setDestinationFileName(targetPath); // 合并pdf mergePdf.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); return new File(targetPath); } }
package utils; import javax.servlet.http.HttpServletResponse; import java.io.*; /** * 導出PDF */ public class PdfUtil { // 下載 public static void downloadFile(HttpServletResponse response, String filePath, String fileName) { if (fileName != null) { response.reset(); response.setContentType("application/pdf"); // 設置文件路徑 File file = new File(filePath); if (file.exists()) { byte[] buffer = new byte[1024]; FileInputStream fis = null; BufferedInputStream bis = null; try { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); OutputStream os = response.getOutputStream(); int i = bis.read(buffer); while (i != -1) { os.write(buffer, 0, i); i = bis.read(buffer); } System.out.println("download success!"); } catch (Exception e) { e.printStackTrace(); } finally { if (bis != null) { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } file.delete(); } } } }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/u012954706/article/details/81511216