UniDAC源码深度解析:从统一数据访问到Delphi跨数据库开发实战
简介本资源是专为 Delphi 13.0 FireMonkeyD13 FS开发者打造的 UniDAC 10.3.0 完整源码版面向中高级 Delphi 跨平台数据库应用开发者解决多数据库统一接入、零依赖部署与移动端适配等核心痛点。压缩包共含 938 个文件以 557 个 Pascal 源码.pas、77 个包含文件.inc、62 个资源文件.res及 51 个窗体描述.dfm/.lfm为主体涵盖全部驱动实现、可视化编辑器如 UniConnectionEditor.dfm、DASQLGeneratorFrame.dfm、宏处理框架UniMacrosFrame.dfm及安装构建脚本Make.bat、dac.bmk完整支撑从 IDE 集成到 iOS/Android 编译的全链路开发。资源包大小为 12.19MB结构清晰、模块分离明确开箱即用。目前已有 259 人学习下载开发者可直接获取企业级功能支持——包括参数化 SQL、批量更新、事务自动恢复及跨 Win/macOS/Linux/iOS/Android 的纯源码直连能力无需额外 DLL 或运行时依赖。1. 项目概述UniDAC源码与工具包的深度解析最近在整理一些老项目的数据库连接组件时又翻出了这个熟悉的压缩包——“UniDAC10.3.0 for D13 FS 完整源码版附带安装工具.7z”。对于使用Delphi进行跨数据库开发的同行来说UniDAC这个名字应该不陌生。它全称是Universal Data Access Components是Devart公司推出的一套功能强大的数据库连接组件库其核心价值在于为Delphi/C Builder开发者提供了一个统一的接口来连接和操作多种数据库比如Oracle、SQL Server、MySQL、PostgreSQL、SQLite等等。这个版本号10.3.0对应的是支持Delphi 10.3 Rio内部版本号D13的完整版而“FS”通常指代“Full Source”即包含了全部的源代码。为什么一个带源码的组件库压缩包会成为一个值得探讨的“项目”这远不止是一个简单的“下载-安装”过程。对于开发者而言拥有完整源码意味着你获得了最高级别的控制权和调试能力。当你在复杂的生产环境中遇到一个诡异的数据库连接超时问题或者需要针对特定数据库版本进行性能优化时能够深入组件内部查看SQL是如何被组装的、连接池是如何管理的、异常是如何抛出的这种能力是无价的。它把数据库连接从一个“黑盒”操作变成了一个你可以完全理解、甚至定制改造的透明过程。这个压缩包里附带的“安装工具”通常也不是简单的安装向导而是一套用于将源码编译、链接并集成到IDE的脚本或程序其本身的设计也蕴含了对Delphi包管理、版本兼容性处理的智慧。因此这个项目标题背后实际上是一个关于“如何专业地获取、理解、安装并最终掌控一个关键开发组件”的完整工作流。它适合所有使用Delphi进行中大型、涉及多数据库支持的应用开发的工程师、架构师无论是希望解决现有项目中的数据库访问痛点还是为新技术选型进行评估深入理解这套源码和工具都将大有裨益。接下来我将从一个资深开发者的角度带你彻底拆解这个压缩包从设计思路到实操安装再到源码层面的核心要点最后分享一些我踩过的坑和独家技巧。2. UniDAC核心价值与源码版优势剖析2.1 统一数据访问层UDAL的设计哲学在深入压缩包之前我们必须先理解UniDAC试图解决的根本问题。在早期的Delphi开发中连接不同的数据库需要使用不同的组件集用TADOConnection连SQL Server用TIBDatabase连InterBase用第三方控件连MySQL。这导致应用程序与特定数据库绑定严重代码中充斥着条件编译和数据库特性判断可维护性极差。UniDAC提出了一种抽象层设计即统一数据访问层。它的核心设计哲学是定义一套通用的、面向集合的接口如TUniConnectionTUniQuery在这些接口之下为每种数据库实现一个特定的“Provider”提供者或“Driver”驱动。应用程序只与通用接口交互而由底层的Provider去处理与具体数据库通讯的协议细节如Oracle的OCI、MySQL的libmysqlclient。这样做带来了几个立竿见影的好处代码解耦与可移植性业务逻辑代码不再依赖特定数据库的API。如果需要从Firebird迁移到PostgreSQL理论上只需更换连接字符串和底层驱动业务代码改动极小。开发效率提升开发者只需学习一套组件的方法、属性和事件就能操作所有支持的数据库降低了学习成本。功能一致性UniDAC在通用接口上提供了对事务、存储过程、批量操作、元数据查询等高级功能的统一封装即使某些数据库原生不支持某些特性如SQLite的存储过程UniDAC也可能在客户端模拟实现保证了上层应用体验的一致性。2.2 “完整源码版”带来的质变市面上常见的UniDAC分发形式有仅包含DCU/BPL二进制文件的运行时包、包含.dcu和.dfm的设计期包、以及最珍贵的完整源码版。拥有“完整源码版”绝不仅仅是“可以看看代码”那么简单它意味着深度调试与问题定位当程序在生产环境抛出“ORA-12154: TNS: 无法解析指定的连接标识符”时如果只有二进制文件你只能在自己的代码和Oracle网络配置里盲目排查。但有了源码你可以直接在UniProvider.pas或OracleUniProvider.pas中设置断点一步步跟踪连接字符串是如何被解析、TNS名称是如何被查找的。这种能力能将排查时间从数小时缩短到数分钟。定制化与功能增强假设你的项目需要使用一种非常特殊的Oracle数据类型或者需要对MySQL的某次查询进行特定的性能调优。你可以直接修改对应的Provider源码添加或覆盖相关方法。例如你可以修改TMySQLUniProvider中构建分页查询SQL的逻辑以适应不同MySQL版本的语法差异。学习高级编程技巧Devart的代码质量通常很高阅读其源码是学习Delphi高级编程、设计模式如工厂模式用于创建Provider、适配器模式用于兼容不同数据库命令的绝佳途径。你可以看到如何优雅地处理资源生命周期、如何进行高效的异常处理、如何设计可扩展的框架。长期版本兼容性保障Delphi IDE版本更新频繁官方二进制版可能无法及时支持最新的Delphi。拥有源码你就可以尝试自己为新的Delphi版本编译组件避免项目升级被一个第三方组件卡住。注意修改第三方商业组件的源码需要谨慎并充分理解其授权协议。通常修改后的版本仅供内部使用重新分发可能受到限制。务必保留好原始源码的备份。2.3 版本标识“for D13 FS”的具体含义“D13”特指Embarcadero Delphi 10.3 Rio其内部版本号就是13。这意味着该源码包中的项目文件.dproj、包文件.dpk以及可能的条件编译指令都是针对Delphi 10.3的编译器特性、RTL版本和IDE接口进行配置的。 “FS”即“Full Source”再次强调包含了所有单元的.pas源码文件而不仅仅是接口声明文件。这通常包括核心库Uni开头的单元如UniProvider.pasUniConnection.pasUniQuery.pas等定义了通用接口和基础实现。数据库特定Provider如OracleUniProvider.pasSQLServerUniProvider.pasMySQLUniProvider.pas等。设计期代码用于在IDE组件面板上显示图标、配置属性编辑器的单元。辅助工具与脚本可能包含注册脚本、编译批处理文件等。3. 安装工具解析与自动化部署实践3.1 安装工具包的结构与功能预判一个成熟的“附带安装工具”的源码包其工具部分通常不是单一的可执行文件而是一个小型的自动化工程集。在解压“UniDAC10.3.0 for D13 FS 完整源码版附带安装工具.7z”后我们预期会看到类似如下的目录结构UniDAC10.3.0_D13_FS/ ├── Source/ # 核心源码目录 │ ├── Core/ # 核心通用单元 │ ├── Providers/ # 各数据库Provider实现 │ ├── Design/ # 设计期代码 │ └── ... # 其他辅助单元 ├── Lib/ # 可能预编译的DCU文件针对不同Delphi版本 ├── Packages/ # Delphi包项目文件 (.dpk, .dproj) ├── InstallTool/ # 安装工具目录 │ ├── Compile.bat # 编译脚本 │ ├── Register.bat # 注册BPL到IDE的脚本 │ ├── Install.exe # 图形化安装程序如果有 │ └── Config.ini # 安装配置文件 ├── Demos/ # 示例程序 ├── Docs/ # 帮助文档 └── Readme.txt # 说明文件安装工具的核心任务自动化以下流程环境检测检查系统中已安装的Delphi版本通过注册表或特定路径确认是否为Delphi 10.3。源码编译按照正确的顺序编译运行时包UniDAC*_D*.dpk和设计期包UniDAC*_D*_Design.dpk。顺序至关重要设计期包依赖运行时包。输出管理将编译生成的.bplIDE插件包、.dcu编译单元、.dcp包符号文件输出到指定的目录通常是$(BDSCOMMONDIR)\Bpl和$(BDSCOMMONDIR)\Dcp。IDE注册将设计期包注册到Delphi IDE中使得组件出现在工具面板上。3.2 手动安装流程详解当工具失效时安装工具并非万能尤其在非标准环境如便携版Delphi、自定义目录下可能失败。掌握手动安装是必备技能。以下是详细步骤步骤一环境准备与源码检查首先确认你的Delphi 10.3 Rio可以正常打开和编译其他项目。然后浏览Source目录重点查看Core和Providers确认源码完整。打开Packages文件夹找到针对D13的包文件通常命名包含_D13或_260260是10.3的编译器版本号。步骤二编译运行时包Runtime Package用Delphi 10.3打开UniDACXXX_D13.dpkXXX代表版本号如UniDAC103_D13.dpk。在项目管理器中对包名右键选择“编译”Compile。不要选择“构建”Build除非首次编译。“编译”只编译有变化的单元速度更快。编译成功后会在项目输出目录可在项目选项Output Directory中查看生成UniDACXXX_D13.bpl和UniDACXXX_D13.dcp文件。关键操作将生成的.bpl文件复制到$(BDSCOMMONDIR)\Bpl目录例如C:\Users\Public\Documents\Embarcadero\Studio\20.0\Bpl。将.dcp文件复制到$(BDSCOMMONDIR)\Dcp目录。这一步是让IDE和其他项目能定位到这些包。步骤三编译设计期包Design-Time Package打开UniDACXXX_D13_Design.dpk。在项目管理器中右键该设计期包选择“编译”。编译成功后同样会生成对应的.bpl和.dcp文件。将设计期包的.bpl也复制到公共Bpl目录。注意设计期包的.dcp通常不需要单独复制因为其依赖的运行时包的.dcp已经在了。步骤四在IDE中安装组件在Delphi IDE中点击Component-Install Packages...。在弹出的对话框中点击Add...按钮。导航到公共Bpl目录选择刚才编译好的UniDACXXX_D13_Design.bpl文件点击打开。在包列表中找到它并确保其被勾选然后点击OK。如果一切顺利你会在工具面板上看到一个新的标签页如“UniDAC”里面包含了TUniConnectionTUniQuery等组件。实操心得我习惯在编译前在项目选项Delphi Compiler-Output directory中将所有包的输出目录统一设置为一个自定义的、易于管理的文件夹如D:\MyComponents\UniDAC10.3\Output而不是默认的.\$(Platform)\$(Config)。这样所有生成的文件都在一起方便备份和清理。复制到公共目录这一步可以写一个简单的批处理脚本自动化。3.3 安装工具的内部机制与自定义如果提供的安装工具是脚本如.bat我们可以分析其内容来理解其逻辑。一个典型的Compile.bat可能包含如下命令echo off set BDSC:\Program Files (x86)\Embarcadero\Studio\20.0\bin\bds.exe set PKG_DIRPackages set OUTPUT_DIR..\Output echo 正在编译运行时包... %BDS% -pDelphi -b %PKG_DIR%\UniDAC103_D13.dpk compile_log.txt 21 if errorlevel 1 goto error echo 正在编译设计期包... %BDS% -pDelphi -b %PKG_DIR%\UniDAC103_D13_Design.dpk compile_log.txt 21 if errorlevel 1 goto error echo 复制文件到公共目录... xcopy /Y %OUTPUT_DIR%\*.bpl %PUBLIC%\Documents\Embarcadero\Studio\20.0\Bpl\ xcopy /Y %OUTPUT_DIR%\*.dcp %PUBLIC%\Documents\Embarcadero\Studio\20.0\Dcp\ echo 安装成功 goto end :error echo 编译失败请查看 compile_log.txt 文件。 pause :end你可以根据自己Delphi的安装路径和偏好修改这个批处理文件中的BDS、PKG_DIR和OUTPUT_DIR变量。-b参数表示构建build-pDelphi指定平台。4. 核心源码结构探秘与关键单元解读4.1 源码目录架构与核心模块划分拥有源码后像阅读一本优秀的开源框架文档一样去浏览其目录结构是快速建立整体认知的最佳方式。一个典型的UniDAC完整源码目录可能如下Source/ ├── Core/ # 核心抽象与基础类 │ ├── UniConsts.pas # 常量定义错误码、版本号 │ ├── UniProvider.pas # 所有Provider的抽象基类 (TUniProvider) │ ├── UniConnection.pas # 核心连接类 (TUniConnection) │ ├── UniQuery.pas # 数据集核心 (TUniQuery) │ ├── UniScript.pas # SQL脚本执行器 │ └── ... # 事务、元数据、事件参数等 ├── Providers/ # 具体数据库实现 │ ├── Base/ # Provider基础辅助类 │ ├── Oracle/ # Oracle Provider │ │ ├── OracleUniProvider.pas # 主单元 │ │ ├── OracleClasses.pas # Oracle特定类 │ │ └── OracleUniProviderReg.pas # 注册单元 │ ├── SQLServer/ # SQL Server Provider │ ├── MySQL/ # MySQL Provider │ ├── PostgreSQL/ # PostgreSQL Provider │ ├── SQLite/ # SQLite Provider │ └── ... # 其他数据库 ├── Design/ # 设计期代码 │ ├── UniDACReg.pas # 组件注册到IDE面板 │ ├── UniConnectionEditors.pas # 连接编辑器 │ └── ... # 属性编辑器、组件图标等 ├── MetaData/ # 数据库元数据访问类 ├── GUI/ # 可视化控件如连接对话框 └── ... # 其他辅助模块如加密、性能监视器这种结构清晰地体现了“抽象与实现分离”的原则。Core目录定义了“要做什么”接口和通用逻辑Providers目录定义了“针对某个数据库具体怎么做”。4.2 核心类深度解析以TUniConnection和TUniQuery为例TUniConnection连接管理的枢纽打开UniConnection.pas你会发现TUniConnection的核心职责是管理数据库会话的生命周期。其关键私有字段通常包括FProvider: TUniProvider指向当前活动Provider的引用。这是多态性的关键。FServerFDatabaseFUsernameFPassword等连接参数。FConnected: Boolean连接状态标志。其Connect方法的大致逻辑如下伪代码表示procedure TUniCustomConnection.Connect; begin if FConnected then Exit; // 已连接则返回 CheckProvider; // 确保Provider已创建根据ProviderName属性 ValidateConnectionParams; // 验证参数 FProvider.Open; // 调用具体Provider的Open方法建立物理连接 FConnected : True; DoConnect; // 触发OnConnect事件 end;CheckProvider方法是工厂模式的体现它根据ProviderName属性如OracleMySQL动态创建对应的TUniProvider子类实例。这种设计使得增加对新数据库的支持变得非常清晰只需在Providers目录下新增一个Provider实现并在工厂注册即可。TUniQuerySQL执行的封装器TUniQuery继承自TDataSet是数据操作的核心。它的关键是将SQL命令的解析、参数绑定、执行、结果集获取等操作委托给内部的TUniProvider实例。例如其Open方法用于执行SELECT语句的核心流程准备SQL语句可能包括宏替换、参数解析。调用FProvider.CreateCommand创建一个数据库特定的命令对象。将参数集合Params绑定到命令对象。调用命令对象的Execute方法。从命令对象获取返回的数据流并填充到内部的数据缓冲区中。阅读UniQuery.pas中关于SetSQL和ExecSQL的代码你能深刻理解它如何处理动态SQL、如何管理参数TParams以及如何与底层的Provider交互。这对于你编写高性能、安全的数据库代码如防止SQL注入有直接的指导意义。4.3 Provider实现机制以MySQL Provider为例让我们深入Providers/MySQL/MySQLUniProvider.pas看一个具体实现。TMySQLUniProvider类继承自TUniProvider。它必须重写Override父类中所有抽象方法例如GetConnection返回一个TMySQLUniConnection实例该类封装了与libmysqlclient.dll的交互。CreateCommand返回一个TMySQLUniCommand实例用于执行SQL语句。StartTransactionCommitRollback实现MySQL的事务语义。在TMySQLUniCommand中你会看到它如何将通用的TUniQuery.SQL.Text可能是带参数的SQL转换为MySQL客户端库mysql_real_query函数能接受的格式以及如何将TParams中的参数值通过mysql_stmt_bind_param进行绑定。这是理解数据库驱动底层工作原理的绝佳窗口。注意事项不同Provider的性能调优点不同。例如Oracle Provider可能关注连接池OCI Session Pooling和数组绑定Array Binding的设置而SQLite Provider则更关注事务模式和文件锁定的策略。在源码中搜索FOptimization或FTuning相关的字段往往能找到这些数据库特定的优化开关。5. 实战应用从源码调试到定制化开发5.1 将UniDAC源码引入项目进行调试为了利用源码进行调试你需要让你的主项目在编译时使用.pas源码而非预编译的.dcu文件。有两种常用方法方法一修改项目搜索路径推荐在Delphi中打开你的应用程序项目。进入Project-Options-Delphi Compiler-Search path。将UniDAC源码的根目录例如D:\MyComponents\UniDAC10.3\Source添加到搜索路径的最前面。确保它在你之前设置的公共DCU目录如$(BDSCOMMONDIR)\Dcp之前。清理并重新编译你的项目。此时编译器会优先在源码目录中查找.pas文件并为你重新编译生成项目专用的.dcu同时将调试信息链接进去。方法二直接添加源码到项目在项目管理器中右键你的项目选择Add...。浏览并添加你需要调试的核心单元如UniConnection.pasUniQuery.pas以及你正在使用的特定Provider单元如MySQLUniProvider.pas。这种方法更直接但管理起来稍显繁琐特别是当单元间有依赖关系时。配置完成后在你的应用程序代码中设置断点然后当执行到UniDAC内部的代码如TUniConnection.Connect时IDE就会自动跳转到对应的源码文件允许你单步跟踪。5.2 常见问题排查与源码级解决方案问题一连接Oracle数据库时出现“ORA-12154”错误。常规排查检查TNS名称、连接字符串。源码级排查在OracleUniProvider.pas中找到构建连接字符串或解析Server属性的代码处设置断点。单步执行查看最终传递给OCI的connect string到底是什么。你会发现UniDAC可能根据你的输入是//host:port/service格式还是TNS名称采用了不同的连接方式。可能的问题环境变量TNS_ADMIN未设置或指向错误导致OCI无法找到tnsnames.ora文件。你可以在源码中看到它调用OCIServerAttach之前对连接字符串的处理逻辑。问题二执行大批量插入时MySQL性能低下。常规优化尝试开启事务使用TUniQuery的Array DML属性。源码级分析与定制打开MySQLUniProvider.pas找到TMySQLUniCommand.Execute或相关的方法。查看它是否使用了mysql_stmt预处理语句Prepared Statement来执行批量操作。如果没有或者其批量绑定的实现不够高效。定制可能你可以尝试重写这个方法。例如对于大批量INSERT可以将其改写成使用INSERT INTO ... VALUES (...) (...) (...)的多值语法这通常比逐条执行或简单的预处理批处理更快。但要注意SQL长度限制和语法兼容性。问题三需要支持一种新的自定义数据库类型。实现思路这展示了UniDAC框架的可扩展性。在Providers目录下创建一个新的子目录例如MyDB。创建一个新的Provider单元如MyDBUniProvider.pas。让它继承自TUniProvider。参考SQLiteUniProvider.pas相对简单的实现重写所有必需的抽象方法GetConnectionClassGetCommandClassStartTransaction等。你需要封装你的数据库客户端库的API。在Provider单元中注册你的新Provider名称。重新编译设计期包和运行时包或仅将新单元路径加入你的主项目搜索路径。现在你可以在TUniConnection.ProviderName的下拉列表中看到MyDB并使用它进行连接。5.3 性能调优与内存管理要点阅读源码能让你更精准地进行调优连接池Connection Pooling在UniConnection.pas中查找Pooling相关属性。源码会揭示连接池是如何实现的通常是维护一个空闲连接列表。理解其生命周期创建、获取、释放、超时销毁有助于你设置合理的PoolingMaxPoolSizeConnectTimeout等参数避免连接泄漏或耗尽。语句缓存Statement Caching对于Oracle等数据库频繁准备Prepare相同的SQL语句开销很大。在OracleUniProvider.pas中搜索FStatementCache。了解其缓存机制后你可以更好地决定是否以及何时调用TUniQuery.Prepare或者依赖UniDAC的自动管理。内存与资源释放重点关注TUniCustomConnection.Disconnect和各个Provider命令对象的Destroy方法。确保在你的应用程序中连接和查询对象在不再使用时被正确释放放在try...finally块中或使用接口管理生命周期。源码会显示它们是否及时关闭了底层的数据库句柄、释放了结果集内存。6. 版本迁移、兼容性考量与项目集成建议6.1 从旧版本UniDAC或其它组件迁移如果你正在将一个使用老版本UniDAC如7.x或其它数据库组件如ADO dbExpress的项目升级到使用此版本源码能提供巨大帮助。API变更检查对比新旧版本UniConnection.pas等核心单元的接口部分public和published节查看属性、方法、事件是否有增减或改名。例如某个调优属性可能从Options.LocalMasterDetail移到了SpecificOptions属性下。行为差异分析有时接口没变但内部行为变了。例如在某个版本中TUniQuery.Open在遇到空结果集时的行为可能从抛出异常改为静默处理。通过阅读新旧版本对应方法的源码可以快速定位这种潜在的不兼容点。替代方案验证如果你想从dbExpress迁移可以同时打开dbExpress的TSQLConnection源码和UniDAC的TUniConnection源码对比它们在处理事务隔离级别、游标管理等方面的差异编写更精准的适配代码或测试用例。6.2 与不同Delphi版本的兼容性处理这个源码包是针对D1310.3 Rio的。如果你想在更高版本的Delphi如11 Alexandria中使用可能会遇到编译错误。常见编译错误单元依赖变更Delphi RTL/FMX单元结构可能变化。错误信息如“Unit XYZ was compiled with a different version of ABC”。你需要找到项目或包文件中uses列表里出问题的单元根据新Delphi版本调整引用。API废弃或变更例如AnsiString相关函数在跨平台版本中行为有变化。在源码中搜索{$IFDEF}条件编译指令通常Devart已经为不同版本做了适配。如果没有你需要手动添加条件编译或使用更通用的字符串类型如string。包输出名称需要将.dpk文件中的{$LIBSUFFIX 260}10.3的编译器版本修改为对应新版本的标识如Alexandria是350。迁移步骤备份原始源码。用新版本Delphi打开.dproj或.dpk文件让IDE自动升级项目格式。尝试编译运行时包。根据错误信息逐一修复。重点关注条件编译指令{$IFDEF VERXXX}和已废弃的API。编译通过后再尝试编译设计期包。重要修改包输出名称和搜索路径避免与旧版本冲突。6.3 在大型项目中的集成与管理策略在团队开发的大型项目中如何管理这样一个带源码的第三方组件版本控制将整个解压后的UniDAC10.3.0_D13_FS目录包括源码、安装工具、文档纳入你的版本控制系统如Git。建议使用子模块Submodule或子仓库Subtree的方式与主项目代码分离但关联。统一编译输出如前所述在组件的包项目设置中将所有输出.bpl.dcp.dcu定向到项目组内一个统一的、相对路径的目录如..\..\Build\UniDAC。不要在开发者的本地公共Bpl目录编译。这保证了团队所有成员使用完全一致的二进制文件。项目搜索路径配置在团队共享的项目选项文件.dproj或构建脚本中设置源码路径指向版本库内的路径。例如Search Path $(MY_PROJECT_ROOT)\ThirdParty\UniDAC10.3.0\Source;$(MY_PROJECT_ROOT)\ThirdParty\UniDAC10.3.0\Output\$(Platform)\$(Config)。持续集成CI在CI服务器如Jenkins TeamCity上构建脚本的第一步应包括编译UniDAC运行时包和设计期包如果设计期包不是必须的可以只编译运行时包。确保CI环境与开发环境使用的组件版本完全一致。通过这样的流程UniDAC10.3.0 for D13 FS 完整源码版附带安装工具.7z从一个简单的压缩包转变为了一个可被严格管理、深度定制、为团队赋能的战略性开发资产。它不再是一个易碎的“黑盒”依赖而是一个你可以完全理解、控制和信赖的基石。本文还有配套的精品资源点击获取