微信小程序推广系统的核心不是"发链接",而是把渠道、用户、行为和结算串成一条可追踪、可归因、可对账、可风控的链路。工程视角下,它要解决四个问题:渠道怎么区分、行为怎么归因、佣金怎么算准、作弊怎么拦住。本文从这四个层面拆解,并给出可运行骨架。
典型分层:
核心原则:归因规则服务端确定,结算依据可追溯,用户授权可证明。
小程序推广常见的渠道标记方式:
scene=ch_1001,适合线下物料。?ch=1001&cid=abc,适合线上投放。/s/abc123 映射到具体渠道。服务端解析并落库:
import re
import hashlib
from dataclasses import dataclass
CHANNEL_RE = re.compile(r"^[a-z0-9_]{1,32}$")
@dataclass(frozen=True)
class Channel:
channel_id: str
campaign_id: str = ""
source: str = "unknown"
def parse_channel(raw: dict) -> Channel | None:
ch = (raw.get("ch") or "").strip().lower()
if not CHANNEL_RE.match(ch):
return None
cid = (raw.get("cid") or "").strip()[:64]
return Channel(channel_id=ch, campaign_id=cid,
source=raw.get("src", "unknown"))
def device_fingerprint(openid: str, appid: str) -> str:
raw = f"{appid}:{openid}"
return hashlib.sha256(raw.encode()).hexdigest()[:16]要点:渠道 ID 做格式校验,避免注入;设备指纹用哈希,不存原始 openid 明文。
归因是推广系统最容易扯皮的地方。必须提前定义规则:
from datetime import datetime, timedelta
ATTRIBUTION_WINDOW = timedelta(days=7)
def attribute(user_id: str, order_time: datetime,
clicks: list[dict]) -> str | None:
valid = [
c for c in clicks
if order_time - c["click_time"] <= ATTRIBUTION_WINDOW
]
if not valid:
return None
valid.sort(key=lambda c: c["click_time"], reverse=True)
return valid[0]["channel_id"]
def dedup_key(user_id: str, order_id: str) -> str:
return hashlib.sha256(
f"{user_id}:{order_id}".encode()
).hexdigest()[:24]dedup_key 用于结算幂等:同一订单重复上报,只会结算一次。
结算错误是推广系统的重大事故。两条底线:幂等和可对账。
from decimal import Decimal, ROUND_HALF_UP
COMMISSION_RATE = {
"default": Decimal("0.05"),
"vip_channel": Decimal("0.08"),
}
def calc_commission(amount: Decimal, channel_id: str) -> Decimal:
rate = COMMISSION_RATE.get(channel_id, COMMISSION_RATE["default"])
return (amount * rate).quantize(Decimal("0.01"),
rounding=ROUND_HALF_UP)
SETTLED: dict[str, str] = {}
def settle(order_id: str, user_id: str, amount: Decimal,
channel_id: str) -> dict:
key = dedup_key(user_id, order_id)
if key in SETTLED:
return {"status": "duplicate", "settle_id": SETTLED[key]}
commission = calc_commission(amount, channel_id)
if commission <= 0:
return {"status": "rejected", "reason": "zero commission"}
settle_id = f"st_{key}"
SETTLED[key] = settle_id
return {
"status": "ok",
"settle_id": settle_id,
"commission": str(commission),
"channel_id": channel_id,
}生产环境要把 SETTLED 换成数据库唯一索引,并对每日佣金总额做对账:
def reconcile(daily_orders: list[dict],
daily_settlements: list[dict]) -> dict:
order_total = sum(Decimal(o["amount"]) for o in daily_orders)
settle_total = sum(Decimal(s["amount"]) for s in daily_settlements)
diff = order_total - settle_total
return {
"order_total": str(order_total),
"settle_total": str(settle_total),
"diff": str(diff),
"ok": diff == 0,
}对账不平必须告警,不能等到用户提现才发现。
import time
from collections import defaultdict
RATE_LIMIT = defaultdict(list)
def check_rate(user_id: str, action: str,
window: int = 60, limit: int = 10) -> bool:
now = time.time()
key = f"{user_id}:{action}"
RATE_LIMIT[key] = [t for t in RATE_LIMIT[key] if now - t < window]
if len(RATE_LIMIT[key]) >= limit:
return False
RATE_LIMIT[key].append(now)
return True
BLACKLIST = {"device_x", "device_y"}
def risk_check(user_id: str, device_id: str,
amount: Decimal) -> list[str]:
flags = []
if device_id in BLACKLIST:
flags.append("device_blacklisted")
if not check_rate(user_id, "order"):
flags.append("rate_exceeded")
if amount > Decimal("10000"):
flags.append("high_amount")
return flags风控规则要可配置、可回溯。每条拦截都要记录原因,便于申诉和复盘。
维度 | 要求 | 做法 |
|---|---|---|
授权 | 用户同意后才追踪 | 隐私协议 + 授权弹窗 |
脱敏 | 不存 openid 明文 | 哈希后存储 |
留痕 | 归因和结算可追溯 | trace_id + 审计日志 |
审核 | 佣金提现需人工复核 | 大额提现二次确认 |
合规 | 遵守微信运营规范 | 不诱导分享、不虚假宣传 |
安全 | 密钥不入前端 | 环境变量 + 服务端签名 |
import re
PII = re.compile(r"(\d{11}|\d{17}[\dXx]|[\w.+-]+@[\w-]+\.[\w.]+)")
def mask(text: str) -> str:
return PII.sub("[REDACTED]", text)
def audit_promotion(text: str) -> None:
bad = {"最好", "第一", "国家级", "保证收益", "稳赚"}
if any(w in text for w in bad):
raise ValueError("含绝对化或承诺性用语")微信小程序推广系统的工程核心,是把归因和结算做成可审计、可复现、可拦截的链路:
代码可以简单,但归因规则、幂等、对账、风控和合规不能省。推广系统直接涉及资金和用户数据,任何一处疏忽都可能造成资损或法律风险。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。