用户行为-需求文档
1.什么是行为
用户行为数据的记录包括了关注、点赞、不喜欢、收藏、阅读等行为
黑马头条项目整个项目开发涉及web展示和大数据分析来给用户推荐文章,如何找出哪些文章是热点文章进行针对性的推荐呢?这个时候需要进行大数据分析的准备工作,埋点。
所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、阅读文章的时长,观看视频的时长等等。
2.关注
如上效果:
当前登录后的用户可以关注作者,也可以取消关注作者
一个用户关注了作者,作者是由用户实名认证以后开通的作者权限,才有了作者信息,作者肯定是app中的一个用户。
从用户的角度出发:一个用户可以关注其他多个作者 —— 我的关注
从作者的角度出发:一个用户(同是作者)也可以拥有很多个粉丝 —— 我的粉丝
3 点赞或取消点赞
- 当前登录的用户点击了”赞“,就要保存当前行为数据
- 可以取消点赞
4 阅读
当用户查看了某一篇文章,需要记录当前用户查看的次数,阅读时长(非必要),阅读文章的比例(非必要),加载的时长(非必要)
5 不喜欢
为什么会有不喜欢?
一旦用户点击了不喜欢,不再给当前用户推荐这一类型的文章信息
6 收藏
记录当前登录人收藏的文章
7 文章详情-行为数据回显
主要是用来展示文章的关系,app端用户必须登录,判断当前用户是否已经关注该文章的作者、是否收藏了此文章、是否点赞了文章、是否不喜欢该文章等
例:如果当前用户点赞了该文章,点赞按钮进行高亮,其他功能类似。
8 注意:
- 所有的行为数据,都存储到redis中
- 点赞、阅读、不喜欢需要专门创建一个微服务来处理数据,新建模块:heima-leadnews-behavior
- 关注需要在heima-leadnews-user微服务中实现
- 收藏与文章详情数据回显在heima-leadnews-article微服务中实现
用户行为-接口文档
1 点赞
接口地址:/api/v1/likes_behavior
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"articleId": 0,
"operation": 0,
"type": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | LikesBehaviorDto | LikesBehaviorDto |
articleId | 文章id | false | long | ||
operation | 0 点赞 1 取消点赞 | false | short | ||
type | 0文章 1动态 2评论 | false | short |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"code": 0,
"data": {},
"errorMessage": "",
"host": ""
}
2 阅读
接口地址:/api/v1/read_behavior
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"articleId": 0,
"count": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 |
---|---|---|---|
dto | dto | body | true |
articleId | 文章id | long | false |
count | 阅读次数 | int | false |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"code": 0,
"data": {},
"errorMessage": "",
"host": ""
}
3 不喜欢
接口地址:/api/v1/un_likes_behavior
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"articleId": 0,
"type": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | UnLikesBehaviorDto | UnLikesBehaviorDto |
articleId | 文章id | false | long | ||
type | 0 不喜欢 1 取消不喜欢 | false | short |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"code": 0,
"data": {},
"errorMessage": "",
"host": ""
}
4 关注与取消关注
接口地址:/api/v1/user/user_follow
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"articleId": 0,
"authorId": 0,
"operation": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | UserRelationDto | UserRelationDto |
articleId | 文章id | false | long | ||
authorId | 作者id | false | int | ||
operation | 0 关注 1 取消 | false | short |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"code": 0,+
"data": {},
"errorMessage": "",
"host": ""
}
5 文章收藏
接口地址:/api/v1/collection_behavior
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"entryId": 0,
"operation": 0,
"publishedTime": "",
"type": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | CollectionBehaviorDto | CollectionBehaviorDto |
entryId | 文章id | false | long | ||
operation | 0收藏 1取消收藏 | false | short | ||
publishedTime | 发布时间 | false | date | ||
type | 0文章 1动态 | false | short |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"code": 0,
"data": {},
"errorMessage": "",
"host": ""
}
6 加载文章行为-数据回显
接口地址:/api/v1/article/load_article_behavior
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{
"articleId": 0,
"authorId": 0
}
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | ArticleInfoDto | ArticleInfoDto |
articleId | 文章id | false | long | ||
authorId | 作者id | false | int |
响应状态:
状态码 | 说明 | schema |
---|---|---|
200 | OK | ResponseResult |
201 | Created | |
401 | Unauthorized | |
403 | Forbidden | |
404 | Not Found |
响应参数:
参数名称 | 参数说明 | 类型 | schema |
---|---|---|---|
code | integer(int32) | integer(int32) | |
data | object | ||
errorMessage | string | ||
host | string |
响应示例:
{
"host":null,
"code":200,
"errorMessage":"操作成功",
"data":{
"islike":false,
"isunlike":false,
"iscollection":false,
"isfollow":false
}
}
jackson进行序列化和反序列化解决
- 当后端响应给前端的数据中包含了id或者特殊标识(可自定义)的时候,把当前数据进行转换为String类型
- 当前端传递后后端的dto中有id或者特殊标识(可自定义)的时候,把当前数据转为Integer或Long类型。
特殊标识类说明:
IdEncrypt 自定义注解 作用在需要转换类型的字段属性上,用于非id的属性上 在model包下
package com.heima.model.common.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@JacksonAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface IdEncrypt {
}
序列化和反序列化类说明:以下类理解为主,可直接在资料文件夹下拷贝到leadnews-common模块中使用。
ConfusionSerializer 用于序列化自增数字的混淆
javapublic class ConfusionSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { try { if (value != null) { jsonGenerator.writeString(value.toString()); return; } }catch (Exception e){ e.printStackTrace(); } serializers.defaultSerializeValue(value, jsonGenerator); } }
ConfusionDeserializer 用于反序列化自增数字的混淆解密
javapublic class ConfusionDeserializer extends JsonDeserializer<Object> { JsonDeserializer<Object> deserializer = null; JavaType type =null; public ConfusionDeserializer(JsonDeserializer<Object> deserializer, JavaType type){ this.deserializer = deserializer; this.type = type; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{ try { if(type!=null){ if(type.getTypeName().contains("Long")){ return Long.valueOf(p.getValueAsString()); } if(type.getTypeName().contains("Integer")){ return Integer.valueOf(p.getValueAsString()); } } return IdsUtils.decryptLong(p.getValueAsString()); }catch (Exception e){ if(deserializer!=null){ return deserializer.deserialize(p,ctxt); }else { return p.getCurrentValue(); } } } }
ConfusionSerializerModifier 用于过滤序列化时处理的字段
javapublic class ConfusionSerializerModifier extends BeanSerializerModifier { @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { List<BeanPropertyWriter> newWriter = new ArrayList<>(); for(BeanPropertyWriter writer : beanProperties){ String name = writer.getType().getTypeName(); if(null == writer.getAnnotation(IdEncrypt.class) && !writer.getName().equalsIgnoreCase("id")){ newWriter.add(writer); } else { writer.assignSerializer(new ConfusionSerializer()); newWriter.add(writer); } } return newWriter; } }
ConfusionDeserializerModifier 用于过滤反序列化时处理的字段
javapublic class ConfusionDeserializerModifier extends BeanDeserializerModifier { @Override public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDescription, final BeanDeserializerBuilder builder) { Iterator it = builder.getProperties(); while (it.hasNext()) { SettableBeanProperty p = (SettableBeanProperty) it.next(); if ((null != p.getAnnotation(IdEncrypt.class)||p.getName().equalsIgnoreCase("id"))) { JsonDeserializer<Object> current = p.getValueDeserializer(); builder.addOrReplaceProperty(p.withValueDeserializer(new ConfusionDeserializer(p.getValueDeserializer(),p.getType())), true); } } return builder; } }
ConfusionModule 用于注册模块和修改器
javapublic class ConfusionModule extends Module { public final static String MODULE_NAME = "jackson-confusion-encryption"; public final static Version VERSION = new Version(1,0,0,null,"heima",MODULE_NAME); @Override public String getModuleName() { return MODULE_NAME; } @Override public Version version() { return VERSION; } @Override public void setupModule(SetupContext context) { context.addBeanSerializerModifier(new ConfusionSerializerModifier()); context.addBeanDeserializerModifier(new ConfusionDeserializerModifier()); } /** * 注册当前模块 * @return */ public static ObjectMapper registerModule(ObjectMapper objectMapper){ //CamelCase策略,Java对象属性:personId,序列化后属性:persionId //PascalCase策略,Java对象属性:personId,序列化后属性:PersonId //SnakeCase策略,Java对象属性:personId,序列化后属性:person_id //KebabCase策略,Java对象属性:personId,序列化后属性:person-id // 忽略多余字段,抛错 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); return objectMapper.registerModule(new ConfusionModule()); } }
InitJacksonConfig 提供自动化配置默认ObjectMapper,让整个框架自动处理id混淆
java@Configuration public class InitJacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper = ConfusionModule.registerModule(objectMapper); return objectMapper; } }
在common模块中的自动配置的spring.factories中添加如下内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.swagger.SwaggerConfiguration,\
com.heima.common.swagger.Swagger2Configuration,\
com.heima.common.exception.ExceptionCatch,\
com.heima.common.aliyun.GreenTextScan,\
com.heima.common.aliyun.GreenImageScan,\
com.heima.common.jackson.InitJacksonConfig
在dto中传递参数的时候如果想要把数值类型转为json,可以使用@IdEncrypt
标识字段进行转换,如下:
@Data
public class ArticleInfoDto {
// 文章ID
@IdEncrypt
Long articleId;
}