---
name: fastcar-framework
description: FastCar Framework 核心开发指南。Use when building or modifying FastCar TypeScript applications with IoC, dependency injection, decorators, @fastcar/koa APIs, lifecycle hooks, configuration, templates, or framework-level examples.
---

# FastCar Framework

FastCar 是基于 TypeScript 的 Node.js 企业级应用开发框架，采用 IoC（控制反转）设计思想。

## Agent 使用指南

使用本 skill 时：

- 先遵守 `skills/AGENTS.md` 的共享规则。
- 本 skill 只描述 FastCar Framework 项目/API 约束，不要泛化到 NestJS、Express、Spring 或其他 TypeScript 框架。
- 适合处理 IoC、`@Component`、`@Service`、`@Controller`、`@Autowired`、Koa Web、配置、生命周期和项目模板。
- 生成 Koa Controller 时，不要使用 `@Body`、`@Param`、`@Query`。
- 路由装饰器必须写成 `@GET()` / `@POST()`，不能省略括号。
- Koa Controller 路由优先使用三段式动作命名，例如 `/api/items/detail/:id`、`/api/items/update/:id`，避免在同一层级混用 `/list`、`/create` 和 `/:id`。
- 使用 `@ValidForm` + `@Rule()` 后，Controller 参数已经被 FastCar 按规则校验并格式化，不要再写 `DTO.from(body).toInput()` 之类的二次转换。
- 示例代码必须保留关键 import，尤其是 `Context` 从 `koa` 导入。
- `@fastcar/*` 模块必须使用 TypeScript 静态 `import`，不要使用 CommonJS `require()`。
- 示例中的 `localhost`、端口、token、数据库或 Redis 连接参数仅是本地占位；生产配置必须来自环境变量、配置中心或密钥管理系统。
- 静态权限门禁（如角色、系统管理员、固定 permission）应优先在 Controller 方法上通过装饰器声明；不要把 `requireRole`、`requireSystemAdmin` 等入口级权限 guard 写在 Service 中。

## 核心概念

### IoC 容器与装饰器

| 装饰器 | 用途 | 示例 |
|--------|------|------|
| `@Application` | 入口应用类 | `@Application class App {}` |
| `@Component` | 通用组件 | `@Component class UtilService {}` |
| `@Service` | 服务层 | `@Service class BizService {}` |
| `@Controller` | 控制器层 | `@Controller class ApiController {}` |
| `@Repository` | 数据访问层 | `@Repository class DataRepository {}` |
| `@Autowired` | 依赖注入 | `@Autowired private service!: BizService;` |

### 基础应用结构

```typescript
import { FastCarApplication } from "@fastcar/core";
import { Application, Autowired, Component, Service, Controller } from "@fastcar/core/annotation";

@Service
class BizService {
  getData() {
    return [{ id: 1, name: "示例" }];
  }
}

@Controller
class ApiController {
  @Autowired
  private service!: BizService;
  
  getData() {
    return this.service.getData();
  }
}

@Application
class App {
  app!: FastCarApplication;
  
  async start() {
    console.log("应用启动成功!");
  }
}

const app = new App();
app.start();
```

## 模块速查

### Import 约束

FastCar 示例、模板和业务代码必须使用静态 import：

```typescript
import { FastCarApplication } from "@fastcar/core";
import { Application } from "@fastcar/core/annotation";
import { EnableKoa } from "@fastcar/koa/annotation";
```

不要写：

```typescript
const { Application } = require("@fastcar/core/annotation");
const { EnableKoa } = require("@fastcar/koa/annotation");
```

### Web 开发 (@fastcar/koa)

**路由装饰器使用方式：**

```typescript
import { GET, POST, REQUEST } from "@fastcar/koa/annotation";
import { Context } from "koa";

@Controller
@REQUEST("/api/items")
class ItemController {
  // GET 请求 - 无路径参数时必须有括号
  @GET()
  async list() {
    return { data: [] };
  }

  // GET 请求 - 有路径参数。使用三段式动作路由，避免 /list 被 /:id 误匹配。
  @GET("/detail/:id")
  async getById(id: string, ctx: Context) {
    return { id };
  }

  // POST 请求
  @POST()
  async create(body: ItemDTO, ctx: Context) {
    return { created: true };
  }
}
```

**⚠️ 重要：FastCar 没有 `@Body`, `@Param`, `@Query` 装饰器**

- 请求参数直接作为方法参数传入
- 第一个参数为请求数据（GET 的 query / POST 的 body / 路径参数）
- 第二个参数为 Koa 上下文 `ctx: Context`，**可省略**
- `Context` 需从 `koa` 导入：`import { Context } from "koa"`

