项目开发
Trae IDE 提供了完整的项目开发生命周期支持,从项目创建到部署上线,每个环节都经过精心设计,帮助开发者高效构建现代化应用程序。
项目创建
项目模板
Web 应用模板
bash
# React 项目
trae create my-react-app --template react
# 包含:React 18, TypeScript, Vite, ESLint, Prettier
# Vue 项目
trae create my-vue-app --template vue
# 包含:Vue 3, TypeScript, Vite, Vue Router, Pinia
# Angular 项目
trae create my-angular-app --template angular
# 包含:Angular 17, TypeScript, Angular CLI, RxJS
# Next.js 项目
trae create my-nextjs-app --template nextjs
# 包含:Next.js 14, TypeScript, Tailwind CSS, App Router后端服务模板
bash
# Node.js API
trae create my-api --template node-api
# 包含:Express, TypeScript, Prisma, JWT 认证
# Python FastAPI
trae create my-fastapi --template fastapi
# 包含:FastAPI, SQLAlchemy, Alembic, Pydantic
# Java Spring Boot
trae create my-spring-app --template spring-boot
# 包含:Spring Boot 3, Spring Security, JPA, Maven
# Go Gin API
trae create my-go-api --template go-gin
# 包含:Gin, GORM, JWT, Go Modules全栈项目模板
bash
# MERN Stack
trae create my-mern-app --template mern
# 前端:React + TypeScript + Vite
# 后端:Node.js + Express + MongoDB
# T3 Stack
trae create my-t3-app --template t3
# Next.js + TypeScript + tRPC + Prisma + NextAuth
# Django + React
trae create my-django-react --template django-react
# 后端:Django + DRF + PostgreSQL
# 前端:React + TypeScript + Vite自定义项目结构
项目配置文件
json
// trae.config.json
{
"name": "my-awesome-project",
"version": "1.0.0",
"type": "fullstack",
"structure": {
"frontend": {
"framework": "react",
"language": "typescript",
"bundler": "vite",
"styling": "tailwindcss"
},
"backend": {
"framework": "express",
"language": "typescript",
"database": "postgresql",
"orm": "prisma"
},
"shared": {
"types": "./shared/types",
"utils": "./shared/utils",
"constants": "./shared/constants"
}
},
"scripts": {
"dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"",
"build": "npm run build:frontend && npm run build:backend",
"test": "npm run test:frontend && npm run test:backend"
}
}目录结构生成
my-awesome-project/
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── hooks/
│ │ ├── utils/
│ │ └── types/
│ ├── public/
│ ├── package.json
│ └── vite.config.ts
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ ├── services/
│ │ ├── models/
│ │ ├── middleware/
│ │ └── routes/
│ ├── prisma/
│ ├── package.json
│ └── tsconfig.json
├── shared/
│ ├── types/
│ ├── utils/
│ └── constants/
├── docs/
├── docker-compose.yml
└── README.md开发工作流
智能代码生成
组件生成
jsx
// 输入:"创建用户列表组件"
// AI 生成:
import React, { useState, useEffect } from 'react';
import { User } from '../types/User';
import { userService } from '../services/userService';
interface UserListProps {
onUserSelect?: (user: User) => void;
searchTerm?: string;
}
const UserList: React.FC<UserListProps> = ({ onUserSelect, searchTerm }) => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const userData = await userService.getUsers(searchTerm);
setUsers(userData);
} catch (err) {
setError('Failed to fetch users');
} finally {
setLoading(false);
}
};
fetchUsers();
}, [searchTerm]);
if (loading) return <div className="loading">Loading users...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="user-list">
{users.map(user => (
<div
key={user.id}
className="user-item"
onClick={() => onUserSelect?.(user)}
>
<img src={user.avatar} alt={user.name} className="user-avatar" />
<div className="user-info">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</div>
))}
</div>
);
};
export default UserList;API 路由生成
typescript
// 输入:"创建用户管理 API"
// AI 生成:
import { Router, Request, Response } from 'express';
import { UserService } from '../services/UserService';
import { CreateUserDto, UpdateUserDto } from '../dto/UserDto';
import { authMiddleware } from '../middleware/auth';
import { validateDto } from '../middleware/validation';
const router = Router();
const userService = new UserService();
// 获取所有用户
router.get('/', authMiddleware, async (req: Request, res: Response) => {
try {
const { page = 1, limit = 10, search } = req.query;
const users = await userService.getUsers({
page: Number(page),
limit: Number(limit),
search: search as string
});
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
// 获取单个用户
router.get('/:id', authMiddleware, async (req: Request, res: Response) => {
try {
const user = await userService.getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// 创建用户
router.post('/',
authMiddleware,
validateDto(CreateUserDto),
async (req: Request, res: Response) => {
try {
const user = await userService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: 'Failed to create user' });
}
}
);
// 更新用户
router.put('/:id',
authMiddleware,
validateDto(UpdateUserDto),
async (req: Request, res: Response) => {
try {
const user = await userService.updateUser(req.params.id, req.body);
res.json(user);
} catch (error) {
res.status(400).json({ error: 'Failed to update user' });
}
}
);
// 删除用户
router.delete('/:id', authMiddleware, async (req: Request, res: Response) => {
try {
await userService.deleteUser(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: 'Failed to delete user' });
}
});
export default router;实时协作开发
Live Share 功能
bash
# 启动协作会话
trae share start
# 生成分享链接:https://trae.ai/share/abc123
# 加入协作会话
trae share join abc123
# 协作功能
- 实时代码同步
- 共享终端访问
- 协作调试
- 语音/视频通话
- 共享服务器端口代码审查集成
javascript
// 代码审查配置
{
"codeReview": {
"enabled": true,
"autoAssign": true,
"reviewers": ["@team-lead", "@senior-dev"],
"rules": {
"minReviewers": 2,
"requireApproval": true,
"blockMergeOnFailedChecks": true
},
"checks": {
"linting": true,
"testing": true,
"security": true,
"performance": true
}
}
}构建系统
现代构建工具
Vite 配置
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@types': resolve(__dirname, 'src/types')
}
},
build: {
target: 'esnext',
minify: 'esbuild',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
ui: ['@mui/material', '@emotion/react']
}
}
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});Webpack 配置(高级项目)
javascript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction
? '[name].[contenthash].js'
: '[name].js',
clean: true
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
...(isProduction ? [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
] : [])
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true
}
};
};构建优化
代码分割
typescript
// 路由级代码分割
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}资源优化
javascript
// 图片优化配置
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB
}
},
generator: {
filename: 'images/[name].[hash][ext]'
},
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { progressive: true, quality: 80 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.90], speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 80 }
}
}
]
}测试集成
单元测试
Jest + React Testing Library
typescript
// UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserList } from './UserList';
import { userService } from '../services/userService';
// Mock 服务
jest.mock('../services/userService');
const mockUserService = userService as jest.Mocked<typeof userService>;
const mockUsers = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' }
];
describe('UserList', () => {
beforeEach(() => {
mockUserService.getUsers.mockResolvedValue(mockUsers);
});
afterEach(() => {
jest.clearAllMocks();
});
it('renders user list correctly', async () => {
render(<UserList />);
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('Bob')).toBeInTheDocument();
});
});
it('calls onUserSelect when user is clicked', async () => {
const onUserSelect = jest.fn();
render(<UserList onUserSelect={onUserSelect} />);
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
await userEvent.click(screen.getByText('Alice'));
expect(onUserSelect).toHaveBeenCalledWith(mockUsers[0]);
});
it('displays loading state', () => {
mockUserService.getUsers.mockImplementation(
() => new Promise(resolve => setTimeout(resolve, 1000))
);
render(<UserList />);
expect(screen.getByText('Loading users...')).toBeInTheDocument();
});
it('displays error state', async () => {
mockUserService.getUsers.mockRejectedValue(new Error('API Error'));
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Failed to fetch users')).toBeInTheDocument();
});
});
});后端测试
typescript
// userService.test.ts
import { UserService } from './UserService';
import { UserRepository } from '../repositories/UserRepository';
import { CreateUserDto } from '../dto/UserDto';
jest.mock('../repositories/UserRepository');
const mockUserRepository = UserRepository as jest.MockedClass<typeof UserRepository>;
describe('UserService', () => {
let userService: UserService;
let userRepository: jest.Mocked<UserRepository>;
beforeEach(() => {
userRepository = new mockUserRepository() as jest.Mocked<UserRepository>;
userService = new UserService(userRepository);
});
describe('createUser', () => {
it('should create user successfully', async () => {
const createUserDto: CreateUserDto = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};
const expectedUser = {
id: '1',
...createUserDto,
createdAt: new Date()
};
userRepository.create.mockResolvedValue(expectedUser);
userRepository.findByEmail.mockResolvedValue(null);
const result = await userService.createUser(createUserDto);
expect(userRepository.findByEmail).toHaveBeenCalledWith(createUserDto.email);
expect(userRepository.create).toHaveBeenCalledWith(createUserDto);
expect(result).toEqual(expectedUser);
});
it('should throw error if email already exists', async () => {
const createUserDto: CreateUserDto = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};
userRepository.findByEmail.mockResolvedValue({ id: '1' } as any);
await expect(userService.createUser(createUserDto))
.rejects
.toThrow('Email already exists');
expect(userRepository.create).not.toHaveBeenCalled();
});
});
});端到端测试
Playwright 测试
typescript
// e2e/user-management.spec.ts
import { test, expect } from '@playwright/test';
test.describe('User Management', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/users');
});
test('should display user list', async ({ page }) => {
await expect(page.locator('.user-list')).toBeVisible();
await expect(page.locator('.user-item')).toHaveCount(3);
});
test('should create new user', async ({ page }) => {
await page.click('[data-testid="add-user-button"]');
await page.fill('[data-testid="name-input"]', 'New User');
await page.fill('[data-testid="email-input"]', 'newuser@example.com');
await page.fill('[data-testid="password-input"]', 'password123');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('text=User created successfully')).toBeVisible();
await expect(page.locator('text=New User')).toBeVisible();
});
test('should edit user', async ({ page }) => {
await page.click('[data-testid="user-1"] [data-testid="edit-button"]');
await page.fill('[data-testid="name-input"]', 'Updated Name');
await page.click('[data-testid="save-button"]');
await expect(page.locator('text=User updated successfully')).toBeVisible();
await expect(page.locator('text=Updated Name')).toBeVisible();
});
test('should delete user', async ({ page }) => {
await page.click('[data-testid="user-1"] [data-testid="delete-button"]');
await page.click('[data-testid="confirm-delete"]');
await expect(page.locator('text=User deleted successfully')).toBeVisible();
await expect(page.locator('[data-testid="user-1"]')).not.toBeVisible();
});
});测试配置
Jest 配置
javascript
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy'
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/reportWebVitals.ts'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{ts,tsx}'
]
};包管理
依赖管理
package.json 优化
json
{
"name": "my-awesome-app",
"version": "1.0.0",
"private": true,
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix",
"type-check": "tsc --noEmit",
"prepare": "husky install"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"@tanstack/react-query": "^4.24.0",
"axios": "^1.3.0",
"zustand": "^4.3.0"
},
"devDependencies": {
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^4.9.4",
"vite": "^4.1.0",
"eslint": "^8.34.0",
"prettier": "^2.8.0",
"husky": "^8.0.0",
"lint-staged": "^13.1.0"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}Monorepo 管理
json
// lerna.json
{
"version": "independent",
"npmClient": "npm",
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish",
"registry": "https://registry.npmjs.org/"
},
"bootstrap": {
"ignore": "component-*",
"npmClientArgs": ["--no-package-lock"]
}
},
"packages": [
"packages/*"
]
}
// 工作区结构
packages/
├── shared/ # 共享工具和类型
├── ui-components/ # UI 组件库
├── web-app/ # Web 应用
├── mobile-app/ # 移动应用
└── api-server/ # API 服务版本管理
语义化版本
bash
# 自动版本管理
npm run release:patch # 1.0.0 -> 1.0.1
npm run release:minor # 1.0.0 -> 1.1.0
npm run release:major # 1.0.0 -> 2.0.0
# 预发布版本
npm run release:alpha # 1.0.0 -> 1.0.1-alpha.0
npm run release:beta # 1.0.0 -> 1.0.1-beta.0
npm run release:rc # 1.0.0 -> 1.0.1-rc.0Changelog 生成
markdown
# Changelog
## [1.2.0] - 2024-01-15
### Added
- 新增用户管理功能
- 添加暗色主题支持
- 集成 AI 代码补全
### Changed
- 优化页面加载性能
- 更新 UI 组件库到 v2.0
- 改进错误处理机制
### Fixed
- 修复登录状态丢失问题
- 解决移动端布局异常
- 修复内存泄漏问题
### Security
- 升级依赖包修复安全漏洞
- 加强 API 访问控制数据库集成
ORM 配置
Prisma 配置
prisma
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
avatar String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("posts")
}
enum Role {
USER
ADMIN
}数据库服务
typescript
// services/DatabaseService.ts
import { PrismaClient } from '@prisma/client';
class DatabaseService {
private prisma: PrismaClient;
constructor() {
this.prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
});
}
async connect() {
try {
await this.prisma.$connect();
console.log('Database connected successfully');
} catch (error) {
console.error('Database connection failed:', error);
throw error;
}
}
async disconnect() {
await this.prisma.$disconnect();
}
get client() {
return this.prisma;
}
async healthCheck() {
try {
await this.prisma.$queryRaw`SELECT 1`;
return { status: 'healthy', timestamp: new Date() };
} catch (error) {
return { status: 'unhealthy', error: error.message, timestamp: new Date() };
}
}
}
export const databaseService = new DatabaseService();数据迁移
迁移脚本
sql
-- migrations/001_initial_schema.sql
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"avatar" TEXT,
"role" "Role" NOT NULL DEFAULT 'USER',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "posts" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN NOT NULL DEFAULT false,
"authorId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
ALTER TABLE "posts" ADD CONSTRAINT "posts_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;种子数据
typescript
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
// 创建管理员用户
const adminUser = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
role: 'ADMIN',
},
});
// 创建示例文章
const post1 = await prisma.post.upsert({
where: { id: 'post-1' },
update: {},
create: {
id: 'post-1',
title: 'Welcome to Trae IDE',
content: 'This is your first post created with Trae IDE.',
published: true,
authorId: adminUser.id,
},
});
console.log({ adminUser, post1 });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});API 开发
RESTful API 设计
API 路由结构
typescript
// routes/api.ts
import { Router } from 'express';
import userRoutes from './users';
import postRoutes from './posts';
import authRoutes from './auth';
import { authMiddleware } from '../middleware/auth';
import { rateLimitMiddleware } from '../middleware/rateLimit';
const router = Router();
// 公开路由
router.use('/auth', rateLimitMiddleware, authRoutes);
// 受保护的路由
router.use('/users', authMiddleware, userRoutes);
router.use('/posts', authMiddleware, postRoutes);
// API 健康检查
router.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version
});
});
export default router;API 文档生成
typescript
// swagger.ts
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Trae API',
version: '1.0.0',
description: 'API documentation for Trae application',
},
servers: [
{
url: 'http://localhost:8000/api',
description: 'Development server',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
},
apis: ['./src/routes/*.ts'], // 扫描路由文件中的注释
};
const specs = swaggerJsdoc(options);
export { specs, swaggerUi };
/**
* @swagger
* /users:
* get:
* summary: Get all users
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* description: Number of items per page
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: object
* properties:
* users:
* type: array
* items:
* $ref: '#/components/schemas/User'
* pagination:
* $ref: '#/components/schemas/Pagination'
*/GraphQL API
GraphQL Schema
typescript
// schema/typeDefs.ts
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
type User {
id: ID!
email: String!
name: String
avatar: String
role: Role!
posts: [Post!]!
createdAt: String!
updatedAt: String!
}
type Post {
id: ID!
title: String!
content: String
published: Boolean!
author: User!
createdAt: String!
updatedAt: String!
}
enum Role {
USER
ADMIN
}
type Query {
users(page: Int, limit: Int): UserConnection!
user(id: ID!): User
posts(published: Boolean): [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
}
input CreateUserInput {
email: String!
name: String
role: Role = USER
}
input UpdateUserInput {
email: String
name: String
role: Role
}
input CreatePostInput {
title: String!
content: String
published: Boolean = false
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
type UserConnection {
nodes: [User!]!
pageInfo: PageInfo!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
`;GraphQL Resolvers
typescript
// schema/resolvers.ts
import { databaseService } from '../services/DatabaseService';
import { AuthenticationError, ForbiddenError } from 'apollo-server-express';
export const resolvers = {
Query: {
users: async (_, { page = 1, limit = 10 }, { user }) => {
if (!user) throw new AuthenticationError('Not authenticated');
if (user.role !== 'ADMIN') throw new ForbiddenError('Not authorized');
const skip = (page - 1) * limit;
const users = await databaseService.client.user.findMany({
skip,
take: limit,
orderBy: { createdAt: 'desc' },
});
const total = await databaseService.client.user.count();
const hasNextPage = skip + limit < total;
const hasPreviousPage = page > 1;
return {
nodes: users,
pageInfo: {
hasNextPage,
hasPreviousPage,
startCursor: users[0]?.id,
endCursor: users[users.length - 1]?.id,
},
};
},
user: async (_, { id }, { user }) => {
if (!user) throw new AuthenticationError('Not authenticated');
return databaseService.client.user.findUnique({
where: { id },
include: { posts: true },
});
},
posts: async (_, { published }, { user }) => {
if (!user) throw new AuthenticationError('Not authenticated');
return databaseService.client.post.findMany({
where: published !== undefined ? { published } : {},
include: { author: true },
orderBy: { createdAt: 'desc' },
});
},
},
Mutation: {
createUser: async (_, { input }, { user }) => {
if (!user || user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
return databaseService.client.user.create({
data: input,
});
},
createPost: async (_, { input }, { user }) => {
if (!user) throw new AuthenticationError('Not authenticated');
return databaseService.client.post.create({
data: {
...input,
authorId: user.id,
},
include: { author: true },
});
},
},
User: {
posts: async (parent) => {
return databaseService.client.post.findMany({
where: { authorId: parent.id },
});
},
},
Post: {
author: async (parent) => {
return databaseService.client.user.findUnique({
where: { id: parent.authorId },
});
},
},
};部署配置
Docker 容器化
Dockerfile
dockerfile
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
COPY prisma ./prisma/
# 安装依赖
RUN npm ci --only=production && npm cache clean --force
# 生成 Prisma 客户端
RUN npx prisma generate
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM node:18-alpine AS production
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
# 切换到非 root 用户
USER nextjs
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/api/health || exit 1
# 启动应用
CMD ["npm", "start"]docker-compose.yml
yaml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- JWT_SECRET=your-jwt-secret
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./uploads:/app/uploads
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
default:
driver: bridgeCI/CD 流水线
GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run type checking
run: npm run type-check
- name: Run tests
run: npm run test:coverage
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push Docker image
run: |
docker push myapp:${{ github.sha }}
docker push myapp:latest
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker system prune -f环境管理
环境配置
环境变量管理
typescript
// config/environment.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.string().transform(Number).default('8000'),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional(),
AWS_ACCESS_KEY_ID: z.string().optional(),
AWS_SECRET_ACCESS_KEY: z.string().optional(),
AWS_REGION: z.string().default('us-east-1'),
SMTP_HOST: z.string().optional(),
SMTP_PORT: z.string().transform(Number).optional(),
SMTP_USER: z.string().optional(),
SMTP_PASS: z.string().optional(),
});
const env = envSchema.parse(process.env);
export const config = {
app: {
env: env.NODE_ENV,
port: env.PORT,
isDevelopment: env.NODE_ENV === 'development',
isProduction: env.NODE_ENV === 'production',
isTest: env.NODE_ENV === 'test',
},
database: {
url: env.DATABASE_URL,
},
auth: {
jwtSecret: env.JWT_SECRET,
jwtExpiresIn: '7d',
},
redis: {
url: env.REDIS_URL,
},
aws: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
region: env.AWS_REGION,
},
email: {
host: env.SMTP_HOST,
port: env.SMTP_PORT,
user: env.SMTP_USER,
pass: env.SMTP_PASS,
},
};多环境配置
bash
# .env.development
NODE_ENV=development
PORT=8000
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp_dev
JWT_SECRET=development-jwt-secret-key-32-chars
REDIS_URL=redis://localhost:6379
LOG_LEVEL=debug
# .env.production
NODE_ENV=production
PORT=8000
DATABASE_URL=${DATABASE_URL}
JWT_SECRET=${JWT_SECRET}
REDIS_URL=${REDIS_URL}
LOG_LEVEL=info
# .env.test
NODE_ENV=test
PORT=8001
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp_test
JWT_SECRET=test-jwt-secret-key-32-characters
LOG_LEVEL=silent代码质量
代码规范
ESLint 配置
javascript
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
plugins: [
'@typescript-eslint',
'react',
'react-hooks',
'jsx-a11y',
'import'
],
rules: {
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index'
],
'newlines-between': 'always'
}
],
'prefer-const': 'error',
'no-var': 'error',
'no-console': 'warn'
},
settings: {
react: {
version: 'detect'
}
}
};Prettier 配置
json
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "lf"
}Git Hooks
Husky 配置
bash
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx commitlint --edit $1
# .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run type-check
npm run testCommitlint 配置
javascript
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修复
'docs', // 文档
'style', // 格式
'refactor', // 重构
'perf', // 性能优化
'test', // 测试
'chore', // 构建过程或辅助工具的变动
'ci', // CI 配置
'build', // 构建系统
'revert' // 回滚
]
],
'subject-max-length': [2, 'always', 100],
'subject-case': [2, 'never', ['pascal-case', 'upper-case']]
}
};监控和分析
性能监控
应用性能监控
typescript
// monitoring/performance.ts
import { performance } from 'perf_hooks';
import { Request, Response, NextFunction } from 'express';
interface PerformanceMetrics {
endpoint: string;
method: string;
duration: number;
statusCode: number;
timestamp: Date;
}
class PerformanceMonitor {
private metrics: PerformanceMetrics[] = [];
middleware() {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = performance.now();
res.on('finish', () => {
const endTime = performance.now();
const duration = endTime - startTime;
const metric: PerformanceMetrics = {
endpoint: req.route?.path || req.path,
method: req.method,
duration,
statusCode: res.statusCode,
timestamp: new Date()
};
this.recordMetric(metric);
// 发送到监控服务
this.sendToMonitoring(metric);
}
private sendToMonitoring(metric: PerformanceMetrics) {
// 发送到 Prometheus、DataDog 等监控服务
if (process.env.MONITORING_ENABLED === 'true') {
// 实现监控数据发送逻辑
}
}
getMetrics() {
return this.metrics;
}
getAverageResponseTime(endpoint?: string) {
const filteredMetrics = endpoint
? this.metrics.filter(m => m.endpoint === endpoint)
: this.metrics;
if (filteredMetrics.length === 0) return 0;
const total = filteredMetrics.reduce((sum, m) => sum + m.duration, 0);
return total / filteredMetrics.length;
}
}
export const performanceMonitor = new PerformanceMonitor();错误监控
typescript
// monitoring/errorTracking.ts
import { Request, Response, NextFunction } from 'express';
interface ErrorLog {
message: string;
stack?: string;
endpoint: string;
method: string;
userAgent?: string;
ip: string;
timestamp: Date;
userId?: string;
}
class ErrorTracker {
private errors: ErrorLog[] = [];
middleware() {
return (error: Error, req: Request, res: Response, next: NextFunction) => {
const errorLog: ErrorLog = {
message: error.message,
stack: error.stack,
endpoint: req.path,
method: req.method,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date(),
userId: req.user?.id
};
this.logError(errorLog);
// 发送到错误追踪服务(如 Sentry)
this.sendToErrorService(error, req);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
});
};
}
private logError(errorLog: ErrorLog) {
this.errors.push(errorLog);
console.error('Error logged:', errorLog);
}
private sendToErrorService(error: Error, req: Request) {
// 集成 Sentry 或其他错误追踪服务
if (process.env.SENTRY_DSN) {
// Sentry.captureException(error, { req });
}
}
}
export const errorTracker = new ErrorTracker();日志管理
结构化日志
typescript
// utils/logger.ts
import winston from 'winston';
import { config } from '../config/environment';
const logFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
);
const logger = winston.createLogger({
level: config.app.isDevelopment ? 'debug' : 'info',
format: logFormat,
defaultMeta: { service: 'trae-api' },
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
if (config.app.isDevelopment) {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
export { logger };团队协作
代码审查流程
Pull Request 模板
markdown
<!-- .github/pull_request_template.md -->
## 变更描述
### 变更类型
- [ ] 新功能 (feature)
- [ ] 修复 (fix)
- [ ] 文档 (docs)
- [ ] 样式 (style)
- [ ] 重构 (refactor)
- [ ] 性能优化 (perf)
- [ ] 测试 (test)
- [ ] 构建 (build)
- [ ] CI (ci)
- [ ] 其他 (chore)
### 变更内容
-
-
-
### 测试
- [ ] 单元测试已通过
- [ ] 集成测试已通过
- [ ] 手动测试已完成
- [ ] 性能测试已完成(如适用)
### 检查清单
- [ ] 代码遵循项目规范
- [ ] 已添加必要的测试
- [ ] 文档已更新
- [ ] 无破坏性变更
- [ ] 已考虑向后兼容性
### 截图(如适用)
### 相关 Issue
Closes #
### 额外说明代码审查指南
markdown
# 代码审查指南
## 审查重点
### 功能性
- [ ] 代码是否实现了预期功能
- [ ] 边界条件是否处理正确
- [ ] 错误处理是否完善
- [ ] 性能是否满足要求
### 代码质量
- [ ] 代码结构是否清晰
- [ ] 命名是否有意义
- [ ] 是否遵循 DRY 原则
- [ ] 是否有代码重复
### 安全性
- [ ] 是否存在安全漏洞
- [ ] 输入验证是否充分
- [ ] 敏感信息是否正确处理
- [ ] 权限控制是否合理
### 测试
- [ ] 测试覆盖率是否足够
- [ ] 测试用例是否全面
- [ ] 测试是否可靠
### 文档
- [ ] 代码注释是否清晰
- [ ] API 文档是否更新
- [ ] README 是否需要更新项目管理
Issue 模板
markdown
<!-- .github/ISSUE_TEMPLATE/bug_report.md -->
---
name: Bug 报告
about: 创建一个 bug 报告来帮助我们改进
title: '[BUG] '
labels: 'bug'
assignees: ''
---
## Bug 描述
简洁明了地描述这个 bug。
## 复现步骤
1. 进入 '...'
2. 点击 '....'
3. 滚动到 '....'
4. 看到错误
## 期望行为
简洁明了地描述你期望发生什么。
## 实际行为
简洁明了地描述实际发生了什么。
## 截图
如果适用,添加截图来帮助解释你的问题。
## 环境信息
- OS: [e.g. macOS, Windows, Linux]
- 浏览器: [e.g. Chrome, Safari, Firefox]
- 版本: [e.g. 22]
- Node.js 版本: [e.g. 18.0.0]
## 额外信息
在这里添加关于问题的任何其他信息。最佳实践
开发规范
文件组织
src/
├── components/ # 可复用组件
│ ├── ui/ # 基础 UI 组件
│ ├── forms/ # 表单组件
│ └── layout/ # 布局组件
├── pages/ # 页面组件
├── hooks/ # 自定义 Hooks
├── services/ # API 服务
├── utils/ # 工具函数
├── types/ # TypeScript 类型定义
├── constants/ # 常量定义
├── styles/ # 样式文件
└── tests/ # 测试文件命名规范
typescript
// 组件命名:PascalCase
const UserProfile = () => {};
const NavigationBar = () => {};
// 函数命名:camelCase
const getUserData = () => {};
const handleSubmit = () => {};
// 常量命名:SCREAMING_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;
// 文件命名:kebab-case
// user-profile.component.tsx
// api-service.ts
// user-types.ts代码注释
typescript
/**
* 用户服务类
* 处理所有与用户相关的 API 操作
*/
class UserService {
/**
* 获取用户信息
* @param userId - 用户 ID
* @returns Promise<User> 用户信息
* @throws {NotFoundError} 当用户不存在时
*/
async getUser(userId: string): Promise<User> {
// 实现逻辑
}
/**
* 更新用户信息
* @param userId - 用户 ID
* @param updateData - 要更新的数据
* @returns Promise<User> 更新后的用户信息
*/
async updateUser(userId: string, updateData: Partial<User>): Promise<User> {
// 实现逻辑
}
}性能优化
React 性能优化
typescript
// 使用 React.memo 优化组件渲染
const UserCard = React.memo(({ user, onEdit }: UserCardProps) => {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
});
// 使用 useMemo 优化计算
const ExpensiveComponent = ({ items }: { items: Item[] }) => {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>总计: {expensiveValue}</div>;
};
// 使用 useCallback 优化函数引用
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <ChildComponent onIncrement={handleIncrement} />;
};代码分割和懒加载
typescript
// 路由级代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserManagement = lazy(() => import('./pages/UserManagement'));
const Settings = lazy(() => import('./pages/Settings'));
// 组件级懒加载
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const App = () => {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/users" element={<UserManagement />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
};入门指南
快速开始
- 创建新项目
bash
trae create my-project --template react-typescript
cd my-project- 启动开发服务器
bash
npm run dev- 开始开发
- 在
src/目录下编写代码 - 使用 AI 助手生成组件和功能
- 实时预览更改
- 构建和部署
bash
npm run build
npm run deploy学习资源
通过 Trae IDE 的项目开发功能,你可以高效地构建现代化的 Web 应用程序,从项目初始化到生产部署,每个环节都有完善的工具支持。