案例 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 |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
| 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 |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将下列文件按原始包名放入 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 即可运行。
UserBehavior.java
Section titled “UserBehavior.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 用户近 90 天行为与价值要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic 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;}UserSegmentDecision.java
Section titled “UserSegmentDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 用户画像分层与营销策略决策 */@Datapublic 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); }}UserSegmentRules.java
Section titled “UserSegmentRules.java”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);}user-segment.xml
Section titled “user-segment.xml”<?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>UserSegmentService.java
Section titled “UserSegmentService.java”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 "进入静默池,仅大促节点低频触达,不做主动打扰"; }}UserSegmentServiceTest.java
Section titled “UserSegmentServiceTest.java”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; }}在工程根目录执行:
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 会员中台与信创环境可直接嵌入每日批量分层任务。