**路由命名规范：**

- Controller base 下不要直接暴露 `/:id`，尤其不要和 `/list`、`/create`、`/import-preview` 等动作路由混用。
- 单资源接口使用三段式动作路径：`GET /api/items/detail/:id`、`PATCH /api/items/update/:id`、`POST /api/items/archive/:id`。
- FastCar Koa 的路由适配会继续执行后续匹配层；过宽的 `/:id` 可能把 `list`、`create` 等动作名当成 id，覆盖前一个路由的响应。

❌ **错误：**
```typescript
@Controller
@REQUEST("/api/items")
class ItemController {
  @GET()
  async list() {
    return this.service.list();
  }

  @GET("/:id")
  async get(body: { id: string }) {
    return this.service.get(body.id);
  }
}
```

✅ **正确：**
```typescript
@Controller
@REQUEST("/api/items")
class ItemController {
  @GET()
  async list() {
    return this.service.list();
  }

  @GET("/detail/:id")
  async get(body: { id: string }) {
    return this.service.get(body.id);
  }
}
```

**权限声明边界：**

- 入口级权限尽可能放在 Controller：例如角色白名单、系统管理员、固定 permission 等静态门禁，应由项目自定义的 `@RequireRole(...)`、`@RequireSystemAdmin()`、`@RequirePermission(...)` 等方法级装饰器声明。
- Service 不应直接写 `requireRole(ctx, ...)`、`requireSystemAdmin(ctx)` 这类静态入口门禁；Service 只保留业务规则、资源归属、租户隔离、状态机校验、动态权限和一致性保护。
- 如果权限依赖目标资源、请求体、数据库状态或运行时工具定义，例如“admin 不能提升 owner”“最后 owner 不能删除”“只能操作本人资产”“工具按 requiredRole 动态过滤”，可以保留在 Service 或领域层。

❌ **错误：**
```typescript
@Service
class ItemService {
  async create(ctx: ActorContext, body: CreateItemDTO) {
    requireRole(ctx, [Role.owner, Role.admin, Role.editor]);
    return this.repository.save(body);
  }
}
```

✅ **正确：**
```typescript
@Controller
@REQUEST("/api/items")
class ItemController {
  @Autowired
  private service!: ItemService;

  @RequireRole(Role.owner, Role.admin, Role.editor)
  @ValidForm
  @POST()
  async create(@Rule() body: CreateItemDTO, ctx: Context) {
    return this.service.create(getActor(ctx), body);
  }
}

@Service
class ItemService {
  async create(ctx: ActorContext, body: CreateItemDTO) {
    return this.repository.saveForOrganization(ctx.organizationId, body);
  }
}
```

### 数据库 (@fastcar/mysql)

**实体定义：**

```typescript
import { Table, Field, DBType, PrimaryKey, NotNull, Size } from "@fastcar/core/annotation";

@Table("entities")
class Entity {
  @Field("id")
  @DBType("int")
  @PrimaryKey
  id!: number;
  
  @Field("name")
  @DBType("varchar")
  @NotNull
  @Size({ maxSize: 50 })
  name!: string;
}
```

**Mapper 定义：**

```typescript
import { Entity, Repository } from "@fastcar/core/annotation";
import { MysqlMapper } from "@fastcar/mysql";

@Entity(Entity)
@Repository
class EntityMapper extends MysqlMapper<Entity> {}
export default EntityMapper;
```

**Service 中使用：**

```typescript
import { Service, Autowired } from "@fastcar/core/annotation";
import { OrderEnum } from "@fastcar/core/db";
import EntityMapper from "./EntityMapper";

@Service
class EntityService {
  @Autowired
  private mapper!: EntityMapper;

  async getList() {
    return this.mapper.select({
      where: { status: 1 },
      orders: { createTime: OrderEnum.desc },
      limit: 10
    });
  }

  async getOne(id: number) {
    return this.mapper.selectOne({ where: { id } });
  }

  async create(data: Entity) {
    return this.mapper.saveOne(data);
  }

  async update(id: number, data: Partial<Entity>) {
    return this.mapper.update({ where: { id }, row: data });
  }

  async delete(id: number) {
    return this.mapper.delete({ where: { id } });
  }
}
```

### 表单验证 (@fastcar/core)

```typescript
import { ValidForm, NotNull, Size, Rule } from "@fastcar/core/annotation";

class ItemDTO {
  @NotNull
  name!: string;
  
  @Size({ minSize: 1, maxSize: 150 })
  value!: number;
}

@Controller
@REQUEST("/api/items")
class ItemController {
  @GET()
  async list(page: number = 1, pageSize: number = 10) {
    return { page, pageSize, data: [] };
  }

  @ValidForm
  @POST()
  async create(@Rule() body: ItemDTO) {
    const { name, value } = body;
    return this.service.create({ name, value });
  }
}
```

