CMS / SaaS 半内置集成方案

本方案面向自研CMS系统、SaaS建站平台、多租户系统等场景,支持你在自己的产品后台里"白标"集成澄泓统计能力,用户无需离开你的系统即可查看统计数据。

🔒 安全架构说明:Internal API 挂载在 /api/internal/ 前缀下,不对外公开文档,采用 Token + IP白名单 双因素鉴权,公网无法扫描和调用。

集成架构

你的CMS/SaaS系统部署在独立服务器上,通过公网调用澄泓统计的 Cloudflare Worker:

你的CMS/SaaS 公网 CF Worker Token校验 + IP白名单双保险

安全配置

环境变量配置

在 Cloudflare Worker 或私有化部署的环境变量中配置以下两项:

变量名说明示例
INTERNAL_TOKEN32位随机字符串,CMS调用时通过 X-Internal-Token Header携带a1b2c3d4e5f6...32位
ALLOWED_CMS_IPSCMS服务器出口IP白名单,逗号分隔1.2.3.4,5.6.7.8
⚠️ 重要:如果你的CMS服务器出口IP是动态的(如云函数、Serverless),请联系技术支持改用"每实例API Key"方案(方案B)。

鉴权流程

text
CMS 请求 → CF Worker
  1. 读取 Header: X-Internal-Token
  2. 读取 IP:     CF-Connecting-IP
  3. Token 不匹配 → 403 Forbidden
  4. IP 不在白名单 → 403 Forbidden
  5. 双校验通过  → 执行业务逻辑

接口列表

所有接口 Base URL 为你的统计服务域名,请求均需携带鉴权头。

1. 新建站点

CMS 开站成功后调用,自动在统计系统创建站点并返回接入信息。

http
POST /api/internal/site.create
Headers:
  X-Internal-Token: {INTERNAL_TOKEN}
  Content-Type: application/json

Body:
{
  "domain": "example.com",       // 必填:站点域名
  "name": "客户站点名称",         // 必填:站点名称
  "owner_email": "u***@example.com", // 可选:站长邮箱
  "timezone": "Asia/Shanghai"    // 可选:时区,默认 Asia/Shanghai
}

Response 200:
{
  "ok": true,
  "data": {
    "tid": "tid_a1b2c3d4e5",                           // Tracking ID(用于SDK上报)
    "script_url": "https://analytics.example.com/t.js", // SDK脚本地址
    "embed_token": "et_xxxxxxxxxxxxxxxxxxxxxxxx",      // iframe仪表盘嵌入令牌
    "embed_url": "https://analytics.example.com/dash/tid_a1b2c3d4e5?embed=1&token=et_xxx",
    "site_id": 123,                                    // 内部站点ID
    "name": "客户站点名称",
    "domain": "example.com"
  }
}

2. 删除站点

CMS 删站时调用,同步删除统计站点及其数据(软删除,数据保留30天可恢复)。

http
DELETE /api/internal/site.delete?tid=tid_a1b2c3d4e5
Headers:
  X-Internal-Token: {INTERNAL_TOKEN}

Response 200:
{
  "ok": true,
  "data": { "deleted": true, "tid": "tid_a1b2c3d4e5" }
}

3. 获取站点指标数据

CMS 后台自己绘制统计卡片时调用,返回核心指标数据。

http
GET /api/internal/site.metrics/tid_a1b2c3d4e5?period=7d
Headers:
  X-Internal-Token: {INTERNAL_TOKEN}

Query 参数:
  period: 时间范围,可选 24h / 7d / 30d,默认 7d

Response 200:
{
  "ok": true,
  "data": {
    "visitors": 3420,        // 访客数(UV)
    "pageviews": 8956,       // 页面浏览量(PV)
    "sessions": 4210,        // 会话数
    "online": 127,           // 当前在线人数
    "bounce_rate": 35.2,     // 跳出率 %
    "avg_duration": 195,     // 平均停留时长(秒)
    "avg_pages": 2.6,        // 平均浏览页数
    "top_pages": [...],      // 热门页面 TOP10
    "top_sources": [...],    // 流量来源 TOP10
    "top_countries": [...],  // 国家分布 TOP10
    "trend": [...]           // 趋势数据(按天)
  }
}

4. iframe 嵌入仪表盘

在 CMS 后台通过 iframe 嵌入完整的统计仪表盘,用户无需跳转到统计系统。

