Appearance
GraphQL应用
本文档引用文件
- usr.graphql.ts
- graphql.config.js
- lib/graphql.ts
- src/graphql.ts
- base/graphql.ts
- gen/graphql.ts
- base/graphql.ts
目录
项目结构
本项目采用分层模块化架构,GraphQL相关实现主要集中在deno目录下。核心结构包括:
codegen/__out__/deno/gen/base/:存放由代码生成工具自动生成的GraphQL模式文件(.graphql.ts),每个业务实体(如用户、角色、组织等)均有独立的GraphQL定义。deno/gen/:包含对所有生成模块的统一导入入口。deno/src/:包含自定义业务模块的GraphQL定义。deno/lib/:包含基础服务(如OSS、App)的GraphQL定义。- 配置文件
graphql.config.js用于指定GraphQL模式的扫描路径。
该结构实现了自动生成与手动扩展的良好分离,便于维护和扩展。
图示来源
本节来源
GraphQL模式定义
GraphQL模式(Schema)通过TypeScript文件中的字符串模板定义,使用defineGraphql函数注册解析器和类型。以用户(Usr)为例,其模式定义包含:
- 标量类型:
UsrId,用于标识用户唯一ID。 - 枚举类型:
UsrType,定义用户类型(登录用户、第三方接口)。 - 对象类型:
UsrModel,描述用户实体的所有字段,包含ID、用户名、角色、组织等信息,并附带中文注释。 - 输入类型:
UsrInput和UsrSearch,分别用于创建/更新操作和查询条件过滤。 - 查询类型:
Query,定义数据读取操作,如findAllUsr(分页查询)、findByIdUsr(根据ID查询)等。 - 变更类型:
Mutation,定义数据写入操作,如createsUsr(批量创建)、updateByIdUsr(更新)、deleteByIdsUsr(删除)等。
所有模式定义均通过defineGraphql(resolver, /* GraphQL */ ...)语法与对应的解析器绑定。
本节来源
解析器与数据流
GraphQL解析器(Resolver)位于*.resolver.ts文件中,与.graphql.ts文件同目录。当GraphQL请求到达时,执行流程如下:
- 请求通过GraphQL网关(由
lib/oak/gql.ts提供)接收。 - 根据请求中的操作类型(Query/Mutation)和字段名,查找对应的解析器函数。
- 解析器函数调用DAO层(
*.dao.ts)与数据库交互,获取或修改数据。 - DAO层返回原始数据,解析器将其封装为
UsrModel等GraphQL类型对象。 - 结果通过GraphQL响应返回给客户端。
例如,findAllUsr查询会调用usr.resolver.ts中的对应函数,该函数接收search、page、sort参数,委托usr.dao.ts执行分页SQL查询,并将结果集转换为UsrModel数组。
图示来源
架构集成与模块化设计
项目通过多层import语句实现GraphQL模式的自动聚合,形成清晰的依赖树:
- 最底层:每个实体的
*.graphql.ts文件定义其自身的GraphQL类型和解析器。 - 中间层:
deno/gen/base/graphql.ts导入所有由代码生成器生成的模块。deno/src/base/graphql.ts导入所有自定义业务模块。
- 上层:
deno/gen/graphql.ts导入gen/base/graphql.ts。deno/src/graphql.ts导入src/base/graphql.ts。
- 顶层:
deno/lib/graphql.ts统一导入/gen/graphql.ts、/src/graphql.ts以及/lib/oss/oss.graphql.ts等服务模块。
最终,lib/graphql.ts作为单一入口,被GraphQL服务器加载,从而注册所有模式。graphql.config.js中的schema配置指定了模式文件的扫描路径,确保开发工具(如IDE、代码生成器)能正确识别。
本节来源
查询与变更操作详解
查询操作 (Query)
findCountUsr(search: UsrSearch): 根据搜索条件返回用户总数,用于分页计算总页数。findAllUsr(search: UsrSearch, page: PageInput, sort: [SortInput!]): 核心分页查询接口,返回用户列表。PageInput包含页码和页大小,SortInput定义排序字段和方向。findOneUsr(search: UsrSearch, sort: [SortInput!]): 返回满足条件的第一个用户。findByIdUsr(id: UsrId!): 根据唯一ID精确查询单个用户。findByIdsUsr(ids: [UsrId!]!): 根据ID列表批量查询用户。findLastOrderByUsr: 查询order_by字段的最大值,用于新记录的排序定位。
变更操作 (Mutation)
createsUsr(inputs: [UsrInput!]!, unique_type: UniqueType): 批量创建用户,支持唯一性检查策略。updateByIdUsr(id: UsrId!, input: UsrInput!): 根据ID更新单个用户信息。deleteByIdsUsr(ids: [UsrId!]!): 标记删除(软删除)指定ID的用户。enableByIdsUsr(ids: [UsrId!]!, is_enabled: Int!): 批量启用或禁用用户。lockByIdsUsr(ids: [UsrId!]!, is_locked: Int!): 批量锁定或解锁用户。revertByIdsUsr(ids: [UsrId!]!): 还原已删除的用户。forceDeleteByIdsUsr(ids: [UsrId!]!): 从数据库中彻底删除用户(硬删除)。
本节来源
类型系统与输入验证
GraphQL类型系统提供了强类型保障:
- 标量类型:除了内置的
String、Int、Boolean,项目定义了UsrId、NaiveDateTime等自定义标量,确保ID格式和时间格式的统一。 - 枚举类型:
UsrType等枚举限制了字段的合法取值范围,避免无效数据。 - 输入对象:
UsrInput和UsrSearch结构化了客户端传参,UsrSearch提供了丰富的查询条件(如_like模糊匹配、_is_null空值判断、数组包含等),支持复杂业务场景。 - 非空约束:使用
!符号(如UsrId!)明确标识必填字段,提升API的健壮性。
虽然文档未直接展示,但结合lib/validators目录,可推断项目在解析器或DAO层对输入进行了进一步的业务规则验证(如邮箱格式、密码强度等)。
本节来源
自动生成机制分析
项目通过codegen模块实现GraphQL模式的自动化生成,其工作流程如下:
- 源数据:基于数据库表结构(可能在
codegen/src/tables/中定义)或配置文件。 - 代码生成:运行
codegen.ts脚本,读取源数据,应用模板(codegen/src/template/)。 - 输出:生成
codegen/__out__/deno/gen/base/下的*.model.ts、*.dao.ts、*.service.ts、*.resolver.ts和*.graphql.ts文件。 - 集成:生成的
*.graphql.ts文件被gen/base/graphql.ts自动导入,最终通过lib/graphql.ts集成到应用中。
这种机制极大地减少了重复性代码,保证了数据模型、DAO、GraphQL接口之间的一致性,当数据库结构变更时,只需重新生成代码即可同步更新API。
本节来源
实际使用示例
查询用户列表(分页)
graphql
query GetUsers($search: UsrSearch, $page: PageInput) {
findAllUsr(search: $search, page: $page) {
id
lbl
username
role_ids_lbl
org_ids_lbl
is_enabled_lbl
create_time_lbl
}
}变量 (Variables):
json
{
"search": {
"username_like": "admin",
"is_enabled": [1]
},
"page": {
"page_no": 1,
"page_size": 10
}
}创建用户
graphql
mutation CreateUsers($inputs: [UsrInput!]!) {
createsUsr(inputs: $inputs) {
id
}
}变量 (Variables):
json
{
"inputs": [
{
"lbl": "张三",
"username": "zhangsan",
"password": "encrypted_password",
"role_ids": ["role_admin"],
"org_ids": ["org_001"],
"default_org_id": "org_001",
"type": "login"
}
]
}更新用户状态
graphql
mutation EnableUsers($ids: [UsrId!]!, $isEnabled: Int!) {
enableByIdsUsr(ids: $ids, is_enabled: $isEnabled)
}变量 (Variables):
json
{
"ids": ["usr_001", "usr_002"],
"isEnabled": 1
}本节来源