**表单验证规则：**

| 装饰器 | 用途 | 示例 |
|--------|------|------|
| `@ValidForm` | 开启方法参数校验 | 放在方法上 |
| `@Rule()` | 标记校验对象，并让 FastCar 按 DTO 规则读取、格式化和回写参数 | 放在 DTO 参数前 |
| `@NotNull` | 参数不能为空 | 放在 DTO 字段上 |
| `@Size({min, max})` | 大小限制 | 放在 DTO 字段上 |

**Controller 使用规范：**

- Controller 只负责接收 `@Rule()` 后的参数并转交 service，不要在 Controller 内重复构造 DTO。
- `@Rule()` 会结合 DTO 字段装饰器执行校验，并通过 `DataFormat.formatValue` 对字段做基础格式化后回写到参数对象。
- DTO 类只声明入参结构和校验规则，不要为了 Controller 调用而添加 `static from()`、`toInput()` 等转换方法。
- 需要 trim、兼容字段合并、枚举兜底、业务默认值等业务归一化时，放在 service 或专门的 mapper/helper 中处理，不要混进 Controller 校验链路。

❌ **错误：**
```typescript
@ValidForm
@POST()
async create(@Rule() body: ItemDTO) {
  return this.service.create(ItemDTO.from(body).toInput());
}
```

✅ **正确：**
```typescript
@ValidForm
@POST()
async create(@Rule() body: ItemDTO) {
  return this.service.create(body);
}
```

### Redis (@fastcar/redis)

`@fastcar/redis` 使用 `EnableRedis` 启用，业务侧通过继承并注入 `RedisTemplate` 使用；不要使用不存在的 `RedisClient` / `@RedisClient`。

```typescript
import { Autowired, DS, Repository, Service } from "@fastcar/core/annotation";
import { RedisTemplate } from "@fastcar/redis";

@Repository
@DS("default")
class AppRedisTemplate extends RedisTemplate {}

@Service
class CacheService {
  @Autowired
  private redisTemplate!: AppRedisTemplate;
  
  async get(key: string) {
    return this.redisTemplate.get(key);
  }
  
  async set(key: string, value: string, ttl?: number) {
    if (ttl) {
      await this.redisTemplate.setEx(key, value, ttl);
      return;
    }
    await this.redisTemplate.set(key, value);
  }

  async cacheUser(id: number, user: { id: number; name: string }) {
    await this.redisTemplate.setJson(`user:${id}`, user, 3600);
    return this.redisTemplate.getJson<{ id: number; name: string }>(`user:${id}`);
  }
}
```

### 定时任务 (@fastcar/timer)

> **推荐使用 `@fastcar/timer/scheduling2` 模块**

```typescript
import { ScheduledInterval, ScheduledCron } from "@fastcar/timer/scheduling2";

@Component
class TaskService {
  // 间隔执行（毫秒）
  @ScheduledInterval({ fixedRate: 60000 })
  async intervalTask() {
    console.log("每分钟执行");
  }
  
  // Cron 表达式
  @ScheduledCron("0 0 * * * *")
  async hourlyTask() {
    console.log("每小时执行");
  }
}
```

### 工作线程池 (@fastcar/workerpool)

```typescript
import { WorkerPool, WorkerTask } from "@fastcar/workerpool/annotation";

@Component
class ComputeService {
  @WorkerPool({ minWorkers: 2, maxWorkers: 4 })
  private pool!: WorkerPool;
  
  @WorkerTask
  heavyComputation(data: number[]): number {
    return data.reduce((a, b) => a + b, 0);
  }
}
```

上述 `.reduce()` 仅适用于已在内存中的纯计算输入；数据库分页、聚合、排序和关联统计仍必须下推到数据库层完成。

## 项目模板速查

FastCar CLI 提供 5 种项目模板：

| 模板 | 适用场景 | 核心依赖 | 关键注解 |
|------|---------|---------|---------|
| web | RESTful API 服务 | @fastcar/koa, @fastcar/server | @EnableKoa |
| static | 静态资源服务器 | @fastcar/koa, @fastcar/server | @EnableKoa + KoaStatic |
| rpc | RPC 微服务通信 | @fastcar/rpc, @fastcar/server | @EnableRPC |
| cos | 对象存储/文件上传 | @fastcar/koa, @fastcar/cossdk, @fastcar/server | @EnableKoa |
| microservices | 分布式多服务架构 | @fastcar/koa, @fastcar/rpc, @fastcar/server, @fastcar/timer | @EnableKoa / @EnableRPC |

