Lombok技术揭秘 _ 自动生成带代码的幕后机制

1. Lombok简介

1.1 Lombok是什么

引入官方解释:

  1. Project Lombok 是一个 JAVA 库,它可以自动插入编辑器和构建工具,为您的 JAVA 锦上添花。
  2. 再也不要写另一个 getter/setter 或 equals 等方法,只要有一个注注解,你的类就有一个功能齐全的生成器,自动记录变量,等等。

1.2 Lombck相关注解功能介绍

注解 功能
@Getter @Setter 1.能够作用在字段或类上生成 get/set 方法; 2.value 设置方法访问级别默认 public ;
@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor 1.生成无参/全参/( final @Nonnull 参数)的构造方法作用于类。 2. staticName 有值时,会私有当前构造方法,提供指定名称 public 静态构造方法。 3. access 构造器的访问权限,默认public 。
@ToString 1.生成类的 toString 方法,作用在类上。 2.属性 includeFieldNames 是否打印字段名称。 3.属性 exclude 排除不希望生成在 toString 中的字段。 4.属性 of 指定生成在 toString 的字段。 5.属性 callSuper 是否生成父类属性在toString。
@Data 等同于生成 @ToString、@EqualsAndHashCode、@Getter、@Setter、 @RequiredArgsConstrutor 注解。
@Builder 用在类、构造器、方法上提供建造者模式的构建器类,用于创建对象。
@Slf4j 在类中生成一个日志记录器( Logger )的字段。
@SneakyThrows 方法上自动添加异常处理代码,以使编译器不会抛出异常未处理的警告。
@Value 用于创建不可变的值对象( Value Object )类.即所有属性都是final的,并且只有 getter 方法,没有 setter 方法。
@Accessor 为属性生成自定义的访问方法,可以控制方法的名称、修饰符、参数等。
@Cleanup 为需要关闭的资源自动调用 close 方法,避免资源泄漏。

2. Lombok原理介绍

2.1 Java类文件编译过程

首先,我们知道 Lombok 功能是作用在类编译时期,那我们来看下一个类编译的过程。

  1. 定义一个 PersonDTO.Java 类
arduino 复制代码
public class PersonDTO {
  //姓名
  private String name;  
}
  1. Javac PersonDTO.Java 对源代码进行解析转化,会生成一棵抽象语法树( AST );
  2. 运行过程中会调用实现了 JSR 269 注解处理器,下面介绍;
  3. JSR 实现可处理自定义逻辑,包括可修改编译后的抽象语法树(AST);
  4. Javac 使用修改后的抽象语法树(AST)生成字节码文件;

过程如下图:

AST 是抽象语法树(Abstract Syntax Tree) 的缩写,是 JAVA 源代码展示的一种树状结构它将代码的结构和语法元素映射到树节点上,使得程序可以在编译、

分析和转换过程中更容易地操作和理解。有兴趣可以学习 JavaParser 源码, 了解将 Java 源代码解析生成成一个抽象语法树( AST ),这个树形结构表示了代码的

语法结构包括类、方法、变量、语句等等过程。

github地址:github.com/javaparser/....

如: PersonDTO.Java 在 idea 中使用可视化工具展示文件 AST 树

2.2 JSR 269介绍

首先 JSR 269全称" Pluggable Annotation Processing API ",是 JAVA 平台的一项规范,也被称之为注解处理器 API 。在Java6引入,用于在编译时处理

注解,目标是提供更丰富的编译时元数据处理能力,以增强Java编译器的功能。这个规范允许开发人员创建自定义的注解处理器,这些处理器可以在编译时检查、

分析和生成Java代码。

应用框架:

  1. Servlet、JAX-RS(RESTful Web服务)JSR 269 来生成用于处理 HTTP 请求的代码。
  2. Spring Boot 项目中以处理各种自定义注解,如 @Controller、@Service、@Repository 等。这些注解可以用于自动化配置、依赖注入等方面。
  3. Hibernate 它使用 JSR 269 来处理 JPA 注解,并生成与数据库交互的代码。
  4. Lombok 是一个 JAVA 库,它通过注解处理器生成常见的 JAVA 代码,如 getter、setter、equals、hashCode 等,以简化开发工作。
  5. MapStruct 是一个用于对象映射的 JAVA 库,它使用 JSR 269 来生成类型安全的映射代码,帮助开发人员将一个对象映射到另一个对象。

如何实现自定义注解注解处理器:

