返回首页

API变现实战教程2026:用FastAPI把代码工具变成被动收入印钞机,含完整Python代码

变现教程 — 把你的工具变成印钞机

🎯 课程目标:学习如何将你的代码工具/能力包装成 REST API 服务,上架 API 市场,实现持续被动收入。本课程包含完整的 FastAPI 实战代码,涵盖网关设计、认证、限流、计费和自动文档生成。


第一章:什么是 API 经济

1.1 API 经济概述

API 经济(API Economy)是指通过开放和消费 API 来创造商业价值的经济模式。当今世界,几乎所有互联网服务都通过 API 连接:

  • Stripe 的 API 处理了全球数千亿美元的支付
  • 的 API 让任何应用都能接入 能力
  • Twilio 的 API 让任何应用都能发短信、打电话

API 变现的本质:把你写好的能力(代码),通过标准接口开放出去,让别人付费调用。

1.2 为什么 API 是最好的被动收入

维度 传统软件 API 服务
交付方式 需要安装部署 HTTP 调用即用
计费方式 一次性/年度订阅 按调用次数实时计费
扩展性 需要销售团队 开发者自助注册使用
维护成本 每个客户独立部署 集中维护,所有人共享
变现速度 慢,需要漫长的销售周期 快,开发者看完文档就能付费调用

1.3 你的 82 个工具如何变成 API

假设你已经有了 82 个工具/脚本(文本处理、数据转换、 调用、格式转换等),变现路径如下:

工具脚本 → FastAPI 包装 → API 网关(认证+限流+计费) → 上架市场 → 被动收入

每一步本课程都会详细讲解。


第二章:用 FastAPI 包装工具为 REST API

2.1 项目结构

api-monetization/
├── app/
│   ├── __init__.py
│   ├── main.py                  # FastAPI 入口
│   ├── config.py                # 配置
│   ├── .py              # 数据库
│   ├── models/
│   │   ├── __init__.py
│   │   ├── user.py              # 用户模型
│   │   └── usage.py             # 使用记录模型
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── api.py               # API 请求/响应模型
│   ├── middleware/
│   │   ├── __init__.py
│   │   ├── auth.py              # API Key 认证
│   │   ├── rate_limit.py        # 限流
│   │   └── billing.py           # 计费中间件
│   ├── routers/
│   │   ├── __init__.py
│   │   ├── auth.py              # 认证路由
│   │   ├── tools.py             # 工具 API 路由
│   │   ├── text.py              # 文本处理工具
│   │   ├── .py             # 图片处理工具
│   │   ├── .py              # 数据处理工具
│   │   └── ai.py                # AI 工具
│   └── services/
│       ├── __init__.py
│       ├── tool_registry.py     # 工具注册表
│       └── billing.py           # 计费服务
├── tools/                       # 你的 82 个工具
│   ├── text_tools.py
│   ├── image_tools.py
│   ├── data_tools.py
│   └── ai_tools.py
├── requirements.txt
├── Dockerfile
└── .env.example

2.2 核心配置(config.py)

# app/config.py
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    APP_NAME: str = "ToolAPI"
    APP_VERSION: str = "1.0.0"
    DEBUG: bool = False

    # 数据库
    DATABASE_URL: str = "postgresql://postgres:***@localhost:5432/toolapi"

    # Redis
    REDIS_URL: str = "redis://localhost:***@app.get("/")
def root():
    return {
        "name": settings.APP_NAME,
        "version": settings.APP_VERSION,
        "endpoints": {
            "docs": "/docs",
            "redoc": "/redoc",
            "openapi": "/openapi.json",
            "tools": "/api/v1/tools",
        },
    }


@app.get("/health")
def health():
    return {"status": "healthy", "version": settings.APP_VERSION}

2.4 完整的工具注册系统

# app/services/tool_registry.py
"""工具注册系统 — 管理所有 API 工具的注册、分类和元数据"""

from typing import Any, Callable, Dict, List, Optional
from dataclasses import dataclass, field
from enum import Enum


class ToolCategory(str, Enum):
    TEXT = "text"
    IMAGE = "image"
    DATA = "data"
    AI = "ai"
    CONVERSION = "conversion"
    VALIDATION = "validation"


class PricingTier(str, Enum):
     = "free"           # 免费
    BASIC = "basic"         # 基础版
    PRO = "pro"             # 专业版
     = "enterprise"  # 企业版


