后端工具与封装开发文档

本文档整理 Chatbuddy AI 后端中已经封装好的基础类、装饰器、模块和工具函数。目标是让开发者新增业务模块、扩展 API、数据库操作、上传/队列/缓存逻辑时,优先复用现有封装。

范围与导入规则

类型位置导入方式用途
基础 Controller/Servicepackages/@chatbuddy-ai/base/src@chatbuddy-ai/base标准 CRUD、分页、事务、日志
DTO/Pipepackages/@chatbuddy-ai/dto/srcpackages/@chatbuddy-ai/pipe/src@chatbuddy-ai/dto@chatbuddy-ai/pipe/...分页 DTO、UUID 校验
错误封装packages/@chatbuddy-ai/errors/src@chatbuddy-ai/errors统一业务错误
主应用 Controller 装饰器packages/api/src/common/decorators@common/decoratorsWeb/Console/OpenAPI/权限/会员/API Key
通用后端装饰器packages/@chatbuddy-ai/decorators/src@chatbuddy-ai/decoratorsPublic、Playground、BuildFileUrl、SkipTransform、SuperAdminOnly
扩展后端装饰器packages/core/src/decorators@chatbuddy-ai/core/decoratorsExtension Controller、Extension Entity、MemberOnly
TypeORM/实体packages/@chatbuddy-ai/db/src@chatbuddy-ai/db/...TypeORM、实体、Seeder、文件 URL
后端核心模块packages/core/src/modules@chatbuddy-ai/core/modules@chatbuddy-ai/core上传、云存储、队列、计费、密钥、扩展
缓存/Redispackages/@chatbuddy-ai/cache/src@chatbuddy-ai/cacheCacheModule、RedisModule
通用工具packages/@chatbuddy-ai/utils/src@chatbuddy-ai/utilswhere、路径、状态、类型转换、文件、安全、版本
扩展 SDKpackages/@chatbuddy-ai/extension-sdk/src@chatbuddy-ai/extension-sdk扩展调用平台用户/AI/计费能力、tsup 配置
建议:
Service 优先继承BaseService<Entity>
Controller 优先继承BaseController
Controller 路由优先使用项目封装装饰器,不直接裸写@Controller("xxx")
查询 DTO 优先继承PaginationDto
路径 id 优先用UUIDValidationPipe
业务错误优先用HttpErrorFactory
文件路径响应优先用BuildFileUrl

BaseController

位置:packages/@chatbuddy-ai/base/src/controllers/base.controller.ts
封装内容:
自动创建 NestLogger,logger context 为子类名。
提供paginationResult(data, total, paginationDto)
import { BaseController } from "@chatbuddy-ai/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;
}
通常不需在 Controller 手动调用它,因为BaseService.paginate
已经返回同样格式。只有 Controller 自己组合数据时再用。

BaseService

位置:packages/@chatbuddy-ai/base/src/services/base.service.ts
适用条件:
实体有id: string字段。
使用 TypeORM Repository。
业务是常见 CRUD、分页、字段过滤、事务、软移除/恢复。

基本写法

import { BaseService } from "@chatbuddy-ai/base";
import { InjectRepository } from "@chatbuddy-ai/db/@nestjs/typeorm";
import { Repository } from "@chatbuddy-ai/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 "@chatbuddy-ai/db/typeorm";
import { buildWhere } from "@chatbuddy-ai/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, }, }, });
}

字段过滤

excludeFieldsincludeFields支持嵌套路径,比如author.passworditems.*.avatar
return this.findOneById(id, { relations: ["author"], excludeFields: ["author.password", "author.salt"] as const,
});
或只包含指定字段:
return this.findAll({ includeFields: ["id", "title", "author.nickname"] as const,
});
注意:
includeFieldsexcludeFields不建议同时使用。
字段过滤发生在查询返回后,不等同于 SQL select;敏感字段最好同时在 TypeORM select 层规避。

QueryBuilder 分页

适合复杂关联、聚合、手写条件:
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 不在同一个事务。
多个 Service 协作时,尽量让最外层 Service 负责启用事务并传递entityManager

锁和重试

BaseService提供LockTypeLockOptions
import { LockType } from "@chatbuddy-ai/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)数组包含查询
这些是 protected 方法,只能在子类里用。

DTO 与 Pipe

PaginationDto

