数据库管理
IAM 项目使用 Drizzle ORM 和 PostgreSQL(Neon)进行数据管理。Schema 定义在 packages/db/src/schema/,按职责分为三个模块,由 schema/index.ts 统一导出:
| 模块 | 内容 |
|---|---|
schema/auth.ts |
Better Auth 认证表、OAuth Provider 表、审计与限流表 |
schema/wecom.ts |
企业微信通讯录同步表 |
schema/provisioning.ts |
应用同步连接等 IAM 自有的业务表 |
因此导入时必须带模块子路径,例如 @IAM/db/schema/auth:
import { db } from '@IAM/db'import { user } from '@IAM/db/schema/auth'@IAM/db/schema 这种不带模块的裸路径无法解析,仓库中也没有这种写法。
| 模块 | 表 |
|---|---|
schema/auth.ts |
user、session、account、verification、jwks |
oauth_client、oauth_resource、oauth_client_resource、oauth_client_role |
|
oauth_client_user_access、oauth_client_department_access |
|
oauth_access_token、oauth_refresh_token、oauth_consent、oauth_client_assertion |
|
rate_limit、audit_log、user_profile_audit、app_provisioning_state |
|
schema/wecom.ts |
wecom_department、wecom_user_profile、wecom_user_department、wecom_sync_task |
schema/provisioning.ts |
provisioning_connection、provisioning_reconcile_job、provisioning_user_reconcile_job |
使用 pgTable 定义表。下面是仓库中真实的 provisioning_connection 定义(packages/db/src/schema/provisioning.ts),演示了主键、外键、默认值与唯一索引:
import { boolean, integer, pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
import { oauthClient } from './auth'
export const provisioningConnection = pgTable( 'provisioning_connection', { clientId: text('client_id') .primaryKey() .references(() => oauthClient.clientId, { onDelete: 'cascade' }), enabled: boolean('enabled').notNull().default(false), endpoint: text('endpoint').notNull(), keyId: text('key_id').notNull(), encryptedSecret: text('encrypted_secret').notNull(), version: integer('version').notNull().default(1), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow() }, (table) => [uniqueIndex('provisioning_connection_key_id_uidx').on(table.keyId)])text():文本字段timestamp():时间戳字段boolean():布尔字段integer():整数字段numeric():数值字段
.primaryKey():主键.notNull():非空.default():默认值.unique():唯一约束.references():外键引用
使用 relations 表达表之间的关系。真实示例(schema/auth.ts 中的 session 与 user):
import { relations } from 'drizzle-orm'
export const sessionRelations = relations(session, ({ one, many }) => ({ user: one(user, { fields: [session.userId], references: [user.id] }), oauthRefreshTokens: many(oauthRefreshToken), oauthAccessTokens: many(oauthAccessToken)}))one:一对一或多对一many:一对多或多对多
以下示例使用真实的 user 表。
import { db } from '@IAM/db'import { user } from '@IAM/db/schema/auth'import { eq } from 'drizzle-orm'
// 查询所有记录const allUsers = await db.select().from(user)
// 查询单条记录const [found] = await db.select().from(user).where(eq(user.id, userId))@IAM/db 还导出 db 与 sql,需要写原生 SQL 时使用 db.execute(sql\…`)`。
import { eq, and, or, like } from 'drizzle-orm'
// 等值查询const activeUsers = await db.select().from(user).where(eq(user.status, 'active'))
// 多条件查询const candidates = await db .select() .from(user) .where(and(eq(user.status, 'active'), eq(user.banned, false)))
// 模糊查询const matched = await db .select() .from(user) .where(like(user.name, `%${keyword}%`))await db.insert(provisioningConnection).values({ clientId: 'crm-client-id', endpoint: 'https://crm.example.com/api/internal/iam/provisioning/users', keyId: 'generated-key-id', encryptedSecret: encrypted})
// 插入并返回const [created] = await db.insert(provisioningConnection).values(input).returning()await db .update(provisioningConnection) .set({ enabled: true }) .where(eq(provisioningConnection.clientId, clientId))
// 更新并返回const [updated] = await db .update(provisioningConnection) .set({ enabled: true }) .where(eq(provisioningConnection.clientId, clientId)) .returning()注意 updated_at 上的 $onUpdate 由 Drizzle 在更新语句中写入,绕过 ORM 手写 SQL 时不会自动刷新。
await db.delete(provisioningConnection).where(eq(provisioningConnection.clientId, clientId))外键带 onDelete: 'cascade' 时会随主记录级联删除(例如 provisioning_connection 随 oauth_client 删除)。
import { db } from '@IAM/db'import { session, user } from '@IAM/db/schema/auth'import { eq } from 'drizzle-orm'
const sessionsWithUser = await db .select({ session, user }) .from(session) .innerJoin(user, eq(session.userId, user.id))Schema 与迁移
Section titled “Schema 与迁移”开发、测试和生产环境都应优先使用可提交、可审查的 migration:
# 生成迁移文件pnpm db:generate
# 应用迁移pnpm db:migrate如果调整 Better Auth 插件或认证字段,先固定使用 1.7.1 生成认证 schema:
pnpm dlx auth@1.7.1 generate --yes \ --config packages/auth/src/index.ts \ --output packages/db/src/schema/auth.tspnpm db:generatepnpm db:migrateDATABASE_URL 用于运行时访问;DATABASE_URL_DIRECT 用于 migration/DDL,未配置时 Drizzle 会回退到 DATABASE_URL。迁移文件输出到 packages/db/src/migrations/。
pnpm db:push 会直接修改数据库,可能造成 schema、migration 与真实数据库漂移,只允许在可丢弃的本地数据库临时验证。正式变更必须回到 migration 流程。
数据库管理界面
Section titled “数据库管理界面”使用 Drizzle Studio 可视化管理数据库:
pnpm db:studio这将打开一个 Web 界面,可以查看表结构、浏览和编辑数据、执行 SQL 查询。
- 使用迁移:所有正式变更都生成并提交 migration
- 索引优化:为常用查询字段添加索引
- 关系完整性:使用外键确保数据完整性
- 类型安全:利用 Drizzle 的类型推断
- 查询优化:避免 N+1 查询问题