@dataclass
class ToolMeta:
    name: str
    description: str
    category: ToolCategory
    pricing_tier: PricingTier
    cost_per_call: int           # 每次调用费用(分)
    input_schema: Dict[str, Any]
    output_schema: Dict[str, Any]
    tags: List[str] = field(default_factory=list)
    rate_limit: int = 100       # 每分钟调用上限
    handler: Optional[Callable] = None

    def to_dict(self) -> dict:
        return {
            "name": self.name,
            "description": self.description,
            "category": self.category.value,
            "pricing_tier": self.pricing_tier.value,
            "cost_per_call_cents": self.cost_per_call,
            "rate_limit_per_minute": self.rate_limit,
            "tags": self.tags,
            "input_schema": self.input_schema,
            "output_schema": self.output_schema,
        }


class ToolRegistry:
    """工具注册中心 — 单例模式"""

    _instance = None
    _tools: Dict[str, ToolMeta] = {}

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def register(self, tool_id: str, : ToolMeta):
        """注册一个工具"""
        self._tools[tool_id] = meta
        return meta

    def get(self, tool_id: str) -> Optional[ToolMeta]:
        return self._tools.get(tool_id)

    def list_tools(
        self,
        category: Optional[ToolCategory] = None,
        pricing_tier: Optional[PricingTier] = None,
    ) -> List[Dict]:
        tools = list(self._tools.values())
        if category:
            tools = [t for t in tools if t.category == category]
        if pricing_tier:
            tools = [t for t in tools if t.pricing_tier == pricing_tier]
        return [t.to_dict() for t in tools]

    def get_categories(self) -> Dict[str, int]:
        result = {}
        for tool in self._tools.values():
            cat = tool.category.value
            result[cat] = result.get(cat, 0) + 1
        return result


# 全局注册表实例
registry = ToolRegistry()

2.5 工具实现示例

# tools/text_tools.py
"""文本处理工具集 — 每个工具都是独立的函数,通过注册系统暴露为 API"""

import hashlib
import base64
import json
import re
from collections import Counter
from typing import Any, Dict, List

from app.services.tool_registry import (
    registry,
    ToolMeta,
    ToolCategory,
    PricingTier,
)


# ===== 工具1: 文本摘要 =====
def text_summarize(text: str, max_sentences: int = 3) -> Dict[str, Any]:
    """简易文本摘要(基于句子权重)"""
    sentences = re.split(r'[。!?.!?\n]+', text)
    sentences = [s.strip() for s in sentences if len(s.strip()) > 5]

    if len(sentences) <= max_sentences:
        return {"summary": "。".join(sentences), "sentence_count": len(sentences)}

    # 计算词频
    words = re.findall(r'[\w\u4e00-\u9fff]+', text.lower())
    word_freq = Counter(words)

    # 按句子得分排序
    scored = []
    for i, sent in enumerate(sentences):
        sent_words = re.findall(r'[\w\u4e00-\u9fff]+', sent.lower())
        score = sum(word_freq.get(w, 0) for w in sent_words)
        # 位置权重:开头和结尾的句子更重要
        if i < 2:
            score *= 1.5
        if i >= len(sentences) - 2:
            score *= 1.2
        scored.append((score, i, sent))

    scored.sort(reverse=True)
    selected = sorted(scored[:max_sentences], key=lambda x: x[1])
    summary = "。".join(s[2] for s in selected)

    return {"summary": summary, "original_length": len(text), "summary_length": len(summary)}


registry.register(
    "text_summarize",
    ToolMeta(
        name="文本摘要",
        description="对输入文本进行自动摘要提取",
        category=ToolCategory.TEXT,
        pricing_tier=PricingTier.FREE,
        cost_per_call=0,
        rate_limit=30,
        tags=["", "摘要", "文本处理"],
        input_schema={
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "待摘要的文本"},
                "max_sentences": {"type": "integer", "default": 3, "description": "摘要最大句子数"},
            },
            "required": ["text"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "summary": {"type": "string"},
                "original_length": {"type": "integer"},
                "summary_length": {"type": "integer"},
            },
        },
    ),
)


