Unity游戏开发中Excel数据读取的三种方案与实战避坑指南

📅 2026/7/12 9:42:05
Unity游戏开发中Excel数据读取的三种方案与实战避坑指南
1. 项目概述Unity与Excel数据联动的那些事儿在游戏开发尤其是Unity项目里数据驱动设计早已是标配。策划同学把数值、配置、对话文本一股脑儿塞进Excel我们程序就得想办法把这些数据“喂”给游戏。听起来简单不就是读个文件嘛但真干起来从选型、编码到打包发布每一步都可能藏着意想不到的坑。特别是当你需要兼容不同版本的Excel文件.xls和.xlsx甚至还要考虑在编辑器里直接读取、运行时动态加载或者与WPS这类第三方办公软件产生的文件打交道时问题就复杂了。我自己就经历过不少“血泪史”编辑器里读得好好的一打包成WebGL就报错用NPOI读取WPS保存的文件死活打不开还有那令人头疼的依赖库版本冲突。所以今天我不打算只讲某个库的API怎么用而是想结合我这些年踩过的坑系统性地聊聊在Unity中读取Excel数据的几种主流方法、它们各自的适用场景以及如何优雅地避开那些常见的“雷区”。无论你是刚接触Unity数据管理的新手还是正在为某个诡异的数据读取问题头疼的老鸟希望这篇结合了实战经验和深度原理分析的长文能给你带来实实在在的帮助。2. 核心方案选型与深度解析面对Unity读取Excel的需求我们通常有几条路可以走。每一条路背后都有其设计哲学和适用边界选错了后续的开发效率、运行性能和跨平台兼容性都会大打折扣。2.1 方案一使用成熟的第三方.NET库以NPOI为例这是最经典、最灵活也是坑最多的一条路。NPOI是一个开源的.NET库可以让你在不安装Microsoft Office的情况下直接读写Excel、Word等Office文档。在Unity的Windows或Mac编辑器环境下这通常是首选。为什么选择NPOI它的最大优势是功能强大且免费。你可以精确控制单元格格式、公式、甚至图表虽然游戏开发中很少用到。更重要的是它能同时处理老旧的.xlsExcel 97-2003和现代的.xlsxExcel 2007格式。对于需要从Excel源文件直接读取、并可能在编辑器工具链中生成二进制配置表如bytes, json, scriptable object的项目来说NPOI几乎是唯一的选择。核心依赖与版本陷阱NPOI并非一个独立的DLL。它强依赖于SharpZipLib这个库来处理.xlsx文件的压缩包因为.xlsx本质上是一个ZIP压缩包里面是一堆XML。这就是开头网络资料里提到的那个经典大坑Unity NPOI 无法读取xlsx。问题的根源在于依赖版本不匹配。你从NuGet下载的NPOI包其依赖的SharpZipLib版本是固定的。而你的Unity项目里可能因为其他资源包比如某些Asset Store的插件已经包含了一个不同版本的SharpZipLib。当Unity编译时它不知道应该用哪一个或者错误地加载了不兼容的版本导致NPOI在尝试解压.xlsx文件时崩溃。实操心得解决这个问题的黄金法则不是“添加”而是“替换”。不要简单地把NPOI和它自带的SharpZipLib一起扔进Plugins文件夹。正确做法是在一个干净的C#类库项目中通过NuGet安装你需要的NPOI版本例如2.5.6。从该项目的packages目录或编译输出目录找到NPOI.dll和其对应的ICSharpCode.SharpZipLib.dll。将这两个DLL一起复制到Unity项目的Assets/Plugins或根据平台放在Assets/Plugins/x86等子目录下覆盖任何已有的旧版本SharpZipLib。区分HSSF与XSSF这是另一个新手常犯的错误。NPOI用两套不同的类来处理不同格式的文件HSSFWorkbook: 用于处理.xls格式Excel 97-2003。它基于二进制格式。XSSFWorkbook: 用于处理.xlsx格式Excel 2007及以上。它基于Open XML格式即ZIPXML。如果你用HSSFWorkbook去读一个.xlsx文件或者反之必然会失败。更隐蔽的坑是创建文件时如果你创建了一个XSSFWorkbook对象却用.xls后缀保存用Microsoft Office打开会报错但WPS有时却能打开这反而会误导你。// 正确的读取方式示例 using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using NPOI.HSSF.UserModel; using System.IO; public IWorkbook ReadExcelFile(string filePath) { IWorkbook workbook; using (FileStream fs new FileStream(filePath, FileMode.Open, FileAccess.Read)) { // 根据文件扩展名决定使用哪个实现类 if (Path.GetExtension(filePath).ToLower() .xlsx) { workbook new XSSFWorkbook(fs); // 处理.xlsx } else { workbook new HSSFWorkbook(fs); // 处理.xls } } return workbook; }2.2 方案二将Excel预处理为中间格式JSON/CSV/ScriptableObject如果你受够了二进制库的依赖和平台兼容性问题这是一个极其清爽的替代方案。核心思想是不在游戏运行时直接读取Excel。工作流如下策划使用Excel编辑数据。通过一个编辑器工具可以是用NPOI写的C#脚本也可以是Python脚本甚至是用WPS/Excel的宏将Excel文件导出为更通用的格式如JSON或CSV。在Unity中使用内置的JsonUtility、Newtonsoft.Json需导入或简单的字符串分割来读取CSV将数据加载到内存中的数据结构或直接生成ScriptableObject资源。为什么推荐这个方案零运行时依赖游戏本体不需要携带NPOI、SharpZipLib等任何额外DLL。包体更小加载更安全。跨平台无忧JSON/CSV是纯文本在任何平台Windows, Mac, Linux, iOS, Android, WebGL上的读取方式都一致且稳定彻底避免了原生插件兼容性问题。性能更优解析优化过的二进制或文本格式通常比直接解析复杂的Excel文件更快。数据安全你可以对导出的数据进行加密或混淆而原始的Excel文件无需打包进游戏。工具链搭建建议你可以编写一个Editor Window提供“一键导出”按钮。点击后工具遍历指定目录下所有Excel文件用NPOI读取然后序列化为JSON文件存放在Resources或Addressables可寻址的路径下。这样策划只需维护Excel程序只需关心JSON解析逻辑职责清晰。// 一个简单的编辑器导出工具示例 #if UNITY_EDITOR using UnityEditor; using System.Collections.Generic; using Newtonsoft.Json; // 需要导入Newtonsoft.Json包 public class ExcelExporter : EditorWindow { [MenuItem(Tools/Export Excel to JSON)] static void Export() { string excelPath Assets/Config/Excel/; string outputPath Assets/Resources/Config/; // 1. 使用NPOI读取excelPath下所有文件 // 2. 将每一张Sheet解析为自定义的DataClass列表 // 3. 使用JsonConvert.SerializeObject将列表序列化 // 4. 将JSON字符串写入到outputPath文件名对应Sheet名 Debug.Log(导出完成); } } #endif2.3 方案三利用ODBC或OLE DB仅限Windows平台/编辑器这是一种相对“古老”但在某些特定场景下直接有效的方法。通过.NET Framework提供的System.Data.Odbc或System.Data.OleDb命名空间你可以像访问数据库一样访问Excel文件。它的工作原理是将Excel文件视为一个数据源每个Sheet就是一张表。string connectionString Driver{Microsoft Excel Driver (*.xls, *.xlsx)};DBQ filePath; using (OdbcConnection conn new OdbcConnection(connectionString)) { conn.Open(); string query SELECT * FROM [Sheet1$]; // 查询Sheet1 OdbcCommand cmd new OdbcCommand(query, conn); OdbcDataReader reader cmd.ExecuteReader(); while (reader.Read()) { // 读取每一行数据 } }致命缺陷严重依赖系统环境需要目标机器上安装正确版本的Microsoft Access Database EngineACE OLEDB驱动或Microsoft Excel。你的游戏不可能要求玩家也安装这个。仅限Windows此方案在Mac、移动端或WebGL上完全不可用。性能一般相比于直接解析文件通过驱动层查询开销更大。结论绝不推荐在最终发布的游戏运行时使用此方案。它唯一的用武之地可能是在Windows系统的Unity编辑器内部用于开发一些快速的数据预览或导入工具且你确定所有开发者的电脑环境一致。3. 实战构建一个健壮的Excel读取模块了解了方案我们来动手搭建一个。我们的目标是在Unity编辑器内高效、稳定地读取Excel并导出为游戏运行时使用的轻量级数据格式。3.1 环境准备与依赖管理第一步也是最容易出错的一步是管理好NPOI及其依赖。获取正确的DLL不要直接从网上下载来路不明的DLL。建议通过Visual Studio的NuGet包管理器为一个新的.NET Framework类库项目安装NPOI。这会自动解决依赖。我们需要的版本应该与Unity的.NET兼容性级别匹配。对于使用.NET 4.x的Unity版本选择NPOI 2.5.x或2.6.x通常是安全的。导入Unity从该项目的输出目录bin/Debug或bin/Release或NuGet包的lib文件夹中找到以下文件NPOI.dllICSharpCode.SharpZipLib.dll版本必须与NPOI匹配可能还有NPOI.OOXML.dll,NPOI.OpenXml4Net.dll等一并导入。放置位置在Unity项目的Assets文件夹下创建Plugins目录将这些DLL拖入。确保它们的“Platform Settings”正确例如任何平台都兼容。注意事项如果你之前项目里已经存在ICSharpCode.SharpZipLib.dll可能来自其他资源包务必用NPOI配套的版本将其替换。这是避免“无法读取xlsx”问题的关键。3.2 核心读取代码封装我们来封装一个通用的Excel读取器它需要处理格式判断、空单元格、不同类型数据读取等细节。// ExcelReader.cs using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using NPOI.HSSF.UserModel; using System.Collections.Generic; using System.IO; using UnityEngine; public static class ExcelReader { /// summary /// 将指定Sheet读取为二维字符串列表第一行通常是表头 /// /summary /// param namefilePathExcel文件绝对路径/param /// param namesheetIndexSheet索引默认为0/param /// param namestartRow从第几行开始读0-based默认为0包含表头/param /// returns/returns public static ListListstring ReadSheetToStringGrid(string filePath, int sheetIndex 0, int startRow 0) { if (!File.Exists(filePath)) { Debug.LogError($Excel文件不存在{filePath}); return null; } IWorkbook workbook; using (FileStream fs new FileStream(filePath, FileMode.Open, FileAccess.Read)) { string ext Path.GetExtension(filePath).ToLower(); if (ext .xlsx) { workbook new XSSFWorkbook(fs); } else if (ext .xls) { workbook new HSSFWorkbook(fs); } else { Debug.LogError($不支持的文件格式{ext}); return null; } } ISheet sheet workbook.GetSheetAt(sheetIndex); if (sheet null) { Debug.LogError($Sheet索引{sheetIndex}不存在。); return null; } ListListstring grid new ListListstring(); // 计算最大列数避免每行长度不一致 int maxColCount 0; for (int i startRow; i sheet.LastRowNum; i) { IRow row sheet.GetRow(i); if (row ! null) { maxColCount Mathf.Max(maxColCount, row.LastCellNum); } } for (int rowIndex startRow; rowIndex sheet.LastRowNum; rowIndex) { IRow row sheet.GetRow(rowIndex); Liststring rowData new Liststring(); for (int colIndex 0; colIndex maxColCount; colIndex) { string cellValue ; if (row ! null) { ICell cell row.GetCell(colIndex); cellValue GetCellValueAsString(cell); } rowData.Add(cellValue); } grid.Add(rowData); } return grid; } /// summary /// 将单元格内容安全地转换为字符串 /// /summary private static string GetCellValueAsString(ICell cell) { if (cell null) return ; switch (cell.CellType) { case CellType.String: return cell.StringCellValue; case CellType.Numeric: // 判断是否为日期格式 if (DateUtil.IsCellDateFormatted(cell)) { return cell.DateCellValue.ToString(yyyy-MM-dd HH:mm:ss); } else { // 避免数值变成科学计数法或带多余小数点的字符串 return cell.NumericCellValue.ToString(); } case CellType.Boolean: return cell.BooleanCellValue.ToString(); case CellType.Formula: // 对于公式可以尝试获取缓存的计算结果或者重新计算复杂 // 简单处理返回公式字符串本身或尝试获取计算值 try { return cell.StringCellValue; // 或 cell.NumericCellValue.ToString() 等 } catch { return cell.CellFormula; } case CellType.Error: return ErrorEval.GetText(cell.ErrorCellValue); case CellType.Blank: default: return ; } } }这个ReadSheetToStringGrid方法提供了一个基础但强大的读取框架。它返回一个二维列表你可以很方便地将其映射到自定义的数据类Data Class中。3.3 进阶映射到强类型数据类在实际项目中我们更希望将Excel的每一行直接映射到一个C#对象实例。这需要预先定义好数据结构。假设我们有一个配置英雄属性的Excel表HeroConfig.xlsx结构如下IDNameHPAttackSkillID1001战士100015050011002法师8002005002定义数据类[System.Serializable] public class HeroConfigData { public int ID; public string Name; public float HP; public int Attack; public int SkillID; }编写映射逻辑 我们可以扩展ExcelReader添加一个泛型方法通过反射或预定义的列映射规则将字符串网格转换为对象列表。这里展示一个基于列索引的简单映射public static ListT ReadSheetToObjectListT(string filePath, int sheetIndex 0, int headerRowIndex 0) where T : new() { var grid ReadSheetToStringGrid(filePath, sheetIndex, headerRowIndex); if (grid null || grid.Count 1) return new ListT(); ListT result new ListT(); // 假设第一行是表头第二行开始是数据 for (int i 1; i grid.Count; i) // i1 跳过表头行 { T obj new T(); System.Type type typeof(T); var fields type.GetFields(); // 获取所有公共字段 // 简单映射按字段定义顺序对应列顺序生产环境应用更健壮的映射如根据表头名 for (int j 0; j fields.Length j grid[i].Count; j) { var field fields[j]; string cellValue grid[i][j]; try { // 将字符串转换为字段对应的类型 object convertedValue Convert.ChangeType(cellValue, field.FieldType); field.SetValue(obj, convertedValue); } catch (Exception e) { Debug.LogWarning($将单元格值{cellValue}转换为{field.FieldType}类型时出错位于行{i1}列{j1}。错误{e.Message}); // 可以设置默认值 field.SetValue(obj, GetDefaultValue(field.FieldType)); } } result.Add(obj); } return result; } private static object GetDefaultValue(Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; }使用方式ListHeroConfigData heroList ExcelReader.ReadSheetToObjectListHeroConfigData(C:/Config/HeroConfig.xlsx); foreach (var hero in heroList) { Debug.Log($英雄{hero.Name}, HP: {hero.HP}); }3.4 编辑器工具集成与自动化为了让策划和测试人员无需接触代码我们需要在Unity编辑器内创建一个友好的工具界面。创建Editor Window// ExcelToolWindow.cs #if UNITY_EDITOR using UnityEditor; using UnityEngine; using System.IO; public class ExcelToolWindow : EditorWindow { private string excelFolderPath Assets/Config/Excel/; private string jsonOutputPath Assets/Resources/Config/; [MenuItem(Window/Excel配置工具)] static void Init() { GetWindowExcelToolWindow(Excel工具).Show(); } void OnGUI() { GUILayout.Label(Excel数据导出工具, EditorStyles.boldLabel); EditorGUILayout.Space(); excelFolderPath EditorGUILayout.TextField(Excel文件夹路径:, excelFolderPath); jsonOutputPath EditorGUILayout.TextField(JSON输出路径:, jsonOutputPath); EditorGUILayout.Space(); if (GUILayout.Button(一键导出所有配置表)) { ExportAllExcelFiles(); } EditorGUILayout.HelpBox(点击按钮后将读取指定文件夹下所有.xlsx和.xls文件并将每个Sheet导出为同名的JSON文件。, MessageType.Info); } void ExportAllExcelFiles() { if (!Directory.Exists(excelFolderPath)) { EditorUtility.DisplayDialog(错误, $Excel文件夹不存在{excelFolderPath}, 确定); return; } Directory.CreateDirectory(jsonOutputPath); // 确保输出目录存在 string[] excelFiles Directory.GetFiles(excelFolderPath, *.xlsx); excelFiles excelFiles.Concat(Directory.GetFiles(excelFolderPath, *.xls)).ToArray(); int successCount 0; foreach (var file in excelFiles) { try { // 1. 使用ExcelReader读取文件 // 2. 根据文件名或内部约定决定映射到哪种数据类例如HeroConfig.xlsx - HeroConfigData // 3. 将数据列表序列化为JSON // 4. 写入到jsonOutputPath下文件名如 HeroConfig.json successCount; Debug.Log($已导出{Path.GetFileName(file)}); } catch (Exception e) { Debug.LogError($导出文件{file}时出错{e.Message}); } } AssetDatabase.Refresh(); // 刷新Unity资源数据库让新生成的JSON文件显示出来 EditorUtility.DisplayDialog(完成, $成功导出 {successCount}/{excelFiles.Length} 个文件。, 确定); } } #endif这个工具窗口提供了路径配置和“一键导出”功能。在实际项目中你还需要一个“配置映射表”来定义哪个Excel文件对应哪个C#数据类以及Sheet名和列名的映射关系。这可以通过一个ScriptableObject配置文件来实现。4. 深度避坑指南与疑难杂症排查即使按照上述步骤操作在实际开发中你仍可能遇到各种奇怪的问题。下面是我总结的一些高频“坑点”及其解决方案。4.1 平台兼容性“巨坑”WebGL与移动端问题描述在Unity Editor里运行完美的Excel读取代码打包成WebGL或iOS/Android后直接崩溃或报DllNotFoundException。根本原因NPOI及其依赖的SharpZipLib是使用.NET编写的托管DLL但它们内部可能调用了某些本地Native代码或者其实现依赖于完整的.NET Framework/ .NET Standard库而WebGL使用Mono或IL2CPP编译为WebAssembly和某些移动端平台的运行时环境并不完全支持这些库的所有功能。特别是文件系统访问System.IO在WebGL中受到浏览器的严格限制。解决方案彻底放弃运行时读取Excel这是最根本、最推荐的方案。如前所述使用“编辑器导出为中间格式JSON/CSV/Asset”的工作流。游戏运行时只读取轻量的、平台无关的中间文件。如果必须在运行时读取极不推荐WebGL几乎不可能。浏览器无法直接访问用户本地文件系统除非通过input typefile上传。即使你将Excel文件放在StreamingAssets里WebGL也无法使用System.IO.File来读取它。你需要使用UnityWebRequest来加载但加载的是二进制流你仍然需要能在WebAssembly环境中运行的纯C# Excel解析库NPOI很可能不行。iOS/Android理论上如果NPOI是纯托管代码且使用的.NET API在Unity的.NET Standard子集中IL2CPP可以将其编译。但风险极高需要充分测试。务必确保所有DLL的“Platform Settings”中已为目标平台如iOS勾选。核心原则Excel文件是设计期/编辑期资源而非运行期资源。将其转换是构建流程的一部分不应在玩家设备上进行。4.2 WPS与Microsoft Office的兼容性差异问题描述用WPS保存的.xlsx文件用NPOI读取时可能报错“无效的文件头”或读取到的数据乱码反之用NPOI创建的文件WPS能打开但Microsoft Office报错。原因分析文件格式细节差异虽然都遵循Open XML标准但WPS和MS Office在实现某些细节如默认的压缩算法、某些可选属性的写入上可能存在微小差异。NPOI等开源库通常以MS Office的行为为基准进行开发对WPS的某些“非标”写法可能兼容性不佳。文件后缀与内容不匹配如前所述如果你用XSSFWorkbook创建了内容却保存为.xls后缀WPS可能更“宽容”地尝试解析而MS Office会直接拒绝。解决方案统一工具链在团队内约定配置表只使用Microsoft Excel进行编辑和保存。这是最一劳永逸的办法。使用NPOI的自动检测在读取时可以不依赖文件后缀名而是尝试用WorkbookFactory自动检测并创建正确的Workbook对象。这能避免因后缀名错误导致的格式误判。using (FileStream fs new FileStream(filePath, FileMode.Open, FileAccess.Read)) { // WorkbookFactory会自动判断是HSSF还是XSSF IWorkbook workbook WorkbookFactory.Create(fs); }保存时显式指定格式在创建和保存文件时明确使用正确的类和方法。// 创建.xlsx文件 IWorkbook workbook new XSSFWorkbook(); // ... 添加数据 ... using (FileStream fs new FileStream(output.xlsx, FileMode.Create)) { workbook.Write(fs); }4.3 性能优化与内存管理当Excel文件非常大几十MB数万行时直接读取可能导致编辑器卡顿甚至内存溢出。优化策略流式读取NPOI的WorkbookFactory.Create在读取时会将整个文件加载到内存。对于超大文件可以考虑使用XSSF和SAXSimple API for XML模式进行事件驱动的流式读取但这会复杂很多通常只用于特殊的数据提取场景。分Sheet读取如果文件有多个Sheet不要一次性获取所有ISheet对象。用workbook.GetSheetAt(index)按需读取。及时释放资源确保IWorkbook、FileStream等对象在使用后被正确释放使用using语句块。编辑器工具异步化对于一键导出大量Excel文件的工具可以考虑使用EditorApplication.delayCall或编写一个简单的协程进度条避免编辑器界面卡死。终极方案——预处理再次强调在构建Build或数据提交时将Excel批量预处理成二进制或JSON格式。游戏运行时读取这些优化后的格式性能会好几个数量级。4.4 常见错误速查表错误现象可能原因解决方案FileNotFoundException或DllNotFoundException(NPOI/SharpZipLib)DLL未正确放入Plugins文件夹或平台设置不正确。检查DLL是否在Assets/Plugins下并在Inspector中确认目标平台已勾选。确保NPOI和SharpZipLib版本匹配。“无法读取xlsx”或“无效文件头”1. SharpZipLib版本不匹配。2. 文件实际损坏或不是Excel文件。3. 文件被其他进程占用。1. 使用NPOI配套的SharpZipLib替换项目中的旧版本。2. 用Excel或WPS手动打开文件确认。3. 关闭可能占用该文件的Excel/WPS程序。读取数值或日期错误单元格格式设置问题。NPOI读取日期时需要判断单元格是否为日期格式。使用DateUtil.IsCellDateFormatted(cell)判断再使用cell.DateCellValue获取。对于自定义的数字格式可能需要手动处理cell.NumericCellValue。读取公式单元格得到的是公式字符串而非结果单元格类型是CellType.Formula未获取计算值。尝试使用cell.StringCellValue或cell.NumericCellValue来获取缓存的计算结果。注意这要求Excel文件在保存时已计算过公式。打包后非WebGL读取失败Excel文件未包含在构建中或路径错误。将Excel文件放在Resources文件夹下不推荐会使包体变大或更好的方式使用Addressables或AssetBundle系统进行加载并确保其包含在构建里。再次强调最佳实践是只打包导出的JSON/Asset文件。在Mac版Unity编辑器读取失败NPOI对文件路径或权限敏感。确保文件路径正确并且Unity有读取权限。尝试使用绝对路径。检查文件是否被锁定。5. 扩展与版本控制和工作流的结合在团队协作中Excel配置表也是源代码的一部分需要纳入版本控制如Git。二进制文件问题.xlsx和.xls是二进制文件Git对其差异比较和合并无能为力。这可能导致合并冲突难以解决。建议虽然将Excel文件加入版本控制但团队应约定每次只由一个人编辑特定的Excel文件并通过锁机制或沟通来避免并行修改。更好的做法是将导出的、可读的JSON或CSV中间文件也纳入版本控制作为数据变更的清晰记录。自动化构建集成你可以在CI/CD持续集成/持续部署流水线中加入一个步骤在打包前自动运行你的Excel导出工具确保游戏中的数据总是最新的。数据校验在导出工具中可以加入数据校验逻辑。例如检查ID是否唯一、数值是否在合理范围内、引用其他表的ID是否存在等。在导出阶段就发现数据错误比在游戏测试时崩溃要好得多。最后关于WPS的一些特殊“命令”或功能如宏、JSAPI在Unity数据读取的上下文中我们基本不涉及。Unity读取的是文件的数据内容与用什么软件生成、该软件有什么高级功能无关。只要它最终保存为标准格式.xls或.xlsx我们的读取代码就应该能处理。如果遇到WPS特有的格式兼容性问题回归到“统一使用MS Office”这个解决方案是最稳妥的。整个流程走下来你会发现在Unity中处理Excel数据的核心不在于“读”这个动作本身而在于如何设计一个稳定、高效、跨平台的数据管道。从策划编辑到程序读取、验证、转换再到游戏运行时加载每一步都需要仔细考量。希望这篇长文能帮你理清思路搭建起属于自己项目的、坚如磐石的数据管理方案。