### 各模板入口示例

**Web 模板**
```typescript
import { Application } from "@fastcar/core/annotation";
import { EnableKoa, KoaMiddleware } from "@fastcar/koa/annotation";
import { ExceptionGlobalHandler, KoaBodyParser } from "@fastcar/koa";

@Application
@EnableKoa
@KoaMiddleware(ExceptionGlobalHandler)
@KoaMiddleware(KoaBodyParser)
class APP {
  app!: FastCarApplication;
}
export default new APP();
```

**RPC 模板**
```typescript
import { Application } from "@fastcar/core/annotation";
import { EnableRPC } from "@fastcar/rpc/annotation";

@Application
@EnableRPC
class APP {}
export default new APP();
```

**Microservices 模板**
微服务模板包含多服务架构：center（服务中心）、connector（连接器）、message（消息服务）、web（Web服务）、base（基础服务）。

### 项目结构示例

```
template/
├── src/
│   ├── controller/       # 控制器（web/cos）
│   ├── dto/              # DTO 类（表单验证）
│   ├── service/          # 服务层
│   ├── model/            # 数据模型
│   └── app.ts            # 应用入口
├── resource/
│   └── application.yml   # 配置文件
├── package.json
└── tsconfig.json
```

### 模板依赖安装

```bash
# Web / Static
npm i @fastcar/core @fastcar/koa @fastcar/server

# RPC
npm i @fastcar/core @fastcar/rpc @fastcar/server

# COS
npm i @fastcar/core @fastcar/koa @fastcar/cossdk @fastcar/server

# Microservices
npm i @fastcar/core @fastcar/koa @fastcar/rpc @fastcar/server @fastcar/timer
```

## 配置管理

配置文件放在 `resource/application.yml`。支持按 `env` 加载多文件，例如 `application-dev.yml` 会与主配置合并。

用户可见的错误消息、提示文案、prompt 模板、词条和多语言映射也应放在 `resource/` 下的配置或数据文件中，优先使用独立 `.yml` 文件，例如 `resource/error-messages.yml`、`resource/conversation-guardrail-texts.yml`。不要把大段业务文案表硬编码在 Middleware、Controller 或 Service 里，也不要塞进 `application.yml` / `application-*.yml`；`application*.yml` 只放应用启动参数、数据源、端口、provider/model ID、阈值等运行配置。业务代码应通过 `@Configure("xxx.yml")` 配置类注入，负责结构化读取、校验和按 errorCode/locale 选择文案。

### 独立业务配置示例

```yaml
# resource/conversation-guardrail-texts.yml
pendingTaskWait:
  zh-CN: |
    ### 当前图片仍在生成中

    上一张图片还没完成，请等待生成结束后再继续编辑。
  en-US: |
    ### The image is still being generated

    Wait until generation completes before editing it.
```

```typescript
import { Configure } from "@fastcar/core/annotation";

@Configure("conversation-guardrail-texts.yml")
class ConversationGuardrailTextConfig {
  pendingTaskWait: Record<string, string> = {};
}

@Service
class ConversationService {
  @Autowired
  private guardrailTextConfig!: ConversationGuardrailTextConfig;
}
```

### 基础配置示例

```yaml
application:
  name: my-app
  version: 1.0.0
  env: dev

settings:
  mysql:
    host: localhost
    port: 3306
    database: mydb
    username: root
    password: password
  redis:
    - { source: "default", host: "localhost", port: 6379 }
```

使用配置：

```typescript
import { Configure, Value } from "@fastcar/core/annotation";

@Configure
class AppConfig {
  @Value("server.port")
  port!: number;

  @Value("mysql.host")
  dbHost!: string;
}
```

### Web 模板 application.yml

```yaml
application:
  env: "dev"

settings:
  koa:
    server:
      - { port: 8080, host: "0.0.0.0" }
    koaStatic:
      { "public": "public" }
    koaBodyParser:
      enableTypes: ["json", "form", "text"]
```

### RPC 模板配置

```yaml
application:
  name: "fastcar-boot-rpc"

settings:
  rpc:
    list:
      - id: "server-1"
        type: "ws"
        server: { port: 1235 }
        serviceType: "base"
        secure:
          username: "user"
          password: "password"
```

### Microservices 模板配置

```yaml
settings:
  microservices:
    center:
      token: "your-token-here"
      servers:
        - host: "localhost"
          clusters: 1
          list:
            - type: "ws"
              server: { port: 60000 }
    connector:
      token: "your-token-here"
      servers:
        - host: "localhost"
          clusters: 1
          list:
            - front: true
              type: "ws"
              server: { port: 60100 }
```

