把 70×70 图纸压成字符行很节省体积:. 表示空格,0-9 和 A-F 表示最多 16 种颜色。但这种格式的危险也很直接------少一个字符、混入一个全角符号,运行时仍可能继续解析,直到编号图出现错位、统计数不一致或某一行突然变短。
这篇文章给《拼豆制图》的 PatternAssetCharts 增加一层数据入口校验。目标不是等 ArkUI 渲染异常后再排查,而是在资源进入 Harmony os 应用时就给出图纸 ID、行号、列号和错误原因。

一、压缩格式必须先写成契约
当前资源模型很简洁:
ts
export interface AssetChart {
colors: string[];
previewRows: string[];
chartRows: string[];
}
仅靠接口无法表达以下约束:
chartRows不得为空;- 每一行宽度必须相同;
- 字符只能来自
.、0-9、A-F; - token 对应的颜色索引不能超过
colors.length - 1; - 颜色必须是六位十六进制值;
- 预览宽高要与约定一致。
这些规则应该由一个入口统一执行,而不是散落在渲染代码中。
二、为校验结果保留位置上下文
直接抛出"资源错误"对 50 张图纸几乎没有帮助。定义结构化问题:
ts
export interface AssetIssue {
patternId: string;
field: string;
row: number;
col: number;
message: string;
}
export interface AssetValidationResult {
valid: boolean;
issues: AssetIssue[];
}
行列从 0 还是 1 开始要统一。对开发日志建议展示 1 基坐标,和编辑器行号更接近。

三、第一关:空数组与矩阵宽度
ts
private static validateRows(
patternId: string,
field: string,
rows: string[],
issues: AssetIssue[]
): void {
if (rows.length === 0) {
issues.push({ patternId, field, row: 0, col: 0, message: '矩阵为空' });
return;
}
const expectedWidth = rows[0].length;
if (expectedWidth === 0) {
issues.push({ patternId, field, row: 1, col: 0, message: '首行为空' });
}
for (let row = 0; row < rows.length; row++) {
if (rows[row].length !== expectedWidth) {
issues.push({
patternId,
field,
row: row + 1,
col: rows[row].length,
message: `行宽应为 ${expectedWidth},实际为 ${rows[row].length}`
});
}
}
}
当前 rowWidth() 只读取第一行,后续短行不会自动补齐。提前拒绝不规则矩阵,可以防止 width × height 与格子数量失配。
四、第二关:token 字符集
ts
private static validateTokens(
patternId: string,
field: string,
rows: string[],
colorCount: number,
issues: AssetIssue[]
): void {
for (let row = 0; row < rows.length; row++) {
for (let col = 0; col < rows[row].length; col++) {
const token = rows[row].charAt(col);
if (token === '.') {
continue;
}
const index = '0123456789ABCDEF'.indexOf(token);
if (index < 0) {
issues.push({ patternId, field, row: row + 1, col: col + 1,
message: `非法字符 ${token}` });
} else if (index >= colorCount) {
issues.push({ patternId, field, row: row + 1, col: col + 1,
message: `颜色索引 ${index} 超出调色板` });
}
}
}
}
实现时不要把未知字符静默当成空格。静默降级会让错误图纸看起来"只是少了几颗豆",反而更难定位。
五、错误位置统一转换成人类坐标
数组下标从 0 开始,而资源编辑人员习惯从第 1 行、第 1 列定位。可以把坐标转换集中到一个入口,避免某条规则报 0 基坐标、另一条规则报 1 基坐标:
ts
private static addIssue(
issues: AssetIssue[],
patternId: string,
field: string,
rowIndex: number,
columnIndex: number,
message: string
): void {
issues.push({
patternId: patternId,
field: field,
row: rowIndex + 1,
col: columnIndex + 1,
message: message
});
}
这样日志中的 第 12 行第 8 列 可以直接对应文本编辑器中的位置,不需要修复人员每次手动加一。自动化测试则继续向 addIssue() 传入数组下标,内部语义保持清楚。
六、第三关:颜色格式与重复色号
ts
private static validateColors(patternId: string, colors: string[], issues: AssetIssue[]): void {
const seen = new Set<string>();
for (let i = 0; i < colors.length; i++) {
const value = colors[i].toUpperCase();
if (!/^#[0-9A-F]{6}$/.test(value)) {
issues.push({ patternId, field: 'colors', row: i + 1, col: 0,
message: `颜色格式错误:${colors[i]}` });
continue;
}
if (seen.has(value)) {
issues.push({ patternId, field: 'colors', row: i + 1, col: 0,
message: `重复颜色:${value}` });
}
seen.add(value);
}
}
重复颜色不一定导致崩溃,但会让两个色号视觉相同,用户备料时很容易误判,因此值得作为资源问题暴露出来。
七、统一校验入口
ts
static validate(patternId: string, asset: AssetChart): AssetValidationResult {
const issues: AssetIssue[] = [];
AssetChartValidator.validateColors(patternId, asset.colors, issues);
AssetChartValidator.validateRows(patternId, 'previewRows', asset.previewRows, issues);
AssetChartValidator.validateRows(patternId, 'chartRows', asset.chartRows, issues);
AssetChartValidator.validateTokens(patternId, 'previewRows', asset.previewRows,
asset.colors.length, issues);
AssetChartValidator.validateTokens(patternId, 'chartRows', asset.chartRows,
asset.colors.length, issues);
return { valid: issues.length === 0, issues };
}

