跳转到内容

案例 28:用户画像标签动态分层

本案例对应 JQuick-Java 仓库 USE-CASES 场景 28「用户画像标签动态分层」,属于电商用户运营领域。系统对用户近 90 天行为数据做两条并行分层:按浏览与最近成交做活跃度分层(ACTIVE/WARM/COLD/DORMANT),按四个价值维度评分汇总为 0-75 的价值分并划分 HIGH/MID/LOW/NO_VALUE,再结合风险与身份标签装配差异化营销策略。

电商运营每天要对全站用户做精细化触达:谁该由专属客户经理一对一维护、谁该发大额召回券防流失、谁只适合低价爆品拉首单、谁应进入静默池不再打扰。人工分层既跟不上用户行为变化,也无法保证口径统一,需要一套按最新行为数据每日重算的动态分层机制。

活跃度分层由一个规则方法完成,综合「近 30 天有浏览的天数」与「最近一次成交距今天数」两个信号逐级覆盖:浏览 ≥15 天且 7 天内成交为 ACTIVE;浏览 ≥8 天或 30 天内成交为 WARM;浏览 ≥3 天或 90 天内成交为 COLD;长期无行为为 DORMANT。

价值侧四个维度独立评分:90 天成交金额(0-30)、90 天订单数(0-20)、会员等级(0-15)、历史平均客单价(0-10),合计 0-75。Service 按 55/35/15 三条线映射 HIGH_VALUE、MID_VALUE、LOW_VALUE、NO_VALUE。此外 Java 侧补充动态标签:优惠券敏感度 ≥60 打 PRICE_SENSITIVE,退货率 ≥30 打 HIGH_RETURN_RISK,活跃且高价值打 VIP_CULTIVATION,沉睡的高等级会员(rank ≥4)打 WINBACK。

决策输出物为 UserSegmentDecision:活跃分层、价值分层、价值评分、标签集合与最终营销策略文案。策略装配有明确短路优先级:高退货风险用户最优先,执行「审慎营销:仅做内容种草,收紧退货险与运费险权益」;其后依次是高价值活跃 VIP 一对一维护、高价值非活跃大额召回券、中价值活跃/温客满减加购券、中价值品牌内容触达、低价值活跃/温客签到促首单,兜底进入静默池低频触达。

  • 分层阈值随经营节奏频繁调整(大促前后对 ACTIVE、HIGH_VALUE 的划线不同),分数线硬编码在画像任务里,调整一次就要改代码重发布。
  • 四个价值维度权重需要独立调参与 A/B 实验,GMV、订单数、会员等级、客单价耦合在同一段评分逻辑中难以单独灰度。
  • 活跃分层与价值分层是两条正交口径,还要叠加券敏感、高退货率等风险标签,纯写死的分支组合会随标签数量指数级膨胀。
  • 营销策略有严格的业务优先级(如高退货风险必须压过 VIP 礼遇),缺乏短路机制时容易给退货惯常用户发大额券造成资损。
  • 分层结果要能回答「这个用户为什么是高价值、为什么被静默」,黑盒画像无法向运营与商家审计回溯。
  • 老会员与营销中台多运行在 JDK 8、信创 JDK 上,难以引入需要独立计算集群与重型控制台的画像规则平台。

本案例采用「入参模型 + 决策模型 + 规则接口 + XML 规则脚本 + Service 编排 + JUnit 测试」六件套结构:活跃分层与四维评分全部以接口方法声明、阈值外置 XML,Service 负责分数汇总、分层映射、标签收集与策略短路装配。

层次 职责 对应文件
领域模型层 承载用户 90 天行为要素,输出双分层、价值分、标签与营销策略 UserBehavior.java、UserSegmentDecision.java
规则接口层 声明活跃分层与四个评分方法,@Param 按名绑定 UserSegmentRules.java
规则脚本层 活跃分档与四维分值阈值外置,支持热更新 user-segment.xml
编排与测试层 分值汇总、55/35/15 分层、标签收集、策略优先级装配;6 组用户形态回归 UserSegmentService.java、UserSegmentServiceTest.java
规则方法 口径 分值/输出
activitySegment 浏览 ≥15 天且 7 天内成交=ACTIVE;浏览 ≥8 天或 30 天内成交=WARM;浏览 ≥3 天或 90 天内成交=COLD;否则 DORMANT(逐级覆盖) ACTIVE/WARM/COLD/DORMANT
scoreGmv 90 天 GMV ≥5000 得 30、≥2000 得 22、≥800 得 14、≥300 得 7,否则 0 0-30
scoreOrders 90 天订单数 ≥12 得 20、≥6 得 15、≥3 得 9、≥1 得 4,否则 0 0-20
scoreMember 会员等级 S5=15、S4=12、S3=8、S2=4、S1 为 0 0-15
scoreAvgPrice 平均客单价 ≥1000 得 10、≥500 得 7、≥200 得 4,否则 0 0-10