html
<!-- 在你的CMS后台页面中嵌入 -->
<iframe
  src="https://analytics.example.com/dash/tid_a1b2c3d4e5?embed=1&token={embed_token}"
  width="100%"
  height="800px"
  frameborder="0"
  allow="clipboard-write"
></iframe>

embed 模式特性:

  • 极简仪表盘界面,无导航栏、侧边栏、用户菜单
  • 支持在线人数、访客数、浏览量、跳出率等核心指标展示
  • 内置趋势折线图、热门页面、来源分析
  • 支持 24h/7天/30天 时间范围切换
  • 自动每30秒刷新数据
  • 自动适配暗色/亮色模式(跟随系统设置)
  • 响应式布局,适配各种 iframe 尺寸

embed_token 获取方式

embed_token 在调用 site.create 时返回的 embed_url 中已包含,长期有效。如果需要重新生成,可以调用:

http
POST /api/internal/site.embed-token
Headers:
  X-Internal-Token: {INTERNAL_TOKEN}
Body: { "tid": "tid_a1b2c3d4e5" }

Response:
{
  "ok": true,
  "data": {
    "embed_token": "et_xxxxxxxxxxxxxxxx",
    "embed_url": "https://analytics.example.com/dash/tid_xxx?embed=1&token=et_xxx"
  }
}

SDK 接入脚本注入

站点创建成功后,你需要在客户网站页面中注入 SDK 脚本。建议在 CMS 的公共 footer 模板中输出:

html
<!-- 每个站点使用自己的 tid -->
<script async defer
  data-tid="{{ customer.tid }}"
  src="{{ analytics_script_url }}">
</script>

调用示例(Go)

go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

const (
    AnalyticsURL = "https://analytics.example.com"
    InternalToken = "your-32-char-token"
)

type CreateSiteReq struct {
    Domain     string `json:"domain"`
    Name       string `json:"name"`
    OwnerEmail string `json:"owner_email,omitempty"`
}

type CreateSiteResp struct {
    Ok   bool `json:"ok"`
    Data struct {
        Tid       string `json:"tid"`
        ScriptURL string `json:"script_url"`
        EmbedURL  string `json:"embed_url"`
        SiteID    int    `json:"site_id"`
    } `json:"data"`
}

func CreateSite(domain, name, email string) (*CreateSiteResp, error) {
    body, _ := json.Marshal(CreateSiteReq{
        Domain: domain, Name: name, OwnerEmail: email,
    })

    req, _ := http.NewRequest("POST", AnalyticsURL+"/api/internal/site.create", bytes.NewReader(body))
    req.Header.Set("X-Internal-Token", InternalToken)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode == 403 {
        return nil, fmt.Errorf("internal API auth failed: check token and IP whitelist")
    }

    data, _ := io.ReadAll(resp.Body)
    var result CreateSiteResp
    json.Unmarshal(data, &result)
    return &result, nil
}

调用示例(PHP/WordPress)

php
<?php
function analytics_create_site($domain, $name, $email = '') {
    $url = 'https://analytics.example.com/api/internal/site.create';
    $token = 'your-32-char-token';

    $body = json_encode([
        'domain' => $domain,
        'name' => $name,
        'owner_email' => $email,
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_HTTPHEADER => [
            'X-Internal-Token: ' . $token,
            'Content-Type: application/json',
        ],
        CURLOPT_TIMEOUT => 10,
    ]);

    $resp = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code === 403) {
        throw new Exception('Internal API auth failed');
    }

    return json_decode($resp, true);
}

🔐 多租户隔离机制(重要)

⚠️ 关键安全设计:为支持多家CMS/SaaS平台同时接入,系统实现了严格的多租户数据隔离。合作方A 绝对无法 删除、查询或修改合作方B创建的站点数据。

配置方式(多合作方)

当有多个CMS合作方时,使用 CMS_PARTNERS 环境变量配置(JSON数组格式),每个合作方使用独立的Token和IP白名单:

env
# 主管理员Token(拥有所有权限,谨慎保管)
INTERNAL_TOKEN=your-master-admin-token-32chars

# 多合作方配置(JSON数组)
CMS_PARTNERS=[{"id":"wp-plugin","name":"WordPress插件","token":"wp-plugin-token-xxx","ips":["1.2.3.4","5.6.7.8"]},{"id":"abc-cms","name":"ABC建站系统","token":"abc-cms-token-yyy","ips":["9.10.11.12"]}]

# 主管理员IP白名单(逗号分隔)
ALLOWED_CMS_IPS=100.101.102.103,104.105.106.107

