一、前言
背景:当前例子推送属于半自动,每次策略调整都需要算法同学配合调整,诉求是将电销例子推送策略产品化,由业务侧配置推送规则,实现电销团队自助运营无需技术同学介入。
主要流程:从公海池子里圈选 -> 锁定例子 -> 分发例子
本期聚焦于锁定例子,即圈选规则,圈选规则如下:
筛选条件:
| 字段 | 数值类型(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
- 执行 SQL 写入对应代码
ODPS推送的公海表,SQL:
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
/**
* 根据操作符和值类型构建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)) {
handleNumericCondition(sqlBuilder, fieldName, operator, value);
return;
}
// 处理鮋达次数
if ("last_touched_times".equals(ruleType)) {
handleNumericCondition(sqlBuilder, fieldName, operator, value);
return;
}
// 处理接通次数
if ("jt_times".equals(ruleType)) {
handleNumericCondition(sqlBuilder, fieldName, operator, value);
return;
}
// 处理例子归属地区
if ("city".equals(ruleType)) {
handleStringListCondition(sqlBuilder, fieldName, operator, value);
return;
}
// 处理例子来源
if ("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;
}
}
开发过程中遇到的BUG:SqlRunner 连接不释放
报错如下:
究其原因:创建和使用的不是同一个 sqlSession,导致没释放。
查看 mybatis 版本:3.1.0
解决如下:
1、升级版本:官方 3.4.3.1 已经更修复
2、自己重写 SqlRunner 个,为 SafeSqlRunner,并手动释放。
/**
* 兼容旧版本 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)位图 |
|---|---|---|---|
| 实现难易 | 简单 | 中等 | 中等 |
| 数据库限制 | 列数限制:一般1000ADB 上限:4096 | 列数限制:一般1000ADB 上限:4096 | bitmap最大长度为1GB |
| 查询性能(与 / 或) | 慢 | 较慢 | 相对快 |