# ===== 工具2: 关键词提取 =====
def extract_keywords(text: str, top_n: int = 10) -> Dict[str, Any]:
    """基于 TF 的关键词提取"""
    words = re.findall(r'[\w\u4e00-\u9fff]{2,}', text.lower())

    # 停用词(简化版)
    stopwords = set("的了是在我有和人这中大为上个国不以到说时要就出会也年对自其")
    words = [w for w in words if w not in stopwords and len(w) >= 2]

    freq = Counter(words)
    top_keywords = freq.most_common(top_n)

    return {
        "keywords": [{"word": w, "count": } for w, c in top_keywords],
        "total_words": len(words),
        "unique_words": len(freq),
    }


registry.register(
    "text_keywords",
    ToolMeta(
        name="关键词提取",
        description="从文本中提取最重要的关键词",
        category=ToolCategory.TEXT,
        pricing_tier=PricingTier.FREE,
        cost_per_call=0,
        rate_limit=30,
        tags=["nlp", "关键词", "文本分析"],
        input_schema={
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "待分析的文本"},
                "top_n": {"type": "integer", "default": 10, "description": "返回关键词数量"},
            },
            "required": ["text"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "keywords": {"type": "array", "items": {"type": "object"}},
                "total_words": {"type": "integer"},
                "unique_words": {"type": "integer"},
            },
        },
    ),
)


# ===== 工具3: 文本加密(Base64) =====
def text_encode(text: str, encoding: str = "base64") -> Dict[str, Any]:
    """文本编码/加密"""
    if encoding == "base64":
        result = base64.b64encode(text.encode()).decode()
    elif encoding == "hex":
        result = text.encode().hex()
    elif encoding == "md5":
        result = hashlib.md5(text.encode()).hexdigest()
    elif encoding == "sha256":
        result = hashlib.sha256(text.encode()).hexdigest()
    else:
        return {"error": f"不支持的编码方式: {encoding}"}

    return {"input": text[:50] + "..." if len(text) > 50 else text, "encoding": encoding, "result": result}


registry.register(
    "text_encode",
    ToolMeta(
        name="文本编码",
        description="支持 Base64、Hex、MD5、SHA256 编码",
        category=ToolCategory.CONVERSION,
        pricing_tier=PricingTier.FREE,
        cost_per_call=0,
        rate_limit=60,
        tags=["编码", "加密", "转换"],
        input_schema={
            "type": "object",
            "properties": {
                "text": {"type": "string"},
                "encoding": {"type": "string", "enum": ["base64", "hex", "md5", "sha256"], "default": "base64"},
            },
            "required": ["text"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "input": {"type": "string"},
                "encoding": {"type": "string"},
                "result": {"type": "string"},
            },
        },
    ),
)


# ===== 工具4: JSON 格式化 =====
def json_format(text: str, indent: int = 2) -> Dict[str, Any]:
    """JSON 格式化和验证"""
    try:
        obj = json.loads(text)
        formatted = json.dumps(obj, indent=indent, ensure_ascii=False)
        return {
            "valid": True,
            "formatted": formatted,
            "keys_count": len(obj) if isinstance(obj, dict) else None,
            "type": type(obj).__name__,
        }
    except json.JSONDecodeError as e:
        return {"valid": False, "error": str(e)}


registry.register(
    "json_format",
    ToolMeta(
        name="JSON格式化",
        description="格式化和验证 JSON 数据",
        category=ToolCategory.DATA,
        pricing_tier=PricingTier.FREE,
        cost_per_call=0,
        rate_limit=60,
        tags=["json", "格式化", "验证"],
        input_schema={
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "JSON 字符串"},
                "indent": {"type": "integer", "default": 2},
            },
            "required": ["text"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "valid": {"type": "boolean"},
                "formatted": {"type": "string"},
            },
        },
    ),
)


# ===== 工具5: 正则表达式测试 =====
def regex_test(pattern: str, text: str) -> Dict[str, Any]:
    """正则表达式测试"""
    try:
        matches = list(re.finditer(pattern, text))
        return {
            "valid_pattern": True,
            "match_count": len(matches),
            "matches": [
                {"text": m.group(), "start": m.start(), "end": m.end()}
                for m in matches[:20]
            ],
        }
    except re.error as e:
        return {"valid_pattern": False, "error": str(e)}


