1 Preface
在社交 APP 中,经常会看到用户标签功能,那么这个功能如何实现呢? 如微信的用户标签。
考虑两种情况:
- 标签的量级较大,我们需要基于 ES 来加速查询;
- 标签量级较小,可以使用数据库实现一个简易版的标签功能;
下面我们使用数据库,实现一个简易的标签功能;
2 需求分析
以上面的截图为例,标签需要支持多选,那么问题的核心是:怎么支持指定标签组过滤?
先理清这些标签之间的关系:同一行的标签之间是或关系
,多行之间是与关系
。
那么,这里想要直接通过拼接 sql 实现,就比较复杂了。那就尝试在内存中分步实现。
3 实现思路
3.1 思路一
- 从用户和标签关联表中, 查询每个用户的 标签ID列表<userid, [tag_id1, tag_id2…]>;
- 循环检查 cids 每个ID group; (组之间是与关系, 组内是或关系)
- 找出所有满足条件的 user_ids;
3.2 思路二
根据传入的标签, 查询这些标签下的所有用户 (按category_id 排序)
- 然后按 category_id 分组,对同一组的 user_id 合并+去重 (或关系);
注意点
: (要核对分组数量, 因为有的分组可能没有用户)
- 把多个 category_id 分组,对 user_id 计算交集(与关系)
- 把合法的 userID 当做查询条件,查询即可
4 核心代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import "github.com/scylladb/go-set/uset"
// doMapReduce 获取符合条件的 UserIDs 列表
// 按 category_id 分组,然后计算所有分组的交集
func doMapReduce(records []vo.UserTag, categoryCount int) []uint {
if len(records) <= 0 {
return nil
}
// 1 把同一category_id 下的 userID, 合并+去重
var prevID = records[0].CategoryTagID
var current = uset.NewWithSize(100)
var sets = []*uset.Set{current}
for _, row := range records {
if row.CategoryTagID == prevID {
current.Add(row.UserID)
} else {
// new set
prevID = row.CategoryTagID
current = uset.New(row.UserID)
sets = append(sets, current)
}
}
// 2 集合个数, 应该等于请求的标签类别个数
if len(sets) != categoryCount {
return nil
}
// 3 计算交集,返回列表
return uset.Intersection(sets...).List()
}
|
5 总结
参考