1.声明自定义注解;如 Lombok 下的 @Data,@Getter,@Setter等。

2.实现 Process接口,或者继承 AbstractProcessor 复写 process 方法,处理自定义注解逻辑。

scala 复制代码
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.util.Set;
​
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本
public class MyAnnotationProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)   {
    // 在这里处理自定义注解,生成代码或执行其他任务
    return true;
  }
}

3.注册注解处理器

两种方式:

  1. Resource 文件:项目 META-INF/services 创建 javax.annotation.processing.Processor 文件,自定义注解处理器的全类名写到此文件中。
  2. 通过谷歌工具包 auto-service ,可自动生成以上配置文件。
typescript 复制代码
<!-- 编译注解执行注册 jar包-->
<dependency>
  <groupId>com.google.auto.service</groupId>
  <artifactId>auto-service</artifactId>
  <version>1.0.1</version>
</dependency> 
​
@AutoService(Processor.class) //谷歌工具包方式:注册注解处理器
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) //支持的Java版本
public class MyAnnotationProcessor extends AbstractProcessor {
​
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
  {
    // 在这里处理自定义注解,生成代码或执行其他任务
    return true;
  }
}

2.3 Lombok 实现原理

1. Lombok 实际就是结合注解处理器和 AST 技术, Lombok 实现的注解处理器会遍历 AST ,查找与 Lombok 注解相关的元素,根据注解的要求生成新的代码。

2.编译前后的 AST 语法树对比

加入@Getter注解编译后

3. Lombok 注解处理器,采用 Resource 方式注册编译注解处理器

注解处理器 AnnotationProcessor 源码:

typescript 复制代码
class AnnotationProcessorHider {
​
  public static class AstModificationNotifierData {
    public volatile static boolean lombokInvoked = false;
  }
  
  public static class AnnotationProcessor extends AbstractProcessor {
    // 获取支持的注解类型
    @Override
    public Set<String> getSupportedOptions() {
      return instance.getSupportedOptions();
    }
​
    // 获取支持的注解类型
    @Override
    public Set<String> getSupportedAnnotationTypes() {
      return instance.getSupportedAnnotationTypes();
    }
​
    // 支持的JDK版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
      return instance.getSupportedSourceVersion();
    }
​
    //初始化环境
    @Override
    public void init(ProcessingEnvironment processingEnv) {
      disableJava9SillyWarning();
      AstModificationNotifierData.lombokInvoked = true;
      instance.init(processingEnv);
      super.init(processingEnv);
    }
​
    // 处理自定义注解逻辑
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment 
      roundEnv) {
      return instance.process(annotations, roundEnv);
    }
  }

自定义注解处理器 Handler : 在 Jar 包的 lombok.javac.handlers下,每个注解处理对应一个 Handler. 如 HadlerGetter.java 操作 AST 树生成 getter 方法.

2.4手动实现一个 @Getter 功能

2.4.1.创建 maven 工程 demo 包含两个子模块 getter/getter-use

2.4.2. getter 工程

pom文件:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>demo</artifactId>
    <groupId>com.example</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>getter</artifactId>
​
  <dependencies>
    <!-- 注解执行器依赖jar -->
    <dependency>
      <groupId>com.sun</groupId>
      <artifactId>tools</artifactId>
      <version>1.6.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/../lib/tools.jar</systemPath>
    </dependency>
​
    <!-- 编译注解执行注册 -->
    <dependency>
      <groupId>com.google.auto.service</groupId>
      <artifactId>auto-service</artifactId>
      <version>1.0.1</version>
    </dependency>
  </dependencies>
</project>

自定义 GetterTest 注解

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * @author zcy1
 */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GetterTest {
}

GetterTest 编译注解处理器 GetterProcessor

ini 复制代码
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
​
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
​
/**
 * @author zcy1
 */
@AutoService(Processor.class) //谷歌工具包方式:注册注解处理器
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("GetterTest")
public class GetterProcessor extends AbstractProcessor {
​
  /**
   * 用于在编译器打印消息的组件
   */
  private Messager messager;
  
  /**
   * 提供待处理抽象语法树
   */
  private JavacTrees trees;
  
  /**
   * 用来构造语法树节点
   */
  private TreeMaker treeMaker;
  
  /**
   * 用于创建标识符的对象
   */
  private Names names;
  