registry.register(
    "regex_test",
    ToolMeta(
        name="正则表达式测试",
        description="测试正则表达式并返回匹配结果",
        category=ToolCategory.DEVELOPER,
        pricing_tier=PricingTier.FREE,
        cost_per_call=0,
        rate_limit=60,
        tags=["正则", "开发工具"],
        input_schema={
            "type": "object",
            "properties": {
                "pattern": {"type": "string"},
                "text": {"type": "string"},
            },
            "required": ["pattern", "text"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "valid_pattern": {"type": "boolean"},
                "match_count": {"type": "integer"},
                "matches": {"type": "array"},
            },
        },
    ),
)

2.6 认证中间件

# app/middleware/auth.py
"""API Key 认证中间件"""

from fastapi import Request, HTTPException, Depends
from fastapi. import APIKeyHeader
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.user import APIUser

api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)


async def get_api_user(
    request: Request,
    api_key: str = Depends(api_key_header),
    db: Session = Depends(get_db),
) -> APIUser:
    """通过 API Key 认证用户"""
    if not api_key:
        raise HTTPException(
            status_code=401,
            detail={
                "error": "missing_api_key",
                "message": "请在请求头中提供 X-API-Key",
                "docs": "https://yourdomain.com/docs/authentication",
            },
        )

    user = db.query(APIUser).filter(APIUser.api_key == api_key, APIUser.is_active == True).first()
    if not user:
        raise HTTPException(
            status_code=401,
            detail={
                "error": "invalid_api_key",
                "message": "API Key 无效或已禁用",
            },
        )

    # 将用户信息附加到 request.state
    request.state.user = user
    return user


def require_tier(minimum_tier: str):
    """要求用户达到最低套餐等级"""
    tier_order = {"free": 0, "basic": 1, "pro": 2, "enterprise": 3}

    async def check_tier(user: APIUser = Depends(get_api_user)):
        if tier_order.get(user.tier, 0) < tier_order.get(minimum_tier, 0):
            raise HTTPException(
                status_code=403,
                detail={
                    "error": "upgrade_required",
                    "message": f"此功能需要 {minimum_tier} 或更高套餐",
                    "current_tier": user.tier,
                    "required_tier": minimum_tier,
                },
            )
        return user

    return check_tier

2.7 限流中间件

# app/middleware/rate_limit.py
"""基于 Redis 的 API 限流"""

import time
from fastapi import Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
import redis.asyncio as redis
from app.config import settings

redis_pool = None


async def get_redis():
    global redis_pool
    if redis_pool is None:
        redis_pool = redis.from_url(settings.REDIS_URL, decode_responses=True)
    return redis_pool


class RateLimitMiddleware(BaseHTTPMiddleware):
    """滑动窗口限流"""

    async def dispatch(self, request: Request, call_next):
        # 只限流 API 路径
        if not request.url.path.startswith("/api/v1/"):
            return await call_next(request)

        # 获取用户标识
        api_key = request.headers.get("X-API-Key", "")
        if not api_key:
            return await call_next(request)  # 认证中间件会处理

        r = await get_redis()

        # 获取用户的限流配置
        user_tier = getattr(request.state, "user_tier", "free")
        limits = {
            "free": 60,        # 60次/分钟
            "basic": 300,      # 300次/分钟
            "pro": 1200,       # 1200次/分钟
            "enterprise": 6000, # 6000次/分钟
        }
        max_requests = limits.get(user_tier, 60)

        # 滑动窗口计数
        now = time.time()
        window = 60  # 1分钟窗口
        key = f"rl:{api_key}"

        pipe = r.pipeline()
        pipe.zremrangebyscore(key, 0, now - window)
        pipe.zadd(key, {str(now): now})
        pipe.zcard(key)
        pipe.expire(key, window)
        results = await pipe.execute()

        current_count = results[2]

        if current_count > max_requests:
            return JSONResponse(
                status_code=429,
                content={
                    "error": "rate_limit_exceeded",
                    "message": f"超出速率限制 ({max_requests}/分钟)",
                    "retry_after": 1,
                },
                headers={
                    "X-RateLimit-Limit": str(max_requests),
                    "X-RateLimit-Remaining": "0",
                    "X-RateLimit-Reset": str(int(now + window)),
                    "Retry-After": "1",
                },
            )

        response = await call_next(request)
        response.headers["X-RateLimit-Limit"] = str(max_requests)
        response.headers["X-RateLimit-Remaining"] = str(max(0, max_requests - current_count))
        response.headers["X-RateLimit-Reset"] = str(int(now + window))
        return response

2.8 工具 API 路由(核心)

