验证
配置验证是配表质量保障的重要环节。通过自动化的检查机制,可以在开发过程中及早发现配置错误、引用断裂和数据不一致问题。
cfggen 提供了完整的验证机制,涵盖以下四个方面:
- Schema 结构验证:检查配置定义的语法和逻辑错误
- 数据值验证:检查配置数据类型正确
- 数据一致性检查:验证外键引用的正确性,避免”悬空引用”
- 未引用记录检测:发现孤立的配置数据,帮助清理冗余内容
您可以通过两种方式使用验证功能:
- 命令行验证(本文档重点):使用
-gen verify命令,适合集成到 CI/CD 流程 - 编辑器验证:在 cfgeditor 图形界面中实时查看检测结果,适合开发过程中使用(详见编辑器功能介绍 - 未引用记录检测)
-gen verify 命令
Section titled “-gen verify 命令”java -jar cfggen.jar -datadir <配置目录> -gen verify[,参数]参数说明:
-datadir:指定配表根目录,目录下必须有config.cfg文件-gen verify:- 无参数:执行外键检查
unreferenced:只检查未引用记录entry:只检查入口记录
示例:
# 执行外键一致性检查java -jar cfggen.jar -datadir ../example/config -gen verify
# 额外检查未引用记录,入口记录java -jar cfggen.jar -datadir ../example/config -gen verify,unreferenced,entry验证是如何工作的
Section titled “验证是如何工作的”验证分为三个阶段,层层递进,确保配置的质量和完整性。
第一阶段:Schema 验证
Section titled “第一阶段:Schema 验证”时机:解析 .cfg 文件时
检查内容:
- 配置定义的语法是否正确
- 类型定义是否完整
- 外键引用关系是否合理
结果处理:
- 收集所有错误和警告
- 如果存在严重错误(Err),立即停止并需要先修复 schema 问题
第二阶段:数据验证
Section titled “第二阶段:数据验证”时机:读取 Excel、CSV、JSON 文件时
检查内容:
- 数据是否符合 schema 定义
- 数据类型和格式是否正确
- 外键引用是否完整
结果处理:
- 收集所有数据值错误和警告
- 可以选择是否允许错误继续(取决于配置)
第三阶段:未引用记录检测
Section titled “第三阶段:未引用记录检测”时机:数据加载完成后
检查内容:
- 从 entry/enum 表和 root 表开始追踪引用链
- 标记所有被引用的记录
- 找出孤立的记录
结果处理:
- 输出未引用记录列表
- 不影响系统运行,仅作为提示信息
Schema 验证错误
Section titled “Schema 验证错误”Schema 验证在解析 .cfg 文件时进行,检查配置定义的语法和逻辑正确性。
快速提示:初次使用时,最常遇到的错误是 命名类(表名大小写、名称冲突)和 外键类(引用不存在、类型不匹配)问题。
严重错误(Err)
Section titled “严重错误(Err)”严重错误会阻止系统继续运行,必须修复后才能继续。按类型分组如下:
| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
TableNameNotLowerCase | table 名称必须全小写 | 修改表名为小写 |
NameConflict | table、struct、interface 名字冲突 | 重命名冲突的类型 |
InnerNameConflict | field 名字冲突 | 重命名冲突的字段 |
DataHeadNameNotIdentifier | 数据文件的名称行不是合法标识符 | 检查 CSV/Excel 的第二行英文名 |
DataHeadNameDuplicated | 数据文件的名称行有重复 | 修改重复的名称 |
| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
TypeStructNotFound | 类型未找到 | 检查类型名是否拼写正确 |
PrimitiveFieldFmtMustBeAuto | primitive 类型字段 fmt 必须是 auto | 移除或改为 auto |
StructFieldFmtMustBeAutoOrPack | struct 类型字段 fmt 必须是 auto 或 pack | 调整 fmt 设置 |
ListFieldFmtMustBePackOrSepOrFixOrBlock | list 类型字段 fmt 必须是 pack、sep、fix、block 之一 | 调整 fmt 设置 |
MapFieldFmtMustBePackOrFixOrBlock | map 类型字段 fmt 必须是 pack、fix、block 之一 | 调整 fmt 设置 |
KeyTypeNotSupport | 键类型不支持(只支持 int、long、bool、str 或 struct) | 修改键类型 |
| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
RefTableNotFound | 外键对应的 table 不存在 | 检查表名拼写 |
RefTableKeyNotUniq | 外键指向的键不是唯一键 | 使用表的唯一键作为外键目标 |
ListRefMultiKeyNotSupport | listRef 不支持多字段外键 | 改为单字段外键 |
RefLocalKeyRemoteKeyCountNotMatch | 外键的 local key 和 remote key 数量不匹配 | 确保两边字段数量一致 |
RefLocalKeyRemoteKeyTypeNotMatch | 外键的 local key 和 remote key 类型不匹配 | 确保两边字段类型一致 |
RefContainerNullable | list、map 的 ref 不应该是 nullable | 移除 nullable 设置 |
KeyNotFound | 主键、唯一键、外键的本地键不存在 | 检查字段名拼写 |
Interface 和 Entry 相关
Section titled “Interface 和 Entry 相关”| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
EnumRefNotFound | interface 对应的枚举表不存在 | 创建枚举表或修改 interface 定义 |
InterfaceImplEmpty | interface 里没有 struct 实现 | 添加至少一个 struct 实现 |
DefaultImplNotFound | interface 的 defaultImpl 未找到 | 修正 defaultImpl 名称 |
EntryNotFound | table 的 entry 或 enum 对应的字段类型不是 string | 修改字段类型为 string |
EntryFieldTypeNotStr | entry 字段类型不是 string | 修改字段类型为 string |
PrimaryKeyNotEnumOrIntWhenEnum | 枚举表的主键必须是 enum 或 int 类型 | 修改主键类型 |
数据文件格式相关
Section titled “数据文件格式相关”| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
SplitDataHeaderNotEqual | 数据文件头不匹配 schema 定义 | 更新数据文件或检查 schema |
FieldHeaderSpanNotEnough | 字段需要的列数超过了 header 的剩余列数 | 检查 struct/list 字段的 span 与 Excel 列数是否匹配 |
JsonTableNotSupportExcel | 标记为 json 的 table 不能有对应的 excel 文件 | 删除 Excel 文件或移除 json 标记 |
JsonTableNotSupportMap | 标记为 json 的 table 不能使用 map 类型 | 改用 list 类型 |
MappingToExcelLoop | 结构有循环且未使用 pack,无法映射到 excel | 使用 pack 格式或重构结构 |
警告(Warn)
Section titled “警告(Warn)”警告不会阻止系统运行,但建议修复以避免潜在问题。
| 警告代码 | 警告描述 | 优先级 |
|---|---|---|
NameMayConflictByRef | interface 的局部名字空间可能跟全局冲突;分文件存储 schema 时可能顶层名字冲突 | 高 - 可能导致运行时错误 |
StructNotUsed | 结构定义了但未被使用 | 低 - 可能是未来使用 |
InterfaceNotUsed | 接口定义了但未被使用 | 低 - 可能是未来使用 |
LowercaseNotOnStrOrText | lowercase 修饰符只能用于 str 或 text 类型 | 中 - 修饰符无效 |
SuggestTypeUnknown | 建议的类型未知 | 中 - 类型推断失败 |
弱警告(WeakWarn)
Section titled “弱警告(WeakWarn)”弱警告默认不显示,使用 -weakwarn 参数时才会输出。
| 弱警告代码 | 弱警告描述 |
|---|---|
FilterRefIgnoredByRefTableNotFound | 经 tag 过滤后外键被忽略,因引用的表不存在 |
FilterRefIgnoredByRefKeyNotFound | 经 tag 过滤后外键被忽略,因引用的键不存在 |
数据验证错误
Section titled “数据验证错误”数据验证在读取 Excel、CSV、JSON 文件时进行,检查配置数据的完整性和正确性。
值错误(VErr)
Section titled “值错误(VErr)”值错误表示数据存在问题,需要修复。按类型分组如下:
格式解析错误
Section titled “格式解析错误”| 错误代码 | 错误描述 | 常见原因 |
|---|---|---|
ParsePackErr | 解析 pack 字符串失败 | 格式不符合定义 |
NotMatchFieldType | 单元格值与字段类型不匹配 | 数据类型错误 |
FieldCellSpanNotEnough | 字段需要的单元格个数不足 | Excel 列数不够 |
FieldCellNotUsed | 字段单元格未被使用 | 数据格式问题 |
键值重复错误
Section titled “键值重复错误”| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
PrimaryOrUniqueKeyDuplicated | 主键或唯一键值重复 | 修改重复的键值 |
MapKeyDuplicated | map 类型的 key 重复 | 修改重复的 key |
EntryDuplicated | entry 或 enum 值重复 | 修改重复的入口值 |
引用完整性错误
Section titled “引用完整性错误”| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
ForeignValueNotFound | 外键引用的值不存在 | 修正外键值或创建被引用的记录 |
InterfaceCellEmptyButHasNoDefaultImpl | 接口单元格为空但未设置 defaultImpl | 填充值或设置 defaultImpl |
InterfaceCellImplNotFound | 接口的实现类型不存在 | 修正实现类型名称 |
数据完整性错误
Section titled “数据完整性错误”| 错误代码 | 错误描述 | 解决方案 |
|---|---|---|
EnumEmpty | 枚举单元格为空 | 填入枚举值 |
EntryContainsSpace | entry 或 enum 值包含空格 | 移除空格 |
MustFillButCellEmpty | 必填字段但单元格为空 | 填入数据 |
RefNotNullableButCellEmpty | 外键字段非空但单元格为空 | 填入外键值或添加 nullable |
JSON 相关错误
Section titled “JSON 相关错误”| 错误代码 | 错误描述 | 常见原因 |
|---|---|---|
JsonFileReadErr | JSON 文件读取失败 | 文件路径错误或权限问题 |
JsonStrEmpty | JSON 字符串为空 | 数据缺失 |
JsonParseException | JSON 解析失败 | JSON 格式错误 |
JsonTypeNotExist | JSON 中引用的类型不存在 | 检查类型定义 |
JsonTypeNotMatch | JSON 类型与不匹配 | 修改数据类型 |
JsonValueNotMatchType | JSON 值与类型不匹配 | 修改值或类型定义 |
| 错误代码 | 错误描述 |
|---|---|
InternalError | 系统内部错误(建议报告问题) |
值警告(VWarn)
Section titled “值警告(VWarn)”值警告表示可能存在数据问题,但不影响系统运行。
| 警告代码 | 警告描述 | 建议 |
|---|---|---|
JsonHasExtraFields | JSON 包含 schema 定义中不存在的字段 | 检查是否有字段拼写错误,或更新 schema |
未引用记录检测
Section titled “未引用记录检测”未引用记录检测是验证的第三阶段,在数据加载完成后进行。
理想情况(所有记录都被引用):
未引用记录检查: 所有记录都被引用,没有发现未引用的记录。发现问题时的输出示例:
========== 未被引用的记录检查结果 ==========共发现 2 个table中有未被引用的记录总计 15 条记录未被引用
注:理想情况下未引用记录数应该为 0。 如果程序通过硬编码 ID(魔数)引用配置,建议改用外键关联。 如果表是动态访问的(如等级表、地块表),应添加 (root) 标记。
==========Table: Skill (10条未引用) - 1001, 火球术 - 1002, 冰霜箭 - 1003, 雷击术 ...==========Table: Item (5条未引用) - 5001, 铁剑 - 5002, 木盾 ...========== 检查完成 ==========输出内容说明:
| 信息项 | 说明 |
|---|---|
| 共发现 X 个table | 存在未引用记录的表的数量 |
| 总计 X 条记录 | 所有未引用记录的总数 |
| 每个表的详细信息 | 表名和该表的未引用记录列表,每个表最多显示 50 条 |
| ID, 标题格式 | 如果记录设置了 title 元数据,会显示为 “ID, 名称” 格式,否则只显示 ID |
理解”被引用”
Section titled “理解”被引用””想象一下,配置表就像一个互相链接的网络系统。有些节点是”入口”(Entry/Enum 表),有些节点被其他节点”链接”(外键引用),还有些节点是”独立访问点”(root 标记的表)。
一条记录被认为是被引用的,当它满足以下任一条件:
- Entry/Enum 表中的记录:这些表是系统的顶层入口,记录天然被视为被引用
- root 标记的表中的记录:表被标记为
(root)元数据,表示这些记录通过特殊方式被访问(详见下文) - 被外键引用的记录:被其他记录通过外键关联(
->或=>)所指向
特殊用途的表:root 标记
Section titled “特殊用途的表:root 标记”有些表虽然看似”未引用”,但它们有特殊的访问方式,不应该被视为孤立数据。对于这类表,应该在 .cfg schema 定义中添加 (root) 元数据。
语法示例:
table LevelReward[level] (root) { level: int reward: -> Item}常见的 root 表类型:
| 表类型 | 说明 | 示例 |
|---|---|---|
| 等级表 | 以等级为 key,程序根据玩家等级动态查找 | LevelReward、LevelExp |
| 地块表 | 以地块坐标为 key,程序根据位置动态访问 | MapTile、MapBlock |
| 随机表 | 程序从表中随机抽取记录 | RandomDropPool、RandomSkill |
| 时间表 | 以时间段或时间戳为 key | DailyReward、SeasonConfig |
为什么需要 root 标记?
这些表的记录通常不会被其他配置通过外键直接引用,而是由程序通过特定逻辑(等级计算、坐标查找、随机抽取等)动态访问。标记为 (root) 后,未引用记录检测会跳过这些表,避免产生误报。
未引用记录的处理建议
Section titled “未引用记录的处理建议”根据不同场景,未引用记录通常有以下几种情况:
1. 策划正在编辑中
Section titled “1. 策划正在编辑中”- 这些配置是新创建的,还没有通过外键关联到系统
- 这是正常的工作流程,您可以根据项目进度决定何时建立关联
2. 程序代码中使用了”魔数”
Section titled “2. 程序代码中使用了”魔数””- 如果程序代码直接写死了配置 ID(魔数)来引用数据,而不是通过配置系统的 entry、enum、外键机制
- 建议:应该将硬编码的 ID 改为使用配置的外键关联,这样可以:
- 提高代码可维护性
- 避免因配置 ID 变更导致的错误
- 让配置关系更加清晰可见
3. 需要添加 (root) 标记
Section titled “3. 需要添加 (root) 标记”- 如果表确实属于特殊用途类型(等级表、地块表、随机表等)
- 应该在 schema 定义中添加
(root)标记
入口记录检测
Section titled “入口记录检测”入口记录检测用于收集和显示系统的入口记录信息。入口记录是配置系统的”根节点”,从这些记录开始可以追踪到所有被引用的记录。
使用 -gen verify,entry 命令时的输出示例:
========== 入口记录检查结果 ==========共发现 3 个包含入口记录的表共 6 条记录作为入口点
表:completeconditiontype [ENUM] (3条入口记录) - 1, 击杀怪物条件 - 2, 与NPC对话条件 - 3, 收集物品条件表:task [ENTRY] (2条入口记录) - 1001, 主线任务 - 1002, 日常任务表:LevelReward [ROOT] (1条入口记录) - 10, 10级奖励========== 检查完成 ==========如果没有找到入口记录:
入口记录检查:未找到入口记录。输出内容说明:
| 信息项 | 说明 |
|---|---|
| 共发现 X 个包含入口记录的表 | 包含入口记录的表数量 |
| 共 X 条记录作为入口点 | 所有入口记录的总数 |
| [ENUM] | 枚举表标记,所有记录都视为入口 |
| [ENTRY] | 入口表标记,只有 entry 字段有值的记录才是入口 |
| [ROOT] | 根表标记,所有记录都视为入口 |
| ID, 标题格式 | 如果记录设置了 title 元数据,会显示为 “ID, 标题” 格式,否则只显示 ID |
限制:每个表最多显示 10 条记录。
入口记录的类型
Section titled “入口记录的类型”系统会自动识别三种类型的入口记录:
| 类型 | 标记 | 说明 |
|---|---|---|
| 枚举表 | [ENUM] | 标记为 (enum='字段') 的表,所有记录都视为入口 |
| 入口表 | [ENTRY] | 标记为 (entry='字段') 的表,只有 entry 字段有值的记录才是入口 |
| 根表 | [ROOT] | 标记为 (root) 元数据的表,所有记录都视为入口 |
入口记录的作用
Section titled “入口记录的作用”入口记录是配置引用链的起点:
- [ENUM] 表:为 interface 提供实现类选择的枚举表,所有实现类型名称都是入口
- [ENTRY] 表:程序代码中通过静态成员直接访问的记录,entry 字段标识了入口名称
- [ROOT] 表:特殊访问方式的表(等级表、地块表、随机表等),程序通过特定逻辑动态访问
从这些入口记录出发,系统可以追踪到所有被引用的记录,从而发现孤立的配置数据。
入口记录与未引用记录的关系
Section titled “入口记录与未引用记录的关系”| 功能 | 命令 | 作用 |
|---|---|---|
| 入口记录检测 | -gen verify,entry | 显示系统的入口点,帮助理解配置的访问路径 |
| 未引用记录检测 | -gen verify,unreferenced | 发现不在引用链中的孤立记录 |
两者结合使用,可以全面了解配置的引用关系和系统结构。
掌握验证命令和结果解读后,以下建议可以帮助您充分发挥验证功能的作用:
| 场景 | 建议操作 |
|---|---|
| 日常开发 | 使用 cfgeditor 图形界面实时查看检测结果 |
| 迭代结束 | 运行 -gen verify 命令进行完整验证 |
| CI/CD 流程 | 集成 -gen verify 命令,自动检测配置完整性 |
- 优先使用外键关联:而非硬编码 ID(魔数),充分发挥配置系统的优势
- 正确标记特殊表:为特殊用途的表添加
(root)标记,确保检测结果准确 - 及时清理冗余数据:对于确认不再使用的记录,及时删除以保持配置库整洁
CI/CD 集成示例
Section titled “CI/CD 集成示例”# 在 CI 脚本中添加验证步骤java -jar cfggen.jar -datadir config -gen verifyif [ $? -ne 0 ]; then echo "配置验证失败,请检查未引用记录或错误" exit 1fi- 命令行工具 - 了解更多命令行参数和用法
- 元数据配置 - 了解
(root)元数据配置 - 外键关联 - 了解外键的定义和使用
- 配置编辑器功能介绍 - 在图形界面中使用验证功能
- 最佳实践 - 了解配置开发的最佳实践
Q: 我的配置表显示大量未引用记录,但程序运行正常,怎么办?
Section titled “Q: 我的配置表显示大量未引用记录,但程序运行正常,怎么办?”A: 这种情况通常是程序使用了硬编码的 ID(魔数)来引用配置数据。
解决方案:
- 如果是动态访问的表(如等级表、地块表、随机表),添加
(root)标记 - 如果是应该通过外键关联的数据,建议逐步改用外键关联:
- 提高代码可维护性
- 避免因配置 ID 变更导致的错误
- 让配置关系更加清晰可见
Q: 检测速度太慢,能只检测特定的表吗?
Section titled “Q: 检测速度太慢,能只检测特定的表吗?”A: 命令行版本的 -gen verify 会检测所有表,不支持选择性检测。
建议方案:
- CI/CD 中:使用完整检测,确保配置质量
- 本地开发时:使用 cfgeditor 图形界面,可以选择特定表进行检测
Q: (root) 标记会影响代码生成吗?
Section titled “Q: (root) 标记会影响代码生成吗?”A: 不会。(root) 标记只影响未引用记录检测的行为,不会改变生成的代码结构或运行时行为。
Q: 如何区分警告(Warn)和弱警告(WeakWarn)?
Section titled “Q: 如何区分警告(Warn)和弱警告(WeakWarn)?”A:
- 警告(Warn):默认显示,提示可能的问题,建议根据优先级决定是否修复
- 弱警告(WeakWarn):默认不显示,需要使用
-weakwarn参数才会显示,通常是次要的提示性信息
Q: Schema 验证报错太多,无从下手怎么办?
Section titled “Q: Schema 验证报错太多,无从下手怎么办?”A: 建议按以下优先级处理:
- 命名类错误(
TableNameNotLowerCase、NameConflict):最快修复 - 类型类错误(
TypeStructNotFound、KeyTypeNotSupport):检查类型定义 - 外键类错误(
RefTableNotFound、RefLocalKeyRemoteKeyTypeNotMatch):检查引用关系 - 格式类错误(
MappingToExcelLoop、JsonTableNotSupportMap):可能需要调整结构
Q: 可以忽略验证错误继续运行吗?
Section titled “Q: 可以忽略验证错误继续运行吗?”A:
- Schema 严重错误(Err):不能忽略,必须修复后才能继续
- Schema 警告(Warn):可以忽略,但建议修复以避免潜在问题
- 数据值错误(VErr):只有在为cfgeditor提供编辑时允许继续
- 未引用记录:不影响运行,仅作为质量提示