  /**
   * 获取一些注解处理器执行处理逻辑时一些关键对象
   * @param processingEnv 处理环境
   */
  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    this.messager = processingEnv.getMessager();
    this.trees = JavacTrees.instance(processingEnv);
    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
    this.treeMaker = TreeMaker.instance(context);
    this.names = Names.instance(context);
  }
  
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
  {
    // 获取自定义GetterTest注解的类
    Set<? extends Element> elementsAnnotatedWith = 
      roundEnv.getElementsAnnotatedWith(GetterTest.class);
    elementsAnnotatedWith.forEach(e -> {
      JCTree tree = trees.getTree(e);
        tree.accept(new TreeTranslator() {
          @Override
          public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
            List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
              // 在抽象树中找出所有的成员变量
              for (JCTree jcTree : jcClassDecl.defs) {
                if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
                  JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
                    jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                 }
              }
              // 对于变量进行生成getter方法的操作
              jcVariableDeclList.forEach(jcVariableDecl -> {
                messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " 
                  has been processed");
                  jcClassDecl.defs = 
                    jcClassDecl.defs.prepend(createGetterMethod(jcVariableDecl));
              });
              super.visitClassDef(jcClassDecl);
              }
          });
      });
      return true;
  }
  
  private JCTree.JCMethodDecl createGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
    ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
   
    // 生成表达式 this.name = name
    JCTree.JCExpressionStatement aThis = 
      makeAssignment(treeMaker.Select(treeMaker.Ident(names.fromString("this")),               jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));        
    statements.append(aThis);
    JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
​
    // 生成入参 (String name)
    JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
            jcVariableDecl.getName(), jcVariableDecl.vartype, null);
    List<JCTree.JCVariableDecl> parameters = List.of(param);
​
    // 生成返回对象 void
    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
​
       //生成方法名 getName
    Name methodName = getMethodName(jcVariableDecl.getName());
​
    // 返回语法树对象
  return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),methodName, methodType,   
    List.nil(),parameters, List.nil(), block, null);
  
  }
  
  /**
   * 驼峰方法名
   */
  private Name getMethodName(Name name) {
    String s = name.toString();
    return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, 
      name.length()));
  }
  
  private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, 
    JCTree.JCExpression rhs) {
    return treeMaker.Exec(treeMaker.Assign(lhs, rhs));
  }
}

2.4.4 getter-use工程

pom 文件 引入getter工程

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>demo</artifactId>
    <groupId>com.example</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>getter-use</artifactId>
​
  <dependencies>
    <dependency>
    <groupId>com.example</groupId>
    <artifactId>getter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

PersonDTO 使用@GetterTest

kotlin 复制代码
@GetterTest
public class PersonDTO {
  private String name;

  private Integer age;
}

2.5.5 项目进行编译

getter 模块下自动生成注册器

getter-use 模块下 PersonDTO.class 可见生成了对应属性的 get 方法

3.总结

本文通过以上对 Lombok 相关介绍,通过对 JAVA 文件编译过程分析和 JSR269 实现的方式, 基于这个规范然后引申出 Lombok 实现原理过程介绍,以及手动实现 getter 案例,想必我们对 Lombok 原理也有了相应的了解。虽然 Lombok 提供了许多便利,由于生成的代码不在源文件中可见,就会导致代码的可读性和维护性较差。在工作中 Lombok 使用时注意闭坑:

问题 解决
@Data 和 @Builder 一起使用时,无参构造方法会被干掉 手动加上注解: @AllArgsConstructor、@NoArgsConstructor。
@Builder 导致类属性默认值无效。 有默认值属性上加注解 : @lombok.Builder.Default。
@Data 生成的 toString 方法,默认能不输出父类属性 子类添加: @ToString(callSuper = true)。

参考文献

Lombok 官网地址: projectlombok.org

JavaParser 源码地址: github.com/javaparser/...

JAVA 抽象语法树 AST 浅析与使用: www.freesion.com/article/406...

