[toc]
伴随着互联网的发展,数据标签不知从何时应运而生。譬如各种社交APP,个人中心都有标签功能,言简意赅,对某人的文字概述。诚然,数据标签也可以用到日常运营平台中,其作用可以作为归类,检索作用。如果发挥的好,就像自己整一个书架一样,瞬间找到文学类,计算机类,科学类书籍。这就得看你是否灵活运用了,当然下面案例就是讲究如何设计。
1、需求场景
假设现在我们有多个模块,然后我们想通过对多个功能模块的查询列表增加数据标签,以便数据归类和检索功能,这时候我们怎么设计呢?
比如说现在我们有学生模块、老师模块、课程模块,然后实现各个模块的检索和分类。
2、方案设计
2.1 关系型DB存储
2.1.1 表结构设计
如果我们采用关系型DB,譬如MySQL存储,可以参考的表结构如下
CREATE TABLE `tags` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`buz_type` tinyint(4) NOT NULL COMMENT '业务类型(1:学生 2:老师 3:课程)',
`source_id` int(11) NOT NULL COMMENT '关联id',
`tags` varchar(4000) DEFAULT NULL COMMENT '标签,多个以,分割',
PRIMARY KEY (`id`),
UNIQUE KEY `udx_tags` (`buz_type`,`source_id`)
) ENGINE=InnoDB COMMENT='标签表';
如上,我们通过一个中间表,其中buz_type表示业务类型,source_id表示关联ID、tags为标签字段。这样设计的好处,可以通过buz_type增加业务类型支持更多模块。
2.1.2 java代码示例
以下代码为伪代码,示例代码如下
学生类、老师类、标签类
/**
* @description: 学生
* @Date : 2020/4/24 下午7:40
* @Author : 石冬冬-Seig Heil
*/
@Data
public class Student implements Serializable {
/**
* id
*/
private Integer id;
/**
* 学生标签
*/
private String name;
/**
* 标签
*/
private List<String> tags;
}
/**
* @description: 老师
* @Date : 2020/4/24 下午7:40
* @Author : 石冬冬-Seig Heil
*/
@Data
public class Teacher implements Serializable {
/**
* id
*/
private Integer id;
/**
* 学生标签
*/
private String name;
/**
* 标签
*/
private List<String> tags;
}
/**
* @description: 标签
* @Date : 2020/4/24 下午9:14
* @Author : 石冬冬-Seig Heil
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Tag implements Serializable {
private Integer id;
private Integer buzType;
private Integer sourceId;
private String tags;
}
/**
* @description: 标签查询参数对象
* @Date : 2020/4/24 下午9:17
* @Author : 石冬冬-Seig Heil
*/
@Data
public class TagParam {
private Integer buzType;
private Integer sourceId;
}
Service类
/**
* @description: 标签类
* @Date : 2020/4/24 下午9:16
* @Author : 石冬冬-Seig Heil
*/
public class TagService {
public List<Tag> queryTags(TagParam param) {
//查询数据,这里是伪代码
return new ArrayList<>();
}
}
/**
* @description: 学生类
* @Date : 2020/4/24 下午9:27
* @Author : 石冬冬-Seig Heil
*/
public class StudentService {
private TagComponent tagComponent;
public List<Student> queryList() {
// 1、假设这里查到数据
List<Student> resultList = new ArrayList<>();
// 2、对数据景先处理
tagComponent.bindTags(resultList,1,each -> each.getId(),(each,tags) -> each.setTags(tags));
return resultList;
}
}
/**
* @description: 老师类
* @Date : 2020/4/24 下午9:27
* @Author : 石冬冬-Seig Heil
*/
public class TeacherService {
private TagComponent tagComponent;
public List<Teacher> queryList() {
// 1、假设这里查到数据
List<Teacher> resultList = new ArrayList<>();
// 2、对数据景先处理
tagComponent.bindTags(resultList,2,each -> each.getId(),(each,tags) -> each.setTags(tags));
return resultList;
}
}
Service类
/**
* @description: 标签组件类
* @Date : 2020/4/24 下午9:21
* @Author : 石冬冬-Seig Heil
*/
@Slf4j
public class TagComponent {
private TagService tagService;
/**
* 绑定标签,巧用Function<T,R> 和 BiConsumer<T,U>两个函数
* @param list 数据元素
* @param buzType 业务类型
* @param function Function<T,R>
* @param consumer BiConsumer<T,U>
* @param <T> 集合元素
* @param <R> 集合元素属性
*/
public <T,R> void bindTags(List<T> list,Integer buzType,Function<T,R> function, BiConsumer<T,List<String>> consumer){
try {
//1、构建查询参数,只需要业务类型集合
TagParam form = new TagParam();
form.setBuzType(buzType);
//2、查询对应业务类型标签
List<Tag> tagList = tagService.queryTags(form);
if(!tagList.isEmpty()){
//3、构建Map,key=业务id,value=标签
Map<Integer,String> tagMap = tagList.stream().collect(Collectors.toMap(Tag::getSourceId,Tag::getTags));
list.forEach(rule -> {
//4、获取对应业务id的标签
String tags = tagMap.get(function.apply(rule));
if(null != tags && tags.length() > 0){
//5、回调处理,把数据标签集合以及集合元素给调用处理
consumer.accept(rule, Arrays.asList(tags.split(",")));
}
});
}
} catch (Exception e) {
log.error("设置标签异常,tuple={}",e);
}
}
}
2.2 结构化DB存储
如果选型 Redis,这里我们可以使用K-V数据存储即可。
代码嘛,就不用写了。简单思路如下
key=关联id
value=集合,json数组即可
set(key,value)
我们可以使用基于Redis缓存+MySQL混合式使用,减少对数据库的频繁查询,毕竟标签也是读多写少的应用场景。一旦选择此方案,我们想到数据的一致性,或者基于Canal实现,脱离业务逻辑处理以解耦。
友情链接:
下面的是我的公众号二维码图片,欢迎关注。