# app/routers/tools.py
"""统一工具 API 路由 — 所有工具通过一个统一接口调用"""

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field

from app.database import get_db
from app.middleware.auth import get_api_user
from app.models.user import APIUser
from app.models.usage import UsageRecord
from app.services.tool_registry import registry, ToolCategory, PricingTier
from app.services.billing import BillingService

 = APIRouter()


class ToolCallRequest(BaseModel):
    tool_id: str = Field(..., description="工具 ID")
    params: Dict[str, Any] = Field(..., description="工具参数")


class ToolCallResponse(BaseModel):
    success: bool
    tool_id: str
    result: Any
    usage: Dict[str, Any]


@router.get("/tools", summary="列出所有可用工具")
async def list_tools(
    category: Optional[str] = None,
    pricing_tier: Optional[str] = None,
    user: APIUser = Depends(get_api_user),
):
    """获取所有可用工具列表"""
    cat = ToolCategory(category) if category else None
    tier = PricingTier(pricing_tier) if pricing_tier else None
    tools = registry.list_tools(category=cat, pricing_tier=tier)
    return {
        "total": len(tools),
        "tools": tools,
        "categories": registry.get_categories(),
    }


@router.get("/tools/{tool_id}", summary="获取工具详情")
async def get_tool(tool_id: str, user: APIUser = Depends(get_api_user)):
    """获取单个工具的详细信息"""
    tool = registry.get(tool_id)
    if not tool:
        raise HTTPException(status_code=404, detail=f"工具不存在: {tool_id}")
    return tool.to_dict()


@router.post("/tools/call", response_model=ToolCallResponse, summary="调用工具")
async def call_tool(
    req: ToolCallRequest,
    request: Request,
    user: APIUser = Depends(get_api_user),
    db: Session = Depends(get_db),
):
    """
    统一工具调用接口。所有工具都通过此接口调用。

    示例:
    ```json
    {
        "tool_id": "text_summarize",
        "params": {
            "text": "这是一段很长的文本...",
            "max_sentences": 3
        }
    }
    ```
    """
    # 1. 检查工具是否存在
    tool = registry.get(req.tool_id)
    if not tool:
        raise HTTPException(status_code=404, detail=f"工具不存在: {req.tool_id}")

    # 2. 检查套餐权限
    tier_order = {"free": 0, "basic": 1, "pro": 2, "enterprise": 3}
    if tier_order.get(user.tier, 0) < tier_order.get(tool.pricing_tier.value, 0):
        raise HTTPException(
            status_code=403,
            detail=f"工具 '{req.tool_id}' 需要 {tool.pricing_tier.value} 套餐,当前套餐: {user.tier}",
        )

    # 3. 检查余额(付费工具)
    if tool.cost_per_call > 0:
        if user.balance_cents < tool.cost_per_call:
            raise HTTPException(
                status_code=402,
                detail={
                    "error": "insufficient_balance",
                    "message": f"余额不足,需要 {tool.cost_per_call/100} 元",
                    "current_balance": user.balance_cents / 100,
                },
            )

    # 4. 执行工具
    if not tool.handler:
        raise HTTPException(status_code=500, detail="工具处理函数未注册")

    try:
        result = tool.handler(**req.params)
    except TypeError as e:
        raise HTTPException(status_code=400, detail=f"参数错误: {str(e)}")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"工具执行失败: {str(e)}")

    # 5. 记录使用和扣费
    BillingService.record_usage(
        db=db,
        user_id=user.id,
        tool_id=req.tool_id,
        cost_cents=tool.cost_per_call,
    )

    if tool.cost_per_call > 0:
        BillingService.deduct_balance(db, user.id, tool.cost_per_call)

    return ToolCallResponse(
        success=True,
        tool_id=req.tool_id,
        result=result,
        usage={
            "cost_cents": tool.cost_per_call,
            "remaining_balance_cents": user.balance_cents - tool.cost_per_call,
        },
    )

2.9 计费服务

# app/services/billing.py
"""计费服务 — 记录使用量、扣费、余额管理"""

from datetime import datetime, timedelta
from typing import Dict, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func

from app.models.user import APIUser
from app.models.usage import UsageRecord


