跳转到内容

验证

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

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 格式或重构结构
SeqFieldMustBeIntseq 只能用于 int 类型字段修改字段类型为 int 或移除 seq

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

警告代码警告描述优先级
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外键引用的值不存在修正外键值或创建被引用的记录
InterfaceCellImplNotFound接口的实现类型不存在修正实现类型名称
错误代码错误描述解决方案
EnumEmpty枚举单元格为空填入枚举值
EntryContainsSpaceentry 或 enum 值包含空格移除空格
MustFillButCellEmpty必填字段但单元格为空填入数据
RefNotNullableButCellEmpty外键字段非空但单元格为空填入外键值或添加 nullable
SeqValueNotContinuousseq 字段的值不连续(应为 0, 1, 2, …)修正字段值使其构成连续序列
错误代码错误描述常见原因
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提供编辑时允许继续
  • 未引用记录:不影响运行,仅作为质量提示