Repository 只能接收 valid 的资源。开发构建可以直接抛错;线上构建可以隐藏损坏条目并记录 ID,但不应继续把坏矩阵交给详情页。
八、批量资源校验
50 张资源适合一次性遍历:
ts
const ids = PatternAssetCharts.ids();
const allIssues: AssetIssue[] = [];
for (let i = 0; i < ids.length; i++) {
const asset = PatternAssetCharts.get(ids[i]);
if (asset !== null) {
allIssues.push(...AssetChartValidator.validate(ids[i], asset).issues);
}
}
expect(allIssues).assertEqual([]);
若数据由脚本生成,还应让生成脚本和 ArkTS 测试使用同一套 token 契约,避免两端规则逐渐偏离。
九、常见问题与修复
| 问题 | 表现 | 修复 |
|---|---|---|
| 未知字符当空格 | 图案缺豆但不报错 | 非 . 的未知字符必须失败 |
| 只校验首行宽度 | 中间行错位 | 遍历每一行比较宽度 |
| token 合法但无对应颜色 | 某色块变白 | 对比索引与调色板长度 |
颜色字符串解析出 NaN |
导出颜色异常 | 入口校验六位十六进制 |
问题信息要优先服务于资源修复人员。与其只写"图纸非法",不如给出图纸 ID、矩阵字段、行列位置、实际字符和允许范围。信息越接近源数据,越不需要在页面、仓库和资源文件之间反复定位。
十、验证清单
- 空矩阵会返回明确问题。
- 任意短行都能定位到具体行号。
- 全角数字、空格和小写外字符不会被吞掉。
- 超出调色板的 token 会被拒绝。
- 每个问题包含图纸 ID 与字段名。
- 批量资源测试在新增图纸时自动执行。
提交新资源前应同时跑单张边界用例和 50 张批量用例。单张用例负责证明每条规则能够报出预期位置,批量用例负责证明现有资源整体闭合;两者结合,才不会出现规则本身写错却被全量数据"恰好通过"的情况。
十一、总结
字符矩阵压缩了资源体积,也把很多错误推迟到了运行时。为宽度、字符集、调色板和颜色格式建立统一入口校验,Harmony os 应用才能把"某张图看起来不对"变成一条可定位、可复现、可阻断的问题记录。
标签:Harmony os、ArkTS、数据校验、字符矩阵、资源工程