class BillingService:

    @staticmethod
    def record_usage(db: Session, user_id: int, tool_id: str, cost_cents: int = 0):
        """记录一次 API 使用"""
        record = UsageRecord(
            user_id=user_id,
            tool_id=tool_id,
            cost_cents=cost_cents,
        )
        db.add(record)
        db.commit()

    @staticmethod
    def deduct_balance(db: Session, user_id: int, amount_cents: int):
        """扣减用户余额"""
        user = db.query(APIUser).filter(APIUser.id == user_id).first()
        if user:
            user.balance_cents = max(0, user.balance_cents - amount_cents)
            db.commit()

    @staticmethod
    def get_usage_stats(db: Session, user_id: int, days: int = 30) -> Dict:
        """获取使用统计"""
        since = datetime.utcnow() - timedelta(days=days)

        # 总调用次数和费用
        totals = (
            db.query(
                func.count(UsageRecord.id).label("total_calls"),
                func.coalesce(func.sum(UsageRecord.cost_cents), 0).label("total_cost"),
            )
            .filter(UsageRecord.user_id == user_id, UsageRecord.created_at >= since)
            .first()
        )

        # 按工具分组统计
        by_tool = (
            db.query(
                UsageRecord.tool_id,
                func.count(UsageRecord.id).label("calls"),
                func.sum(UsageRecord.cost_cents).label("cost"),
            )
            .filter(UsageRecord.user_id == user_id, UsageRecord.created_at >= since)
            .group_by(UsageRecord.tool_id)
            .order_by(func.count(UsageRecord.id).desc())
            .limit(20)
            .all()
        )

        # 每日统计
        daily = []
        for i in range(min(days, 30) - 1, -1, -1):
            day = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=i)
            day_end = day + timedelta(days=1)
            count = (
                db.query(func.count(UsageRecord.id))
                .filter(
                    UsageRecord.user_id == user_id,
                    UsageRecord.created_at >= day,
                    UsageRecord.created_at < day_end,
                )
                .scalar()
            )
            daily.append({"date": day.strftime("%Y-%m-%d"), "calls": count or 0})

        return {
            "period_days": days,
            "total_calls": totals.total_calls or 0,
            "total_cost_cents": int(totals.total_cost or 0),
            "total_cost_yuan": round(int(totals.total_cost or 0) / 100, 2),
            "top_tools": [
                {"tool_id": r.tool_id, "calls": r.calls, "cost_cents": int(r.cost or 0)}
                for r in by_tool
            ],
            "daily": daily,
        }

    @staticmethod
    def add_balance(db: Session, user_id: int, amount_cents: int):
        """充值余额"""
        user = db.query(APIUser).filter(APIUser.id == user_id).first()
        if user:
            user.balance_cents += amount_cents
            db.commit()
            return user.balance_cents
        return None

2.10 使用记录模型

# app/models/usage.py
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from app.database import Base


class UsageRecord(Base):
    __tablename__ = "usage_records"

    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(Integer, ForeignKey("api_users.id"), nullable=False, index=True)
    tool_id = Column(String(100), nullable=False, index=True)
    cost_cents = Column(Integer, default=0)
    created_at = Column(DateTime, default=datetime.utcnow, index=True)

第三章:API 文档自动生成

3.1 OpenAPI / Swagger

FastAPI 自动基于你的 Pydantic 模型和路由函数生成 OpenAPI 文档。只需访问 /docs 即可看到完整的交互式 API 文档。

3.2 增强文档质量

# 在路由装饰器中添加详细描述
@router.post(
    "/tools/call",
    response_model=ToolCallResponse,
    summary="调用工具",
    description="""
    统一工具调用接口。所有工具都通过此接口调用。

    **使用流程:**
    1. 先调用 `GET /api/v1/tools` 获取可用工具列表
    2. 选择需要的工具,构造参数
    3. 调用 `POST /api/v1/tools/call`,传入 `tool_id` 和 `params`

    **示例请求:**
    ```json
    {
        "tool_id": "text_summarize",
        "params": {
            "text": "你的长文本...",
            "max_sentences": 3
        }
    }
    ```

    **注意事项:**
    - 免费工具无需扣费
    - 付费工具会自动从余额扣除
    - 超出套餐权限会返回 403
    """,
    responses={
        200: {"description": "工具调用成功"},
        400: {"description": "参数错误"},
        402: {"description": "余额不足"},
        403: {"description": "套餐权限不足"},
        404: {"description": "工具不存在"},
        429: {"description": "超出速率限制"},
    },
    tags=["tools"],
)
async def call_tool(...):
    ...

