跳转到内容

验证

配置验证是配表质量保障的重要环节。通过自动化的检查机制,可以在开发过程中及早发现配置错误、引用断裂和数据不一致问题。

cfggen 提供了完整的验证机制,涵盖以下四个方面:

  • Schema 结构验证:检查配置定义的语法和逻辑错误
  • 数据值验证:检查配置数据类型正确
  • 数据一致性检查:验证外键引用的正确性,避免”悬空引用”
  • 未引用记录检测:发现孤立的配置数据,帮助清理冗余内容

您可以通过两种方式使用验证功能:

  • 命令行验证(本文档重点):使用 -gen verify 命令,适合集成到 CI/CD 流程
  • 编辑器验证:在 cfgeditor 图形界面中实时查看检测结果,适合开发过程中使用(详见编辑器功能介绍 - 未引用记录检测
Terminal window
java -jar cfggen.jar -datadir <配置目录> -gen verify[,参数]

参数说明

  • -datadir:指定配表根目录,目录下必须有 config.cfg 文件
  • -gen verify
    • 无参数:执行外键检查
    • unreferenced:只检查未引用记录
    • entry:只检查入口记录

示例

Terminal window
# 执行外键一致性检查
java -jar cfggen.jar -datadir ../example/config -gen verify
# 额外检查未引用记录,入口记录
java -jar cfggen.jar -datadir ../example/config -gen verify,unreferenced,entry

验证分为三个阶段,层层递进,确保配置的质量和完整性。

时机:解析 .cfg 文件时

检查内容

  • 配置定义的语法是否正确
  • 类型定义是否完整
  • 外键引用关系是否合理

结果处理

  • 收集所有错误和警告
  • 如果存在严重错误(Err),立即停止并需要先修复 schema 问题

时机:读取 Excel、CSV、JSON 文件时

检查内容

  • 数据是否符合 schema 定义
  • 数据类型和格式是否正确
  • 外键引用是否完整

结果处理

  • 收集所有数据值错误和警告
  • 可以选择是否允许错误继续(取决于配置)

时机:数据加载完成后

检查内容

  • 从 entry/enum 表和 root 表开始追踪引用链
  • 标记所有被引用的记录
  • 找出孤立的记录

结果处理

  • 输出未引用记录列表
  • 不影响系统运行,仅作为提示信息

Schema 验证在解析 .cfg 文件时进行,检查配置定义的语法和逻辑正确性。

快速提示:初次使用时,最常遇到的错误是 命名类(表名大小写、名称冲突)和 外键类(引用不存在、类型不匹配)问题。

严重错误会阻止系统继续运行,必须修复后才能继续。按类型分组如下:

错误代码错误描述解决方案
TableNameNotLowerCasetable 名称必须全小写修改表名为小写
NameConflicttable、struct、interface 名字冲突重命名冲突的类型
InnerNameConflictfield 名字冲突重命名冲突的字段
DataHeadNameNotIdentifier数据文件的名称行不是合法标识符检查 CSV/Excel 的第二行英文名
DataHeadNameDuplicated数据文件的名称行有重复修改重复的名称
错误代码错误描述解决方案
TypeStructNotFound类型未找到检查类型名是否拼写正确
PrimitiveFieldFmtMustBeAutoprimitive 类型字段 fmt 必须是 auto移除或改为 auto
StructFieldFmtMustBeAutoOrPackstruct 类型字段 fmt 必须是 auto 或 pack调整 fmt 设置
ListFieldFmtMustBePackOrSepOrFixOrBlocklist 类型字段 fmt 必须是 pack、sep、fix、block 之一调整 fmt 设置
MapFieldFmtMustBePackOrFixOrBlockmap 类型字段 fmt 必须是 pack、fix、block 之一调整 fmt 设置
KeyTypeNotSupport键类型不支持(只支持 int、long、bool、str 或 struct)修改键类型
错误代码错误描述解决方案
RefTableNotFound外键对应的 table 不存在检查表名拼写
RefTableKeyNotUniq外键指向的键不是唯一键使用表的唯一键作为外键目标
ListRefMultiKeyNotSupportlistRef 不支持多字段外键改为单字段外键
RefLocalKeyRemoteKeyCountNotMatch外键的 local key 和 remote key 数量不匹配确保两边字段数量一致
RefLocalKeyRemoteKeyTypeNotMatch外键的 local key 和 remote key 类型不匹配确保两边字段类型一致
RefContainerNullablelist、map 的 ref 不应该是 nullable移除 nullable 设置
KeyNotFound主键、唯一键、外键的本地键不存在检查字段名拼写
错误代码错误描述解决方案
EnumRefNotFoundinterface 对应的枚举表不存在创建枚举表或修改 interface 定义
InterfaceImplEmptyinterface 里没有 struct 实现添加至少一个 struct 实现
DefaultImplNotFoundinterface 的 defaultImpl 未找到修正 defaultImpl 名称
EntryNotFoundtable 的 entry 或 enum 对应的字段类型不是 string修改字段类型为 string
EntryFieldTypeNotStrentry 字段类型不是 string修改字段类型为 string
PrimaryKeyNotEnumOrIntWhenEnum枚举表的主键必须是 enum 或 int 类型修改主键类型
错误代码错误描述解决方案
SplitDataHeaderNotEqual数据文件头不匹配 schema 定义更新数据文件或检查 schema
FieldHeaderSpanNotEnough字段需要的列数超过了 header 的剩余列数检查 struct/list 字段的 span 与 Excel 列数是否匹配
JsonTableNotSupportExcel标记为 json 的 table 不能有对应的 excel 文件删除 Excel 文件或移除 json 标记
JsonTableNotSupportMap标记为 json 的 table 不能使用 map 类型改用 list 类型
MappingToExcelLoop结构有循环且未使用 pack,无法映射到 excel使用 pack 格式或重构结构

警告不会阻止系统运行,但建议修复以避免潜在问题。

警告代码警告描述优先级
NameMayConflictByRefinterface 的局部名字空间可能跟全局冲突;分文件存储 schema 时可能顶层名字冲突 - 可能导致运行时错误
StructNotUsed结构定义了但未被使用低 - 可能是未来使用
InterfaceNotUsed接口定义了但未被使用低 - 可能是未来使用
LowercaseNotOnStrOrTextlowercase 修饰符只能用于 str 或 text 类型中 - 修饰符无效
SuggestTypeUnknown建议的类型未知中 - 类型推断失败

弱警告默认不显示,使用 -weakwarn 参数时才会输出。

弱警告代码弱警告描述
FilterRefIgnoredByRefTableNotFound经 tag 过滤后外键被忽略,因引用的表不存在
FilterRefIgnoredByRefKeyNotFound经 tag 过滤后外键被忽略,因引用的键不存在

数据验证在读取 Excel、CSV、JSON 文件时进行,检查配置数据的完整性和正确性。

值错误表示数据存在问题,需要修复。按类型分组如下:

错误代码错误描述常见原因
ParsePackErr解析 pack 字符串失败格式不符合定义
NotMatchFieldType单元格值与字段类型不匹配数据类型错误
FieldCellSpanNotEnough字段需要的单元格个数不足Excel 列数不够
FieldCellNotUsed字段单元格未被使用数据格式问题
错误代码错误描述解决方案
PrimaryOrUniqueKeyDuplicated主键或唯一键值重复修改重复的键值
MapKeyDuplicatedmap 类型的 key 重复修改重复的 key
EntryDuplicatedentry 或 enum 值重复修改重复的入口值
错误代码错误描述解决方案
ForeignValueNotFound外键引用的值不存在修正外键值或创建被引用的记录
InterfaceCellEmptyButHasNoDefaultImpl接口单元格为空但未设置 defaultImpl填充值或设置 defaultImpl
InterfaceCellImplNotFound接口的实现类型不存在修正实现类型名称
错误代码错误描述解决方案
EnumEmpty枚举单元格为空填入枚举值
EntryContainsSpaceentry 或 enum 值包含空格移除空格
MustFillButCellEmpty必填字段但单元格为空填入数据
RefNotNullableButCellEmpty外键字段非空但单元格为空填入外键值或添加 nullable
错误代码错误描述常见原因
JsonFileReadErrJSON 文件读取失败文件路径错误或权限问题
JsonStrEmptyJSON 字符串为空数据缺失
JsonParseExceptionJSON 解析失败JSON 格式错误
JsonTypeNotExistJSON 中引用的类型不存在检查类型定义
JsonTypeNotMatchJSON 类型与不匹配修改数据类型
JsonValueNotMatchTypeJSON 值与类型不匹配修改值或类型定义
错误代码错误描述
InternalError系统内部错误(建议报告问题)

值警告表示可能存在数据问题,但不影响系统运行。

警告代码警告描述建议
JsonHasExtraFieldsJSON 包含 schema 定义中不存在的字段检查是否有字段拼写错误,或更新 schema

未引用记录检测是验证的第三阶段,在数据加载完成后进行。

理想情况(所有记录都被引用):

未引用记录检查: 所有记录都被引用,没有发现未引用的记录。

发现问题时的输出示例

========== 未被引用的记录检查结果 ==========
共发现 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

想象一下,配置表就像一个互相链接的网络系统。有些节点是”入口”(Entry/Enum 表),有些节点被其他节点”链接”(外键引用),还有些节点是”独立访问点”(root 标记的表)。

一条记录被认为是被引用的,当它满足以下任一条件:

  • Entry/Enum 表中的记录:这些表是系统的顶层入口,记录天然被视为被引用
  • root 标记的表中的记录:表被标记为 (root) 元数据,表示这些记录通过特殊方式被访问(详见下文)
  • 被外键引用的记录:被其他记录通过外键关联(->=>)所指向

有些表虽然看似”未引用”,但它们有特殊的访问方式,不应该被视为孤立数据。对于这类表,应该在 .cfg schema 定义中添加 (root) 元数据。

语法示例

table LevelReward[level] (root) {
level: int
reward: -> Item
}

常见的 root 表类型

表类型说明示例
等级表以等级为 key,程序根据玩家等级动态查找LevelRewardLevelExp
地块表以地块坐标为 key,程序根据位置动态访问MapTileMapBlock
随机表程序从表中随机抽取记录RandomDropPoolRandomSkill
时间表以时间段或时间戳为 keyDailyRewardSeasonConfig

为什么需要 root 标记?

这些表的记录通常不会被其他配置通过外键直接引用,而是由程序通过特定逻辑(等级计算、坐标查找、随机抽取等)动态访问。标记为 (root) 后,未引用记录检测会跳过这些表,避免产生误报。

根据不同场景,未引用记录通常有以下几种情况:

  • 这些配置是新创建的,还没有通过外键关联到系统
  • 这是正常的工作流程,您可以根据项目进度决定何时建立关联
  • 如果程序代码直接写死了配置 ID(魔数)来引用数据,而不是通过配置系统的 entry、enum、外键机制
  • 建议:应该将硬编码的 ID 改为使用配置的外键关联,这样可以:
    • 提高代码可维护性
    • 避免因配置 ID 变更导致的错误
    • 让配置关系更加清晰可见
  • 如果表确实属于特殊用途类型(等级表、地块表、随机表等)
  • 应该在 schema 定义中添加 (root) 标记

入口记录检测用于收集和显示系统的入口记录信息。入口记录是配置系统的”根节点”,从这些记录开始可以追踪到所有被引用的记录。

使用 -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 条记录。

系统会自动识别三种类型的入口记录:

类型标记说明
枚举表[ENUM]标记为 (enum='字段') 的表,所有记录都视为入口
入口表[ENTRY]标记为 (entry='字段') 的表,只有 entry 字段有值的记录才是入口
根表[ROOT]标记为 (root) 元数据的表,所有记录都视为入口

入口记录是配置引用链的起点:

  1. [ENUM] 表:为 interface 提供实现类选择的枚举表,所有实现类型名称都是入口
  2. [ENTRY] 表:程序代码中通过静态成员直接访问的记录,entry 字段标识了入口名称
  3. [ROOT] 表:特殊访问方式的表(等级表、地块表、随机表等),程序通过特定逻辑动态访问

从这些入口记录出发,系统可以追踪到所有被引用的记录,从而发现孤立的配置数据。

功能命令作用
入口记录检测-gen verify,entry显示系统的入口点,帮助理解配置的访问路径
未引用记录检测-gen verify,unreferenced发现不在引用链中的孤立记录

两者结合使用,可以全面了解配置的引用关系和系统结构。


掌握验证命令和结果解读后,以下建议可以帮助您充分发挥验证功能的作用:

场景建议操作
日常开发使用 cfgeditor 图形界面实时查看检测结果
迭代结束运行 -gen verify 命令进行完整验证
CI/CD 流程集成 -gen verify 命令,自动检测配置完整性
  • 优先使用外键关联:而非硬编码 ID(魔数),充分发挥配置系统的优势
  • 正确标记特殊表:为特殊用途的表添加 (root) 标记,确保检测结果准确
  • 及时清理冗余数据:对于确认不再使用的记录,及时删除以保持配置库整洁
Terminal window
# 在 CI 脚本中添加验证步骤
java -jar cfggen.jar -datadir config -gen verify
if [ $? -ne 0 ]; then
echo "配置验证失败,请检查未引用记录或错误"
exit 1
fi


Q: 我的配置表显示大量未引用记录,但程序运行正常,怎么办?

Section titled “Q: 我的配置表显示大量未引用记录,但程序运行正常,怎么办?”

A: 这种情况通常是程序使用了硬编码的 ID(魔数)来引用配置数据。

解决方案

  1. 如果是动态访问的表(如等级表、地块表、随机表),添加 (root) 标记
  2. 如果是应该通过外键关联的数据,建议逐步改用外键关联:
    • 提高代码可维护性
    • 避免因配置 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: 建议按以下优先级处理:

  1. 命名类错误TableNameNotLowerCaseNameConflict):最快修复
  2. 类型类错误TypeStructNotFoundKeyTypeNotSupport):检查类型定义
  3. 外键类错误RefTableNotFoundRefLocalKeyRemoteKeyTypeNotMatch):检查引用关系
  4. 格式类错误MappingToExcelLoopJsonTableNotSupportMap):可能需要调整结构

Q: 可以忽略验证错误继续运行吗?

Section titled “Q: 可以忽略验证错误继续运行吗?”

A:

  • Schema 严重错误(Err):不能忽略,必须修复后才能继续
  • Schema 警告(Warn):可以忽略,但建议修复以避免潜在问题
  • 数据值错误(VErr):只有在为cfgeditor提供编辑时允许继续
  • 未引用记录:不影响运行,仅作为质量提示