位置:packages/@chatbuddy-ai/dto/src/pagination.dto.ts
字段:
字段默认值校验
page1number,最小 1
pageSize15number,最小 1
import { PaginationDto } from "@chatbuddy-ai/dto";
import { IsOptional, IsString } from "class-validator"; export class QueryArticleDto extends PaginationDto { @IsOptional @IsString title?: string;
}

UUIDValidationPipe

位置:packages/@chatbuddy-ai/pipe/src/param-validate.pipe.ts
import { UUIDValidationPipe } from "@chatbuddy-ai/pipe/param-validate.pipe"; @Get(":id")
findOne(@Param("id", UUIDValidationPipe) id: string) { return this.service.findOneById(id);
}
如果 id 不是 UUID,会抛BadRequestException("Invalid UUID format")

错误封装

位置:packages/@chatbuddy-ai/errors/src
核心:
ApplicationError
HttpError
HttpErrorFactory
HttpStatus
业务层推荐使用HttpErrorFactory
import { HttpErrorFactory } from "@chatbuddy-ai/errors"; if (!entity) { throw HttpErrorFactory.notFound("记录不存在");
} if (!canUpdate) { throw HttpErrorFactory.forbidden("无权操作");
} if (!dto.name) { throw HttpErrorFactory.badRequest("名称不能为空");
}
使用建议:
Service 中抛业务错误用HttpErrorFactory
Controller 中不要吞掉错误后返回{ success: false },除非这是已约定的接口格式。
第三方回调需原样返回时,配合SkipTransform

主应用 Controller 装饰器

位置:packages/api/src/common/decorators

WebController