## 生命周期钩子

```typescript
import { ApplicationStart, ApplicationStop, ApplicationInit } from "@fastcar/core/annotation";

@Component
class LifecycleService {
  @ApplicationStart
  async onStart() {
    console.log("应用启动");
  }
  
  @ApplicationStop
  async onStop() {
    console.log("应用停止");
  }
  
  @ApplicationInit
  async init() {
    console.log("初始化完成");
  }
}
```

## 工具类

```typescript
import { DateUtil, CryptoUtil, FileUtil, TypeUtil } from "@fastcar/core/utils";

// 日期时间
DateUtil.toDateTime(); // "2024-03-10 15:30:45"
DateUtil.toDay();      // "2024-03-10"

// 加密
CryptoUtil.aesEncode(key, iv, "data");
CryptoUtil.sha256Encode("password");

// 文件操作
FileUtil.getFilePathList("./src");
FileUtil.getResource("./config.yml");

// 类型判断
TypeUtil.isFunction(() => {});  // true
TypeUtil.isClass(MyClass);       // true
```

## 完整模块列表

| 模块 | 安装命令 | 用途 |
|------|----------|------|
| @fastcar/core | `npm i @fastcar/core` | IoC 容器、配置管理 |
| @fastcar/koa | `npm i @fastcar/koa @fastcar/server` | Web 开发 |
| @fastcar/mysql | `npm i @fastcar/mysql` | MySQL 数据库 |
| @fastcar/pgsql | `npm i @fastcar/pgsql` | PostgreSQL |
| @fastcar/mongo | `npm i @fastcar/mongo` | MongoDB |
| @fastcar/redis | `npm i @fastcar/redis` | Redis 缓存 |
| @fastcar/cache | `npm i @fastcar/cache` | 缓存组件 |
| @fastcar/timer | `npm i @fastcar/timer` | 定时任务 |
| @fastcar/timewheel | `npm i @fastcar/timewheel` | 时间轮延时任务 |
| @fastcar/workerpool | `npm i @fastcar/workerpool` | 工作线程池 |
| @fastcar/rpc | `npm i @fastcar/rpc` | RPC 通信 |
| @fastcar/serverless | `npm i @fastcar/serverless` | Serverless 支持 |
| @fastcar/cos-sdk | `npm i @fastcar/cos-sdk` | 对象存储 |

## 快速开始新项目

### 使用 CLI 创建项目（推荐）

```bash
# Web 项目
mkdir my-web-app && cd my-web-app
fastcar-cli init web
npm install
npm run debug

# RPC 项目
mkdir my-rpc-app && cd my-rpc-app
fastcar-cli init rpc
npm install
npm run debug

# Microservices 项目
mkdir my-ms-app && cd my-ms-app
fastcar-cli init micro
npm install
npm run start-node
```

## 常见错误与注意事项

### 1. 路由装饰器必须有括号

❌ **错误：**
```typescript
@GET
async list() { }
```

✅ **正确：**
```typescript
@GET()
async list() { }
```

### 1.1 @fastcar 包必须使用 import

❌ **错误：**
```typescript
const { Controller } = require("@fastcar/core/annotation");
const { GET } = require("@fastcar/koa/annotation");
```

✅ **正确：**
```typescript
import { Controller } from "@fastcar/core/annotation";
import { GET } from "@fastcar/koa/annotation";
```

### 2. 不要使用不存在的装饰器

❌ **错误：**
```typescript
import { Body, Param, Query } from "@fastcar/koa/annotation";

@GET("/:id")
async getById(@Param("id") id: string) { }
```

✅ **正确：**
```typescript
@GET("/:id")
async getById(id: string) { }
```

### 3. 表单验证使用 @ValidForm + @Rule

❌ **错误：**
```typescript
@POST()
async create(@Body body: ItemDTO) { }
```

✅ **正确：**
```typescript
@ValidForm
@POST()
async create(@Rule() body: ItemDTO) { }
```

### 4. @Rule 参数不要二次 DTO 转换

`@Rule()` 参数已经由 FastCar 校验并格式化。Controller 不要再调用 `DTO.from(body).toInput()`，否则会造成重复校验、职责混乱，并容易与框架格式化结果冲突。

❌ **错误：**
```typescript
@ValidForm
@POST()
async create(@Rule() body: ItemDTO) {
  return this.service.create(ItemDTO.from(body).toInput());
}
```

✅ **正确：**
```typescript
@ValidForm
@POST()
async create(@Rule() body: ItemDTO) {
  return this.service.create(body);
}
```