复制代码
复制代码
scss 复制代码
| 4. 重要挽留用户 (↑↓↓) | 最近一次购买时间短、购买频率中、金额小、高访问频率、最后一次访问时间短 | - 提供积分奖励          |
scss 复制代码
| 5. 一般价值用户 (↓↑↑) | 最近一次购买时间中、购买频率高、金额大、中访问频率、最后一次访问时间中 | - 提供购物建议          |
scss 复制代码
| 6. 一般召回用户 (↓↑↓) | 最近一次购买时间中、购买频率高、金额小、中访问频率、最后一次访问时间中 | - 发送激励性促销信息       |
scss 复制代码
| 7. 一般发展用户 (↓↓↑) | 最近一次购买时间中、购买频率中、金额大、中访问频率、最后一次访问时间中 | - 提供新产品预告         |
scss 复制代码
| 8. 一般发展用户 (↓↓↓) | 最近一次购买时间长、购买频率低、金额小、低访问频率、最后一次访问时间中 | - 发送感谢信,赠送礼品      |
markdown 复制代码
### **4.2 不同周期用户的客户关怀策略拟定**
markdown 复制代码
0.  提高客户满意度和忠诚度:客户关怀可以通过提供个性化的服务和优惠,满足客户的需求和期望,从而提高客户对企业的满意度和忠诚度。高满意度的客户更有可能成为企业的长期忠实客户,从而为企业带来更多的利润。
markdown 复制代码
0.  激活潜在价值:对于低 RFM 客户和低 Recency 客户,企业需要通过客户关怀来激活他们的需求,提高他们的购买频率和购买金额。例如通过了解这些客户的需求和喜好,企业可以提供更好的服务和优惠,吸引他们进行更多的购买,从而挖掘出他们的潜在价值。
markdown 复制代码
0.  维护管理客户关系:对于低 Recency 客户,企业需要通过客户关怀来维护和管理客户关系。虽然这些客户的购买频率和购买金额较低,但企业仍然需要与他们保持联系,了解他们的需求和喜好,并提供适当的服务和优惠,以保持他们的忠诚度。
markdown 复制代码
### **4.3 优化产品及业务上的推广策略**
diff 复制代码
-   针对不同客户群体提供定制化产品根据 RFM 分群结果,可以将客户划分为不同群体,例如高价值客户、中价值客户和低价值客户等。针对不同群体的客户需求和偏好,可以提供定制化的产品或服务,以满足他们的需求。例如,对于高价值客户可以提供更高端的产品或服务,对于低价值客户则可以提供更实惠的产品或服务。
diff 复制代码
-   制定合理的价格策略针对不同客户群体制定合理的价格策略可以提高产品和业务的推广效果。例如,对于高价值客户可以提供折扣或优惠,以吸引他们购买更高档的产品或服务;对于低价值客户则可以采取低价策略,以吸引他们购买更实惠的产品或服务。
diff 复制代码
-   制定精准的推广策略根据 RFM 分群结果,可以了解不同客户群体的购买行为和喜好,以此制定更精准的推广策略。例如,针对高价值客户可以提供更有吸引力的优惠券或礼品,以鼓励他们购买更多产品或服务;针对低价值客户则可以通过社交媒体或广告渠道进行宣传和推销。
diff 复制代码
-   优化产品和服务根据 RFM 分群结果,可以对现有产品和服务进行分析和优化。例如,针对高价值客户可以提供更完善的服务和保障,以提高客户满意度和忠诚度;针对低价值客户则可以通过简化流程和提高效率等方式,改善客户体验。
markdown 复制代码
## **结论**
​

在以上内容中,我们探讨了使用 RFM 模型进行用户分群的方法和步骤,包括业务定义与用户识别、构建 RFM 体系、 RFM 分群实践以及 RFM 分群的应用。通过对客户进行更精细的分类,并在此基础上制定个性化营销策略、客户生命周期管理策略以及产品和业务推广策略等。这些策略的优化可以提高客户的价值与忠诚度,进而提高企业的业务效益。