将下列文件按原始包名放入 JQuick-Java 测试工程:两个模型放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口与测试类放入 src/test/java/com/github/paohaijiao/demo/,Service 放入 src/test/java/com/github/paohaijiao/demo/service/,XML 脚本放入 src/test/resources/demo/。在工程根目录执行 mvn test -Dtest=UserSegmentServiceTest 即可运行。

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户近 90 天行为与价值要素
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserBehavior {
/**
* 用户号
*/
private String userId;
/**
* 最近一次成交距今天数
*/
private int lastOrderDays;
/**
* 90 天成交订单数
*/
private int orderCount90d;
/**
* 90 天成交金额(元)
*/
private double gmv90d;
/**
* 历史平均客单价(元)
*/
private double avgOrderAmount;
/**
* 近 30 天有浏览行为的天数
*/
private int browseDays30d;
/**
* 优惠券订单占比(整数百分比)
*/
private int couponSensitivity;
/**
* 退货率(整数百分比)
*/
private int returnRate;
/**
* 会员等级序位(S1=1 ... S5=5)
*/
private int memberRank;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 用户画像分层与营销策略决策
*/
@Data
public class UserSegmentDecision {
/**
* 用户号
*/
private String userId;
/**
* 活跃分层:ACTIVE 活跃 / WARM 温客 / COLD 冷客 / DORMANT 沉睡
*/
private String activitySegment;
/**
* 价值分层:HIGH_VALUE 高价值 / MID_VALUE 中价值 / LOW_VALUE 低价值 / NO_VALUE 无价值
*/
private String valueSegment;
/**
* 价值评分(0-75)
*/
private int valueScore;
/**
* 动态营销标签
*/
private List<String> tags = new ArrayList<String>();
/**
* 营销策略
*/
private String marketingStrategy;
public void addTag(String tag) {
this.tags.add(tag);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 28:用户活跃度分层与价值评分规则
*/
public interface UserSegmentRules {
/**
* 活跃度分层:按浏览天数与最近购买日逐级覆盖
*/
String activitySegment(@Param("browseDays") int browseDays,
@Param("lastOrderDays") int lastOrderDays);
/**
* 消费金额得分(0-30)
*/
int scoreGmv(@Param("gmv") double gmv);
/**
* 订单数得分(0-20)
*/
int scoreOrders(@Param("orders") int orders);
/**
* 会员等级得分(0-15)
*/
int scoreMember(@Param("rank") int rank);
/**
* 客单价得分(0-10)
*/
int scoreAvgPrice(@Param("avgAmount") double avgAmount);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-java.dtd">
<javas namespace="com.github.paohaijiao.demo.UserSegmentRules">
<java name="activitySegment" returnClass="java.lang.String">
<![CDATA[
str def activitySegment(int:browseDays, int:lastOrderDays) {
if (browseDays >= 15) {
if (lastOrderDays <= 7) {
return "ACTIVE";
} else {
if (browseDays >= 8 || lastOrderDays <= 30) {
return "WARM";
} else {
if (browseDays >= 3 || lastOrderDays <= 90) {
return "COLD";
} else {
return "DORMANT";
}
}
}
} else if (browseDays >= 8 || lastOrderDays <= 30) {
return "WARM";
} else if (browseDays >= 3 || lastOrderDays <= 90) {
return "COLD";
} else {
return "DORMANT";
}
}
]]>
</java>
<java name="scoreGmv" returnClass="int">
<![CDATA[
int def scoreGmv(double:gmv) {
if (gmv >= 5000) {
return 30;
} else if (gmv >= 2000) {
return 22;
} else if (gmv >= 800) {
return 14;
} else if (gmv >= 300) {
return 7;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreOrders" returnClass="int">
<![CDATA[
int def scoreOrders(int:orders) {
if (orders >= 12) {
return 20;
} else if (orders >= 6) {
return 15;
} else if (orders >= 3) {
return 9;
} else if (orders >= 1) {
return 4;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreMember" returnClass="int">
<![CDATA[
int def scoreMember(int:rank) {
if (rank >= 5) {
return 15;
} else if (rank == 4) {
return 12;
} else if (rank == 3) {
return 8;
} else if (rank == 2) {
return 4;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreAvgPrice" returnClass="int">
<![CDATA[
int def scoreAvgPrice(double:avgAmount) {
if (avgAmount >= 1000) {
return 10;
} else if (avgAmount >= 500) {
return 7;
} else if (avgAmount >= 200) {
return 4;
} else {
return 0;
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.UserSegmentRules;
import com.github.paohaijiao.demo.model.UserBehavior;
import com.github.paohaijiao.demo.model.UserSegmentDecision;
/**
* 场景 28:用户画像动态分层与营销策略装配
*/
public class UserSegmentService {
private final UserSegmentRules rules;
public UserSegmentService(UserSegmentRules rules) {
this.rules = rules;
}
public UserSegmentDecision segment(UserBehavior user) {
UserSegmentDecision d = new UserSegmentDecision();
d.setUserId(user.getUserId());
String activity = rules.activitySegment(user.getBrowseDays30d(), user.getLastOrderDays());
d.setActivitySegment(activity);
int valueScore = rules.scoreGmv(user.getGmv90d())
+ rules.scoreOrders(user.getOrderCount90d())
+ rules.scoreMember(user.getMemberRank())
+ rules.scoreAvgPrice(user.getAvgOrderAmount());
d.setValueScore(valueScore);
String value;
if (valueScore >= 55) {
value = "HIGH_VALUE";
} else if (valueScore >= 35) {
value = "MID_VALUE";
} else if (valueScore >= 15) {
value = "LOW_VALUE";
} else {
value = "NO_VALUE";
}
d.setValueSegment(value);
collectTags(d, user, activity, value);
d.setMarketingStrategy(buildStrategy(activity, value, d.getTags()));
return d;
}
private void collectTags(UserSegmentDecision d, UserBehavior user,
String activity, String value) {
if (user.getCouponSensitivity() >= 60) {
d.addTag("PRICE_SENSITIVE");
}
if (user.getReturnRate() >= 30) {
d.addTag("HIGH_RETURN_RISK");
}
if ("ACTIVE".equals(activity) && "HIGH_VALUE".equals(value)) {
d.addTag("VIP_CULTIVATION");
}
if ("DORMANT".equals(activity) && user.getMemberRank() >= 4) {
d.addTag("WINBACK");
}
}
private String buildStrategy(String activity, String value, java.util.List<String> tags) {
if (tags.contains("HIGH_RETURN_RISK")) {
return "审慎营销:仅做内容种草,收紧退货险与运费险权益";
}
if ("HIGH_VALUE".equals(value) && "ACTIVE".equals(activity)) {
return "专属客户经理一对一维护,发放 VIP 专享权益";
}
if ("HIGH_VALUE".equals(value)) {
return "EDM/短信推送大额召回券,安排客服一对一唤醒";
}
if ("MID_VALUE".equals(value)
&& ("ACTIVE".equals(activity) || "WARM".equals(activity))) {
return "推送满减加购券与凑单活动,提升客单价";
}
if ("MID_VALUE".equals(value)) {
return "EDM 推送品牌内容与爆品清单,保持品牌触达";
}
if ("LOW_VALUE".equals(value)
&& ("ACTIVE".equals(activity) || "WARM".equals(activity))) {
return "签到领积分与低价爆品推送,促进首单与复购";
}
return "进入静默池,仅大促节点低频触达,不做主动打扰";
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.UserBehavior;
import com.github.paohaijiao.demo.model.UserSegmentDecision;
import com.github.paohaijiao.demo.service.UserSegmentService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 28:用户画像标签动态分层
* <p>
* 覆盖 6 组用户形态:高价值活跃 VIP、高价值温客召回、中价值价格敏感、
* 无价值沉睡静默、活跃高退货审慎营销、高价值冷客 EDM 唤醒(55 分边界)。
*/
public class UserSegmentServiceTest {
private UserSegmentService service;
@Before
public void setUp() {
UserSegmentRules rules = JQuickJava.create()
.createApi(UserSegmentRules.class, "demo/user-segment.xml");
this.service = new UserSegmentService(rules);
}
/**
* 全维度头部用户 → ACTIVE / HIGH_VALUE(75) / VIP 培育
*/
@Test
public void shouldSegmentActiveHighValueVip() {
UserBehavior u = base("U001");
u.setBrowseDays30d(22);
u.setLastOrderDays(3);
u.setOrderCount90d(15);
u.setGmv90d(6000);
u.setAvgOrderAmount(1200);
u.setMemberRank(5);
u.setCouponSensitivity(30);
u.setReturnRate(5);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("ACTIVE", d.getActivitySegment());
Assert.assertEquals(75, d.getValueScore());
Assert.assertEquals("HIGH_VALUE", d.getValueSegment());
Assert.assertTrue(d.getTags().contains("VIP_CULTIVATION"));
Assert.assertTrue(d.getMarketingStrategy().contains("专属客户经理"));
}
/**
* 高价值但浏览活跃转温 → 大额召回券防流失
*/
@Test
public void shouldRecallHighValueWarmUser() {
UserBehavior u = base("U002");
u.setBrowseDays30d(10);
u.setLastOrderDays(25);
u.setOrderCount90d(8);
u.setGmv90d(5200);
u.setAvgOrderAmount(1100);
u.setMemberRank(4);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("WARM", d.getActivitySegment());
Assert.assertEquals(67, d.getValueScore());
Assert.assertEquals("HIGH_VALUE", d.getValueSegment());
Assert.assertTrue(d.getMarketingStrategy().contains("大额召回券"));
}
/**
* 价值分恰好 35 的温客且券敏感 → 中价值,满减加购券
*/
@Test
public void shouldSegmentPriceSensitiveMidValue() {
UserBehavior u = base("U003");
u.setBrowseDays30d(12);
u.setLastOrderDays(5);
u.setOrderCount90d(4);
u.setGmv90d(900);
u.setAvgOrderAmount(300);
u.setMemberRank(3);
u.setCouponSensitivity(70);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("WARM", d.getActivitySegment());
Assert.assertEquals(35, d.getValueScore());
Assert.assertEquals("MID_VALUE", d.getValueSegment());
Assert.assertTrue(d.getTags().contains("PRICE_SENSITIVE"));
Assert.assertTrue(d.getMarketingStrategy().contains("满减加购券"));
}
/**
* 长期无行为低等级用户 → DORMANT / NO_VALUE,静默池
*/
@Test
public void shouldSilenceDormantNoValueUser() {
UserBehavior u = base("U004");
u.setBrowseDays30d(1);
u.setLastOrderDays(150);
u.setOrderCount90d(0);
u.setGmv90d(0);
u.setAvgOrderAmount(0);
u.setMemberRank(1);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("DORMANT", d.getActivitySegment());
Assert.assertEquals(0, d.getValueScore());
Assert.assertEquals("NO_VALUE", d.getValueSegment());
Assert.assertEquals(0, d.getTags().size());
Assert.assertTrue(d.getMarketingStrategy().contains("静默池"));
}
/**
* 活跃中价值但退货率 35% → 高退货风险,审慎营销优先于活跃策略
*/
@Test
public void shouldApplyCautiousStrategyForHighReturnRisk() {
UserBehavior u = base("U005");
u.setBrowseDays30d(20);
u.setLastOrderDays(2);
u.setOrderCount90d(10);
u.setGmv90d(2200);
u.setAvgOrderAmount(400);
u.setMemberRank(3);
u.setReturnRate(35);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("ACTIVE", d.getActivitySegment());
Assert.assertEquals(49, d.getValueScore());
Assert.assertEquals("MID_VALUE", d.getValueSegment());
Assert.assertTrue(d.getTags().contains("HIGH_RETURN_RISK"));
Assert.assertTrue(d.getMarketingStrategy().contains("审慎营销"));
}
/**
* 价值分恰好 55 的冷客(高等级)→ HIGH_VALUE + EDM 唤醒,不打 VIP 标签
*/
@Test
public void shouldWakeHighValueColdUserAtThreshold() {
UserBehavior u = base("U006");
u.setBrowseDays30d(4);
u.setLastOrderDays(60);
u.setOrderCount90d(3);
u.setGmv90d(5000);
u.setAvgOrderAmount(200);
u.setMemberRank(4);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("COLD", d.getActivitySegment());
Assert.assertEquals(55, d.getValueScore());
Assert.assertEquals("HIGH_VALUE", d.getValueSegment());
Assert.assertFalse(d.getTags().contains("VIP_CULTIVATION"));
Assert.assertFalse(d.getTags().contains("WINBACK"));
Assert.assertTrue(d.getMarketingStrategy().contains("大额召回券"));
}
private UserBehavior base(String id) {
UserBehavior u = new UserBehavior();
u.setUserId(id);
u.setLastOrderDays(999);
u.setOrderCount90d(0);
u.setGmv90d(0);
u.setAvgOrderAmount(0);
u.setBrowseDays30d(0);
u.setCouponSensitivity(0);
u.setReturnRate(0);
u.setMemberRank(1);
return u;
}
}

在工程根目录执行:

Terminal window
mvn test -Dtest=UserSegmentServiceTest

测试覆盖形态(共 6 个 @Test 方法):

测试方法 业务形态 关键断言/期望决策
shouldSegmentActiveHighValueVip 浏览 22 天、3 天前成交、15 单、GMV 6000、客单 1200、S5 ACTIVE、valueScore=75、HIGH_VALUE;含 VIP_CULTIVATION 标签;策略含「专属客户经理」
shouldRecallHighValueWarmUser 浏览 10 天、25 天前成交、8 单、GMV 5200、S4 WARM、valueScore=67、HIGH_VALUE;策略含「大额召回券」
shouldSegmentPriceSensitiveMidValue 浏览 12 天、4 单、GMV 900、客单 300、S3、券敏感度 70,恰好 35 分 WARM、valueScore=35、MID_VALUE;含 PRICE_SENSITIVE;策略含「满减加购券」
shouldSilenceDormantNoValueUser 浏览 1 天、150 天未成交、0 单 0 元、S1 DORMANT、valueScore=0、NO_VALUE、tags 为空(size=0);策略含「静默池」
shouldApplyCautiousStrategyForHighReturnRisk 活跃中价值(10 单、GMV 2200、S3)但退货率 35% ACTIVE、valueScore=49、MID_VALUE;含 HIGH_RETURN_RISK;策略含「审慎营销」,验证风险短路优先
shouldWakeHighValueColdUserAtThreshold 浏览 4 天、60 天前成交、GMV 5000、S4,恰好 55 分 COLD、valueScore=55、HIGH_VALUE;不含 VIP_CULTIVATION、不含 WINBACK;策略含「大额召回券」
  • 分层阈值外置热更新:活跃四档与四维分值全部在 user-segment.xml,大促前后重划分数线只改 XML 重建代理,画像批处理任务无需改代码。
  • 一方法一口径、可独立调参:GMV、订单数、会员等级、客单价各占一个方法,运营可单独调整某一维度档位并审计贡献,四维相加后由 Service 映射价值层。
  • 策略短路优先级固化在 Java 侧:buildStrategy 先判 HIGH_RETURN_RISK 审慎营销,再依次判 VIP、召回、加购、静默,风险兜底先于权益发放,避免给退货高危用户发大额券。
  • 双轴分层 + 动态标签正交组合:活跃与价值两条规则轴独立计算,券敏感、高退货、VIP_CULTIVATION、WINBACK 等标签在 Java 侧按阈值追加,新增标签不必改动 XML 评分逻辑。
  • 边界分有测试锁口径:35 分中价值、55 分高价值冷客(不打 VIP、不打 WINBACK)等临界形态均有断言,阈值调整后测试立刻暴露口径变化。
  • @Param 按名绑定、代理复用:接口经 ASM 动态代理实现,无独立计算进程与重型依赖,JDK 8 会员中台与信创环境可直接嵌入每日批量分层任务。