Revert "feat(ecc): prune plugin 43→12 items, promote 7 rules to .claude/rules/ (#245)"

This reverts commit 1bd68ff534.
This commit is contained in:
Affaan Mustafa
2026-02-20 01:11:30 -08:00
parent 1bd68ff534
commit 0e9f613fd1
536 changed files with 111479 additions and 253 deletions

View File

@@ -0,0 +1,587 @@
---
name: backend-patterns
description: 后端架构模式、API设计、数据库优化以及针对Node.js、Express和Next.js API路由的服务器端最佳实践。
---
# 后端开发模式
用于可扩展服务器端应用程序的后端架构模式和最佳实践。
## API 设计模式
### RESTful API 结构
```typescript
// ✅ Resource-based URLs
GET /api/markets # List resources
GET /api/markets/:id # Get single resource
POST /api/markets # Create resource
PUT /api/markets/:id # Replace resource
PATCH /api/markets/:id # Update resource
DELETE /api/markets/:id # Delete resource
// ✅ Query parameters for filtering, sorting, pagination
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
### 仓储模式
```typescript
// Abstract data access logic
interface MarketRepository {
findAll(filters?: MarketFilters): Promise<Market[]>
findById(id: string): Promise<Market | null>
create(data: CreateMarketDto): Promise<Market>
update(id: string, data: UpdateMarketDto): Promise<Market>
delete(id: string): Promise<void>
}
class SupabaseMarketRepository implements MarketRepository {
async findAll(filters?: MarketFilters): Promise<Market[]> {
let query = supabase.from('markets').select('*')
if (filters?.status) {
query = query.eq('status', filters.status)
}
if (filters?.limit) {
query = query.limit(filters.limit)
}
const { data, error } = await query
if (error) throw new Error(error.message)
return data
}
// Other methods...
}
```
### 服务层模式
```typescript
// Business logic separated from data access
class MarketService {
constructor(private marketRepo: MarketRepository) {}
async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {
// Business logic
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
// Fetch full data
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
// Sort by similarity
return markets.sort((a, b) => {
const scoreA = results.find(r => r.id === a.id)?.score || 0
const scoreB = results.find(r => r.id === b.id)?.score || 0
return scoreA - scoreB
})
}
private async vectorSearch(embedding: number[], limit: number) {
// Vector search implementation
}
}
```
### 中间件模式
```typescript
// Request/response processing pipeline
export function withAuth(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({ error: 'Unauthorized' })
}
try {
const user = await verifyToken(token)
req.user = user
return handler(req, res)
} catch (error) {
return res.status(401).json({ error: 'Invalid token' })
}
}
}
// Usage
export default withAuth(async (req, res) => {
// Handler has access to req.user
})
```
## 数据库模式
### 查询优化
```typescript
// ✅ GOOD: Select only needed columns
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
.eq('status', 'active')
.order('volume', { ascending: false })
.limit(10)
// ❌ BAD: Select everything
const { data } = await supabase
.from('markets')
.select('*')
```
### N+1 查询预防
```typescript
// ❌ BAD: N+1 query problem
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N queries
}
// ✅ GOOD: Batch fetch
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 query
const creatorMap = new Map(creators.map(c => [c.id, c]))
markets.forEach(market => {
market.creator = creatorMap.get(market.creator_id)
})
```
### 事务模式
```typescript
async function createMarketWithPosition(
marketData: CreateMarketDto,
positionData: CreatePositionDto
) {
// Use Supabase transaction
const { data, error } = await supabase.rpc('create_market_with_position', {
market_data: marketData,
position_data: positionData
})
if (error) throw new Error('Transaction failed')
return data
}
// SQL function in Supabase
CREATE OR REPLACE FUNCTION create_market_with_position(
market_data jsonb,
position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $
BEGIN
-- Start transaction automatically
INSERT INTO markets VALUES (market_data);
INSERT INTO positions VALUES (position_data);
RETURN jsonb_build_object('success', true);
EXCEPTION
WHEN OTHERS THEN
-- Rollback happens automatically
RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$;
```
## 缓存策略
### Redis 缓存层
```typescript
class CachedMarketRepository implements MarketRepository {
constructor(
private baseRepo: MarketRepository,
private redis: RedisClient
) {}
async findById(id: string): Promise<Market | null> {
// Check cache first
const cached = await this.redis.get(`market:${id}`)
if (cached) {
return JSON.parse(cached)
}
// Cache miss - fetch from database
const market = await this.baseRepo.findById(id)
if (market) {
// Cache for 5 minutes
await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
}
return market
}
async invalidateCache(id: string): Promise<void> {
await this.redis.del(`market:${id}`)
}
}
```
### 旁路缓存模式
```typescript
async function getMarketWithCache(id: string): Promise<Market> {
const cacheKey = `market:${id}`
// Try cache
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// Cache miss - fetch from DB
const market = await db.markets.findUnique({ where: { id } })
if (!market) throw new Error('Market not found')
// Update cache
await redis.setex(cacheKey, 300, JSON.stringify(market))
return market
}
```
## 错误处理模式
### 集中式错误处理程序
```typescript
class ApiError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message)
Object.setPrototypeOf(this, ApiError.prototype)
}
}
export function errorHandler(error: unknown, req: Request): Response {
if (error instanceof ApiError) {
return NextResponse.json({
success: false,
error: error.message
}, { status: error.statusCode })
}
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
// Log unexpected errors
console.error('Unexpected error:', error)
return NextResponse.json({
success: false,
error: 'Internal server error'
}, { status: 500 })
}
// Usage
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return errorHandler(error, request)
}
}
```
### 指数退避重试
```typescript
async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error) {
lastError = error as Error
if (i < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
throw lastError!
}
// Usage
const data = await fetchWithRetry(() => fetchFromAPI())
```
## 认证与授权
### JWT 令牌验证
```typescript
import jwt from 'jsonwebtoken'
interface JWTPayload {
userId: string
email: string
role: 'admin' | 'user'
}
export function verifyToken(token: string): JWTPayload {
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload
return payload
} catch (error) {
throw new ApiError(401, 'Invalid token')
}
}
export async function requireAuth(request: Request) {
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
throw new ApiError(401, 'Missing authorization token')
}
return verifyToken(token)
}
// Usage in API route
export async function GET(request: Request) {
const user = await requireAuth(request)
const data = await getDataForUser(user.userId)
return NextResponse.json({ success: true, data })
}
```
### 基于角色的访问控制
```typescript
type Permission = 'read' | 'write' | 'delete' | 'admin'
interface User {
id: string
role: 'admin' | 'moderator' | 'user'
}
const rolePermissions: Record<User['role'], Permission[]> = {
admin: ['read', 'write', 'delete', 'admin'],
moderator: ['read', 'write', 'delete'],
user: ['read', 'write']
}
export function hasPermission(user: User, permission: Permission): boolean {
return rolePermissions[user.role].includes(permission)
}
export function requirePermission(permission: Permission) {
return (handler: (request: Request, user: User) => Promise<Response>) => {
return async (request: Request) => {
const user = await requireAuth(request)
if (!hasPermission(user, permission)) {
throw new ApiError(403, 'Insufficient permissions')
}
return handler(request, user)
}
}
}
// Usage - HOF wraps the handler
export const DELETE = requirePermission('delete')(
async (request: Request, user: User) => {
// Handler receives authenticated user with verified permission
return new Response('Deleted', { status: 200 })
}
)
```
## 速率限制
### 简单的内存速率限制器
```typescript
class RateLimiter {
private requests = new Map<string, number[]>()
async checkLimit(
identifier: string,
maxRequests: number,
windowMs: number
): Promise<boolean> {
const now = Date.now()
const requests = this.requests.get(identifier) || []
// Remove old requests outside window
const recentRequests = requests.filter(time => now - time < windowMs)
if (recentRequests.length >= maxRequests) {
return false // Rate limit exceeded
}
// Add current request
recentRequests.push(now)
this.requests.set(identifier, recentRequests)
return true
}
}
const limiter = new RateLimiter()
export async function GET(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const allowed = await limiter.checkLimit(ip, 100, 60000) // 100 req/min
if (!allowed) {
return NextResponse.json({
error: 'Rate limit exceeded'
}, { status: 429 })
}
// Continue with request
}
```
## 后台作业与队列
### 简单队列模式
```typescript
class JobQueue<T> {
private queue: T[] = []
private processing = false
async add(job: T): Promise<void> {
this.queue.push(job)
if (!this.processing) {
this.process()
}
}
private async process(): Promise<void> {
this.processing = true
while (this.queue.length > 0) {
const job = this.queue.shift()!
try {
await this.execute(job)
} catch (error) {
console.error('Job failed:', error)
}
}
this.processing = false
}
private async execute(job: T): Promise<void> {
// Job execution logic
}
}
// Usage for indexing markets
interface IndexJob {
marketId: string
}
const indexQueue = new JobQueue<IndexJob>()
export async function POST(request: Request) {
const { marketId } = await request.json()
// Add to queue instead of blocking
await indexQueue.add({ marketId })
return NextResponse.json({ success: true, message: 'Job queued' })
}
```
## 日志记录与监控
### 结构化日志记录
```typescript
interface LogContext {
userId?: string
requestId?: string
method?: string
path?: string
[key: string]: unknown
}
class Logger {
log(level: 'info' | 'warn' | 'error', message: string, context?: LogContext) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...context
}
console.log(JSON.stringify(entry))
}
info(message: string, context?: LogContext) {
this.log('info', message, context)
}
warn(message: string, context?: LogContext) {
this.log('warn', message, context)
}
error(message: string, error: Error, context?: LogContext) {
this.log('error', message, {
...context,
error: error.message,
stack: error.stack
})
}
}
const logger = new Logger()
// Usage
export async function GET(request: Request) {
const requestId = crypto.randomUUID()
logger.info('Fetching markets', {
requestId,
method: 'GET',
path: '/api/markets'
})
try {
const markets = await fetchMarkets()
return NextResponse.json({ success: true, data: markets })
} catch (error) {
logger.error('Failed to fetch markets', error as Error, { requestId })
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
}
}
```
**记住**:后端模式支持可扩展、可维护的服务器端应用程序。选择适合你复杂程度的模式。

View File

@@ -0,0 +1,435 @@
---
name: clickhouse-io
description: ClickHouse数据库模式、查询优化、分析和数据工程最佳实践适用于高性能分析工作负载。
---
# ClickHouse 分析模式
用于高性能分析和数据工程的 ClickHouse 特定模式。
## 概述
ClickHouse 是一个用于在线分析处理 (OLAP) 的列式数据库管理系统 (DBMS)。它针对大型数据集上的快速分析查询进行了优化。
**关键特性:**
* 列式存储
* 数据压缩
* 并行查询执行
* 分布式查询
* 实时分析
## 表设计模式
### MergeTree 引擎 (最常用)
```sql
CREATE TABLE markets_analytics (
date Date,
market_id String,
market_name String,
volume UInt64,
trades UInt32,
unique_traders UInt32,
avg_trade_size Float64,
created_at DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, market_id)
SETTINGS index_granularity = 8192;
```
### ReplacingMergeTree (去重)
```sql
-- For data that may have duplicates (e.g., from multiple sources)
CREATE TABLE user_events (
event_id String,
user_id String,
event_type String,
timestamp DateTime,
properties String
) ENGINE = ReplacingMergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (user_id, event_id, timestamp)
PRIMARY KEY (user_id, event_id);
```
### AggregatingMergeTree (预聚合)
```sql
-- For maintaining aggregated metrics
CREATE TABLE market_stats_hourly (
hour DateTime,
market_id String,
total_volume AggregateFunction(sum, UInt64),
total_trades AggregateFunction(count, UInt32),
unique_users AggregateFunction(uniq, String)
) ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (hour, market_id);
-- Query aggregated data
SELECT
hour,
market_id,
sumMerge(total_volume) AS volume,
countMerge(total_trades) AS trades,
uniqMerge(unique_users) AS users
FROM market_stats_hourly
WHERE hour >= toStartOfHour(now() - INTERVAL 24 HOUR)
GROUP BY hour, market_id
ORDER BY hour DESC;
```
## 查询优化模式
### 高效过滤
```sql
-- ✅ GOOD: Use indexed columns first
SELECT *
FROM markets_analytics
WHERE date >= '2025-01-01'
AND market_id = 'market-123'
AND volume > 1000
ORDER BY date DESC
LIMIT 100;
-- ❌ BAD: Filter on non-indexed columns first
SELECT *
FROM markets_analytics
WHERE volume > 1000
AND market_name LIKE '%election%'
AND date >= '2025-01-01';
```
### 聚合
```sql
-- ✅ GOOD: Use ClickHouse-specific aggregation functions
SELECT
toStartOfDay(created_at) AS day,
market_id,
sum(volume) AS total_volume,
count() AS total_trades,
uniq(trader_id) AS unique_traders,
avg(trade_size) AS avg_size
FROM trades
WHERE created_at >= today() - INTERVAL 7 DAY
GROUP BY day, market_id
ORDER BY day DESC, total_volume DESC;
-- ✅ Use quantile for percentiles (more efficient than percentile)
SELECT
quantile(0.50)(trade_size) AS median,
quantile(0.95)(trade_size) AS p95,
quantile(0.99)(trade_size) AS p99
FROM trades
WHERE created_at >= now() - INTERVAL 1 HOUR;
```
### 窗口函数
```sql
-- Calculate running totals
SELECT
date,
market_id,
volume,
sum(volume) OVER (
PARTITION BY market_id
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_volume
FROM markets_analytics
WHERE date >= today() - INTERVAL 30 DAY
ORDER BY market_id, date;
```
## 数据插入模式
### 批量插入 (推荐)
```typescript
import { ClickHouse } from 'clickhouse'
const clickhouse = new ClickHouse({
url: process.env.CLICKHOUSE_URL,
port: 8123,
basicAuth: {
username: process.env.CLICKHOUSE_USER,
password: process.env.CLICKHOUSE_PASSWORD
}
})
// ✅ Batch insert (efficient)
async function bulkInsertTrades(trades: Trade[]) {
const values = trades.map(trade => `(
'${trade.id}',
'${trade.market_id}',
'${trade.user_id}',
${trade.amount},
'${trade.timestamp.toISOString()}'
)`).join(',')
await clickhouse.query(`
INSERT INTO trades (id, market_id, user_id, amount, timestamp)
VALUES ${values}
`).toPromise()
}
// ❌ Individual inserts (slow)
async function insertTrade(trade: Trade) {
// Don't do this in a loop!
await clickhouse.query(`
INSERT INTO trades VALUES ('${trade.id}', ...)
`).toPromise()
}
```
### 流式插入
```typescript
// For continuous data ingestion
import { createWriteStream } from 'fs'
import { pipeline } from 'stream/promises'
async function streamInserts() {
const stream = clickhouse.insert('trades').stream()
for await (const batch of dataSource) {
stream.write(batch)
}
await stream.end()
}
```
## 物化视图
### 实时聚合
```sql
-- Create materialized view for hourly stats
CREATE MATERIALIZED VIEW market_stats_hourly_mv
TO market_stats_hourly
AS SELECT
toStartOfHour(timestamp) AS hour,
market_id,
sumState(amount) AS total_volume,
countState() AS total_trades,
uniqState(user_id) AS unique_users
FROM trades
GROUP BY hour, market_id;
-- Query the materialized view
SELECT
hour,
market_id,
sumMerge(total_volume) AS volume,
countMerge(total_trades) AS trades,
uniqMerge(unique_users) AS users
FROM market_stats_hourly
WHERE hour >= now() - INTERVAL 24 HOUR
GROUP BY hour, market_id;
```
## 性能监控
### 查询性能
```sql
-- Check slow queries
SELECT
query_id,
user,
query,
query_duration_ms,
read_rows,
read_bytes,
memory_usage
FROM system.query_log
WHERE type = 'QueryFinish'
AND query_duration_ms > 1000
AND event_time >= now() - INTERVAL 1 HOUR
ORDER BY query_duration_ms DESC
LIMIT 10;
```
### 表统计信息
```sql
-- Check table sizes
SELECT
database,
table,
formatReadableSize(sum(bytes)) AS size,
sum(rows) AS rows,
max(modification_time) AS latest_modification
FROM system.parts
WHERE active
GROUP BY database, table
ORDER BY sum(bytes) DESC;
```
## 常见分析查询
### 时间序列分析
```sql
-- Daily active users
SELECT
toDate(timestamp) AS date,
uniq(user_id) AS daily_active_users
FROM events
WHERE timestamp >= today() - INTERVAL 30 DAY
GROUP BY date
ORDER BY date;
-- Retention analysis
SELECT
signup_date,
countIf(days_since_signup = 0) AS day_0,
countIf(days_since_signup = 1) AS day_1,
countIf(days_since_signup = 7) AS day_7,
countIf(days_since_signup = 30) AS day_30
FROM (
SELECT
user_id,
min(toDate(timestamp)) AS signup_date,
toDate(timestamp) AS activity_date,
dateDiff('day', signup_date, activity_date) AS days_since_signup
FROM events
GROUP BY user_id, activity_date
)
GROUP BY signup_date
ORDER BY signup_date DESC;
```
### 漏斗分析
```sql
-- Conversion funnel
SELECT
countIf(step = 'viewed_market') AS viewed,
countIf(step = 'clicked_trade') AS clicked,
countIf(step = 'completed_trade') AS completed,
round(clicked / viewed * 100, 2) AS view_to_click_rate,
round(completed / clicked * 100, 2) AS click_to_completion_rate
FROM (
SELECT
user_id,
session_id,
event_type AS step
FROM events
WHERE event_date = today()
)
GROUP BY session_id;
```
### 队列分析
```sql
-- User cohorts by signup month
SELECT
toStartOfMonth(signup_date) AS cohort,
toStartOfMonth(activity_date) AS month,
dateDiff('month', cohort, month) AS months_since_signup,
count(DISTINCT user_id) AS active_users
FROM (
SELECT
user_id,
min(toDate(timestamp)) OVER (PARTITION BY user_id) AS signup_date,
toDate(timestamp) AS activity_date
FROM events
)
GROUP BY cohort, month, months_since_signup
ORDER BY cohort, months_since_signup;
```
## 数据流水线模式
### ETL 模式
```typescript
// Extract, Transform, Load
async function etlPipeline() {
// 1. Extract from source
const rawData = await extractFromPostgres()
// 2. Transform
const transformed = rawData.map(row => ({
date: new Date(row.created_at).toISOString().split('T')[0],
market_id: row.market_slug,
volume: parseFloat(row.total_volume),
trades: parseInt(row.trade_count)
}))
// 3. Load to ClickHouse
await bulkInsertToClickHouse(transformed)
}
// Run periodically
setInterval(etlPipeline, 60 * 60 * 1000) // Every hour
```
### 变更数据捕获 (CDC)
```typescript
// Listen to PostgreSQL changes and sync to ClickHouse
import { Client } from 'pg'
const pgClient = new Client({ connectionString: process.env.DATABASE_URL })
pgClient.query('LISTEN market_updates')
pgClient.on('notification', async (msg) => {
const update = JSON.parse(msg.payload)
await clickhouse.insert('market_updates', [
{
market_id: update.id,
event_type: update.operation, // INSERT, UPDATE, DELETE
timestamp: new Date(),
data: JSON.stringify(update.new_data)
}
])
})
```
## 最佳实践
### 1. 分区策略
* 按时间分区 (通常是月或日)
* 避免过多分区 (影响性能)
* 对分区键使用 DATE 类型
### 2. 排序键
* 将最常过滤的列放在前面
* 考虑基数 (高基数优先)
* 排序影响压缩
### 3. 数据类型
* 使用最合适的较小类型 (UInt32 对比 UInt64)
* 对重复字符串使用 LowCardinality
* 对分类数据使用 Enum
### 4. 避免
* SELECT \* (指定列)
* FINAL (改为在查询前合并数据)
* 过多的 JOIN (分析场景下进行反规范化)
* 频繁的小批量插入 (改为批量)
### 5. 监控
* 跟踪查询性能
* 监控磁盘使用情况
* 检查合并操作
* 查看慢查询日志
**记住**: ClickHouse 擅长分析工作负载。根据查询模式设计表,批量插入,并利用物化视图进行实时聚合。

View File

@@ -0,0 +1,527 @@
---
name: coding-standards
description: 适用于TypeScript、JavaScript、React和Node.js开发的通用编码标准、最佳实践和模式。
---
# 编码标准与最佳实践
适用于所有项目的通用编码标准。
## 代码质量原则
### 1. 可读性优先
* 代码被阅读的次数远多于被编写的次数
* 清晰的变量和函数名
* 优先选择自文档化代码,而非注释
* 一致的格式化
### 2. KISS (保持简单,傻瓜)
* 采用能工作的最简单方案
* 避免过度设计
* 不要过早优化
* 易于理解 > 聪明的代码
### 3. DRY (不要重复自己)
* 将通用逻辑提取到函数中
* 创建可复用的组件
* 跨模块共享工具函数
* 避免复制粘贴式编程
### 4. YAGNI (你不会需要它)
* 不要预先构建不需要的功能
* 避免推测性泛化
* 仅在需要时增加复杂性
* 从简单开始,需要时再重构
## TypeScript/JavaScript 标准
### 变量命名
```typescript
// ✅ GOOD: Descriptive names
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000
// ❌ BAD: Unclear names
const q = 'election'
const flag = true
const x = 1000
```
### 函数命名
```typescript
// ✅ GOOD: Verb-noun pattern
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }
// ❌ BAD: Unclear or noun-only
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }
```
### 不可变性模式 (关键)
```typescript
// ✅ ALWAYS use spread operator
const updatedUser = {
...user,
name: 'New Name'
}
const updatedArray = [...items, newItem]
// ❌ NEVER mutate directly
user.name = 'New Name' // BAD
items.push(newItem) // BAD
```
### 错误处理
```typescript
// ✅ GOOD: Comprehensive error handling
async function fetchData(url: string) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
console.error('Fetch failed:', error)
throw new Error('Failed to fetch data')
}
}
// ❌ BAD: No error handling
async function fetchData(url) {
const response = await fetch(url)
return response.json()
}
```
### Async/Await 最佳实践
```typescript
// ✅ GOOD: Parallel execution when possible
const [users, markets, stats] = await Promise.all([
fetchUsers(),
fetchMarkets(),
fetchStats()
])
// ❌ BAD: Sequential when unnecessary
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()
```
### 类型安全
```typescript
// ✅ GOOD: Proper types
interface Market {
id: string
name: string
status: 'active' | 'resolved' | 'closed'
created_at: Date
}
function getMarket(id: string): Promise<Market> {
// Implementation
}
// ❌ BAD: Using 'any'
function getMarket(id: any): Promise<any> {
// Implementation
}
```
## React 最佳实践
### 组件结构
```typescript
// ✅ GOOD: Functional component with types
interface ButtonProps {
children: React.ReactNode
onClick: () => void
disabled?: boolean
variant?: 'primary' | 'secondary'
}
export function Button({
children,
onClick,
disabled = false,
variant = 'primary'
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{children}
</button>
)
}
// ❌ BAD: No types, unclear structure
export function Button(props) {
return <button onClick={props.onClick}>{props.children}</button>
}
```
### 自定义 Hooks
```typescript
// ✅ GOOD: Reusable custom hook
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
// Usage
const debouncedQuery = useDebounce(searchQuery, 500)
```
### 状态管理
```typescript
// ✅ GOOD: Proper state updates
const [count, setCount] = useState(0)
// Functional update for state based on previous state
setCount(prev => prev + 1)
// ❌ BAD: Direct state reference
setCount(count + 1) // Can be stale in async scenarios
```
### 条件渲染
```typescript
// ✅ GOOD: Clear conditional rendering
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataDisplay data={data} />}
// ❌ BAD: Ternary hell
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
```
## API 设计标准
### REST API 约定
```
GET /api/markets # List all markets
GET /api/markets/:id # Get specific market
POST /api/markets # Create new market
PUT /api/markets/:id # Update market (full)
PATCH /api/markets/:id # Update market (partial)
DELETE /api/markets/:id # Delete market
# Query parameters for filtering
GET /api/markets?status=active&limit=10&offset=0
```
### 响应格式
```typescript
// ✅ GOOD: Consistent response structure
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
meta?: {
total: number
page: number
limit: number
}
}
// Success response
return NextResponse.json({
success: true,
data: markets,
meta: { total: 100, page: 1, limit: 10 }
})
// Error response
return NextResponse.json({
success: false,
error: 'Invalid request'
}, { status: 400 })
```
### 输入验证
```typescript
import { z } from 'zod'
// ✅ GOOD: Schema validation
const CreateMarketSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().min(1).max(2000),
endDate: z.string().datetime(),
categories: z.array(z.string()).min(1)
})
export async function POST(request: Request) {
const body = await request.json()
try {
const validated = CreateMarketSchema.parse(body)
// Proceed with validated data
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
}
}
```
## 文件组织
### 项目结构
```
src/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ ├── markets/ # Market pages
│ └── (auth)/ # Auth pages (route groups)
├── components/ # React components
│ ├── ui/ # Generic UI components
│ ├── forms/ # Form components
│ └── layouts/ # Layout components
├── hooks/ # Custom React hooks
├── lib/ # Utilities and configs
│ ├── api/ # API clients
│ ├── utils/ # Helper functions
│ └── constants/ # Constants
├── types/ # TypeScript types
└── styles/ # Global styles
```
### 文件命名
```
components/Button.tsx # PascalCase for components
hooks/useAuth.ts # camelCase with 'use' prefix
lib/formatDate.ts # camelCase for utilities
types/market.types.ts # camelCase with .types suffix
```
## 注释与文档
### 何时添加注释
```typescript
// ✅ GOOD: Explain WHY, not WHAT
// Use exponential backoff to avoid overwhelming the API during outages
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
// Deliberately using mutation here for performance with large arrays
items.push(newItem)
// ❌ BAD: Stating the obvious
// Increment counter by 1
count++
// Set name to user's name
name = user.name
```
### 公共 API 的 JSDoc
````typescript
/**
* Searches markets using semantic similarity.
*
* @param query - Natural language search query
* @param limit - Maximum number of results (default: 10)
* @returns Array of markets sorted by similarity score
* @throws {Error} If OpenAI API fails or Redis unavailable
*
* @example
* ```typescript
* const results = await searchMarkets('election', 5)
* console.log(results[0].name) // "Trump vs Biden"
* ```
*/
export async function searchMarkets(
query: string,
limit: number = 10
): Promise<Market[]> {
// Implementation
}
````
## 性能最佳实践
### 记忆化
```typescript
import { useMemo, useCallback } from 'react'
// ✅ GOOD: Memoize expensive computations
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// ✅ GOOD: Memoize callbacks
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
```
### 懒加载
```typescript
import { lazy, Suspense } from 'react'
// ✅ GOOD: Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))
export function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
)
}
```
### 数据库查询
```typescript
// ✅ GOOD: Select only needed columns
const { data } = await supabase
.from('markets')
.select('id, name, status')
.limit(10)
// ❌ BAD: Select everything
const { data } = await supabase
.from('markets')
.select('*')
```
## 测试标准
### 测试结构 (AAA 模式)
```typescript
test('calculates similarity correctly', () => {
// Arrange
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
// Act
const similarity = calculateCosineSimilarity(vector1, vector2)
// Assert
expect(similarity).toBe(0)
})
```
### 测试命名
```typescript
// ✅ GOOD: Descriptive test names
test('returns empty array when no markets match query', () => { })
test('throws error when OpenAI API key is missing', () => { })
test('falls back to substring search when Redis unavailable', () => { })
// ❌ BAD: Vague test names
test('works', () => { })
test('test search', () => { })
```
## 代码异味检测
警惕以下反模式:
### 1. 长函数
```typescript
// ❌ BAD: Function > 50 lines
function processMarketData() {
// 100 lines of code
}
// ✅ GOOD: Split into smaller functions
function processMarketData() {
const validated = validateData()
const transformed = transformData(validated)
return saveData(transformed)
}
```
### 2. 深层嵌套
```typescript
// ❌ BAD: 5+ levels of nesting
if (user) {
if (user.isAdmin) {
if (market) {
if (market.isActive) {
if (hasPermission) {
// Do something
}
}
}
}
}
// ✅ GOOD: Early returns
if (!user) return
if (!user.isAdmin) return
if (!market) return
if (!market.isActive) return
if (!hasPermission) return
// Do something
```
### 3. 魔法数字
```typescript
// ❌ BAD: Unexplained numbers
if (retryCount > 3) { }
setTimeout(callback, 500)
// ✅ GOOD: Named constants
const MAX_RETRIES = 3
const DEBOUNCE_DELAY_MS = 500
if (retryCount > MAX_RETRIES) { }
setTimeout(callback, DEBOUNCE_DELAY_MS)
```
**记住**:代码质量不容妥协。清晰、可维护的代码能够实现快速开发和自信的重构。

View File

@@ -0,0 +1,314 @@
---
name: configure-ecc
description: Everything Claude Code 的交互式安装程序 — 引导用户选择并安装技能和规则到用户级或项目级目录,验证路径,并可选择性地优化已安装的文件。
---
# 配置 Everything Claude Code (ECC)
一个交互式、分步安装向导,用于 Everything Claude Code 项目。使用 `AskUserQuestion` 引导用户选择性安装技能和规则,然后验证正确性并提供优化。
## 何时激活
* 用户说 "configure ecc"、"install ecc"、"setup everything claude code" 或类似表述
* 用户想要从此项目中选择性安装技能或规则
* 用户想要验证或修复现有的 ECC 安装
* 用户想要为其项目优化已安装的技能或规则
## 先决条件
此技能必须在激活前对 Claude Code 可访问。有两种引导方式:
1. **通过插件**: `/plugin install everything-claude-code` — 插件会自动加载此技能
2. **手动**: 仅将此技能复制到 `~/.claude/skills/configure-ecc/SKILL.md`,然后通过说 "configure ecc" 激活
***
## 步骤 0克隆 ECC 仓库
在任何安装之前,将最新的 ECC 源代码克隆到 `/tmp`
```bash
rm -rf /tmp/everything-claude-code
git clone https://github.com/affaan-m/everything-claude-code.git /tmp/everything-claude-code
```
`ECC_ROOT=/tmp/everything-claude-code` 设置为所有后续复制操作的源。
如果克隆失败(网络问题等),使用 `AskUserQuestion` 要求用户提供现有 ECC 克隆的本地路径。
***
## 步骤 1选择安装级别
使用 `AskUserQuestion` 询问用户安装位置:
```
Question: "Where should ECC components be installed?"
Options:
- "User-level (~/.claude/)" — "Applies to all your Claude Code projects"
- "Project-level (.claude/)" — "Applies only to the current project"
- "Both" — "Common/shared items user-level, project-specific items project-level"
```
将选择存储为 `INSTALL_LEVEL`。设置目标目录:
* 用户级别:`TARGET=~/.claude`
* 项目级别:`TARGET=.claude`(相对于当前项目根目录)
* 两者:`TARGET_USER=~/.claude``TARGET_PROJECT=.claude`
如果目标目录不存在,则创建它们:
```bash
mkdir -p $TARGET/skills $TARGET/rules
```
***
## 步骤 2选择并安装技能
### 2a选择技能类别
共有 27 项技能,分为 4 个类别。使用 `AskUserQuestion``multiSelect: true`
```
Question: "Which skill categories do you want to install?"
Options:
- "Framework & Language" — "Django, Spring Boot, Go, Python, Java, Frontend, Backend patterns"
- "Database" — "PostgreSQL, ClickHouse, JPA/Hibernate patterns"
- "Workflow & Quality" — "TDD, verification, learning, security review, compaction"
- "All skills" — "Install every available skill"
```
### 2b确认单项技能
对于每个选定的类别,打印下面的完整技能列表,并要求用户确认或取消选择特定的技能。如果列表超过 4 项,将列表打印为文本,并使用 `AskUserQuestion`,提供一个 "安装所有列出项" 的选项,以及一个 "其他" 选项供用户粘贴特定名称。
**类别框架与语言16 项技能)**
| 技能 | 描述 |
|-------|-------------|
| `backend-patterns` | Node.js/Express/Next.js 的后端架构、API 设计、服务器端最佳实践 |
| `coding-standards` | TypeScript、JavaScript、React、Node.js 的通用编码标准 |
| `django-patterns` | Django 架构、使用 DRF 的 REST API、ORM、缓存、信号、中间件 |
| `django-security` | Django 安全身份验证、CSRF、SQL 注入、XSS 防护 |
| `django-tdd` | 使用 pytest-django、factory\_boy、模拟、覆盖率的 Django 测试 |
| `django-verification` | Django 验证循环:迁移、代码检查、测试、安全扫描 |
| `frontend-patterns` | React、Next.js、状态管理、性能、UI 模式 |
| `golang-patterns` | 地道的 Go 模式、健壮 Go 应用程序的约定 |
| `golang-testing` | Go 测试:表格驱动测试、子测试、基准测试、模糊测试 |
| `java-coding-standards` | Spring Boot 的 Java 编码标准命名、不可变性、Optional、流 |
| `python-patterns` | Pythonic 惯用法、PEP 8、类型提示、最佳实践 |
| `python-testing` | 使用 pytest、TDD、夹具、模拟、参数化的 Python 测试 |
| `springboot-patterns` | Spring Boot 架构、REST API、分层服务、缓存、异步 |
| `springboot-security` | Spring Security身份验证/授权、验证、CSRF、密钥、速率限制 |
| `springboot-tdd` | 使用 JUnit 5、Mockito、MockMvc、Testcontainers 的 Spring Boot TDD |
| `springboot-verification` | Spring Boot 验证:构建、静态分析、测试、安全扫描 |
**类别数据库3 项技能)**
| 技能 | 描述 |
|-------|-------------|
| `clickhouse-io` | ClickHouse 模式、查询优化、分析、数据工程 |
| `jpa-patterns` | JPA/Hibernate 实体设计、关系、查询优化、事务 |
| `postgres-patterns` | PostgreSQL 查询优化、模式设计、索引、安全 |
**类别工作流与质量8 项技能)**
| 技能 | 描述 |
|-------|-------------|
| `continuous-learning` | 从会话中自动提取可重用模式作为习得技能 |
| `continuous-learning-v2` | 基于本能的学习,带有置信度评分,演变为技能/命令/代理 |
| `eval-harness` | 用于评估驱动开发 (EDD) 的正式评估框架 |
| `iterative-retrieval` | 用于子代理上下文问题的渐进式上下文优化 |
| `security-review` | 安全检查清单身份验证、输入、密钥、API、支付功能 |
| `strategic-compact` | 在逻辑间隔处建议手动上下文压缩 |
| `tdd-workflow` | 强制要求 TDD覆盖率 80% 以上:单元测试、集成测试、端到端测试 |
| `verification-loop` | 验证和质量循环模式 |
**独立技能**
| 技能 | 描述 |
|-------|-------------|
| `project-guidelines-example` | 用于创建项目特定技能的模板 |
### 2c执行安装
对于每个选定的技能,复制整个技能目录:
```bash
cp -r $ECC_ROOT/skills/<skill-name> $TARGET/skills/
```
注意:`continuous-learning``continuous-learning-v2` 有额外的文件config.json、钩子、脚本——确保复制整个目录而不仅仅是 SKILL.md。
***
## 步骤 3选择并安装规则
使用 `AskUserQuestion``multiSelect: true`
```
Question: "Which rule sets do you want to install?"
Options:
- "Common rules (Recommended)" — "Language-agnostic principles: coding style, git workflow, testing, security, etc. (8 files)"
- "TypeScript/JavaScript" — "TS/JS patterns, hooks, testing with Playwright (5 files)"
- "Python" — "Python patterns, pytest, black/ruff formatting (5 files)"
- "Go" — "Go patterns, table-driven tests, gofmt/staticcheck (5 files)"
```
执行安装:
```bash
# Common rules (flat copy into rules/)
cp -r $ECC_ROOT/rules/common/* $TARGET/rules/
# Language-specific rules (flat copy into rules/)
cp -r $ECC_ROOT/rules/typescript/* $TARGET/rules/ # if selected
cp -r $ECC_ROOT/rules/python/* $TARGET/rules/ # if selected
cp -r $ECC_ROOT/rules/golang/* $TARGET/rules/ # if selected
```
**重要**:如果用户选择了任何特定语言的规则但**没有**选择通用规则,警告他们:
> "特定语言规则扩展了通用规则。不安装通用规则可能导致覆盖不完整。是否也安装通用规则?"
***
## 步骤 4安装后验证
安装后,执行这些自动化检查:
### 4a验证文件存在
列出所有已安装的文件并确认它们存在于目标位置:
```bash
ls -la $TARGET/skills/
ls -la $TARGET/rules/
```
### 4b检查路径引用
扫描所有已安装的 `.md` 文件中的路径引用:
```bash
grep -rn "~/.claude/" $TARGET/skills/ $TARGET/rules/
grep -rn "../common/" $TARGET/rules/
grep -rn "skills/" $TARGET/skills/
```
**对于项目级别安装**,标记任何对 `~/.claude/` 路径的引用:
* 如果技能引用 `~/.claude/settings.json` — 这通常没问题(设置始终是用户级别的)
* 如果技能引用 `~/.claude/skills/``~/.claude/rules/` — 如果仅安装在项目级别,这可能损坏
* 如果技能通过名称引用另一项技能 — 检查被引用的技能是否也已安装
### 4c检查技能间的交叉引用
有些技能会引用其他技能。验证这些依赖关系:
* `django-tdd` 可能引用 `django-patterns`
* `springboot-tdd` 可能引用 `springboot-patterns`
* `continuous-learning-v2` 引用 `~/.claude/homunculus/` 目录
* `python-testing` 可能引用 `python-patterns`
* `golang-testing` 可能引用 `golang-patterns`
* 特定语言规则引用其 `common/` 对应项
### 4d报告问题
对于发现的每个问题,报告:
1. **文件**:包含问题引用的文件
2. **行号**:行号
3. **问题**:哪里出错了(例如,"引用了 ~/.claude/skills/python-patterns 但 python-patterns 未安装"
4. **建议的修复**:该怎么做(例如,"安装 python-patterns 技能" 或 "将路径更新为 .claude/skills/"
***
## 步骤 5优化已安装文件可选
使用 `AskUserQuestion`
```
Question: "Would you like to optimize the installed files for your project?"
Options:
- "Optimize skills" — "Remove irrelevant sections, adjust paths, tailor to your tech stack"
- "Optimize rules" — "Adjust coverage targets, add project-specific patterns, customize tool configs"
- "Optimize both" — "Full optimization of all installed files"
- "Skip" — "Keep everything as-is"
```
### 如果优化技能:
1. 读取每个已安装的 SKILL.md
2. 询问用户其项目的技术栈是什么(如果尚不清楚)
3. 对于每项技能,建议删除无关部分
4. 在安装目标处就地编辑 SKILL.md 文件(**不是**源仓库)
5. 修复在步骤 4 中发现的任何路径问题
### 如果优化规则:
1. 读取每个已安装的规则 .md 文件
2. 询问用户的偏好:
* 测试覆盖率目标(默认 80%
* 首选的格式化工具
* Git 工作流约定
* 安全要求
3. 在安装目标处就地编辑规则文件
**关键**:只修改安装目标(`$TARGET/`)中的文件,**绝不**修改源 ECC 仓库(`$ECC_ROOT/`)中的文件。
***
## 步骤 6安装摘要
`/tmp` 清理克隆的仓库:
```bash
rm -rf /tmp/everything-claude-code
```
然后打印摘要报告:
```
## ECC Installation Complete
### Installation Target
- Level: [user-level / project-level / both]
- Path: [target path]
### Skills Installed ([count])
- skill-1, skill-2, skill-3, ...
### Rules Installed ([count])
- common (8 files)
- typescript (5 files)
- ...
### Verification Results
- [count] issues found, [count] fixed
- [list any remaining issues]
### Optimizations Applied
- [list changes made, or "None"]
```
***
## 故障排除
### "Claude Code 未获取技能"
* 验证技能目录包含一个 `SKILL.md` 文件(不仅仅是松散的 .md 文件)
* 对于用户级别:检查 `~/.claude/skills/<skill-name>/SKILL.md` 是否存在
* 对于项目级别:检查 `.claude/skills/<skill-name>/SKILL.md` 是否存在
### "规则不工作"
* 规则是平面文件,不在子目录中:`$TARGET/rules/coding-style.md`(正确)对比 `$TARGET/rules/common/coding-style.md`(对于平面安装不正确)
* 安装规则后重启 Claude Code
### "项目级别安装后出现路径引用错误"
* 有些技能假设 `~/.claude/` 路径。运行步骤 4 验证来查找并修复这些问题。
* 对于 `continuous-learning-v2``~/.claude/homunculus/` 目录始终是用户级别的 — 这是预期的,不是错误。

View File

@@ -0,0 +1,290 @@
---
name: continuous-learning-v2
description: 基于本能的学习系统,通过钩子观察会话,创建具有置信度评分的原子本能,并将其演化为技能/命令/代理。
version: 2.0.0
---
# 持续学习 v2 - 基于本能的架构
一个高级学习系统,通过原子化的“本能”——带有置信度评分的小型习得行为——将你的 Claude Code 会话转化为可重用的知识。
## v2 的新特性
| 特性 | v1 | v2 |
|---------|----|----|
| 观察 | 停止钩子(会话结束) | 工具使用前/后100% 可靠) |
| 分析 | 主上下文 | 后台代理Haiku |
| 粒度 | 完整技能 | 原子化的“本能” |
| 置信度 | 无 | 0.3-0.9 加权 |
| 演进 | 直接到技能 | 本能 → 聚类 → 技能/命令/代理 |
| 共享 | 无 | 导出/导入本能 |
## 本能模型
一个本能是一个小型习得行为:
```yaml
---
id: prefer-functional-style
trigger: "when writing new functions"
confidence: 0.7
domain: "code-style"
source: "session-observation"
---
# Prefer Functional Style
## Action
Use functional patterns over classes when appropriate.
## Evidence
- Observed 5 instances of functional pattern preference
- User corrected class-based approach to functional on 2025-01-15
```
**属性:**
* **原子性** — 一个触发条件,一个动作
* **置信度加权** — 0.3 = 尝试性的0.9 = 近乎确定
* **领域标记** — 代码风格、测试、git、调试、工作流等
* **证据支持** — 追踪是哪些观察创建了它
## 工作原理
```
Session Activity
│ Hooks capture prompts + tool use (100% reliable)
┌─────────────────────────────────────────┐
│ observations.jsonl │
│ (prompts, tool calls, outcomes) │
└─────────────────────────────────────────┘
│ Observer agent reads (background, Haiku)
┌─────────────────────────────────────────┐
│ PATTERN DETECTION │
│ • User corrections → instinct │
│ • Error resolutions → instinct │
│ • Repeated workflows → instinct │
└─────────────────────────────────────────┘
│ Creates/updates
┌─────────────────────────────────────────┐
│ instincts/personal/ │
│ • prefer-functional.md (0.7) │
│ • always-test-first.md (0.9) │
│ • use-zod-validation.md (0.6) │
└─────────────────────────────────────────┘
│ /evolve clusters
┌─────────────────────────────────────────┐
│ evolved/ │
│ • commands/new-feature.md │
│ • skills/testing-workflow.md │
│ • agents/refactor-specialist.md │
└─────────────────────────────────────────┘
```
## 快速开始
### 1. 启用观察钩子
添加到你的 `~/.claude/settings.json` 中。
**如果作为插件安装**(推荐):
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh pre"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh post"
}]
}]
}
}
```
**如果手动安装**到 `~/.claude/skills`
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh pre"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh post"
}]
}]
}
}
```
### 2. 初始化目录结构
Python CLI 会自动创建这些目录,但你也可以手动创建:
```bash
mkdir -p ~/.claude/homunculus/{instincts/{personal,inherited},evolved/{agents,skills,commands}}
touch ~/.claude/homunculus/observations.jsonl
```
### 3. 使用本能命令
```bash
/instinct-status # Show learned instincts with confidence scores
/evolve # Cluster related instincts into skills/commands
/instinct-export # Export instincts for sharing
/instinct-import # Import instincts from others
```
## 命令
| 命令 | 描述 |
|---------|-------------|
| `/instinct-status` | 显示所有已习得的本能及其置信度 |
| `/evolve` | 将相关本能聚类为技能/命令 |
| `/instinct-export` | 导出本能用于共享 |
| `/instinct-import <file>` | 从他人处导入本能 |
## 配置
编辑 `config.json`
```json
{
"version": "2.0",
"observation": {
"enabled": true,
"store_path": "~/.claude/homunculus/observations.jsonl",
"max_file_size_mb": 10,
"archive_after_days": 7
},
"instincts": {
"personal_path": "~/.claude/homunculus/instincts/personal/",
"inherited_path": "~/.claude/homunculus/instincts/inherited/",
"min_confidence": 0.3,
"auto_approve_threshold": 0.7,
"confidence_decay_rate": 0.05
},
"observer": {
"enabled": true,
"model": "haiku",
"run_interval_minutes": 5,
"patterns_to_detect": [
"user_corrections",
"error_resolutions",
"repeated_workflows",
"tool_preferences"
]
},
"evolution": {
"cluster_threshold": 3,
"evolved_path": "~/.claude/homunculus/evolved/"
}
}
```
## 文件结构
```
~/.claude/homunculus/
├── identity.json # Your profile, technical level
├── observations.jsonl # Current session observations
├── observations.archive/ # Processed observations
├── instincts/
│ ├── personal/ # Auto-learned instincts
│ └── inherited/ # Imported from others
└── evolved/
├── agents/ # Generated specialist agents
├── skills/ # Generated skills
└── commands/ # Generated commands
```
## 与技能创建器的集成
当你使用 [技能创建器 GitHub 应用](https://skill-creator.app) 时,它现在会生成**两者**
* 传统的 SKILL.md 文件(用于向后兼容)
* 本能集合(用于 v2 学习系统)
来自仓库分析的本能带有 `source: "repo-analysis"` 标记,并包含源仓库 URL。
## 置信度评分
置信度随时间演变:
| 分数 | 含义 | 行为 |
|-------|---------|----------|
| 0.3 | 尝试性的 | 建议但不强制执行 |
| 0.5 | 中等的 | 相关时应用 |
| 0.7 | 强烈的 | 自动批准应用 |
| 0.9 | 近乎确定的 | 核心行为 |
**置信度增加**当:
* 模式被反复观察到
* 用户未纠正建议的行为
* 来自其他来源的相似本能一致
**置信度降低**当:
* 用户明确纠正该行为
* 长时间未观察到该模式
* 出现矛盾证据
## 为什么用钩子而非技能进行观察?
> “v1 依赖技能进行观察。技能是概率性的——它们基于 Claude 的判断,大约有 50-80% 的概率触发。”
钩子**100% 触发**,是确定性的。这意味着:
* 每次工具调用都被观察到
* 不会错过任何模式
* 学习是全面的
## 向后兼容性
v2 与 v1 完全兼容:
* 现有的 `~/.claude/skills/learned/` 技能仍然有效
* 停止钩子仍然运行(但现在也输入到 v2
* 渐进式迁移路径:并行运行两者
## 隐私
* 观察数据**保留在**你的本地机器上
* 只有**本能**(模式)可以被导出
* 不会共享实际的代码或对话内容
* 你控制导出的内容
## 相关链接
* [技能创建器](https://skill-creator.app) - 从仓库历史生成本能
* [Homunculus](https://github.com/humanplane/homunculus) - v2 架构的灵感来源
* [长文指南](https://x.com/affaanmustafa/status/2014040193557471352) - 持续学习部分
***
*基于本能的学习:一次一个观察,教会 Claude 你的模式。*

View File

@@ -0,0 +1,150 @@
---
name: observer
description: 背景代理,通过分析会话观察来检测模式并创建本能。使用俳句以实现成本效益。
model: haiku
run_mode: background
---
# Observer Agent
一个后台代理,用于分析 Claude Code 会话中的观察结果,以检测模式并创建本能。
## 何时运行
* 在显著会话活动后20+ 工具调用)
* 当用户运行 `/analyze-patterns`
* 按计划间隔(可配置,默认 5 分钟)
* 当被观察钩子触发时 (SIGUSR1)
## 输入
`~/.claude/homunculus/observations.jsonl` 读取观察结果:
```jsonl
{"timestamp":"2025-01-22T10:30:00Z","event":"tool_start","session":"abc123","tool":"Edit","input":"..."}
{"timestamp":"2025-01-22T10:30:01Z","event":"tool_complete","session":"abc123","tool":"Edit","output":"..."}
{"timestamp":"2025-01-22T10:30:05Z","event":"tool_start","session":"abc123","tool":"Bash","input":"npm test"}
{"timestamp":"2025-01-22T10:30:10Z","event":"tool_complete","session":"abc123","tool":"Bash","output":"All tests pass"}
```
## 模式检测
在观察结果中寻找以下模式:
### 1. 用户更正
当用户的后续消息纠正了 Claude 之前的操作时:
* "不,使用 X 而不是 Y"
* "实际上,我的意思是……"
* 立即的撤销/重做模式
→ 创建本能:"当执行 X 时,优先使用 Y"
### 2. 错误解决
当错误发生后紧接着修复时:
* 工具输出包含错误
* 接下来的几个工具调用修复了它
* 相同类型的错误以类似方式多次解决
→ 创建本能:"当遇到错误 X 时,尝试 Y"
### 3. 重复的工作流
当多次使用相同的工具序列时:
* 具有相似输入的相同工具序列
* 一起变化的文件模式
* 时间上聚集的操作
→ 创建工作流本能:"当执行 X 时,遵循步骤 Y, Z, W"
### 4. 工具偏好
当始终偏好使用某些工具时:
* 总是在编辑前使用 Grep
* 优先使用 Read 而不是 Bash cat
* 对特定任务使用特定的 Bash 命令
→ 创建本能:"当需要 X 时,使用工具 Y"
## 输出
`~/.claude/homunculus/instincts/personal/` 中创建/更新本能:
```yaml
---
id: prefer-grep-before-edit
trigger: "when searching for code to modify"
confidence: 0.65
domain: "workflow"
source: "session-observation"
---
# Prefer Grep Before Edit
## Action
Always use Grep to find the exact location before using Edit.
## Evidence
- Observed 8 times in session abc123
- Pattern: Grep → Read → Edit sequence
- Last observed: 2025-01-22
```
## 置信度计算
基于观察频率的初始置信度:
* 1-2 次观察0.3(初步)
* 3-5 次观察0.5(中等)
* 6-10 次观察0.7(强)
* 11+ 次观察0.85(非常强)
置信度随时间调整:
* 每次确认性观察 +0.05
* 每次矛盾性观察 -0.1
* 每周无观察 -0.02(衰减)
## 重要准则
1. **保持保守**仅为清晰模式3+ 次观察)创建本能
2. **保持具体**:狭窄的触发器优于宽泛的触发器
3. **跟踪证据**:始终包含导致本能的观察结果
4. **尊重隐私**:绝不包含实际代码片段,只包含模式
5. **合并相似项**:如果新本能与现有本能相似,则更新而非重复
## 示例分析会话
给定观察结果:
```jsonl
{"event":"tool_start","tool":"Grep","input":"pattern: useState"}
{"event":"tool_complete","tool":"Grep","output":"Found in 3 files"}
{"event":"tool_start","tool":"Read","input":"src/hooks/useAuth.ts"}
{"event":"tool_complete","tool":"Read","output":"[file content]"}
{"event":"tool_start","tool":"Edit","input":"src/hooks/useAuth.ts..."}
```
分析:
* 检测到工作流Grep → Read → Edit
* 频率:本次会话中看到 5 次
* 创建本能:
* 触发器:"when modifying code"
* 操作:"Search with Grep, confirm with Read, then Edit"
* 置信度0.6
* 领域:"workflow"
## 与 Skill Creator 集成
当本能从 Skill Creator仓库分析导入时它们具有
* `source: "repo-analysis"`
* `source_repo: "https://github.com/..."`
这些应被视为具有更高初始置信度0.7+)的团队/项目约定。

View File

@@ -0,0 +1,111 @@
---
name: continuous-learning
description: 自动从Claude Code会话中提取可重用模式并将其保存为学习技能供未来使用。
---
# 持续学习技能
自动评估 Claude Code 会话的结尾,以提取可重用的模式,这些模式可以保存为学习到的技能。
## 工作原理
此技能作为 **停止钩子** 在每个会话结束时运行:
1. **会话评估**检查会话是否包含足够多的消息默认10 条以上)
2. **模式检测**:从会话中识别可提取的模式
3. **技能提取**:将有用的模式保存到 `~/.claude/skills/learned/`
## 配置
编辑 `config.json` 以进行自定义:
```json
{
"min_session_length": 10,
"extraction_threshold": "medium",
"auto_approve": false,
"learned_skills_path": "~/.claude/skills/learned/",
"patterns_to_detect": [
"error_resolution",
"user_corrections",
"workarounds",
"debugging_techniques",
"project_specific"
],
"ignore_patterns": [
"simple_typos",
"one_time_fixes",
"external_api_issues"
]
}
```
## 模式类型
| 模式 | 描述 |
|---------|-------------|
| `error_resolution` | 特定错误是如何解决的 |
| `user_corrections` | 来自用户纠正的模式 |
| `workarounds` | 框架/库特殊性的解决方案 |
| `debugging_techniques` | 有效的调试方法 |
| `project_specific` | 项目特定的约定 |
## 钩子设置
添加到你的 `~/.claude/settings.json` 中:
```json
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
}]
}]
}
}
```
## 为什么使用停止钩子?
* **轻量级**:仅在会话结束时运行一次
* **非阻塞**:不会给每条消息增加延迟
* **完整上下文**:可以访问完整的会话记录
## 相关
* [长篇指南](https://x.com/affaanmustafa/status/2014040193557471352) - 关于持续学习的章节
* `/learn` 命令 - 在会话中手动提取模式
***
## 对比说明研究2025年1月
### 与 Homunculus (github.com/humanplane/homunculus) 对比
Homunculus v2 采用了更复杂的方法:
| 功能 | 我们的方法 | Homunculus v2 |
|---------|--------------|---------------|
| 观察 | 停止钩子(会话结束时) | PreToolUse/PostToolUse 钩子100% 可靠) |
| 分析 | 主上下文 | 后台代理 (Haiku) |
| 粒度 | 完整技能 | 原子化的“本能” |
| 置信度 | 无 | 0.3-0.9 加权 |
| 演进 | 直接到技能 | 本能 → 集群 → 技能/命令/代理 |
| 共享 | 无 | 导出/导入本能 |
**来自 homunculus 的关键见解:**
> "v1 依赖技能来观察。技能是概率性的——它们触发的概率约为 50-80%。v2 使用钩子进行观察100% 可靠),并以本能作为学习行为的原子单元。"
### 潜在的 v2 增强功能
1. **基于本能的学习** - 更小、原子化的行为,附带置信度评分
2. **后台观察者** - Haiku 代理并行分析
3. **置信度衰减** - 如果被反驳,本能会降低置信度
4. **领域标记** - 代码风格、测试、git、调试等
5. **演进路径** - 将相关本能聚类为技能/命令
完整规格请参见:`/Users/affoon/Documents/tasks/12-continuous-learning-v2.md`

View File

@@ -0,0 +1,322 @@
---
name: cpp-testing
description: 仅在编写/更新/修复C++测试、配置GoogleTest/CTest、诊断失败或不稳定的测试或添加覆盖率/消毒器时使用。
---
# C++ 测试(代理技能)
针对现代 C++C++17/20的代理导向测试工作流使用 GoogleTest/GoogleMock 和 CMake/CTest。
## 使用时机
* 编写新的 C++ 测试或修复现有测试
* 为 C++ 组件设计单元/集成测试覆盖
* 添加测试覆盖、CI 门控或回归保护
* 配置 CMake/CTest 工作流以实现一致的执行
* 调查测试失败或偶发性行为
* 启用用于内存/竞态诊断的消毒剂
### 不适用时机
* 在不修改测试的情况下实现新的产品功能
* 与测试覆盖或失败无关的大规模重构
* 没有测试回归需要验证的性能调优
* 非 C++ 项目或非测试任务
## 核心概念
* **TDD 循环**:红 → 绿 → 重构(先写测试,最小化修复,然后清理)。
* **隔离**:优先使用依赖注入和仿制品,而非全局状态。
* **测试布局**`tests/unit``tests/integration``tests/testdata`
* **Mock 与 Fake**Mock 用于交互Fake 用于有状态行为。
* **CTest 发现**:使用 `gtest_discover_tests()` 进行稳定的测试发现。
* **CI 信号**:先运行子集,然后使用 `--output-on-failure` 运行完整套件。
## TDD 工作流
遵循 RED → GREEN → REFACTOR 循环:
1. **RED**:编写一个捕获新行为的失败测试
2. **GREEN**:实现最小的更改以使其通过
3. **REFACTOR**:在测试保持通过的同时进行清理
```cpp
// tests/add_test.cpp
#include <gtest/gtest.h>
int Add(int a, int b); // Provided by production code.
TEST(AddTest, AddsTwoNumbers) { // RED
EXPECT_EQ(Add(2, 3), 5);
}
// src/add.cpp
int Add(int a, int b) { // GREEN
return a + b;
}
// REFACTOR: simplify/rename once tests pass
```
## 代码示例
### 基础单元测试 (gtest)
```cpp
// tests/calculator_test.cpp
#include <gtest/gtest.h>
int Add(int a, int b); // Provided by production code.
TEST(CalculatorTest, AddsTwoNumbers) {
EXPECT_EQ(Add(2, 3), 5);
}
```
### 夹具 (gtest)
```cpp
// tests/user_store_test.cpp
// Pseudocode stub: replace UserStore/User with project types.
#include <gtest/gtest.h>
#include <memory>
#include <optional>
#include <string>
struct User { std::string name; };
class UserStore {
public:
explicit UserStore(std::string /*path*/) {}
void Seed(std::initializer_list<User> /*users*/) {}
std::optional<User> Find(const std::string &/*name*/) { return User{"alice"}; }
};
class UserStoreTest : public ::testing::Test {
protected:
void SetUp() override {
store = std::make_unique<UserStore>(":memory:");
store->Seed({{"alice"}, {"bob"}});
}
std::unique_ptr<UserStore> store;
};
TEST_F(UserStoreTest, FindsExistingUser) {
auto user = store->Find("alice");
ASSERT_TRUE(user.has_value());
EXPECT_EQ(user->name, "alice");
}
```
### Mock (gmock)
```cpp
// tests/notifier_test.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
class Notifier {
public:
virtual ~Notifier() = default;
virtual void Send(const std::string &message) = 0;
};
class MockNotifier : public Notifier {
public:
MOCK_METHOD(void, Send, (const std::string &message), (override));
};
class Service {
public:
explicit Service(Notifier &notifier) : notifier_(notifier) {}
void Publish(const std::string &message) { notifier_.Send(message); }
private:
Notifier &notifier_;
};
TEST(ServiceTest, SendsNotifications) {
MockNotifier notifier;
Service service(notifier);
EXPECT_CALL(notifier, Send("hello")).Times(1);
service.Publish("hello");
}
```
### CMake/CTest 快速入门
```cmake
# CMakeLists.txt (excerpt)
cmake_minimum_required(VERSION 3.20)
project(example LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
# Prefer project-locked versions. If using a tag, use a pinned version per project policy.
set(GTEST_VERSION v1.17.0) # Adjust to project policy.
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip
)
FetchContent_MakeAvailable(googletest)
add_executable(example_tests
tests/calculator_test.cpp
src/calculator.cpp
)
target_link_libraries(example_tests GTest::gtest GTest::gmock GTest::gtest_main)
enable_testing()
include(GoogleTest)
gtest_discover_tests(example_tests)
```
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
ctest --test-dir build --output-on-failure
```
## 运行测试
```bash
ctest --test-dir build --output-on-failure
ctest --test-dir build -R ClampTest
ctest --test-dir build -R "UserStoreTest.*" --output-on-failure
```
```bash
./build/example_tests --gtest_filter=ClampTest.*
./build/example_tests --gtest_filter=UserStoreTest.FindsExistingUser
```
## 调试失败
1. 使用 gtest 过滤器重新运行单个失败的测试。
2. 在失败的断言周围添加作用域日志记录。
3. 启用消毒剂后重新运行。
4. 根本原因修复后,扩展到完整套件。
## 覆盖率
优先使用目标级别的设置,而非全局标志。
```cmake
option(ENABLE_COVERAGE "Enable coverage flags" OFF)
if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(example_tests PRIVATE --coverage)
target_link_options(example_tests PRIVATE --coverage)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(example_tests PRIVATE -fprofile-instr-generate -fcoverage-mapping)
target_link_options(example_tests PRIVATE -fprofile-instr-generate)
endif()
endif()
```
GCC + gcov + lcov
```bash
cmake -S . -B build-cov -DENABLE_COVERAGE=ON
cmake --build build-cov -j
ctest --test-dir build-cov
lcov --capture --directory build-cov --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
genhtml coverage.info --output-directory coverage
```
Clang + llvm-cov
```bash
cmake -S . -B build-llvm -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER=clang++
cmake --build build-llvm -j
LLVM_PROFILE_FILE="build-llvm/default.profraw" ctest --test-dir build-llvm
llvm-profdata merge -sparse build-llvm/default.profraw -o build-llvm/default.profdata
llvm-cov report build-llvm/example_tests -instr-profile=build-llvm/default.profdata
```
## 消毒剂
```cmake
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
if(ENABLE_ASAN)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
if(ENABLE_UBSAN)
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer)
add_link_options(-fsanitize=undefined)
endif()
if(ENABLE_TSAN)
add_compile_options(-fsanitize=thread)
add_link_options(-fsanitize=thread)
endif()
```
## 偶发性测试防护
* 切勿使用 `sleep` 进行同步;使用条件变量或门闩。
* 为每个测试创建唯一的临时目录并始终清理它们。
* 避免在单元测试中依赖真实时间、网络或文件系统。
* 对随机化输入使用确定性种子。
## 最佳实践
### 应该做
* 保持测试的确定性和隔离性
* 优先使用依赖注入而非全局变量
* 对前置条件使用 `ASSERT_*`,对多个检查使用 `EXPECT_*`
* 在 CTest 标签或目录中分离单元测试与集成测试
* 在 CI 中运行消毒剂以进行内存和竞态检测
### 不应该做
* 不要在单元测试中依赖真实时间或网络
* 当可以使用条件变量时,不要使用睡眠作为同步手段
* 不要过度模拟简单的值对象
* 不要对非关键日志使用脆弱的字符串匹配
### 常见陷阱
* **使用固定的临时路径** → 为每个测试生成唯一的临时目录并清理它们。
* **依赖挂钟时间** → 注入时钟或使用模拟时间源。
* **偶发性并发测试** → 使用条件变量/门闩和有界等待。
* **隐藏的全局状态** → 在夹具中重置全局状态或移除全局变量。
* **过度模拟** → 对有状态行为优先使用 Fake仅对交互进行 Mock。
* **缺少消毒剂运行** → 在 CI 中添加 ASan/UBSan/TSan 构建。
* **仅在调试版本上计算覆盖率** → 确保覆盖率目标使用一致的标志。
## 可选附录:模糊测试 / 属性测试
仅在项目已支持 LLVM/libFuzzer 或属性测试库时使用。
* **libFuzzer**:最适合 I/O 最少的纯函数。
* **RapidCheck**:基于属性的测试,用于验证不变量。
最小的 libFuzzer 测试框架(伪代码:替换 ParseConfig
```cpp
#include <cstddef>
#include <cstdint>
#include <string>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
std::string input(reinterpret_cast<const char *>(data), size);
// ParseConfig(input); // project function
return 0;
}
```
## GoogleTest 的替代方案
* **Catch2**:仅头文件,表达性强的匹配器
* **doctest**:轻量级,编译开销最小

View File

@@ -0,0 +1,733 @@
---
name: django-patterns
description: Django架构模式、使用DRF的REST API设计、ORM最佳实践、缓存、信号、中间件以及生产级Django应用程序。
---
# Django 开发模式
适用于可扩展、可维护应用程序的生产级 Django 架构模式。
## 何时激活
* 构建 Django Web 应用程序时
* 设计 Django REST Framework API 时
* 使用 Django ORM 和模型时
* 设置 Django 项目结构时
* 实现缓存、信号、中间件时
## 项目结构
### 推荐布局
```
myproject/
├── config/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # Base settings
│ │ ├── development.py # Dev settings
│ │ ├── production.py # Production settings
│ │ └── test.py # Test settings
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── manage.py
└── apps/
├── __init__.py
├── users/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ ├── serializers.py
│ ├── urls.py
│ ├── permissions.py
│ ├── filters.py
│ ├── services.py
│ └── tests/
└── products/
└── ...
```
### 拆分设置模式
```python
# config/settings/base.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env('DJANGO_SECRET_KEY')
DEBUG = False
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
# Local apps
'apps.users',
'apps.products',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT', default='5432'),
}
}
# config/settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES['default']['NAME'] = 'myproject_dev'
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# config/settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True,
},
},
}
```
## 模型设计模式
### 模型最佳实践
```python
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator
class User(AbstractUser):
"""Custom user model extending AbstractUser."""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
birth_date = models.DateField(null=True, blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta:
db_table = 'users'
verbose_name = 'user'
verbose_name_plural = 'users'
ordering = ['-date_joined']
def __str__(self):
return self.email
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
class Product(models.Model):
"""Product model with proper field configuration."""
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True, max_length=250)
description = models.TextField(blank=True)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)]
)
stock = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
category = models.ForeignKey(
'Category',
on_delete=models.CASCADE,
related_name='products'
)
tags = models.ManyToManyField('Tag', blank=True, related_name='products')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'products'
ordering = ['-created_at']
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'is_active']),
]
constraints = [
models.CheckConstraint(
check=models.Q(price__gte=0),
name='price_non_negative'
)
]
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
```
### QuerySet 最佳实践
```python
from django.db import models
class ProductQuerySet(models.QuerySet):
"""Custom QuerySet for Product model."""
def active(self):
"""Return only active products."""
return self.filter(is_active=True)
def with_category(self):
"""Select related category to avoid N+1 queries."""
return self.select_related('category')
def with_tags(self):
"""Prefetch tags for many-to-many relationship."""
return self.prefetch_related('tags')
def in_stock(self):
"""Return products with stock > 0."""
return self.filter(stock__gt=0)
def search(self, query):
"""Search products by name or description."""
return self.filter(
models.Q(name__icontains=query) |
models.Q(description__icontains=query)
)
class Product(models.Model):
# ... fields ...
objects = ProductQuerySet.as_manager() # Use custom QuerySet
# Usage
Product.objects.active().with_category().in_stock()
```
### 管理器方法
```python
class ProductManager(models.Manager):
"""Custom manager for complex queries."""
def get_or_none(self, **kwargs):
"""Return object or None instead of DoesNotExist."""
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return None
def create_with_tags(self, name, price, tag_names):
"""Create product with associated tags."""
product = self.create(name=name, price=price)
tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]
product.tags.set(tags)
return product
def bulk_update_stock(self, product_ids, quantity):
"""Bulk update stock for multiple products."""
return self.filter(id__in=product_ids).update(stock=quantity)
# In model
class Product(models.Model):
# ... fields ...
custom = ProductManager()
```
## Django REST Framework 模式
### 序列化器模式
```python
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
from .models import Product, User
class ProductSerializer(serializers.ModelSerializer):
"""Serializer for Product model."""
category_name = serializers.CharField(source='category.name', read_only=True)
average_rating = serializers.FloatField(read_only=True)
discount_price = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id', 'name', 'slug', 'description', 'price',
'discount_price', 'stock', 'category_name',
'average_rating', 'created_at'
]
read_only_fields = ['id', 'slug', 'created_at']
def get_discount_price(self, obj):
"""Calculate discount price if applicable."""
if hasattr(obj, 'discount') and obj.discount:
return obj.price * (1 - obj.discount.percent / 100)
return obj.price
def validate_price(self, value):
"""Ensure price is non-negative."""
if value < 0:
raise serializers.ValidationError("Price cannot be negative.")
return value
class ProductCreateSerializer(serializers.ModelSerializer):
"""Serializer for creating products."""
class Meta:
model = Product
fields = ['name', 'description', 'price', 'stock', 'category']
def validate(self, data):
"""Custom validation for multiple fields."""
if data['price'] > 10000 and data['stock'] > 100:
raise serializers.ValidationError(
"Cannot have high-value products with large stock."
)
return data
class UserRegistrationSerializer(serializers.ModelSerializer):
"""Serializer for user registration."""
password = serializers.CharField(
write_only=True,
required=True,
validators=[validate_password],
style={'input_type': 'password'}
)
password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})
class Meta:
model = User
fields = ['email', 'username', 'password', 'password_confirm']
def validate(self, data):
"""Validate passwords match."""
if data['password'] != data['password_confirm']:
raise serializers.ValidationError({
"password_confirm": "Password fields didn't match."
})
return data
def create(self, validated_data):
"""Create user with hashed password."""
validated_data.pop('password_confirm')
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
return user
```
### ViewSet 模式
```python
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product
from .serializers import ProductSerializer, ProductCreateSerializer
from .permissions import IsOwnerOrReadOnly
from .filters import ProductFilter
from .services import ProductService
class ProductViewSet(viewsets.ModelViewSet):
"""ViewSet for Product model."""
queryset = Product.objects.select_related('category').prefetch_related('tags')
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ProductFilter
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at', 'name']
ordering = ['-created_at']
def get_serializer_class(self):
"""Return appropriate serializer based on action."""
if self.action == 'create':
return ProductCreateSerializer
return ProductSerializer
def perform_create(self, serializer):
"""Save with user context."""
serializer.save(created_by=self.request.user)
@action(detail=False, methods=['get'])
def featured(self, request):
"""Return featured products."""
featured = self.queryset.filter(is_featured=True)[:10]
serializer = self.get_serializer(featured, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def purchase(self, request, pk=None):
"""Purchase a product."""
product = self.get_object()
service = ProductService()
result = service.purchase(product, request.user)
return Response(result, status=status.HTTP_201_CREATED)
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def my_products(self, request):
"""Return products created by current user."""
products = self.queryset.filter(created_by=request.user)
page = self.paginate_queryset(products)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
```
### 自定义操作
```python
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def add_to_cart(request):
"""Add product to user cart."""
product_id = request.data.get('product_id')
quantity = request.data.get('quantity', 1)
try:
product = Product.objects.get(id=product_id)
except Product.DoesNotExist:
return Response(
{'error': 'Product not found'},
status=status.HTTP_404_NOT_FOUND
)
cart, _ = Cart.objects.get_or_create(user=request.user)
CartItem.objects.create(
cart=cart,
product=product,
quantity=quantity
)
return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)
```
## 服务层模式
```python
# apps/orders/services.py
from typing import Optional
from django.db import transaction
from .models import Order, OrderItem
class OrderService:
"""Service layer for order-related business logic."""
@staticmethod
@transaction.atomic
def create_order(user, cart: Cart) -> Order:
"""Create order from cart."""
order = Order.objects.create(
user=user,
total_price=cart.total_price
)
for item in cart.items.all():
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price
)
# Clear cart
cart.items.all().delete()
return order
@staticmethod
def process_payment(order: Order, payment_data: dict) -> bool:
"""Process payment for order."""
# Integration with payment gateway
payment = PaymentGateway.charge(
amount=order.total_price,
token=payment_data['token']
)
if payment.success:
order.status = Order.Status.PAID
order.save()
# Send confirmation email
OrderService.send_confirmation_email(order)
return True
return False
@staticmethod
def send_confirmation_email(order: Order):
"""Send order confirmation email."""
# Email sending logic
pass
```
## 缓存策略
### 视图级缓存
```python
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name='dispatch') # 15 minutes
class ProductListView(generic.ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
```
### 模板片段缓存
```django
{% load cache %}
{% cache 500 sidebar %}
... expensive sidebar content ...
{% endcache %}
```
### 低级缓存
```python
from django.core.cache import cache
def get_featured_products():
"""Get featured products with caching."""
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(Product.objects.filter(is_featured=True))
cache.set(cache_key, products, timeout=60 * 15) # 15 minutes
return products
```
### QuerySet 缓存
```python
from django.core.cache import cache
def get_popular_categories():
cache_key = 'popular_categories'
categories = cache.get(cache_key)
if categories is None:
categories = list(Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=10).order_by('-product_count')[:20])
cache.set(cache_key, categories, timeout=60 * 60) # 1 hour
return categories
```
## 信号
### 信号模式
```python
# apps/users/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Profile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Create profile when user is created."""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Save profile when user is saved."""
instance.profile.save()
# apps/users/apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'
def ready(self):
"""Import signals when app is ready."""
import apps.users.signals
```
## 中间件
### 自定义中间件
```python
# middleware/active_user_middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):
"""Middleware to track active users."""
def process_request(self, request):
"""Process incoming request."""
if request.user.is_authenticated:
# Update last active time
request.user.last_active = timezone.now()
request.user.save(update_fields=['last_active'])
class RequestLoggingMiddleware(MiddlewareMixin):
"""Middleware for logging requests."""
def process_request(self, request):
"""Log request start time."""
request.start_time = time.time()
def process_response(self, request, response):
"""Log request duration."""
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
return response
```
## 性能优化
### N+1 查询预防
```python
# Bad - N+1 queries
products = Product.objects.all()
for product in products:
print(product.category.name) # Separate query for each product
# Good - Single query with select_related
products = Product.objects.select_related('category').all()
for product in products:
print(product.category.name)
# Good - Prefetch for many-to-many
products = Product.objects.prefetch_related('tags').all()
for product in products:
for tag in product.tags.all():
print(tag.name)
```
### 数据库索引
```python
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['name']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'created_at']),
]
```
### 批量操作
```python
# Bulk create
Product.objects.bulk_create([
Product(name=f'Product {i}', price=10.00)
for i in range(1000)
])
# Bulk update
products = Product.objects.all()[:100]
for product in products:
product.is_active = True
Product.objects.bulk_update(products, ['is_active'])
# Bulk delete
Product.objects.filter(stock=0).delete()
```
## 快速参考
| 模式 | 描述 |
|---------|-------------|
| 拆分设置 | 分离开发/生产/测试设置 |
| 自定义 QuerySet | 可重用的查询方法 |
| 服务层 | 业务逻辑分离 |
| ViewSet | REST API 端点 |
| 序列化器验证 | 请求/响应转换 |
| select\_related | 外键优化 |
| prefetch\_related | 多对多优化 |
| 缓存优先 | 缓存昂贵操作 |
| 信号 | 事件驱动操作 |
| 中间件 | 请求/响应处理 |
请记住Django 提供了许多快捷方式,但对于生产应用程序来说,结构和组织比简洁的代码更重要。为可维护性而构建。

View File

@@ -0,0 +1,592 @@
---
name: django-security
description: Django安全最佳实践身份验证授权CSRF保护SQL注入预防XSS预防和安全部署配置。
---
# Django 安全最佳实践
保护 Django 应用程序免受常见漏洞侵害的全面安全指南。
## 何时启用
* 设置 Django 认证和授权时
* 实现用户权限和角色时
* 配置生产环境安全设置时
* 审查 Django 应用程序的安全问题时
* 将 Django 应用程序部署到生产环境时
## 核心安全设置
### 生产环境设置配置
```python
# settings/production.py
import os
DEBUG = False # CRITICAL: Never use True in production
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
# Security headers
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# HTTPS and Cookies
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'
# Secret key (must be set via environment variable)
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 12,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
```
## 认证
### 自定义用户模型
```python
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
"""Custom user model for better security."""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
USERNAME_FIELD = 'email' # Use email as username
REQUIRED_FIELDS = ['username']
class Meta:
db_table = 'users'
verbose_name = 'User'
verbose_name_plural = 'Users'
def __str__(self):
return self.email
# settings/base.py
AUTH_USER_MODEL = 'users.User'
```
### 密码哈希
```python
# Django uses PBKDF2 by default. For stronger security:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
```
### 会话管理
```python
# Session configuration
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # Or 'db'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 3600 * 24 * 7 # 1 week
SESSION_SAVE_EVERY_REQUEST = False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Better UX, but less secure
```
## 授权
### 权限
```python
# models.py
from django.db import models
from django.contrib.auth.models import Permission
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
('can_edit_others', 'Can edit posts of others'),
]
def user_can_edit(self, user):
"""Check if user can edit this post."""
return self.author == user or user.has_perm('app.can_edit_others')
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'app.can_edit_others'
raise_exception = True # Return 403 instead of redirect
def get_queryset(self):
"""Only allow users to edit their own posts."""
return Post.objects.filter(author=self.request.user)
```
### 自定义权限
```python
# permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""Allow only owners to edit objects."""
def has_object_permission(self, request, view, obj):
# Read permissions allowed for any request
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions only for owner
return obj.author == request.user
class IsAdminOrReadOnly(permissions.BasePermission):
"""Allow admins to do anything, others read-only."""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user and request.user.is_staff
class IsVerifiedUser(permissions.BasePermission):
"""Allow only verified users."""
def has_permission(self, request, view):
return request.user and request.user.is_authenticated and request.user.is_verified
```
### 基于角色的访问控制 (RBAC)
```python
# models.py
from django.contrib.auth.models import AbstractUser, Group
class User(AbstractUser):
ROLE_CHOICES = [
('admin', 'Administrator'),
('moderator', 'Moderator'),
('user', 'Regular User'),
]
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
def is_admin(self):
return self.role == 'admin' or self.is_superuser
def is_moderator(self):
return self.role in ['admin', 'moderator']
# Mixins
class AdminRequiredMixin:
"""Mixin to require admin role."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.is_admin():
from django.core.exceptions import PermissionDenied
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
```
## SQL 注入防护
### Django ORM 保护
```python
# GOOD: Django ORM automatically escapes parameters
def get_user(username):
return User.objects.get(username=username) # Safe
# GOOD: Using parameters with raw()
def search_users(query):
return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
# BAD: Never directly interpolate user input
def get_user_bad(username):
return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # VULNERABLE!
# GOOD: Using filter with proper escaping
def get_users_by_email(email):
return User.objects.filter(email__iexact=email) # Safe
# GOOD: Using Q objects for complex queries
from django.db.models import Q
def search_users_complex(query):
return User.objects.filter(
Q(username__icontains=query) |
Q(email__icontains=query)
) # Safe
```
### 使用 raw() 的额外安全措施
```python
# If you must use raw SQL, always use parameters
User.objects.raw(
'SELECT * FROM users WHERE email = %s AND status = %s',
[user_input_email, status]
)
```
## XSS 防护
### 模板转义
```django
{# Django auto-escapes variables by default - SAFE #}
{{ user_input }} {# Escaped HTML #}
{# Explicitly mark safe only for trusted content #}
{{ trusted_html|safe }} {# Not escaped #}
{# Use template filters for safe HTML #}
{{ user_input|escape }} {# Same as default #}
{{ user_input|striptags }} {# Remove all HTML tags #}
{# JavaScript escaping #}
<script>
var username = {{ username|escapejs }};
</script>
```
### 安全字符串处理
```python
from django.utils.safestring import mark_safe
from django.utils.html import escape
# BAD: Never mark user input as safe without escaping
def render_bad(user_input):
return mark_safe(user_input) # VULNERABLE!
# GOOD: Escape first, then mark safe
def render_good(user_input):
return mark_safe(escape(user_input))
# GOOD: Use format_html for HTML with variables
from django.utils.html import format_html
def greet_user(username):
return format_html('<span class="user">{}</span>', escape(username))
```
### HTTP 头部
```python
# settings.py
SECURE_CONTENT_TYPE_NOSNIFF = True # Prevent MIME sniffing
SECURE_BROWSER_XSS_FILTER = True # Enable XSS filter
X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking
# Custom middleware
from django.conf import settings
class SecurityHeaderMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['X-Content-Type-Options'] = 'nosniff'
response['X-Frame-Options'] = 'DENY'
response['X-XSS-Protection'] = '1; mode=block'
response['Content-Security-Policy'] = "default-src 'self'"
return response
```
## CSRF 防护
### 默认 CSRF 防护
```python
# settings.py - CSRF is enabled by default
CSRF_COOKIE_SECURE = True # Only send over HTTPS
CSRF_COOKIE_HTTPONLY = True # Prevent JavaScript access
CSRF_COOKIE_SAMESITE = 'Lax' # Prevent CSRF in some cases
CSRF_TRUSTED_ORIGINS = ['https://example.com'] # Trusted domains
# Template usage
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
# AJAX requests
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
fetch('/api/endpoint/', {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
```
### 豁免视图(谨慎使用)
```python
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt # Only use when absolutely necessary!
def webhook_view(request):
# Webhook from external service
pass
```
## 文件上传安全
### 文件验证
```python
import os
from django.core.exceptions import ValidationError
def validate_file_extension(value):
"""Validate file extension."""
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
def validate_file_size(value):
"""Validate file size (max 5MB)."""
filesize = value.size
if filesize > 5 * 1024 * 1024:
raise ValidationError('File too large. Max size is 5MB.')
# models.py
class Document(models.Model):
file = models.FileField(
upload_to='documents/',
validators=[validate_file_extension, validate_file_size]
)
```
### 安全的文件存储
```python
# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'
# Use a separate domain for media in production
MEDIA_DOMAIN = 'https://media.example.com'
# Don't serve user uploads directly
# Use whitenoise or a CDN for static files
# Use a separate server or S3 for media files
```
## API 安全
### 速率限制
```python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
'upload': '10/hour',
}
}
# Custom throttle
from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
rate = '60/min'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
rate = '1000/day'
```
### API 认证
```python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def protected_view(request):
return Response({'message': 'You are authenticated'})
```
## 安全头部
### 内容安全策略
```python
# settings.py
CSP_DEFAULT_SRC = "'self'"
CSP_SCRIPT_SRC = "'self' https://cdn.example.com"
CSP_STYLE_SRC = "'self' 'unsafe-inline'"
CSP_IMG_SRC = "'self' data: https:"
CSP_CONNECT_SRC = "'self' https://api.example.com"
# Middleware
class CSPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Content-Security-Policy'] = (
f"default-src {CSP_DEFAULT_SRC}; "
f"script-src {CSP_SCRIPT_SRC}; "
f"style-src {CSP_STYLE_SRC}; "
f"img-src {CSP_IMG_SRC}; "
f"connect-src {CSP_CONNECT_SRC}"
)
return response
```
## 环境变量
### 管理密钥
```python
# Use python-decouple or django-environ
import environ
env = environ.Env(
# set casting, default value
DEBUG=(bool, False)
)
# reading .env file
environ.Env.read_env()
SECRET_KEY = env('DJANGO_SECRET_KEY')
DATABASE_URL = env('DATABASE_URL')
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
# .env file (never commit this)
DEBUG=False
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
ALLOWED_HOSTS=example.com,www.example.com
```
## 记录安全事件
```python
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': '/var/log/django/security.log',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.security': {
'handlers': ['file', 'console'],
'level': 'WARNING',
'propagate': True,
},
'django.request': {
'handlers': ['file'],
'level': 'ERROR',
'propagate': False,
},
},
}
```
## 快速安全检查清单
| 检查项 | 描述 |
|-------|-------------|
| `DEBUG = False` | 切勿在生产环境中启用 DEBUG |
| 仅限 HTTPS | 强制 SSL使用安全 Cookie |
| 强密钥 | 对 SECRET\_KEY 使用环境变量 |
| 密码验证 | 启用所有密码验证器 |
| CSRF 防护 | 默认启用,不要禁用 |
| XSS 防护 | Django 自动转义,不要在用户输入上使用 `&#124;safe` |
| SQL 注入 | 使用 ORM切勿在查询中拼接字符串 |
| 文件上传 | 验证文件类型和大小 |
| 速率限制 | 限制 API 端点访问频率 |
| 安全头部 | CSP、X-Frame-Options、HSTS |
| 日志记录 | 记录安全事件 |
| 更新 | 保持 Django 及其依赖项为最新版本 |
请记住:安全是一个过程,而非产品。请定期审查并更新您的安全实践。

View File

@@ -0,0 +1,728 @@
---
name: django-tdd
description: Django测试策略包括pytest-django、TDD方法论、factory_boy、模拟、覆盖率以及测试Django REST Framework API。
---
# 使用 TDD 进行 Django 测试
使用 pytest、factory\_boy 和 Django REST Framework 进行 Django 应用程序的测试驱动开发。
## 何时激活
* 编写新的 Django 应用程序时
* 实现 Django REST Framework API 时
* 测试 Django 模型、视图和序列化器时
* 为 Django 项目设置测试基础设施时
## Django 的 TDD 工作流
### 红-绿-重构循环
```python
# Step 1: RED - Write failing test
def test_user_creation():
user = User.objects.create_user(email='test@example.com', password='testpass123')
assert user.email == 'test@example.com'
assert user.check_password('testpass123')
assert not user.is_staff
# Step 2: GREEN - Make test pass
# Create User model or factory
# Step 3: REFACTOR - Improve while keeping tests green
```
## 设置
### pytest 配置
```ini
# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings.test
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--reuse-db
--nomigrations
--cov=apps
--cov-report=html
--cov-report=term-missing
--strict-markers
markers =
slow: marks tests as slow
integration: marks tests as integration tests
```
### 测试设置
```python
# config/settings/test.py
from .base import *
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# Disable migrations for speed
class DisableMigrations:
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
# Faster password hashing
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
# Email backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Celery always eager
CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True
```
### conftest.py
```python
# tests/conftest.py
import pytest
from django.utils import timezone
from django.contrib.auth import get_user_model
User = get_user_model()
@pytest.fixture(autouse=True)
def timezone_settings(settings):
"""Ensure consistent timezone."""
settings.TIME_ZONE = 'UTC'
@pytest.fixture
def user(db):
"""Create a test user."""
return User.objects.create_user(
email='test@example.com',
password='testpass123',
username='testuser'
)
@pytest.fixture
def admin_user(db):
"""Create an admin user."""
return User.objects.create_superuser(
email='admin@example.com',
password='adminpass123',
username='admin'
)
@pytest.fixture
def authenticated_client(client, user):
"""Return authenticated client."""
client.force_login(user)
return client
@pytest.fixture
def api_client():
"""Return DRF API client."""
from rest_framework.test import APIClient
return APIClient()
@pytest.fixture
def authenticated_api_client(api_client, user):
"""Return authenticated API client."""
api_client.force_authenticate(user=user)
return api_client
```
## Factory Boy
### 工厂设置
```python
# tests/factories.py
import factory
from factory import fuzzy
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from apps.products.models import Product, Category
User = get_user_model()
class UserFactory(factory.django.DjangoModelFactory):
"""Factory for User model."""
class Meta:
model = User
email = factory.Sequence(lambda n: f"user{n}@example.com")
username = factory.Sequence(lambda n: f"user{n}")
password = factory.PostGenerationMethodCall('set_password', 'testpass123')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
is_active = True
class CategoryFactory(factory.django.DjangoModelFactory):
"""Factory for Category model."""
class Meta:
model = Category
name = factory.Faker('word')
slug = factory.LazyAttribute(lambda obj: obj.name.lower())
description = factory.Faker('text')
class ProductFactory(factory.django.DjangoModelFactory):
"""Factory for Product model."""
class Meta:
model = Product
name = factory.Faker('sentence', nb_words=3)
slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-'))
description = factory.Faker('text')
price = fuzzy.FuzzyDecimal(10.00, 1000.00, 2)
stock = fuzzy.FuzzyInteger(0, 100)
is_active = True
category = factory.SubFactory(CategoryFactory)
created_by = factory.SubFactory(UserFactory)
@factory.post_generation
def tags(self, create, extracted, **kwargs):
"""Add tags to product."""
if not create:
return
if extracted:
for tag in extracted:
self.tags.add(tag)
```
### 使用工厂
```python
# tests/test_models.py
import pytest
from tests.factories import ProductFactory, UserFactory
def test_product_creation():
"""Test product creation using factory."""
product = ProductFactory(price=100.00, stock=50)
assert product.price == 100.00
assert product.stock == 50
assert product.is_active is True
def test_product_with_tags():
"""Test product with tags."""
tags = [TagFactory(name='electronics'), TagFactory(name='new')]
product = ProductFactory(tags=tags)
assert product.tags.count() == 2
def test_multiple_products():
"""Test creating multiple products."""
products = ProductFactory.create_batch(10)
assert len(products) == 10
```
## 模型测试
### 模型测试
```python
# tests/test_models.py
import pytest
from django.core.exceptions import ValidationError
from tests.factories import UserFactory, ProductFactory
class TestUserModel:
"""Test User model."""
def test_create_user(self, db):
"""Test creating a regular user."""
user = UserFactory(email='test@example.com')
assert user.email == 'test@example.com'
assert user.check_password('testpass123')
assert not user.is_staff
assert not user.is_superuser
def test_create_superuser(self, db):
"""Test creating a superuser."""
user = UserFactory(
email='admin@example.com',
is_staff=True,
is_superuser=True
)
assert user.is_staff
assert user.is_superuser
def test_user_str(self, db):
"""Test user string representation."""
user = UserFactory(email='test@example.com')
assert str(user) == 'test@example.com'
class TestProductModel:
"""Test Product model."""
def test_product_creation(self, db):
"""Test creating a product."""
product = ProductFactory()
assert product.id is not None
assert product.is_active is True
assert product.created_at is not None
def test_product_slug_generation(self, db):
"""Test automatic slug generation."""
product = ProductFactory(name='Test Product')
assert product.slug == 'test-product'
def test_product_price_validation(self, db):
"""Test price cannot be negative."""
product = ProductFactory(price=-10)
with pytest.raises(ValidationError):
product.full_clean()
def test_product_manager_active(self, db):
"""Test active manager method."""
ProductFactory.create_batch(5, is_active=True)
ProductFactory.create_batch(3, is_active=False)
active_count = Product.objects.active().count()
assert active_count == 5
def test_product_stock_management(self, db):
"""Test stock management."""
product = ProductFactory(stock=10)
product.reduce_stock(5)
product.refresh_from_db()
assert product.stock == 5
with pytest.raises(ValueError):
product.reduce_stock(10) # Not enough stock
```
## 视图测试
### Django 视图测试
```python
# tests/test_views.py
import pytest
from django.urls import reverse
from tests.factories import ProductFactory, UserFactory
class TestProductViews:
"""Test product views."""
def test_product_list(self, client, db):
"""Test product list view."""
ProductFactory.create_batch(10)
response = client.get(reverse('products:list'))
assert response.status_code == 200
assert len(response.context['products']) == 10
def test_product_detail(self, client, db):
"""Test product detail view."""
product = ProductFactory()
response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))
assert response.status_code == 200
assert response.context['product'] == product
def test_product_create_requires_login(self, client, db):
"""Test product creation requires authentication."""
response = client.get(reverse('products:create'))
assert response.status_code == 302
assert response.url.startswith('/accounts/login/')
def test_product_create_authenticated(self, authenticated_client, db):
"""Test product creation as authenticated user."""
response = authenticated_client.get(reverse('products:create'))
assert response.status_code == 200
def test_product_create_post(self, authenticated_client, db, category):
"""Test creating a product via POST."""
data = {
'name': 'Test Product',
'description': 'A test product',
'price': '99.99',
'stock': 10,
'category': category.id,
}
response = authenticated_client.post(reverse('products:create'), data)
assert response.status_code == 302
assert Product.objects.filter(name='Test Product').exists()
```
## DRF API 测试
### 序列化器测试
```python
# tests/test_serializers.py
import pytest
from rest_framework.exceptions import ValidationError
from apps.products.serializers import ProductSerializer
from tests.factories import ProductFactory
class TestProductSerializer:
"""Test ProductSerializer."""
def test_serialize_product(self, db):
"""Test serializing a product."""
product = ProductFactory()
serializer = ProductSerializer(product)
data = serializer.data
assert data['id'] == product.id
assert data['name'] == product.name
assert data['price'] == str(product.price)
def test_deserialize_product(self, db):
"""Test deserializing product data."""
data = {
'name': 'Test Product',
'description': 'Test description',
'price': '99.99',
'stock': 10,
'category': 1,
}
serializer = ProductSerializer(data=data)
assert serializer.is_valid()
product = serializer.save()
assert product.name == 'Test Product'
assert float(product.price) == 99.99
def test_price_validation(self, db):
"""Test price validation."""
data = {
'name': 'Test Product',
'price': '-10.00',
'stock': 10,
}
serializer = ProductSerializer(data=data)
assert not serializer.is_valid()
assert 'price' in serializer.errors
def test_stock_validation(self, db):
"""Test stock cannot be negative."""
data = {
'name': 'Test Product',
'price': '99.99',
'stock': -5,
}
serializer = ProductSerializer(data=data)
assert not serializer.is_valid()
assert 'stock' in serializer.errors
```
### API ViewSet 测试
```python
# tests/test_api.py
import pytest
from rest_framework.test import APIClient
from rest_framework import status
from django.urls import reverse
from tests.factories import ProductFactory, UserFactory
class TestProductAPI:
"""Test Product API endpoints."""
@pytest.fixture
def api_client(self):
"""Return API client."""
return APIClient()
def test_list_products(self, api_client, db):
"""Test listing products."""
ProductFactory.create_batch(10)
url = reverse('api:product-list')
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.data['count'] == 10
def test_retrieve_product(self, api_client, db):
"""Test retrieving a product."""
product = ProductFactory()
url = reverse('api:product-detail', kwargs={'pk': product.id})
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.data['id'] == product.id
def test_create_product_unauthorized(self, api_client, db):
"""Test creating product without authentication."""
url = reverse('api:product-list')
data = {'name': 'Test Product', 'price': '99.99'}
response = api_client.post(url, data)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_create_product_authorized(self, authenticated_api_client, db):
"""Test creating product as authenticated user."""
url = reverse('api:product-list')
data = {
'name': 'Test Product',
'description': 'Test',
'price': '99.99',
'stock': 10,
}
response = authenticated_api_client.post(url, data)
assert response.status_code == status.HTTP_201_CREATED
assert response.data['name'] == 'Test Product'
def test_update_product(self, authenticated_api_client, db):
"""Test updating a product."""
product = ProductFactory(created_by=authenticated_api_client.user)
url = reverse('api:product-detail', kwargs={'pk': product.id})
data = {'name': 'Updated Product'}
response = authenticated_api_client.patch(url, data)
assert response.status_code == status.HTTP_200_OK
assert response.data['name'] == 'Updated Product'
def test_delete_product(self, authenticated_api_client, db):
"""Test deleting a product."""
product = ProductFactory(created_by=authenticated_api_client.user)
url = reverse('api:product-detail', kwargs={'pk': product.id})
response = authenticated_api_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
def test_filter_products_by_price(self, api_client, db):
"""Test filtering products by price."""
ProductFactory(price=50)
ProductFactory(price=150)
url = reverse('api:product-list')
response = api_client.get(url, {'price_min': 100})
assert response.status_code == status.HTTP_200_OK
assert response.data['count'] == 1
def test_search_products(self, api_client, db):
"""Test searching products."""
ProductFactory(name='Apple iPhone')
ProductFactory(name='Samsung Galaxy')
url = reverse('api:product-list')
response = api_client.get(url, {'search': 'Apple'})
assert response.status_code == status.HTTP_200_OK
assert response.data['count'] == 1
```
## 模拟与打补丁
### 模拟外部服务
```python
# tests/test_views.py
from unittest.mock import patch, Mock
import pytest
class TestPaymentView:
"""Test payment view with mocked payment gateway."""
@patch('apps.payments.services.stripe')
def test_successful_payment(self, mock_stripe, client, user, product):
"""Test successful payment with mocked Stripe."""
# Configure mock
mock_stripe.Charge.create.return_value = {
'id': 'ch_123',
'status': 'succeeded',
'amount': 9999,
}
client.force_login(user)
response = client.post(reverse('payments:process'), {
'product_id': product.id,
'token': 'tok_visa',
})
assert response.status_code == 302
mock_stripe.Charge.create.assert_called_once()
@patch('apps.payments.services.stripe')
def test_failed_payment(self, mock_stripe, client, user, product):
"""Test failed payment."""
mock_stripe.Charge.create.side_effect = Exception('Card declined')
client.force_login(user)
response = client.post(reverse('payments:process'), {
'product_id': product.id,
'token': 'tok_visa',
})
assert response.status_code == 302
assert 'error' in response.url
```
### 模拟邮件发送
```python
# tests/test_email.py
from django.core import mail
from django.test import override_settings
@override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
def test_order_confirmation_email(db, order):
"""Test order confirmation email."""
order.send_confirmation_email()
assert len(mail.outbox) == 1
assert order.user.email in mail.outbox[0].to
assert 'Order Confirmation' in mail.outbox[0].subject
```
## 集成测试
### 完整流程测试
```python
# tests/test_integration.py
import pytest
from django.urls import reverse
from tests.factories import UserFactory, ProductFactory
class TestCheckoutFlow:
"""Test complete checkout flow."""
def test_guest_to_purchase_flow(self, client, db):
"""Test complete flow from guest to purchase."""
# Step 1: Register
response = client.post(reverse('users:register'), {
'email': 'test@example.com',
'password': 'testpass123',
'password_confirm': 'testpass123',
})
assert response.status_code == 302
# Step 2: Login
response = client.post(reverse('users:login'), {
'email': 'test@example.com',
'password': 'testpass123',
})
assert response.status_code == 302
# Step 3: Browse products
product = ProductFactory(price=100)
response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))
assert response.status_code == 200
# Step 4: Add to cart
response = client.post(reverse('cart:add'), {
'product_id': product.id,
'quantity': 1,
})
assert response.status_code == 302
# Step 5: Checkout
response = client.get(reverse('checkout:review'))
assert response.status_code == 200
assert product.name in response.content.decode()
# Step 6: Complete purchase
with patch('apps.checkout.services.process_payment') as mock_payment:
mock_payment.return_value = True
response = client.post(reverse('checkout:complete'))
assert response.status_code == 302
assert Order.objects.filter(user__email='test@example.com').exists()
```
## 测试最佳实践
### 应该做
* **使用工厂**:而不是手动创建对象
* **每个测试一个断言**:保持测试聚焦
* **描述性测试名称**`test_user_cannot_delete_others_post`
* **测试边界情况**空输入、None 值、边界条件
* **模拟外部服务**:不要依赖外部 API
* **使用夹具**:消除重复
* **测试权限**:确保授权有效
* **保持测试快速**:使用 `--reuse-db``--nomigrations`
### 不应该做
* **不要测试 Django 内部**:相信 Django 能正常工作
* **不要测试第三方代码**:相信库能正常工作
* **不要忽略失败的测试**:所有测试必须通过
* **不要让测试产生依赖**:测试应该能以任何顺序运行
* **不要过度模拟**:只模拟外部依赖
* **不要测试私有方法**:测试公共接口
* **不要使用生产数据库**:始终使用测试数据库
## 覆盖率
### 覆盖率配置
```bash
# Run tests with coverage
pytest --cov=apps --cov-report=html --cov-report=term-missing
# Generate HTML report
open htmlcov/index.html
```
### 覆盖率目标
| 组件 | 目标覆盖率 |
|-----------|-----------------|
| 模型 | 90%+ |
| 序列化器 | 85%+ |
| 视图 | 80%+ |
| 服务 | 90%+ |
| 工具 | 80%+ |
| 总体 | 80%+ |
## 快速参考
| 模式 | 用途 |
|---------|-------|
| `@pytest.mark.django_db` | 启用数据库访问 |
| `client` | Django 测试客户端 |
| `api_client` | DRF API 客户端 |
| `factory.create_batch(n)` | 创建多个对象 |
| `patch('module.function')` | 模拟外部依赖 |
| `override_settings` | 临时更改设置 |
| `force_authenticate()` | 在测试中绕过身份验证 |
| `assertRedirects` | 检查重定向 |
| `assertTemplateUsed` | 验证模板使用 |
| `mail.outbox` | 检查已发送的邮件 |
记住:测试即文档。好的测试解释了你的代码应如何工作。保持测试简单、可读和可维护。

View File

@@ -0,0 +1,466 @@
---
name: django-verification
description: Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR.
---
# Django 验证循环
在发起 PR 之前、进行重大更改之后以及部署之前运行,以确保 Django 应用程序的质量和安全性。
## 阶段 1: 环境检查
```bash
# Verify Python version
python --version # Should match project requirements
# Check virtual environment
which python
pip list --outdated
# Verify environment variables
python -c "import os; import environ; print('DJANGO_SECRET_KEY set' if os.environ.get('DJANGO_SECRET_KEY') else 'MISSING: DJANGO_SECRET_KEY')"
```
如果环境配置错误,请停止并修复。
## 阶段 2: 代码质量与格式化
```bash
# Type checking
mypy . --config-file pyproject.toml
# Linting with ruff
ruff check . --fix
# Formatting with black
black . --check
black . # Auto-fix
# Import sorting
isort . --check-only
isort . # Auto-fix
# Django-specific checks
python manage.py check --deploy
```
常见问题:
* 公共函数缺少类型提示
* 违反 PEP 8 格式规范
* 导入未排序
* 生产配置中遗留调试设置
## 阶段 3: 数据库迁移
```bash
# Check for unapplied migrations
python manage.py showmigrations
# Create missing migrations
python manage.py makemigrations --check
# Dry-run migration application
python manage.py migrate --plan
# Apply migrations (test environment)
python manage.py migrate
# Check for migration conflicts
python manage.py makemigrations --merge # Only if conflicts exist
```
报告:
* 待应用的迁移数量
* 任何迁移冲突
* 模型更改未生成迁移
## 阶段 4: 测试与覆盖率
```bash
# Run all tests with pytest
pytest --cov=apps --cov-report=html --cov-report=term-missing --reuse-db
# Run specific app tests
pytest apps/users/tests/
# Run with markers
pytest -m "not slow" # Skip slow tests
pytest -m integration # Only integration tests
# Coverage report
open htmlcov/index.html
```
报告:
* 总测试数X 通过Y 失败Z 跳过
* 总体覆盖率XX%
* 按应用划分的覆盖率明细
覆盖率目标:
| 组件 | 目标 |
|-----------|--------|
| 模型 | 90%+ |
| 序列化器 | 85%+ |
| 视图 | 80%+ |
| 服务 | 90%+ |
| 总体 | 80%+ |
## 阶段 5: 安全扫描
```bash
# Dependency vulnerabilities
pip-audit
safety check --full-report
# Django security checks
python manage.py check --deploy
# Bandit security linter
bandit -r . -f json -o bandit-report.json
# Secret scanning (if gitleaks is installed)
gitleaks detect --source . --verbose
# Environment variable check
python -c "from django.core.exceptions import ImproperlyConfigured; from django.conf import settings; settings.DEBUG"
```
报告:
* 发现易受攻击的依赖项
* 安全配置问题
* 检测到硬编码的密钥
* DEBUG 模式状态(生产环境中应为 False
## 阶段 6: Django 管理命令
```bash
# Check for model issues
python manage.py check
# Collect static files
python manage.py collectstatic --noinput --clear
# Create superuser (if needed for tests)
echo "from apps.users.models import User; User.objects.create_superuser('admin@example.com', 'admin')" | python manage.py shell
# Database integrity
python manage.py check --database default
# Cache verification (if using Redis)
python -c "from django.core.cache import cache; cache.set('test', 'value', 10); print(cache.get('test'))"
```
## 阶段 7: 性能检查
```bash
# Django Debug Toolbar output (check for N+1 queries)
# Run in dev mode with DEBUG=True and access a page
# Look for duplicate queries in SQL panel
# Query count analysis
django-admin debugsqlshell # If django-debug-sqlshell installed
# Check for missing indexes
python manage.py shell << EOF
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT table_name, index_name FROM information_schema.statistics WHERE table_schema = 'public'")
print(cursor.fetchall())
EOF
```
报告:
* 每页查询次数(典型页面应 < 50
* 缺少数据库索引
* 检测到重复查询
## 阶段 8: 静态资源
```bash
# Check for npm dependencies (if using npm)
npm audit
npm audit fix
# Build static files (if using webpack/vite)
npm run build
# Verify static files
ls -la staticfiles/
python manage.py findstatic css/style.css
```
## 阶段 9: 配置审查
```python
# Run in Python shell to verify settings
python manage.py shell << EOF
from django.conf import settings
import os
# Critical checks
checks = {
'DEBUG is False': not settings.DEBUG,
'SECRET_KEY set': bool(settings.SECRET_KEY and len(settings.SECRET_KEY) > 30),
'ALLOWED_HOSTS set': len(settings.ALLOWED_HOSTS) > 0,
'HTTPS enabled': getattr(settings, 'SECURE_SSL_REDIRECT', False),
'HSTS enabled': getattr(settings, 'SECURE_HSTS_SECONDS', 0) > 0,
'Database configured': settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3',
}
for check, result in checks.items():
status = '' if result else ''
print(f"{status} {check}")
EOF
```
## 阶段 10: 日志配置
```bash
# Test logging output
python manage.py shell << EOF
import logging
logger = logging.getLogger('django')
logger.warning('Test warning message')
logger.error('Test error message')
EOF
# Check log files (if configured)
tail -f /var/log/django/django.log
```
## 阶段 11: API 文档(如果使用 DRF
```bash
# Generate schema
python manage.py generateschema --format openapi-json > schema.json
# Validate schema
# Check if schema.json is valid JSON
python -c "import json; json.load(open('schema.json'))"
# Access Swagger UI (if using drf-yasg)
# Visit http://localhost:8000/swagger/ in browser
```
## 阶段 12: 差异审查
```bash
# Show diff statistics
git diff --stat
# Show actual changes
git diff
# Show changed files
git diff --name-only
# Check for common issues
git diff | grep -i "todo\|fixme\|hack\|xxx"
git diff | grep "print(" # Debug statements
git diff | grep "DEBUG = True" # Debug mode
git diff | grep "import pdb" # Debugger
```
检查清单:
* 无调试语句print, pdb, breakpoint()
* 关键代码中无 TODO/FIXME 注释
* 无硬编码的密钥或凭证
* 模型更改包含数据库迁移
* 配置更改已记录
* 外部调用存在错误处理
* 需要时已进行事务管理
## 输出模板
```
DJANGO VERIFICATION REPORT
==========================
Phase 1: Environment Check
✓ Python 3.11.5
✓ Virtual environment active
✓ All environment variables set
Phase 2: Code Quality
✓ mypy: No type errors
✗ ruff: 3 issues found (auto-fixed)
✓ black: No formatting issues
✓ isort: Imports properly sorted
✓ manage.py check: No issues
Phase 3: Migrations
✓ No unapplied migrations
✓ No migration conflicts
✓ All models have migrations
Phase 4: Tests + Coverage
Tests: 247 passed, 0 failed, 5 skipped
Coverage:
Overall: 87%
users: 92%
products: 89%
orders: 85%
payments: 91%
Phase 5: Security Scan
✗ pip-audit: 2 vulnerabilities found (fix required)
✓ safety check: No issues
✓ bandit: No security issues
✓ No secrets detected
✓ DEBUG = False
Phase 6: Django Commands
✓ collectstatic completed
✓ Database integrity OK
✓ Cache backend reachable
Phase 7: Performance
✓ No N+1 queries detected
✓ Database indexes configured
✓ Query count acceptable
Phase 8: Static Assets
✓ npm audit: No vulnerabilities
✓ Assets built successfully
✓ Static files collected
Phase 9: Configuration
✓ DEBUG = False
✓ SECRET_KEY configured
✓ ALLOWED_HOSTS set
✓ HTTPS enabled
✓ HSTS enabled
✓ Database configured
Phase 10: Logging
✓ Logging configured
✓ Log files writable
Phase 11: API Documentation
✓ Schema generated
✓ Swagger UI accessible
Phase 12: Diff Review
Files changed: 12
+450, -120 lines
✓ No debug statements
✓ No hardcoded secrets
✓ Migrations included
RECOMMENDATION: ⚠️ Fix pip-audit vulnerabilities before deploying
NEXT STEPS:
1. Update vulnerable dependencies
2. Re-run security scan
3. Deploy to staging for final testing
```
## 预部署检查清单
* \[ ] 所有测试通过
* \[ ] 覆盖率 ≥ 80%
* \[ ] 无安全漏洞
* \[ ] 无未应用的迁移
* \[ ] 生产设置中 DEBUG = False
* \[ ] SECRET\_KEY 已正确配置
* \[ ] ALLOWED\_HOSTS 设置正确
* \[ ] 数据库备份已启用
* \[ ] 静态文件已收集并提供服务
* \[ ] 日志配置正常且有效
* \[ ] 错误监控Sentry 等)已配置
* \[ ] CDN 已配置(如果适用)
* \[ ] Redis/缓存后端已配置
* \[ ] Celery 工作进程正在运行(如果适用)
* \[ ] HTTPS/SSL 已配置
* \[ ] 环境变量已记录
## 持续集成
### GitHub Actions 示例
```yaml
# .github/workflows/django-verification.yml
name: Django Verification
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install ruff black mypy pytest pytest-django pytest-cov bandit safety pip-audit
- name: Code quality checks
run: |
ruff check .
black . --check
isort . --check-only
mypy .
- name: Security scan
run: |
bandit -r . -f json -o bandit-report.json
safety check --full-report
pip-audit
- name: Run tests
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
DJANGO_SECRET_KEY: test-secret-key
run: |
pytest --cov=apps --cov-report=xml --cov-report=term-missing
- name: Upload coverage
uses: codecov/codecov-action@v3
```
## 快速参考
| 检查项 | 命令 |
|-------|---------|
| 环境 | `python --version` |
| 类型检查 | `mypy .` |
| 代码检查 | `ruff check .` |
| 格式化 | `black . --check` |
| 迁移 | `python manage.py makemigrations --check` |
| 测试 | `pytest --cov=apps` |
| 安全 | `pip-audit && bandit -r .` |
| Django 检查 | `python manage.py check --deploy` |
| 收集静态文件 | `python manage.py collectstatic --noinput` |
| 差异统计 | `git diff --stat` |
请记住:自动化验证可以发现常见问题,但不能替代在预发布环境中的手动代码审查和测试。

View File

@@ -0,0 +1,260 @@
---
name: eval-harness
description: 克劳德代码会话的正式评估框架实施评估驱动开发EDD原则
tools: Read, Write, Edit, Bash, Grep, Glob
---
# Eval Harness 技能
一个用于 Claude Code 会话的正式评估框架,实现了评估驱动开发 (EDD) 原则。
## 理念
评估驱动开发将评估视为 "AI 开发的单元测试"
* 在实现 **之前** 定义预期行为
* 在开发过程中持续运行评估
* 跟踪每次更改的回归情况
* 使用 pass@k 指标来衡量可靠性
## 评估类型
### 能力评估
测试 Claude 是否能完成之前无法完成的事情:
```markdown
[能力评估:功能名称]
任务:描述 Claude 应完成的工作
成功标准:
- [ ] 标准 1
- [ ] 标准 2
- [ ] 标准 标准 3
预期输出:对预期结果的描述
```
### 回归评估
确保更改不会破坏现有功能:
```markdown
[回归评估:功能名称]
基线SHA 或检查点名称
测试:
- 现有测试-1通过/失败
- 现有测试-2通过/失败
- 现有测试-3通过/失败
结果X/Y 通过(之前为 Y/Y
```
## 评分器类型
### 1. 基于代码的评分器
使用代码进行确定性检查:
```bash
# Check if file contains expected pattern
grep -q "export function handleAuth" src/auth.ts && echo "PASS" || echo "FAIL"
# Check if tests pass
npm test -- --testPathPattern="auth" && echo "PASS" || echo "FAIL"
# Check if build succeeds
npm run build && echo "PASS" || echo "FAIL"
```
### 2. 基于模型的评分器
使用 Claude 来评估开放式输出:
```markdown
[MODEL GRADER PROMPT]
评估以下代码变更:
1. 它是否解决了所述问题?
2. 它的结构是否良好?
3. 是否处理了边界情况?
4. 错误处理是否恰当?
评分1-5 (1=差5=优秀)
推理:[解释]
```
### 3. 人工评分器
标记为需要手动审查:
```markdown
[HUMAN REVIEW REQUIRED]
变更:对更改内容的描述
原因:为何需要人工审核
风险等级:低/中/高
```
## 指标
### pass@k
"k 次尝试中至少成功一次"
* pass@1:首次尝试成功率
* pass@33 次尝试内成功率
* 典型目标pass@3 > 90%
### pass^k
"所有 k 次试验都成功"
* 更高的可靠性门槛
* pass^3连续 3 次成功
* 用于关键路径
## 评估工作流程
### 1. 定义(编码前)
```markdown
## 评估定义:功能-xyz
### 能力评估
1. 可以创建新用户账户
2. 可以验证电子邮件格式
3. 可以安全地哈希密码
### 回归评估
1. 现有登录功能仍然有效
2. 会话管理未改变
3. 注销流程完整
### 成功指标
- 能力评估的 pass@3 > 90%
- 回归评估的 pass^3 = 100%
```
### 2. 实现
编写代码以通过已定义的评估。
### 3. 评估
```bash
# Run capability evals
[Run each capability eval, record PASS/FAIL]
# Run regression evals
npm test -- --testPathPattern="existing"
# Generate report
```
### 4. 报告
```markdown
评估报告:功能-xyz
========================
能力评估:
创建用户: 通过(通过@1
验证邮箱: 通过(通过@2
哈希密码: 通过(通过@1
总计: 3/3 通过
回归评估:
登录流程: 通过
会话管理: 通过
登出流程: 通过
总计: 3/3 通过
指标:
通过@1 67% (2/3)
通过@3 100% (3/3)
状态:准备就绪,待审核
```
## 集成模式
### 实施前
```
/eval define feature-name
```
`.claude/evals/feature-name.md` 处创建评估定义文件
### 实施过程中
```
/eval check feature-name
```
运行当前评估并报告状态
### 实施后
```
/eval report feature-name
```
生成完整的评估报告
## 评估存储
将评估存储在项目中:
```
.claude/
evals/
feature-xyz.md # Eval definition
feature-xyz.log # Eval run history
baseline.json # Regression baselines
```
## 最佳实践
1. **在编码前定义评估** - 强制清晰地思考成功标准
2. **频繁运行评估** - 及早发现回归问题
3. **随时间跟踪 pass@k** - 监控可靠性趋势
4. **尽可能使用代码评分器** - 确定性 > 概率性
5. **对安全性进行人工审查** - 永远不要完全自动化安全检查
6. **保持评估快速** - 缓慢的评估不会被运行
7. **评估与代码版本化** - 评估是一等工件
## 示例:添加身份验证
```markdown
## EVAL添加身份验证
### 第 1 阶段:定义 (10 分钟)
能力评估:
- [ ] 用户可以使用邮箱/密码注册
- [ ] 用户可以使用有效凭证登录
- [ ] 无效凭证被拒绝并显示适当的错误
- [ ] 会话在页面重新加载后保持
- [ ] 登出操作清除会话
回归评估:
- [ ] 公共路由仍可访问
- [ ] API 响应未改变
- [ ] 数据库模式兼容
### 第 2 阶段:实施 (时间不定)
[编写代码]
### 第 3 阶段:评估
运行:/eval check add-authentication
### 第 4 阶段:报告
评估报告:添加身份验证
==============================
能力5/5 通过 (pass@3: 100%)
回归3/3 通过 (pass^3: 100%)
状态:可以发布
```

View File

@@ -0,0 +1,631 @@
---
name: frontend-patterns
description: React、Next.js、状态管理、性能优化和UI最佳实践的前端开发模式。
---
# 前端开发模式
适用于 React、Next.js 和高性能用户界面的现代前端模式。
## 组件模式
### 组合优于继承
```typescript
// ✅ GOOD: Component composition
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
}
export function Card({ children, variant = 'default' }: CardProps) {
return <div className={`card card-${variant}`}>{children}</div>
}
export function CardHeader({ children }: { children: React.ReactNode }) {
return <div className="card-header">{children}</div>
}
export function CardBody({ children }: { children: React.ReactNode }) {
return <div className="card-body">{children}</div>
}
// Usage
<Card>
<CardHeader>Title</CardHeader>
<CardBody>Content</CardBody>
</Card>
```
### 复合组件
```typescript
interface TabsContextValue {
activeTab: string
setActiveTab: (tab: string) => void
}
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
export function Tabs({ children, defaultTab }: {
children: React.ReactNode
defaultTab: string
}) {
const [activeTab, setActiveTab] = useState(defaultTab)
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
)
}
export function TabList({ children }: { children: React.ReactNode }) {
return <div className="tab-list">{children}</div>
}
export function Tab({ id, children }: { id: string, children: React.ReactNode }) {
const context = useContext(TabsContext)
if (!context) throw new Error('Tab must be used within Tabs')
return (
<button
className={context.activeTab === id ? 'active' : ''}
onClick={() => context.setActiveTab(id)}
>
{children}
</button>
)
}
// Usage
<Tabs defaultTab="overview">
<TabList>
<Tab id="overview">Overview</Tab>
<Tab id="details">Details</Tab>
</TabList>
</Tabs>
```
### 渲染属性模式
```typescript
interface DataLoaderProps<T> {
url: string
children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
}
export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return <>{children(data, loading, error)}</>
}
// Usage
<DataLoader<Market[]> url="/api/markets">
{(markets, loading, error) => {
if (loading) return <Spinner />
if (error) return <Error error={error} />
return <MarketList markets={markets!} />
}}
</DataLoader>
```
## 自定义 Hooks 模式
### 状态管理 Hook
```typescript
export function useToggle(initialValue = false): [boolean, () => void] {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => {
setValue(v => !v)
}, [])
return [value, toggle]
}
// Usage
const [isOpen, toggleOpen] = useToggle()
```
### 异步数据获取 Hook
```typescript
interface UseQueryOptions<T> {
onSuccess?: (data: T) => void
onError?: (error: Error) => void
enabled?: boolean
}
export function useQuery<T>(
key: string,
fetcher: () => Promise<T>,
options?: UseQueryOptions<T>
) {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<Error | null>(null)
const [loading, setLoading] = useState(false)
const refetch = useCallback(async () => {
setLoading(true)
setError(null)
try {
const result = await fetcher()
setData(result)
options?.onSuccess?.(result)
} catch (err) {
const error = err as Error
setError(error)
options?.onError?.(error)
} finally {
setLoading(false)
}
}, [fetcher, options])
useEffect(() => {
if (options?.enabled !== false) {
refetch()
}
}, [key, refetch, options?.enabled])
return { data, error, loading, refetch }
}
// Usage
const { data: markets, loading, error, refetch } = useQuery(
'markets',
() => fetch('/api/markets').then(r => r.json()),
{
onSuccess: data => console.log('Fetched', data.length, 'markets'),
onError: err => console.error('Failed:', err)
}
)
```
### 防抖 Hook
```typescript
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
// Usage
const [searchQuery, setSearchQuery] = useState('')
const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {
if (debouncedQuery) {
performSearch(debouncedQuery)
}
}, [debouncedQuery])
```
## 状态管理模式
### Context + Reducer 模式
```typescript
interface State {
markets: Market[]
selectedMarket: Market | null
loading: boolean
}
type Action =
| { type: 'SET_MARKETS'; payload: Market[] }
| { type: 'SELECT_MARKET'; payload: Market }
| { type: 'SET_LOADING'; payload: boolean }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_MARKETS':
return { ...state, markets: action.payload }
case 'SELECT_MARKET':
return { ...state, selectedMarket: action.payload }
case 'SET_LOADING':
return { ...state, loading: action.payload }
default:
return state
}
}
const MarketContext = createContext<{
state: State
dispatch: Dispatch<Action>
} | undefined>(undefined)
export function MarketProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
markets: [],
selectedMarket: null,
loading: false
})
return (
<MarketContext.Provider value={{ state, dispatch }}>
{children}
</MarketContext.Provider>
)
}
export function useMarkets() {
const context = useContext(MarketContext)
if (!context) throw new Error('useMarkets must be used within MarketProvider')
return context
}
```
## 性能优化
### 记忆化
```typescript
// ✅ useMemo for expensive computations
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// ✅ useCallback for functions passed to children
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
// ✅ React.memo for pure components
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
return (
<div className="market-card">
<h3>{market.name}</h3>
<p>{market.description}</p>
</div>
)
})
```
### 代码分割与懒加载
```typescript
import { lazy, Suspense } from 'react'
// ✅ Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
export function Dashboard() {
return (
<div>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
<Suspense fallback={null}>
<ThreeJsBackground />
</Suspense>
</div>
)
}
```
### 长列表虚拟化
```typescript
import { useVirtualizer } from '@tanstack/react-virtual'
export function VirtualMarketList({ markets }: { markets: Market[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: markets.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100, // Estimated row height
overscan: 5 // Extra items to render
})
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative'
}}
>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<MarketCard market={markets[virtualRow.index]} />
</div>
))}
</div>
</div>
)
}
```
## 表单处理模式
### 带验证的受控表单
```typescript
interface FormData {
name: string
description: string
endDate: string
}
interface FormErrors {
name?: string
description?: string
endDate?: string
}
export function CreateMarketForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
description: '',
endDate: ''
})
const [errors, setErrors] = useState<FormErrors>({})
const validate = (): boolean => {
const newErrors: FormErrors = {}
if (!formData.name.trim()) {
newErrors.name = 'Name is required'
} else if (formData.name.length > 200) {
newErrors.name = 'Name must be under 200 characters'
}
if (!formData.description.trim()) {
newErrors.description = 'Description is required'
}
if (!formData.endDate) {
newErrors.endDate = 'End date is required'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validate()) return
try {
await createMarket(formData)
// Success handling
} catch (error) {
// Error handling
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
placeholder="Market name"
/>
{errors.name && <span className="error">{errors.name}</span>}
{/* Other fields */}
<button type="submit">Create Market</button>
</form>
)
}
```
## 错误边界模式
```typescript
interface ErrorBoundaryState {
hasError: boolean
error: Error | null
}
export class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
ErrorBoundaryState
> {
state: ErrorBoundaryState = {
hasError: false,
error: null
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error boundary caught:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
)
}
return this.props.children
}
}
// Usage
<ErrorBoundary>
<App />
</ErrorBoundary>
```
## 动画模式
### Framer Motion 动画
```typescript
import { motion, AnimatePresence } from 'framer-motion'
// ✅ List animations
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
return (
<AnimatePresence>
{markets.map(market => (
<motion.div
key={market.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<MarketCard market={market} />
</motion.div>
))}
</AnimatePresence>
)
}
// ✅ Modal animations
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="modal-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
<motion.div
className="modal-content"
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
)
}
```
## 无障碍模式
### 键盘导航
```typescript
export function Dropdown({ options, onSelect }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false)
const [activeIndex, setActiveIndex] = useState(0)
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
setActiveIndex(i => Math.min(i + 1, options.length - 1))
break
case 'ArrowUp':
e.preventDefault()
setActiveIndex(i => Math.max(i - 1, 0))
break
case 'Enter':
e.preventDefault()
onSelect(options[activeIndex])
setIsOpen(false)
break
case 'Escape':
setIsOpen(false)
break
}
}
return (
<div
role="combobox"
aria-expanded={isOpen}
aria-haspopup="listbox"
onKeyDown={handleKeyDown}
>
{/* Dropdown implementation */}
</div>
)
}
```
### 焦点管理
```typescript
export function Modal({ isOpen, onClose, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocusRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (isOpen) {
// Save currently focused element
previousFocusRef.current = document.activeElement as HTMLElement
// Focus modal
modalRef.current?.focus()
} else {
// Restore focus when closing
previousFocusRef.current?.focus()
}
}, [isOpen])
return isOpen ? (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
tabIndex={-1}
onKeyDown={e => e.key === 'Escape' && onClose()}
>
{children}
</div>
) : null
}
```
**记住**:现代前端模式能实现可维护、高性能的用户界面。选择适合你项目复杂度的模式。

View File

@@ -0,0 +1,673 @@
---
name: golang-patterns
description: 构建稳健、高效且可维护的Go应用程序的惯用Go模式、最佳实践和约定。
---
# Go 开发模式
用于构建健壮、高效和可维护应用程序的惯用 Go 模式与最佳实践。
## 何时激活
* 编写新的 Go 代码时
* 审查 Go 代码时
* 重构现有 Go 代码时
* 设计 Go 包/模块时
## 核心原则
### 1. 简洁与清晰
Go 推崇简洁而非精巧。代码应该显而易见且易于阅读。
```go
// Good: Clear and direct
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Bad: Overly clever
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
```
### 2. 让零值变得有用
设计类型时,应使其零值无需初始化即可立即使用。
```go
// Good: Zero value is useful
type Counter struct {
mu sync.Mutex
count int // zero value is 0, ready to use
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// Good: bytes.Buffer works with zero value
var buf bytes.Buffer
buf.WriteString("hello")
// Bad: Requires initialization
type BadCounter struct {
counts map[string]int // nil map will panic
}
```
### 3. 接受接口,返回结构体
函数应该接受接口参数并返回具体类型。
```go
// Good: Accepts interface, returns concrete type
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// Bad: Returns interface (hides implementation details unnecessarily)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}
```
## 错误处理模式
### 带上下文的错误包装
```go
// Good: Wrap errors with context
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
```
### 自定义错误类型
```go
// Define domain-specific errors
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Sentinel errors for common cases
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
```
### 使用 errors.Is 和 errors.As 检查错误
```go
func HandleError(err error) {
// Check for specific error
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
// Check for error type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error on field %s: %s",
validationErr.Field, validationErr.Message)
return
}
// Unknown error
log.Printf("Unexpected error: %v", err)
}
```
### 永不忽略错误
```go
// Bad: Ignoring error with blank identifier
result, _ := doSomething()
// Good: Handle or explicitly document why it's safe to ignore
result, err := doSomething()
if err != nil {
return err
}
// Acceptable: When error truly doesn't matter (rare)
_ = writer.Close() // Best-effort cleanup, error logged elsewhere
```
## 并发模式
### 工作池
```go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
```
### 用于取消和超时的 Context
```go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
```
### 优雅关闭
```go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
```
### 用于协调 Goroutine 的 errgroup
```go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // Capture loop variables
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
```
### 避免 Goroutine 泄漏
```go
// Bad: Goroutine leak if context is cancelled
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // Blocks forever if no receiver
}()
return ch
}
// Good: Properly handles cancellation
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // Buffered channel
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}
```
## 接口设计
### 小而专注的接口
```go
// Good: Single-method interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces as needed
type ReadWriteCloser interface {
Reader
Writer
Closer
}
```
### 在接口使用处定义接口
```go
// In the consumer package, not the provider
package service
// UserStore defines what this service needs
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// Concrete implementation can be in another package
// It doesn't need to know about this interface
```
### 使用类型断言实现可选行为
```go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// Flush if supported
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}
```
## 包组织
### 标准项目布局
```text
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/
│ └── client/ # Public API client
├── api/
│ └── v1/ # API definitions (proto, OpenAPI)
├── testdata/ # Test fixtures
├── go.mod
├── go.sum
└── Makefile
```
### 包命名
```go
// Good: Short, lowercase, no underscores
package http
package json
package user
// Bad: Verbose, mixed case, or redundant
package httpHandler
package json_parser
package userService // Redundant 'Service' suffix
```
### 避免包级状态
```go
// Bad: Global mutable state
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// Good: Dependency injection
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}
```
## 结构体设计
### 函数式选项模式
```go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // default
logger: log.Default(), // default
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)
```
### 使用嵌入实现组合
```go
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // Embedding - Server gets Log method
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// Usage
s := NewServer(":8080")
s.Log("Starting...") // Calls embedded Logger.Log
```
## 内存与性能
### 当大小已知时预分配切片
```go
// Bad: Grows slice multiple times
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// Good: Single allocation
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}
```
### 为频繁分配使用 sync.Pool
```go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// Process...
return buf.Bytes()
}
```
### 避免在循环中进行字符串拼接
```go
// Bad: Creates many string allocations
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// Good: Single allocation with strings.Builder
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// Best: Use standard library
func join(parts []string) string {
return strings.Join(parts, ",")
}
```
## Go 工具集成
### 基本命令
```bash
# Build and run
go build ./...
go run ./cmd/myapp
# Testing
go test ./...
go test -race ./...
go test -cover ./...
# Static analysis
go vet ./...
staticcheck ./...
golangci-lint run
# Module management
go mod tidy
go mod verify
# Formatting
gofmt -w .
goimports -w .
```
### 推荐的 Linter 配置 (.golangci.yml)
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: false
```
## 快速参考Go 惯用法
| 惯用法 | 描述 |
|-------|-------------|
| 接受接口,返回结构体 | 函数接受接口参数,返回具体类型 |
| 错误即值 | 将错误视为一等值,而非异常 |
| 不要通过共享内存来通信 | 使用通道在 goroutine 之间进行协调 |
| 让零值变得有用 | 类型应无需显式初始化即可工作 |
| 少量复制优于少量依赖 | 避免不必要的外部依赖 |
| 清晰优于精巧 | 优先考虑可读性而非精巧性 |
| gofmt 虽非最爱,但却是每个人的朋友 | 始终使用 gofmt/goimports 格式化代码 |
| 提前返回 | 先处理错误,保持主逻辑路径无缩进 |
## 应避免的反模式
```go
// Bad: Naked returns in long functions
func process() (result int, err error) {
// ... 50 lines ...
return // What is being returned?
}
// Bad: Using panic for control flow
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // Don't do this
}
return user
}
// Bad: Passing context in struct
type Request struct {
ctx context.Context // Context should be first param
ID string
}
// Good: Context as first parameter
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// Bad: Mixing value and pointer receivers
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // Value receiver
func (c *Counter) Increment() { c.n++ } // Pointer receiver
// Pick one style and be consistent
```
**记住**Go 代码应该以最好的方式显得“乏味”——可预测、一致且易于理解。如有疑问,保持简单。

View File

@@ -0,0 +1,721 @@
---
name: golang-testing
description: Go测试模式包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循TDD方法论采用地道的Go实践。
---
# Go 测试模式
遵循 TDD 方法论,用于编写可靠、可维护测试的全面 Go 测试模式。
## 何时激活
* 编写新的 Go 函数或方法时
* 为现有代码添加测试覆盖率时
* 为性能关键代码创建基准测试时
* 为输入验证实现模糊测试时
* 在 Go 项目中遵循 TDD 工作流时
## Go 的 TDD 工作流
### 红-绿-重构循环
```
RED → Write a failing test first
GREEN → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT → Continue with next requirement
```
### Go 中的分步 TDD
```go
// Step 1: Define the interface/signature
// calculator.go
package calculator
func Add(a, b int) int {
panic("not implemented") // Placeholder
}
// Step 2: Write failing test (RED)
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
// Step 3: Run test - verify FAIL
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented
// Step 4: Implement minimal code (GREEN)
func Add(a, b int) int {
return a + b
}
// Step 5: Run test - verify PASS
// $ go test
// PASS
// Step 6: Refactor if needed, verify tests still pass
```
## 表驱动测试
Go 测试的标准模式。以最少的代码实现全面的覆盖。
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero values", 0, 0, 0},
{"mixed signs", -1, 1, 0},
{"large numbers", 1000000, 2000000, 3000000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.expected)
}
})
}
}
```
### 包含错误情况的表驱动测试
```go
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{
name: "valid config",
input: `{"host": "localhost", "port": 8080}`,
want: &Config{Host: "localhost", Port: 8080},
},
{
name: "invalid JSON",
input: `{invalid}`,
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "minimal config",
input: `{}`,
want: &Config{}, // Zero value config
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %+v; want %+v", got, tt.want)
}
})
}
}
```
## 子测试和子基准测试
### 组织相关测试
```go
func TestUser(t *testing.T) {
// Setup shared by all subtests
db := setupTestDB(t)
t.Run("Create", func(t *testing.T) {
user := &User{Name: "Alice"}
err := db.CreateUser(user)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if user.ID == "" {
t.Error("expected user ID to be set")
}
})
t.Run("Get", func(t *testing.T) {
user, err := db.GetUser("alice-id")
if err != nil {
t.Fatalf("GetUser failed: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
})
t.Run("Update", func(t *testing.T) {
// ...
})
t.Run("Delete", func(t *testing.T) {
// ...
})
}
```
### 并行子测试
```go
func TestParallel(t *testing.T) {
tests := []struct {
name string
input string
}{
{"case1", "input1"},
{"case2", "input2"},
{"case3", "input3"},
}
for _, tt := range tests {
tt := tt // Capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Run subtests in parallel
result := Process(tt.input)
// assertions...
_ = result
})
}
}
```
## 测试辅助函数
### 辅助函数
```go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper() // Marks this as a helper function
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
// Cleanup when test finishes
t.Cleanup(func() {
db.Close()
})
// Run migrations
if _, err := db.Exec(schema); err != nil {
t.Fatalf("failed to create schema: %v", err)
}
return db
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
```
### 临时文件和目录
```go
func TestFileProcessing(t *testing.T) {
// Create temp directory - automatically cleaned up
tmpDir := t.TempDir()
// Create test file
testFile := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0644)
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
// Run test
result, err := ProcessFile(testFile)
if err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}
// Assert...
_ = result
}
```
## 黄金文件
针对存储在 `testdata/` 中的预期输出文件进行测试。
```go
var update = flag.Bool("update", false, "update golden files")
func TestRender(t *testing.T) {
tests := []struct {
name string
input Template
}{
{"simple", Template{Name: "test"}},
{"complex", Template{Name: "test", Items: []string{"a", "b"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Render(tt.input)
golden := filepath.Join("testdata", tt.name+".golden")
if *update {
// Update golden file: go test -update
err := os.WriteFile(golden, got, 0644)
if err != nil {
t.Fatalf("failed to update golden file: %v", err)
}
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("failed to read golden file: %v", err)
}
if !bytes.Equal(got, want) {
t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
}
```
## 使用接口进行模拟
### 基于接口的模拟
```go
// Define interface for dependencies
type UserRepository interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
// Production implementation
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
// Real database query
}
// Mock implementation for tests
type MockUserRepository struct {
GetUserFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUser(id string) (*User, error) {
return m.GetUserFunc(id)
}
func (m *MockUserRepository) SaveUser(user *User) error {
return m.SaveUserFunc(user)
}
// Test using mock
func TestUserService(t *testing.T) {
mock := &MockUserRepository{
GetUserFunc: func(id string) (*User, error) {
if id == "123" {
return &User{ID: "123", Name: "Alice"}, nil
}
return nil, ErrNotFound
},
}
service := NewUserService(mock)
user, err := service.GetUserProfile("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
}
```
## 基准测试
### 基本基准测试
```go
func BenchmarkProcess(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer() // Don't count setup time
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Run: go test -bench=BenchmarkProcess -benchmem
// Output: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
```
### 不同大小的基准测试
```go
func BenchmarkSort(b *testing.B) {
sizes := []int{100, 1000, 10000, 100000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateRandomSlice(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Make a copy to avoid sorting already sorted data
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
})
}
}
```
### 内存分配基准测试
```go
func BenchmarkStringConcat(b *testing.B) {
parts := []string{"hello", "world", "foo", "bar", "baz"}
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for _, p := range parts {
s += p
}
_ = s
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, p := range parts {
sb.WriteString(p)
}
_ = sb.String()
}
})
b.Run("join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.Join(parts, "")
}
})
}
```
## 模糊测试 (Go 1.18+)
### 基本模糊测试
```go
func FuzzParseJSON(f *testing.F) {
// Add seed corpus
f.Add(`{"name": "test"}`)
f.Add(`{"count": 123}`)
f.Add(`[]`)
f.Add(`""`)
f.Fuzz(func(t *testing.T, input string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(input), &result)
if err != nil {
// Invalid JSON is expected for random input
return
}
// If parsing succeeded, re-encoding should work
_, err = json.Marshal(result)
if err != nil {
t.Errorf("Marshal failed after successful Unmarshal: %v", err)
}
})
}
// Run: go test -fuzz=FuzzParseJSON -fuzztime=30s
```
### 多输入模糊测试
```go
func FuzzCompare(f *testing.F) {
f.Add("hello", "world")
f.Add("", "")
f.Add("abc", "abc")
f.Fuzz(func(t *testing.T, a, b string) {
result := Compare(a, b)
// Property: Compare(a, a) should always equal 0
if a == b && result != 0 {
t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
}
// Property: Compare(a, b) and Compare(b, a) should have opposite signs
reverse := Compare(b, a)
if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
if result != 0 || reverse != 0 {
t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
a, b, result, b, a, reverse)
}
}
})
}
```
## 测试覆盖率
### 运行覆盖率
```bash
# Basic coverage
go test -cover ./...
# Generate coverage profile
go test -coverprofile=coverage.out ./...
# View coverage in browser
go tool cover -html=coverage.out
# View coverage by function
go tool cover -func=coverage.out
# Coverage with race detection
go test -race -coverprofile=coverage.out ./...
```
### 覆盖率目标
| 代码类型 | 目标 |
|-----------|--------|
| 关键业务逻辑 | 100% |
| 公共 API | 90%+ |
| 通用代码 | 80%+ |
| 生成的代码 | 排除 |
### 从覆盖率中排除生成的代码
```go
//go:generate mockgen -source=interface.go -destination=mock_interface.go
// In coverage profile, exclude with build tags:
// go test -cover -tags=!generate ./...
```
## HTTP 处理器测试
```go
func TestHealthHandler(t *testing.T) {
// Create request
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
// Call handler
HealthHandler(w, req)
// Check response
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Errorf("got body %q; want %q", body, "OK")
}
}
func TestAPIHandler(t *testing.T) {
tests := []struct {
name string
method string
path string
body string
wantStatus int
wantBody string
}{
{
name: "get user",
method: http.MethodGet,
path: "/users/123",
wantStatus: http.StatusOK,
wantBody: `{"id":"123","name":"Alice"}`,
},
{
name: "not found",
method: http.MethodGet,
path: "/users/999",
wantStatus: http.StatusNotFound,
},
{
name: "create user",
method: http.MethodPost,
path: "/users",
body: `{"name":"Bob"}`,
wantStatus: http.StatusCreated,
},
}
handler := NewAPIHandler()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var body io.Reader
if tt.body != "" {
body = strings.NewReader(tt.body)
}
req := httptest.NewRequest(tt.method, tt.path, body)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != tt.wantStatus {
t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
}
if tt.wantBody != "" && w.Body.String() != tt.wantBody {
t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
}
})
}
}
```
## 命令测试
```bash
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific test
go test -run TestAdd ./...
# Run tests matching pattern
go test -run "TestUser/Create" ./...
# Run tests with race detector
go test -race ./...
# Run tests with coverage
go test -cover -coverprofile=coverage.out ./...
# Run short tests only
go test -short ./...
# Run tests with timeout
go test -timeout 30s ./...
# Run benchmarks
go test -bench=. -benchmem ./...
# Run fuzzing
go test -fuzz=FuzzParse -fuzztime=30s ./...
# Count test runs (for flaky test detection)
go test -count=10 ./...
```
## 最佳实践
**应该:**
* **先**写测试 (TDD)
* 使用表驱动测试以实现全面覆盖
* 测试行为,而非实现
* 在辅助函数中使用 `t.Helper()`
* 对于独立的测试使用 `t.Parallel()`
* 使用 `t.Cleanup()` 清理资源
* 使用描述场景的有意义的测试名称
**不应该:**
* 直接测试私有函数 (通过公共 API 测试)
* 在测试中使用 `time.Sleep()` (使用通道或条件)
* 忽略不稳定的测试 (修复或移除它们)
* 模拟所有东西 (在可能的情况下优先使用集成测试)
* 跳过错误路径测试
## 与 CI/CD 集成
```yaml
# GitHub Actions example
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run tests
run: go test -race -coverprofile=coverage.out ./...
- name: Check coverage
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
awk -F'%' '{if ($1 < 80) exit 1}'
```
**记住**:测试即文档。它们展示了你的代码应如何使用。清晰地编写它们并保持更新。

