🚀 关于 ai-data-analyzer
本文档属于开源项目 ai-data-analyzer 的技术文档系列。这是一个专为 AI 数据分析场景打造的全栈解决方案,包含前端可视化、Nest.js 后端服务、数据库设计、AI Agent 集成等完整模块。欢迎 Star、Fork 和贡献代码!
在 AI Agent 应用中,数据是核心资产:原始数据、特征与中间结果、用户请求历史、任务状态、分析产物、评估与审计记录等都需要可靠的持久化。
本节聚焦如何在 Nest.js 后端建立数据基础,介绍两类常见 ORM 方案:TypeORM 与 Prisma,并给出在 Nest.js 中的典型集成方式与注意事项。
2.2.1 为什么使用 ORM?
直接手写 SQL 能获得极高控制力,但在复杂业务中会带来重复、耦合与易错点。ORM(Object-Relational Mapping)提供了一层抽象:用面向对象的方式建模与访问数据,再由 ORM 生成参数化 SQL 并执行。
常见收益:
- 开发效率:CRUD 与查询构建器减少重复劳动
- 类型与结构:与 TypeScript 配合可在编译期发现接口/字段错误
- 可维护性:实体、迁移、仓库/服务层边界清晰
- 安全性:默认参数化查询可显著降低 SQL 注入风险(但仍需避免拼接原始 SQL)
2.2.2 数据库选型(以 PostgreSQL 为例)
AI 数据分析的后端通常需要:
- 大量结构化数据(用户、任务、权限、配置)
- 半结构化数据(模型输入输出、日志、评估指标等)
PostgreSQL 常作为首选之一,原因包括:
- 支持
JSONB、索引与丰富查询能力,适合存放半结构化数据 - 成熟的事务与一致性能力,适合任务状态与审计数据
当然,MySQL、SQLite 也都能胜任部分场景:SQLite 适合本地开发或轻量部署;生产环境常用 PostgreSQL/MySQL。
2.2.3 方案 A:在 Nest.js 中集成 TypeORM
Nest.js 提供官方集成包 @nestjs/typeorm。下面以 PostgreSQL 为例。
步骤 1:安装依赖
bash
pnpm add @nestjs/typeorm typeorm pg @nestjs/config
步骤 2:配置环境变量(示例)
在项目根目录的 .env:
ini
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=your_username
DATABASE_PASSWORD=your_password
DATABASE_NAME=ai_analysis_db
步骤 3:配置数据库连接
推荐使用 ConfigModule + TypeOrmModule.forRootAsync(),避免在模块装配时直接散落读取 process.env,并便于测试与多环境切换。
这段配置写在哪里?
- 最常见(推荐入门) :直接写在后端项目的根模块
src/app.module.ts的imports里。 - 更清晰(项目变大后) :抽到单独的
DatabaseModule(例如src/database/database.module.ts),再由AppModule导入这个模块。本文先用第一种写法,最直观。
ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DATABASE_HOST', 'localhost'),
port: Number(config.get('DATABASE_PORT', 5432)),
username: config.get<string>('DATABASE_USER'),
password: config.get<string>('DATABASE_PASSWORD'),
database: config.get<string>('DATABASE_NAME'),
autoLoadEntities: true,
synchronize: false,
logging: ['error'],
}),
}),
],
})
export class AppModule {}
重要提示:
synchronize: true仅建议用于本地开发/一次性原型验证;生产环境不要使用,避免误删/误改表结构。autoLoadEntities: true会自动加载各模块通过TypeOrmModule.forFeature()注册的实体,避免维护 glob 路径。
步骤 4:使用 Nest CLI 生成资源(推荐)
最专业、高效的方式是使用 Nest CLI 的 generate resource 命令。它不仅会创建文件,还会自动生成模块(Module)、控制器(Controller)、服务(Service)、实体(Entity)和 DTO 的完整骨架,并自动配置依赖注入。
- 运行生成命令:
bash
# 在 backend 目录下运行
pnpm dlx @nestjs/cli g resource analysis-results
交互式提示选择:
- What transport layer do you use? -> 选择
REST API - Would you like to generate CRUD entry points? -> 选择
Y(Yes)
这会自动创建 src/analysis-results/ 目录及所有必要文件,并在 app.module.ts 中自动导入新模块。
- 编写实体代码:
CLI 默认会在 src/analysis-results/entities/ 下生成一个空的实体文件。请将其内容替换为以下代码:
ts
// src/analysis-results/entities/analysis-result.entity.ts
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
@Entity('analysis_results')
export class AnalysisResult {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'text' })
taskName: string;
@Column({ type: 'jsonb', nullable: true })
inputData?: Record<string, unknown>;
@Column({ type: 'jsonb' })
outputData: Record<string, unknown>;
@Column({ type: 'text', nullable: true })
modelId?: string;
@Column({ type: 'varchar', length: 50, default: 'pending' })
status: string;
@CreateDateColumn({ type: 'timestamp with time zone' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamp with time zone' })
updatedAt: Date;
}
注意 :
jsonb类型仅在 PostgreSQL 中可用。
步骤 5:注册实体与实现 CRUD
虽然 CLI 生成了模块,但 TypeORM 需要我们显式注册实体,才能让 Repository 正常工作。
- 在 Feature Module 中注册实体:
打开 src/analysis-results/analysis-results.module.ts,添加 TypeOrmModule.forFeature:
ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AnalysisResultsService } from './analysis-results.service';
import { AnalysisResultsController } from './analysis-results.controller';
import { AnalysisResult } from './entities/analysis-result.entity';
@Module({
imports: [TypeOrmModule.forFeature([AnalysisResult])], // <--- 关键:注册实体
controllers: [AnalysisResultsController],
providers: [AnalysisResultsService],
})
export class AnalysisResultsModule {}
- 在 Service 中注入 Repository:
打开 src/analysis-results/analysis-results.service.ts,注入 Repository 并实现增删改查:
ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AnalysisResult } from './entities/analysis-result.entity';
@Injectable()
export class AnalysisResultsService {
constructor(
@InjectRepository(AnalysisResult)
private readonly analysisResultRepository: Repository<AnalysisResult>,
) {}
async create(data: Partial<AnalysisResult>): Promise<AnalysisResult> {
const entity = this.analysisResultRepository.create(data);
return this.analysisResultRepository.save(entity);
}
async findAll(): Promise<AnalysisResult[]> {
return this.analysisResultRepository.find();
}
}
如果你希望对入参做验证,通常会配合 DTO + class-validator:
bash
pnpm add class-validator class-transformer
2.2.4 数据库迁移(Migrations)实战指南
在生产环境中,严禁使用 synchronize: true,因为它可能导致数据丢失。标准的做法是使用 Migrations(迁移文件) 来管理数据库结构变更。
由于 TypeORM 的 CLI 需要独立的配置源,我们按以下步骤配置,确保新手也能一次跑通。
步骤 1:创建 CLI 专用配置文件
在 src 目录下新建 data-source.ts,这个文件专门给 TypeORM CLI 使用,不影响 NestJS 应用本身的运行。
ts
// src/data-source.ts
import { DataSource } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { join } from 'path';
// 为了让 dotenv 在 TS 环境下安全运行
import 'dotenv/config';
// 手动配置路径
process.env.DOTENV_CONFIG_PATH = join(__dirname, '../../.env');
// 实例化一个临时的 ConfigService 供 TypeORM CLI 使用
const configService = new ConfigService();
export const AppDataSource = new DataSource({
type: 'postgres',
host: configService.get<string>('DATABASE_HOST', 'localhost'),
port: Number(configService.get<number>('DATABASE_PORT', 5432)),
username: configService.get<string>('DATABASE_USER'),
password: configService.get<string>('DATABASE_PASSWORD'),
database: configService.get<string>('DATABASE_NAME'),
// 自动扫描实体文件
entities: [join(__dirname, '**/*.entity{.ts,.js}')],
// 迁移文件存放路径
migrations: [join(__dirname, 'migrations/*{.ts,.js}')],
synchronize: false, // CLI 模式下必须为 false
logging: true,
});
步骤 2:配置 package.json 命令
为了方便使用,我们在 package.json 的 scripts 中添加快捷命令。请将以下内容合并到你的 scripts 字段中:
json
"scripts": {
// ... 其他原有命令
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
"migration:create": "pnpm typeorm migration:create",
"migration:generate": "pnpm typeorm migration:generate -d src/data-source.ts",
"migration:run": "pnpm typeorm migration:run -d src/data-source.ts",
"migration:revert": "pnpm typeorm migration:revert -d src/data-source.ts"
}
步骤 3:生成并运行迁移
提示:执行迁移命令前,请确保你的 PostgreSQL 数据库服务已经启动并且可以正常连接(配置的 host、port、user、password 和 database 必须正确)。如果没有安装本地数据库,你可以通过 Docker 快速启动一个:
bash# 在项目根目录创建一个 docker-compose.yml 文件 # 然后运行以下命令启动数据库: docker compose up -d
当你修改了实体(Entity)代码后(比如添加了一个字段),按照以下流程操作:
- 生成迁移文件 :
TypeORM 会自动对比实体代码和数据库当前的差异,生成 SQL 语句。
bash
# 这里的 src/migrations/AddStatus 是迁移文件名,可以自定义
pnpm migration:generate src/migrations/AddStatus
成功后,你会看到 src/migrations/ 下多了一个时间戳开头的文件,里面包含了 up (升级) 和 down (回滚) 的 SQL 逻辑。
- 应用迁移 :
将生成的 SQL 执行到数据库中。
bash
pnpm migration:run
- 回滚(可选) :
如果刚才的迁移有问题,可以撤销最后一次迁移。
bash
pnpm migration:revert
常见问题 :如果执行命令报错
Connect ECONNREFUSED,请检查 Docker 容器是否开启,或.env中的数据库端口配置是否正确。
2.2.5 方案 B:Prisma(概览)
Prisma 采用 Schema-First:先在 schema.prisma 定义数据模型,再生成类型安全的客户端代码。它的迁移工具链与开发体验通常更"现代化"。
安装与初始化
bash
pnpm add prisma @prisma/client
pnpm dlx prisma init
示例 Schema(PostgreSQL)
prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model AnalysisResult {
id String @id @default(uuid())
taskName String
inputData Json?
outputData Json
modelId String?
status String @default("pending")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("analysis_results")
}
迁移与生成
bash
# 开发环境快速同步(不生成迁移文件)
pnpm dlx prisma db push
# 推荐的开发迁移(生成并应用迁移)
pnpm dlx prisma migrate dev --name init
在 Nest.js 中通常会封装一个 PrismaService 管理连接,并在业务服务中注入使用。
2.2.6 最佳实践(建议保留)
- 环境变量管理:数据库凭证、连接串只放在服务端环境变量中,避免写入仓库。
- 迁移优先 :生产环境关闭
synchronize,用迁移控制变更并可回滚。 - 事务与一致性:涉及多表更新或"写入 + 状态推进"的链路务必使用事务。
- 索引与查询 :对高频查询字段(例如
status、createdAt、业务维度字段)建立合适索引。 - 错误处理与重试:连接失败、死锁、唯一键冲突要在服务层做清晰的错误语义与必要重试。
- 日志与审计:开发环境可开启更详细 SQL 日志,生产环境建议记录慢查询、错误与关键审计事件。
总结
本节完成了"数据基础"这一层的关键认知与落地方式:
- 理解为什么在 Nest.js 中使用 ORM
- 明确数据库选型要点(以 PostgreSQL 为例)
- 掌握 TypeORM 在 Nest.js 中的典型集成路径(配置、实体、仓库注入)
- 了解生产环境应采用迁移管理表结构
- 对 Prisma 的集成方式与迁移策略建立整体认知
现在你的 Nest.js 应用已具备"可持续演进"的数据层基础,为后续 AI Agent 的任务记录、结果沉淀、评估与审计打下底座。下一节将讨论如何更安全地管理敏感配置(例如 API Key 等)。