4. Redis Value Schema 定义实战:String 与 Hash 约束

契约校验的核心在于对 Redis Value 的结构与内容进行精确描述。本章将聚焦于两种最常用的数据类型——String 和 Hash,通过定义具体的 Schema 规则,实现从“存什么”到“怎么存”的规范化控制。

4.1 String 类型约束:长度与正则

String 是 Redis 中最基础的值类型,但实际业务中往往需要限制其内容格式。例如:手机号必须是 11 位数字、验证码必须是 6 位数字、用户昵称长度在 2-20 个字符之间。通过 Schema 定义长度与正则约束,可以在写入前完成校验。

4.1.1 长度约束定义

长度约束用于限制 String 值的字符数量(非字节数)。支持 min 和 max 两个参数,分别表示最小长度和最大长度。

参数 类型 说明 示例
min int 最小字符长度(含) min: 2
max int 最大字符长度(含) max: 20
实战要点:长度约束基于字符数计算,对于中文、Emoji 等 Unicode 字符,一个字符算一个长度。如果业务需要按字节限制,需在正则中额外处理。

4.1.2 正则约束定义

正则约束通过 pattern 字段指定一个正则表达式,用于匹配 String 值的完整内容。正则表达式必须使用 ^ 和 $ 包裹,确保全值匹配。

// 契约 Schema 示例:String 类型约束
{
  "key": "user:phone:{id}",
  "type": "string",
  "constraints": {
    "length": {
      "min": 11,
      "max": 11
    },
    "pattern": "^1[3-9]\\d{9}$"
  }
}
提示:正则表达式中的反斜杠需要转义。例如 \d 在 JSON 中应写为 \\d。建议在定义前使用在线工具验证正则的正确性。

4.1.3 String 约束校验流程

下图展示了 String 类型在写入 Redis 前的完整校验流程:

String 类型约束校验流程 写入请求 长度校验 正则校验 写入 Redis 拒绝写入 通过 不通过

4.2 Hash 类型约束:字段必填与类型

Hash 是 Redis 中用于存储对象类型的数据结构,其内部由多个 field-value 对组成。在实际业务中,Hash 的字段结构往往是固定的,例如用户信息必须包含 name、age、email 等字段。通过 Schema 定义字段的必填与类型约束,可以确保 Hash 数据的完整性。

4.2.1 字段必填约束

字段必填约束通过 required 数组指定,数组中的每个元素对应一个必须存在的 field 名称。当写入或更新 Hash 时,如果缺少任何一个必填字段,校验将拒绝操作。

// 契约 Schema 示例:Hash 字段必填约束
{
  "key": "user:profile:{id}",
  "type": "hash",
  "fields": {
    "name": {
      "type": "string",
      "required": true,
      "constraints": {
        "length": { "min": 2, "max": 50 }
      }
    },
    "age": {
      "type": "integer",
      "required": true,
      "constraints": {
        "range": { "min": 0, "max": 150 }
      }
    },
    "email": {
      "type": "string",
      "required": false,
      "constraints": {
        "pattern": "^[\\w.-]+@[\\w.-]+\\.\\w{2,4}$"
      }
    }
  }
}

4.2.2 字段类型约束

每个字段可以独立定义其数据类型,支持的类型包括:string、integer、float、boolean、json 等。类型约束确保字段值符合预期的数据格式,避免出现“年龄存成字符串”或“邮箱存成数字”等数据污染问题。

类型 说明 校验规则
string 字符串 值必须为字符串类型
integer 整数 值必须为整数,支持负数
float 浮点数 值必须为数字,支持小数
boolean 布尔值 值必须为 true 或 false
json JSON 对象 值必须为合法的 JSON 字符串
注意:Redis 本身不区分数据类型,所有值都以字符串形式存储。类型约束是在契约校验层完成的,校验通过后才会将值写入 Redis。因此,类型约束不会改变 Redis 的存储行为,但能有效防止错误数据进入。

4.2.3 Hash 字段约束校验流程

下图展示了 Hash 类型在写入 Redis 前的字段校验流程:

Hash 字段约束校验流程 Hash 写入请求 字段存在性检查 字段类型校验 写入 Redis 拒绝写入 通过 不通过 校验规则说明: 1. 必填字段缺失 → 直接拒绝写入 2. 字段类型不匹配 → 直接拒绝写入(如 integer 字段收到字符串 "abc")

4.3 实战案例:用户信息契约定义

结合 String 和 Hash 的约束规则,下面给出一个完整的用户信息契约定义示例:

// 用户信息契约定义
{
  "version": "1.0",
  "keys": [
    {
      "key": "user:phone:{id}",
      "description": "用户手机号",
      "type": "string",
      "constraints": {
        "length": { "min": 11, "max": 11 },
        "pattern": "^1[3-9]\\d{9}$"
      }
    },
    {
      "key": "user:profile:{id}",
      "description": "用户详细信息",
      "type": "hash",
      "fields": {
        "name": {
          "type": "string",
          "required": true,
          "constraints": {
            "length": { "min": 2, "max": 20 },
            "pattern": "^[\\u4e00-\\u9fa5a-zA-Z]+$"
          }
        },
        "age": {
          "type": "integer",
          "required": true,
          "constraints": {
            "range": { "min": 0, "max": 150 }
          }
        },
        "email": {
          "type": "string",
          "required": false,
          "constraints": {
            "pattern": "^[\\w.-]+@[\\w.-]+\\.\\w{2,4}$"
          }
        },
        "vip_level": {
          "type": "integer",
          "required": false,
          "constraints": {
            "range": { "min": 0, "max": 5 }
          }
        }
      }
    }
  ]
}
最佳实践:在定义契约时,建议将 String 类型的长度约束和正则约束组合使用。长度约束可以快速过滤明显不合法的值,正则约束则用于精确匹配格式。两者结合能有效减少正则引擎的调用次数,提升校验性能。

4.4 常见错误与避坑指南

  • 正则未使用 ^ 和 $:导致部分匹配通过,例如 \d{11} 会匹配到 "abc12345678901def" 中的数字部分,应改为 ^\d{11}$。
  • Hash 字段类型误判:Redis 中所有值都是字符串,契约校验层需要将字符串转换为目标类型后再校验。例如 "123" 可以转为 integer,但 "abc" 不能。
  • 必填字段与默认值混淆:必填字段要求写入时必须提供,与字段是否有默认值无关。如果业务需要默认值,应在应用层处理,而非契约层。
  • 长度单位混淆:长度约束基于字符数,而非字节数。对于包含中文的字符串,一个汉字算一个字符,但在 Redis 中实际占用 3 个字节(UTF-8 编码)。