templates/extension-starter/src/api。| 类型 | 导入方式 | 用途 |
|---|---|---|
| 基础 Controller/Service | @buildingai/base | 日志、分页结果、CRUD、事务、分页查询 |
| 插件 Controller/Entity 装饰器 | @buildingai/core/decorators | 插件前台接口、控制台接口、插件实体 |
| 通用装饰器 | @buildingai/decorators/... | 公开接口、当前用户、文件 URL、 响应转换 |
| DTO | @buildingai/dto | 分页查询 DTO |
| Pipe | @buildingai/pipe/param-validate.pipe | UUID 参数校验 |
| 错误封装 | @buildingai/errors | 统一业务错误 |
| 数据库 | @buildingai/db/@nestjs/typeorm、@buildingai/db/typeorm | TypeORM Module、Repository、装饰器、查询操作符 |
| 平台实体 | @buildingai/db/entities | 使用平台已有实体,如 User |
| 插件 SDK | @buildingai/extension-sdk | 平台用户、AI、计费、tsup 构建配置 |
| 工具函数 | @buildingai/utils | where 构造、路径、类型转换、版本、文件、安全等 |
import { ExtensionWebController } from "@buildingai/core/decorators";import { BaseController } from "@buildingai/base";
import { ExtensionWebController } from "@buildingai/core/decorators";
import { Public } from "@buildingai/decorators/public.decorator";
import { UUIDValidationPipe } from "@buildingai/pipe/param-validate.pipe";
import { Get, Param, Query } from "@nestjs/common";
@ExtensionWebController("article")
export class ArticleWebController extends BaseController {
constructor(private readonly articleService: ArticleService) {
super();
}
@Get()
@Public()
async findAll(@Query() query: QueryArticleDto) {
return this.articleService.list(query);
}
@Get(":id")
@Public()
async findOne(@Param("id", UUIDValidationPipe) id: string) {
return this.articleService.findOneById(id);
}
}| 写法 | 说明 |
|---|---|
@ExtensionWebController("article") | 设置 Controller path |
@ExtensionWebController({ path: "article" }) | 对象写法 |
@ExtensionWebController({ path: "article", skipAuth: true }) | 整个 Controller 跳过鉴权 |
| 能力 | 说明 |
|---|---|
| 插件路由前缀 | 默认 /{extensionIdentifier}/api/{path} |
| 环境前缀 | 如果存在 VITE_APP_WEB_API_PREFIX,使用该前缀 |
| 插件 metadata | 设置插件包名和 Web Controller 标记 |
| 鉴权控制 | skipAuth 会设置公开 metadata |
| path 校验 | path 不能包含 / 和 : |
import { ExtensionConsoleController } from "@buildingai/core/decorators";import { BaseController } from "@buildingai/base";
import { ExtensionConsoleController } from "@buildingai/core/decorators";
import { Playground } from "@buildingai/decorators/playground.decorator";
import { type UserPlayground } from "@buildingai/db";
import { UUIDValidationPipe } from "@buildingai/pipe/param-validate.pipe";
import { Body, Delete, Get, Param, Post, Query } from "@nestjs/common";
@ExtensionConsoleController("article", "文章管理")
export class ArticleController extends BaseController {
constructor(private readonly articleService: ArticleService) {
super();
}
@Post()
async create(@Body() dto: CreateArticleDto, @Playground() user: UserPlayground) {
return this.articleService.createArticle(dto, user.id);
}
@Get()
async findAll(@Query() query: QueryArticleDto) {
return this.articleService.list(query);
}
@Delete(":id")
async remove(@Param("id", UUIDValidationPipe) id: string) {
await this.articleService.delete(id);
return { success: true };
}
}| 写法 | 说明 |
|---|---|
@ExtensionConsoleController("article", "文章管理") | 设置 path 和权限组名称 |
@ExtensionConsoleController({ path: "article" }, "文章管理") | 对象写法 |
@ExtensionConsoleController({ path: "article", skipAuth: true }, "文章管理") | 跳过鉴权 |
| 能力 | 说明 |
|---|---|
| 插件路由前缀 | 默认 /{extensionIdentifier}/consoleapi/{path} |
| 环境前缀 | 如果存在 VITE_APP_CONSOLE_API_PREFIX,使用该前缀 |
| 插件 metadata | 设置插件包名和 Console Controller 标记 |
| 权限组 metadata | 自动设置 code: "${extensionIdentifier}@${path}" 和 name: groupName |
| path 校验 | path 不能包含 / 和 : |
import { ExtensionEntity } from "@buildingai/core/decorators";import { ExtensionEntity } from "@buildingai/core/decorators";
import {
Column,
CreateDateColumn,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "@buildingai/db/typeorm";
@ExtensionEntity()
export class Article {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ length: 200, comment: "文章标题" })
title: string;
@Column({ type: "text", nullable: true, comment: "摘要" })
summary?: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}| 写法 | 说明 |
|---|---|
@ExtensionEntity() | 表名默认用类名转 snake_case |
@ExtensionEntity("article") | 指定表名 |
@ExtensionEntity({ name: "article" }) | 使用 TypeORM EntityOptions |
| 能力 | 说明 |
|---|---|
| 插件 schema | 根据插件安装目录推导 schema |
| 表名处理 | 支持默认表名和自定义表名 |
| TypeORM Entity | 内部应用 TypeORM Entity 装饰器 |
import { BaseController } from "@buildingai/base";| 能力 | 说明 |
|---|---|
logger | 自动创建 Nest Logger,context 为子类名 |
paginationResult | 组装标准分页响应 |
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);
}
}{
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}import { BaseService } from "@buildingai/base";import { BaseService } from "@buildingai/base";
import { InjectRepository } from "@buildingai/db/@nestjs/typeorm";
import { Repository } from "@buildingai/db/typeorm";
import { HttpErrorFactory } from "@buildingai/errors";
import { Injectable } from "@nestjs/common";
import { Article } from "../../../db/entities/article.entity";
import { CreateArticleDto, QueryArticleDto } from "../dto";
@Injectable()
export class ArticleService extends BaseService<Article> {
constructor(
@InjectRepository(Article)
private readonly articleRepository: Repository<Article>,
) {
super(articleRepository);
}
async createArticle(dto: CreateArticleDto) {
return this.create(dto);
}
async list(query: QueryArticleDto) {
const where = query.title ? this.ilike("title", query.title) : {};
return this.paginate(query, {
where,
order: { createdAt: "DESC" },
});
}
async publish(id: string) {
const article = await this.findOneById(id);
if (!article) {
throw HttpErrorFactory.notFound("文章不存在");
}
return this.updateById(id, { status: "published" });
}
}| 方法 | 说明 |
|---|---|
create | 创建单条记录 |
createMany | 批量创建 |
updateById | 按 id 更新 |
update | 按条件更新 |
findOneById | 按 id 查询 |
findOne | 按条件查询单条 |
findAll | 查询列表 |
count | 计数 |
delete | 删除 |
deleteMany | 批量删除 |
restore | 恢复软删除记录 |
paginate | Repository 分页 |
paginateQueryBuilder | QueryBuilder 分页 |
createTransaction | 创建事务 |
withTransaction | 在事务中执行 |
withRetry | 重试执行 |
applyLockToFindOptions | 查询锁配置 |
| 方法 | 说明 |
|---|---|
ilike(field, value) | PostgreSQL 不区分大小写模糊搜索 |
textSearch(field, value) | PostgreSQL 全文搜索 |
jsonQuery(field, path, value) | JSON 字段查询 |
arrayContains(field, values) | 数组包含查询 |
| 选项 | 说明 |
|---|---|
includeFields | 只返回指定字段,支持 author.nickname |
excludeFields | 排除指定字段,支持嵌套字段 |
return this.findOneById(id, {
relations: ["author"],
includeFields: ["id", "title", "author.id", "author.nickname"] as const,
});import { PaginationDto } from "@buildingai/dto";| 字段 | 默认值 | 说明 |
|---|---|---|
page | 1 | 当前页 |
pageSize | 15 | 每页数量 |
import { PaginationDto } from "@buildingai/dto";
import { IsOptional, IsString, IsUUID } from "class-validator";
export class QueryArticleDto extends PaginationDto {
@IsOptional()
@IsString()
title?: string;
@IsOptional()
@IsUUID()
categoryId?: string;
}import { UUIDValidationPipe } from "@buildingai/pipe/param-validate.pipe";import { Get, Param } from "@nestjs/common";
@Get(":id")
async findOne(@Param("id", UUIDValidationPipe) id: string) {
return this.articleService.findOneById(id);
}import { Public } from "@buildingai/decorators/public.decorator";@Get("published")
@Public()
async getPublished() {
return this.articleService.getPublishedArticles();
}import { Playground } from "@buildingai/decorators/playground.decorator";import { type UserPlayground } from "@buildingai/db";
@Post()
async create(@Body() dto: CreateArticleDto, @Playground() user: UserPlayground) {
return this.articleService.createArticle(dto, user.id);
}async create(@Playground("id") userId: string) {
return userId;
}import { BuildFileUrl } from "@buildingai/decorators";@Get(":id")
@BuildFileUrl(["cover", "author.avatar", "items.*.thumbnail"])
async findOne(@Param("id", UUIDValidationPipe) id: string) {
return this.articleService.findOneById(id);
}| 写法 | 说明 |
|---|---|
"cover" | 普通字段 |
"author.avatar" | 嵌套字段 |
"items.*.thumbnail" | 数组通配字段 |
{ field: "images", isArray: true } | 数组字段 |
import { SkipTransform } from "@buildingai/decorators";@Get("raw")
@SkipTransform()
async raw() {
return "plain text";
}import { HttpErrorFactory } from "@buildingai/errors";| 方法 | 说明 |
|---|---|
badRequest(message) | 参数或业务状态错误 |
unauthorized(message) | 未登录或认证失败 |
forbidden(message) | 无权限 |
notFound(message) | 资源不存在 |
internal(message) | 服务内部错误 |
if (!article) {
throw HttpErrorFactory.notFound("文章不存在");
}
if (!category) {
throw HttpErrorFactory.badRequest("栏目不存在");
}import { ApplicationError, HttpError, HttpStatus } from "@buildingai/errors";import { InjectRepository, TypeOrmModule } from "@buildingai/db/@nestjs/typeorm";
import { Repository } from "@buildingai/db/typeorm";import { TypeOrmModule } from "@buildingai/db/@nestjs/typeorm";
import { Module } from "@nestjs/common";
import { Article } from "../../db/entities/article.entity";
import { ArticleController } from "./controllers/console/article.controller";
import { ArticleWebController } from "./controllers/web/article.web.controller";
import { ArticleService } from "./services/article.service";
@Module({
imports: [TypeOrmModule.forFeature([Article])],
controllers: [ArticleController, ArticleWebController],
providers: [ArticleService],
exports: [ArticleService],
})
export class ArticleModule {}constructor(
@InjectRepository(Article)
private readonly articleRepository: Repository<Article>,
) {}import {
Column,
CreateDateColumn,
In,
Like,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "@buildingai/db/typeorm";| 类型 | 说明 |
|---|---|
Column | 字段 |
PrimaryGeneratedColumn | 主键 |
CreateDateColumn | 创建时间 |
UpdateDateColumn | 更新时间 |
ManyToOne、OneToMany、JoinColumn | 关联 |
In、Like、ILike、Raw | 查询操作符 |
Repository | TypeORM Repository |
FindOptionsWhere | 查询条件类型 |
import { User } from "@buildingai/db/entities";import { User } from "@buildingai/db/entities";
import { JoinColumn, ManyToOne } from "@buildingai/db/typeorm";
@ManyToOne(() => User, { nullable: true })
@JoinColumn({ name: "authorId" })
author?: User;import { PublicUserService } from "@buildingai/extension-sdk";constructor(private readonly userService: PublicUserService) {}
async attachAuthor(authorId: string) {
const author = await this.userService.findUserById(authorId);
if (!author) {
throw HttpErrorFactory.notFound("作者不存在");
}
return author;
}import { AiPublicModule, PublicAiModelService } from "@buildingai/extension-sdk";import { AiPublicModule } from "@buildingai/extension-sdk";
import { Module } from "@nestjs/common";
@Module({
imports: [AiPublicModule],
providers: [GenerateService],
exports: [GenerateService],
})
export class GenerateModule {}import { PublicAiModelService } from "@buildingai/extension-sdk";
constructor(private readonly aiModelService: PublicAiModelService) {}import { ExtensionBillingModule, ExtensionBillingService } from "@buildingai/extension-sdk";import { ExtensionBillingModule } from "@buildingai/extension-sdk";
import { Module } from "@nestjs/common";
@Module({
imports: [ExtensionBillingModule],
providers: [GenerateService],
exports: [GenerateService],
})
export class GenerateModule {}import { ExtensionBillingService } from "@buildingai/extension-sdk";
constructor(private readonly billingService: ExtensionBillingService) {}import type { ExtensionPowerDeductionOptions } from "@buildingai/extension-sdk";import { buildWhere } from "@buildingai/utils";undefined 条件,构造 TypeORM where。import { Like } from "@buildingai/db/typeorm";
import { buildWhere } from "@buildingai/utils";
const where = buildWhere<Article>({
title: query.title ? Like(`%${query.title}%`) : undefined,
categoryId: query.categoryId,
status: query.status,
});
return this.paginate(query, { where });