第四章:API 市场上架

4.1 RapidAPI

RapidAPI 是全球最大的 API 市场,月活开发者超过 300 万。

上架步骤:

  1. 注册 RapidAPI 开发者账号:https://docs.rapidapi.com/docs/quickstart
  2. 创建 API Provider Hub
  3. 填写 API 信息(名称、描述、文档 URL)
  4. 设置定价方案:
    • Basic: $0/月,100 次调用
    • Pro: $9.99/月,10000 次调用
    • Ultra: $49.99/月,100000 次调用
  5. 提交审核(通常 1-3 个工作日)

RapidAPI 分成:平台抽取 20%,你获得 80%。

4.2 APILayer / APIHub

4.3 自建 API 门户

如果不想被平台抽成,可以自建:

# 简单的 API 门户首页
@app.get("/portal", include_in_schema=False)
def api_portal():
    tools = registry.list_tools()
    return HTMLResponse(f"""
    <>
    <head><title>ToolAPI - API 市场</title></head>
    <body>
        <h1>🚀 ToolAPI</h1>
        <p>共 {len(tools)} 个 API 工具可用</p>
        <h2>快速开始</h2>
        <pre>
# 1. 获取 API Key
curl -X POST https://api.toolapi.com/auth/register \\
  -d '{{"email": "[email protected]", "password": "yourpass"}}'

# 2. 调用工具
curl -X POST https://api.toolapi.com/api/v1/tools/call \\
  -H "X-API-Key: sk_xxxxx" \\
  -d '{{"tool_id": "text_summarize", "params": {{"text": "..."}}}}'
        </pre>
        <h2>可用工具</h2>
        {"".join(f'<h3>{t["name"]}</h3><p>{t["description"]}</p>' for t in tools)}
    </body>
    </html>
    """)

第五章:定价策略

5.1 按调用次数

最简单直接的定价方式:

费用 = 调用次数 × 单次价格

适合:工具类、数据查询类 API

5.2 按功能分层

不同功能不同价格:

层级 价格 功能
Free $0 基础工具(文本、编码)
Basic $9.99/月 数据处理、格式转换
Pro $29.99/月 AI 工具、高级分析
Enterprise $99.99/月 全部功能 + SLA

5.3 包月订阅 + 超量计费

月费包含 N 次调用,超出部分按次计费

这是最推荐的方式,既有稳定的 MRR,又能捕获大客户的增量收入。


第六章:完整部署方案

6.1 Dockerfile

# Dockerfile
FROM :3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

6.2 docker-compose.yml

version: "3.8"
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://postgres:***@db:5432/toolapi
      REDIS_URL: redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: toolapi
      POSTGRES_PASSWORD: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  :
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - api

volumes:
  pgdata:

6.3 Nginx 配置

# nginx.conf
events {
    worker_connections 1024;
}

http {
    # 限流配置
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    server {
        listen 80;
        server_name api.toolapi.com;

        # API 限流
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://api:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # 文档页面(无限流)
        location /docs {
            proxy_pass http://api:8000;
            proxy_set_header Host $host;
        }

        location / {
            proxy_pass http://api:8000;
        }
    }
}

6.4 requirements.txt

fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
pydantic[email]==2.5.2
pydantic-settings==2.1.0
redis==5.0.1
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.6

总结

通过本课程,你学到了:

  1. API 经济:理解了为什么 API 是最好的被动收入方式
  2. 工具包装:用 FastAPI + 工具注册系统,将任意 Python 函数包装为标准 API
  3. 网关设计:实现了 API Key 认证、滑动窗口限流、计费扣费的完整网关
  4. 自动文档:利用 FastAPI 的 OpenAPI 自动生成交互式 API 文档
  5. 市场上架:了解了 RapidAPI、APILayer 等市场的上架流程和分成模式
  6. 定价策略:按调用次数、按功能分层、包月+超量三种定价方式
  7. 完整部署:Docker + Nginx 的生产级部署方案

下一步行动:

  1. 把你的 82 个工具逐个包装为 API(每个只需 5 分钟)
  2. 注册 RapidAPI 开发者账号并上架
  3. 在社交媒体和开发者社区推广你的 API
  4. 监控使用数据,优化定价和功能

记住:好的 API 不仅要好用,还要好发现。文档质量、开发者体验、定价透明度是决定你的 API 能否变现的关键。

评论