You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建符合CAS登记号完整校验规则的TypeScript自定义类型

如何创建符合CAS登记号完整校验规则的TypeScript自定义类型

我明白你想实现一个严格贴合CAS登记号规则的TypeScript自定义类型——既要卡准格式、长度这些基础要求,还要能校验关键的校验和,这确实有点挑战,毕竟TypeScript的类型系统是静态的,要做这种计算型校验得花点巧思。先理清楚核心问题,再一步步解决:

先明确CAS的核心规则(避免走偏)

先把CAS的校验标准再梳理一遍,确保我们的类型能覆盖所有要求:

  • 整体格式:XXX-XX-X,其中:
    • 第一部分:2-7位数字,不能以0开头
    • 第二部分:固定2位数字,允许前导零甚至全零(比如50-00-0是合法的)
    • 第三部分:1位校验和数字,需通过前两部分计算得出
  • 校验和计算逻辑:
    1. 拼接第一、第二部分为完整字符串(比如151-21 → 15121)
    2. 反转这个字符串(15121 → 12151)
    3. 每个字符乘以它的位置序号(从1开始),求和后取模10,结果必须等于第三部分

现有代码的问题分析

你之前用Range生成数字类型的思路有两个明显的坑:

  1. 类型爆炸导致编译错误:Range<0, 9999999>会生成近千万个数字类型,TypeScript直接扛不住,报2589错误是必然的
  2. 无法处理字符串前导零:比如CAS第二部分的01作为数字会被转成1,但CAS要求它必须是两位字符串,数字类型无法保留前导零
  3. 校验和完全没覆盖:用Range只能限制数字范围,根本做不了计算型的校验

解决方案:用字符串模板类型+递归类型实现全校验

我们换个思路,直接在字符串层面做约束和计算,完全避开数字类型的缺陷:

第一步:定义基础辅助类型

先定义最基础的数字字符类型,方便后续组合:

type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type NonZeroDigit = Exclude<Digit, '0'>;

第二步:格式与长度的静态约束

用模板字符串类型直接定义各部分的格式,精准控制长度和前导零规则:

// 第一部分:2-7位数字,不能以0开头
type CASPartA = `${NonZeroDigit}${Digit}${Digit}?${Digit}?${Digit}?${Digit}?${Digit}?`;
// 解释:1个非零数字 + 1-6个可选数字 → 总长度2-7,且首字符非零

// 第二部分:固定2位数字,允许前导零/全零
type CASPartB = `${Digit}${Digit}`;

// 第三部分:1位数字(校验和)
type CASPartC = Digit;

第三步:实现类型层面的校验和计算

这是最核心的部分,我们用递归类型实现字符串拼接、反转、求和取模:

// 辅助类型:拼接两个字符串
type Concat<A extends string, B extends string> = `${A}${B}`;

// 辅助类型:反转字符串(递归实现)
type Reverse<S extends string, Acc extends string = ''> = 
  S extends `${infer First}${infer Rest}` 
    ? Reverse<Rest, `${First}${Acc}`> 
    : Acc;

// 辅助类型:计算反转后的字符串的校验和(递归遍历每个字符,乘位置求和)
type CalculateChecksum<
  S extends string, 
  Position extends number = 1, 
  Sum extends number = 0
> = 
  S extends `${infer D}${infer Rest}` 
    ? CalculateChecksum<
        Rest, 
        // 位置+1:用数组长度模拟数字递增
        [...Array<Position>, 0]['length'], 
        // 求和:当前数字转成数字 + 之前的和
        Sum + (D extends `${infer N extends number}` ? N * Position : 0)
      > 
    : // 取模10,转成字符串类型
      `${Sum extends infer T ? T extends number ? T % 10 : never : never}`;

第四步:组合成完整的ValidCAS类型

最后把所有约束组合起来,只允许符合校验和的CAS字符串:

// 先定义符合格式的CAS字符串类型(未校验校验和)
type CASFormat = `${CASPartA}-${CASPartB}-${CASPartC}`;

// 核心类型:从CASFormat中筛选出校验和合法的字符串
type ValidCAS<T extends string = CASFormat> = 
  T extends `${infer A}-${infer B}-${infer C}` 
    ? CalculateChecksum<Reverse<Concat<A, B>>> extends C 
      ? T 
      : never 
    : never;

测试验证

现在用你的测试用例试试,完全符合预期:

// 合法CAS:不会报错
const validCAS1: ValidCAS = '151-21-3';
const validCAS2: ValidCAS = '50-00-0';
const validCAS3: ValidCAS = '134842-07-2';
const validCAS4: ValidCAS = '7664-93-9';

// 不合法CAS:编译报错(符合预期)
const invalidCAS1: ValidCAS = '6123-1-1'; // 第二部分不是2位
const invalidCAS2: ValidCAS = '7647-01-1'; // 校验和错误
const invalidCAS3: ValidCAS = '0000000-00-0'; // 第一部分以0开头
const invalidCAS4: ValidCAS = '600000012-999-1'; // 格式错误(第二部分3位)
const invalidCAS5: ValidCAS = 'abcd-ef-g'; // 非数字字符

关键优势

  1. 避免类型爆炸:用字符串模板代替大范围数字枚举,TypeScript编译速度快很多,不会报2589错误
  2. 精准覆盖所有规则:包括前导零、长度、校验和的全量校验
  3. 编译时校验:所有不合法的CAS都会在编写代码时直接报错,不需要等到运行时

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:57:58