用于前台用户侧 API。
import { 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 {}

ConsoleController

用于后台控制台 API,会自动记录权限组 metadata。
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 {}

OpenApiController

用于开放 API,路由前缀为v1,由 API Key guard 鉴权。
@OpenApiController("chat-messages")
export class OpenChatController { @Post chat {}
}

Permissions

@Permissions({ code: "user:create", name: "创建用户", action: "创建", group: "user", groupName: "用户管理",
})
@Post
create {}
介绍:
主应用权限会被标记为系统权限。
权限扫描和后台权限配置依赖这些 metadata。
不需展示在权限列表但仍要控制访问时,可配置hidden: true

AgentPublicAccess 与 AgentApiKey

适合智能体公开访问或 API Key 访问场景。
@AgentPublicAccess({ route: "/public/agents/:id/chat", targetPath: "/api/agents/:id/chat", method: "POST",
})
@Post(":id/chat")
chat {} @AgentApiKey
@Post(":id/messages")
messages {}

通用后端装饰器

位置:packages/@chatbuddy-ai/decorators/src

Public

跳过登录认证。
import { Public } from "@chatbuddy-ai/decorators"; @Public
@Get("config")
getPublicConfig {}

Playground

获取当前用户上下文。
import { Playground } from "@chatbuddy-ai/decorators/playground.decorator";
import type { UserPlayground } from "@chatbuddy-ai/db"; @Post
create(@Body dto: CreateDto, @Playground user: UserPlayground) { return this.service.create(dto, user.id);
}
注意:Playground不允许在公共路由中使用;公共路由没有登录上下文。

BuildFileUrl

用于响应返回前拼接文件访问域名。
import { BuildFileUrl } from "@chatbuddy-ai/decorators"; @BuildFileUrl(["avatar", "cover", "author.avatar", "items.*.thumbnail"])
@Get
list {}
支持:
简单字段:avatar
嵌套字段:author.avatar
数组字段:items.*.image
配置对象:{ field: "images", isArray: true }

SkipTransform

跳过统一响应包装,适合支付回调、第三方 webhook、下载响应。
import { SkipTransform } from "@chatbuddy-ai/decorators"; @SkipTransform
@Post("webhook")
webhook { return "success";
}

SuperAdminOnly

import { SuperAdminOnly } from "@chatbuddy-ai/decorators"; @SuperAdminOnly
@Delete(":id")
removeSystemResource {}

扩展后端装饰器

位置:packages/core/src/decorators

ExtensionWebController

扩展前台 API。
import { ExtensionWebController } from "@chatbuddy-ai/core/decorators"; @ExtensionWebController("article")
export class ArticleWebController { @Get list {}
}
路由格式:
/{extensionIdentifier}/api/{path}

ExtensionConsoleController

扩展后台 API,会注册扩展权限组。
import { ExtensionConsoleController } from "@chatbuddy-ai/core/decorators"; @ExtensionConsoleController("article", "文章管理")
export class ArticleController extends BaseController {}
路由格式:
/{extensionIdentifier}/console/{path}
权限组 code:
{extensionIdentifier}@{path}

ExtensionEntity

扩展实体自动打开扩展独立 schema。
import { ExtensionEntity } from "@chatbuddy-ai/core/decorators";
import { Column, PrimaryGeneratedColumn } from "@chatbuddy-ai/db/typeorm"; @ExtensionEntity("article")
export class Article { @PrimaryGeneratedColumn("uuid") id: string; @Column title: string;
}
使用注意:
只在扩展实体中使用。
构建后的调用栈会用于识别扩展目录。
表名可以传字符串,也可以传 EntityOptions。

MemberOnly

用于会员等级限制,主应用和扩展都存在类似装饰器。扩展里优先使用@chatbuddy-ai/core/decorators
@MemberOnly({ code: "simple-blog:advanced-export", name: "高级导出", description: "导出完整文章数据",
})
@Post("export")
exportAll {}

TypeORM 与数据库封装

常用导入:
import { TypeOrmModule } from "@chatbuddy-ai/db/@nestjs/typeorm";
import { User } from "@chatbuddy-ai/db/entities";
import { DataSource, Repository } from "@chatbuddy-ai/db/typeorm";
模块注册:
@Module({ imports: [TypeOrmModule.forFeature([Article, Category])], providers: [ArticleService, CategoryService], controllers: [ArticleController],
})
export class ArticleModule {}
Seeder:
import { BaseSeeder } from "@chatbuddy-ai/db"; export class ArticleSeeder extends BaseSeeder { async run { // seed data }
}
文件 URL:
实体字段可通过 DB 层装饰器标记归一化。
Controller 响应字段可用BuildFileUrl做域名拼接。

后端核心模块

位置:packages/core/src/modules

UploadModule / FileUploadService

适用:
后端处理上传记录。
提交文件元数据。
结合云存储服务返回访问 URL。
import { UploadModule, FileUploadService } from "@chatbuddy-ai/core/modules"; @Module({ imports: [UploadModule], providers: [MyService],
})
export class MyModule {}

CloudStorageModule / CloudStorageService

适用:统一云存储访问,当前主要包含 OSS 实现。
import { CloudStorageModule, CloudStorageService } from "@chatbuddy-ai/core/modules";

QueueModule / QueueService

适用:异步任务、邮件、文件处理、向量化等。
import { QueueModule, QueueService } from "@chatbuddy-ai/core/modules"; @Module({ imports: [QueueModule],
})
export class WorkerModule { constructor(private readonly queueService: QueueService) {}
}

BillingModule / AppBillingService

适用:平台级算力/余额扣费、充值、会员权益扣减。
import { AppBillingService, BillingModule } from "@chatbuddy-ai/core/modules";

SecretModule / SecretService

适用:密钥模板、用户/系统密钥管理,避免业务模块直接处理敏感信息。
import { SecretModule, SecretService } from "@chatbuddy-ai/core/modules";

ExtensionsService

适用:扩展安装、启停、配置、schema、版本管理相关逻辑。
import { ExtensionsService } from "@chatbuddy-ai/core/modules";

ScheduleService

位置:packages/core/src/services/schedule.service.ts
适用:运行时注册和管理 cron、interval、timeout。
import { ScheduleService } from "@chatbuddy-ai/core";
如果只是普通 Nest 定时任务,优先使用@chatbuddy-ai/core/@nestjs/schedule中 re-export 的 Nest
schedule 能力,保持版本一致。

Cache 与 Redis

位置:packages/@chatbuddy-ai/cache/src
导入:
import { CacheModule, CacheService, RedisModule, RedisService } from "@chatbuddy-ai/cache";
模块使用:
@Module({ imports: [RedisModule, CacheModule],
})
export class MyModule {}
建议:
需直接操作 Redis 时注入RedisService
普通业务缓存优先注入CacheService
缓存 key 应包含业务模块前缀,避免冲突。

通用工具函数

位置:packages/@chatbuddy-ai/utils/src

类型转换

import { asArray, asBoolean, asDate, asDefined, asError, asJson, asNumber, asObject, asOneOf, asString,
} from "@chatbuddy-ai/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");

条件对象与 where

import { buildConditionalObject, buildWhere } from "@chatbuddy-ai/utils";
import { Like } from "@chatbuddy-ai/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 "@chatbuddy-ai/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 "@chatbuddy-ai/utils"; if (isEnabled(user.status)) {
}
if (isDisabled(config.status)) {
}
isDevelopment( => this.logger.debug("debug only"));

装饰器 metadata 工具

import { getOverrideMetadata } from "@chatbuddy-ai/utils"; const permissions = getOverrideMetadata(reflector, DECORATOR_KEYS.PERMISSIONS_KEY, context);
适用:Guard、Interceptor、Filter 中读取 Controller/Handler metadata。

文件、安全、版本

工具作用
FileDownloader下载文件
ExtensionFileManager扩展文件管理
FileUrlProcessorUtil处理响应中的文件 URL
parseExtensionIdentifier从 URL 路径解析扩展标识
parsePackageName解析 npm 包名
maskSensitiveValue敏感值脱敏
decryptValue解密敏感值
checkVersionCompatibility检查扩展/引擎版本兼容
BdVersionChatbuddy AI 版本工具
createResolvablePromise可外部 resolve/reject 的 Promise
DelayedPromise延迟 Promise
StreamUtils流处理工具

扩展后端 SDK

位置:packages/@chatbuddy-ai/extension-sdk/src
导入:
import { AiPublicModule, ExtensionBillingModule, ExtensionBillingService, PublicAiModelService, PublicUserService, defineChatbuddy AITsupConfig,
} from "@chatbuddy-ai/extension-sdk";

PublicUserService

扩展里查询平台用户信息。
@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>); }
}