权限隔离规则

角色可访问站点范围说明
主管理员(Master)所有站点使用 INTERNAL_TOKEN,可操作所有站点(包括各合作方创建的)
CMS合作方A仅自己创建的站点使用自己的partner token,只能操作 cms_partner_id = 自己partner_id 的站点
CMS合作方B仅自己创建的站点同上,无法看到A创建的任何站点

示例:A无法删除B的站点

http
# 合作方A使用自己的Token尝试删除B创建的站点 tid_b_001
DELETE /api/internal/site.delete?tid=tid_b_001
Headers:
  X-Internal-Token: abc-cms-token-yyy  (A的Token)
  ↓
Response 404:
{ "ok": false, "error": "Site not found or access denied" }

# 即使tid真实存在,只要不属于当前合作方,就返回"不存在"
# 防止枚举攻击和越权访问

🏷️ 白标与自定义域名说明

两种域名的区别

域名类型适用用户用途权限要求
JS采集域名
(custom_domain)
Pro版及以上用于数据采集脚本(t.js)的自定义域名,避免被AdBlock拦截Pro套餐即可
后台登录域名
(admin_domain)
企业版/授权用户用于登录统计后台的完全自定义域名,实现100%白标企业版或管理员单独授权 can_white_label

权限说明

  • 普通用户/免费版:只能使用你的主域名登录后台,无白标权限
  • Pro版:可以绑定JS采集域名(用于数据上报),但后台登录仍使用你的主域名
  • 企业版/白标授权用户:可以修改品牌名称、Logo、配色、页脚,绑定自己的后台登录域名,用户完全看不到你的品牌
✅ 授权方式:管理员可以在后台用户管理中,单独给任意用户授权白标权限(无需升级到企业版),灵活控制哪些客户可以使用白标功能。

🔒 私有化部署防传播方案

📋 授权机制:私有化部署采用 LICENSE_KEY 域名绑定授权 机制,防止客户未经授权传播源码。

授权验证原理

  1. 每个私有化部署客户获得一个唯一的 LICENSE_KEY
  2. LICENSE_KEY 使用 HMAC-SHA256 签名,包含客户名称、授权域名、有效期、功能权限等信息
  3. 服务启动时验证LICENSE_KEY,检查:
    • 签名是否有效(防止伪造)
    • 当前访问域名是否匹配授权域名
    • 授权是否过期
  4. 非localhost/非授权域名访问时,系统拒绝服务

环境变量配置

env
# SaaS/云模式(你的官方服务):
LICENSE_KEY=cloud

# 私有化部署客户:
LICENSE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkxPRSJ9.eyJsaWNlbnNlZF90byI6IkFCQyBDb21wYW55IiwiZG9tYWluIjoiYW5hbHl0aWNzLmFiYy5jb20iLCJleHAiOjE4OTM0NTYwMDAsImZlYXR1cmVzIjpbIndoaXRlX2xhYmVsIiwiY3VzdG9tX2RvbWFpbnMiLCJhcGkiLCJ1bmxpbWl0ZWRfc2l0ZXMiXX0.signature_here

# 开发模式(仅localhost可用):
LICENSE_KEY=dev

授权密钥生成

LICENSE_KEY 由你(软件供应商)使用私钥生成,分发给客户。生成示例代码:

javascript
import { generateLicenseKey } from './src/lib/license.js';

// 给客户ABC公司生成1年授权,绑定域名 analytics.abc.com
const licenseKey = generateLicenseKey({
  licensed_to: 'ABC Company',
  domain: 'analytics.abc.com',  // 或 '*' 表示不限制域名(不推荐)
  expires_at: Math.floor(Date.now()/1000) + 365*86400, // 1年有效期
  features: ['white_label', 'custom_domains', 'api', 'unlimited_sites'],
  max_sites: -1 // -1表示不限站点数
}, 'your-private-signing-secret-keep-this-safe');

console.log(licenseKey);
⚠️ 安全提示:
  • 签名密钥(signing secret)绝对不能泄露,不能出现在客户的代码中
  • 分发给客户的代码中只保留验证公钥/密钥,不包含生成密钥的代码
  • 建议生产环境使用非对称加密(RSA/ECDSA)替换HMAC,进一步提高安全性
  • 授权域名建议精确绑定,避免使用通配符 *
📞 接入支持:如需开通 Internal API 权限、配置 IP 白名单、获取白标授权、购买企业版或私有化部署 License,请通过 商务合作 页面联系我们。