开发者指南
积分系统开发指南
架构概述
积分系统由几个关键组件组成:
- 积分管理:积分操作的核心逻辑
 - 配额服务:资源消耗跟踪
 - 数据库架构:积分存储和历史记录
 - API 端点:积分管理接口
 - 定时任务:自动积分续期
 
数据库架构
积分系统使用以下数据库表:
用户积分表
CREATE TABLE user_credits (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES user(id),
  credits INTEGER NOT NULL DEFAULT 0,
  monthly_credits INTEGER NOT NULL DEFAULT 0,
  last_reset_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);积分交易表
CREATE TABLE credit_transactions (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES user(id),
  amount INTEGER NOT NULL,
  type TEXT NOT NULL, -- 'earn', 'spend', 'refund', 'grant'
  description TEXT,
  metadata JSONB,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);核心服务
积分服务
主要的积分服务处理所有积分操作:
// src/lib/credits/credit-service.ts
import { db } from '@/server/db'
import { userCredits, creditTransactions } from '@/server/db/schema'
import { eq } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
export class CreditService {
  static async getUserCredits(userId: string) {
    const [credits] = await db
      .select()
      .from(userCredits)
      .where(eq(userCredits.userId, userId))
      .limit(1)
    return credits || null
  }
  static async addCredits(
    userId: string,
    amount: number,
    type: 'earn' | 'grant' | 'refund',
    description?: string,
    metadata?: Record<string, any>
  ) {
    return await db.transaction(async (tx) => {
      // 更新用户积分
      await tx
        .update(userCredits)
        .set({
          credits: sql`credits + ${amount}`,
          updatedAt: new Date()
        })
        .where(eq(userCredits.userId, userId))
      // 记录交易
      await tx.insert(creditTransactions).values({
        id: uuidv4(),
        userId,
        amount,
        type,
        description,
        metadata
      })
    })
  }
  static async spendCredits(
    userId: string,
    amount: number,
    description?: string,
    metadata?: Record<string, any>
  ) {
    const userCredit = await this.getUserCredits(userId)
    
    if (!userCredit || userCredit.credits < amount) {
      throw new Error('积分不足')
    }
    return await db.transaction(async (tx) => {
      // 扣除积分
      await tx
        .update(userCredits)
        .set({
          credits: sql`credits - ${amount}`,
          updatedAt: new Date()
        })
        .where(eq(userCredits.userId, userId))
      // 记录交易
      await tx.insert(creditTransactions).values({
        id: uuidv4(),
        userId,
        amount: -amount,
        type: 'spend',
        description,
        metadata
      })
    })
  }
}配额服务
配额服务管理资源消耗:
// src/lib/quota/quota-service.ts
import { CreditService } from '@/lib/credits/credit-service'
import { CREDIT_COSTS } from '@/config/credits.config'
export class QuotaService {
  static async checkQuota(userId: string, resource: string, amount = 1) {
    const cost = CREDIT_COSTS[resource] * amount
    const userCredits = await CreditService.getUserCredits(userId)
    
    if (!userCredits || userCredits.credits < cost) {
      return { allowed: false, cost, available: userCredits?.credits || 0 }
    }
    
    return { allowed: true, cost, available: userCredits.credits }
  }
  static async consumeQuota(
    userId: string,
    resource: string,
    amount = 1,
    metadata?: Record<string, any>
  ) {
    const cost = CREDIT_COSTS[resource] * amount
    
    await CreditService.spendCredits(
      userId,
      cost,
      `使用了 ${amount} 个 ${resource}`,
      { resource, amount, ...metadata }
    )
    
    return { cost, remaining: await this.getRemainingCredits(userId) }
  }
  static async getRemainingCredits(userId: string) {
    const userCredits = await CreditService.getUserCredits(userId)
    return userCredits?.credits || 0
  }
}配置
积分成本在积分配置文件中配置:
// src/config/credits.config.ts
export const CREDIT_COSTS = {
  'ai_chat_message': 1,
  'file_upload': 2,
  'api_call': 1,
  'premium_feature': 5
} as const
export const CREDIT_LIMITS = {
  FREE_TIER: 100,
  PRO_TIER: 1000,
  ENTERPRISE_TIER: 10000
} as const
export const MONTHLY_CREDIT_GRANTS = {
  FREE_TIER: 100,
  PRO_TIER: 1000,
  ENTERPRISE_TIER: 10000
} as constAPI 端点
获取用户积分
// src/app/api/credits/route.ts
export async function GET(request: NextRequest) {
  const session = await auth.api.getSession({ headers: request.headers })
  
  if (!session?.user) {
    return NextResponse.json({ error: '未授权' }, { status: 401 })
  }
  const credits = await CreditService.getUserCredits(session.user.id)
  const transactions = await CreditService.getTransactionHistory(
    session.user.id,
    10
  )
  return NextResponse.json({ credits, transactions })
}授予积分(管理员)
export async function POST(request: NextRequest) {
  const session = await auth.api.getSession({ headers: request.headers })
  
  if (!session?.user?.role === 'admin') {
    return NextResponse.json({ error: '禁止访问' }, { status: 403 })
  }
  const { userId, amount, description } = await request.json()
  await CreditService.addCredits(userId, amount, 'grant', description)
  return NextResponse.json({ success: true })
}中间件集成
将积分检查集成到您的 API 中间件中:
// src/middleware/credit-check.ts
import { QuotaService } from '@/lib/quota/quota-service'
export async function creditCheckMiddleware(
  userId: string,
  resource: string,
  amount = 1
) {
  const quota = await QuotaService.checkQuota(userId, resource, amount)
  
  if (!quota.allowed) {
    throw new Error(`积分不足。需要:${quota.cost},可用:${quota.available}`)
  }
  
  return quota
}定时任务
自动月度积分续期:
// src/server/cron/credit-renewal.ts
import { CreditService } from '@/lib/credits/credit-service'
import { MONTHLY_CREDIT_GRANTS } from '@/config/credits.config'
export async function renewMonthlyCredits() {
  const users = await db.select().from(user).where(eq(user.banned, false))
  
  for (const user of users) {
    const grantAmount = MONTHLY_CREDIT_GRANTS[user.tier] || MONTHLY_CREDIT_GRANTS.FREE_TIER
    
    await CreditService.addCredits(
      user.id,
      grantAmount,
      'grant',
      '月度积分续期'
    )
  }
}使用示例
在 API 路由中
export async function POST(request: NextRequest) {
  const session = await auth.api.getSession({ headers: request.headers })
  
  // 处理前检查配额
  const quota = await QuotaService.checkQuota(session.user.id, 'ai_chat_message')
  if (!quota.allowed) {
    return NextResponse.json(
      { error: '积分不足', required: quota.cost },
      { status: 402 }
    )
  }
  
  // 处理请求
  const result = await processAIChat(request)
  
  // 成功处理后消耗积分
  await QuotaService.consumeQuota(session.user.id, 'ai_chat_message')
  
  return NextResponse.json(result)
}在 React 组件中
// src/hooks/use-credits.ts
export function useCredits() {
  const [credits, setCredits] = useState(null)
  const [loading, setLoading] = useState(true)
  
  useEffect(() => {
    fetchCredits()
  }, [])
  
  const fetchCredits = async () => {
    try {
      const response = await fetch('/api/credits')
      const data = await response.json()
      setCredits(data.credits)
    } catch (error) {
      console.error('获取积分失败:', error)
    } finally {
      setLoading(false)
    }
  }
  
  return { credits, loading, refetch: fetchCredits }
}最佳实践
- 事务安全:始终对积分操作使用数据库事务
 - 错误处理:为积分不足实现适当的错误处理
 - 审计跟踪:保留详细的交易记录以便调试
 - 速率限制:结合速率限制以获得额外保护
 - 监控:为异常积分消耗模式设置警报
 - 测试:为积分操作编写全面的测试
 
测试
// tests/unit/credit-service.test.ts
import { CreditService } from '@/lib/credits/credit-service'
describe('CreditService', () => {
  it('应该正确添加积分', async () => {
    const userId = 'test-user'
    const initialCredits = await CreditService.getUserCredits(userId)
    
    await CreditService.addCredits(userId, 100, 'grant', '测试授予')
    
    const updatedCredits = await CreditService.getUserCredits(userId)
    expect(updatedCredits.credits).toBe(initialCredits.credits + 100)
  })
  
  it('应该防止花费超过可用积分', async () => {
    const userId = 'test-user'
    
    await expect(
      CreditService.spendCredits(userId, 999999, '测试花费')
    ).rejects.toThrow('积分不足')
  })
})这个积分系统为您的 SaaS 应用程序中管理用户配额和资源消耗提供了强大的基础。