PublicAiModelService

扩展调用平台 AI 模型能力时使用。模块中导入AiPublicModule
@Module({ imports: [AiPublicModule], providers: [MyAiService],
})
export class MyModule {}

ExtensionBillingService

扩展功能扣费/扣算力时使用。模块中导入ExtensionBillingModule
@Module({ imports: [ExtensionBillingModule], providers: [ExportService],
})
export class ExportModule {}

defineChatbuddy AITsupConfig

扩展后端构建配置。
import { defineChatbuddy AITsupConfig } from "@chatbuddy-ai/extension-sdk"; export default defineChatbuddy AITsupConfig({ assets: ["db/seeds/data", "assets/**/*"],
});
默认:
入口:src/api/**/*.ts
输出:build
格式:CommonJS
target:es2023
tsconfig:tsconfig.api.json
构建成功后复制静态资源

新增后端业务模块流程

主应用模块

1.
packages/api/src/modules/{module}下建dtoservicescontrollersmodule.ts
2.
Entity 使用@chatbuddy-ai/db/entities中已有实体,或新增到 DB 包。
3.
查询 DTO 继承PaginationDto
4.
Service 继承BaseService<Entity>
5.
Controller 继承BaseController
6.
前台接口用WebController,后台接口用ConsoleController
7.
权限接口补Permissions
8.
id 参数用UUIDValidationPipe
9.
当前用户用Playground
10.
业务错误用HttpErrorFactory
11.
返回文件字段用BuildFileUrl

扩展模块

1.
templates/extension-starter/src/api为结构参考。
2.
Entity 使用ExtensionEntity
3.
Controller 使用ExtensionWebController/ExtensionConsoleController
4.
Service 继承BaseService<Entity>
5.
需平台用户时注入PublicUserService
6.
需 AI 能力时导入AiPublicModule
7.
需扣费时导入ExtensionBillingModule
8.
构建使用defineChatbuddy AITsupConfig

常见反模式

在 Controller 里直接写复杂业务逻辑。应放到 Service。
Service 不继承BaseService,重复写 CRUD 和分页。
分页接口不继承PaginationDto,导致 page/pageSize 类型不稳定。
业务错误直接throw new Error,前端无法得到统一业务码。
扩展实体使用普通@Entity,导致没有打开扩展 schema。
扩展 Controller 裸写@Controller,导致路由前缀、权限组和扩展标识丢失。
文件 URL 在业务里手动拼接域名。应使用BuildFileUrl或 DB 文件 URL 处理封装。
多表写入不传entityManager,导致事务不完整。

319 篇文档 · 内容同步自官方帮助中心