• Qt之显示PDF文件


    之前使用过mupdf库,能够成功显示pdf,但是我用着有BUG,不太理解它的代码,搞了好久都不行。后面又试了其他库,如pdfium、popler、下载了很多例程,都跑不起来!后面偶然得知xpdf库,看起来应该容易编译,因此这里主要是针对xpdf库的编译。

    目前状态:终于成功了!2023-10-06(直接看章节3即可)

    写作不易,觉得好就请疯狂点赞!!!!!!

    写作不易,觉得好就请疯狂点赞!!!!!!

    写作不易,觉得好就请疯狂点赞!!!!!!

    20231006_222307

    一、官网

    Download Xpdf and XpdfReader

    官网有大致的介绍,以及最新版本的xpdf源码的下载链接。但是没有具体讲怎么移植xpdf。

    二、windows下的xpdf的编译

    2.1、准备windows下的编译环境

    我的编译环境

    win10操作系统

    vs2015社区版

    qt版本:qt5.9.5

    在官网下载工具链。如下图红色框中所示,点击后即可下载。这个压缩包是linux下的格式,解压需要费点功夫。

    下载并解压后放在xpdf源码目录下,如下图所示。

    2.2、编译freetype

    在如下图的路径,双击使用VS打开freetype.sln(我的VS是vs2015社区版),选择Release和win32后,点击运行进行编译,编程出dll文件。(由于dll不能直接运行,因此会报如下的错误,忽略即可)。

    编译完成在 如下的路径下即可看到dll和lib文件。

    工程路径:\freetype-2.12.0\builds\windows\vc2010

    编译结构路径:\freetype-2.12.0\objs

    在xpdf源码目录新建文件夹freetype,将刚才生成的lib和dll文件拷贝到该文件夹中,就完成了freetype的编译。

    2.3、编译lcms

    在如下图路径打开lcms的工程,右键点击lcms2_DLL将其设为启动项目,选择Release和win32后,点击运行进行编译,编程出dll文件。

    工程路径:\lcms2-2.12\Projects\VC2015

    编译结构路径:\lcms2-2.12\bin

    在xpdf源码目录新建文件夹lcms,将刚才生成的lib和dll文件拷贝到该文件夹中,就完成了lcms的编译。

    2.4、编译zlib

    zlib-1.2.12版本有点bug,需要先处理一下。

    1、拷贝zlib-1.2.11(去网上下载一个)中的masmx76到contrib目录下;

    2、修改代码

     (1)修改函数uLong ZEXPORT crc32_combine(crc1, crc2, len2)
        z_off_t len2改为z_off64_t len2;

    (2)修改函数:uLong ZEXPORT crc32_combine_gen(len2)
        z_off_t len2改为z_off64_t len2

    (3)修改函数:uLong crc32_combine_op(crc1, crc2, op)

    改为uLong ZEXPORT  crc32_combine_op(crc1, crc2, op)

    3、右键zlibvc,设置 SAFESEH 映像是不安全的异常处理程序关闭

    完成 后开始正式的编译。

    在如下图路径打开zlib的工程,选择Release和win32后,点击运行进行编译,编程出dll文件。

    工程路径:\zlib-1.2.12\contrib\vstudio\vc14

    编译结构路径:\zlib-1.2.12\contrib\vstudio\vc14\x86\ZlibDllRelease

    在xpdf源码目录新建文件夹zlib,将刚才生成的lib和dll文件拷贝到该文件夹中,就完成了zlib的编译。

    如果出现异常情况:

    问题1.没有bld_ml32.bat

    编译器报错详情:

    问题解决:查看contrib目录下,确实没有masmx86,拷贝zlib-1.2.11中的masmx86和masmx64到contrib目录下,问题解决。

    问题2.实参的字节长度不同于以前的调用或引用

    编译器报错详情如下:

    问题解决:在crc32.c中修改如下内容

    1.修改函数uLong ZEXPORT crc32_combine(crc1, crc2, len2)
        uLong crc1;
        uLong crc2;
        z_off_t len2;为z_off64_t len2;

    2.修改函数:1.uLong ZEXPORT crc32_combine_gen(len2)
        z_off_t len2;为z_off64_t len2

    3.修改:

    uLong crc32_combine_op(crc1, crc2, op)为

    uLong ZEXPORT  crc32_combine_op(crc1, crc2, op)

    问题3:模块对于 SAFESEH 映像是不安全的。

    使用Release编译,忽略其他编译警告,编译通过。
     

    2.5、编译libpng

    libpng的编译以来zlib,因此需要将zlib的源码放在libpng源码的同级目录(前面已经放过了)

    打开zlib.props文件,修改zlib的路径和版本(放在同级目录就不需要修改路径,修改版本就行)

    在如下图路径打开libpng的工程,右键点击libpng将其设为启动项目,选择Release和win32后,点击运行进行编译,编程出dll文件。

    工程路径:\libpng-1.6.35\projects\vstudio

    编译结构路径:\libpng-1.6.35\projects\vstudio\Release

    在xpdf源码目录新建文件夹libpng,将刚才生成的lib和dll文件拷贝到该文件夹中,就完成了libpng的编译。

    2.6、编译xpdf

    2.6.1 设置编译参数

    (1) 在xpdf源码目录下新建bulid文件夹,

    (2) 打开vs2015的命令提示符工具(看你的vs版本),并进入build路径(ctrl+c后在命令框右键即可粘贴)

    输入如下命令,设置各个库的包含路径和动态库的路径

    cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DFREETYPE_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/freetype/freetype.dll" -DFREETYPE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/freetype-2.12.0" -DPNG_PNG_INCLUDE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/libpng-1.6.35" -DPNG_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/libpng/libpng16.dll" -DZLIB_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/zlib/zlibwapi.dll" -DZLIB_INCLUDE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/zlib" ..

    我的结果如下:

    1. E:\qt5.9.5\userfile\20231005\xpdf-4.04\build>cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DFREETYPE_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/freetype/freetype.dll" -DFREETYPE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/freetype-2.12.0" -DPNG_PNG_INCLUDE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/libpng-1.6.35" -DPNG_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/libpng/libpng16.dll" -DZLIB_LIBRARY="E:/qt5.9.5/userfile/20231005/xpdf-4.04/zlib/zlibwapi.dll" -DZLIB_INCLUDE_DIR="E:/qt5.9.5/userfile/20231005/xpdf-4.04/zlib" ..
    2. -- The C compiler identification is MSVC 19.0.24215.1
    3. -- The CXX compiler identification is MSVC 19.0.24215.1
    4. -- Check for working C compiler: F:/vs2015/VC/bin/cl.exe
    5. -- Check for working C compiler: F:/vs2015/VC/bin/cl.exe -- works
    6. -- Detecting C compiler ABI info
    7. -- Detecting C compiler ABI info - done
    8. -- Detecting C compile features
    9. -- Detecting C compile features - done
    10. -- Check for working CXX compiler: F:/vs2015/VC/bin/cl.exe
    11. -- Check for working CXX compiler: F:/vs2015/VC/bin/cl.exe -- works
    12. -- Detecting CXX compiler ABI info
    13. -- Detecting CXX compiler ABI info - done
    14. -- Detecting CXX compile features
    15. -- Detecting CXX compile features - done
    16. -- Looking for mkstemp
    17. -- Looking for mkstemp - not found
    18. -- Looking for mkstemps
    19. -- Looking for mkstemps - not found
    20. -- Looking for popen
    21. -- Looking for popen - not found
    22. -- Performing Test HAVE_STD_SORT
    23. -- Performing Test HAVE_STD_SORT - Success
    24. -- Looking for fseeko
    25. -- Looking for fseeko - not found
    26. -- Looking for fseek64
    27. -- Looking for fseek64 - not found
    28. -- Looking for _fseeki64
    29. -- Looking for _fseeki64 - found
    30. -- Found FreeType (old-style includes): E:/qt5.9.5/userfile/20231005/xpdf-4.04/freetype/freetype.dll
    31. -- Found ZLIB: E:/qt5.9.5/userfile/20231005/xpdf-4.04/zlib/zlibwapi.dll
    32. -- Found PNG: E:/qt5.9.5/userfile/20231005/xpdf-4.04/libpng/libpng16.dll (found version "1.6.35")
    33. -- Qt5 found
    34. -- Looking for pthread.h
    35. -- Looking for pthread.h - not found
    36. -- Found Threads: TRUE
    37. -- Configuring done
    38. -- Generating done
    39. -- Build files have been written to: E:/qt5.9.5/userfile/20231005/xpdf-4.04/build

    2.6.2 开始编译

    在命令框输入:

    nmake

    我失败在如下这一步(2023-10-05)

    三、xpdf库在windows上新建QT工程

    如需源码,请上闲鱼,搜索:科技代码小卖部

    3.1 新建一个新工程

    在qtcreator下新建工程,如下图XPDFDemo20231006New4_04

    3.2 拷贝头文件

    (1)拷贝freetype库的头文件到新工程目录下

    在freetype库的源码下,拷贝整个include文件夹到新工程目录。

    (2)拷贝freetype库的库文件(lib和dll)到新工程目录下

    在新工程目录下新建lib文件夹,拷贝编译后的freetype库文件到该路径下(编译方法见前面的2.2小节)

    (3)拷贝xpdf的头文件到新工程目录下

    将xpdf库源码中的相关头文件拷贝到新工程目录下,新建一个aconf.h文件,将下面的代码拷贝到里面(源码里只有aconf.h.in,需要编译才能生成aconf.h,如想自行编译,见2.6小节,在build文件夹下编译后会生成aconf.h文件)

    1. /*
    2. * aconf.h
    3. *
    4. * This file is modified by cmake.
    5. *
    6. * Copyright 2002-2015 Glyph & Cog, LLC
    7. */
    8. #ifndef ACONF_H
    9. #define ACONF_H
    10. #include
    11. /*
    12. * Use A4 paper size instead of Letter for PostScript output.
    13. */
    14. #define A4_PAPER 1
    15. /*
    16. * Do not allow text selection.
    17. */
    18. #define NO_TEXT_SELECT 0
    19. /*
    20. * Include support for OPI comments.
    21. */
    22. #define OPI_SUPPORT 1
    23. /*
    24. * Enable multithreading support.
    25. */
    26. #define MULTITHREADED 0
    27. /*
    28. * Enable C++ exceptions.
    29. */
    30. #define USE_EXCEPTIONS 1
    31. /*
    32. * Use fixed point (instead of floating point) arithmetic.
    33. */
    34. #define USE_FIXEDPOINT 1
    35. /*
    36. * Enable support for CMYK output.
    37. */
    38. #define SPLASH_CMYK 1
    39. /*
    40. * Enable support for DeviceN output.
    41. */
    42. #define SPLASH_DEVICEN 1
    43. /*
    44. * Enable support for highlighted regions.
    45. */
    46. #define HIGHLIGHTED_REGIONS 1
    47. /*
    48. * Full path for the system-wide xpdfrc file.
    49. */
    50. //@SYSTEM_XPDFRC_DEFINE@
    51. /*
    52. * Directory to use for the ${DATADIR} variable in the xpdfrc config
    53. * file.
    54. */
    55. //@XPDFRC_DATADIR_DEFINE@
    56. /*
    57. * Various include files and functions.
    58. */
    59. #define HAVE_MKSTEMP 1
    60. #define HAVE_MKSTEMPS 1
    61. #define HAVE_POPEN 1
    62. #define HAVE_STD_SORT 1
    63. #define HAVE_FSEEKO 0
    64. #define HAVE_FSEEK64 0
    65. #define HAVE_FSEEKI64 1
    66. #define _FILE_OFFSET_BITS 64
    67. #define _LARGE_FILES 1
    68. #define _LARGEFILE_SOURCE 1
    69. /*
    70. * This is defined if using FreeType 2.
    71. */
    72. #define HAVE_FREETYPE_H 1
    73. /*
    74. * This is defined if using D-Type 4.
    75. */
    76. #define HAVE_DTYPE4_H 0
    77. /*
    78. * This is defined if using libpaper.
    79. */
    80. #define HAVE_PAPER_H 0
    81. /*
    82. * This is defined if using libfontconfig.
    83. */
    84. #define HAVE_FONTCONFIG 0
    85. /*
    86. * Defined if the Splash library is avaiable.
    87. */
    88. #define HAVE_SPLASH 0
    89. /*
    90. * Defined if using lcms2.
    91. */
    92. #define HAVE_LCMS 0
    93. /*
    94. * Defined for evaluation mode.
    95. */
    96. #define EVAL_MODE 1
    97. /*
    98. * Defined when building the closed source XpdfReader binary.
    99. */
    100. #define BUILDING_XPDFREADER 0
    101. #endif

    (4)拷贝xpdf例程到新工程中

    将xpdf库源码中的xpdf-qt文件夹拷贝到新工程目录下。

    3.3 配置pro文件

    删除core和gui模块,添加QT库network,printsupport和axcontainer。如下图所示     

      添加头文件和库文件的检索路径

    3.4 添加已有文件到新工程

    在Header上右键,选择Add Existing Directory,勾选所有需要导入的文件,批量完成导入。

    在默认的基础上,把2个rc资源文件也勾选上,如下图所示。

    3.5 删除多余文件

    由于例程没有用qt界面编辑器,且自带main函数,因此需要将原新建工程的以下文件删除:

    main.cpp

    mainwindow.h

    mainwindow.cpp

    mainwindow.ui

    删除后的工程结构如下图所示。

    3.6 修改源码

    源码还有点错误,需要修改。先点击运行进行编译,会报如下的错误。

    (1)修改XpdfViewer.cc

    打开XpdfViewer.cc文件,添加头文件

    #include 

    (2)修改XpdfApp.cc

    由于字符集的问题,需要将XpdfApp.cc中的void XpdfApp::readPagesFile() 函数整个换为下方的代码,进行字符转换。

    1. char * wchar2char(const wchar_t* szUnicodeString)
    2. {
    3. UINT nCodePage = 936; //GB2312
    4. int nLength=WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,NULL,0,NULL,NULL);
    5. char* pBuffer=new char[nLength+1];
    6. WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,pBuffer,nLength,NULL,NULL);
    7. pBuffer[nLength]=0;
    8. return pBuffer;
    9. }
    10. wchar_t * char2wchar(const char* cchar)
    11. {
    12. wchar_t *m_wchar;
    13. int len = MultiByteToWideChar( CP_ACP ,0,cchar ,strlen( cchar), NULL,0);
    14. m_wchar= new wchar_t[len+1];
    15. MultiByteToWideChar( CP_ACP ,0,cchar,strlen( cchar),m_wchar,len);
    16. m_wchar[len]= '\0' ;
    17. return m_wchar;
    18. }
    19. void XpdfApp::readPagesFile() {
    20. // construct the file name (first time only)
    21. if (savedPagesFileName.isEmpty()) {
    22. #ifdef _WIN32
    23. wchar_t wpath[MAX_PATH];
    24. // char path[MAX_PATH];
    25. if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL,
    26. SHGFP_TYPE_CURRENT, wpath) != S_OK) {
    27. return;
    28. }
    29. //char path[MAX_PATH];
    30. char* path=wchar2char(wpath);
    31. savedPagesFileName = QString::fromLocal8Bit(path);
    32. savedPagesFileName.append("/xpdf");
    33. CreateDirectory(char2wchar(savedPagesFileName.toLocal8Bit().constData()), NULL);
    34. savedPagesFileName.append("/xpdf.pages");
    35. // char path[MAX_PATH];
    36. // if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL,
    37. // SHGFP_TYPE_CURRENT, path) != S_OK) {
    38. // return;
    39. // }
    40. // savedPagesFileName = QString::fromLocal8Bit(path);
    41. // savedPagesFileName.append("/xpdf");
    42. // CreateDirectory(savedPagesFileName.toLocal8Bit().constData(), NULL);
    43. // savedPagesFileName.append("/xpdf.pages");
    44. #else
    45. GString *path = getHomeDir();
    46. savedPagesFileName = QString::fromUtf8(path->getCString());
    47. delete path;
    48. savedPagesFileName.append("/.xpdf.pages");
    49. #endif
    50. }
    51. // no change since last read, so no need to re-read
    52. if (savedPagesFileTimestamp.isValid() &&
    53. QFileInfo(savedPagesFileName).lastModified() == savedPagesFileTimestamp) {
    54. return;
    55. }
    56. // mark all entries invalid
    57. for (int i = 0; i < maxSavedPageNumbers; ++i) {
    58. savedPageNumbers[i].fileName.clear();
    59. savedPageNumbers[i].pageNumber = 1;
    60. }
    61. // read the file
    62. FILE *f = openFile(savedPagesFileName.toUtf8().constData(), "rb");
    63. if (!f) {
    64. return;
    65. }
    66. char buf[1024];
    67. if (!fgets(buf, sizeof(buf), f) ||
    68. strcmp(buf, "xpdf.pages-1\n") != 0) {
    69. fclose(f);
    70. return;
    71. }
    72. int i = 0;
    73. while (i < maxSavedPageNumbers && fgets(buf, sizeof(buf), f)) {
    74. int n = (int)strlen(buf);
    75. if (n > 0 && buf[n-1] == '\n') {
    76. buf[n-1] = '\0';
    77. }
    78. char *p = buf;
    79. while (*p != ' ' && *p) {
    80. ++p;
    81. }
    82. if (!*p) {
    83. continue;
    84. }
    85. *p++ = '\0';
    86. savedPageNumbers[i].pageNumber = atoi(buf);
    87. savedPageNumbers[i].fileName = QString::fromUtf8(p);
    88. ++i;
    89. }
    90. fclose(f);
    91. // save the timestamp
    92. savedPagesFileTimestamp = QFileInfo(savedPagesFileName).lastModified();
    93. }

    (3)完成后又会报如下图的错误

    png.h是libpng库里的头文件,如果不需要pdf转html,转txt等功能,将把对应的.cc文件删掉。如果需要,就要编译libpng库,并导入对应的头文件。(我这里不需要这些高级的功能,就直接删掉了),需要删掉的文件如下:

    HTMLGen.cc

    pdftopng.cc

    删除后重新执行qmake命令,然后点击运行。

    (4)完成后又会报如下图的错误

     这是因为前面说的的高级功能,每个都有main函数并能生成各自的exe文件,从而导致main函数重复了。把他们都删除掉,需要删除的文件如下:

    pdfdetach.cc

    pdffonts.cc

    pdfimages.cc

    pdfinfo.cc

    pdftohtml.cc

    pdftoppm.cc

    pdftops.cc

    pdftotext.cc

    同样,完成后重新执行qmake命令,然后点击运行。

    3.6 xpdf库的使用

    前面设置好,xpdf库的配置就已经完成了,已经可以开始使用了。点击运行即弹出程序。(ENJOY!!!!!)

    四、xpdf库的自用

    如需源码,请上闲鱼,搜索:科技代码小卖部

    4.1 工程构建方式

    导入xpdf-qt文件时,只需要导入QtPDFCore和XpdfWidget的头文件和c文件。如下图所示

    4.2 初始化pdf显示库

    如下图所示,我把代码精简过了。

    1. //*******************设置pdf文档显示的字体库路径*******************
    2. GlobalParams::defaultTextEncoding = "UCS-2";
    3. globalParams = new GlobalParams(cfgFileArg);
    4. QString dir = QCoreApplication::applicationDirPath();
    5. globalParams->setBaseDir(dir.toLocal8Bit().constData());
    6. dir += "/xpdf-t1fonts";
    7. globalParams->setupBaseFonts(dir.toLocal8Bit().constData());
    8. //*******************初始化pdf页面类*******************
    9. pdf = new XpdfWidget(NULL,QColor(255,255,255), QColor(169,169,169),gFalse);//参数2:pdf空白处颜色,参数3:pdf文件外颜色
    10. pdf->enableHyperlinks(false);//是否启动超链接
    11. pdf->setKeyPassthrough(true);//是否响应按键事件
    12. pdf->setMousePassthrough(true);//是否响应鼠标事件
    13. connect(pdf, SIGNAL(mouseWheel(QWheelEvent*)),this, SLOT(mouseWheel(QWheelEvent*)));//鼠标事件
    14. ui->stackedWidget->addWidget(pdf);//pdf页面类加载到UI界面中
    15. ui->stackedWidget->setCurrentWidget(pdf);//设置UI界面当前显示的pdf页面类
    16. pdf->setFocus(Qt::OtherFocusReason);

    4.3 打开pdf文件

    1. //按钮槽函数:打开pdf文件
    2. void MainWindow::on_Btn_OpenPDF_clicked()
    3. {
    4. //*******************打开文件前先关闭已有的文件*******************
    5. try {
    6. pdf->closeFile();
    7. } catch (GMemException e) {
    8. }
    9. //*******************打开pdf文件*******************
    10. QDir startDir = QDir(".");//打开文件时默认的起始路径
    11. QString fileName = QFileDialog::getOpenFileName(this, "Open PDF File",
    12. startDir.canonicalPath(),
    13. "PDF files (*.pdf)");
    14. //如果文件为空,直接返回
    15. if (fileName.isEmpty()) {
    16. return;
    17. }
    18. //打开文件
    19. int err = pdf->loadFile(fileName, "");//打开文件
    20. if (err != XpdfWidget::pdfOk) {
    21. QMessageBox::warning(NULL, "打开文件错误",
    22. "打不开文件 '" + fileName + "'");
    23. }else{
    24. pdf->zoomCentered(XpdfWidget::zoomToWidth);//调整到合适大小
    25. }
    26. }

    4.4 缩放和滚动页面

    1. void MainWindow::mouseWheel(QWheelEvent *e) {
    2. //*******************文件打开状态才响应*******************
    3. if (!pdf->hasOpenDocument()) {
    4. return;
    5. }
    6. //*******************响应缩放或滚动页面*******************
    7. QPoint delta = e->angleDelta();//获取滚动的距离
    8. //ctrl键被按下,进行缩放,否则进行滚动显示
    9. if(e->modifiers() == Qt::ControlModifier) {
    10. if (delta.y() > 0) {
    11. double z = pdf->getZoomPercent(pdf->getMidPage());
    12. pdf->zoomCentered(z*1.2);
    13. } else if (delta.y() < 0) {
    14. double z = pdf->getZoomPercent(pdf->getMidPage());
    15. pdf->zoomCentered(z*0.8);
    16. }
    17. }else{
    18. //滚动显示
    19. if (delta.y() > 0) {
    20. pdf->getCore()->scrollUpPrevPage(delta.ry());
    21. } else if (delta.y() < 0) {
    22. pdf->getCore()->scrollDownNextPage(-delta.ry());
    23. }
    24. }
    25. }

    4.5 缩放到合适大小

    1. //恢复到合适大小
    2. void MainWindow::on_Btn_OpenPDF_2_clicked()
    3. {
    4. //*******************文件打开状态才响应*******************
    5. if (!pdf->hasOpenDocument()) {
    6. return;
    7. }
    8. pdf->zoomCentered(XpdfWidget::zoomToWidth);
    9. }

    4.6 关闭pdf文件

    1. void MainWindow::on_Btn_ClosePDF_clicked()
    2. {
    3. //*******************打开文件前先关闭已有的文件*******************
    4. try {
    5. pdf->closeFile();
    6. } catch (GMemException e) {
    7. }
    8. }

  • 相关阅读:
    STL之阶段总结、大作业指导
    数据结构绪论思维导图
    【小月电子】FPGA开发板(Spirit_V4)系统学习教程-LESSON9简易测试系统
    【2022世界杯开源项目实战】使用docker部署world-cup-2022-cli-dashboard数据看板工具
    # ruby安装设置笔记
    免费的VMware ?就是它了!【送源码】
    HIMA F3236 F7553 面向制造业的可视化人工智能
    记一次asp.net 8 服务器爆满的解决过程
    数据库界的Swagger:一键生成数据库文档!
    Jmeter测试添加凭证和导出压测结果
  • 原文地址:https://blog.csdn.net/kissgoodbye2012/article/details/133529450