​
shell 复制代码
## 参考文献
less 复制代码
0.  [租赁大数据看板建设过程中数据清洗及程度思考| 人人都是产品经理](https://www.woshipm.com/data-analysis/4998713.html)
perl 复制代码
0.  [大数据环境下互联网行业数据仓库/数据平台的架构之漫谈-- lxw的大数据田地](https://www.google.com/url?sa=i&url=http%3A%2F%2Flxw1234.com%2Farchives%2F2015%2F08%2F471.htm&psig=AOvVaw1ly3haDNkVTaYZCxdheeDN&ust=1695196644458000&source=images&cd=vfe&opi=89978449&ved=0CBAQjhxqFwoTCKihkeKZtoEDFQAAAAAdAAAAABAD)
less 复制代码
0.  [数据分析模型- RFM模型- 《有数BI用户手册》](http://longlinchem.com/index/manual/p/rfm.html)
shell 复制代码
## 推荐阅读
less 复制代码
[基于jvm-sandbox-repeater的流量降噪方案](https://juejin.cn/post/7280429214607736890)
less 复制代码
[海量数据处理方案](https://juejin.cn/post/7280047442795888700)
复制代码
    
less 复制代码
[工作了5年你居然不知道版本号有这些规范?](https://juejin.cn/post/7278238875456684090)
less 复制代码
[线程池 ThreadPoolExecutor 基础介绍](https://juejin.cn/post/7277846069369929784)
less 复制代码
[Netty-EventLoop实现原理](https://juejin.cn/post/7277426842814332983)
​

​
shell 复制代码
## 招贤纳士
objectivec 复制代码
政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。
css 复制代码
如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊......如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 <zcy-tc@cai-inc.com>
shell 复制代码
## 微信公众号
​

文章同步发布,政采云技术团队公众号,欢迎关注
bash 复制代码
![文章顶部.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/942d5c9084bb4855b04a5f00eabff23f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=4722&h=696&s=167544&e=png&b=fafcff)

摘要

RFM 分析是一种用于洞悉客户价值和行为的强大工具,广泛应用于市场营销和客户关系管理。本文将介绍 RFM 分析如何在数据产品不充分的情况下实现以及如何利用RFM分析来优化营销策略,提高客户满意度,增加业务收益。

导言

  • RFM 分析最早起源于直邮行业,

  • 用于确定哪些客户最有可能回应和购买邮寄目录中的产品。随着信息技术的发展, RFM 分析逐渐在各个行业得到广泛应用,成为理解客户行为和价值的关键工具。例如电子商务、金融、酒店业等。

  • 主要的商业目标:

    • 客户洞察: RFM 分析允许企业深入了解客户的消费行为。通过分析最近一次交易时间,交易频率和交易金额,企业可以确定哪些客户更活跃,哪些客户更有价值。
    • 个性化营销: 基于 RFM 分析的结果,企业可以制定个性化的营销策略。不同 RFM 分组的客户可以接收到适合其需求和偏好的定制化推广信息,从而提高市场活动的效果。
    • 客户忠诚度: 通过识别和奖励高价值客户,企业可以增加客户的忠诚度。 RFM 分析有助于保持现有客户,并吸引他们继续购买。
    • 销售增长: 通过深入了解客户行为,企业可以发现销售增长的潜在机会。优化市场策略和产品定价可以提高业务的销售额。

第一部分: RFM 分析基础

1.1 什么是 RFM 分析?

RFM 的概念( Recency , Frequency , Monetization )是一种客户分类和预测的分析模型,用于识别最有价值的客户和预测他们未来的购买行为。它的三个维度分别是最近一次购买的时间、购买频率和客户生命周期价值。RFM模型可以帮助企业了解客户的消费习惯和需求,以便更好地为客户提供个性化的产品和服务,从而提高客户忠诚度和增加销售收入。

1.2 用户分层模型的对比

回归到一个问题,数据分析可以使用的用户分层模型茫茫多,为什么就一定要是RFM模型?选择模型一定是基于当前的业务场景和业务需求,从可行性及优劣势考虑是最匹配当前需求的。同时,越简单的模型带来快速的理解以及实现;

模型名称 优势 劣势
RFM 模型 - 相对简单,易于理解和实施。- 基于用户的实际交易行为。 - 可用于识别高价值客户和流失客户。 - 只关注了交易行为,忽略了其他因素。 - 需要大量的历史交易数据。
客户细分模型(如地理位置、购买历史、产品偏好) - 提供了更全面的用户洞察。 - 可根据不同业务需求定义各种细分标准。 - 需要多维度数据,可能需要数据清洗和处理。 - 定义细分标准需要主观判断。
协同过滤模型(基于用户相似性) - 个性化推荐,提高用户体验。 - 可发现潜在的兴趣和交叉销售机会。 - 对新用户或冷启动问题不够适用。 - 数据稀疏性可能导致模型不准确。
生命周期价值( CLV )模型 - 评估客户的长期价值,有助于制定长期策略。 - 考虑了客户的购买历史和保留率。 - 需要历史数据和长期观察。 - 预测未来可能不准确。
漏斗分析模型 - 识别流失点和改进转化率。 - 可用于优化购买路径。 - 仅关注了特定的用户流程。 - 需要大量的事件数据。
社交网络分析模型(分析用户在社交网络中的连接) - 识别影响力用户和信息传播模式。 - 用于社交媒体营销和口碑营销。 - 仅适用于与社交网络相关的业务。 - 数据收集可能有挑战。

1.3 业务识别

笔者的业务是供应链平台 TO B 服务,当前主要聚焦的服务模式在用户进入平台完成注册、访问及交易转化。以下为业务识别的内容:

业务识别 说明
产品阶段 MVP 阶段,注重核心用户体验,挖掘用户痛点
业务阶段重心 拉新拓客、提升留存
业务模式

第二部分:构建 RFM 体系

2.1 数据收集和准备

2.1.1主要方式:

平台获取:通过互联网这个工具,我们可以通过像数据仓库这样的工具,帮助我们更深入地获取和分析数据。

2.1.2其它方式:

观察调研:这种方法通常用于用户研究,通过用户访谈和问卷调查等方式收集特定人群对产品或服务的反馈数据,了解他们的需求和行为特征。

问卷调查:这是一种传统的获取数据的方式,包括纸质问卷和互联网问卷两种形式,主要用于收集用户对产品或服务的反馈数据,从而了解市场和用户需求。

公开信息:包括国家统计数据、地方政府公开数据、上市公司年报、季报等,这些都可以从互联网上获取。

付费数据:在市场上,有一些产品化数据交易平台提供多领域的付费数据资源,可以按需购买使用。

2.2 数据预处理

数据清洗主要关注数据的质量和精度。它的目标是识别和修正数据中的错误和不一致性,以提高数据的质量和准确性,为后续的数据分析和建模提供可靠的基础。数据预处理包括数据集成、数据转换和数据规约等过程,旨在消除数据中的噪声、冗余和不一致性,使数据更加适合进行分析和建模。

2.3 构建 RFM 模型

指标 关系
Recency (最近购买时间) & Frequency (购买频率) Recency 和 Frequency 通常呈负相关关系。最近购买过的客户可能购买频率较低,因为他们可能刚买完商品,需求被满足,因此短时间内不再购买。相反,购买频率高的客户可能最近没有购买,因为他们可能经常购买商品,需求被满足,因此短时间内不再购买。
Recency (最近购买时间) & Monetary (购买金额) Recency 和 Monetary 通常呈正相关关系。最近购买过的客户可能购买金额较高,因为他们可能购买更多的商品或者更贵的商品。相反,购买金额高的客户可能最近没有购买,因为他们可能已经购买了足够的商品,需求被满足,因此短时间内不再购买。
Frequency (购买频率) & Monetary (购买金额) Frequency 和 Monetary 通常呈正相关关系。购买频率高的客户可能购买金额较高,因为他们可能需要更多的商品或者更贵的商品。相反,购买金额高的客户可能购买频率较低,因为他们可能已经购买了足够的商品,需求被满足,因此短时间内不再购买。

2.3.1 最近一次交易时间( Recency )

当计算最近一次交易时间时,我们需要确定适当的时间窗口,对于该场景,最近一次的供应链交易日期和最近一次的网超交易日期相对重要。

复制代码
vbnet 复制代码
  '''
  select 
  company_id as "机构id",
  company_name as "机构名称",
  max_pur_date as "最近一次交易距今日期时间"
  from main_table
  
  # 定义最近交易天数间隔区间和对应的分值
  transaction_interval_mapping = {
  (0, 7): 5,
  (8, 14): 4,
  (15, 30): 3,
  (31, 90): 2,
  (91, float('inf')): 1
    }
  '''

2.3.2 交易频率( Frequency )

交易频率的最直观指标就是交易订单的数量,我们通过聚合客户近一个月的交易订单数以及历史累计的交易订单数去进行分析;同时根据不同的场景,我们需要充分考虑和权衡各种因素,去选择交易的不同类型,进行分析。

复制代码
python 复制代码
  '''
  select 
  company_id as "机构id",
  company_name as "机构名称",
  coalesce(pur_cnt_30,0) as "近30天交易订单数"
  from main_table
  
  # 定义访问天数区间和对应的分值
  visit_interval_mapping = {
  (0, 1): 1,
  (1, 3): 2,
  (4, 7): 3,
  (8, 14): 4,
  (15, 30): 5
    }
  '''
  

2.3.3 交易金额( Monetary )

首先我们需要将每个客户的交易金额进行汇总。形成一个累计的交易金额或者是一个特定时间窗口内的交易金额,比如一个月。然后将汇总的交易金额按照一定的规则进行分组。最后针对每个等级,计算其交易金额的占比。

复制代码
python 复制代码
  '''
  select 
  company_id as "机构id",
  company_name as "机构名称",
  coalesce(pur_amt_30,0) as "近30天交易金额"
  from main_table
  '''

第三部分:RFM 用户分群应用

3.1 用户分群选择

概念 使用 RFM 体系将客户分成不同的群组的方法主要是根据客户的 Recency 、 Frequency 和 Monetary 三个指标进行划分
使用 RFM 体系将客户分成不同的群组 收集客户的购买数据,包括购买时间、购买频率和购买金额等信息。根据这些数据,计算出每个客户的 Recency 、 Frequency 和 Monetary 指标。根据这些指标,将客户分成不同的群组。例如,可以根据 Recency 指标将客户分为 " 近期的 " 和 " 非近期的 ",根据 Frequency 指标将客户分为 " 高频购买的 " 和 " 低频购买的 ",根据 Monetary 指标将客户分为 " 高金额购买的 " 和 " 低金额购买的 "。对不同的群组进行分析,了解他们的特点和需求,以便制定相应的商业策略。
目的 提高营销效果:通过将客户分成不同的群组,可以针对不同群组的特点和需求,制定不同的营销策略,提高营销效果。优化客户管理:通过将客户分成不同的群组,可以针对不同群组的特点,制定不同的客户管理策略,提高客户满意度和忠诚度。提高预测准确性:通过将客户分成不同的群组,可以对不同群组的购买行为进行预测,以便更好地规划生产和库存管理。
复制代码
arduino 复制代码
  '''
  # 定义函数将分值映射为标签
  def label_mapping(value, median):
  if value <= median:
  return '低'
  else:
  return '高'
​
# 根据中值将R、F、M分值映射为标签
  df_rfm_clean['R_Label'] = 
  df_rfm_clean['R_Merged_Score_Normalized'].apply(lambda x: label_mapping(x, 
  R_median))
  df_rfm_clean['F_Label'] = 
  df_rfm_clean['F_Merged_Score_Normalized'].apply(lambda x: label_mapping(x, 
  F_median))
  df_rfm_clean['M_Label'] = df_rfm_clean['M_Score_Normalized'].apply(lambda 
  x: label_mapping(x, M_median))
  
  # 定义客户标签映射字典
  customer_label_mapping = {
        ('低', '低', '低'): '一般挽留用户',
        ('高', '低', '低'): '一般发展用户',
        ('低', '高', '低'): '一般保持用户',
        ('高', '高', '低'): '一般价值用户',
        ('低', '低', '高'): '重要挽留用户',
        ('高', '低', '高'): '重要发展用户',
        ('低', '高', '高'): '重要保持用户',
        ('高', '高', '高'): '重要价值用户'
    }
​
  # 根据R、F、M的标签组合生成最终的客户标签列
  df_rfm_clean['Label of Customer'] = df_rfm_clean.apply(
        lambda row: customer_label_mapping.get((row['R_Label'], 
  row['F_Label'], row['M_Label'])),
        axis = 1)
  '''

3.2 客户细分的示例

  1. 以实例来看,其中一般挽留客户的特点是最近购买时间较远,购买频率较低,购买金额较小。这类客户对企业的重要性较低,但可能具有潜在价值。
  2. 一般发展用户的特点是最近购买时间较近,但购买频率较低,购买金额也不高。这类客户可能对企业有潜在价值,需要企业加强营销和服务。
  3. 重要价值客户的特点就是最近购买时间较近,购买频率较高,购买金额较大。这类客户是企业的核心客户,对企业的贡献最大,需要重点关注和服务。

第四部分: RFM 分群结果的应用

4.1 个性化营销的制定

RFM 细分 特征 个性化营销策略
1. 重要价值用户 (↑↑↑) 最近一次购买时间短、购买频率高、金额大、高访问频率、最后一次访问时间短 - 发送特定产品的折扣券或促销信息
2. 重要召回用户 (↑↑↓) 最近一次购买时间短、购买频率高、金额小、高访问频率、最后一次访问时间短 - 发送满减券
3. 重要发展用户 (↑↓↑) 最近一次购买时间短、购买频率中、金额大、高访问频率、最后一次访问时间短 - 提供 VIP 会员权益
4. 重要挽留用户 (↑↓↓) 最近一次购买时间短、购买频率中、金额小、高访问频率、最后一次访问时间短 - 提供积分奖励
5. 一般价值用户 (↓↑↑) 最近一次购买时间中、购买频率高、金额大、中访问频率、最后一次访问时间中 - 提供购物建议
6. 一般召回用户 (↓↑↓) 最近一次购买时间中、购买频率高、金额小、中访问频率、最后一次访问时间中 - 发送激励性促销信息
7. 一般发展用户 (↓↓↑) 最近一次购买时间中、购买频率中、金额大、中访问频率、最后一次访问时间中 - 提供新产品预告
8. 一般发展用户 (↓↓↓) 最近一次购买时间长、购买频率低、金额小、低访问频率、最后一次访问时间中 - 发送感谢信,赠送礼品

4.2 不同周期用户的客户关怀策略拟定

  1. 提高客户满意度和忠诚度:客户关怀可以通过提供个性化的服务和优惠,满足客户的需求和期望,从而提高客户对企业的满意度和忠诚度。高满意度的客户更有可能成为企业的长期忠实客户,从而为企业带来更多的利润。
  2. 激活潜在价值:对于低 RFM 客户和低 Recency 客户,企业需要通过客户关怀来激活他们的需求,提高他们的购买频率和购买金额。例如通过了解这些客户的需求和喜好,企业可以提供更好的服务和优惠,吸引他们进行更多的购买,从而挖掘出他们的潜在价值。
  3. 维护管理客户关系:对于低 Recency 客户,企业需要通过客户关怀来维护和管理客户关系。虽然这些客户的购买频率和购买金额较低,但企业仍然需要与他们保持联系,了解他们的需求和喜好,并提供适当的服务和优惠,以保持他们的忠诚度。

4.3 优化产品及业务上的推广策略

  • 针对不同客户群体提供定制化产品根据 RFM 分群结果,可以将客户划分为不同群体,例如高价值客户、中价值客户和低价值客户等。针对不同群体的客户需求和偏好,可以提供定制化的产品或服务,以满足他们的需求。例如,对于高价值客户可以提供更高端的产品或服务,对于低价值客户则可以提供更实惠的产品或服务。
  • 制定合理的价格策略针对不同客户群体制定合理的价格策略可以提高产品和业务的推广效果。例如,对于高价值客户可以提供折扣或优惠,以吸引他们购买更高档的产品或服务;对于低价值客户则可以采取低价策略,以吸引他们购买更实惠的产品或服务。
  • 制定精准的推广策略根据 RFM 分群结果,可以了解不同客户群体的购买行为和喜好,以此制定更精准的推广策略。例如,针对高价值客户可以提供更有吸引力的优惠券或礼品,以鼓励他们购买更多产品或服务;针对低价值客户则可以通过社交媒体或广告渠道进行宣传和推销。
  • 优化产品和服务根据 RFM 分群结果,可以对现有产品和服务进行分析和优化。例如,针对高价值客户可以提供更完善的服务和保障,以提高客户满意度和忠诚度;针对低价值客户则可以通过简化流程和提高效率等方式,改善客户体验。

结论

在以上内容中,我们探讨了使用 RFM 模型进行用户分群的方法和步骤,包括业务定义与用户识别、构建 RFM 体系、 RFM 分群实践以及 RFM 分群的应用。通过对客户进行更精细的分类,并在此基础上制定个性化营销策略、客户生命周期管理策略以及产品和业务推广策略等。这些策略的优化可以提高客户的价值与忠诚度,进而提高企业的业务效益。

参考文献

  1. 租赁大数据看板建设过程中数据清洗及程度思考| 人人都是产品经理
  2. 大数据环境下互联网行业数据仓库/数据平台的架构之漫谈-- lxw的大数据田地
  3. 数据分析模型- RFM模型- 《有数BI用户手册》

推荐阅读

构建RFM体系:优化客户分析和营销策略

基于jvm-sandbox-repeater的流量降噪方案

海量数据处理方案

工作了5年你居然不知道版本号有这些规范?

线程池 ThreadPoolExecutor 基础介绍

招贤纳士

政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。

如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊......如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com

微信公众号

文章同步发布,政采云技术团队公众号,欢迎关注

相关推荐
写bug写bug5 个月前
使用 Lombok 提升编码效率,让你准点下班
java·后端·project lombok
猿java6 个月前
Lombok是如何工作的?生产环境使用它需要考虑哪些因素?
java·后端·project lombok
叶_落9 个月前
Lombok 简单使用
后端·project lombok
一只叫煤球的猫1 年前
盘点Lombok的几个骚操作
java·后端·project lombok