CMS / SaaS 半内置集成方案
本方案面向自研CMS系统、SaaS建站平台、多租户系统等场景,支持你在自己的产品后台里"白标"集成澄泓统计能力,用户无需离开你的系统即可查看统计数据。
🔒 安全架构说明:Internal API 挂载在
/api/internal/ 前缀下,不对外公开文档,采用 Token + IP白名单 双因素鉴权,公网无法扫描和调用。
集成架构
你的CMS/SaaS系统部署在独立服务器上,通过公网调用澄泓统计的 Cloudflare Worker:
你的CMS/SaaS
→
公网
→
CF Worker
Token校验 + IP白名单双保险
安全配置
环境变量配置
在 Cloudflare Worker 或私有化部署的环境变量中配置以下两项:
| 变量名 | 说明 | 示例 |
|---|---|---|
INTERNAL_TOKEN | 32位随机字符串,CMS调用时通过 X-Internal-Token Header携带 | a1b2c3d4e5f6...32位 |
ALLOWED_CMS_IPS | CMS服务器出口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 域名绑定授权 机制,防止客户未经授权传播源码。
授权验证原理
- 每个私有化部署客户获得一个唯一的
LICENSE_KEY - LICENSE_KEY 使用 HMAC-SHA256 签名,包含客户名称、授权域名、有效期、功能权限等信息
- 服务启动时验证LICENSE_KEY,检查:
- 签名是否有效(防止伪造)
- 当前访问域名是否匹配授权域名
- 授权是否过期
- 非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,请通过 商务合作 页面联系我们。