View File

@@ -0,0 +1,206 @@
---
name: iterative-retrieval
description: 用于逐步优化上下文检索以解决子代理上下文问题的模式
---
# 迭代检索模式
解决多智能体工作流中的“上下文问题”,即子智能体在开始工作前不知道需要哪些上下文。
## 问题
子智能体被生成时上下文有限。它们不知道:
* 哪些文件包含相关代码
* 代码库中存在哪些模式
* 项目使用什么术语
标准方法会失败:
* **发送所有内容**:超出上下文限制
* **不发送任何内容**:智能体缺乏关键信息
* **猜测所需内容**:经常出错
## 解决方案:迭代检索
一个逐步优化上下文的 4 阶段循环:
```
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ DISPATCH │─────▶│ EVALUATE │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ LOOP │◀─────│ REFINE │ │
│ └──────────┘ └──────────┘ │
│ │
│ Max 3 cycles, then proceed │
└─────────────────────────────────────────────┘
```
### 阶段 1调度
初始的广泛查询以收集候选文件:
```javascript
// Start with high-level intent
const initialQuery = {
patterns: ['src/**/*.ts', 'lib/**/*.ts'],
keywords: ['authentication', 'user', 'session'],
excludes: ['*.test.ts', '*.spec.ts']
};
// Dispatch to retrieval agent
const candidates = await retrieveFiles(initialQuery);
```
### 阶段 2评估
评估检索到的内容的相关性:
```javascript
function evaluateRelevance(files, task) {
return files.map(file => ({
path: file.path,
relevance: scoreRelevance(file.content, task),
reason: explainRelevance(file.content, task),
missingContext: identifyGaps(file.content, task)
}));
}
```
评分标准:
* **高 (0.8-1.0)**:直接实现目标功能
* **中 (0.5-0.7)**:包含相关模式或类型
* **低 (0.2-0.4)**:略微相关
* **无 (0-0.2)**:不相关,排除
### 阶段 3优化
根据评估结果更新搜索条件:
```javascript
function refineQuery(evaluation, previousQuery) {
return {
// Add new patterns discovered in high-relevance files
patterns: [...previousQuery.patterns, ...extractPatterns(evaluation)],
// Add terminology found in codebase
keywords: [...previousQuery.keywords, ...extractKeywords(evaluation)],
// Exclude confirmed irrelevant paths
excludes: [...previousQuery.excludes, ...evaluation
.filter(e => e.relevance < 0.2)
.map(e => e.path)
],
// Target specific gaps
focusAreas: evaluation
.flatMap(e => e.missingContext)
.filter(unique)
};
}
```
### 阶段 4循环
使用优化后的条件重复(最多 3 个周期):
```javascript
async function iterativeRetrieve(task, maxCycles = 3) {
let query = createInitialQuery(task);
let bestContext = [];
for (let cycle = 0; cycle < maxCycles; cycle++) {
const candidates = await retrieveFiles(query);
const evaluation = evaluateRelevance(candidates, task);
// Check if we have sufficient context
const highRelevance = evaluation.filter(e => e.relevance >= 0.7);
if (highRelevance.length >= 3 && !hasCriticalGaps(evaluation)) {
return highRelevance;
}
// Refine and continue
query = refineQuery(evaluation, query);
bestContext = mergeContext(bestContext, highRelevance);
}
return bestContext;
}
```
## 实际示例
### 示例 1错误修复上下文
```
Task: "Fix the authentication token expiry bug"
Cycle 1:
DISPATCH: Search for "token", "auth", "expiry" in src/**
EVALUATE: Found auth.ts (0.9), tokens.ts (0.8), user.ts (0.3)
REFINE: Add "refresh", "jwt" keywords; exclude user.ts
Cycle 2:
DISPATCH: Search refined terms
EVALUATE: Found session-manager.ts (0.95), jwt-utils.ts (0.85)
REFINE: Sufficient context (2 high-relevance files)
Result: auth.ts, tokens.ts, session-manager.ts, jwt-utils.ts
```
### 示例 2功能实现
```
Task: "Add rate limiting to API endpoints"
Cycle 1:
DISPATCH: Search "rate", "limit", "api" in routes/**
EVALUATE: No matches - codebase uses "throttle" terminology
REFINE: Add "throttle", "middleware" keywords
Cycle 2:
DISPATCH: Search refined terms
EVALUATE: Found throttle.ts (0.9), middleware/index.ts (0.7)
REFINE: Need router patterns
Cycle 3:
DISPATCH: Search "router", "express" patterns
EVALUATE: Found router-setup.ts (0.8)
REFINE: Sufficient context
Result: throttle.ts, middleware/index.ts, router-setup.ts
```
## 与智能体集成
在智能体提示中使用:
```markdown
在为该任务检索上下文时:
1. 从广泛的关键词搜索开始
2. 评估每个文件的相关性0-1 分制)
3. 识别仍缺失哪些上下文
4. 优化搜索条件并重复(最多 3 个循环)
5. 返回相关性 >= 0.7 的文件
```
## 最佳实践
1. **先宽泛,后逐步细化** - 不要过度指定初始查询
2. **学习代码库术语** - 第一轮循环通常能揭示命名约定
3. **跟踪缺失内容** - 明确识别差距以驱动优化
4. **在“足够好”时停止** - 3 个高相关性文件胜过 10 个中等相关性文件
5. **自信地排除** - 低相关性文件不会变得相关
## 相关
* [长篇指南](https://x.com/affaanmustafa/status/2014040193557471352) - 子智能体编排部分
* `continuous-learning` 技能 - 用于随时间改进的模式
*`~/.claude/agents/` 中的智能体定义

View File

@@ -0,0 +1,138 @@
---
name: java-coding-standards
description: Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout.
---
# Java 编码规范
适用于 Spring Boot 服务中可读、可维护的 Java (17+) 代码的规范。
## 核心原则
* 清晰优于巧妙
* 默认不可变;最小化共享可变状态
* 快速失败并提供有意义的异常
* 一致的命名和包结构
## 命名
```java
// ✅ Classes/Records: PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}
// ✅ Methods/fields: camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}
// ✅ Constants: UPPER_SNAKE_CASE
private static final int MAX_PAGE_SIZE = 100;
```
## 不可变性
```java
// ✅ Favor records and final fields
public record MarketDto(Long id, String name, MarketStatus status) {}
public class Market {
private final Long id;
private final String name;
// getters only, no setters
}
```
## Optional 使用
```java
// ✅ Return Optional from find* methods
Optional<Market> market = marketRepository.findBySlug(slug);
// ✅ Map/flatMap instead of get()
return market
.map(MarketResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
```
## Streams 最佳实践
```java
// ✅ Use streams for transformations, keep pipelines short
List<String> names = markets.stream()
.map(Market::name)
.filter(Objects::nonNull)
.toList();
// ❌ Avoid complex nested streams; prefer loops for clarity
```
## 异常
* 领域错误使用非受检异常;包装技术异常时提供上下文
* 创建特定领域的异常(例如,`MarketNotFoundException`
* 避免宽泛的 `catch (Exception ex)`,除非在中心位置重新抛出/记录
```java
throw new MarketNotFoundException(slug);
```
## 泛型和类型安全
* 避免原始类型;声明泛型参数
* 对于可复用的工具类,优先使用有界泛型
```java
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
```
## 项目结构 (Maven/Gradle)
```
src/main/java/com/example/app/
config/
controller/
service/
repository/
domain/
dto/
util/
src/main/resources/
application.yml
src/test/java/... (mirrors main)
```
## 格式化和风格
* 一致地使用 2 或 4 个空格(项目标准)
* 每个文件一个公共顶级类型
* 保持方法简短且专注;提取辅助方法
* 成员顺序:常量、字段、构造函数、公共方法、受保护方法、私有方法
## 需要避免的代码坏味道
* 长参数列表 → 使用 DTO/构建器
* 深度嵌套 → 提前返回
* 魔法数字 → 命名常量
* 静态可变状态 → 优先使用依赖注入
* 静默捕获块 → 记录日志并处理或重新抛出
## 日志记录
```java
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);
```
## Null 处理
* 仅在不可避免时接受 `@Nullable`;否则使用 `@NonNull`
* 在输入上使用 Bean 验证(`@NotNull`, `@NotBlank`
## 测试期望
* 使用 JUnit 5 + AssertJ 进行流畅的断言
* 使用 Mockito 进行模拟;尽可能避免部分模拟
* 倾向于确定性测试;没有隐藏的休眠
**记住**:保持代码意图明确、类型安全且可观察。除非证明有必要,否则优先考虑可维护性而非微优化。

View File

@@ -0,0 +1,145 @@
---
name: jpa-patterns
description: Spring Boot中的JPA/Hibernate实体设计、关系、查询优化、事务、审计、索引、分页和连接池模式。
---
# JPA/Hibernate 模式
用于 Spring Boot 中的数据建模、存储库和性能调优。
## 实体设计
```java
@Entity
@Table(name = "markets", indexes = {
@Index(name = "idx_markets_slug", columnList = "slug", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class MarketEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(nullable = false, unique = true, length = 120)
private String slug;
@Enumerated(EnumType.STRING)
private MarketStatus status = MarketStatus.ACTIVE;
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;
}
```
启用审计:
```java
@Configuration
@EnableJpaAuditing
class JpaConfig {}
```
## 关联关系和 N+1 预防
```java
@OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PositionEntity> positions = new ArrayList<>();
```
* 默认使用延迟加载;需要时在查询中使用 `JOIN FETCH`
* 避免在集合上使用 `EAGER`;对于读取路径使用 DTO 投影
```java
@Query("select m from MarketEntity m left join fetch m.positions where m.id = :id")
Optional<MarketEntity> findWithPositions(@Param("id") Long id);
```
## 存储库模式
```java
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
Optional<MarketEntity> findBySlug(String slug);
@Query("select m from MarketEntity m where m.status = :status")
Page<MarketEntity> findByStatus(@Param("status") MarketStatus status, Pageable pageable);
}
```
* 使用投影进行轻量级查询:
```java
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page<MarketSummary> findAllBy(Pageable pageable);
```
## 事务
* 使用 `@Transactional` 注解服务方法
* 对读取路径使用 `@Transactional(readOnly = true)` 以进行优化
* 谨慎选择传播行为;避免长时间运行的事务
```java
@Transactional
public Market updateStatus(Long id, MarketStatus status) {
MarketEntity entity = repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Market"));
entity.setStatus(status);
return Market.from(entity);
}
```
## 分页
```java
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<MarketEntity> markets = repo.findByStatus(MarketStatus.ACTIVE, page);
```
对于类似游标的分页,在 JPQL 中包含 `id > :lastId` 并配合排序。
## 索引和性能
* 为常用过滤器添加索引(`status``slug`、外键)
* 使用与查询模式匹配的复合索引(`status, created_at`
* 避免 `select *`;仅投影需要的列
* 使用 `saveAll``hibernate.jdbc.batch_size` 进行批量写入
## 连接池 (HikariCP)
推荐属性:
```
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
```
对于 PostgreSQL LOB 处理,添加:
```
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
```
## 缓存
* 一级缓存是每个 EntityManager 的;避免在事务之间保持实体
* 对于读取频繁的实体,谨慎考虑二级缓存;验证驱逐策略
## 迁移
* 使用 Flyway 或 Liquibase切勿在生产中依赖 Hibernate 自动 DDL
* 保持迁移的幂等性和可添加性;避免无计划地删除列
## 测试数据访问
* 首选使用 Testcontainers 的 `@DataJpaTest` 来镜像生产环境
* 使用日志断言 SQL 效率:设置 `logging.level.org.hibernate.SQL=DEBUG``logging.level.org.hibernate.orm.jdbc.bind=TRACE` 以查看参数值
**请记住**:保持实体精简,查询有针对性,事务简短。通过获取策略和投影来预防 N+1 问题,并根据读写路径建立索引。

View File

@@ -0,0 +1,165 @@
---
name: nutrient-document-processing
description: 使用Nutrient DWS API处理、转换、OCR、提取、编辑、签署和填写文档。支持PDF、DOCX、XLSX、PPTX、HTML和图像文件。
---
# 文档处理
使用 [Nutrient DWS Processor API](https://www.nutrient.io/api/) 处理文档。转换格式、提取文本和表格、对扫描文档进行 OCR、编辑 PII、添加水印、数字签名以及填写 PDF 表单。
## 设置
**[nutrient.io](https://dashboard.nutrient.io/sign_up/?product=processor)** 获取一个免费的 API 密钥
```bash
export NUTRIENT_API_KEY="pdf_live_..."
```
所有请求都以 multipart POST 形式发送到 `https://api.nutrient.io/build`,并附带一个 `instructions` JSON 字段。
## 操作
### 转换文档
```bash
# DOCX to PDF
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.docx=@document.docx" \
-F 'instructions={"parts":[{"file":"document.docx"}]}' \
-o output.pdf
# PDF to DOCX
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"output":{"type":"docx"}}' \
-o output.docx
# HTML to PDF
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "index.html=@index.html" \
-F 'instructions={"parts":[{"html":"index.html"}]}' \
-o output.pdf
```
支持的输入格式PDF, DOCX, XLSX, PPTX, DOC, XLS, PPT, PPS, PPSX, ODT, RTF, HTML, JPG, PNG, TIFF, HEIC, GIF, WebP, SVG, TGA, EPS。
### 提取文本和数据
```bash
# Extract plain text
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"output":{"type":"text"}}' \
-o output.txt
# Extract tables as Excel
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"output":{"type":"xlsx"}}' \
-o tables.xlsx
```
### OCR 扫描文档
```bash
# OCR to searchable PDF (supports 100+ languages)
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "scanned.pdf=@scanned.pdf" \
-F 'instructions={"parts":[{"file":"scanned.pdf"}],"actions":[{"type":"ocr","language":"english"}]}' \
-o searchable.pdf
```
支持语言:通过 ISO 639-2 代码支持 100 多种语言(例如,`eng`, `deu`, `fra`, `spa`, `jpn`, `kor`, `chi_sim`, `chi_tra`, `ara`, `hin`, `rus`)。完整的语言名称如 `english``german` 也适用。查看 [完整的 OCR 语言表](https://www.nutrient.io/guides/document-engine/ocr/language-support/) 以获取所有支持的代码。
### 编辑敏感信息
```bash
# Pattern-based (SSN, email)
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"actions":[{"type":"redaction","strategy":"preset","strategyOptions":{"preset":"social-security-number"}},{"type":"redaction","strategy":"preset","strategyOptions":{"preset":"email-address"}}]}' \
-o redacted.pdf
# Regex-based
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"actions":[{"type":"redaction","strategy":"regex","strategyOptions":{"regex":"\\b[A-Z]{2}\\d{6}\\b"}}]}' \
-o redacted.pdf
```
预设:`social-security-number`, `email-address`, `credit-card-number`, `international-phone-number`, `north-american-phone-number`, `date`, `time`, `url`, `ipv4`, `ipv6`, `mac-address`, `us-zip-code`, `vin`
### 添加水印
```bash
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"actions":[{"type":"watermark","text":"CONFIDENTIAL","fontSize":72,"opacity":0.3,"rotation":-45}]}' \
-o watermarked.pdf
```
### 数字签名
```bash
# Self-signed CMS signature
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "document.pdf=@document.pdf" \
-F 'instructions={"parts":[{"file":"document.pdf"}],"actions":[{"type":"sign","signatureType":"cms"}]}' \
-o signed.pdf
```
### 填写 PDF 表单
```bash
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "form.pdf=@form.pdf" \
-F 'instructions={"parts":[{"file":"form.pdf"}],"actions":[{"type":"fillForm","formFields":{"name":"Jane Smith","email":"jane@example.com","date":"2026-02-06"}}]}' \
-o filled.pdf
```
## MCP 服务器(替代方案)
对于原生工具集成,请使用 MCP 服务器代替 curl
```json
{
"mcpServers": {
"nutrient-dws": {
"command": "npx",
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
"env": {
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY",
"SANDBOX_PATH": "/path/to/working/directory"
}
}
}
}
```
## 使用场景
* 在格式之间转换文档PDF, DOCX, XLSX, PPTX, HTML, 图像)
* 从 PDF 中提取文本、表格或键值对
* 对扫描文档或图像进行 OCR
* 在共享文档前编辑 PII
* 为草稿或机密文档添加水印
* 数字签署合同或协议
* 以编程方式填写 PDF 表单
## 链接
* [API 演练场](https://dashboard.nutrient.io/processor-api/playground/)
* [完整 API 文档](https://www.nutrient.io/guides/dws-processor/)
* [代理技能仓库](https://github.com/PSPDFKit-labs/nutrient-agent-skill)
* [npm MCP 服务器](https://www.npmjs.com/package/@nutrient-sdk/dws-mcp-server)

View File

@@ -0,0 +1,153 @@
---
name: postgres-patterns
description: 基于Supabase最佳实践的PostgreSQL数据库模式用于查询优化、架构设计、索引和安全。
---
# PostgreSQL 模式
PostgreSQL 最佳实践快速参考。如需详细指导,请使用 `database-reviewer` 智能体。
## 何时激活
* 编写 SQL 查询或迁移时
* 设计数据库模式时
* 排查慢查询时
* 实施行级安全性时
* 设置连接池时
## 快速参考
### 索引速查表
| 查询模式 | 索引类型 | 示例 |
|--------------|------------|---------|
| `WHERE col = value` | B-tree默认 | `CREATE INDEX idx ON t (col)` |
| `WHERE col > value` | B-tree | `CREATE INDEX idx ON t (col)` |
| `WHERE a = x AND b > y` | 复合索引 | `CREATE INDEX idx ON t (a, b)` |
| `WHERE jsonb @> '{}'` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| `WHERE tsv @@ query` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| 时间序列范围查询 | BRIN | `CREATE INDEX idx ON t USING brin (col)` |
### 数据类型快速参考
| 使用场景 | 正确类型 | 避免使用 |
|----------|-------------|-------|
| ID | `bigint` | `int`,随机 UUID |
| 字符串 | `text` | `varchar(255)` |
| 时间戳 | `timestamptz` | `timestamp` |
| 货币 | `numeric(10,2)` | `float` |
| 标志位 | `boolean` | `varchar``int` |
### 常见模式
**复合索引顺序:**
```sql
-- Equality columns first, then range columns
CREATE INDEX idx ON orders (status, created_at);
-- Works for: WHERE status = 'pending' AND created_at > '2024-01-01'
```
**覆盖索引:**
```sql
CREATE INDEX idx ON users (email) INCLUDE (name, created_at);
-- Avoids table lookup for SELECT email, name, created_at
```
**部分索引:**
```sql
CREATE INDEX idx ON users (email) WHERE deleted_at IS NULL;
-- Smaller index, only includes active users
```
**RLS 策略(优化版):**
```sql
CREATE POLICY policy ON orders
USING ((SELECT auth.uid()) = user_id); -- Wrap in SELECT!
```
**UPSERT**
```sql
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value;
```
**游标分页:**
```sql
SELECT * FROM products WHERE id > $last_id ORDER BY id LIMIT 20;
-- O(1) vs OFFSET which is O(n)
```
**队列处理:**
```sql
UPDATE jobs SET status = 'processing'
WHERE id = (
SELECT id FROM jobs WHERE status = 'pending'
ORDER BY created_at LIMIT 1
FOR UPDATE SKIP LOCKED
) RETURNING *;
```
### 反模式检测\*\*
```sql
-- Find unindexed foreign keys
SELECT conrelid::regclass, a.attname
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
WHERE c.contype = 'f'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
);
-- Find slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC;
-- Check table bloat
SELECT relname, n_dead_tup, last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
```
### 配置模板
```sql
-- Connection limits (adjust for RAM)
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB';
-- Timeouts
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET statement_timeout = '30s';
-- Monitoring
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Security defaults
REVOKE ALL ON SCHEMA public FROM public;
SELECT pg_reload_conf();
```
## 相关
* 智能体:`database-reviewer` - 完整的数据库审查工作流
* 技能:`clickhouse-io` - ClickHouse 分析模式
* 技能:`backend-patterns` - API 和后端模式
***
*基于 [Supabase Agent Skills](https://github.com/supabase/agent-skills) (MIT License)*

View File

@@ -0,0 +1,350 @@
# 项目指南技能(示例)
这是一个项目特定技能的示例。将其用作您自己项目的模板。
基于一个真实的生产应用程序:[Zenith](https://zenith.chat) - 由 AI 驱动的客户发现平台。
***
## 何时使用
在为其设计的特定项目上工作时,请参考此技能。项目技能包含:
* 架构概述
* 文件结构
* 代码模式
* 测试要求
* 部署工作流
***
## 架构概述
**技术栈:**
* **前端**: Next.js 15 (App Router), TypeScript, React
* **后端**: FastAPI (Python), Pydantic 模型
* **数据库**: Supabase (PostgreSQL)
* **AI**: Claude API支持工具调用和结构化输出
* **部署**: Google Cloud Run
* **测试**: Playwright (E2E), pytest (后端), React Testing Library
**服务:**
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend │
│ Next.js 15 + TypeScript + TailwindCSS │
│ Deployed: Vercel / Cloud Run │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Backend │
│ FastAPI + Python 3.11 + Pydantic │
│ Deployed: Cloud Run │
└─────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Supabase │ │ Claude │ │ Redis │
│ Database │ │ API │ │ Cache │
└──────────┘ └──────────┘ └──────────┘
```
***
## 文件结构
```
project/
├── frontend/
│ └── src/
│ ├── app/ # Next.js app router pages
│ │ ├── api/ # API routes
│ │ ├── (auth)/ # Auth-protected routes
│ │ └── workspace/ # Main app workspace
│ ├── components/ # React components
│ │ ├── ui/ # Base UI components
│ │ ├── forms/ # Form components
│ │ └── layouts/ # Layout components
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities
│ ├── types/ # TypeScript definitions
│ └── config/ # Configuration
├── backend/
│ ├── routers/ # FastAPI route handlers
│ ├── models.py # Pydantic models
│ ├── main.py # FastAPI app entry
│ ├── auth_system.py # Authentication
│ ├── database.py # Database operations
│ ├── services/ # Business logic
│ └── tests/ # pytest tests
├── deploy/ # Deployment configs
├── docs/ # Documentation
└── scripts/ # Utility scripts
```
***
## 代码模式
### API 响应格式 (FastAPI)
```python
from pydantic import BaseModel
from typing import Generic, TypeVar, Optional
T = TypeVar('T')
class ApiResponse(BaseModel, Generic[T]):
success: bool
data: Optional[T] = None
error: Optional[str] = None
@classmethod
def ok(cls, data: T) -> "ApiResponse[T]":
return cls(success=True, data=data)
@classmethod
def fail(cls, error: str) -> "ApiResponse[T]":
return cls(success=False, error=error)
```
### 前端 API 调用 (TypeScript)
```typescript
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
}
async function fetchApi<T>(
endpoint: string,
options?: RequestInit
): Promise<ApiResponse<T>> {
try {
const response = await fetch(`/api${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
})
if (!response.ok) {
return { success: false, error: `HTTP ${response.status}` }
}
return await response.json()
} catch (error) {
return { success: false, error: String(error) }
}
}
```
### Claude AI 集成 (结构化输出)
```python
from anthropic import Anthropic
from pydantic import BaseModel
class AnalysisResult(BaseModel):
summary: str
key_points: list[str]
confidence: float
async def analyze_with_claude(content: str) -> AnalysisResult:
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": content}],
tools=[{
"name": "provide_analysis",
"description": "Provide structured analysis",
"input_schema": AnalysisResult.model_json_schema()
}],
tool_choice={"type": "tool", "name": "provide_analysis"}
)
# Extract tool use result
tool_use = next(
block for block in response.content
if block.type == "tool_use"
)
return AnalysisResult(**tool_use.input)
```
### 自定义 Hooks (React)
```typescript
import { useState, useCallback } from 'react'
interface UseApiState<T> {
data: T | null
loading: boolean
error: string | null
}
export function useApi<T>(
fetchFn: () => Promise<ApiResponse<T>>
) {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: false,
error: null,
})
const execute = useCallback(async () => {
setState(prev => ({ ...prev, loading: true, error: null }))
const result = await fetchFn()
if (result.success) {
setState({ data: result.data!, loading: false, error: null })
} else {
setState({ data: null, loading: false, error: result.error! })
}
}, [fetchFn])
return { ...state, execute }
}
```
***
## 测试要求
### 后端 (pytest)
```bash
# Run all tests
poetry run pytest tests/
# Run with coverage
poetry run pytest tests/ --cov=. --cov-report=html
# Run specific test file
poetry run pytest tests/test_auth.py -v
```
**测试结构:**
```python
import pytest
from httpx import AsyncClient
from main import app
@pytest.fixture
async def client():
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
@pytest.mark.asyncio
async def test_health_check(client: AsyncClient):
response = await client.get("/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"
```
### 前端 (React Testing Library)
```bash
# Run tests
npm run test
# Run with coverage
npm run test -- --coverage
# Run E2E tests
npm run test:e2e
```
**测试结构:**
```typescript
import { render, screen, fireEvent } from '@testing-library/react'
import { WorkspacePanel } from './WorkspacePanel'
describe('WorkspacePanel', () => {
it('renders workspace correctly', () => {
render(<WorkspacePanel />)
expect(screen.getByRole('main')).toBeInTheDocument()
})
it('handles session creation', async () => {
render(<WorkspacePanel />)
fireEvent.click(screen.getByText('New Session'))
expect(await screen.findByText('Session created')).toBeInTheDocument()
})
})
```
***
## 部署工作流
### 部署前检查清单
* \[ ] 所有测试在本地通过
* \[ ] `npm run build` 成功 (前端)
* \[ ] `poetry run pytest` 通过 (后端)
* \[ ] 没有硬编码的密钥
* \[ ] 环境变量已记录
* \[ ] 数据库迁移就绪
### 部署命令
```bash
# Build and deploy frontend
cd frontend && npm run build
gcloud run deploy frontend --source .
# Build and deploy backend
cd backend
gcloud run deploy backend --source .
```
### 环境变量
```bash
# Frontend (.env.local)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
# Backend (.env)
DATABASE_URL=postgresql://...
ANTHROPIC_API_KEY=sk-ant-...
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_KEY=eyJ...
```
***
## 关键规则
1. 在代码、注释或文档中**不使用表情符号**
2. **不可变性** - 永不改变对象或数组
3. **测试驱动开发 (TDD)** - 在实现之前编写测试
4. **最低 80% 覆盖率**
5. **许多小文件** - 典型 200-400 行,最多 800 行
6. 在生产代码中**不使用 console.log**
7. 使用 try/catch 进行**适当的错误处理**
8. 使用 Pydantic/Zod 进行**输入验证**
***
## 相关技能
* `coding-standards.md` - 通用编码最佳实践
* `backend-patterns.md` - API 和数据库模式
* `frontend-patterns.md` - React 和 Next.js 模式
* `tdd-workflow/` - 测试驱动开发方法论

View File

@@ -0,0 +1,749 @@
---
name: python-patterns
description: Pythonic 惯用法、PEP 8 标准、类型提示以及构建健壮、高效、可维护的 Python 应用程序的最佳实践。
---
# Python 开发模式
用于构建健壮、高效和可维护应用程序的惯用 Python 模式与最佳实践。
## 何时激活
* 编写新的 Python 代码
* 审查 Python 代码
* 重构现有的 Python 代码
* 设计 Python 包/模块
## 核心原则
### 1. 可读性很重要
Python 优先考虑可读性。代码应该清晰且易于理解。
```python
# Good: Clear and readable
def get_active_users(users: list[User]) -> list[User]:
"""Return only active users from the provided list."""
return [user for user in users if user.is_active]
# Bad: Clever but confusing
def get_active_users(u):
return [x for x in u if x.a]
```
### 2. 显式优于隐式
避免魔法;清晰说明你的代码在做什么。
```python
# Good: Explicit configuration
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Bad: Hidden side effects
import some_module
some_module.setup() # What does this do?
```
### 3. EAFP - 请求宽恕比请求许可更容易
Python 倾向于使用异常处理而非检查条件。
```python
# Good: EAFP style
def get_value(dictionary: dict, key: str) -> Any:
try:
return dictionary[key]
except KeyError:
return default_value
# Bad: LBYL (Look Before You Leap) style
def get_value(dictionary: dict, key: str) -> Any:
if key in dictionary:
return dictionary[key]
else:
return default_value
```
## 类型提示
### 基本类型注解
```python
from typing import Optional, List, Dict, Any
def process_user(
user_id: str,
data: Dict[str, Any],
active: bool = True
) -> Optional[User]:
"""Process a user and return the updated User or None."""
if not active:
return None
return User(user_id, data)
```
### 现代类型提示Python 3.9+
```python
# Python 3.9+ - Use built-in types
def process_items(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Python 3.8 and earlier - Use typing module
from typing import List, Dict
def process_items(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
```
### 类型别名和 TypeVar
```python
from typing import TypeVar, Union
# Type alias for complex types
JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]
def parse_json(data: str) -> JSON:
return json.loads(data)
# Generic types
T = TypeVar('T')
def first(items: list[T]) -> T | None:
"""Return the first item or None if list is empty."""
return items[0] if items else None
```
### 基于协议的鸭子类型
```python
from typing import Protocol
class Renderable(Protocol):
def render(self) -> str:
"""Render the object to a string."""
def render_all(items: list[Renderable]) -> str:
"""Render all items that implement the Renderable protocol."""
return "\n".join(item.render() for item in items)
```
## 错误处理模式
### 特定异常处理
```python
# Good: Catch specific exceptions
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except FileNotFoundError as e:
raise ConfigError(f"Config file not found: {path}") from e
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in config: {path}") from e
# Bad: Bare except
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except:
return None # Silent failure!
```
### 异常链
```python
def process_data(data: str) -> Result:
try:
parsed = json.loads(data)
except json.JSONDecodeError as e:
# Chain exceptions to preserve the traceback
raise ValueError(f"Failed to parse data: {data}") from e
```
### 自定义异常层次结构
```python
class AppError(Exception):
"""Base exception for all application errors."""
pass
class ValidationError(AppError):
"""Raised when input validation fails."""
pass
class NotFoundError(AppError):
"""Raised when a requested resource is not found."""
pass
# Usage
def get_user(user_id: str) -> User:
user = db.find_user(user_id)
if not user:
raise NotFoundError(f"User not found: {user_id}")
return user
```
## 上下文管理器
### 资源管理
```python
# Good: Using context managers
def process_file(path: str) -> str:
with open(path, 'r') as f:
return f.read()
# Bad: Manual resource management
def process_file(path: str) -> str:
f = open(path, 'r')
try:
return f.read()
finally:
f.close()
```
### 自定义上下文管理器
```python
from contextlib import contextmanager
@contextmanager
def timer(name: str):
"""Context manager to time a block of code."""
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"{name} took {elapsed:.4f} seconds")
# Usage
with timer("data processing"):
process_large_dataset()
```
### 上下文管理器类
```python
class DatabaseTransaction:
def __init__(self, connection):
self.connection = connection
def __enter__(self):
self.connection.begin_transaction()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.connection.commit()
else:
self.connection.rollback()
return False # Don't suppress exceptions
# Usage
with DatabaseTransaction(conn):
user = conn.create_user(user_data)
conn.create_profile(user.id, profile_data)
```
## 推导式和生成器
### 列表推导式
```python
# Good: List comprehension for simple transformations
names = [user.name for user in users if user.is_active]
# Bad: Manual loop
names = []
for user in users:
if user.is_active:
names.append(user.name)
# Complex comprehensions should be expanded
# Bad: Too complex
result = [x * 2 for x in items if x > 0 if x % 2 == 0]
# Good: Use a generator function
def filter_and_transform(items: Iterable[int]) -> list[int]:
result = []
for x in items:
if x > 0 and x % 2 == 0:
result.append(x * 2)
return result
```
### 生成器表达式
```python
# Good: Generator for lazy evaluation
total = sum(x * x for x in range(1_000_000))
# Bad: Creates large intermediate list
total = sum([x * x for x in range(1_000_000)])
```
### 生成器函数
```python
def read_large_file(path: str) -> Iterator[str]:
"""Read a large file line by line."""
with open(path) as f:
for line in f:
yield line.strip()
# Usage
for line in read_large_file("huge.txt"):
process(line)
```
## 数据类和命名元组
### 数据类
```python
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
"""User entity with automatic __init__, __repr__, and __eq__."""
id: str
name: str
email: str
created_at: datetime = field(default_factory=datetime.now)
is_active: bool = True
# Usage
user = User(
id="123",
name="Alice",
email="alice@example.com"
)
```
### 带验证的数据类
```python
@dataclass
class User:
email: str
age: int
def __post_init__(self):
# Validate email format
if "@" not in self.email:
raise ValueError(f"Invalid email: {self.email}")
# Validate age range
if self.age < 0 or self.age > 150:
raise ValueError(f"Invalid age: {self.age}")
```
### 命名元组
```python
from typing import NamedTuple
class Point(NamedTuple):
"""Immutable 2D point."""
x: float
y: float
def distance(self, other: 'Point') -> float:
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
# Usage
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance(p2)) # 5.0
```
## 装饰器
### 函数装饰器
```python
import functools
import time
def timer(func: Callable) -> Callable:
"""Decorator to time function execution."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
# slow_function() prints: slow_function took 1.0012s
```
### 参数化装饰器
```python
def repeat(times: int):
"""Decorator to repeat a function multiple times."""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=3)
def greet(name: str) -> str:
return f"Hello, {name}!"
# greet("Alice") returns ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"]
```
### 基于类的装饰器
```python
class CountCalls:
"""Decorator that counts how many times a function is called."""
def __init__(self, func: Callable):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} has been called {self.count} times")
return self.func(*args, **kwargs)
@CountCalls
def process():
pass
# Each call to process() prints the call count
```
## 并发模式
### 用于 I/O 密集型任务的线程
```python
import concurrent.futures
import threading
def fetch_url(url: str) -> str:
"""Fetch a URL (I/O-bound operation)."""
import urllib.request
with urllib.request.urlopen(url) as response:
return response.read().decode()
def fetch_all_urls(urls: list[str]) -> dict[str, str]:
"""Fetch multiple URLs concurrently using threads."""
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
results = {}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
results[url] = future.result()
except Exception as e:
results[url] = f"Error: {e}"
return results
```
### 用于 CPU 密集型任务的多进程
```python
def process_data(data: list[int]) -> int:
"""CPU-intensive computation."""
return sum(x ** 2 for x in data)
def process_all(datasets: list[list[int]]) -> list[int]:
"""Process multiple datasets using multiple processes."""
with concurrent.futures.ProcessPoolExecutor() as executor:
results = list(executor.map(process_data, datasets))
return results
```
### 用于并发 I/O 的异步/等待
```python
import asyncio
async def fetch_async(url: str) -> str:
"""Fetch a URL asynchronously."""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def fetch_all(urls: list[str]) -> dict[str, str]:
"""Fetch multiple URLs concurrently."""
tasks = [fetch_async(url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return dict(zip(urls, results))
```
## 包组织
### 标准项目布局
```
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_api.py
│ └── test_models.py
├── pyproject.toml
├── README.md
└── .gitignore
```
### 导入约定
```python
# Good: Import order - stdlib, third-party, local
import os
import sys
from pathlib import Path
import requests
from fastapi import FastAPI
from mypackage.models import User
from mypackage.utils import format_name
# Good: Use isort for automatic import sorting
# pip install isort
```
### **init**.py 用于包导出
```python
# mypackage/__init__.py
"""mypackage - A sample Python package."""
__version__ = "1.0.0"
# Export main classes/functions at package level
from mypackage.models import User, Post
from mypackage.utils import format_name
__all__ = ["User", "Post", "format_name"]
```
## 内存和性能
### 使用 **slots** 提高内存效率
```python
# Bad: Regular class uses __dict__ (more memory)
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
# Good: __slots__ reduces memory usage
class Point:
__slots__ = ['x', 'y']
def __init__(self, x: float, y: float):
self.x = x
self.y = y
```
### 生成器用于大数据
```python
# Bad: Returns full list in memory
def read_lines(path: str) -> list[str]:
with open(path) as f:
return [line.strip() for line in f]
# Good: Yields lines one at a time
def read_lines(path: str) -> Iterator[str]:
with open(path) as f:
for line in f:
yield line.strip()
```
### 避免在循环中进行字符串拼接
```python
# Bad: O(n²) due to string immutability
result = ""
for item in items:
result += str(item)
# Good: O(n) using join
result = "".join(str(item) for item in items)
# Good: Using StringIO for building
from io import StringIO
buffer = StringIO()
for item in items:
buffer.write(str(item))
result = buffer.getvalue()
```
## Python 工具集成
### 基本命令
```bash
# Code formatting
black .
isort .
# Linting
ruff check .
pylint mypackage/
# Type checking
mypy .
# Testing
pytest --cov=mypackage --cov-report=html
# Security scanning
bandit -r .
# Dependency management
pip-audit
safety check
```
### pyproject.toml 配置
```toml
[project]
name = "mypackage"
version = "1.0.0"
requires-python = ">=3.9"
dependencies = [
"requests>=2.31.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.5.0",
]
[tool.black]
line-length = 88
target-version = ['py39']
[tool.ruff]
line-length = 88
select = ["E", "F", "I", "N", "W"]
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=mypackage --cov-report=term-missing"
```
## 快速参考Python 惯用法
| 惯用法 | 描述 |
|-------|-------------|
| EAFP | 请求宽恕比请求许可更容易 |
| 上下文管理器 | 使用 `with` 进行资源管理 |
| 列表推导式 | 用于简单的转换 |
| 生成器 | 用于惰性求值和大数据集 |
| 类型提示 | 注解函数签名 |
| 数据类 | 用于具有自动生成方法的数据容器 |
| `__slots__` | 用于内存优化 |
| f-strings | 用于字符串格式化Python 3.6+ |
| `pathlib.Path` | 用于路径操作Python 3.4+ |
| `enumerate` | 用于循环中的索引-元素对 |
## 要避免的反模式
```python
# Bad: Mutable default arguments
def append_to(item, items=[]):
items.append(item)
return items
# Good: Use None and create new list
def append_to(item, items=None):
if items is None:
items = []
items.append(item)
return items
# Bad: Checking type with type()
if type(obj) == list:
process(obj)
# Good: Use isinstance
if isinstance(obj, list):
process(obj)
# Bad: Comparing to None with ==
if value == None:
process()
# Good: Use is
if value is None:
process()
# Bad: from module import *
from os.path import *
# Good: Explicit imports
from os.path import join, exists
# Bad: Bare except
try:
risky_operation()
except:
pass
# Good: Specific exception
try:
risky_operation()
except SpecificError as e:
logger.error(f"Operation failed: {e}")
```
**记住**Python 代码应该具有可读性、显式性,并遵循最小意外原则。如有疑问,优先考虑清晰性而非巧妙性。

View File

@@ -0,0 +1,815 @@
---
name: python-testing
description: 使用pytest、TDD方法、夹具、模拟、参数化和覆盖率要求的Python测试策略。
---
# Python 测试模式
使用 pytest、TDD 方法论和最佳实践的 Python 应用程序全面测试策略。
## 何时激活
* 编写新的 Python 代码(遵循 TDD红、绿、重构
* 为 Python 项目设计测试套件
* 审查 Python 测试覆盖率
* 设置测试基础设施
## 核心测试理念
### 测试驱动开发 (TDD)
始终遵循 TDD 循环:
1. **红**:为期望的行为编写一个失败的测试
2. **绿**:编写最少的代码使测试通过
3. **重构**:在保持测试通过的同时改进代码
```python
# Step 1: Write failing test (RED)
def test_add_numbers():
result = add(2, 3)
assert result == 5
# Step 2: Write minimal implementation (GREEN)
def add(a, b):
return a + b
# Step 3: Refactor if needed (REFACTOR)
```
### 覆盖率要求
* **目标**80%+ 代码覆盖率
* **关键路径**:需要 100% 覆盖率
* 使用 `pytest --cov` 来测量覆盖率
```bash
pytest --cov=mypackage --cov-report=term-missing --cov-report=html
```
## pytest 基础
### 基本测试结构
```python
import pytest
def test_addition():
"""Test basic addition."""
assert 2 + 2 == 4
def test_string_uppercase():
"""Test string uppercasing."""
text = "hello"
assert text.upper() == "HELLO"
def test_list_append():
"""Test list append."""
items = [1, 2, 3]
items.append(4)
assert 4 in items
assert len(items) == 4
```
### 断言
```python
# Equality
assert result == expected
# Inequality
assert result != unexpected
# Truthiness
assert result # Truthy
assert not result # Falsy
assert result is True # Exactly True
assert result is False # Exactly False
assert result is None # Exactly None
# Membership
assert item in collection
assert item not in collection
# Comparisons
assert result > 0
assert 0 <= result <= 100
# Type checking
assert isinstance(result, str)
# Exception testing (preferred approach)
with pytest.raises(ValueError):
raise ValueError("error message")
# Check exception message
with pytest.raises(ValueError, match="invalid input"):
raise ValueError("invalid input provided")
# Check exception attributes
with pytest.raises(ValueError) as exc_info:
raise ValueError("error message")
assert str(exc_info.value) == "error message"
```
## 夹具
### 基本夹具使用
```python
import pytest
@pytest.fixture
def sample_data():
"""Fixture providing sample data."""
return {"name": "Alice", "age": 30}
def test_sample_data(sample_data):
"""Test using the fixture."""
assert sample_data["name"] == "Alice"
assert sample_data["age"] == 30
```
### 带设置/拆卸的夹具
```python
@pytest.fixture
def database():
"""Fixture with setup and teardown."""
# Setup
db = Database(":memory:")
db.create_tables()
db.insert_test_data()
yield db # Provide to test
# Teardown
db.close()
def test_database_query(database):
"""Test database operations."""
result = database.query("SELECT * FROM users")
assert len(result) > 0
```
### 夹具作用域
```python
# Function scope (default) - runs for each test
@pytest.fixture
def temp_file():
with open("temp.txt", "w") as f:
yield f
os.remove("temp.txt")
# Module scope - runs once per module
@pytest.fixture(scope="module")
def module_db():
db = Database(":memory:")
db.create_tables()
yield db
db.close()
# Session scope - runs once per test session
@pytest.fixture(scope="session")
def shared_resource():
resource = ExpensiveResource()
yield resource
resource.cleanup()
```
### 带参数的夹具
```python
@pytest.fixture(params=[1, 2, 3])
def number(request):
"""Parameterized fixture."""
return request.param
def test_numbers(number):
"""Test runs 3 times, once for each parameter."""
assert number > 0
```
### 使用多个夹具
```python
@pytest.fixture
def user():
return User(id=1, name="Alice")
@pytest.fixture
def admin():
return User(id=2, name="Admin", role="admin")
def test_user_admin_interaction(user, admin):
"""Test using multiple fixtures."""
assert admin.can_manage(user)
```
### 自动使用夹具
```python
@pytest.fixture(autouse=True)
def reset_config():
"""Automatically runs before every test."""
Config.reset()
yield
Config.cleanup()
def test_without_fixture_call():
# reset_config runs automatically
assert Config.get_setting("debug") is False
```
### 使用 Conftest.py 共享夹具
```python
# tests/conftest.py
import pytest
@pytest.fixture
def client():
"""Shared fixture for all tests."""
app = create_app(testing=True)
with app.test_client() as client:
yield client
@pytest.fixture
def auth_headers(client):
"""Generate auth headers for API testing."""
response = client.post("/api/login", json={
"username": "test",
"password": "test"
})
token = response.json["token"]
return {"Authorization": f"Bearer {token}"}
```
## 参数化
### 基本参数化
```python
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
"""Test runs 3 times with different inputs."""
assert input.upper() == expected
```
### 多参数
```python
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
"""Test addition with multiple inputs."""
assert add(a, b) == expected
```
### 带 ID 的参数化
```python
@pytest.mark.parametrize("input,expected", [
("valid@email.com", True),
("invalid", False),
("@no-domain.com", False),
], ids=["valid-email", "missing-at", "missing-domain"])
def test_email_validation(input, expected):
"""Test email validation with readable test IDs."""
assert is_valid_email(input) is expected
```
### 参数化夹具
```python
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
"""Test against multiple database backends."""
if request.param == "sqlite":
return Database(":memory:")
elif request.param == "postgresql":
return Database("postgresql://localhost/test")
elif request.param == "mysql":
return Database("mysql://localhost/test")
def test_database_operations(db):
"""Test runs 3 times, once for each database."""
result = db.query("SELECT 1")
assert result is not None
```
## 标记器和测试选择
### 自定义标记器
```python
# Mark slow tests
@pytest.mark.slow
def test_slow_operation():
time.sleep(5)
# Mark integration tests
@pytest.mark.integration
def test_api_integration():
response = requests.get("https://api.example.com")
assert response.status_code == 200
# Mark unit tests
@pytest.mark.unit
def test_unit_logic():
assert calculate(2, 3) == 5
```
### 运行特定测试
```bash
# Run only fast tests
pytest -m "not slow"
# Run only integration tests
pytest -m integration
# Run integration or slow tests
pytest -m "integration or slow"
# Run tests marked as unit but not slow
pytest -m "unit and not slow"
```
### 在 pytest.ini 中配置标记器
```ini
[pytest]
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit tests
django: marks tests as requiring Django
```
## 模拟和补丁
### 模拟函数
```python
from unittest.mock import patch, Mock
@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
"""Test with mocked external API."""
api_call_mock.return_value = {"status": "success"}
result = my_function()
api_call_mock.assert_called_once()
assert result["status"] == "success"
```
### 模拟返回值
```python
@patch("mypackage.Database.connect")
def test_database_connection(connect_mock):
"""Test with mocked database connection."""
connect_mock.return_value = MockConnection()
db = Database()
db.connect()
connect_mock.assert_called_once_with("localhost")
```
### 模拟异常
```python
@patch("mypackage.api_call")
def test_api_error_handling(api_call_mock):
"""Test error handling with mocked exception."""
api_call_mock.side_effect = ConnectionError("Network error")
with pytest.raises(ConnectionError):
api_call()
api_call_mock.assert_called_once()
```
### 模拟上下文管理器
```python
@patch("builtins.open", new_callable=mock_open)
def test_file_reading(mock_file):
"""Test file reading with mocked open."""
mock_file.return_value.read.return_value = "file content"
result = read_file("test.txt")
mock_file.assert_called_once_with("test.txt", "r")
assert result == "file content"
```
### 使用 Autospec
```python
@patch("mypackage.DBConnection", autospec=True)
def test_autospec(db_mock):
"""Test with autospec to catch API misuse."""
db = db_mock.return_value
db.query("SELECT * FROM users")
# This would fail if DBConnection doesn't have query method
db_mock.assert_called_once()
```
### 模拟类实例
```python
class TestUserService:
@patch("mypackage.UserRepository")
def test_create_user(self, repo_mock):
"""Test user creation with mocked repository."""
repo_mock.return_value.save.return_value = User(id=1, name="Alice")
service = UserService(repo_mock.return_value)
user = service.create_user(name="Alice")
assert user.name == "Alice"
repo_mock.return_value.save.assert_called_once()
```
### 模拟属性
```python
@pytest.fixture
def mock_config():
"""Create a mock with a property."""
config = Mock()
type(config).debug = PropertyMock(return_value=True)
type(config).api_key = PropertyMock(return_value="test-key")
return config
def test_with_mock_config(mock_config):
"""Test with mocked config properties."""
assert mock_config.debug is True
assert mock_config.api_key == "test-key"
```
## 测试异步代码
### 使用 pytest-asyncio 进行异步测试
```python
import pytest
@pytest.mark.asyncio
async def test_async_function():
"""Test async function."""
result = await async_add(2, 3)
assert result == 5
@pytest.mark.asyncio
async def test_async_with_fixture(async_client):
"""Test async with async fixture."""
response = await async_client.get("/api/users")
assert response.status_code == 200
```
### 异步夹具
```python
@pytest.fixture
async def async_client():
"""Async fixture providing async test client."""
app = create_app()
async with app.test_client() as client:
yield client
@pytest.mark.asyncio
async def test_api_endpoint(async_client):
"""Test using async fixture."""
response = await async_client.get("/api/data")
assert response.status_code == 200
```
### 模拟异步函数
```python
@pytest.mark.asyncio
@patch("mypackage.async_api_call")
async def test_async_mock(api_call_mock):
"""Test async function with mock."""
api_call_mock.return_value = {"status": "ok"}
result = await my_async_function()
api_call_mock.assert_awaited_once()
assert result["status"] == "ok"
```
## 测试异常
### 测试预期异常
```python
def test_divide_by_zero():
"""Test that dividing by zero raises ZeroDivisionError."""
with pytest.raises(ZeroDivisionError):
divide(10, 0)
def test_custom_exception():
"""Test custom exception with message."""
with pytest.raises(ValueError, match="invalid input"):
validate_input("invalid")
```
### 测试异常属性
```python
def test_exception_with_details():
"""Test exception with custom attributes."""
with pytest.raises(CustomError) as exc_info:
raise CustomError("error", code=400)
assert exc_info.value.code == 400
assert "error" in str(exc_info.value)
```
## 测试副作用
### 测试文件操作
```python
import tempfile
import os
def test_file_processing():
"""Test file processing with temp file."""
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("test content")
temp_path = f.name
try:
result = process_file(temp_path)
assert result == "processed: test content"
finally:
os.unlink(temp_path)
```
### 使用 pytest 的 tmp\_path 夹具进行测试
```python
def test_with_tmp_path(tmp_path):
"""Test using pytest's built-in temp path fixture."""
test_file = tmp_path / "test.txt"
test_file.write_text("hello world")
result = process_file(str(test_file))
assert result == "hello world"
# tmp_path automatically cleaned up
```
### 使用 tmpdir 夹具进行测试
```python
def test_with_tmpdir(tmpdir):
"""Test using pytest's tmpdir fixture."""
test_file = tmpdir.join("test.txt")
test_file.write("data")
result = process_file(str(test_file))
assert result == "data"
```
## 测试组织
### 目录结构
```
tests/
├── conftest.py # Shared fixtures
├── __init__.py
├── unit/ # Unit tests
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_utils.py
│ └── test_services.py
├── integration/ # Integration tests
│ ├── __init__.py
│ ├── test_api.py
│ └── test_database.py
└── e2e/ # End-to-end tests
├── __init__.py
└── test_user_flow.py
```
### 测试类
```python
class TestUserService:
"""Group related tests in a class."""
@pytest.fixture(autouse=True)
def setup(self):
"""Setup runs before each test in this class."""
self.service = UserService()
def test_create_user(self):
"""Test user creation."""
user = self.service.create_user("Alice")
assert user.name == "Alice"
def test_delete_user(self):
"""Test user deletion."""
user = User(id=1, name="Bob")
self.service.delete_user(user)
assert not self.service.user_exists(1)
```
## 最佳实践
### 应该做
* **遵循 TDD**:在代码之前编写测试(红-绿-重构)
* **测试单一事物**:每个测试应验证一个单一行为
* **使用描述性名称**`test_user_login_with_invalid_credentials_fails`
* **使用夹具**:用夹具消除重复
* **模拟外部依赖**:不要依赖外部服务
* **测试边界情况**空输入、None 值、边界条件
* **目标 80%+ 覆盖率**:关注关键路径
* **保持测试快速**:使用标记来分离慢速测试
### 不要做
* **不要测试实现**:测试行为,而非内部实现
* **不要在测试中使用复杂的条件语句**:保持测试简单
* **不要忽略测试失败**:所有测试必须通过
* **不要测试第三方代码**:相信库能正常工作
* **不要在测试之间共享状态**:测试应该是独立的
* **不要在测试中捕获异常**:使用 `pytest.raises`
* **不要使用 print 语句**:使用断言和 pytest 输出
* **不要编写过于脆弱的测试**:避免过度具体的模拟
## 常见模式
### 测试 API 端点 (FastAPI/Flask)
```python
@pytest.fixture
def client():
app = create_app(testing=True)
return app.test_client()
def test_get_user(client):
response = client.get("/api/users/1")
assert response.status_code == 200
assert response.json["id"] == 1
def test_create_user(client):
response = client.post("/api/users", json={
"name": "Alice",
"email": "alice@example.com"
})
assert response.status_code == 201
assert response.json["name"] == "Alice"
```
### 测试数据库操作
```python
@pytest.fixture
def db_session():
"""Create a test database session."""
session = Session(bind=engine)
session.begin_nested()
yield session
session.rollback()
session.close()
def test_create_user(db_session):
user = User(name="Alice", email="alice@example.com")
db_session.add(user)
db_session.commit()
retrieved = db_session.query(User).filter_by(name="Alice").first()
assert retrieved.email == "alice@example.com"
```
### 测试类方法
```python
class TestCalculator:
@pytest.fixture
def calculator(self):
return Calculator()
def test_add(self, calculator):
assert calculator.add(2, 3) == 5
def test_divide_by_zero(self, calculator):
with pytest.raises(ZeroDivisionError):
calculator.divide(10, 0)
```
## pytest 配置
### pytest.ini
```ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--strict-markers
--disable-warnings
--cov=mypackage
--cov-report=term-missing
--cov-report=html
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit tests
```
### pyproject.toml
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--cov=mypackage",
"--cov-report=term-missing",
"--cov-report=html",
]
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
]
```
## 运行测试
```bash
# Run all tests
pytest
# Run specific file
pytest tests/test_utils.py
# Run specific test
pytest tests/test_utils.py::test_function
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=mypackage --cov-report=html
# Run only fast tests
pytest -m "not slow"
# Run until first failure
pytest -x
# Run and stop on N failures
pytest --maxfail=3
# Run last failed tests
pytest --lf
# Run tests with pattern
pytest -k "test_user"
# Run with debugger on failure
pytest --pdb
```
## 快速参考
| 模式 | 用法 |
|---------|-------|
| `pytest.raises()` | 测试预期异常 |
| `@pytest.fixture()` | 创建可重用的测试夹具 |
| `@pytest.mark.parametrize()` | 使用多个输入运行测试 |
| `@pytest.mark.slow` | 标记慢速测试 |
| `pytest -m "not slow"` | 跳过慢速测试 |
| `@patch()` | 模拟函数和类 |
| `tmp_path` 夹具 | 自动临时目录 |
| `pytest --cov` | 生成覆盖率报告 |
| `assert` | 简单且可读的断言 |
**记住**:测试也是代码。保持它们干净、可读且可维护。好的测试能发现错误;优秀的测试能预防错误。

View File

@@ -0,0 +1,526 @@
---
name: security-review
description: Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns.
---
# 安全审查技能
此技能确保所有代码遵循安全最佳实践,并识别潜在漏洞。
## 何时激活
* 实现身份验证或授权时
* 处理用户输入或文件上传时
* 创建新的 API 端点时
* 处理密钥或凭据时
* 实现支付功能时
* 存储或传输敏感数据时
* 集成第三方 API 时
## 安全检查清单
### 1. 密钥管理
#### ❌ 绝对不要这样做
```typescript
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
const dbPassword = "password123" // In source code
```
#### ✅ 始终这样做
```typescript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// Verify secrets exist
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
```
#### 验证步骤
* \[ ] 没有硬编码的 API 密钥、令牌或密码
* \[ ] 所有密钥都存储在环境变量中
* \[ ] `.env` 文件在 .gitignore 中
* \[ ] git 历史记录中没有密钥
* \[ ] 生产环境密钥存储在托管平台中Vercel, Railway
### 2. 输入验证
#### 始终验证用户输入
```typescript
import { z } from 'zod'
// Define validation schema
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150)
})
// Validate before processing
export async function createUser(input: unknown) {
try {
const validated = CreateUserSchema.parse(input)
return await db.users.create(validated)
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
throw error
}
}
```
#### 文件上传验证
```typescript
function validateFileUpload(file: File) {
// Size check (5MB max)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
throw new Error('File too large (max 5MB)')
}
// Type check
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type')
}
// Extension check
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
if (!extension || !allowedExtensions.includes(extension)) {
throw new Error('Invalid file extension')
}
return true
}
```
#### 验证步骤
* \[ ] 所有用户输入都使用模式进行了验证
* \[ ] 文件上传受到限制(大小、类型、扩展名)
* \[ ] 查询中没有直接使用用户输入
* \[ ] 使用白名单验证(而非黑名单)
* \[ ] 错误消息不会泄露敏感信息
### 3. SQL 注入防护
#### ❌ 绝对不要拼接 SQL
```typescript
// DANGEROUS - SQL Injection vulnerability
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
```
#### ✅ 始终使用参数化查询
```typescript
// Safe - parameterized query
const { data } = await supabase
.from('users')
.select('*')
.eq('email', userEmail)
// Or with raw SQL
await db.query(
'SELECT * FROM users WHERE email = $1',
[userEmail]
)
```
#### 验证步骤
* \[ ] 所有数据库查询都使用参数化查询
* \[ ] SQL 中没有字符串拼接
* \[ ] 正确使用 ORM/查询构建器
* \[ ] Supabase 查询已正确清理
### 4. 身份验证与授权
#### JWT 令牌处理
```typescript
// ❌ WRONG: localStorage (vulnerable to XSS)
localStorage.setItem('token', token)
// ✅ CORRECT: httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
```
#### 授权检查
```typescript
export async function deleteUser(userId: string, requesterId: string) {
// ALWAYS verify authorization first
const requester = await db.users.findUnique({
where: { id: requesterId }
})
if (requester.role !== 'admin') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 403 }
)
}
// Proceed with deletion
await db.users.delete({ where: { id: userId } })
}
```
#### 行级安全Supabase
```sql
-- Enable RLS on all tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Users can only view their own data
CREATE POLICY "Users view own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Users can only update their own data
CREATE POLICY "Users update own data"
ON users FOR UPDATE
USING (auth.uid() = id);
```
#### 验证步骤
* \[ ] 令牌存储在 httpOnly cookie 中(而非 localStorage
* \[ ] 执行敏感操作前进行授权检查
* \[ ] Supabase 中启用了行级安全
* \[ ] 实现了基于角色的访问控制
* \[ ] 会话管理安全
### 5. XSS 防护
#### 清理 HTML
```typescript
import DOMPurify from 'isomorphic-dompurify'
// ALWAYS sanitize user-provided HTML
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
```
#### 内容安全策略
```typescript
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
`.replace(/\s{2,}/g, ' ').trim()
}
]
```
#### 验证步骤
* \[ ] 用户提供的 HTML 已被清理
* \[ ] 已配置 CSP 头部
* \[ ] 没有渲染未经验证的动态内容
* \[ ] 使用了 React 内置的 XSS 防护
### 6. CSRF 防护
#### CSRF 令牌
```typescript
import { csrf } from '@/lib/csrf'
export async function POST(request: Request) {
const token = request.headers.get('X-CSRF-Token')
if (!csrf.verify(token)) {
return NextResponse.json(
{ error: 'Invalid CSRF token' },
{ status: 403 }
)
}
// Process request
}
```
#### SameSite Cookie
```typescript
res.setHeader('Set-Cookie',
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
```
#### 验证步骤
* \[ ] 状态变更操作上使用了 CSRF 令牌
* \[ ] 所有 Cookie 都设置了 SameSite=Strict
* \[ ] 实现了双重提交 Cookie 模式
### 7. 速率限制
#### API 速率限制
```typescript
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests'
})
// Apply to routes
app.use('/api/', limiter)
```
#### 昂贵操作
```typescript
// Aggressive rate limiting for searches
const searchLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 requests per minute
message: 'Too many search requests'
})
app.use('/api/search', searchLimiter)
```
#### 验证步骤
* \[ ] 所有 API 端点都实施了速率限制
* \[ ] 对昂贵操作有更严格的限制
* \[ ] 基于 IP 的速率限制
* \[ ] 基于用户的速率限制(已认证)
### 8. 敏感数据泄露
#### 日志记录
```typescript
// ❌ WRONG: Logging sensitive data
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// ✅ CORRECT: Redact sensitive data
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
```
#### 错误消息
```typescript
// ❌ WRONG: Exposing internal details
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
{ status: 500 }
)
}
// ✅ CORRECT: Generic error messages
catch (error) {
console.error('Internal error:', error)
return NextResponse.json(
{ error: 'An error occurred. Please try again.' },
{ status: 500 }
)
}
```
#### 验证步骤
* \[ ] 日志中没有密码、令牌或密钥
* \[ ] 对用户显示通用错误消息
* \[ ] 详细错误信息仅在服务器日志中
* \[ ] 没有向用户暴露堆栈跟踪
### 9. 区块链安全Solana
#### 钱包验证
```typescript
import { verify } from '@solana/web3.js'
async function verifyWalletOwnership(
publicKey: string,
signature: string,
message: string
) {
try {
const isValid = verify(
Buffer.from(message),
Buffer.from(signature, 'base64'),
Buffer.from(publicKey, 'base64')
)
return isValid
} catch (error) {
return false
}
}
```
#### 交易验证
```typescript
async function verifyTransaction(transaction: Transaction) {
// Verify recipient
if (transaction.to !== expectedRecipient) {
throw new Error('Invalid recipient')
}
// Verify amount
if (transaction.amount > maxAmount) {
throw new Error('Amount exceeds limit')
}
// Verify user has sufficient balance
const balance = await getBalance(transaction.from)
if (balance < transaction.amount) {
throw new Error('Insufficient balance')
}
return true
}
```
#### 验证步骤
* \[ ] 已验证钱包签名
* \[ ] 已验证交易详情
* \[ ] 交易前检查余额
* \[ ] 没有盲签名交易
### 10. 依赖项安全
#### 定期更新
```bash
# Check for vulnerabilities
npm audit
# Fix automatically fixable issues
npm audit fix
# Update dependencies
npm update
# Check for outdated packages
npm outdated
```
#### 锁定文件
```bash
# ALWAYS commit lock files
git add package-lock.json
# Use in CI/CD for reproducible builds
npm ci # Instead of npm install
```
#### 验证步骤
* \[ ] 依赖项是最新的
* \[ ] 没有已知漏洞npm audit 检查通过)
* \[ ] 提交了锁定文件
* \[ ] GitHub 上启用了 Dependabot
* \[ ] 定期进行安全更新
## 安全测试
### 自动化安全测试
```typescript
// Test authentication
test('requires authentication', async () => {
const response = await fetch('/api/protected')
expect(response.status).toBe(401)
})
// Test authorization
test('requires admin role', async () => {
const response = await fetch('/api/admin', {
headers: { Authorization: `Bearer ${userToken}` }
})
expect(response.status).toBe(403)
})
// Test input validation
test('rejects invalid input', async () => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ email: 'not-an-email' })
})
expect(response.status).toBe(400)
})
// Test rate limiting
test('enforces rate limits', async () => {
const requests = Array(101).fill(null).map(() =>
fetch('/api/endpoint')
)
const responses = await Promise.all(requests)
const tooManyRequests = responses.filter(r => r.status === 429)
expect(tooManyRequests.length).toBeGreaterThan(0)
})
```
## 部署前安全检查清单
在任何生产环境部署前:
* \[ ] **密钥**:没有硬编码的密钥,全部在环境变量中
* \[ ] **输入验证**:所有用户输入都已验证
* \[ ] **SQL 注入**:所有查询都已参数化
* \[ ] **XSS**:用户内容已被清理
* \[ ] **CSRF**:已启用防护
* \[ ] **身份验证**:正确处理令牌
* \[ ] **授权**:已实施角色检查
* \[ ] **速率限制**:所有端点都已启用
* \[ ] **HTTPS**:在生产环境中强制执行
* \[ ] **安全头部**:已配置 CSP、X-Frame-Options
* \[ ] **错误处理**:错误中不包含敏感数据
* \[ ] **日志记录**:日志中不包含敏感数据
* \[ ] **依赖项**:已更新,无漏洞
* \[ ] **行级安全**Supabase 中已启用
* \[ ] **CORS**:已正确配置
* \[ ] **文件上传**:已验证(大小、类型)
* \[ ] **钱包签名**:已验证(如果涉及区块链)
## 资源
* [OWASP Top 10](https://owasp.org/www-project-top-ten/)
* [Next.js 安全](https://nextjs.org/docs/security)
* [Supabase 安全](https://supabase.com/docs/guides/auth)
* [Web 安全学院](https://portswigger.net/web-security)
***
**请记住**:安全不是可选项。一个漏洞就可能危及整个平台。如有疑问,请谨慎行事。

View File

@@ -0,0 +1,361 @@
| name | description |
|------|-------------|
| cloud-infrastructure-security | 在部署到云平台、配置基础设施、管理IAM策略、设置日志记录/监控或实现CI/CD流水线时使用此技能。提供符合最佳实践的云安全检查清单。 |
# 云与基础设施安全技能
此技能确保云基础设施、CI/CD流水线和部署配置遵循安全最佳实践并符合行业标准。
## 何时激活
* 将应用程序部署到云平台AWS、Vercel、Railway、Cloudflare
* 配置IAM角色和权限
* 设置CI/CD流水线
* 实施基础设施即代码Terraform、CloudFormation
* 配置日志记录和监控
* 在云环境中管理密钥
* 设置CDN和边缘安全
* 实施灾难恢复和备份策略
## 云安全检查清单
### 1. IAM 与访问控制
#### 最小权限原则
```yaml
# ✅ CORRECT: Minimal permissions
iam_role:
permissions:
- s3:GetObject # Only read access
- s3:ListBucket
resources:
- arn:aws:s3:::my-bucket/* # Specific bucket only
# ❌ WRONG: Overly broad permissions
iam_role:
permissions:
- s3:* # All S3 actions
resources:
- "*" # All resources
```
#### 多因素认证 (MFA)
```bash
# ALWAYS enable MFA for root/admin accounts
aws iam enable-mfa-device \
--user-name admin \
--serial-number arn:aws:iam::123456789:mfa/admin \
--authentication-code1 123456 \
--authentication-code2 789012
```
#### 验证步骤
* \[ ] 生产环境中未使用根账户
* \[ ] 所有特权账户已启用MFA
* \[ ] 服务账户使用角色,而非长期凭证
* \[ ] IAM策略遵循最小权限原则
* \[ ] 定期进行访问审查
* \[ ] 未使用的凭证已轮换或移除
### 2. 密钥管理
#### 云密钥管理器
```typescript
// ✅ CORRECT: Use cloud secrets manager
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
const apiKey = JSON.parse(secret.SecretString).key;
// ❌ WRONG: Hardcoded or in environment variables only
const apiKey = process.env.API_KEY; // Not rotated, not audited
```
#### 密钥轮换
```bash
# Set up automatic rotation for database credentials
aws secretsmanager rotate-secret \
--secret-id prod/db-password \
--rotation-lambda-arn arn:aws:lambda:region:account:function:rotate \
--rotation-rules AutomaticallyAfterDays=30
```
#### 验证步骤
* \[ ] 所有密钥存储在云密钥管理器AWS Secrets Manager、Vercel Secrets
* \[ ] 数据库凭证已启用自动轮换
* \[ ] API密钥至少每季度轮换一次
* \[ ] 代码、日志或错误消息中没有密钥
* \[ ] 密钥访问已启用审计日志记录
### 3. 网络安全
#### VPC 和防火墙配置
```terraform
# ✅ CORRECT: Restricted security group
resource "aws_security_group" "app" {
name = "app-sg"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # Internal VPC only
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Only HTTPS outbound
}
}
# ❌ WRONG: Open to the internet
resource "aws_security_group" "bad" {
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # All ports, all IPs!
}
}
```
#### 验证步骤
* \[ ] 数据库未公开访问
* \[ ] SSH/RDP端口仅限VPN/堡垒机访问
* \[ ] 安全组遵循最小权限原则
* \[ ] 网络ACL已配置
* \[ ] VPC流日志已启用
### 4. 日志记录与监控
#### CloudWatch/日志记录配置
```typescript
// ✅ CORRECT: Comprehensive logging
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
const logSecurityEvent = async (event: SecurityEvent) => {
await cloudwatch.putLogEvents({
logGroupName: '/aws/security/events',
logStreamName: 'authentication',
logEvents: [{
timestamp: Date.now(),
message: JSON.stringify({
type: event.type,
userId: event.userId,
ip: event.ip,
result: event.result,
// Never log sensitive data
})
}]
});
};
```
#### 验证步骤
* \[ ] 所有服务已启用CloudWatch/日志记录
* \[ ] 失败的身份验证尝试已记录
* \[ ] 管理员操作已审计
* \[ ] 日志保留期已配置合规要求90天以上
* \[ ] 为可疑活动配置了警报
* \[ ] 日志已集中存储且防篡改
### 5. CI/CD 流水线安全
#### 安全流水线配置
```yaml
# ✅ CORRECT: Secure GitHub Actions workflow
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read # Minimal permissions
steps:
- uses: actions/checkout@v4
# Scan for secrets
- name: Secret scanning
uses: trufflesecurity/trufflehog@main
# Dependency audit
- name: Audit dependencies
run: npm audit --audit-level=high
# Use OIDC, not long-lived tokens
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: us-east-1
```
#### 供应链安全
```json
// package.json - Use lock files and integrity checks
{
"scripts": {
"install": "npm ci", // Use ci for reproducible builds
"audit": "npm audit --audit-level=moderate",
"check": "npm outdated"
}
}
```
#### 验证步骤
* \[ ] 使用OIDC而非长期凭证
* \[ ] 流水线中进行密钥扫描
* \[ ] 依赖项漏洞扫描
* \[ ] 容器镜像扫描(如适用)
* \[ ] 分支保护规则已强制执行
* \[ ] 合并前需要代码审查
* \[ ] 已强制执行签名提交
### 6. Cloudflare 与 CDN 安全
#### Cloudflare 安全配置
```typescript
// ✅ CORRECT: Cloudflare Workers with security headers
export default {
async fetch(request: Request): Promise<Response> {
const response = await fetch(request);
// Add security headers
const headers = new Headers(response.headers);
headers.set('X-Frame-Options', 'DENY');
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
headers.set('Permissions-Policy', 'geolocation=(), microphone=()');
return new Response(response.body, {
status: response.status,
headers
});
}
};
```
#### WAF 规则
```bash
# Enable Cloudflare WAF managed rules
# - OWASP Core Ruleset
# - Cloudflare Managed Ruleset
# - Rate limiting rules
# - Bot protection
```
#### 验证步骤
* \[ ] WAF已启用并配置OWASP规则
* \[ ] 已配置速率限制
* \[ ] 机器人防护已激活
* \[ ] DDoS防护已启用
* \[ ] 安全标头已配置
* \[ ] SSL/TLS严格模式已启用
### 7. 备份与灾难恢复
#### 自动化备份
```terraform
# ✅ CORRECT: Automated RDS backups
resource "aws_db_instance" "main" {
allocated_storage = 20
engine = "postgres"
backup_retention_period = 30 # 30 days retention
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
enabled_cloudwatch_logs_exports = ["postgresql"]
deletion_protection = true # Prevent accidental deletion
}
```
#### 验证步骤
* \[ ] 已配置自动化每日备份
* \[ ] 备份保留期符合合规要求
* \[ ] 已启用时间点恢复
* \[ ] 每季度执行备份测试
* \[ ] 灾难恢复计划已记录
* \[ ] RPO和RTO已定义并经过测试
## 部署前云安全检查清单
在任何生产云部署之前:
* \[ ] **IAM**未使用根账户已启用MFA最小权限策略
* \[ ] **密钥**:所有密钥都在云密钥管理器中并已配置轮换
* \[ ] **网络**:安全组受限,无公开数据库
* \[ ] **日志记录**已启用CloudWatch/日志记录并配置保留期
* \[ ] **监控**:为异常情况配置了警报
* \[ ] **CI/CD**OIDC身份验证密钥扫描依赖项审计
* \[ ] **CDN/WAF**Cloudflare WAF已启用并配置OWASP规则
* \[ ] **加密**:静态和传输中的数据均已加密
* \[ ] **备份**:自动化备份并已测试恢复
* \[ ] **合规性**满足GDPR/HIPAA要求如适用
* \[ ] **文档**:基础设施已记录,已创建操作手册
* \[ ] **事件响应**:已制定安全事件计划
## 常见云安全配置错误
### S3 存储桶暴露
```bash
# ❌ WRONG: Public bucket
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
# ✅ CORRECT: Private bucket with specific access
aws s3api put-bucket-acl --bucket my-bucket --acl private
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
```
### RDS 公开访问
```terraform
# ❌ WRONG
resource "aws_db_instance" "bad" {
publicly_accessible = true # NEVER do this!
}
# ✅ CORRECT
resource "aws_db_instance" "good" {
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.db.id]
}
```
## 资源
* [AWS 安全最佳实践](https://aws.amazon.com/security/best-practices/)
* [CIS AWS 基础基准](https://www.cisecurity.org/benchmark/amazon_web_services)
* [Cloudflare 安全文档](https://developers.cloudflare.com/security/)
* [OWASP 云安全](https://owasp.org/www-project-cloud-security/)
* [Terraform 安全最佳实践](https://www.terraform.io/docs/cloud/guides/recommended-practices/)
**请记住**云配置错误是数据泄露的主要原因。一个暴露的S3存储桶或一个权限过大的IAM策略就可能危及整个基础设施。始终遵循最小权限原则和深度防御策略。

View File

@@ -0,0 +1,171 @@
---
name: security-scan
description: 使用AgentShield扫描您的Claude Code配置.claude/目录检测安全漏洞、错误配置和注入风险。检查CLAUDE.md、settings.json、MCP服务器、钩子和代理定义。
---
# 安全扫描技能
使用 [AgentShield](https://github.com/affaan-m/agentshield) 审计您的 Claude Code 配置中的安全问题。
## 何时激活
* 设置新的 Claude Code 项目时
* 修改 `.claude/settings.json``CLAUDE.md` 或 MCP 配置后
* 提交配置更改前
* 加入具有现有 Claude Code 配置的新代码库时
* 定期进行安全卫生检查时
## 扫描内容
| 文件 | 检查项 |
|------|--------|
| `CLAUDE.md` | 硬编码的密钥、自动运行指令、提示词注入模式 |
| `settings.json` | 过于宽松的允许列表、缺失的拒绝列表、危险的绕过标志 |
| `mcp.json` | 有风险的 MCP 服务器、硬编码的环境变量密钥、npx 供应链风险 |
| `hooks/` | 通过 `${file}` 插值导致的命令注入、数据泄露、静默错误抑制 |
| `agents/*.md` | 无限制的工具访问、提示词注入攻击面、缺失的模型规格 |
## 先决条件
必须安装 AgentShield。检查并在需要时安装
```bash
# Check if installed
npx ecc-agentshield --version
# Install globally (recommended)
npm install -g ecc-agentshield
# Or run directly via npx (no install needed)
npx ecc-agentshield scan .
```
## 使用方法
### 基础扫描
针对当前项目的 `.claude/` 目录运行:
```bash
# Scan current project
npx ecc-agentshield scan
# Scan a specific path
npx ecc-agentshield scan --path /path/to/.claude
# Scan with minimum severity filter
npx ecc-agentshield scan --min-severity medium
```
### 输出格式
```bash
# Terminal output (default) — colored report with grade
npx ecc-agentshield scan
# JSON — for CI/CD integration
npx ecc-agentshield scan --format json
# Markdown — for documentation
npx ecc-agentshield scan --format markdown
# HTML — self-contained dark-theme report
npx ecc-agentshield scan --format html > security-report.html
```
### 自动修复
自动应用安全的修复(仅修复标记为可自动修复的问题):
```bash
npx ecc-agentshield scan --fix
```
这将:
* 用环境变量引用替换硬编码的密钥
* 将通配符权限收紧为作用域明确的替代方案
* 绝不修改仅限手动修复的建议
### Opus 4.6 深度分析
运行对抗性的三智能体流程以进行更深入的分析:
```bash
# Requires ANTHROPIC_API_KEY
export ANTHROPIC_API_KEY=your-key
npx ecc-agentshield scan --opus --stream
```
这将运行:
1. **攻击者(红队)** — 寻找攻击向量
2. **防御者(蓝队)** — 建议加固措施
3. **审计员(最终裁决)** — 综合双方观点
### 初始化安全配置
从头开始搭建一个新的安全 `.claude/` 配置:
```bash
npx ecc-agentshield init
```
创建:
* 具有作用域权限和拒绝列表的 `settings.json`
* 遵循安全最佳实践的 `CLAUDE.md`
* `mcp.json` 占位符
### GitHub Action
添加到您的 CI 流水线中:
```yaml
- uses: affaan-m/agentshield@v1
with:
path: '.'
min-severity: 'medium'
fail-on-findings: true
```
## 严重性等级
| 等级 | 分数 | 含义 |
|-------|-------|---------|
| A | 90-100 | 安全配置 |
| B | 75-89 | 轻微问题 |
| C | 60-74 | 需要注意 |
| D | 40-59 | 显著风险 |
| F | 0-39 | 严重漏洞 |
## 结果解读
### 关键发现(立即修复)
* 配置文件中硬编码的 API 密钥或令牌
* 允许列表中存在 `Bash(*)`(无限制的 shell 访问)
* 钩子中通过 `${file}` 插值导致的命令注入
* 运行 shell 的 MCP 服务器
### 高优先级发现(生产前修复)
* CLAUDE.md 中的自动运行指令(提示词注入向量)
* 权限配置中缺少拒绝列表
* 具有不必要 Bash 访问权限的代理
### 中优先级发现(建议修复)
* 钩子中的静默错误抑制(`2>/dev/null``|| true`
* 缺少 PreToolUse 安全钩子
* MCP 服务器配置中的 `npx -y` 自动安装
### 信息性发现(了解情况)
* MCP 服务器缺少描述信息
* 正确标记为良好实践的限制性指令
## 链接
* **GitHub**: [github.com/affaan-m/agentshield](https://github.com/affaan-m/agentshield)
* **npm**: [npmjs.com/package/ecc-agentshield](https://www.npmjs.com/package/ecc-agentshield)

View File

@@ -0,0 +1,303 @@
---
name: springboot-patterns
description: Spring Boot 架构模式、REST API 设计、分层服务、数据访问、缓存、异步处理和日志记录。适用于 Java Spring Boot 后端工作。
---
# Spring Boot 开发模式
用于可扩展、生产级服务的 Spring Boot 架构和 API 模式。
## REST API 结构
```java
@RestController
@RequestMapping("/api/markets")
@Validated
class MarketController {
private final MarketService marketService;
MarketController(MarketService marketService) {
this.marketService = marketService;
}
@GetMapping
ResponseEntity<Page<MarketResponse>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Page<Market> markets = marketService.list(PageRequest.of(page, size));
return ResponseEntity.ok(markets.map(MarketResponse::from));
}
@PostMapping
ResponseEntity<MarketResponse> create(@Valid @RequestBody CreateMarketRequest request) {
Market market = marketService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(MarketResponse.from(market));
}
}
```
## 仓库模式 (Spring Data JPA)
```java
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
@Query("select m from MarketEntity m where m.status = :status order by m.volume desc")
List<MarketEntity> findActive(@Param("status") MarketStatus status, Pageable pageable);
}
```
## 带事务的服务层
```java
@Service
public class MarketService {
private final MarketRepository repo;
public MarketService(MarketRepository repo) {
this.repo = repo;
}
@Transactional
public Market create(CreateMarketRequest request) {
MarketEntity entity = MarketEntity.from(request);
MarketEntity saved = repo.save(entity);
return Market.from(saved);
}
}
```
## DTO 和验证
```java
public record CreateMarketRequest(
@NotBlank @Size(max = 200) String name,
@NotBlank @Size(max = 2000) String description,
@NotNull @FutureOrPresent Instant endDate,
@NotEmpty List<@NotBlank String> categories) {}
public record MarketResponse(Long id, String name, MarketStatus status) {
static MarketResponse from(Market market) {
return new MarketResponse(market.id(), market.name(), market.status());
}
}
```
## 异常处理
```java
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body(ApiError.validation(message));
}
@ExceptionHandler(AccessDeniedException.class)
ResponseEntity<ApiError> handleAccessDenied() {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiError.of("Forbidden"));
}
@ExceptionHandler(Exception.class)
ResponseEntity<ApiError> handleGeneric(Exception ex) {
// Log unexpected errors with stack traces
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiError.of("Internal server error"));
}
}
```
## 缓存
需要在配置类上使用 `@EnableCaching`
```java
@Service
public class MarketCacheService {
private final MarketRepository repo;
public MarketCacheService(MarketRepository repo) {
this.repo = repo;
}
@Cacheable(value = "market", key = "#id")
public Market getById(Long id) {
return repo.findById(id)
.map(Market::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
}
@CacheEvict(value = "market", key = "#id")
public void evict(Long id) {}
}
```
## 异步处理
需要在配置类上使用 `@EnableAsync`
```java
@Service
public class NotificationService {
@Async
public CompletableFuture<Void> sendAsync(Notification notification) {
// send email/SMS
return CompletableFuture.completedFuture(null);
}
}
```
## 日志记录 (SLF4J)
```java
@Service
public class ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportService.class);
public Report generate(Long marketId) {
log.info("generate_report marketId={}", marketId);
try {
// logic
} catch (Exception ex) {
log.error("generate_report_failed marketId={}", marketId, ex);
throw ex;
}
return new Report();
}
}
```
## 中间件 / 过滤器
```java
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
long start = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - start;
log.info("req method={} uri={} status={} durationMs={}",
request.getMethod(), request.getRequestURI(), response.getStatus(), duration);
}
}
}
```
## 分页和排序
```java
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<Market> results = marketService.list(page);
```
## 容错的外部调用
```java
public <T> T withRetry(Supplier<T> supplier, int maxRetries) {
int attempts = 0;
while (true) {
try {
return supplier.get();
} catch (Exception ex) {
attempts++;
if (attempts >= maxRetries) {
throw ex;
}
try {
Thread.sleep((long) Math.pow(2, attempts) * 100L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw ex;
}
}
}
}
```
## 速率限制 (过滤器 + Bucket4j)
**安全须知**:默认情况下 `X-Forwarded-For` 头是不可信的,因为客户端可以伪造它。
仅在以下情况下使用转发头:
1. 您的应用程序位于可信的反向代理nginx、AWS ALB 等)之后
2. 您已将 `ForwardedHeaderFilter` 注册为 bean
3. 您已在应用属性中配置了 `server.forward-headers-strategy=NATIVE``FRAMEWORK`
4. 您的代理配置为覆盖(而非追加)`X-Forwarded-For`
`ForwardedHeaderFilter` 被正确配置时,`request.getRemoteAddr()` 将自动从转发的头中返回正确的客户端 IP。
没有此配置时,请直接使用 `request.getRemoteAddr()`——它返回的是直接连接的 IP这是唯一可信的值。
```java
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
/*
* SECURITY: This filter uses request.getRemoteAddr() to identify clients for rate limiting.
*
* If your application is behind a reverse proxy (nginx, AWS ALB, etc.), you MUST configure
* Spring to handle forwarded headers properly for accurate client IP detection:
*
* 1. Set server.forward-headers-strategy=NATIVE (for cloud platforms) or FRAMEWORK in
* application.properties/yaml
* 2. If using FRAMEWORK strategy, register ForwardedHeaderFilter:
*
* @Bean
* ForwardedHeaderFilter forwardedHeaderFilter() {
* return new ForwardedHeaderFilter();
* }
*
* 3. Ensure your proxy overwrites (not appends) the X-Forwarded-For header to prevent spoofing
* 4. Configure server.tomcat.remoteip.trusted-proxies or equivalent for your container
*
* Without this configuration, request.getRemoteAddr() returns the proxy IP, not the client IP.
* Do NOT read X-Forwarded-For directly—it is trivially spoofable without trusted proxy handling.
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Use getRemoteAddr() which returns the correct client IP when ForwardedHeaderFilter
// is configured, or the direct connection IP otherwise. Never trust X-Forwarded-For
// headers directly without proper proxy configuration.
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp,
k -> Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))
.build());
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
}
}
}
```
## 后台作业
使用 Spring 的 `@Scheduled` 或与队列(如 Kafka、SQS、RabbitMQ集成。保持处理程序是幂等的和可观察的。
## 可观测性
* 通过 Logback 编码器进行结构化日志记录 (JSON)
* 指标Micrometer + Prometheus/OTel
* 追踪:带有 OpenTelemetry 或 Brave 后端的 Micrometer Tracing
## 生产环境默认设置
* 优先使用构造函数注入,避免字段注入
* 启用 `spring.mvc.problemdetails.enabled=true` 以获得 RFC 7807 错误 (Spring Boot 3+)
* 根据工作负载配置 HikariCP 连接池大小,设置超时
* 对查询使用 `@Transactional(readOnly = true)`
* 在适当的地方通过 `@NonNull``Optional` 强制执行空值安全
**记住**:保持控制器精简、服务专注、仓库简单,并集中处理错误。为可维护性和可测试性进行优化。

View File

@@ -0,0 +1,119 @@
---
name: springboot-security
description: Java Spring Boot 服务中关于身份验证/授权、验证、CSRF、密钥、标头、速率限制和依赖安全的 Spring Security 最佳实践。
---
# Spring Boot 安全审查
在添加身份验证、处理输入、创建端点或处理密钥时使用。
## 身份验证
* 优先使用无状态 JWT 或带有撤销列表的不透明令牌
* 对于会话,使用 `httpOnly``Secure``SameSite=Strict` cookie
* 使用 `OncePerRequestFilter` 或资源服务器验证令牌
```java
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
Authentication auth = jwtService.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
```
## 授权
* 启用方法安全:`@EnableMethodSecurity`
* 使用 `@PreAuthorize("hasRole('ADMIN')")``@PreAuthorize("@authz.canEdit(#id)")`
* 默认拒绝;仅公开必需的 scope
## 输入验证
* 在控制器上使用带有 `@Valid` 的 Bean 验证
* 在 DTO 上应用约束:`@NotBlank``@Email``@Size`、自定义验证器
* 在渲染之前使用白名单清理任何 HTML
## SQL 注入预防
* 使用 Spring Data 存储库或参数化查询
* 对于原生查询,使用 `:param` 绑定;切勿拼接字符串
## CSRF 保护
* 对于浏览器会话应用程序,保持 CSRF 启用;在表单/头中包含令牌
* 对于使用 Bearer 令牌的纯 API禁用 CSRF 并依赖无状态身份验证
```java
http
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
```
## 密钥管理
* 源代码中不包含密钥;从环境变量或 vault 加载
* 保持 `application.yml` 不包含凭据;使用占位符
* 定期轮换令牌和数据库凭据
## 安全头
```java
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
.xssProtection(Customizer.withDefaults())
.referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
```
## 速率限制
* 在昂贵的端点上应用 Bucket4j 或网关级限制
* 记录突发流量并告警;返回 429 并提供重试提示
## 依赖项安全
* 在 CI 中运行 OWASP Dependency Check / Snyk
* 保持 Spring Boot 和 Spring Security 在受支持的版本
* 对已知 CVE 使构建失败
## 日志记录和 PII
* 切勿记录密钥、令牌、密码或完整的 PAN 数据
* 擦除敏感字段;使用结构化 JSON 日志记录
## 文件上传
* 验证大小、内容类型和扩展名
* 存储在 Web 根目录之外;如果需要则进行扫描
## 发布前检查清单
* \[ ] 身份验证令牌已验证并正确过期
* \[ ] 每个敏感路径都有授权守卫
* \[ ] 所有输入都已验证和清理
* \[ ] 没有字符串拼接的 SQL
* \[ ] CSRF 策略适用于应用程序类型
* \[ ] 密钥已外部化;未提交任何密钥
* \[ ] 安全头已配置
* \[ ] API 有速率限制
* \[ ] 依赖项已扫描并保持最新
* \[ ] 日志不包含敏感数据
**记住**:默认拒绝、验证输入、最小权限、优先采用安全配置。

View File

@@ -0,0 +1,159 @@
---
name: springboot-tdd
description: 使用JUnit 5、Mockito、MockMvc、Testcontainers和JaCoCo进行Spring Boot的测试驱动开发。适用于添加功能、修复错误或重构时。
---
# Spring Boot TDD 工作流程
适用于 Spring Boot 服务、覆盖率 80%+(单元 + 集成)的 TDD 指南。
## 何时使用
* 新功能或端点
* 错误修复或重构
* 添加数据访问逻辑或安全规则
## 工作流程
1. 先写测试(它们应该失败)
2. 实现最小代码以通过测试
3. 在测试通过后进行重构
4. 强制覆盖率JaCoCo
## 单元测试 (JUnit 5 + Mockito)
```java
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
@Mock MarketRepository repo;
@InjectMocks MarketService service;
@Test
void createsMarket() {
CreateMarketRequest req = new CreateMarketRequest("name", "desc", Instant.now(), List.of("cat"));
when(repo.save(any())).thenAnswer(inv -> inv.getArgument(0));
Market result = service.create(req);
assertThat(result.name()).isEqualTo("name");
verify(repo).save(any());
}
}
```
模式:
* Arrange-Act-Assert
* 避免部分模拟;优先使用显式桩
* 使用 `@ParameterizedTest` 处理变体
## Web 层测试 (MockMvc)
```java
@WebMvcTest(MarketController.class)
class MarketControllerTest {
@Autowired MockMvc mockMvc;
@MockBean MarketService marketService;
@Test
void returnsMarkets() throws Exception {
when(marketService.list(any())).thenReturn(Page.empty());
mockMvc.perform(get("/api/markets"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
}
```
## 集成测试 (SpringBootTest)
```java
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class MarketIntegrationTest {
@Autowired MockMvc mockMvc;
@Test
void createsMarket() throws Exception {
mockMvc.perform(post("/api/markets")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name":"Test","description":"Desc","endDate":"2030-01-01T00:00:00Z","categories":["general"]}
"""))
.andExpect(status().isCreated());
}
}
```
## 持久层测试 (DataJpaTest)
```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(TestContainersConfig.class)
class MarketRepositoryTest {
@Autowired MarketRepository repo;
@Test
void savesAndFinds() {
MarketEntity entity = new MarketEntity();
entity.setName("Test");
repo.save(entity);
Optional<MarketEntity> found = repo.findByName("Test");
assertThat(found).isPresent();
}
}
```
## Testcontainers
* 对 Postgres/Redis 使用可复用的容器以镜像生产环境
* 通过 `@DynamicPropertySource` 连接,将 JDBC URL 注入 Spring 上下文
## 覆盖率 (JaCoCo)
Maven 片段:
```xml
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
```
## 断言
* 为可读性,优先使用 AssertJ (`assertThat`)
* 对于 JSON 响应,使用 `jsonPath`
* 对于异常:`assertThatThrownBy(...)`
## 测试数据构建器
```java
class MarketBuilder {
private String name = "Test";
MarketBuilder withName(String name) { this.name = name; return this; }
Market build() { return new Market(null, name, MarketStatus.ACTIVE); }
}
```
## CI 命令
* Maven: `mvn -T 4 test``mvn verify`
* Gradle: `./gradlew test jacocoTestReport`
**记住**:保持测试快速、隔离且确定。测试行为,而非实现细节。

View File

@@ -0,0 +1,104 @@
---
name: springboot-verification
description: Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR.
---
# Spring Boot 验证循环
在提交 PR 前、重大变更后以及部署前运行。
## 阶段 1构建
```bash
mvn -T 4 clean verify -DskipTests
# or
./gradlew clean assemble -x test
```
如果构建失败,停止并修复。
## 阶段 2静态分析
Maven常用插件
```bash
mvn -T 4 spotbugs:check pmd:check checkstyle:check
```
Gradle如果已配置
```bash
./gradlew checkstyleMain pmdMain spotbugsMain
```
## 阶段 3测试 + 覆盖率
```bash
mvn -T 4 test
mvn jacoco:report # verify 80%+ coverage
# or
./gradlew test jacocoTestReport
```
报告:
* 总测试数,通过/失败
* 覆盖率百分比(行/分支)
## 阶段 4安全扫描
```bash
# Dependency CVEs
mvn org.owasp:dependency-check-maven:check
# or
./gradlew dependencyCheckAnalyze
# Secrets (git)
git secrets --scan # if configured
```
## 阶段 5代码检查/格式化(可选关卡)
```bash
mvn spotless:apply # if using Spotless plugin
./gradlew spotlessApply
```
## 阶段 6差异审查
```bash
git diff --stat
git diff
```
检查清单:
* 没有遗留调试日志(`System.out``log.debug` 没有防护)
* 有意义的错误信息和 HTTP 状态码
* 在需要的地方有事务和验证
* 配置变更已记录
## 输出模板
```
VERIFICATION REPORT
===================
Build: [PASS/FAIL]
Static: [PASS/FAIL] (spotbugs/pmd/checkstyle)
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
Security: [PASS/FAIL] (CVE findings: N)
Diff: [X files changed]
Overall: [READY / NOT READY]
Issues to Fix:
1. ...
2. ...
```
## 持续模式
* 在重大变更时或长时间会话中每 3060 分钟重新运行各阶段
* 保持短循环:`mvn -T 4 test` + spotbugs 以获取快速反馈
**记住**:快速反馈胜过意外惊喜。保持关卡严格——将警告视为生产系统中的缺陷。

View File

@@ -0,0 +1,66 @@
---
name: strategic-compact
description: 建议在逻辑间隔处进行手动上下文压缩,以在任务阶段中保留上下文,而非任意的自动压缩。
---
# 战略精简技能
建议在你的工作流程中的战略节点手动执行 `/compact`,而不是依赖任意的自动精简。
## 为何采用战略精简?
自动精简会在任意时间点触发:
* 通常在任务中途,丢失重要上下文
* 无法感知逻辑任务边界
* 可能中断复杂的多步骤操作
在逻辑边界进行战略精简:
* **探索之后,执行之前** - 精简研究上下文,保留实施计划
* **完成一个里程碑之后** - 为下一阶段全新开始
* **主要上下文切换之前** - 在不同任务开始前清理探索上下文
## 工作原理
`suggest-compact.sh` 脚本在 PreToolUse编辑/写入)时运行并执行:
1. **追踪工具调用** - 计算会话中的工具调用次数
2. **阈值检测** - 在可配置的阈值默认50 次调用)处建议精简
3. **定期提醒** - 在达到阈值后,每 25 次调用提醒一次
## 钩子设置
添加到你的 `~/.claude/settings.json`
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "tool == \"Edit\" || tool == \"Write\"",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
}]
}]
}
}
```
## 配置
环境变量:
* `COMPACT_THRESHOLD` - 首次建议前的工具调用次数默认50
## 最佳实践
1. **规划后精简** - 一旦计划确定,精简以全新开始
2. **调试后精简** - 在继续之前,清理错误解决上下文
3. **不要在实施中途精简** - 保留相关更改的上下文
4. **阅读建议** - 钩子告诉你*何时*,由你决定*是否*
## 相关
* [长篇指南](https://x.com/affaanmustafa/status/2014040193557471352) - 令牌优化部分
* 内存持久化钩子 - 用于在精简后保留的状态

View File

@@ -0,0 +1,439 @@
---
name: tdd-workflow
description: 在编写新功能、修复错误或重构代码时使用此技能。强制执行测试驱动开发包含单元测试、集成测试和端到端测试覆盖率超过80%。
---
# 测试驱动开发工作流
此技能确保所有代码开发遵循TDD原则并具备全面的测试覆盖率。
## 何时激活
* 编写新功能或功能
* 修复错误或问题
* 重构现有代码
* 添加API端点
* 创建新组件
## 核心原则
### 1. 测试优先于代码
始终先编写测试,然后实现代码以使测试通过。
### 2. 覆盖率要求
* 最低80%覆盖率(单元 + 集成 + 端到端)
* 覆盖所有边缘情况
* 测试错误场景
* 验证边界条件
### 3. 测试类型
#### 单元测试
* 单个函数和工具
* 组件逻辑
* 纯函数
* 辅助函数和工具
#### 集成测试
* API端点
* 数据库操作
* 服务交互
* 外部API调用
#### 端到端测试 (Playwright)
* 关键用户流程
* 完整工作流
* 浏览器自动化
* UI交互
## TDD 工作流步骤
### 步骤 1: 编写用户旅程
```
As a [role], I want to [action], so that [benefit]
Example:
As a user, I want to search for markets semantically,
so that I can find relevant markets even without exact keywords.
```
### 步骤 2: 生成测试用例
针对每个用户旅程,创建全面的测试用例:
```typescript
describe('Semantic Search', () => {
it('returns relevant markets for query', async () => {
// Test implementation
})
it('handles empty query gracefully', async () => {
// Test edge case
})
it('falls back to substring search when Redis unavailable', async () => {
// Test fallback behavior
})
it('sorts results by similarity score', async () => {
// Test sorting logic
})
})
```
### 步骤 3: 运行测试(它们应该失败)
```bash
npm test
# Tests should fail - we haven't implemented yet
```
### 步骤 4: 实现代码
编写最少的代码以使测试通过:
```typescript
// Implementation guided by tests
export async function searchMarkets(query: string) {
// Implementation here
}
```
### 步骤 5: 再次运行测试
```bash
npm test
# Tests should now pass
```
### 步骤 6: 重构
在保持测试通过的同时提高代码质量:
* 消除重复
* 改进命名
* 优化性能
* 增强可读性
### 步骤 7: 验证覆盖率
```bash
npm run test:coverage
# Verify 80%+ coverage achieved
```
## 测试模式
### 单元测试模式 (Jest/Vitest)
```typescript
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'
describe('Button Component', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})
```
### API 集成测试模式
```typescript
import { NextRequest } from 'next/server'
import { GET } from './route'
describe('GET /api/markets', () => {
it('returns markets successfully', async () => {
const request = new NextRequest('http://localhost/api/markets')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(Array.isArray(data.data)).toBe(true)
})
it('validates query parameters', async () => {
const request = new NextRequest('http://localhost/api/markets?limit=invalid')
const response = await GET(request)
expect(response.status).toBe(400)
})
it('handles database errors gracefully', async () => {
// Mock database failure
const request = new NextRequest('http://localhost/api/markets')
// Test error handling
})
})
```
### 端到端测试模式 (Playwright)
```typescript
import { test, expect } from '@playwright/test'
test('user can search and filter markets', async ({ page }) => {
// Navigate to markets page
await page.goto('/')
await page.click('a[href="/markets"]')
// Verify page loaded
await expect(page.locator('h1')).toContainText('Markets')
// Search for markets
await page.fill('input[placeholder="Search markets"]', 'election')
// Wait for debounce and results
await page.waitForTimeout(600)
// Verify search results displayed
const results = page.locator('[data-testid="market-card"]')
await expect(results).toHaveCount(5, { timeout: 5000 })
// Verify results contain search term
const firstResult = results.first()
await expect(firstResult).toContainText('election', { ignoreCase: true })
// Filter by status
await page.click('button:has-text("Active")')
// Verify filtered results
await expect(results).toHaveCount(3)
})
test('user can create a new market', async ({ page }) => {
// Login first
await page.goto('/creator-dashboard')
// Fill market creation form
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test description')
await page.fill('input[name="endDate"]', '2025-12-31')
// Submit form
await page.click('button[type="submit"]')
// Verify success message
await expect(page.locator('text=Market created successfully')).toBeVisible()
// Verify redirect to market page
await expect(page).toHaveURL(/\/markets\/test-market/)
})
```
## 测试文件组织
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx # Unit tests
│ │ └── Button.stories.tsx # Storybook
│ └── MarketCard/
│ ├── MarketCard.tsx
│ └── MarketCard.test.tsx
├── app/
│ └── api/
│ └── markets/
│ ├── route.ts
│ └── route.test.ts # Integration tests
└── e2e/
├── markets.spec.ts # E2E tests
├── trading.spec.ts
└── auth.spec.ts
```
## 模拟外部服务
### Supabase 模拟
```typescript
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn(() => ({
eq: jest.fn(() => Promise.resolve({
data: [{ id: 1, name: 'Test Market' }],
error: null
}))
}))
}))
}
}))
```
### Redis 模拟
```typescript
jest.mock('@/lib/redis', () => ({
searchMarketsByVector: jest.fn(() => Promise.resolve([
{ slug: 'test-market', similarity_score: 0.95 }
])),
checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true }))
}))
```
### OpenAI 模拟
```typescript
jest.mock('@/lib/openai', () => ({
generateEmbedding: jest.fn(() => Promise.resolve(
new Array(1536).fill(0.1) // Mock 1536-dim embedding
))
}))
```
## 测试覆盖率验证
### 运行覆盖率报告
```bash
npm run test:coverage
```
### 覆盖率阈值
```json
{
"jest": {
"coverageThresholds": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
## 应避免的常见测试错误
### ❌ 错误:测试实现细节
```typescript
// Don't test internal state
expect(component.state.count).toBe(5)
```
### ✅ 正确:测试用户可见的行为
```typescript
// Test what users see
expect(screen.getByText('Count: 5')).toBeInTheDocument()
```
### ❌ 错误:脆弱的定位器
```typescript
// Breaks easily
await page.click('.css-class-xyz')
```
### ✅ 正确:语义化定位器
```typescript
// Resilient to changes
await page.click('button:has-text("Submit")')
await page.click('[data-testid="submit-button"]')
```
### ❌ 错误:没有测试隔离
```typescript
// Tests depend on each other
test('creates user', () => { /* ... */ })
test('updates same user', () => { /* depends on previous test */ })
```
### ✅ 正确:独立的测试
```typescript
// Each test sets up its own data
test('creates user', () => {
const user = createTestUser()
// Test logic
})
test('updates user', () => {
const user = createTestUser()
// Update logic
})
```
## 持续测试
### 开发期间的监视模式
```bash
npm test -- --watch
# Tests run automatically on file changes
```
### 预提交钩子
```bash
# Runs before every commit
npm test && npm run lint
```
### CI/CD 集成
```yaml
# GitHub Actions
- name: Run Tests
run: npm test -- --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
```
## 最佳实践
1. **先写测试** - 始终遵循TDD
2. **每个测试一个断言** - 专注于单一行为
3. **描述性的测试名称** - 解释测试内容
4. **组织-执行-断言** - 清晰的测试结构
5. **模拟外部依赖** - 隔离单元测试
6. **测试边缘情况** - Null、undefined、空、大量数据
7. **测试错误路径** - 不仅仅是正常路径
8. **保持测试快速** - 单元测试每个 < 50ms
9. **测试后清理** - 无副作用
10. **审查覆盖率报告** - 识别空白
## 成功指标
* 达到 80%+ 代码覆盖率
* 所有测试通过(绿色)
* 没有跳过或禁用的测试
* 快速测试执行(单元测试 < 30秒
* 端到端测试覆盖关键用户流程
* 测试在生产前捕获错误
***
**记住**:测试不是可选的。它们是安全网,能够实现自信的重构、快速的开发和生产的可靠性。

View File

@@ -0,0 +1,130 @@
# 验证循环技能
一个全面的 Claude Code 会话验证系统。
## 何时使用
在以下情况下调用此技能:
* 完成功能或重大代码变更后
* 创建 PR 之前
* 当您希望确保质量门通过时
* 重构之后
## 验证阶段
### 阶段 1构建验证
```bash
# Check if project builds
npm run build 2>&1 | tail -20
# OR
pnpm build 2>&1 | tail -20
```
如果构建失败,请停止并在继续之前修复。
### 阶段 2类型检查
```bash
# TypeScript projects
npx tsc --noEmit 2>&1 | head -30
# Python projects
pyright . 2>&1 | head -30
```
报告所有类型错误。在继续之前修复关键错误。
### 阶段 3代码规范检查
```bash
# JavaScript/TypeScript
npm run lint 2>&1 | head -30
# Python
ruff check . 2>&1 | head -30
```
### 阶段 4测试套件
```bash
# Run tests with coverage
npm run test -- --coverage 2>&1 | tail -50
# Check coverage threshold
# Target: 80% minimum
```
报告:
* 总测试数X
* 通过X
* 失败X
* 覆盖率X%
### 阶段 5安全扫描
```bash
# Check for secrets
grep -rn "sk-" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
grep -rn "api_key" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
# Check for console.log
grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | head -10
```
### 阶段 6差异审查
```bash
# Show what changed
git diff --stat
git diff HEAD~1 --name-only
```
审查每个更改的文件,检查:
* 意外更改
* 缺失的错误处理
* 潜在的边界情况
## 输出格式
运行所有阶段后,生成验证报告:
```
VERIFICATION REPORT
==================
Build: [PASS/FAIL]
Types: [PASS/FAIL] (X errors)
Lint: [PASS/FAIL] (X warnings)
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
Security: [PASS/FAIL] (X issues)
Diff: [X files changed]
Overall: [READY/NOT READY] for PR
Issues to Fix:
1. ...
2. ...
```
## 持续模式
对于长时间会话,每 15 分钟或在重大更改后运行验证:
```markdown
设置一个心理检查点:
- 完成每个函数后
- 完成一个组件后
- 在移动到下一个任务之前
运行: /verify
```
## 与钩子的集成
此技能补充 PostToolUse 钩子,但提供更深入的验证。
钩子会立即捕获问题;此技能提供全面的审查。