[fastadmin] 第五十四篇 FastAdmin 插件-电商系统中的SPM码详解:从阿里系到开发页面追踪实战
电商系统中的SPM码详解:从阿里系到FastAdmin的页面追踪实战
什么是SPM?为什么电商系统都在用?作为技术老师,今天就来彻底讲透这个看似神秘但实际很重要的技术概念。
写在前面
上次我们在讲FastAdmin Shopro邀请功能时,有同学问我:”老师,代码里的spm: '3.1.0.1.1'
是什么意思?为什么要传这个参数?”
这个问题问得很好!SPM码虽然看起来只是一串数字,但它背后承载着整个电商系统的数据追踪体系。今天就来和大家深入聊聊这个话题。
SPM是什么?
官方定义
SPM(Super Position Model)超级位置模型,最初由阿里巴巴提出,是一种用于页面模块位置追踪的编码规范。
简单理解:SPM就是页面上每个元素的”身份证号”,通过它可以精确知道用户在什么地方、什么时间、点击了什么内容。
为什么需要SPM?
在我多年的电商项目开发经验中,经常遇到这样的业务需求:
- “这个商品是从哪个页面跳转过来的?”
- “用户更喜欢点击首页的哪个模块?”
- “分享链接的转化效果如何?”
- “不同推广渠道的用户行为有什么差异?”
如果没有一套标准的位置编码体系,这些问题就很难精确回答。SPM正是为了解决这个问题而生。
SPM的编码规范
标准格式
SPM = a.b.c.d.e
每个位置的含义:
- a: 站点标识(Site)
- b: 页面标识(Page)
- c: 页面区块标识(Module)
- d: 页面模块标识(Widget)
- e: 页面元素标识(Element)
实际案例解析
让我们看看淘宝的SPM是如何使用的:
// 淘宝首页商品推荐模块的某个商品
spm = "2114.1.2.1.3"
解读:
├── 2114: 淘宝站点标识
├── 1: 首页标识
├── 2: 商品推荐区块
├── 1: 第一个商品位
└── 3: 商品图片元素
再看看我们Shopro项目中的例子:
// 注册页面的邀请链接
spm = "3.1.0.1.1"
解读:
├── 3: H5/小程序平台
├── 1: 用户相关页面模块
├── 0: 基础功能区块
├── 1: 注册页面
└── 1: 邀请链接位置
FastAdmin Shopro中的SPM实现
代码中的SPM使用
让我们看看Shopro是如何使用SPM的:
// addons/shopro/model/Share.php
public static function log(Object $user, $params)
{
// 验证SPM参数
if (empty($params['spm'])) {
return false;
}
// 根据页面路径生成描述
$memoText = '通过' . (self::FROM)[$params['from']] . '访问了';
if ($params['page'] == '/pages/index/index') {
$memoText .= '首页'; // SPM: 3.0.0.1.1
}
if ($params['page'] === '/pages/goods/index') {
$memoText .= '商品'; // SPM: 3.2.1.1.1
}
if ($params['page'] === '/pages/goods/groupon') {
$memoText .= '拼团商品'; // SPM: 3.2.2.1.1
}
// 保存分享记录
$shareInfo = self::create([
'user_id' => $user->id,
'share_id' => $shareId,
'spm' => $params['spm'], // 关键:SPM码存储
'page' => $params['page'],
// ... 其他字段
]);
}
SPM设计规范建议
基于我的项目经验,建议大家按照以下规范设计SPM:
/**
* SPM编码规范定义类
*
* 设计原则:
* 1. 层级清晰,便于理解
* 2. 预留扩展空间
* 3. 与业务逻辑对应
*/
class SPMConfig
{
// 平台标识
const PLATFORM_H5 = 3;
const PLATFORM_MINI_PROGRAM = 4;
const PLATFORM_APP = 5;
// 页面模块标识
const MODULE_INDEX = 0; // 首页相关
const MODULE_USER = 1; // 用户相关
const MODULE_GOODS = 2; // 商品相关
const MODULE_ORDER = 3; // 订单相关
const MODULE_ACTIVITY = 4; // 活动相关
// 页面标识
const PAGE_INDEX = 1; // 首页
const PAGE_REGISTER = 1; // 注册页(用户模块)
const PAGE_LOGIN = 2; // 登录页(用户模块)
const PAGE_GOODS_LIST = 1; // 商品列表(商品模块)
const PAGE_GOODS_DETAIL = 2; // 商品详情(商品模块)
/**
* 生成SPM码
*/
public static function generate($platform, $module, $block = 0, $page = 1, $element = 1)
{
return implode('.', [$platform, $module, $block, $page, $element]);
}
/**
* 解析SPM码
*/
public static function parse($spm)
{
$parts = explode('.', $spm);
if (count($parts) !== 5) {
return false;
}
return [
'platform' => $parts[0],
'module' => $parts[1],
'block' => $parts[2],
'page' => $parts[3],
'element' => $parts[4]
];
}
}
实际应用示例
/**
* 在控制器中使用SPM
*/
class GoodsController extends Common
{
public function detail()
{
$goods_id = $this->request->param('id');
$goods = Goods::find($goods_id);
// 记录商品访问
$spm = SPMConfig::generate(
SPMConfig::PLATFORM_H5, // H5平台
SPMConfig::MODULE_GOODS, // 商品模块
0, // 基础区块
SPMConfig::PAGE_GOODS_DETAIL, // 商品详情页
1 // 主要内容区域
);
// 记录访问日志
GoodsLog::create([
'goods_id' => $goods_id,
'user_id' => auth_user_id(),
'spm' => $spm,
'referer' => $this->request->server('HTTP_REFERER')
]);
$this->success('', $goods);
}
}
前端SPM的实现
Vue.js中的SPM实现
/**
* SPM工具类
*/
class SPMTracker {
constructor() {
this.currentSPM = '3.0.0.1.1'; // 默认首页SPM
this.platform = this.detectPlatform();
}
/**
* 检测平台类型
*/
detectPlatform() {
const ua = navigator.userAgent;
if (ua.indexOf('MicroMessenger') > -1) {
return ua.indexOf('miniProgram') > -1 ? '4' : '3'; // 小程序或H5
}
return '3'; // 默认H5
}
/**
* 生成SPM码
*/
generateSPM(module, block = 0, page = 1, element = 1) {
return `${this.platform}.${module}.${block}.${page}.${element}`;
}
/**
* 追踪点击事件
*/
trackClick(element, extraData = {}) {
const spm = element.getAttribute('data-spm') || this.currentSPM;
// 发送追踪数据
this.sendTrackingData({
spm: spm,
action: 'click',
timestamp: Date.now(),
url: location.href,
...extraData
});
}
/**
* 发送追踪数据
*/
sendTrackingData(data) {
// 可以发送到后端,也可以先存储到本地
fetch('/api/tracking', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).catch(err => {
// 失败时存储到本地,后续重试
this.storeLocally(data);
});
}
}
// 全局初始化
const spmTracker = new SPMTracker();
// Vue指令方式使用
Vue.directive('spm', {
bind(el, binding) {
const spm = binding.value || spmTracker.currentSPM;
el.setAttribute('data-spm', spm);
el.addEventListener('click', (e) => {
spmTracker.trackClick(e.target, binding.modifiers);
});
}
});
在模板中使用
<template>
<div class="goods-list">
<!-- 商品列表页SPM: 3.2.1.1.1 -->
<div
v-for="(item, index) in goodsList"
:key="item.id"
v-spm="`3.2.1.1.${index + 1}`"
@click="goGoodsDetail(item)"
class="goods-item"
>
<img :src="item.image" v-spm="`3.2.1.1.${index + 1}.1`" />
<div class="goods-info">
<h3 v-spm="`3.2.1.1.${index + 1}.2`">{{ item.name }}</h3>
<span class="price" v-spm="`3.2.1.1.${index + 1}.3`">¥{{ item.price }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
// 设置当前页面SPM
spmTracker.currentSPM = '3.2.1.1.1';
},
methods: {
goGoodsDetail(item) {
// 跳转时携带来源SPM
this.$router.push({
path: '/goods/detail',
query: {
id: item.id,
spm: spmTracker.currentSPM
}
});
}
}
}
</script>
SPM在数据分析中的应用
数据库设计
-- 用户行为追踪表
CREATE TABLE `user_behavior_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`spm` varchar(50) NOT NULL COMMENT 'SPM码',
`action` varchar(20) NOT NULL COMMENT '行为类型:click/view/share',
`page_url` varchar(255) NOT NULL COMMENT '页面URL',
`referer` varchar(255) DEFAULT NULL COMMENT '来源页面',
`platform` tinyint(1) NOT NULL COMMENT '平台:1-H5 2-小程序 3-APP',
`ip` varchar(15) DEFAULT NULL COMMENT 'IP地址',
`user_agent` text COMMENT '用户代理',
`extra_data` json DEFAULT NULL COMMENT '扩展数据',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_spm` (`user_id`, `spm`),
KEY `idx_spm_time` (`spm`, `create_time`)
);
数据分析查询
/**
* SPM数据分析服务
*/
class SPMAnalysisService
{
/**
* 分析页面热点
*/
public function getPageHeatmap($page_spm, $date_range = 7)
{
$start_date = date('Y-m-d H:i:s', strtotime("-{$date_range} days"));
return Db::name('user_behavior_log')
->where('spm', 'like', $page_spm . '%')
->where('create_time', '>=', $start_date)
->where('action', 'click')
->group('spm')
->field('smp, count(*) as click_count')
->order('click_count DESC')
->select();
}
/**
* 分析用户路径
*/
public function getUserPath($user_id, $date = null)
{
$date = $date ?: date('Y-m-d');
return Db::name('user_behavior_log')
->where('user_id', $user_id)
->where('create_time', 'like', $date . '%')
->order('create_time ASC')
->field('spm, page_url, action, create_time')
->select();
}
/**
* 转化率分析
*/
public function getConversionRate($from_spm, $to_spm, $days = 7)
{
// 复杂的转化率分析逻辑
$start_date = date('Y-m-d', strtotime("-{$days} days"));
// 起始页面访问量
$from_count = Db::name('user_behavior_log')
->where('smp', $from_spm)
->where('create_time', '>=', $start_date)
->count();
// 目标页面到达量(同一用户)
$to_count = Db::name('user_behavior_log')
->alias('a')
->join('user_behavior_log b', 'a.user_id = b.user_id')
->where('a.spm', $from_spm)
->where('b.spm', $to_spm)
->where('a.create_time', '>=', $start_date)
->where('b.create_time', '>', 'a.create_time')
->count();
return [
'from_count' => $from_count,
'to_count' => $to_count,
'conversion_rate' => $from_count > 0 ? round($to_count / $from_count * 100, 2) : 0
];
}
}
数据可视化
/**
* SPM数据报表控制器
*/
class SPMReportController extends AdminController
{
/**
* 页面热力图
*/
public function heatmap()
{
$page = $this->request->param('page', 'index');
$days = $this->request->param('days', 7);
$service = new SPMAnalysisService();
$data = $service->getPageHeatmap("3.0.{$page}", $days);
// 转换为前端图表需要的格式
$chartData = [
'categories' => array_column($data, 'spm'),
'series' => [
[
'name' => '点击量',
'data' => array_column($data, 'click_count')
]
]
];
$this->success('', $chartData);
}
/**
* 用户行为路径分析
*/
public function userPath()
{
$user_id = $this->request->param('user_id');
$date = $this->request->param('date', date('Y-m-d'));
$service = new SPMAnalysisService();
$path = $service->getUserPath($user_id, $date);
// 构建路径图数据
$nodes = [];
$links = [];
foreach ($path as $i => $step) {
$nodes[] = [
'id' => $step['smp'],
'name' => $this->getSPMDescription($step['spm']),
'category' => $this->getSPMCategory($step['spm'])
];
if ($i > 0) {
$links[] = [
'source' => $path[$i-1]['spm'],
'target' => $step['spm']
];
}
}
$this->success('', compact('nodes', 'links'));
}
}
最佳实践与注意事项
1. SPM设计原则
分层清晰:每一层都应该有明确的业务含义
// 好的设计
spm = "3.2.1.5.1" // H5.商品.基础.详情页.主图
// 不好的设计
spm = "123.456.789" // 看不出业务含义
预留扩展:为未来的功能扩展预留编码空间
// 预留扩展空间
const MODULE_USER = 1; // 用户模块:1-19
const MODULE_GOODS = 20; // 商品模块:20-39
const MODULE_ORDER = 40; // 订单模块:40-59
2. 性能优化
批量处理:避免每次点击都发送请求
class SPMTracker {
constructor() {
this.buffer = [];
this.bufferSize = 10;
this.flushInterval = 5000; // 5秒
// 定时刷新缓冲区
setInterval(() => this.flush(), this.flushInterval);
}
track(data) {
this.buffer.push({
...data,
timestamp: Date.now()
});
if (this.buffer.length >= this.bufferSize) {
this.flush();
}
}
flush() {
if (this.buffer.length === 0) return;
fetch('/api/tracking/batch', {
method: 'POST',
body: JSON.stringify(this.buffer)
});
this.buffer = [];
}
}
3. 数据清理策略
/**
* SPM数据清理任务
*/
class SPMCleanupTask
{
/**
* 清理历史数据
*/
public function cleanupOldData()
{
// 保留最近3个月的详细数据
$three_months_ago = date('Y-m-d', strtotime('-3 months'));
// 3个月前的数据进行汇总后删除
$this->aggregateAndDelete($three_months_ago);
}
private function aggregateAndDelete($date)
{
// 按天汇总数据
$daily_stats = Db::name('user_behavior_log')
->where('create_time', '<', $date)
->group('DATE(create_time), spm, action')
->field('
DATE(create_time) as date,
spm,
action,
COUNT(*) as total_count,
COUNT(DISTINCT user_id) as unique_users
')
->select();
// 保存到汇总表
Db::name('spm_daily_stats')->insertAll($daily_stats);
// 删除原始数据
Db::name('user_behavior_log')
->where('create_time', '<', $date)
->delete();
}
}
总结与思考
通过今天的深入讲解,相信大家对SPM有了全面的理解。让我总结几个关键点:
1. SPM的核心价值
精确追踪:知道用户在哪里、做了什么
数据驱动:基于用户行为优化产品设计
转化分析:分析用户路径,提升转化率
2. 实施要点
统一规范:团队内部要有统一的SPM编码规范
分层设计:每一层都要有清晰的业务含义
性能考虑:批量处理,避免影响用户体验
3. 常见误区
过度设计:不是所有元素都需要SPM,要有重点
忽视维护:SPM编码需要文档化,便于维护
数据孤岛:要考虑数据的后续分析和应用
4. 扩展思考
SPM只是用户行为追踪的一种方式,在实际项目中,我们还可能需要:
- 埋点SDK:统一的数据收集工具
- 实时分析:基于流处理的实时数据分析
- AB测试:基于SPM进行页面效果对比
- 个性化推荐:基于用户行为数据的智能推荐
作业与实践
最后,给大家留个作业:
- 设计练习:为你的项目设计一套SPM编码规范
- 实现练习:在前端实现一个SPM追踪工具类
- 分析练习:分析某个页面的用户点击热力图
- 思考题:如何在微信小程序中实现SPM追踪?
技术的魅力在于将复杂的业务需求转化为简洁的技术方案。SPM看似简单,但其背后承载的是整个产品的数据化运营体系。希望这篇文章能帮助大家更好地理解和应用SPM技术。
关注我,一起探讨更多电商系统开发实战技巧
本作品采用《CC 协议》,转载必须注明作者和本文链接