仓颉三方库实战教程:validator4cj 项目全解析
📚 目录
📖 项目概述
🤔 什么是 validator4cj?
validator4cj 是一个基于仓颉编程语言开发的字符串验证和清理库,完整迁移自 JavaScript 的 validator.js 库。该项目展示了如何:
- 将成熟的 JavaScript 库迁移到仓颉语言
- 设计类型安全的 API
- 编写完整的单元测试
- 构建可发布的仓颉三方库
✨ 项目特点
- 📊 100+ 个验证函数:涵盖邮箱、URL、IP、日期、数字、身份证等多种格式验证
- 🔒 类型安全:充分利用仓颉语言的强类型系统
- 📦 模块化设计:每个验证函数独立文件,便于维护
- ✅ 完整测试:32 个测试文件,按 HLT/LLT 分类
- 🌍 标准兼容:遵循 ISO、RFC 等国际标准
🛠️ 环境准备
1️⃣ 安装仓颉语言工具链
确保已安装仓颉语言编译器和包管理器:
# 检查 cjc 版本
cjc --version
# 检查 cjpm 版本
cjpm --version
2️⃣ 克隆项目
git clone https://gitcode.com/cj-awaresome/validator4cj.git
cd validator4cj
3️⃣ 初始化依赖
cjpm update
📁 项目结构分析
📂 目录结构
validator4cj/
├── cjpm.toml # 包配置文件
├── README.md # 项目说明
├── CHANGELOG.md # 版本变更记录
├── LICENSE # 开源协议
├── src/ # 源代码目录
│ ├── core.cj # 包声明
│ ├── main.cj # 入口文件
│ ├── util_string.cj # 工具函数
│ ├── is*.cj # 验证函数(80+ 个)
│ ├── to*.cj # 转换函数(4 个)
│ ├── trim.cj # 字符串修剪
│ ├── escape.cj # HTML 转义
│ ├── contains.cj # 字符串包含
│ ├── equals.cj # 字符串相等
├── test/ # 测试目录(按 HLT/LLT 分类)
│ ├── HLT/ # 高级测试
│ └── LLT/ # 低级测试
└── doc/ # 文档目录
├── design.md # 设计文档
├── feature_api.md # API 文档
└── TUTORIAL.md # 本教程
⚙️ 包配置 (cjpm.toml)
[package]
cjc-version = "1.0.3"
name = "validator4cj"
description = "nothing here"
version = "1.0.0"
target-dir = ""
src-dir = ""
output-type = "static"
compile-option = ""
override-compile-option = ""
link-option = ""
package-configuration = {}
🔑 关键配置说明:
output-type = "static":编译为静态库,允许其他包导入name = "validator4cj":包名,用于导入version = "1.0.0":版本号
💻 核心功能实现
1️⃣ 包声明 (core.cj)
所有源文件必须以包声明开始:
package validator4cj
2️⃣ 简单验证函数实现
以 equals 函数为例:
package validator4cj
func equals(str: String, comparison: String): Bool {
return str == comparison
}
💡 设计要点:
- 函数名清晰表达功能
- 参数类型明确
- 返回值类型明确(
Bool)
3️⃣ 带选项的验证函数实现
以 isEmail 函数为例,展示如何设计带选项的验证函数:
📝 步骤 1:定义选项类
package validator4cj
class EmailOptions {
let allowDisplayName: Bool
let requireDisplayName: Bool
let allowUtf8LocalPart: Bool
let requireTld: Bool
let allowUnderscores: Bool
let blacklistedChars: String
let ignoreMaxLength: Bool
let domainSpecificValidation: Bool
let allowIpDomain: Bool
let hostBlacklist: Array<String>
let hostWhitelist: Array<String>
public init(
allowDisplayName: Bool, requireDisplayName: Bool,
allowUtf8LocalPart: Bool, requireTld: Bool,
allowUnderscores: Bool, blacklistedChars: String,
ignoreMaxLength: Bool, domainSpecificValidation: Bool,
allowIpDomain: Bool, hostBlacklist: Array<String>,
hostWhitelist: Array<String>
) {
this.allowDisplayName = allowDisplayName
this.requireDisplayName = requireDisplayName
this.allowUtf8LocalPart = allowUtf8LocalPart
this.requireTld = requireTld
this.allowUnderscores = allowUnderscores
this.blacklistedChars = blacklistedChars
this.ignoreMaxLength = ignoreMaxLength
this.domainSpecificValidation = domainSpecificValidation
this.allowIpDomain = allowIpDomain
this.hostBlacklist = hostBlacklist
this.hostWhitelist = hostWhitelist
}
}
💡 设计要点:
- 使用
class封装配置选项 - 所有字段使用
let声明(不可变) - 提供
public init构造函数
📝 步骤 2:实现验证函数
package validator4cj
func isEmail(str: String, options: EmailOptions): Bool {
// 1. 基本格式检查
if (str == "") { return false }
// 2. 检查是否包含 @ 符号
if (!contains(str, "@")) { return false }
// 3. 分割本地部分和域名部分
let parts = splitByAt(str)
if (parts.size != 2) { return false }
let localPart = parts[0]
let domain = parts[1]
// 4. 验证本地部分
if (!isValidLocalPart(localPart, options)) {
return false
}
// 5. 验证域名部分
if (!isValidDomain(domain, options)) {
return false
}
// 6. 检查黑名单和白名单
if (options.hostBlacklist.size > 0) {
if (arrayIncludes(options.hostBlacklist, toLower(domain))) {
return false
}
}
return true
}
4️⃣ 字符串处理技巧
🔤 使用 Rune 处理 Unicode
仓颉语言使用 Rune 类型处理 Unicode 字符:
// 字符串转 Rune 数组
let runes = s.toRuneArray()
// 遍历字符串中的字符
for (r in s.runes()) {
if (r >= r'0' && r <= r'9') {
// 处理数字字符
}
}
// 获取字符数量
let count = s.runes() |> count
✂️ 字符串切片操作
let s = "Hello World"
// 从索引 2 到末尾
let sub1 = s[2..] // "llo World"
// 从开头到索引 5(不包含 5)
let sub2 = s[..5] // "Hello"
// 从索引 2 到索引 5(不包含 5)
let sub3 = s[2..5] // "llo"
// 从索引 2 到索引 5(包含 5)
let sub4 = s[2..=5] // "llo "
5️⃣ 类型转换函数实现
以 toInt 为例:
package validator4cj
import std.convert.*
func toInt(s: String): Int64 {
// 处理空字符串
if (s == "") { return 0 }
// 处理前导 + 号
var input = s
if (s[0..1] == "+") {
input = s[1..]
if (input == "") { return 0 }
}
// 验证格式
var hasDigit = false
var i: Int64 = 0
if (input != "" && input[0..1] == "-") { i = 1 }
while (i < input.toRuneArray().size) {
let r = input.toRuneArray()[i]
if (r >= r'0' && r <= r'9') {
hasDigit = true
} else {
return 0 // 包含非数字字符
}
i += 1
}
if (!hasDigit) { return 0 }
// 尝试解析
return Int64.parse(input)
}
💡 设计要点:
- 失败时返回安全的默认值(
0) - 处理边界情况(空字符串、前导符号等)
- 先验证格式再解析,避免异常
6️⃣ 使用 Option 类型
仓颉语言使用 Option<T> 处理可选值:
package validator4cj
class IsIntOptions {
let allowLeadingZeroes: Bool
let min: Option<Int64>
let max: Option<Int64>
let lt: Option<Int64>
let gt: Option<Int64>
public init(
allowLeadingZeroes: Bool,
min: Option<Int64>,
max: Option<Int64>,
lt: Option<Int64>,
gt: Option<Int64>
) {
this.allowLeadingZeroes = allowLeadingZeroes
this.min = min
this.max = max
this.lt = lt
this.gt = gt
}
}
func isInt(s: String, options: IsIntOptions): Bool {
// ... 验证逻辑 ...
// 使用 Option 类型
if (options.min.isSome()) {
let minVal = options.min.getOrThrow()
if (value < minVal) { return false }
}
if (options.max.isSome()) {
let maxVal = options.max.getOrThrow()
if (value > maxVal) { return false }
}
return true
}
🧪 测试编写
🔧 测试框架
使用仓颉语言的 std.unittest 框架:
package validator4cj.test
import std.unittest.*
import std.unittest.testmacro.*
import validator4cj.*
📊 测试分类
🔝 HLT (High Level Test) - 高级测试
测试复杂验证函数,位于 test/HLT/ 或 src/test/HLT_*.cj:
package validator4cj.test
import std.unittest.*
import std.unittest.testmacro.*
import validator4cj.*
@Test
class TestIsEmail {
// 辅助函数:创建默认选项
func defaultEmailOptions(): EmailOptions {
let emptyArray: Array<String> = []
return EmailOptions(
false, // allowDisplayName
false, // requireDisplayName
false, // allowUtf8LocalPart
true, // requireTld
false, // allowUnderscores
"", // blacklistedChars
true, // ignoreMaxLength
false, // domainSpecificValidation
false, // allowIpDomain
emptyArray, // hostBlacklist
emptyArray // hostWhitelist
)
}
@TestCase
func testValidEmails() {
let opts = defaultEmailOptions()
@Assert(isEmail("foo@bar.com", opts) == true)
@Assert(isEmail("test@gmail.com", opts) == true)
@Assert(isEmail("user+tag@example.com", opts) == true)
}
@TestCase
func testInvalidEmails() {
let opts = defaultEmailOptions()
@Assert(isEmail("invalid.com", opts) == false)
@Assert(isEmail("@invalid.com", opts) == false)
@Assert(isEmail("user@", opts) == false)
}
@TestCase
func testEmailWithOptions() {
let opts = defaultEmailOptions()
// 测试特定选项
@Assert(isEmail("foo@bar.com", opts) == true)
}
}
🔻 LLT (Low Level Test) - 低级测试
测试简单函数,位于 test/LLT/ 或 src/test/LLT_*.cj:
package validator4cj.test
import std.unittest.*
import std.unittest.testmacro.*
import validator4cj.*
@Test
class TestTrim {
@TestCase
func testTrimWhitespace() {
// 使用 None 表示默认空白字符
@Assert(trim(" \r\n\tfoo \r\n\t ", None) == "foo")
@Assert(trim(" \r", None) == "")
}
@TestCase
func testTrimCustomChars() {
@Assert(trim("010100201000", Some("01")) == "2")
@Assert(ltrim("010100201000", Some("01")) == "201000")
@Assert(rtrim("010100201000", Some("01")) == "0101002")
}
}
📋 测试注解说明
@Test:标识测试类@TestCase:标识测试用例方法@Assert:断言测试结果
▶️ 运行测试
cd validator4cj
cjpm test
🔨 编译与构建
🏗️ 编译项目
cjpm build
🧹 清理构建产物
cjpm clean
⚡ 增量编译
cjpm build -i
🐛 调试模式编译
cjpm build -g
⚠️ 查看编译警告
cjpm build 2>&1 | grep -i warning
📦 发布与使用
1️⃣ 作为库使用
📥 在其他项目中引用
在 cjpm.toml 中添加依赖:
[dependencies]
validator4cj = { path = "../validator4cj" }
或在项目中使用:
import validator4cj.*
main() {
let emptyArray: Array<String> = []
let opts = EmailOptions(
false, false, false, true, false, "", true, false, false,
emptyArray, emptyArray
)
if (isEmail("user@example.com", opts)) {
println("Valid email")
}
}
2️⃣ 导出函数
所有 package validator4cj 下的公共函数都可以被外部导入。确保:
- 函数使用
public修饰符(如果需要) - 类使用
public修饰符 - 选项类的构造函数使用
public init
3️⃣ 版本管理
在 CHANGELOG.md 中记录版本变更:
# [1.0.0] - 2025-11-05
⭐ 最佳实践
1️⃣ 代码组织
- 一个文件一个函数:每个验证函数独立文件,便于维护
- 工具函数集中:公共工具函数放在
util_string.cj - 测试分类清晰:按 HLT/LLT 分类测试
2️⃣ 命名规范
- 函数名:使用动词开头,如
isEmail,toInt,trim - 类名:使用名词,首字母大写,如
EmailOptions,UrlOptions - 变量名:使用小写字母和下划线,如
local_part,domain_name
3️⃣ 错误处理
- 验证函数:返回
Bool,不抛出异常 - 转换函数:失败时返回安全的默认值
- 边界检查:始终检查空字符串、空数组等边界情况
4️⃣ 性能优化
- 避免重复计算:缓存字符串长度等计算结果
- 提前返回:发现错误立即返回,避免不必要的计算
- 使用高效算法:选择合适的字符串匹配算法
5️⃣ 文档编写
- 函数注释:说明参数、返回值、异常情况
- 示例代码:提供清晰的使用示例
- API 文档:维护完整的 API 文档
6️⃣ 测试覆盖
- 边界测试:测试空字符串、空值等边界情况
- 正常测试:测试正常输入
- 异常测试:测试无效输入
- 选项测试:测试不同选项组合
❓ 常见问题
❓ Q1: 如何处理字符串中的 Unicode 字符?
✅ A: 使用 Rune 类型和 runes() 方法:
// 遍历字符串中的字符
for (r in s.runes()) {
// 处理每个字符
}
// 转换为 Rune 数组
let runes = s.toRuneArray()
❓ Q2: 如何实现可选参数?
✅ A: 使用 Option<T> 类型:
func myFunction(str: String, optional: Option<String>): Bool {
if (optional.isSome()) {
let value = optional.getOrThrow()
// 使用 value
}
// ...
}
❓ Q3: 如何避免编译错误?
✅ A:
- 确保所有函数都有明确的返回类型
- 检查
Option类型的使用(使用isSome()和getOrThrow()) - 处理所有可能的代码路径
❓ Q4: 测试无法导入主包怎么办?
✅ A: 确保 cjpm.toml 中 output-type = "static":
[package]
output-type = "static"
❓ Q5: 如何调试编译错误?
✅ A:
- 使用
cjpm build -g编译调试版本 - 查看编译输出的错误信息
- 检查类型匹配和函数签名
❓ Q6: 如何优化字符串操作性能?
✅ A:
- 使用字符串切片而不是字符串拼接
- 缓存字符串长度
- 使用
toRuneArray()进行多次访问
🎯 实战演练
📝 练习 1: 实现一个简单的验证函数
实现 isEven 函数,验证字符串是否为偶数:
package validator4cj
import std.convert.*
func isEven(str: String): Bool {
let value = toInt(str)
return value % 2 == 0
}
📝 练习 2: 实现带选项的验证函数
实现 isLength 函数,验证字符串长度:
package validator4cj
class LengthOptions {
let min: Option<Int64>
let max: Option<Int64>
public init(min: Option<Int64>, max: Option<Int64>) {
this.min = min
this.max = max
}
}
func isLength(str: String, options: LengthOptions): Bool {
let len = str.runes() |> count
if (options.min.isSome()) {
if (len < options.min.getOrThrow()) {
return false
}
}
if (options.max.isSome()) {
if (len > options.max.getOrThrow()) {
return false
}
}
return true
}
📝 练习 3: 编写测试用例
为 isLength 函数编写测试:
package validator4cj.test
import std.unittest.*
import std.unittest.testmacro.*
import validator4cj.*
@Test
class TestIsLength {
@TestCase
func testValidLength() {
let opts = LengthOptions(Some(5), Some(10))
@Assert(isLength("Hello", opts) == true)
@Assert(isLength("Hello World", opts) == true)
}
@TestCase
func testInvalidLength() {
let opts = LengthOptions(Some(5), Some(10))
@Assert(isLength("Hi", opts) == false)
@Assert(isLength("Hello World!", opts) == false)
}
}
📝 总结
通过 validator4cj 项目,我们学习了:
- 项目结构设计:如何组织仓颉三方库的代码结构
- 类型安全编程:利用仓颉语言的强类型系统
- 选项模式:使用类封装配置选项
- 测试编写:使用
std.unittest编写完整的测试套件 - 字符串处理:使用
Rune正确处理 Unicode - 错误处理:设计安全的错误处理策略
- 最佳实践:遵循仓颉语言的编程规范
📚 参考资源
祝你编码愉快! 🚀
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)