一、原生用户标签体系拆解
1.1 原生标签数据模型
LikeShop 的用户标签功能以“手动标签”为核心。标签本身的定义通过 user.user_label 相关接口管理,核心字段包括 name(标签名称)、remark(备注)、label_type(标签类型:0-手动标签,1-自动标签)。标签的创建和编辑通过 /adminapi/user.user_label/edit 接口完成,参数中 label_type 字段用于标识标签类型。https://www.kancloud.cn/yi_xiang_42/likeshopb2cplus/2505241https://www.kancloud.cn/yi_xiang_42/likeshopb2cplus/2505242
用户与标签的关联通过批量设置接口 /adminapi/user.user/setLabel 实现,请求参数为 user_ids(用户ID数组)和 label_ids(标签ID数组),运营人员在用户管理列表中勾选用户后手动打标。https://www.kancloud.cn/yi_xiang_42/likeshopb2cplus/2505246
在数据存储层面,ls_user 表包含 level(用户等级)字段,但用户标签与用户的关联关系存储在一张独立的关联表中(通常命名为 ls_user_label_relation 或类似),通过 user_id 和 label_id 建立多对多关系。用户搜索条件接口 /adminapi/user.user/otherList 返回的 user_label_list 中包含自动标签和手动标签的混合数据,例如 {"4": "手动标签", "5": "自动标签", "7": "高消费人群"}。https://www.kancloud.cn/yi_xiang_42/likeshopb2cplus/2505245
1.2 原生标签体系的四个核心缺口
缺口一:自动打标未落地。虽然 label_type 字段已预留了“1-自动标签”的枚举,且官方文档中也明确标注了标签类型支持自动标签,但原生系统的用户标签功能定位是“根据用户特征、消费行为等要素给用户分配标签,同一用户可打上多个标签,暂时不支持自动标签”。label_type = 1 的标签在当前版本中仅是一个分类标识,没有配套的自动计算引擎。https://juejin.cn/post/7159369051205500936
缺口二:无规则配置能力。原生标签没有任何规则配置字段,无法定义“什么样的用户应该被打上这个标签”。运营人员只能逐个勾选用户手动打标,无法实现“消费金额超过5000元的用户自动打上高消费标签”这样的自动化逻辑。
缺口三:无标签人群定向能力。原生系统支持按标签筛选用户,但标签仅用于“查看”和“手动操作”,没有与优惠券发放、消息推送等营销动作打通。运营人员需要先按标签筛选出用户列表,再逐个或批量手动发券,流程割裂。
缺口四:无标签更新机制。手动标签一旦打上就永久存在,用户状态发生变化后(如从高消费变为沉默用户),标签不会自动更新或移除,导致标签体系逐渐失真。
1.3 二开策略定位
遵循 LikeShop 的“扩展优先”原则,标签体系二开采用以下策略:
- 不修改原生 user.user_label 接口和标签关联表结构,新建独立的自动标签规则表和服务层
- 自动标签通过定时任务驱动计算,复用 LikeShop 已有的定时任务体系(后台可配置、php think crontab 调用)
- 标签人群定向营销通过扩展优惠券发放接口和消息推送服务实现,在原生接口基础上增加“按标签筛选”能力
- 自动标签的规则配置采用“字段 + 运算符 + 值 + 时间窗口”的结构化模型,运营可在后台可视化配置
二、数据库扩展设计
2.1 自动标签规则配置表
这是自动打标的核心配置表,定义每个自动标签的计算规则:
sql
CREATE TABLE `ls_user_label_rule` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`label_id` int UNSIGNED NOT NULL COMMENT '关联的标签ID(ls_user_label.id)',
`rule_name` varchar(50) NOT NULL DEFAULT '' COMMENT '规则名称',
`rule_group` tinyint NOT NULL DEFAULT 1 COMMENT '规则组:同一组内的条件按match_type组合',
`rule_field` varchar(50) NOT NULL COMMENT '规则字段:total_consume/order_count/avg_order_amount/last_order_time/register_days/province/city/level_id/points',
`rule_operator` varchar(20) NOT NULL DEFAULT '>=' COMMENT '运算符:>=,<=,>,<,=,!=,in,not_in,between,days_ago',
`rule_value` varchar(500) NOT NULL DEFAULT '' COMMENT '规则值',
`match_type` tinyint NOT NULL DEFAULT 1 COMMENT '组内匹配方式:1-满足全部条件(AND),2-满足任一条件(OR)',
`auto_remove` tinyint NOT NULL DEFAULT 1 COMMENT '不满足条件时是否自动移除标签:0-否,1-是',
`update_frequency` tinyint NOT NULL DEFAULT 1 COMMENT '更新频率:1-每日,2-每周,3-每月',
`last_calc_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '上次计算时间',
`last_calc_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '上次计算命中人数',
`is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_label_id` (`label_id`),
KEY `idx_enable` (`is_enable`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户自动标签规则配置表';
rule_field 支持的核心字段包括:
| 字段标识 | 含义 | 数据来源 |
|---|---|---|
| total_consume | 累计消费金额 | ls_order 聚合 |
| order_count | 累计订单数 | ls_order 聚合 |
| avg_order_amount | 客单价 | ls_order 聚合 |
| last_order_time | 最后下单时间 | ls_order 聚合 |
| register_days | 注册天数 | ls_user.create_time |
| province / city | 省份 / 城市 | ls_user 地址信息 |
| level_id | 会员等级 | ls_user.level |
| points | 积分余额 | ls_user.user_integral |
2.2 标签计算日志表
记录每次自动标签计算的结果,便于追溯和调试:
sql
CREATE TABLE `ls_user_label_calc_log` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`label_id` int UNSIGNED NOT NULL COMMENT '标签ID',
`rule_id` int UNSIGNED NOT NULL COMMENT '规则ID',
`calc_type` tinyint NOT NULL DEFAULT 1 COMMENT '计算类型:1-全量计算,2-增量计算',
`matched_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '命中人数',
`added_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '新增打标人数',
`removed_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '移除标签人数',
`calc_duration` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '计算耗时(秒)',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-成功,2-失败',
`error_msg` varchar(500) NOT NULL DEFAULT '' COMMENT '错误信息',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_label_id` (`label_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自动标签计算日志表';
三、自动打标规则引擎实现
3.1 规则解析与 SQL 构建
自动打标的核心是将结构化的规则配置翻译为可执行的 SQL 查询。参考业界自动化标签体系的实践,规则引擎负责将标签定义翻译为高效 SQL。https://developer.aliyun.com/article/1736219
php
class LabelRuleEngine
{
/**
* 解析规则并生成SQL查询条件
*/
public function buildQuery($labelId)
{
$rules = UserLabelRuleModel::where('label_id', $labelId)
->where('is_enable', 1)
->order('rule_group', 'asc')
->select();
if ($rules->isEmpty()) {
throw new Exception('标签规则不存在或已禁用');
}
// 按规则组分组
$groups = [];
foreach ($rules as $rule) {
$groups[$rule['rule_group']][] = $rule;
}
$groupQueries = [];
foreach ($groups as $groupRules) {
$matchType = $groupRules[0]['match_type'];
$conditions = [];
foreach ($groupRules as $rule) {
$condition = $this->translateRule($rule);
if ($condition) {
$conditions[] = $condition;
}
}
if (!empty($conditions)) {
$connector = ($matchType == 1) ? ' AND ' : ' OR ';
$groupQueries[] = '(' . implode($connector, $conditions) . ')';
}
}
if (empty($groupQueries)) return null;
// 多个规则组之间默认按 AND 组合
return implode(' AND ', $groupQueries);
}
/**
* 将单条规则翻译为SQL条件片段
*/
private function translateRule($rule)
{
$field = $rule['rule_field'];
$operator = $rule['rule_operator'];
$value = $rule['rule_value'];
// 直接映射到 ls_user 表的字段
$userFields = ['level_id' => 'level', 'points' => 'user_integral',
'register_days' => 'create_time'];
if (isset($userFields[$field])) {
return $this->buildUserFieldCondition($userFields[$field], $operator, $value);
}
// 需要聚合查询的字段(消费行为)
$aggFields = ['total_consume', 'order_count', 'avg_order_amount', 'last_order_time'];
if (in_array($field, $aggFields)) {
return $this->buildAggregateCondition($field, $operator, $value);
}
// 地域字段
if ($field == 'province' || $field == 'city') {
return $this->buildLocationCondition($field, $operator, $value);
}
return null;
}
private function buildAggregateCondition($field, $operator, $value)
{
$subQuery = $this->getAggregateSubQuery($field);
return $this->buildOperatorCondition("({$subQuery})", $operator, $value);
}
/**
* 构建聚合子查询
*/
private function getAggregateSubQuery($field)
{
switch ($field) {
case 'total_consume':
return "SELECT COALESCE(SUM(order_amount),0) FROM ls_order WHERE user_id = ls_user.id AND pay_status = 1 AND refund_status <> 2";
case 'order_count':
return "SELECT COUNT(*) FROM ls_order WHERE user_id = ls_user.id AND pay_status = 1 AND refund_status <> 2";
case 'avg_order_amount':
return "SELECT COALESCE(AVG(order_amount),0) FROM ls_order WHERE user_id = ls_user.id AND pay_status = 1 AND refund_status <> 2";
case 'last_order_time':
return "SELECT COALESCE(MAX(pay_time),0) FROM ls_order WHERE user_id = ls_user.id AND pay_status = 1";
}
return '0';
}
private function buildOperatorCondition($expr, $operator, $value)
{
switch ($operator) {
case '>=': return "{$expr} >= " . floatval($value);
case '<=': return "{$expr} <= " . floatval($value);
case '>': return "{$expr} > " . floatval($value);
case '<': return "{$expr} < " . floatval($value);
case '=': return "{$expr} = " . floatval($value);
case '!=': return "{$expr} != " . floatval($value);
case 'days_ago':
$threshold = time() - (int)$value * 86400;
return "{$expr} >= {$threshold}";
case 'in':
$items = array_map('intval', explode(',', $value));
return "{$expr} IN (" . implode(',', $items) . ")";
default: return null;
}
}
}
3.2 消费行为自动打标
消费行为打标是自动标签体系的核心场景。常见规则包括:
| 标签名称 | 规则条件 | 自动移除 |
|---|---|---|
| 高消费人群 | 累计消费 ≥ 5000 元 | 是 |
| 高频购买用户 | 订单数 ≥ 10 单且近 30 天有下单 | 是 |
| 客单价高端 | 客单价 ≥ 300 元 | 是 |
| 沉默用户 | 最后下单时间 ≥ 90 天前 | 是 |
| 新客 | 注册天数 ≤ 7 天 | 是 |
消费行为的打标计算通过聚合查询实现。以“高消费人群”为例,规则配置为 rule_field = 'total_consume'、rule_operator = '>='、rule_value = '5000',生成的 SQL 条件为:
sql
(SELECT COALESCE(SUM(order_amount),0)
FROM ls_order
WHERE user_id = ls_user.id AND pay_status = 1 AND refund_status <> 2
) >= 5000
3.3 地域自动打标
地域标签基于用户的收货地址省份或城市进行打标。需要在 ls_user 表中关联用户的默认收货地址,或者通过 ls_user_address 表获取用户的常用地址:
php
private function buildLocationCondition($field, $operator, $value)
{
// 从用户默认地址中获取省市信息
$addrField = ($field == 'province') ? 'province' : 'city';
$subQuery = "SELECT COALESCE((SELECT {$addrField} FROM ls_user_address
WHERE user_id = ls_user.id AND is_default = 1 LIMIT 1), '')";
if ($operator == 'in') {
$items = array_map(function($v) { return "'" . addslashes(trim($v)) . "'"; },
explode(',', $value));
return "{$subQuery} IN (" . implode(',', $items) . ")";
}
return "{$subQuery} = '" . addslashes($value) . "'";
}
3.4 会员等级自动打标
会员等级标签直接基于 ls_user.level 字段进行打标,计算逻辑最简单:
php
// 规则配置:rule_field = 'level_id', rule_operator = 'in', rule_value = '2,3'
// 生成的SQL条件:ls_user.level IN (2,3)
3.5 定时任务驱动打标
自动标签的计算通过定时任务驱动,复用 LikeShop 已有的定时任务体系。LikeShop 的定时任务可以在管理后台配置和开关,设置路径是「系统设置 → 系统维护 → 定时任务」,后台提供了完整的定时任务 CRUD 接口,包括添加任务(/crontab.crontab/add)。
在 Crontab.php 中新增自动标签计算子任务:
php
class AutoLabelCrontab
{
/**
* 自动标签计算任务(每日凌晨执行)
*/
public function calcAutoLabels()
{
$today = date('Y-m-d');
$now = time();
// 查询所有启用的自动标签规则
$rules = UserLabelRuleModel::where('is_enable', 1)
->group('label_id')
->select();
foreach ($rules as $rule) {
$labelId = $rule['label_id'];
// 判断是否到了该标签的更新周期
$frequency = $rule['update_frequency'];
$lastCalcTime = $rule['last_calc_time'];
if (!$this->shouldCalc($frequency, $lastCalcTime, $now)) {
continue;
}
$startTime = microtime(true);
try {
// 1. 构建规则SQL
$engine = new LabelRuleEngine();
$whereCondition = $engine->buildQuery($labelId);
if (!$whereCondition) {
continue;
}
// 2. 查询命中用户
$matchedUsers = Db::name('user')
->alias('u')
->whereRaw($whereCondition)
->where('u.status', 1)
->column('u.id');
$matchedCount = count($matchedUsers);
// 3. 查询当前已打标用户
$existingUsers = Db::name('user_label_relation')
->where('label_id', $labelId)
->column('user_id');
// 4. 计算新增和移除
$toAdd = array_diff($matchedUsers, $existingUsers);
$toRemove = array_diff($existingUsers, $matchedUsers);
// 5. 执行打标和移除
Db::startTrans();
try {
// 批量新增
if (!empty($toAdd)) {
$insertData = [];
foreach ($toAdd as $userId) {
$insertData[] = [
'user_id' => $userId,
'label_id' => $labelId,
'create_time' => $now,
];
}
Db::name('user_label_relation')->insertAll($insertData);
}
// 自动移除(仅当 auto_remove = 1)
if ($rule['auto_remove'] == 1 && !empty($toRemove)) {
Db::name('user_label_relation')
->where('label_id', $labelId)
->whereIn('user_id', $toRemove)
->delete();
}
// 更新规则的最后计算时间
UserLabelRuleModel::where('label_id', $labelId)
->update([
'last_calc_time' => $now,
'last_calc_count' => $matchedCount,
]);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
$duration = round(microtime(true) - $startTime, 2);
// 6. 记录计算日志
UserLabelCalcLogModel::create([
'label_id' => $labelId,
'rule_id' => $rule['id'],
'calc_type' => 1,
'matched_count' => $matchedCount,
'added_count' => count($toAdd),
'removed_count' => $rule['auto_remove'] ? count($toRemove) : 0,
'calc_duration' => $duration,
'status' => 1,
'create_time' => $now,
]);
} catch (\Exception $e) {
UserLabelCalcLogModel::create([
'label_id' => $labelId,
'rule_id' => $rule['id'],
'status' => 2,
'error_msg' => mb_substr($e->getMessage(), 0, 500),
'create_time' => $now,
]);
Log::error("自动标签计算失败 label_id={$labelId}: " . $e->getMessage());
}
}
}
private function shouldCalc($frequency, $lastCalcTime, $now)
{
if ($lastCalcTime == 0) return true;
$lastDate = date('Y-m-d', $lastCalcTime);
$today = date('Y-m-d');
switch ($frequency) {
case 1: return $lastDate != $today; // 每日
case 2: return (time() - $lastCalcTime) >= 7 * 86400; // 每周
case 3: return (time() - $lastCalcTime) >= 30 * 86400; // 每月
default: return true;
}
}
}
3.6 后台配置
在“用户管理 → 用户标签 → 自动标签规则”中增加规则配置页面:
| 配置项 | 类型 | 说明 |
|---|---|---|
| 关联标签 | 下拉选择 | 从已有标签中选择,仅显示 label_type=1 的标签 |
| 规则字段 | 下拉选择 | 累计消费/订单数/客单价/最后下单时间/注册天数/省份/城市/会员等级/积分 |
| 运算符 | 下拉选择 | ≥、≤、>、<、=、≠、包含于、N天内 |
| 规则值 | 文本/数字输入 | 根据字段类型动态调整输入控件 |
| 匹配方式 | 单选 | 满足全部条件 / 满足任一条件 |
| 自动移除 | 开关 | 不满足条件时是否移除标签 |
| 更新频率 | 单选 | 每日/每周/每月 |
| 启用状态 | 开关 | 是否启用该规则 |
四、标签人群定向营销落地
4.1 按标签筛选目标人群
定向营销的第一步是根据标签筛选目标用户。新增标签人群筛选接口,支持多标签组合筛选(同时拥有/拥有任一):
php
class LabelAudienceService
{
/**
* 按标签筛选用户
* @param array $labelIds 标签ID数组
* @param string $matchType 匹配方式:all-同时拥有所有标签,any-拥有任一标签
* @param int $limit 最大返回数量
*/
public function getUsersByLabels($labelIds, $matchType = 'all', $limit = 0)
{
if (empty($labelIds)) {
throw new Exception('请至少选择一个标签');
}
$query = Db::name('user_label_relation')
->alias('r')
->join('user u', 'r.user_id = u.id')
->whereIn('r.label_id', $labelIds)
->where('u.status', 1);
if ($matchType == 'all') {
// 同时拥有所有标签
$query->group('r.user_id')
->having('COUNT(DISTINCT r.label_id) = ' . count($labelIds));
} else {
// 拥有任一标签
$query->group('r.user_id');
}
$userIds = $query->column('r.user_id');
if ($limit > 0) {
$userIds = array_slice($userIds, 0, $limit);
}
return $userIds;
}
/**
* 预览标签人群数量和分布
*/
public function previewAudience($labelIds, $matchType = 'all')
{
$userIds = $this->getUsersByLabels($labelIds, $matchType);
// 统计人群的等级分布
$levelDist = Db::name('user')
->whereIn('id', $userIds)
->group('level')
->field('level, COUNT(*) as cnt')
->select();
// 统计人群的消费总额分布
$consumeDist = Db::name('order')
->whereIn('user_id', $userIds)
->where('pay_status', 1)
->group('user_id')
->field('SUM(order_amount) as total')
->select();
return [
'total_count' => count($userIds),
'level_dist' => $levelDist,
'avg_consume' => count($consumeDist) > 0
? round(array_sum(array_column($consumeDist, 'total')) / count($consumeDist), 2) : 0,
];
}
}
4.2 标签人群定向发券
原生优惠券发放接口 marketing.coupon/send 支持传入 send_user(用户ID数组)。在原生接口基础上封装“按标签发券”的方法:
php
class LabelCouponService
{
/**
* 按标签人群定向发券
*/
public function sendByLabel($couponId, $labelIds, $matchType = 'all', $sendNum = 1)
{
// 1. 查询目标人群
$audienceService = new LabelAudienceService();
$userIds = $audienceService->getUsersByLabels($labelIds, $matchType);
if (empty($userIds)) {
throw new Exception('该标签组合下没有匹配的用户');
}
// 2. 过滤已达领取上限的用户
$coupon = CouponModel::find($couponId);
if (!$coupon) {
throw new Exception('优惠券不存在');
}
$userIds = $this->filterReachedLimitUsers($couponId, $userIds, $coupon);
if (empty($userIds)) {
return ['success_count' => 0, 'msg' => '所有用户均已达到该券的领取上限'];
}
// 3. 分批发放(每批500人)
$totalSuccess = 0;
foreach (array_chunk($userIds, 500) as $batch) {
$result = CouponService::batchSend($couponId, $batch, $sendNum);
$totalSuccess += $result['success_count'];
}
// 4. 记录发放日志
LabelMarketingLogModel::create([
'label_ids' => implode(',', $labelIds),
'match_type' => $matchType,
'coupon_id' => $couponId,
'target_count' => count($userIds),
'success_count' => $totalSuccess,
'create_time' => time(),
]);
return [
'target_count' => count($userIds),
'success_count' => $totalSuccess,
];
}
private function filterReachedLimitUsers($couponId, $userIds, $coupon)
{
if ($coupon['get_num_type'] == 1) return $userIds; // 不限次数
// 查询已领取数量
$receivedCounts = Db::name('coupon_record')
->where('coupon_id', $couponId)
->whereIn('user_id', $userIds)
->group('user_id')
->column('COUNT(*) as cnt', 'user_id');
return array_filter($userIds, function($userId) use ($receivedCounts, $coupon) {
$count = $receivedCounts[$userId] ?? 0;
return $count < $coupon['get_num'];
});
}
}
4.3 标签人群定向消息推送
定向推送支持站内消息、短信、微信模板消息三种渠道,根据标签人群的规模选择推送策略:
php
class LabelPushService
{
/**
* 按标签人群推送消息
*/
public function pushByLabel($labelIds, $matchType, $pushData, $channel = 'message')
{
$audienceService = new LabelAudienceService();
$userIds = $audienceService->getUsersByLabels($labelIds, $matchType);
if (empty($userIds)) {
throw new Exception('目标人群为空');
}
// 大规规模人群走队列异步推送
if (count($userIds) > 100) {
Queue::push(BatchPushJob::class, [
'user_ids' => $userIds,
'push_data' => $pushData,
'channel' => $channel,
]);
return ['target_count' => count($userIds), 'status' => 'queued'];
}
// 小规模直接推送
$successCount = 0;
foreach ($userIds as $userId) {
try {
switch ($channel) {
case 'message':
MessageService::send($userId, $pushData['title'], $pushData['content']);
break;
case 'sms':
$mobile = UserModel::where('id', $userId)->value('mobile');
if ($mobile) {
SmsService::send($mobile, $pushData['content']);
}
break;
case 'wechat':
WxTemplateService::send($userId, $pushData['template_id'], $pushData['data']);
break;
}
$successCount++;
} catch (\Exception $e) {
Log::error("标签定向推送失败 user_id={$userId}: " . $e->getMessage());
}
}
return ['target_count' => count($userIds), 'success_count' => $successCount];
}
}
4.4 后台配置
在“营销 → 定向营销”中增加标签人群定向管理页面:
| 功能 | 说明 |
|---|---|
| 人群预览 | 选择标签组合后,实时预览匹配人数、等级分布、平均消费 |
| 定向发券 | 选择优惠券和目标标签,一键批量发放 |
| 定向推送 | 选择推送渠道和目标标签,编辑推送内容后发送 |
| 营销记录 | 查看历史定向营销记录(发券/推送的时间、目标人群、成功数) |
五、完整开发步骤总结
5.1 数据库变更清单
| 操作 | 表名 | 说明 |
|---|---|---|
| CREATE | ls_user_label_rule | 自动标签规则配置表 |
| CREATE | ls_user_label_calc_log | 自动标签计算日志表 |
| CREATE | ls_label_marketing_log | 标签定向营销记录表 |
5.2 后端开发清单
| 模块 | 文件/类 | 工作内容 |
|---|---|---|
| 数据层 | UserLabelRuleModel | 规则配置 Model |
| 数据层 | UserLabelCalcLogModel | 计算日志 Model |
| 数据层 | LabelMarketingLogModel | 营销记录 Model |
| 业务层 | LabelRuleEngine | 规则解析与 SQL 构建 |
| 业务层 | LabelAudienceService | 标签人群筛选与预览 |
| 业务层 | LabelCouponService | 按标签定向发券 |
| 业务层 | LabelPushService | 按标签定向推送 |
| 命令 | AutoLabelCrontab | 自动标签计算定时任务 |
| 后台接口 | UserLabelRuleController | 规则 CRUD、手动触发计算 |
| 后台接口 | LabelMarketingController | 人群预览、定向发券、定向推送 |
5.3 后台前端开发清单
| 页面 | 工作内容 |
|---|---|
| 用户标签 → 自动标签规则 | 规则列表、新建规则(字段+运算符+值)、启用/禁用、手动触发计算 |
| 用户标签 → 标签详情 | 展示标签下的用户数、计算日志、命中人数趋势 |
| 营销 → 定向营销 | 标签人群预览、定向发券、定向推送、营销记录 |
| 系统设置 → 定时任务 | 新增“自动标签计算”任务记录 |
5.4 开发注意事项
标签计算性能:聚合子查询(如 SUM(order_amount))在大数据量下性能较差。建议为 ls_order 表的 user_id + pay_status 建立联合索引,并考虑将用户的消费统计数据预聚合到 ls_user 表中(如新增 total_consume、order_count 等冗余字段),定时任务定期刷新,标签计算直接读取冗余字段而非实时聚合。
自动移除的边界:auto_remove = 1 时,不满足条件的用户会被移除标签。但需注意:如果用户同时拥有手动标签和自动标签,自动规则的移除操作只应移除自动标签的关联,不应影响手动标签。在关联表中增加 source 字段(1-手动,2-自动)来区分标签来源。
规则冲突处理:同一用户可能同时满足“高消费人群”和“沉默用户”两个标签的规则。标签之间不应互斥,用户可以同时拥有多个标签。但在定向营销时,运营需要理解标签的语义,避免向沉默用户推荐高频购买类内容。
兼容性:新增表均为独立表,不修改原生 user.user_label 相关接口和表结构。标签关联表的 source 字段新增时设置默认值 1(手动),保证存量数据兼容。自动标签计算任务作为定时任务的新增子任务接入,不影响已有的订单超时关闭、退款扫描等任务。