| 类型 | 位置 | 导入方式 | 用途 |
|---|---|---|---|
| 基础 Controller/Service | packages/@buildingai/base/src | @buildingai/base | 标准 CRUD、分页、事务、日志 |
| DTO/Pipe | packages/@buildingai/dto/src、packages/@buildingai/pipe/src | @buildingai/dto、@buildingai/pipe/... | 分页 DTO、UUID 校验 |
| 错误封装 | packages/@buildingai/errors/src | @buildingai/errors | 统一业务错误 |
| 主应用 Controller 装饰器 | packages/api/src/common/decorators | @common/decorators | Web/Console/OpenAPI/权限/会员/API Key |
| 通用后端装饰器 | packages/@buildingai/decorators/src | @buildingai/decorators | Public、Playground、BuildFileUrl、SkipTransform、SuperAdminOnly |
| 扩展后端装饰器 | packages/core/src/decorators | @buildingai/core/decorators | Extension Controller、Extension Entity、MemberOnly |
| TypeORM/实体 | packages/@buildingai/db/src | @buildingai/db/... | TypeORM、实体、Seeder、文件 URL |
| 后端核心模块 | packages/core/src/modules | @buildingai/core/modules 或 @buildingai/core | 上传、云存储、队列、计费、密钥、扩展 |
| 缓存/Redis | packages/@buildingai/cache/src | @buildingai/cache | CacheModule、RedisModule |
| 通用工具 | packages/@buildingai/utils/src | @buildingai/utils | where、路径、状态、类型转换、文件、安全、版本 |
| 扩展 SDK | packages/@buildingai/extension-sdk/src | @buildingai/extension-sdk | 扩展调用平台用户/AI/计费能力、tsup 配置 |
BaseService<Entity>。BaseController。@Controller("xxx")。PaginationDto。UUIDValidationPipe。HttpErrorFactory。BuildFileUrl。packages/@buildingai/base/src/controllers/base.controller.tsLogger,logger context 为子类名。paginationResult(data, total, paginationDto)。import { BaseController } from "@buildingai/base";
export class ArticleController extends BaseController {
async list(dto: QueryArticleDto) {
this.logger.log("query article list");
return this.articleService.list(dto);
}
}paginationResult 返回格式:{
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}BaseService.paginate()packages/@buildingai/base/src/services/base.service.tsid: string 字段。import { BaseService } from "@buildingai/base";
import { InjectRepository } from "@buildingai/db/@nestjs/typeorm";
import { Repository } from "@buildingai/db/typeorm";
import { Injectable } from "@nestjs/common";
@Injectable()
export class ArticleService extends BaseService<Article> {
constructor(@InjectRepository(Article) repo: Repository<Article>) {
super(repo);
}
}| 类型 | 方法 | 说明 |
|---|---|---|
| 分页 | paginate(dto, options) | 基于 findAndCount 的标准分页 |
| 高级分页 | paginateQueryBuilder(qb, dto, excludeFields?, includeFields?, lock?) | 复杂 SQL/关联查询分页 |
| 创建 | create(dto, options?) | 创建单条 |
| 批量创建 | createMany(dtos, options?) | 事务批量创建 |
| 更新 | updateById(id, dto, options?) | 根据 id 更新 |
| 条件更新 | update(where, dto, options?) | 按 where 更新 |
| 查询 | findOneById(id, options?) | id 查询 |
| 条件查询 | findOne(options?) | TypeORM options 查询 |
| 全量查询 | findAll(options?) | 返回数组 |
| 删除 | delete(id, options?) | 删除单条 |
| 批量删除 | deleteMany(idsOrWhere, options?) | 批量删除 |
| 恢复 | restore(id, options?) | 软删除恢复 |
| 计数 | count(options?) | 统计 |
| 事务 | withTransaction(callback, isolationLevel?) | 开启事务 |
| 重试 | withRetry(operation, lockOptions?) | 可重试操作 |
import { Like } from "@buildingai/db/typeorm";
import { buildWhere } from "@buildingai/utils";
async list(dto: QueryArticleDto) {
const where = buildWhere<Article>({
title: dto.title ? Like(`%${dto.title}%`) : undefined,
status: dto.status,
categoryId: dto.categoryId,
});
return this.paginate(dto, {
where,
relations: ["category", "author"],
order: { sort: "DESC", createdAt: "DESC" },
select: {
author: {
id: true,
avatar: true,
nickname: true,
},
},
});
}excludeFields 和 includeFields 支持嵌套路径,例如 author.password、items.*.avatar。return this.findOneById(id, {
relations: ["author"],
excludeFields: ["author.password", "author.salt"] as const,
});return this.findAll({
includeFields: ["id", "title", "author.nickname"] as const,
});includeFields 与 excludeFields 不建议同时使用。const qb = this.repository
.createQueryBuilder("article")
.leftJoinAndSelect("article.category", "category")
.where("article.title ILIKE :keyword", { keyword: `%${dto.keyword}%` })
.orderBy("article.createdAt", "DESC");
return this.paginateQueryBuilder(qb, dto, ["author.password"]);await this.withTransaction(async (manager) => {
await this.updateById(id, dto, { entityManager: manager });
const repo = manager.getRepository(ArticleLog);
await repo.save(repo.create({ articleId: id, action: "update" }));
});BaseService 方法时传 entityManager,避免不同 repository 不在同一个事务。entityManager。BaseService 提供 LockType 和 LockOptions:import { LockType } from "@buildingai/base/services/base.service";
await this.updateById(id, dto, {
lock: {
type: LockType.PESSIMISTIC_WRITE,
retryCount: 3,
retryDelay: 100,
},
});BaseService 内置:| 方法 | 说明 |
|---|---|
ilike(field, value) | PostgreSQL ILIKE 模糊查询 |
textSearch(field, value) | PostgreSQL 全文搜索 |
jsonQuery(jsonField, path, value) | JSON 字段查询 |
arrayContains(field, value) | 数组包含查询 |
packages/@buildingai/dto/src/pagination.dto.ts| 字段 | 默认值 | 校验 |
|---|---|---|
page | 1 | number,最小 1 |
pageSize | 15 | number,最小 1 |
import { PaginationDto } from "@buildingai/dto";
import { IsOptional, IsString } from "class-validator";
export class QueryArticleDto extends PaginationDto {
@IsOptional()
@IsString()
title?: string;
}packages/@buildingai/pipe/src/param-validate.pipe.tsimport { UUIDValidationPipe } from "@buildingai/pipe/param-validate.pipe";
@Get(":id")
findOne(@Param("id", UUIDValidationPipe) id: string) {
return this.service.findOneById(id);
}BadRequestException("Invalid UUID format")。packages/@buildingai/errors/srcApplicationErrorHttpErrorHttpErrorFactoryHttpStatusHttpErrorFactory:import { HttpErrorFactory } from "@buildingai/errors";
if (!entity) {
throw HttpErrorFactory.notFound("记录不存在");
}
if (!canUpdate) {
throw HttpErrorFactory.forbidden("无权操作");
}
if (!dto.name) {
throw HttpErrorFactory.badRequest("名称不能为空");
}HttpErrorFactory。{ success: false },除非这是已约定的接口格式。SkipTransform()。packages/api/src/common/decoratorsimport { WebController } from "@common/decorators";
@WebController("article")
export class ArticleWebController {
@Get()
list() {}
}VITE_APP_WEB_API_PREFIX,使用该前缀。api/{path}。@WebController({ path: "public", skipAuth: true })
export class PublicController {}import { ConsoleController, Permissions } from "@common/decorators";
@ConsoleController("role", "角色管理")
export class RoleController extends BaseController {
@Get()
@Permissions({ code: "role:list", name: "角色列表", action: "查看" })
findAll() {
return this.roleService.findAll();
}
}@ConsoleController(
{
path: "settings",
skipAuth: false,
skipPermissionCheck: true,
},
"系统设置",
)
export class SettingsController {}v1,由 API Key guard 鉴权。@OpenApiController("chat-messages")
export class OpenChatController {
@Post()
chat() {}
}@Permissions({
code: "user:create",
name: "创建用户",
action: "创建",
group: "user",
groupName: "用户管理",
})
@Post()
create() {}hidden: true。@AgentPublicAccess({
route: "/public/agents/:id/chat",
targetPath: "/api/agents/:id/chat",
method: "POST",
})
@Post(":id/chat")
chat() {}
@AgentApiKey()
@Post(":id/messages")
messages() {}packages/@buildingai/decorators/srcimport { Public } from "@buildingai/decorators";
@Public()
@Get("config")
getPublicConfig() {}import { Playground } from "@buildingai/decorators/playground.decorator";
import type { UserPlayground } from "@buildingai/db";
@Post()
create(@Body() dto: CreateDto, @Playground() user: UserPlayground) {
return this.service.create(dto, user.id);
}Playground() 不允许在公共路由中使用;公共路由没有登录上下文。import { BuildFileUrl } from "@buildingai/decorators";
@BuildFileUrl(["avatar", "cover", "author.avatar", "items.*.thumbnail"])
@Get()
list() {}avatarauthor.avataritems.*.image{ field: "images", isArray: true }import { SkipTransform } from "@buildingai/decorators";
@SkipTransform()
@Post("webhook")
webhook() {
return "success";
}import { SuperAdminOnly } from "@buildingai/decorators";
@SuperAdminOnly()
@Delete(":id")
removeSystemResource() {}packages/core/src/decoratorsimport { ExtensionWebController } from "@buildingai/core/decorators";
@ExtensionWebController("article")
export class ArticleWebController {
@Get()
list() {}
}/{extensionIdentifier}/api/{path}import { ExtensionConsoleController } from "@buildingai/core/decorators";
@ExtensionConsoleController("article", "文章管理")
export class ArticleController extends BaseController {}/{extensionIdentifier}/console/{path}{extensionIdentifier}@{path}import { ExtensionEntity } from "@buildingai/core/decorators";
import { Column, PrimaryGeneratedColumn } from "@buildingai/db/typeorm";
@ExtensionEntity("article")
export class Article {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
title: string;
}@buildingai/core/decorators。@MemberOnly({
code: "simple-blog:advanced-export",
name: "高级导出",
description: "导出完整文章数据",
})
@Post("export")
exportAll() {}import { TypeOrmModule } from "@buildingai/db/@nestjs/typeorm";
import { User } from "@buildingai/db/entities";
import { DataSource, Repository } from "@buildingai/db/typeorm";@Module({
imports: [TypeOrmModule.forFeature([Article, Category])],
providers: [ArticleService, CategoryService],
controllers: [ArticleController],
})
export class ArticleModule {}import { BaseSeeder } from "@buildingai/db";
export class ArticleSeeder extends BaseSeeder {
async run() {
// seed data
}
}BuildFileUrl 做域名拼接。packages/core/src/modulesimport { UploadModule, FileUploadService } from "@buildingai/core/modules";
@Module({
imports: [UploadModule],
providers: [MyService],
})
export class MyModule {}import { CloudStorageModule, CloudStorageService } from "@buildingai/core/modules";import { QueueModule, QueueService } from "@buildingai/core/modules";
@Module({
imports: [QueueModule],
})
export class WorkerModule {
constructor(private readonly queueService: QueueService) {}
}import { AppBillingService, BillingModule } from "@buildingai/core/modules";import { SecretModule, SecretService } from "@buildingai/core/modules";import { ExtensionsService } from "@buildingai/core/modules";packages/core/src/services/schedule.service.tsimport { ScheduleService } from "@buildingai/core";@buildingai/core/@nestjs/schedule 中 re-export 的 Nestpackages/@buildingai/cache/srcimport { CacheModule, CacheService, RedisModule, RedisService } from "@buildingai/cache";@Module({
imports: [RedisModule, CacheModule],
})
export class MyModule {}RedisService。CacheService。packages/@buildingai/utils/srcimport {
asArray,
asBoolean,
asDate,
asDefined,
asError,
asJson,
asNumber,
asObject,
asOneOf,
asString,
} from "@buildingai/utils";
const page = asNumber(query.page, 1);
const enabled = asBoolean(dto.enabled);
const tags = asArray(dto.tags);
const payload = asObject(dto.payload);
const type = asOneOf(input.type, ["web", "console"] as const, "web");import { buildConditionalObject, buildWhere } from "@buildingai/utils";
import { Like } from "@buildingai/db/typeorm";
const where = buildWhere({
title: keyword ? Like(`%${keyword}%`) : undefined,
status,
});
const updatePayload = buildConditionalObject({
nickname: dto.nickname,
avatar: dto.avatar || undefined,
});import { joinPaths, joinRouterPaths, validatePath } from "@buildingai/utils";
validatePath(pathSegment);
const route = joinRouterPaths("simple-blog", "console", "article");
const filePath = joinPaths(root, "storage", "uploads");validatePath 常用于 Controller path,防止路径片段里包含 /、: 等非法字符。import { isDevelopment, isDisabled, isEnabled, isProduction } from "@buildingai/utils";
if (isEnabled(user.status)) {
}
if (isDisabled(config.status)) {
}
isDevelopment(() => this.logger.debug("debug only"));import { getOverrideMetadata } from "@buildingai/utils";
const permissions = getOverrideMetadata(reflector, DECORATOR_KEYS.PERMISSIONS_KEY, context);| 工具 | 作用 |
|---|---|
FileDownloader | 下载文件 |
ExtensionFileManager | 扩展文件管理 |
FileUrlProcessorUtil | 处理响应中的文件 URL |
parseExtensionIdentifier | 从 URL 路径解析扩展标识 |
parsePackageName | 解析 npm 包名 |
maskSensitiveValue | 敏感值脱敏 |
decryptValue | 解密敏感值 |
checkVersionCompatibility | 检查扩展/引擎版本兼容 |
BdVersion | BuildingAI 版本工具 |
createResolvablePromise | 可外部 resolve/reject 的 Promise |
DelayedPromise | 延迟 Promise |
StreamUtils | 流处理工具 |
packages/@buildingai/extension-sdk/srcimport {
AiPublicModule,
ExtensionBillingModule,
ExtensionBillingService,
PublicAiModelService,
PublicUserService,
defineBuildingAITsupConfig,
} from "@buildingai/extension-sdk";@Injectable()
export class ArticleService extends BaseService<Article> {
constructor(
@InjectRepository(Article) repo: Repository<Article>,
private readonly userService: PublicUserService,
) {
super(repo);
}
async createArticle(dto: CreateArticleDto, authorId: string) {
const author = await this.userService.findUserById(authorId);
if (!author) throw HttpErrorFactory.notFound("作者不存在");
return this.create({ ...dto, author } as Partial<Article>);
}
}AiPublicModule。@Module({
imports: [AiPublicModule],
providers: [MyAiService],
})
export class MyModule {}ExtensionBillingModule。@Module({
imports: [ExtensionBillingModule],
providers: [ExportService],
})
export class ExportModule {}import { defineBuildingAITsupConfig } from "@buildingai/extension-sdk";
export default defineBuildingAITsupConfig({
assets: ["db/seeds/data", "assets/**/*"],
});src/api/**/*.tsbuildes2023tsconfig.api.jsonpackages/api/src/modules/{module} 下建 dto、services、controllers、module.ts。@buildingai/db/entities 中已有实体,或新增到 DB 包。PaginationDto。BaseService<Entity>。BaseController。WebController,后台接口用 ConsoleController。Permissions。UUIDValidationPipe。Playground()。HttpErrorFactory。BuildFileUrl()。templates/extension-starter/src/api 为结构参考。ExtensionEntity。ExtensionWebController / ExtensionConsoleController。BaseService<Entity>。PublicUserService。AiPublicModule。ExtensionBillingModule。defineBuildingAITsupConfig。BaseService,重复写 CRUD 和分页。PaginationDto,导致 page/pageSize 类型不稳定。throw new Error(),前端无法得到统一业务码。@Entity,导致没有进入扩展 schema。@Controller,导致路由前缀、权限组和扩展标识丢失。BuildFileUrl 或 DB 文件 URL 处理封装。entityManager,导致事务不完整。