找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

560

积分

0

好友

48

主题
发表于 昨天 05:54 | 查看: 7| 回复: 0

背景:当前例子(线索)推送流程属于半自动化,每次策略调整都需要算法同学介入配合。核心诉求是实现策略产品化,让业务侧能够自主配置推送规则,从而让电销团队实现自助运营,无需技术同学重复开发。

主要流程可以概括为:从公海池中圈选目标线索 -> 锁定线索 -> 分发线索。

本期将聚焦于“锁定线索”环节,即如何根据业务规则进行圈选。圈选规则所依赖的筛选条件如下表所示:

筛选条件

字段 数值类型(value) 操作符号(operator)
静默时间:silent_days 数值 等于、小于、小于等于、大于、大于等于、包含于
触达次数: touched_times 数值 等于、小于、小于等于、大于、大于等于、包含于
接通次数:jt_times 数值 等于、小于、小于等于、大于、大于等于、包含于
线索归属地区:city 字符串(, 分隔) 等于、不等于
线索来源: souce_category 字符串(, 分隔) 等于、不等于
关键词:keyword 字符串 等于
是否有公司名称: hasCompanyName true 或 false 等于

二、整体设计与方案选型

方案选择:直接使用 ADB(或兼容的 MySQL)数据库,为每个标签字段建立索引。

选择此方案的考量如下:

  1. 实现难度:简单直接。
  2. 查询性能:面对千万级别的线索量,索引查询可以满足性能要求。
  3. 数据库限制:表的列数限制通常在1000以内,而我们的筛选条件固定且有限,远未触及此上限。

实现流程

  1. 数据同步:通过ODPS任务T+1更新公海池数据,将被锁定的线索标记为不可认领,并更新线索的各类信息。
  2. SQL圈选:根据前端配置的规则,动态拼接SQL语句,执行查询并将结果存入目标表,供后续分配使用。

核心在于,每个筛选条件都对应数据库表中的一个字段,圈选本质上是根据规则动态生成WHERE条件的过程。

ODPS同步的公海表结构示例如下:

create table dws_lead_stategy(
    id bigint auto_increment comment '数据表自增主键' primary key,
    creditcode varchar charset utf8 null,
    leadid bigint null comment 'leads表ID',
    transription_text varchar charset utf8 null comment '文本数据',
    souce_category varchar charset utf8 null comment '线索来源分类,A/B/C',
    last_call_time varchar charset utf8 null comment '末次接通时间',
    last_touch_time varchar charset utf8 null comment '末次触达时间',
    jt_times varchar charset utf8 null comment '接通次数',
    touched_times varchar charset utf8 null comment '触达次数',
    silent_days varchar charset utf8 null comment '当前沉默天数',
    is_locked varchar charset utf8 null comment '当前线索是否被锁定',
    dt varchar charset utf8 null comment '日期',
    created datetime default '2025-08-29 11:37:15.535' not null comment '创建时间',
    modified datetime default '2025-08-29 11:37:15.535' not null on update CURRENT_TIMESTAMP comment '修改时间'
) engine = InnoDB collate = utf8_bin;

规则引擎的核心是根据操作符和值动态构建SQL条件,以下为关键的Java代码片段:

/**
 * 根据操作符和值类型构建SQL条件
 *
 * @param sqlBuilder SQL构建器
 * @param fieldName 字段名
 * @param operator 操作符
 * @param value 值
 * @param ruleType 规则类型
 */
private void appendConditionByOperator(StringBuilder sqlBuilder, String fieldName, String operator,
                                      String value, String ruleType) {
    if (StringUtils.isBlank(value)) {
        return;
    }
    // 特殊处理是否有公司名称
    if ("hasCompanyName".equals(ruleType)) {
        if ("equals".equals(operator) && "1".equalsIgnoreCase(value)) {
            sqlBuilder.append(" AND ").append(fieldName).append(" IS NOT NULL AND ").append(fieldName).append(" != ''");
        } else if ("equals".equals(operator) && "0".equalsIgnoreCase(value)) {
            sqlBuilder.append(" AND (").append(fieldName).append(" IS NULL OR ").append(fieldName).append(" = '')");
        }
        return;
    }
    // 处理数值型条件(如静默天数、触达次数、接通次数)
    if ("silent_days".equals(ruleType) || "last_touched_times".equals(ruleType) || "jt_times".equals(ruleType)) {
        handleNumericCondition(sqlBuilder, fieldName, operator, value);
        return;
    }
    // 处理字符串列表条件(如归属地区、来源)
    if ("city".equals(ruleType) || "source".equals(ruleType)) {
        handleStringListCondition(sqlBuilder, fieldName, operator, value);
        return;
    }
    // 处理关键词全文匹配
    if ("keyword".equals(ruleType)) {
        sqlBuilder.append(" AND (LOWER(transription_text) RLIKE '").append(value)
                .append("' OR LOWER(follow_text) RLIKE '").append(value).append("')");
        return;
    }
}

开发过程中遇到的典型问题:SqlRunner 连接泄漏

问题现象:系统运行一段时间后,出现数据库连接耗尽的相关报错。

根因分析:经排查,项目使用的旧版本 MyBatis(3.1.0)中,SqlRunner 工具类存在缺陷。其在执行过程中会创建和使用不同的 sqlSession,导致连接未能正确释放。

解决方案

  1. 升级框架:官方在 MyBatis 3.4.3.1 版本中已修复此问题,升级是最彻底的解决方案。
  2. 兼容性修复:若无法立即升级,可以重写一个安全的 SqlRunner。我们创建了 SafeSqlRunner,确保使用同一个 SqlSession 并在使用后手动关闭。
/**
 * 兼容旧版本 MyBatis-Plus 的安全 SqlRunner 工具类
 * 解决旧版本 SqlRunner 调用两次 sqlSession() 导致连接泄漏的问题
 */
@Component
public class SafeSqlRunner extends SqlRunner {
    @Autowired
    @Qualifier("sessionFactory") // 多数据源环境下需指定
    private SqlSessionFactory sqlSessionFactory;

    private Map<String, Object> sqlMap(String sql, Object... args) {
        Map<String, Object> param = new HashMap<>();
        param.put("sql", sql);
        param.put("args", args);
        return param;
    }

    public List<Map<String, Object>> selectList(String sql, Object... args) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            return sqlSession.selectList(SELECT_LIST, sqlMap(sql, args));
        }
    }
}

三、方案总结与延伸思考

本文通过电销线索圈选的具体案例,探讨了“标签圈选”的通用实现思路。在实际业务中,选择哪种技术方案需要综合权衡:

  1. 实现难度:评估团队现有技术资源与能力。
  2. 性能与成本:考虑查询速度(尤其是“与/或”逻辑)、存储空间占用(表体积与索引数量)、以及后续迭代时变更索引的效率。

以下是三种常见方案的对比,可供方案选型时参考:

限制/方案 方案一(MySQL)每个标签字段一个索引 方案二(ADB/PG)倒排索引 方案三(ADB/PG)位图
实现难易 简单 中等 中等
数据库限制 列数限制(一般1000,ADB上限4096) 列数限制(一般1000,ADB上限4096) bitmap最大长度为1GB
查询性能(与/或) 较慢 相对快

对于筛选条件固定且数量有限、数据量在千万级以内的场景,采用 MySQL 或 ADB 并为字段建索引的方案(方案一)在实现复杂度和性能之间取得了较好的平衡,是快速落地的不错选择。




上一篇:限价订单簿状态变化详解:从层次结构到多场景实战示例
下一篇:Docker部署Outline知识库实战:私有化部署与配置详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-10 21:17 , Processed in 0.079597 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表