一、课前准备
1. long类型精度丢失问题
解决方案:
- 当后端响应给前端的数据中包含了id或者特殊标识(可自定义)的时候,把当前数据进行转换为String类型
- 当前端传递给后端的dto中有id或者特殊标识(可自定义)的时候,把当前数据转为Integer或Long类型。
解决步骤:
①把课前资料里的jackson包拷贝放到heima-leadnews-common模块下
序列化和反序列化说明:
- ConfusionSerializer:用于序列化自增数字的混淆
public 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:永续反序列化自增数字的混淆解密
public 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:用于过滤序列化时处理的字段
public 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:用于过滤反序列化时处理的字段
public 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:用于注册模块和修改器
public 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,让整个框架自动处理混淆
@Configuration
public class InitJacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = ConfusionModule.registerModule(objectMapper);
return objectMapper;
}
}
②在heima-leadnews-common的spring.factories文件里添加如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.exception.ExceptionCatch,\
com.heima.common.swagger.SwaggerConfiguration,\
com.heima.common.swagger.Swagger2Configuration,\
com.heima.common.aliyun.GreenImageScan,\
com.heima.common.aliyun.GreenTextScan,\
com.heima.common.tess4j.Tess4jClient,\
com.heima.common.redis.CacheService,\
com.heima.common.jackson.InitJacksonConfig
③在heima-leadnews-model模块添加IdEncrypt自定义注解
IdEncrypt 自定义注解作用在需要转换类型的字段属性上,用于非id的属性上
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 {
}
2. 更改IPv4地址
网络设置,解决跨域问题
二、用户行为 - 需求分析与实现
1. 什么是行为
用户行为数据的记录包括了关注、点赞、不喜欢、收藏、阅读等行为。
黑马头条项目整个项目开发设计Web展示和大数据分析来给用户推荐文章,如何找出哪些文章是热点文章进行针对性的推荐呢?这个时候需要进行大数据分析的准备工作,埋点。
所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、阅读文章的时长、观看适配的时长等等。
注意
- 所有的行为数据,都存储到redis中
- 点赞、阅读、不喜欢需要专门创建一个微服务来处理数据,新建模块:heima-leadnews-behavior
- 关注需要在heima-leadnews-user微服务中实现
- 收藏与文章详情数据回显在heima-leadnews-article微服务中实现
2. 关注
2.1 需求分析
如上效果:当前登录后的用户可以关注作者,也可以取消关注作者
一个用户关注了作者,作者是由用户实名认证以后开通的作者权限,才有了作者信息,作者肯定是app中的一个用户。
- 从用户的角度出发:一个用户可以关注其他多个作者 —— 我的关注
- 从作者的角度出发:一个用户(同是作者)也可以拥有很多个粉丝 —— 我的粉丝
2.2 接口设计
说明 | |
接口地址 | /api/v1/user/user_follow |
请求发送 | POST |
参数 | UserRelationDto |
响应 | ResponseResult |
2.3 实现步骤
heima-leadnews-user微服务
步骤①:在heima-leadnews-model的user模块下新增UserRelationDto
package com.heima.model.user.dtos;
import com.heima.model.common.annotation.IdEncrypt;
import lombok.Data;
@Data
public class UserRelationDto {
// 文章作者ID
@IdEncrypt
Integer authorId;
// 文章id
@IdEncrypt
Long articleId;
/**
* 操作方式
* 0 关注
* 1 取消
*/
Short operation;
}
②在common模块下新增常量类 - BehaviorConstants
package com.heima.common.constants;
public class BehaviorConstants {
public static final String LIKE_BEHAVIOR="LIKE-BEHAVIOR-";
public static final String UN_LIKE_BEHAVIOR="UNLIKE-BEHAVIOR-";
public static final String COLLECTION_BEHAVIOR="COLLECTION-BEHAVIOR-";
public static final String READ_BEHAVIOR="READ-BEHAVIOR-";
public static final String APUSER_FOLLOW_RELATION="APUSER-FOLLOW-";
public static final String APUSER_FANS_RELATION="APUSER-FANS-";
}
③ApUserRelationController
package com.heima.user.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.UserRelationDto;
import com.heima.user.service.ApUserRelationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/user")
public class ApUserRelationController {
@Autowired
private ApUserRelationService apUserRelationService;
/**
* 关注与取消关注
* @param dto
* @return
*/
@PostMapping("/user_follow")
public ResponseResult follow(@RequestBody UserRelationDto dto) {
return apUserRelationService.follow(dto);
}
}
④ApUserRelationService
package com.heima.user.service;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.UserRelationDto;
public interface ApUserRelationService {
/**
* 关注与取消关注
* @param dto
* @return
*/
ResponseResult follow(UserRelationDto dto);
}
⑤ApUserRelationServiceImpl
package com.heima.user.service.impl;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.UserRelationDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.user.service.ApUserRelationService;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ApUserRelationServiceImpl implements ApUserRelationService {
@Autowired
private CacheService cacheService;
/**
* 用户关注:0 / 取消关注:1
* @param dto
* @return
*/
@Override
public ResponseResult follow(UserRelationDto dto) {
// 1. 参数校验
if(dto.getOperation() == null || dto.getOperation() < 0 || dto.getOperation() > 1) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 判断是否登录
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
Integer apUserId = user.getId();
// 3. 关注 apUser:follow apUser:fans
Integer followUserId = dto.getAuthorId();
if(dto.getOperation() == 0) {
// 将对方写入我的关注中
cacheService.zAdd(BehaviorConstants.APUSER_FOLLOW_RELATION + apUserId, followUserId.toString(), System.currentTimeMillis());
// 将我写入对方的粉丝中
cacheService.zAdd(BehaviorConstants.APUSER_FANS_RELATION + followUserId, apUserId.toString(), System.currentTimeMillis());
} else {
// 取消关注
cacheService.zRemove(BehaviorConstants.APUSER_FOLLOW_RELATION + apUserId, followUserId.toString());
cacheService.zRemove(BehaviorConstants.APUSER_FANS_RELATION + followUserId, apUserId.toString());
}
// 4. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
⑥在heima-leadnews-user模块下新增interceptor/AppTokenInterceptor拦截器
package com.heima.user.interceptor;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AppTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if(userId != null){
//存入到当前线程中
ApUser apUser = new ApUser();
apUser.setId(Integer.valueOf(userId));
AppThreadLocalUtil.setUser(apUser);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AppThreadLocalUtil.clear();
}
}
⑦在heima-leadnews-user模块下添加config/WebMvcConfig配置
package com.heima.user.config;
import com.heima.user.interceptor.AppTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
}
}
⑧测试
3. 点赞
3.1 需求分析
- 当前登录的用户点击了“赞”,就要保存当前行为数据
- 可以取消点赞
3.2 接口设计
说明 | |
接口地址 | /api/v1/likes_behavior |
请求发送 | POST |
参数 | LikesBehaviorDto |
响应 | ResponseResult |
3.3 实现步骤
heima-leadnews-behavior微服务
步骤①:新建模块heima-leadnews-behavior
②在resources目录下新建bootstrap.yml
server:
port: 51805
spring:
application:
name: leadnews-behavior
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
③添加引导类BehaviorApplication
package com.heima.behavior;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class BehaviorApplication {
public static void main(String[] args) {
SpringApplication.run(BehaviorApplication.class, args);
}
}
④添加拦截器AppTokenInterceptor
package com.heima.behavior.interceptor;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AppTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if(userId != null){
//存入到当前线程中
ApUser apUser = new ApUser();
apUser.setId(Integer.valueOf(userId));
AppThreadLocalUtil.setUser(apUser);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AppThreadLocalUtil.clear();
}
}
⑤WebMvcConfig
package com.heima.behavior.config;
import com.heima.behavior.interceptor.AppTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
}
}
⑥新增一些实体类和常量类
在heima-leadnews-model的behavior/dtos下添加实体类LikesBehaviorDto
package com.heima.model.behavior.dtos;
import lombok.Data;
@Data
public class LikesBehaviorDto {
// 文章、动态、评论等ID
Long articleId;
/**
* 喜欢内容类型
* 0文章
* 1动态
* 2评论
*/
Short type;
/**
* 喜欢操作方式
* 0 点赞
* 1 取消点赞
*/
Short operation;
}
在heima-leadnews-common/constants下新增常量类HotArticleConstants
package com.heima.common.constants;
public class HotArticleConstants {
public static final String HOT_ARTICLE_SCORE_TOPIC="hot.article.score.topic";
public static final String HOT_ARTICLE_INCR_HANDLE_TOPIC="hot.article.incr.handle.topic";
}
在heima-leadnews-model/article/vos 添加实体类HotArticleVo
package com.heima.model.article.vos;
import com.heima.model.article.pojos.ApArticle;
import lombok.Data;
@Data
public class HotArticleVo extends ApArticle {
/**
* 文章分值
*/
private Integer score;
}
在heima-leadnews-model模块下的mess包新增以下两个实体类
ArticleVisitStreamMess
package com.heima.model.mess;
import lombok.Data;
@Data
public class ArticleVisitStreamMess {
/**
* 文章id
*/
private Long articleId;
/**
* 阅读
*/
private int view;
/**
* 收藏
*/
private int collect;
/**
* 评论
*/
private int comment;
/**
* 点赞
*/
private int like;
}
UpdateArticleMess
package com.heima.model.mess;
import lombok.Data;
@Data
public class UpdateArticleMess {
/**
* 修改文章的字段类型
*/
private UpdateArticleType type;
/**
* 文章ID
*/
private Long articleId;
/**
* 修改数据的增量,可为正负
*/
private Integer add;
public enum UpdateArticleType{
COLLECTION,COMMENT,LIKES,VIEWS;
}
}
⑦ApLikesBehaviorController
package com.heima.behavior.controller;
import com.heima.behavior.service.ApLikesBehaviorService;
import com.heima.model.behavior.dtos.LikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/likes_behavior")
public class ApLikesBehaviorController {
@Autowired
private ApLikesBehaviorService apLikesBehaviorService;
/**
* 点赞
* @param dto
* @return
*/
@PostMapping
public ResponseResult like(@RequestBody LikesBehaviorDto dto) {
return apLikesBehaviorService.like(dto);
}
}
⑧ApLikesBehaviorService
package com.heima.behavior.service;
import com.heima.model.behavior.dtos.LikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
public interface ApLikesBehaviorService {
/**
* 点赞
* @param dto
* @return
*/
ResponseResult like(LikesBehaviorDto dto);
}
⑨ApLikesBehaviorServiceImpl
package com.heima.behavior.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.behavior.service.ApLikesBehaviorService;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.constants.HotArticleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.behavior.dtos.LikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@Slf4j
public class ApLikesBehaviorServiceImpl implements ApLikesBehaviorService {
@Autowired
private CacheService cacheService;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 点赞或取消点赞
* @param dto 0:点赞 1:取消点赞
* @return
*/
@Override
public ResponseResult like(LikesBehaviorDto dto) {
// 1. 检查参数
if(dto == null || dto.getArticleId() == null || checkParam(dto)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 是否登录
ApUser apUser = AppThreadLocalUtil.getUser();
if(apUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
UpdateArticleMess mess = new UpdateArticleMess();
mess.setArticleId(dto.getArticleId());
mess.setType(UpdateArticleMess.UpdateArticleType.LIKES);
// 3. 点赞,保存数据
if(dto.getOperation() == 0) {
Object obj = cacheService.hGet(BehaviorConstants.LIKE_BEHAVIOR + dto.getArticleId().toString(), apUser.getId().toString());
if(obj != null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "已点赞");
}
// 保存当前key
log.info("保存当前key:{}, {}, {}", dto.getArticleId(), apUser.getId(), dto);
cacheService.hPut(BehaviorConstants.LIKE_BEHAVIOR + dto.getArticleId().toString(), apUser.getId().toString(), JSON.toJSONString(dto));
mess.setAdd(1);
} else {
// 取消点赞,删除当前key
log.info("删除当前key:{}, {}", dto.getArticleId(), apUser.getId());
cacheService.hDelete(BehaviorConstants.LIKE_BEHAVIOR + dto.getArticleId().toString(), apUser.getId().toString());
mess.setAdd(-1);
}
// 4. 发送消息,数据聚合
kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC,JSON.toJSONString(mess));
// 5. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 检查参数
* @param dto
* @return
*/
private boolean checkParam(LikesBehaviorDto dto) {
// 参数有误
if(dto.getType() > 2 || dto.getType() < 0 || dto.getOperation() > 1 || dto.getOperation() < 0) {
return true;
}
return false;
}
}
⑩在heima-leadnews-article模块下stream包下新增HotArticleStreamHandler
package com.heima.article.stream;
import com.alibaba.fastjson.JSON;
import com.heima.common.constants.HotArticleConstants;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.mess.UpdateArticleMess;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.streams.KeyValue;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.kstream.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
/**
* 用户行为数据的实时聚合
*/
@Configuration
@Slf4j
public class HotArticleStreamHandler {
@Bean
public KStream<String, String> kStream(StreamsBuilder streamsBuilder) {
// 1. 接收消息
KStream<String, String> stream = streamsBuilder.stream(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC);
// 2. 聚合流式处理
stream.map((key, value) -> {
UpdateArticleMess mess = JSON.parseObject(value, UpdateArticleMess.class);
// 重置消息的key:1234343434 和 value:like:1
return new KeyValue<>(mess.getArticleId().toString(), mess.getType().name() + ":" + mess.getAdd());
})
// 按照文章id进行聚合
.groupBy((key, value) -> key)
// 时间窗口:10秒
.windowedBy(TimeWindows.of(Duration.ofSeconds(10)))
// 自行的完成聚合的计算
.aggregate(new Initializer<String>() {
/**
* 初始方法,返回值是消息的value
*
* @return
*/
@Override
public String apply() {
return "COLLECTION:0,COMMENT:0,LIKES:0,VIEWS:0";
}
// 真正的聚合操作,返回值是消息的value
}, new Aggregator<String, String, String>() {
/**
* @param key
* @param value
* @param aggValue
* @return
*/
@Override
public String apply(String key, String value, String aggValue) {
System.out.println(value);
if(StringUtils.isBlank(value)) {
return aggValue;
}
String[] aggAry = aggValue.split(",");
int col = 0, com = 0, like = 0, vie = 0;
for (String agg : aggAry) {
String[] split = agg.split(":");
// 获得初始值,也是时间窗口内计算之后的值
// 根据新的消息值对相应的计数进行累加,确保计数器保存最新状态
switch (UpdateArticleMess.UpdateArticleType.valueOf(split[0])) {
case COLLECTION:
col = Integer.parseInt(split[1]);
break;
case COMMENT:
com = Integer.parseInt(split[1]);
break;
case LIKES:
like = Integer.parseInt(split[1]);
break;
case VIEWS:
vie = Integer.parseInt(split[1]);
break;
}
}
// 累加操作 likes:1
String[] valAry = value.split(":");
switch (UpdateArticleMess.UpdateArticleType.valueOf(valAry[0])) {
case COLLECTION:
col += Integer.parseInt(valAry[1]);
break;
case COMMENT:
com += Integer.parseInt(valAry[1]);
break;
case LIKES:
like += Integer.parseInt(valAry[1]);
break;
case VIEWS:
vie += Integer.parseInt(valAry[1]);
break;
}
String formatStr = String.format("COLLECTION:%d,COMMENT:%d,LIKES:%d,VIEWS:%d", col, com, like, vie);
System.out.println("文章的id: " + key);
System.out.println("当前时间窗口内的消息处理结果: " + formatStr);
return formatStr;
}
}, Materialized.as("hot-article-stream-count-001"))
.toStream()
.map((key, value) -> {
return new KeyValue<>(key.key().toString(), formatObj(key.key().toString(), value));
})
// 发送消息
.to(HotArticleConstants.HOT_ARTICLE_INCR_HANDLE_TOPIC);
return stream;
}
/**
* 格式化消息的value数据
* @param articleId
* @param value
* @return
*/
private String formatObj(String articleId, String value) {
ArticleVisitStreamMess mess = new ArticleVisitStreamMess();
mess.setArticleId(Long.valueOf(articleId));
//COLLECTION:0,COMMENT:0,LIKES:0,VIEWS:0
String[] valAry = value.split(",");
for (String val : valAry) {
String[] split = val.split(":");
switch (UpdateArticleMess.UpdateArticleType.valueOf(split[0])) {
case COLLECTION:
mess.setCollect(Integer.parseInt(split[1]));
break;
case COMMENT:
mess.setComment(Integer.parseInt(split[1]));
break;
case LIKES:
mess.setLike(Integer.parseInt(split[1]));
break;
case VIEWS:
mess.setView(Integer.parseInt(split[1]));
break;
}
}
log.info("聚合消息处理之后的结果为:{}", JSON.toJSONString(mess));
return JSON.toJSONString(mess);
}
}
⑪新增监听器ArticleIncrHandlerListener
package com.heima.article.listener;
import com.alibaba.fastjson.JSON;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.HotArticleConstants;
import com.heima.model.mess.ArticleVisitStreamMess;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ArticleIncrHandleListener {
@Autowired
private ApArticleService apArticleService;
@KafkaListener(topics = HotArticleConstants.HOT_ARTICLE_INCR_HANDLE_TOPIC)
public void onMessage(String msg) {
if(StringUtils.isNotBlank(msg)) {
ArticleVisitStreamMess articleVisitStreamMess = JSON.parseObject(msg, ArticleVisitStreamMess.class);
apArticleService.updateScore(articleVisitStreamMess);
}
}
}
⑫ApArticleService中新增业务方法
/**
* 更新文章的分值,同时更新缓存中的热点文章数据
* @param mess
*/
public void updateScore(ArticleVisitStreamMess mess);
⑬ApArticleServiceImpl实现业务方法
/**
* 更新文章的分值,同时更新缓存中的热点文章数据
* @param mess
*/
@Override
public void updateScore(ArticleVisitStreamMess mess) {
// 1. 更新文章的阅读、点赞、收藏、评论的数量
ApArticle apArticle = updateArticle(mess);
// 2. 计算文章的分值
Integer score = computeScore(apArticle);
score = score * 3;
// 3. 替换当前文章对应的频道的热点数据
replaceDataToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());
// 4. 替换推荐对应的热点数据
replaceDataToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
}
/**
* 替换数据并且存入到redis
* @param apArticle
* @param score
* @param s
*/
private void replaceDataToRedis(ApArticle apArticle, Integer score, String s) {
String articleListStr = cacheService.get(s);
if (StringUtils.isNotBlank(articleListStr)) {
List<HotArticleVo> hotArticleVoList = JSON.parseArray(articleListStr, HotArticleVo.class);
boolean flag = true;
//如果缓存中存在该文章,只更新分值
for (HotArticleVo hotArticleVo : hotArticleVoList) {
if (hotArticleVo.getId().equals(apArticle.getId())) {
hotArticleVo.setScore(score);
flag = false;
break;
}
}
//如果缓存中不存在,查询缓存中分值最小的一条数据,进行分值的比较,如果当前文章的分值大于缓存中的数据,就替换
if (flag) {
if (hotArticleVoList.size() >= 30) {
hotArticleVoList = hotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
HotArticleVo lastHot = hotArticleVoList.get(hotArticleVoList.size() - 1);
if (lastHot.getScore() < score) {
hotArticleVoList.remove(lastHot);
HotArticleVo hot = new HotArticleVo();
BeanUtils.copyProperties(apArticle, hot);
hot.setScore(score);
hotArticleVoList.add(hot);
}
} else {
HotArticleVo hot = new HotArticleVo();
BeanUtils.copyProperties(apArticle, hot);
hot.setScore(score);
hotArticleVoList.add(hot);
}
}
//缓存到redis
hotArticleVoList = hotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
cacheService.set(s, JSON.toJSONString(hotArticleVoList));
}
}
/**
* 计算文章的具体分值
* @param apArticle
* @return
*/
private Integer computeScore(ApArticle apArticle) {
Integer score = 0;
if(apArticle.getLikes() != null) {
score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
}
if(apArticle.getViews() != null){
score += apArticle.getViews();
}
if(apArticle.getComment() != null){
score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
}
if(apArticle.getCollection() != null){
score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
}
return score;
}
/**
* 更新文章行为数量
* @param mess
* @return
*/
private ApArticle updateArticle(ArticleVisitStreamMess mess) {
ApArticle apArticle = getById(mess.getArticleId());
apArticle.setCollection(apArticle.getCollection()==null?0:apArticle.getCollection()+mess.getCollect());
apArticle.setComment(apArticle.getComment()==null?0:apArticle.getComment()+mess.getComment());
apArticle.setLikes(apArticle.getLikes()==null?0:apArticle.getLikes()+mess.getLike());
apArticle.setViews(apArticle.getViews()==null?0:apArticle.getViews()+mess.getView());
updateById(apArticle);
return apArticle;
}
⑭在heima-leadnews-article的config包下新增KafkaStreamConfig配置类
package com.heima.article.config;
import lombok.Getter;
import lombok.Setter;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.StreamsConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafkaStreams;
import org.springframework.kafka.annotation.KafkaStreamsDefaultConfiguration;
import org.springframework.kafka.config.KafkaStreamsConfiguration;
import java.util.HashMap;
import java.util.Map;
/**
* 通过重新注册KafkaStreamsConfiguration对象,设置自定配置参数
*/
@Setter
@Getter
@Configuration
@EnableKafkaStreams
@ConfigurationProperties(prefix="kafka")
public class KafkaStreamConfig {
private static final int MAX_MESSAGE_SIZE = 16* 1024 * 1024;
private String hosts;
private String group;
@Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration defaultKafkaStreamsConfig() {
Map<String, Object> props = new HashMap<>();
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, hosts);
props.put(StreamsConfig.APPLICATION_ID_CONFIG, this.getGroup()+"_stream_aid");
props.put(StreamsConfig.CLIENT_ID_CONFIG, this.getGroup()+"_stream_cid");
props.put(StreamsConfig.RETRIES_CONFIG, 10);
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
return new KafkaStreamsConfiguration(props);
}
}
⑮测试
4. 阅读
4.1 需求分析
当用户查看了某一篇文章,需要记录当前用户查看的次数,阅读时长(非必要),阅读文章的比例(非必要),加载的时长(非必要)
4.2 接口设计
说明 | |
接口地址 | /api/v1/read_behavior |
请求发送 | POST |
参数 | ReadBehaviorDto |
响应 | ResponseResult |
4.3 实现步骤
heima-leadnews-behavior微服务
步骤①:添加一些实体类
ReadBehaviorDto
package com.heima.model.behavior.dtos;
import lombok.Data;
@Data
public class ReadBehaviorDto {
// 文章、动态、评论等ID
Long articleId;
/**
* 阅读次数
*/
Short count;
/**
* 阅读时长(S)
*/
Integer readDuration;
/**
* 阅读百分比
*/
Short percentage;
/**
* 加载时间
*/
Short loadDuration;
}
②添加ApReadBehaviorController接口
package com.heima.behavior.controller;
import com.heima.behavior.service.ApReadBehaviorService;
import com.heima.model.behavior.dtos.ReadBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/read_behavior")
public class ApReadBehaviorController {
@Autowired
private ApReadBehaviorService apReadBehaviorService;
/**
* 用户行为-阅读
* @param dto
* @return
*/
@PostMapping
public ResponseResult readBehavior(@RequestBody ReadBehaviorDto dto) {
return apReadBehaviorService.readBehavior(dto);
}
}
③ApReadBehaviorService
package com.heima.behavior.service;
import com.heima.model.behavior.dtos.ReadBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
public interface ApReadBehaviorService {
/**
* 用户行为 - 阅读
* @param dto
* @return
*/
ResponseResult readBehavior(ReadBehaviorDto dto);
}
④ApReadBehaviorServiceImpl
package com.heima.behavior.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.behavior.service.ApReadBehaviorService;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.constants.HotArticleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.behavior.dtos.ReadBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@Slf4j
public class ApReadBehaviorServiceImpl implements ApReadBehaviorService {
@Autowired
private CacheService cacheService;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 用户行为 - 阅读
* @param dto
* @return
*/
@Override
public ResponseResult readBehavior(ReadBehaviorDto dto) {
// 1. 检查参数
if(dto == null || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 是否登录
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 3. 更新阅读次数
String readBehaviorJson = (String) cacheService.hGet(BehaviorConstants.READ_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString());
if(StringUtils.isNotBlank(readBehaviorJson)) {
ReadBehaviorDto readBehaviorDto = JSON.parseObject(readBehaviorJson, ReadBehaviorDto.class);
dto.setCount((short) (readBehaviorDto.getCount() + dto.getCount()));
}
// 4. 保存当前key
log.info("保存当前key: {} {} {}", dto.getArticleId(), user.getId(), dto);
cacheService.hPut(BehaviorConstants.READ_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString(), JSON.toJSONString(dto));
// 5. 发送消息,数据聚合
UpdateArticleMess mess = new UpdateArticleMess();
mess.setArticleId(dto.getArticleId());
mess.setType(UpdateArticleMess.UpdateArticleType.VIEWS);
mess.setAdd(1);
kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC, JSON.toJSONString(mess));
// 6. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
⑤测试
5. 不喜欢
5.1 需求分析
为什么会有不喜欢?一旦用户点击了“不喜欢”,不再给当前用户推荐这一类型的文章信息
5.2 接口设计
说明 | |
接口地址 | /api/v1/un_likes_behavior |
请求发送 | POST |
参数 | UnLikesBehaviorDto |
响应 | ResponseResult |
5.3 实现步骤
heima-leadnews-behavior微服务
步骤①:添加实体类UnLikesBehaviorDto
package com.heima.model.behavior.dtos;
import com.heima.model.common.annotation.IdEncrypt;
import lombok.Data;
@Data
public class UnLikesBehaviorDto {
// 文章ID
@IdEncrypt
Long articleId;
/**
* 不喜欢操作方式
* 0 不喜欢
* 1 取消不喜欢
*/
Short type;
}
②ApUnlikesBehaviorController
package com.heima.behavior.controller;
import com.heima.behavior.service.ApUnlikesBehaviorService;
import com.heima.model.behavior.dtos.UnLikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/un_likes_behavior")
public class ApUnlikesBehaviorController {
@Autowired
private ApUnlikesBehaviorService apUnlikesBehaviorService;
/**
* 用户行为 - 不喜欢
* @param dto
* @return
*/
@PostMapping
public ResponseResult unlike(@RequestBody UnLikesBehaviorDto dto) {
return apUnlikesBehaviorService.unlike(dto);
}
}
③ApUnlikesBehaviorService
package com.heima.behavior.service;
import com.heima.model.behavior.dtos.UnLikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
public interface ApUnlikesBehaviorService {
/**
* 用户行为 - 不喜欢
* @param dto
* @return
*/
ResponseResult unlike(UnLikesBehaviorDto dto);
}
④ApUnlikesBehaviorServiceImpl
package com.heima.behavior.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.behavior.service.ApUnlikesBehaviorService;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.behavior.dtos.UnLikesBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* APP不喜欢行为表 服务实现类
*/
@Service
@Slf4j
public class ApUnlikesBehaviorServiceImpl implements ApUnlikesBehaviorService {
@Autowired
private CacheService cacheService;
/**
* 用户行为 - 不喜欢
* @param dto
* @return
*/
@Override
public ResponseResult unlike(UnLikesBehaviorDto dto) {
// 1. 检查参数
if(dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 获取用户信息
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 3. 根据操作类型进行对应操作
if(dto.getType() == 0) {
// 不喜欢
log.info("保存当前key: {} {} {}", dto.getArticleId(), user.getId(), dto);
cacheService.hPut(BehaviorConstants.UN_LIKE_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString(), JSON.toJSONString(dto));
} else {
// 取消不喜欢
log.info("删除当前key:{} ,{}, {}", dto.getArticleId(), user.getId(), dto);
cacheService.hDelete(BehaviorConstants.UN_LIKE_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString());
}
// 4. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
⑤测试
6. 收藏
6.1 需求分析
- 记录当前登录用户收藏的文章
6.2 接口设计
说明 | |
接口地址 | /api/v1/collection_behavior |
请求发送 | POST |
参数 | CollectionBehaviorDto |
响应 | ResponseResult |
6.3 实现步骤
heima-leadnews-article微服务
步骤①:新增CollectionBehaviorDto
package com.heima.model.article.dtos;
import com.heima.model.common.annotation.IdEncrypt;
import lombok.Data;
import java.util.Date;
@Data
public class CollectionBehaviorDto {
// 文章、动态ID
@IdEncrypt
Long entryId;
/**
* 收藏内容类型
* 0文章
* 1动态
*/
Short type;
/**
* 操作类型
* 0收藏
* 1取消收藏
*/
Short operation;
Date publishedTime;
}
②ApCollectionController
package com.heima.article.controller.v1;
import com.heima.article.service.ApCollectionService;
import com.heima.model.article.dtos.CollectionBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/collection_behavior")
public class ApCollectionController {
@Autowired
private ApCollectionService apCollectionService;
/**
* 文章收藏
* @param dto
* @return
*/
@PostMapping
public ResponseResult collection(@RequestBody CollectionBehaviorDto dto) {
return apCollectionService.collection(dto);
}
}
③ApCollectionService
package com.heima.article.service;
import com.heima.model.article.dtos.CollectionBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
public interface ApCollectionService {
/**
* 文章收藏
* @param dto
* @return
*/
ResponseResult collection(CollectionBehaviorDto dto);
}
④ApCollectionServiceImpl
package com.heima.article.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.article.service.ApCollectionService;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.CollectionBehaviorDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class ApCollectionServiceImpl implements ApCollectionService {
@Autowired
private CacheService cacheService;
@Override
public ResponseResult collection(CollectionBehaviorDto dto) {
// 1. 检查参数
if(dto.getEntryId() == null || dto == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 判断是否登录
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 3. 查询收藏记录
String collectionJson = (String) cacheService.hGet(BehaviorConstants.COLLECTION_BEHAVIOR + user.getId(), dto.getEntryId().toString());
if(StringUtils.isNotBlank(collectionJson) && dto.getOperation() == 0) {
// 已收藏
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "已收藏");
}
// 4. 收藏
if(dto.getOperation() == 0) {
log.info("文章收藏,保存key: {}, {}, {}", dto.getEntryId(), user.getId().toString(), JSON.toJSONString(dto));
cacheService.hPut(BehaviorConstants.COLLECTION_BEHAVIOR + user.getId(), dto.getEntryId().toString(), JSON.toJSONString(dto));
} else {
// 取消收藏
log.info("文章收藏,删除key: {}, {}, {}", dto.getEntryId().toString(), user.getId().toString(), JSON.toJSONString(dto));
cacheService.hDelete(BehaviorConstants.COLLECTION_BEHAVIOR + user.getId(), dto.getEntryId().toString());
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
⑤测试
7. 文章详情 - 行为数据回显
7.1 需求分析
主要是用来展示文章的关系,app端用户必须登录,判断当前用户是否已经关注该文章的作者、是否收藏了此文章、是否点赞了文章、是否不喜欢该文章等。
例如:如果当前用户点赞了该文章,点赞按钮进行高亮,其他功能类似。
7.2 接口设计
说明 | |
接口地址 | /api/v1article/load_article_behavior |
请求发送 | POST |
参数 | ArticleInfoDto |
响应 | ResponseResult |
7.3 实现步骤
heima-leadnews-article微服务
①ArticleInfoController
package com.heima.article.controller.v1;
import com.heima.article.service.ApArticleService;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleInfoController {
@Autowired
private ApArticleService apArticleService;
/**
* 加载文章详情,数据回显
* @param dto
* @return
*/
@PostMapping("/load_article_behavior")
public ResponseResult loadArticleBehavior(@RequestBody ArticleInfoDto dto) {
return apArticleService.loadArticleBehavior(dto);
}
}
②ApArticleService
/**
* 加载文章详情,数据回显
* @param dto
* @return
*/
ResponseResult loadArticleBehavior(ArticleInfoDto dto);
③ApArticleServiceImpl
/**
* 加载文章详情,数据回显
* @param dto
* @return
*/
@Override
public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {
// 1. 检查参数
if(dto == null || dto.getArticleId() == null || dto.getAuthorId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//{ "isfollow": true, "islike": true,"isunlike": false,"iscollection": true }
boolean isFollow = false, isLike = false, isUnlike = false, isCollection = false;
// 2. 获取用户信息
ApUser user = AppThreadLocalUtil.getUser();
// 3. 用户行为
if(user == null) {
// 3.1 喜欢行为
String likeBehaviorJson = (String) cacheService.hGet(BehaviorConstants.LIKE_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString());
if(StringUtils.isNotBlank(likeBehaviorJson)){
isLike = true;
}
// 3.2 不喜欢的行为
String unLikeBehaviorJson = (String) cacheService.hGet(BehaviorConstants.UN_LIKE_BEHAVIOR + dto.getArticleId().toString(), user.getId().toString());
if(StringUtils.isNotBlank(unLikeBehaviorJson)){
isUnlike = true;
}
// 3.3 是否收藏
String collctionJson = (String) cacheService.hGet(BehaviorConstants.COLLECTION_BEHAVIOR+user.getId(),dto.getArticleId().toString());
if(StringUtils.isNotBlank(collctionJson)){
isCollection = true;
}
// 3.4 是否关注
Double score = cacheService.zScore(BehaviorConstants.APUSER_FOLLOW_RELATION + user.getId(), dto.getAuthorId().toString());
System.out.println(score);
if(score != null){
isFollow = true;
}
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("isfollow", isFollow);
resultMap.put("islike", isLike);
resultMap.put("isunlike", isUnlike);
resultMap.put("iscollection", isCollection);
return ResponseResult.okResult(resultMap);
}
④测试
8. 文章评论
8.1 需求分析
(1)发布评论
- 在文章详情中发表评论,输入内容发表评论
- 必须登录后才能发表评论
- 评论内容不能超过140字
- 评论内容需要要文本垃圾检测
(2)点赞
- 用户点赞,可以增加点赞数量,点赞后不仅仅要增加点赞数,还要记录当前用户对于当前评论的数据记录
- 用户取消点赞,点赞减一,更新点赞数据
(3)评论列表
- 展示评论内容、评论的作者、点赞数、回复数、时间
- 查询评论列表,根据当前文章进行检索,按照创建时间倒序、分页查询(默认10条数据)
- 在结果返回中,如果当天登录人点赞了某条评论,需要高亮展示“点赞按钮”
(4)评论回复-发布
- 需求与发表评论一致
- 保存成功后需要更新评论的回复数量
(5)点赞评论回复
- 需求与评论的点赞一致
(6)查询评论回复列表
- 需求与评论的列表查看一致
8.2 接口设计
CommentController:
保存评论
说明 | |
接口地址 | /api/v1/comment/save |
请求方式 | POST |
参数 | CommentSaveDto |
响应 | ResponseResult |
点赞评论
说明 | |
接口地址 | /api/v1/comment/like |
请求方式 | POST |
参数 | CommentLikeDto |
响应 | ResponseResult |
查询评论列表
说明 | |
接口地址 | /api/v1/comment/load |
请求方式 | POST |
参数 | CommentDto |
响应 | ResponseResult |
CommentRepayController:
查询评论回复列表:
说明 | |
接口地址 | /api/v1/comment_repay/load |
请求方式 | POST |
参数 | CommentRepayDto |
响应 | ResponseResult |
保存:
说明 | |
接口地址 | /api/v1/comment_repay/save |
请求方式 | POST |
参数 | CommentRepaySaveDto |
响应 | ResponseResult |
点赞回复的评论:
说明 | |
接口地址 | /api/v1/comment_repay/like |
请求方式 | POST |
参数 | CommentRepayLikeDto |
响应 | ResponseResult |
9. 自媒体评论管理
9.1 需求分析
(1)评论管理查询
- 按照时间倒序查询发布的文章
- 展示文章标题、评论状态、评论总量
- 分页可以查询
- 可以按照时间范围查询
(2)打开或关闭评论
- 打开评论,可以让ren'he1用户评论该文章
- 关闭评论,不让任何用户评论该文章,关闭评论,则清除所有gai1文章的评论
(3)查看文章详情评论详情
- 点击查看按钮即可查看文章评论d1详细内容
- 展示文章的评论
- 展示评论的对应回复列表
(4)作者回复评论
- 作者可以对某一条评论进行hu回复
(5)作者点赞评论
- 作者可以点赞评论,点赞后点赞数+1
(6)作者可以删除评论
(7)删除评论回复
9.2 接口设计
CommentManagerController:
查询评论列表:
说明 | |
接口地址 | /api/v1/comment/manage/list |
请求方式 | POST |
参数 | CommentManageDto |
响应 | ResponseResult |
删除评论:
说明 | |
接口地址 | /api/v1/comment/manage/del_comment/{commentId} |
请求方式 | DELETE |
参数 | commentId |
响应 | ResponseResult |
删除评论回复:
说明 | |
接口地址 | /api/v1/comment/manage/del_comment_repay/{commentRepayId} |
请求方式 | DELETE |
参数 | commentRepayId |
响应 | ResponseResult |
查看文章评论列表:
说明 | |
接口地址 | /api/v1/comment/manage/find_news_comments |
请求方式 | POST |
参数 | ArticleCommentDto |
响应 | ResponseResult |
打开或关闭评论:
说明 | |
接口地址 | /api/v1/comment/manage/update_comment_status |
请求方式 | POST |
参数 | CommentConfigDto |
响应 | ResponseResult |
回复评论:
说明 | |
接口地址 | /api/v1/comment/manage/comment_repay |
请求方式 | POST |
参数 | CommentRepaySaveDto |
响应 | ResponseResult |
点赞评论:
说明 | |
接口地址 | /api/v1/comment/manage/like |
请求方式 | POST |
参数 | CommentLikeDto |
响应 | ResponseResult |
9.3 实现步骤
1. 评论文章、点赞评论、加载评论
步骤①:新建微服务heima-leadnews-comment
在pom.xml(heima-leadnews-comment)模块下添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
②添加启动类 - CommentApplication
package com.heima.comment;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@ServletComponentScan
@EnableFeignClients(basePackages = "com.heima.apis")
public class CommentApplication {
public static void main(String[] args) {
SpringApplication.run(CommentApplication.class, args);
}
}
③添加resources/bootstrap.yml
server:
port: 51806
spring:
data:
mongodb:
host: 192.168.200.130
port: 27017
database: leadnews-comment
kafka:
bootstrap-servers: 192.168.200.130:9092
producer:
retries: 10
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
application:
name: leadnews-comment
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
④在nacos配置中心添加leadnews-comment.yml
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
data:
mongodb:
host: 192.168.200.130
port: 27017
database: leadnews-comment
⑤在heima-leadnews-model模块下新增与comment相关的以下实体类
model/article模块下:
ArticleCommentDto:
package com.heima.model.article.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
@Data
public class ArticleCommentDto extends PageRequestDto {
private String beginDate;
private String endDate;
private Integer wmUserId;
}
ArticleCommentVo
package com.heima.model.article.vos;
import lombok.Data;
import java.util.Date;
@Data
public class ArticleCommnetVo {
private Long id;
private String title;
private Integer comments;
private Boolean isComment;
private Date publishTime;
}
model/comment模块:
CommentConfigDto:
package com.heima.model.comment.dtos;
import lombok.Data;
@Data
public class CommentConfigDto {
/**
* 文章id
*/
private Long articleId;
/**
* 操作
* 0 关闭评论
* 1 开启评论
*/
private Short operation;
}
CommentDto:
package com.heima.model.comment.dtos;
import lombok.Data;
import java.util.Date;
@Data
public class CommentDto {
/**
* 文章id
*/
private Long articleId;
// 最小时间
private Date minDate;
//是否是首页
private Short index;
}
CommentLikeDto:
package com.heima.model.comment.dtos;
import lombok.Data;
@Data
public class CommentLikeDto {
/**
* 评论id
*/
private String commentId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
CommentManageDto:
package com.heima.model.comment.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
@Data
public class CommentManageDto extends PageRequestDto {
/**
* 文章id
*/
private Long articleId;
}
CommentRepayDto
package com.heima.model.comment.dtos;
import lombok.Data;
import java.util.Date;
@Data
public class CommentRepayDto {
/**
* 评论id
*/
private String commentId;
private Integer size;
// 最小时间
private Date minDate;
}
CommentRepayLikeDto
package com.heima.model.comment.dtos;
import lombok.Data;
@Data
public class CommentRepayLikeDto {
/**
* 回复id
*/
private String commentRepayId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
CommentRepaySaveDto
package com.heima.model.comment.dtos;
import lombok.Data;
@Data
public class CommentRepaySaveDto {
/**
* 评论id
*/
private String commentId;
/**
* 回复内容
*/
private String content;
}
CommentRepaySaveDto
package com.heima.model.comment.dtos;
import com.heima.model.common.annotation.IdEncrypt;
import lombok.Data;
@Data
public class CommentSaveDto {
/**
* 文章id
*/
@IdEncrypt
private Long articleId;
/**
* 评论内容
*/
private String content;
}
CountVo:
package com.heima.model.comment.vos;
import lombok.Data;
@Data
public class CountVo {
private Long countNum;
}
model/wemedia模块下新增StatisticsDto
package com.heima.model.wemedia.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
import java.util.Date;
@Data
public class StatisticsDto extends PageRequestDto {
private String beginDate;
private String endDate;
private Integer wmUserId;
}
⑥在heima-leadnews-comment模块下新增一些实体类
ApComment:
package com.heima.comment.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
/**
* APP评论信息
*/
@Data
@Document("ap_comment")
public class ApComment {
/**
* id
*/
private String id;
/**
* 用户ID 发表评论的用户id
*/
private Integer authorId;
/**
* 用户昵称
*/
private String authorName;
/**
* 文章id或动态id
*/
private Long entryId;
/**
* 频道ID
*/
private Integer channelId;
/**
* 评论内容类型
* 0 文章
* 1 动态
*/
private Short type;
/**
* 评论内容
*/
private String content;
/**
* 作者头像
*/
private String image;
/**
* 点赞数
*/
private Integer likes;
/**
* 回复数
*/
private Integer reply;
/**
* 文章标记
* 0 普通评论
* 1 热点评论
* 2 推荐评论
* 3 置顶评论
* 4 精品评论
* 5 大V 评论
*/
private Short flag;
/**
* 评论排列序号
*/
private Integer ord;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 评论状态 0 关闭状态 1 正常状态
*/
private Boolean status;
}
ApCommentLike:
package com.heima.comment.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* APP评论信息点赞
*/
@Data
@Document("ap_comment_like")
public class ApCommentLike {
/**
* id
*/
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 评论id
*/
private String commentId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
ApCommentRepay:
package com.heima.comment.pojos;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
/**
* APP评论回复信息
*/
@Data
@Document("ap_comment_repay")
public class ApCommentRepay {
/**
* id
*/
@Id
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 用户昵称
*/
private String authorName;
/**
* 评论id
*/
private String commentId;
/**
* 回复内容
*/
private String content;
/**
* 点赞数
*/
private Integer likes;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
}
ApCommentRepayLike:
package com.heima.comment.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* APP评论回复信息点赞信息
*/
@Data
@Document("ap_comment_repay_like")
public class ApCommentRepayLike {
/**
* id
*/
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 评论id
*/
private String commentRepayId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
CommentRepayListVo
package com.heima.comment.pojos;
import lombok.Data;
import java.util.List;
@Data
public class CommentRepayListVo {
private ApComment apComments;
private List<ApCommentRepay> apCommentRepays;
}
CommentRepayVo:
package com.heima.comment.pojos;
import lombok.Data;
@Data
public class CommentRepayVo extends ApCommentRepay {
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
CommentVo:
package com.heima.comment.pojos;
import lombok.Data;
@Data
public class CommentVo extends ApComment {
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
⑦拷贝拦截器到comment模块下
⑧评论的保存、点赞、加载
CommentController:
package com.heima.comment.controller;
import com.heima.comment.service.CommentService;
import com.heima.model.comment.dtos.CommentDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentSaveDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/comment")
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 保存评论
* @param dto
* @return
*/
@PostMapping("/save")
public ResponseResult saveComment(@RequestBody CommentSaveDto dto) {
return commentService.saveComment(dto);
}
/**
* 点赞评论
* @param dto
* @return
*/
@PostMapping("/like")
public ResponseResult like(@RequestBody CommentLikeDto dto) {
return commentService.like(dto);
}
/**
* 加载评论
* @param dto
* @return
*/
@PostMapping("/load")
public ResponseResult findByArticleId(@RequestBody CommentDto dto) {
return commentService.findByArticleId(dto);
}
}
CommentService:
package com.heima.comment.service;
import com.heima.model.comment.dtos.CommentDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentSaveDto;
import com.heima.model.common.dtos.ResponseResult;
public interface CommentService {
/**
* 保存评论
* @param dto
* @return
*/
ResponseResult saveComment(CommentSaveDto dto);
/**
* 点赞评论
* @param dto
* @return
*/
ResponseResult like(CommentLikeDto dto);
/**
* 加载评论列表
* @param dto
* @return
*/
ResponseResult findByArticleId(CommentDto dto);
}
CommentServiceImpl
package com.heima.comment.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.apis.article.IArticleClient;
import com.heima.apis.user.IUserClient;
import com.heima.apis.wemedia.IWemediaClient;
import com.heima.comment.pojos.ApComment;
import com.heima.comment.pojos.ApCommentLike;
import com.heima.comment.pojos.CommentVo;
import com.heima.comment.service.CommentService;
import com.heima.common.constants.HotArticleConstants;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.comment.dtos.CommentDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentSaveDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class CommentServiceImpl implements CommentService {
@Autowired
private IUserClient userClient;
@Autowired
private IArticleClient articleClient;
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Autowired
private IWemediaClient wemediaClient;
/**
* 保存评论
* @param dto
* @return
*/
@Override
public ResponseResult saveComment(CommentSaveDto dto) {
// 1. 检查参数
if(dto == null || StringUtils.isBlank(dto.getContent()) || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 检查文章是否可以评论
if(!checkParam(dto.getArticleId())) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "该文章评论权限已关闭");
}
// 3. 约束评论内容长度
if(dto.getContent().length() > 140) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论内容不能超过140字");
}
// 4. 判断是否登录(从ThreadLocal获得的用户信息只有userId)
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 5. 安全检查
ResponseResult response = wemediaClient.checkSensitive(dto.getContent());
if(!response.getCode().equals(200)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前评论中包含敏感词");
}
// 6. 保存评论
ApUser dbUser = userClient.findUserById(user.getId());
if(dbUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前登录信息有误");
}
ApComment apComment = new ApComment();
apComment.setAuthorId(user.getId());
apComment.setContent(dto.getContent());
apComment.setCreatedTime(new Date());
apComment.setEntryId(dto.getArticleId());
apComment.setImage(dbUser.getImage());
apComment.setAuthorName(dbUser.getName());
apComment.setLikes(0);
apComment.setReply(0);
apComment.setType((short) 0);
apComment.setFlag((short) 0);
mongoTemplate.save(apComment);
// 7. 发送消息,进行聚合
UpdateArticleMess mess = new UpdateArticleMess();
mess.setArticleId(dto.getArticleId());
mess.setAdd(1);
mess.setType(UpdateArticleMess.UpdateArticleType.COMMENT);
kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC, JSON.toJSONString(mess));
// 8. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
private boolean checkParam(Long articleId) {
// 是否可以评论
ResponseResult configResult = articleClient.findArticleConfigByArticleId(articleId);
if(!configResult.getCode().equals(200) || configResult.getData() == null) {
return false;
}
ApArticleConfig apArticleConfig = JSON.parseObject(JSON.toJSONString(configResult.getData()), ApArticleConfig.class);
if(apArticleConfig == null || !apArticleConfig.getIsComment()) {
return false;
}
return true;
}
/**
* 点赞评论
* @param dto
* @return
*/
@Override
public ResponseResult like(CommentLikeDto dto) {
// 1. 检查参数
if(dto == null || dto.getCommentId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 判断是否登录
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 3. 根据id查找评论
ApComment apComment = mongoTemplate.findById(dto.getCommentId(), ApComment.class);
// 4. 点赞
if(apComment != null && dto.getOperation() == 0) {
// 点赞,更新评论点赞数量
apComment.setLikes(apComment.getLikes() + 1);
mongoTemplate.save(apComment);
// 保存评论点赞数据
ApCommentLike apCommentLike = new ApCommentLike();
apCommentLike.setCommentId(apComment.getId());
apCommentLike.setAuthorId(user.getId());
mongoTemplate.save(apCommentLike);
} else {
// 取消点赞,更新评论点赞数量
int tmp = apComment.getLikes() - 1;
tmp = tmp < 1 ? 0 : tmp;
apComment.setLikes(tmp);
mongoTemplate.save(apComment);
// 删除评论点赞
Query query = Query.query(Criteria.where("commentId").is(apComment.getId()).and("authorId").is(user.getId()));
mongoTemplate.remove(query, ApComment.class);
}
// 5. 结果返回
Map<String, Object> result = new HashMap<>();
result.put("likes", apComment.getLikes());
return ResponseResult.okResult(result);
}
/**
* 加载评论列表
* @param dto
* @return
*/
@Override
public ResponseResult findByArticleId(CommentDto dto) {
// 1. 检查参数
if(dto == null || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
int size = 10;
// 2. 加载数据
// 当前文章的评论数据
Query query = Query.query(Criteria.where("entryId").is(dto.getArticleId()).and("createdTime").lt(dto.getMinDate()));
query.with(Sort.by(Sort.Direction.DESC, "createdTime")).limit(size);
List<ApComment> list = mongoTemplate.find(query, ApComment.class);
// 3. 封装数据返回
// 3.1 用户未登录
ApUser user = AppThreadLocalUtil.getUser();
if(user == null) {
return ResponseResult.okResult(list);
}
// 3.2 用户已登录
// 查询当前评论中哪些数据被点赞了
List<String> idList = list.stream().map(x -> x.getId()).collect(Collectors.toList());
Query query1 = Query.query(Criteria.where("commentId").in(idList).and("authorId").is(user.getId()));
List<ApCommentLike> apCommentLikes = mongoTemplate.find(query1, ApCommentLike.class);
if(apCommentLikes == null) {
return ResponseResult.okResult(list);
}
List<CommentVo> resultList = new ArrayList<>();
list.forEach(x -> {
CommentVo vo = new CommentVo();
BeanUtils.copyProperties(x, vo);
for (ApCommentLike apCommentLike : apCommentLikes) {
if(x.getId().equals(apCommentLike.getCommentId())) {
vo.setOperation((short) 0);
break;
}
}
resultList.add(vo);
});
return ResponseResult.okResult(resultList);
}
}
远程调用接口:IUserClient
package com.heima.apis.user;
import com.heima.apis.user.fallback.IUserClientFallback;
import com.heima.model.user.pojos.ApUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "leadnews-user", fallback = IUserClientFallback.class)
public interface IUserClient {
@GetMapping("/api/v1/user/{id}")
ApUser findUserById(@PathVariable("id") Integer id);
}
IUserClientFallback
package com.heima.apis.user.fallback;
import com.heima.apis.user.IUserClient;
import com.heima.model.user.pojos.ApUser;
public class IUserClientFallback implements IUserClient {
@Override
public ApUser findUserById(Integer id) {
return null;
}
}
⑨查询文章配置 -> 是否可评论
IArticleClient
package com.heima.apis.article;
import com.heima.apis.article.fallback.IArticleClientFallback;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(value = "leadnews-article", fallback = IArticleClientFallback.class)
public interface IArticleClient {
/**
* 保存文章
* @param dto
* @return
*/
@PostMapping("/api/v1/article/save")
public ResponseResult saveArticle(@RequestBody ArticleDto dto);
/**
* 查询文章配置信息
* @param articleId
* @return
*/
@GetMapping("/api/v1/article/findArticleConfigByArticleId/{articleId}")
ResponseResult findArticleConfigByArticleId(@PathVariable("articleId") Long articleId);
}
ArticleClient
package com.heima.article.feign;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.apis.article.IArticleClient;
import com.heima.article.service.ApArticleConfigService;
import com.heima.article.service.ApArticleService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class ArticleClient implements IArticleClient {
@Autowired
private ApArticleService apArticleService;
@Autowired
private ApArticleConfigService apArticleConfigService;
@Override
@PostMapping("/api/v1/article/save")
public ResponseResult saveArticle(@RequestBody ArticleDto dto) {
return apArticleService.saveArticle(dto);
}
@GetMapping("/api/v1/article/findArticleConfigByArticleId/{articleId}")
@Override
public ResponseResult findArticleConfigByArticleId(@PathVariable("articleId") Long articleId) {
ApArticleConfig apArticleConfig = apArticleConfigService.getOne(Wrappers.<ApArticleConfig>lambdaQuery().eq(ApArticleConfig::getArticleId, articleId));
return ResponseResult.okResult(apArticleConfig);
}
}
⑩检查评论中是否包含敏感词
IWemediaClient
package com.heima.apis.wemedia;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient("leadnews-wemedia")
public interface IWemediaClient {
@GetMapping("/api/v1/user/findByName/{name}")
WmUser findWmUserByName(@PathVariable("name") String name);
@PostMapping("/api/v1/wm_user/save")
ResponseResult saveWmUser(@RequestBody WmUser wmUser);
@GetMapping("/api/v1/channel/list")
public ResponseResult getChannels();
@PostMapping("/api/v1/wm_sensitive/check")
ResponseResult checkSensitive(@RequestBody String content);
}
WemediaClient
package com.heima.wemedia.feign;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.apis.wemedia.IWemediaClient;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.wemedia.service.WmChannelService;
import com.heima.wemedia.service.WmSensitiveService;
import com.heima.wemedia.service.WmUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class WemediaClient implements IWemediaClient {
@Autowired
private WmUserService wmUserService;
@Autowired
private WmSensitiveService wmSensitiveService;
@Override
@GetMapping("/api/v1/user/findByName/{name}")
public WmUser findWmUserByName(@PathVariable("name") String name) {
return wmUserService.getOne(Wrappers.<WmUser>lambdaQuery().eq(WmUser::getName, name));
}
@Override
@PostMapping("/api/v1/wm_user/save")
public ResponseResult saveWmUser(@RequestBody WmUser wmUser) {
wmUserService.save(wmUser);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
@Autowired
private WmChannelService wmChannelService;
@GetMapping("/api/v1/channel/list")
@Override
public ResponseResult getChannels() {
return wmChannelService.findAll();
}
@Override
@PostMapping("/api/v1/wm_sensitive/check")
public ResponseResult checkSensitive(String content) {
return wmSensitiveService.checkSensitive(content);
}
}
WmSensitiveService
ResponseResult checkSensitive(String content);
WmSensitiveServiceImpl
@Autowired
private WmSensitiveMapper wmSensitiveMapper;
@Override
public ResponseResult checkSensitive(String content) {
// 获取所有的敏感词
List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
List<String> sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
// 初始化敏感词库
SensitiveWordUtil.initMap(sensitiveList);
// 查看评论中是否包含敏感词
Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
if(map.size() > 0) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前评论中包含敏感词: " + map);
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
熔断降级扫描包路径
package com.heima.comment.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {
"com.heima.apis.wemedia.fallback",
"com.heima.apis.user.fallback",
"com.heima.apis.article.fallback"
})
public class InitConfig {
}
配置中心 - leadnews-comment.yml
feign:
# 开启feign对hystrix熔断降级的支持
hystrix:
enabled: true
# 修改调用超时时间
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
⑪测试
2. 回复评论、点赞回复的评论、加载回复的评论
①CommentRepayController
package com.heima.comment.controller;
import com.heima.comment.service.CommentRepayService;
import com.heima.model.comment.dtos.CommentRepayDto;
import com.heima.model.comment.dtos.CommentRepayLikeDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/comment_repay")
public class CommentRepayController {
@Autowired
private CommentRepayService commentRepayService;
/**
* 加载回复的评论
* @param dto
* @return
*/
@PostMapping("/load")
public ResponseResult loadCommentRepay(@RequestBody CommentRepayDto dto) {
return commentRepayService.loadCommentRepay(dto);
}
/**
* 保存回复的评论
* @param dto
* @return
*/
@PostMapping("/save")
public ResponseResult saveCommentRepay(@RequestBody CommentRepaySaveDto dto) {
return commentRepayService.saveCommentRepay(dto);
}
/**
* 点赞回复的评论
* @param dto
* @return
*/
@PostMapping("/like")
public ResponseResult saveCommentRepayLike(@RequestBody CommentRepayLikeDto dto){
return commentRepayService.saveCommentRepayLike(dto);
}
}
②CommentRepayService
package com.heima.comment.service;
import com.heima.model.comment.dtos.CommentRepayDto;
import com.heima.model.comment.dtos.CommentRepayLikeDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.ResponseResult;
public interface CommentRepayService {
/**
* 加载回复的评论
* @param dto
* @return
*/
ResponseResult loadCommentRepay(CommentRepayDto dto);
/**
* 保存回复的评论
* @param dto
* @return
*/
ResponseResult saveCommentRepay(CommentRepaySaveDto dto);
/**
* 点赞回复的评论
* @param dto
* @return
*/
ResponseResult saveCommentRepayLike(CommentRepayLikeDto dto);
}
③CommentRepayServiceImpl
package com.heima.comment.service.impl;
import com.heima.apis.user.IUserClient;
import com.heima.apis.wemedia.IWemediaClient;
import com.heima.comment.pojos.ApComment;
import com.heima.comment.pojos.ApCommentRepay;
import com.heima.comment.pojos.ApCommentRepayLike;
import com.heima.comment.pojos.CommentRepayVo;
import com.heima.comment.service.CommentRepayService;
import com.heima.model.comment.dtos.CommentRepayDto;
import com.heima.model.comment.dtos.CommentRepayLikeDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class CommentRepayServiceImpl implements CommentRepayService {
@Autowired
private IUserClient userClient;
@Autowired
private IWemediaClient wemediaClient;
@Autowired
private MongoTemplate mongoTemplate;
/**
* 加载回复的评论
*
* @param dto
* @return
*/
@Override
public ResponseResult loadCommentRepay(CommentRepayDto dto) {
// 1. 检查参数
if (dto.getCommentId() == null || dto == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
int size = 20;
// 2. 加载数据
Query query = Query.query(Criteria.where("commentId").is(dto.getCommentId()).and("createdTime").lt(dto.getMinDate()));
query.with(Sort.by(Sort.Direction.DESC, "createdTime")).limit(size);
List<ApCommentRepay> list = mongoTemplate.find(query, ApCommentRepay.class);
// 3. 封装数据返回
// 3.1 用户未登录
ApUser user = AppThreadLocalUtil.getUser();
if (user == null) {
return ResponseResult.okResult(list);
}
//3.2 用户已登录
//需要查询当前评论中哪些数据被点赞了
List<String> idList = list.stream().map(x -> x.getId()).collect(Collectors.toList());
Query query1 = Query.query(Criteria.where("commentRepayId").in(idList).and("authorId").is(user.getId()));
List<ApCommentRepayLike> apCommentRepayLikes = mongoTemplate.find(query1, ApCommentRepayLike.class);
if (apCommentRepayLikes == null || apCommentRepayLikes.size() == 0) {
return ResponseResult.okResult(list);
}
List<CommentRepayVo> resultList = new ArrayList<>();
list.forEach(x -> {
CommentRepayVo vo = new CommentRepayVo();
BeanUtils.copyProperties(x, vo);
for (ApCommentRepayLike apCommentRepayLike : apCommentRepayLikes) {
if (x.getId().equals(apCommentRepayLike.getCommentRepayId())) {
vo.setOperation((short) 0);
break;
}
}
resultList.add(vo);
});
return ResponseResult.okResult(resultList);
}
/**
* 保存回复的评论
*
* @param dto
* @return
*/
@Override
public ResponseResult saveCommentRepay(CommentRepaySaveDto dto) {
//1.检查参数
if (dto == null || StringUtils.isBlank(dto.getContent()) || dto.getCommentId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
if (dto.getContent().length() > 140) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论内容不能超过140字");
}
//2.判断是否登录
ApUser user = AppThreadLocalUtil.getUser();
if (user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
//3.安全检查
ResponseResult response = wemediaClient.checkSensitive(dto.getContent());
if (!response.getCode().equals(200)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论中包含违规内容");
}
//4.保存评论
ApUser dbUser = userClient.findUserById(user.getId());
if (dbUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前登录信息有误");
}
ApCommentRepay apCommentRepay = new ApCommentRepay();
apCommentRepay.setAuthorId(user.getId());
apCommentRepay.setContent(dto.getContent());
apCommentRepay.setCreatedTime(new Date());
apCommentRepay.setCommentId(dto.getCommentId());
apCommentRepay.setAuthorName(dbUser.getName());
apCommentRepay.setUpdatedTime(new Date());
apCommentRepay.setLikes(0);
mongoTemplate.save(apCommentRepay);
//5更新回复数量
ApComment apComment = mongoTemplate.findById(dto.getCommentId(), ApComment.class);
apComment.setReply(apComment.getReply() + 1);
mongoTemplate.save(apComment);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 点赞回复的评论
*
* @param dto
* @return
*/
@Override
public ResponseResult saveCommentRepayLike(CommentRepayLikeDto dto) {
//1.检查参数
if (dto == null || dto.getCommentRepayId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.判断是否登录
ApUser user = AppThreadLocalUtil.getUser();
if (user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
ApCommentRepay apCommentRepay = mongoTemplate.findById(dto.getCommentRepayId(), ApCommentRepay.class);
//3.点赞
if (apCommentRepay != null && dto.getOperation() == 0) {
//更新评论点赞数量
apCommentRepay.setLikes(apCommentRepay.getLikes() + 1);
mongoTemplate.save(apCommentRepay);
//保存评论点赞数据
ApCommentRepayLike apCommentRepayLike = new ApCommentRepayLike();
apCommentRepayLike.setCommentRepayId(apCommentRepay.getId());
apCommentRepayLike.setAuthorId(user.getId());
mongoTemplate.save(apCommentRepayLike);
} else {
//更新评论点赞数量
int tmp = apCommentRepay.getLikes() - 1;
tmp = tmp < 1 ? 0 : tmp;
apCommentRepay.setLikes(tmp);
mongoTemplate.save(apCommentRepay);
//删除评论点赞
Query query = Query.query(Criteria.where("commentRepayId").is(apCommentRepay.getId()).and("authorId").is(user.getId()));
mongoTemplate.remove(query, ApCommentRepayLike.class);
}
//4.取消点赞
Map<String, Object> result = new HashMap<>();
result.put("likes", apCommentRepay.getLikes());
return ResponseResult.okResult(result);
}
}
④测试
3. 自媒体端删除对文章的评论
步骤①:在pom.xml(heima-leadnews-wemedia)添加以下依赖
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
②在heima-leadnews-wemedia微服务添加一些实体类
ApComment:
package com.heima.wemedia.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
/**
* APP评论信息
*/
@Data
@Document("ap_comment")
public class ApComment {
/**
* id
*/
private String id;
/**
* 用户ID 发表评论的用户id
*/
private Integer authorId;
/**
* 用户昵称
*/
private String authorName;
/**
* 文章id或动态id
*/
private Long entryId;
/**
* 频道ID
*/
private Integer channelId;
/**
* 评论内容类型
* 0 文章
* 1 动态
*/
private Short type;
/**
* 评论内容
*/
private String content;
/**
* 作者头像
*/
private String image;
/**
* 点赞数
*/
private Integer likes;
/**
* 回复数
*/
private Integer reply;
/**
* 文章标记
* 0 普通评论
* 1 热点评论
* 2 推荐评论
* 3 置顶评论
* 4 精品评论
* 5 大V 评论
*/
private Short flag;
/**
* 评论排列序号
*/
private Integer ord;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 评论状态 0 关闭状态 1 正常状态
*/
private Boolean status;
}
ApCommentLike:
package com.heima.wemedia.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* APP评论信息点赞
*/
@Data
@Document("ap_comment_like")
public class ApCommentLike {
/**
* id
*/
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 评论id
*/
private String commentId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
ApCommentRepay:
package com.heima.wemedia.pojos;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
/**
* APP评论回复信息
*/
@Data
@Document("ap_comment_repay")
public class ApCommentRepay {
/**
* id
*/
@Id
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 用户昵称
*/
private String authorName;
/**
* 评论id
*/
private String commentId;
/**
* 回复内容
*/
private String content;
/**
* 点赞数
*/
private Integer likes;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
}
ApCommentRepayLike:
package com.heima.wemedia.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* APP评论回复信息点赞信息
*/
@Data
@Document("ap_comment_repay_like")
public class ApCommentRepayLike {
/**
* id
*/
private String id;
/**
* 用户ID
*/
private Integer authorId;
/**
* 评论id
*/
private String commentRepayId;
/**
* 0:点赞
* 1:取消点赞
*/
private Short operation;
}
CommentRepayListVo:
package com.heima.wemedia.pojos;
import lombok.Data;
import java.util.List;
@Data
public class CommentRepayListVo {
private ApComment apComments;
private List<ApCommentRepay> apCommentRepays;
}
③CommentManagerController
package com.heima.wemedia.controller.v1;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentManageDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.wemedia.service.CommentManagerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/comment/manage")
public class CommentManagerController {
@Autowired
private CommentManagerService commentManagerService;
/**
* 查询评论列表
* @param dto
* @return
*/
@PostMapping("/list")
public ResponseResult list(@RequestBody CommentManageDto dto) {
return commentManagerService.list(dto);
}
/**
* 删除评论
* @param commentId
* @return
*/
@DeleteMapping("/del_comment/{commentId}")
public ResponseResult delComment(@PathVariable("commentId") String commentId) {
return commentManagerService.delComment(commentId);
}
/**
* 删除评论回复
* @param commentRepayId
* @return
*/
@DeleteMapping("/del_comment_repay/{commentRepayId}")
public ResponseResult delCommentRepay(@PathVariable("commentRepayId") String commentRepayId) {
return commentManagerService.delCommentRepay(commentRepayId);
}
/**
* 分页查询文章评论列表
* @param dto
* @return
*/
@PostMapping("/find_news_comments")
public PageResponseResult findNewsComments(@RequestBody ArticleCommentDto dto) {
return commentManagerService.findNewsComments(dto);
}
/**
* 修改文章评论配置 打开或关闭评论
* @param dto
* @return
*/
@PostMapping("/update_comment_status")
public ResponseResult updateCommentStatus(@RequestBody CommentConfigDto dto) {
return commentManagerService.updateCommentStatus(dto);
}
/**
* 回复评论
* @param dto
* @return
*/
@PostMapping("/comment_repay")
public ResponseResult saveCommentRepay(@RequestBody CommentRepaySaveDto dto) {
return commentManagerService.saveCommentRepay(dto);
}
/**
* 点赞评论
* @param dto
* @return
*/
@PostMapping("/like")
public ResponseResult like(@RequestBody CommentLikeDto dto) {
return commentManagerService.like(dto);
}
}
④CommentManageService
package com.heima.wemedia.service;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentManageDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
public interface CommentManagerService {
/**
* 查询评论列表
* @param dto
* @return
*/
ResponseResult list(CommentManageDto dto);
/**
* 删除评论
* @param commentId
* @return
*/
ResponseResult delComment(String commentId);
/**
* 删除评论回复
* @param commentRepayId
* @return
*/
ResponseResult delCommentRepay(String commentRepayId);
/**
* 分页查询文章评论列表
* @param dto
* @return
*/
PageResponseResult findNewsComments(ArticleCommentDto dto);
/**
* 修改文章评论配置 打开或关闭评论
* @param dto
* @return
*/
ResponseResult updateCommentStatus(CommentConfigDto dto);
/**
* 回复评论
* @param dto
* @return
*/
ResponseResult saveCommentRepay(CommentRepaySaveDto dto);
/**
* 点赞评论
* @param dto
* @return
*/
ResponseResult like(CommentLikeDto dto);
}
⑤CommenrManageServiceImpl
package com.heima.wemedia.service.impl;
import com.heima.apis.article.IArticleClient;
import com.heima.apis.user.IUserClient;
import com.heima.apis.wemedia.IWemediaClient;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentManageDto;
import com.heima.model.comment.dtos.CommentRepaySaveDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.pojos.*;
import com.heima.wemedia.service.CommentManagerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@Slf4j
public class CommentManagerServiceImpl implements CommentManagerService {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private IArticleClient articleClient;
@Autowired
private IUserClient userClient;
@Autowired
private WmUserMapper wmUserMapper;
@Autowired
private IWemediaClient wemediaClient;
/**
* 查询评论回复列表
* @param dto
* @return
*/
@Override
public ResponseResult list(CommentManageDto dto) {
// 1. 检查参数
if(dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 检查分页
dto.checkParam();
List<CommentRepayListVo> commentRepayListVoList = new ArrayList<>();
// 3. 根据文章id查询评论
Query query = Query.query(Criteria.where("entryId").is(dto.getArticleId()));
Pageable pageable = PageRequest.of(dto.getPage(), dto.getSize());
query.with(pageable);
query.with(Sort.by(Sort.Direction.DESC, "createdTime"));
List<ApComment> list = mongoTemplate.find(query, ApComment.class);
// 4. 查询每条评论的回复评论
for (ApComment apComment : list) {
CommentRepayListVo vo = new CommentRepayListVo();
vo.setApComments(apComment);
Query query1 = Query.query(Criteria.where("commentId").is(apComment.getId()));
query1.with(Sort.by(Sort.Direction.DESC, "createdTime"));
List<ApCommentRepay> apCommentRepays = mongoTemplate.find(query1, ApCommentRepay.class);
vo.setApCommentRepays(apCommentRepays);
commentRepayListVoList.add(vo);
}
// 5. 结果返回
return ResponseResult.okResult(commentRepayListVoList);
}
/**
* 删除评论
* @param commentId
* @return
*/
@Override
public ResponseResult delComment(String commentId) {
// 1. 检查参数
if(StringUtils.isBlank(commentId)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论id不能为空");
}
// 2. 删除评论
mongoTemplate.remove(Query.query(Criteria.where("id").is(commentId)), ApComment.class);
// 3. 删除该评论的所有回复内容
mongoTemplate.remove(Query.query(Criteria.where("commentId").is(commentId)), ApCommentRepay.class);
// 4. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 删除评论回复
* @param commentRepayId
* @return
*/
@Override
public ResponseResult delCommentRepay(String commentRepayId) {
// 1. 检查参数
if(StringUtils.isBlank(commentRepayId)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论回复id不能为空");
}
// 2. 删除评论回复
mongoTemplate.remove(Query.query(Criteria.where("id").is(commentRepayId)), ApCommentRepay.class);
// 3. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 分页查询文章评论列表
* @param dto
* @return
*/
@Override
public PageResponseResult findNewsComments(ArticleCommentDto dto) {
// 1. 获取登录用户
WmUser user = WmThreadLocalUtil.getUser();
dto.setWmUserId(user.getId());
// 2. 远程调用
return articleClient.findNewsComments(dto);
}
/**
* 修改文章评论配置 打开或关闭评论
* @param dto
* @return
*/
@Override
public ResponseResult updateCommentStatus(CommentConfigDto dto) {
// 1. 获取当前登录用户
WmUser wmUser = WmThreadLocalUtil.getUser();
// 2. app端用户id
WmUser dbUser = wmUserMapper.selectById(wmUser.getId());
Integer apUserId = dbUser.getApUserId();
// 3. 清空该文章的所有评论
// 个人认为不需要清空关闭评论之前的评论数据
/*List<ApComment> apCommentList = mongoTemplate.find(Query.query(Criteria.where("entryId").is(dto.getArticleId()).and("authorId").is(apUserId)), ApComment.class);
for (ApComment apComment : apCommentList) {
List<ApCommentRepay> commentRepayList = mongoTemplate.find(Query.query(Criteria.where("commentId").is(apComment.getId()).and("authorId").is(apUserId)), ApCommentRepay.class);
List<String> commentRepayIdList = commentRepayList.stream().map(ApCommentRepay::getId).distinct().collect(Collectors.toList());
//删除所有的评论回复点赞数据
mongoTemplate.remove(Query.query(Criteria.where("commentRepayId").in(commentRepayIdList).and("authorId").is(apUserId)), ApCommentRepayLike.class);
//删除该评论的所有的回复内容
mongoTemplate.remove(Query.query(Criteria.where("entryId").is(dto.getArticleId()).and("authorId").is(apUserId)), ApCommentRepay.class);
//删除评论的点赞
mongoTemplate.remove(Query.query(Criteria.where("commentId").is(apComment.getId()).and("authorId").is(apUserId)), ApCommentLike.class);
}
// 4. 删除评论
mongoTemplate.remove(Query.query(Criteria.where("entryId").is(dto.getArticleId()).and("authorId").is(apUserId)),ApComment.class);*/
// 5. 修改app文章的config配置
return articleClient.updateCommentStatus(dto);
}
/**
* 回复评论
* @param dto
* @return
*/
@Override
public ResponseResult saveCommentRepay(CommentRepaySaveDto dto) {
// 1. 检查参数
if(dto == null || StringUtils.isBlank(dto.getContent()) || dto.getCommentId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
if(dto.getContent().length() > 140) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "评论内容不能超过140字");
}
// 2. 安全检查
ResponseResult response = wemediaClient.checkSensitive(dto.getContent());
if(!response.getCode().equals(200)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前评论包含违规内容");
}
// 3. 获取自媒体人信息
WmUser wmUser = WmThreadLocalUtil.getUser();
WmUser dbUser = wmUserMapper.selectById(wmUser.getId());
if(dbUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 4. 获取app端用户信息
ApUser apUser = userClient.findUserById(dbUser.getApUserId());
// 5. 保存评论
ApCommentRepay apCommentRepay = new ApCommentRepay();
apCommentRepay.setAuthorId(apUser.getId());
apCommentRepay.setAuthorName(apUser.getName());
apCommentRepay.setContent(dto.getContent());
apCommentRepay.setCreatedTime(new Date());
apCommentRepay.setCommentId(dto.getCommentId());
apCommentRepay.setUpdatedTime(new Date());
apCommentRepay.setLikes(0);
mongoTemplate.save(apCommentRepay);
// 6. 更新回复数量
ApComment apComment = mongoTemplate.findById(dto.getCommentId(), ApComment.class);
apComment.setReply(apComment.getReply() + 1);
mongoTemplate.save(apComment);
// 7. 结果返回
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 点赞评论
* @param dto
* @return
*/
@Override
public ResponseResult like(CommentLikeDto dto) {
// 1. 检查参数
if (dto == null || dto.getCommentId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2. 查找评论
ApComment apComment = mongoTemplate.findById(dto.getCommentId(), ApComment.class);
// 3. 获取当前登录用户信息
WmUser wmUser = WmThreadLocalUtil.getUser();
WmUser dbUser = wmUserMapper.selectById(wmUser.getId());
if(dbUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 4. 获取app端用户信息
ApUser apUser = userClient.findUserById(dbUser.getApUserId());
// 5. 点赞
if(apComment != null && dto.getOperation() == 0) {
// 更新评论点赞数量
apComment.setLikes(apComment.getLikes() + 1);
mongoTemplate.save(apComment);
// 保存评论点赞数据
ApCommentLike apCommentLike = new ApCommentLike();
apCommentLike.setCommentId(apComment.getId());
apCommentLike.setAuthorId(apUser.getId());
mongoTemplate.save(apCommentLike);
} else {
//更新评论点赞数量
int tmp = apComment.getLikes() - 1;
tmp = tmp < 1 ? 0 : tmp;
apComment.setLikes(tmp);
mongoTemplate.save(apComment);
//删除评论点赞
Query query = Query.query(Criteria.where("commentId").is(apComment.getId()).and("authorId").is(apUser.getId()));
mongoTemplate.remove(query, ApCommentLike.class);
}
//4.取消点赞
Map<String, Object> result = new HashMap<>();
result.put("likes", apComment.getLikes());
return ResponseResult.okResult(result);
}
}
⑥Article端远程调用接口
IArticleClient
package com.heima.apis.article;
import com.heima.apis.article.fallback.IArticleClientFallback;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(value = "leadnews-article", fallback = IArticleClientFallback.class)
public interface IArticleClient {
/**
* 保存文章
* @param dto
* @return
*/
@PostMapping("/api/v1/article/save")
public ResponseResult saveArticle(@RequestBody ArticleDto dto);
/**
* 查询文章配置信息
* @param articleId
* @return
*/
@GetMapping("/api/v1/article/findArticleConfigByArticleId/{articleId}")
ResponseResult findArticleConfigByArticleId(@PathVariable("articleId") Long articleId);
/**
* 分页查询文章的评论
* @param dto
* @return
*/
@PostMapping("/api/v1/article/findNewsComments")
public PageResponseResult findNewsComments(@RequestBody ArticleCommentDto dto);
/**
* 更新文章的评论设置->打开或关闭评论
* @param dto
* @return
*/
@PostMapping("/api/v1/article/updateCommentStatus")
public ResponseResult updateCommentStatus(@RequestBody CommentConfigDto dto);
}
IArticleClientFallback
package com.heima.apis.article.fallback;
import com.heima.apis.article.IArticleClient;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.springframework.stereotype.Component;
@Component
public class IArticleClientFallback implements IArticleClient {
@Override
public ResponseResult saveArticle(ArticleDto dto) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");
}
@Override
public ResponseResult findArticleConfigByArticleId(Long articleId) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");
}
@Override
public PageResponseResult findNewsComments(ArticleCommentDto dto) {
PageResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),0);
responseResult.setCode(501);
responseResult.setErrorMessage("获取数据失败");
return responseResult;
}
@Override
public ResponseResult updateCommentStatus(CommentConfigDto dto) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "更新评论设置失败");
}
}
ArticleClient
package com.heima.article.feign;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.apis.article.IArticleClient;
import com.heima.article.service.ApArticleConfigService;
import com.heima.article.service.ApArticleService;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.comment.dtos.CommentConfigDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class ArticleClient implements IArticleClient {
@Autowired
private ApArticleService apArticleService;
@Autowired
private ApArticleConfigService apArticleConfigService;
// ... ...
/**
* 查询文章的评论列表
* @param dto
* @return
*/
@PostMapping("/api/v1/article/findNewsComments")
@Override
public PageResponseResult findNewsComments(@RequestBody ArticleCommentDto dto) {
return apArticleService.findNewsComments(dto);
}
/**
* 更新文章的评论设置->打开或关闭评论
* @param dto
* @return
*/
@PostMapping("/api/v1/article/updateCommentStatus")
@Override
public ResponseResult updateCommentStatus(@RequestBody CommentConfigDto dto) {
return apArticleConfigService.updateCommentStatus(dto);
}
}
ApArticleService
/**
* 查询文章的评论列表
* @param dto
* @return
*/
PageResponseResult findNewsComments(ArticleCommentDto dto);
ApArticleServiceImpl
/**
* 查询文章的评论统计
* @param dto
* @return
*/
@Override
public PageResponseResult findNewsComments(ArticleCommentDto dto) {
// 1. 统计文章评论信息
Integer currentPage = dto.getPage();
dto.setPage((dto.getPage() - 1) * dto.getSize());
List<ArticleCommnetVo> list = apArticleMapper.findNewsComments(dto);
int count = apArticleMapper.findNewsCommentsCount(dto);
// 2. 构造结果返回
PageResponseResult responseResult = new PageResponseResult(currentPage, dto.getSize(), count);
responseResult.setData(list);
return responseResult;
}
ApArticleMapper
List<ArticleCommnetVo> findNewsComments(@Param("dto")ArticleCommentDto dto);
int findNewsCommentsCount(@Param("dto")ArticleCommentDto dto);
ApArticleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.article.mapper.ApArticleMapper">
<resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="channel_id" property="channelId"/>
<result column="channel_name" property="channelName"/>
<result column="layout" property="layout"/>
<result column="flag" property="flag"/>
<result column="images" property="images"/>
<result column="labels" property="labels"/>
<result column="likes" property="likes"/>
<result column="collection" property="collection"/>
<result column="comment" property="comment"/>
<result column="views" property="views"/>
<result column="province_id" property="provinceId"/>
<result column="city_id" property="cityId"/>
<result column="county_id" property="countyId"/>
<result column="created_time" property="createdTime"/>
<result column="publish_time" property="publishTime"/>
<result column="sync_status" property="syncStatus"/>
<result column="static_url" property="staticUrl"/>
</resultMap>
<select id="loadArticleList" resultMap="resultMap">
SELECT
aa.*
FROM
`ap_article` aa
LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
<where>
and aac.is_delete != 1
and aac.is_down != 1
<!-- loadmore -->
<if test="type != null and type == 1">
and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
</if>
<if test="type != null and type == 2">
and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
</if>
<if test="dto.tag != '__all__'">
and aa.channel_id = #{dto.tag}
</if>
</where>
order by aa.publish_time desc
limit #{dto.size}
</select>
<select id="findArticleListByLast5days" resultMap="resultMap">
SELECT
aa.*
FROM
`ap_article` aa
LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
<where>
and aac.is_delete != 1
and aac.is_down != 1
<if test="dayParam != null">
and aa.publish_time <![CDATA[>=]]> #{dayParam}
</if>
</where>
</select>
<select id="queryLikesAndConllections" resultType="java.util.Map">
SELECT sum(aa.likes) likes,
sum(aa.collection) collections,
count(aa.id) newsCount
FROM ap_article aa,
ap_article_config aac
WHERE aa.id = aac.article_id
AND aac.is_delete != 1
AND aac.is_down != 1
AND aa.author_id = #{wmUserId}
AND aa.publish_time <![CDATA[>=]]> #{beginDate}
AND aa.publish_time <![CDATA[<]]> #{endDate}
</select>
<select id="findNewsComments" parameterType="com.heima.model.wemedia.dtos.StatisticsDto"
resultType="com.heima.model.article.vos.ArticleCommnetVo">
SELECT aa.id, aa.title, aa.comment comments, aac.is_comment isComment,aa.publish_time publishTime
FROM ap_article aa,
ap_article_config aac
WHERE aa.id = aac.article_id
AND aa.author_id = #{dto.wmUserId}
AND aa.publish_time <![CDATA[>=]]> #{dto.beginDate}
AND aa.publish_time <![CDATA[<]]> #{dto.endDate}
order by aa.publish_time
limit #{dto.page},#{dto.size}
</select>
<select id="findNewsCommentsCount" parameterType="com.heima.model.wemedia.dtos.StatisticsDto"
resultType="int">
SELECT count(1)
FROM ap_article aa,
ap_article_config aac
WHERE aa.id = aac.article_id
AND aa.author_id = #{dto.wmUserId}
AND aa.publish_time <![CDATA[>=]]> #{dto.beginDate}
AND aa.publish_time <![CDATA[<]]> #{dto.endDate}
</select>
</mapper>
ApArticleConfigService
/**
* 更新文章的评论设置->打开或关闭评论
* @param dto
* @return
*/
ResponseResult updateCommentStatus(CommentConfigDto dto);
ApArticleConfigServiceImpl
/**
* 更新文章的评论设置->打开或关闭评论
* @param dto
* @return
*/
@Override
public ResponseResult updateCommentStatus(CommentConfigDto dto) {
update(Wrappers.<ApArticleConfig>lambdaUpdate()
.eq(ApArticleConfig::getArticleId, dto.getArticleId())
.set(ApArticleConfig::getIsComment, dto.getOperation()));
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
10. 自媒体-图文数据统计
自媒体微服务 - heima-leadnews-wemedia
10.1 需求分析
如上图所示:
自媒体人登录到自媒体系统可以查看最近的统计报表
- 当前登录的用户可以选择指定时间查看文章的一些统计(图片发布量、文章点赞量、文章收藏量、文章点赞量)
- 分页展示文章列表,展示当前时间范围内的具体文章阅读、评论、收藏的数量。
10.2 接口设计
图文统计
说明 | |
接口地址 | /api/v1/statistics/newsDimension |
请求方式 | GET |
参数 | 开始时间和结束时间 |
响应 | ResponseResult |
分页查询图文统计
说明 | |
接口地址 | /api/v1/statistics/newsPage |
请求方式 | GET |
参数 | StatisticsDto |
响应 | ResponseResult |
10.3 实现步骤
步骤①:WmStatisticsController
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.StatisticsDto;
import com.heima.wemedia.service.WmStatisticsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/statistics")
public class WmStatisticsController {
@Autowired
private WmStatisticsService wmStatisticsService;
@GetMapping("/newsDimension")
public ResponseResult newsDimension(String beginDate, String endDate) {
return wmStatisticsService.newsDimension(beginDate, endDate);
}
@GetMapping("/newPage")
public PageResponseResult newsPage(StatisticsDto dto) {
return wmStatisticsService.newsPage(dto);
}
}
步骤②:WmStatisticsService
package com.heima.wemedia.service;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.StatisticsDto;
public interface WmStatisticsService {
/**
* 图文统计
* @param beginDate
* @param endDate
* @return
*/
ResponseResult newsDimension(String beginDate, String endDate);
/**
* 分页查询图文统计
* @param dto
* @return
*/
PageResponseResult newsPage(StatisticsDto dto);
}
步骤③:WmStatisticsServiceImpl
package com.heima.wemedia.service.impl;
import com.alibaba.fastjson.JSON;
import com.heima.apis.article.IArticleClient;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.StatisticsDto;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.DateUtils;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.service.WmNewsService;
import com.heima.wemedia.service.WmStatisticsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
@Transactional
public class WmStatisticsServiceImpl implements WmStatisticsService {
@Autowired
private WmNewsService wmNewsService;
@Autowired
private IArticleClient articleClient;
/**
* 图文统计
* @param beginDate
* @param endDate
* @return
*/
@Override
public ResponseResult newsDimension(String beginDate, String endDate) {
Map<String, Object> resultMap = new HashMap<>();
// 1. 类型转换 字符串转换为date类型
Date beginDateTime = DateUtils.stringToDate(beginDate);
Date endDateTime = DateUtils.stringToDate(endDate);
WmUser user = WmThreadLocalUtil.getUser();
// 2. 图文发布量
/*int publishNum = wmNewsService.count(Wrappers.<WmNews>lambdaQuery()
.eq(WmNews::getUserId, user.getId())
.eq(WmNews::getStatus, WmNews.Status.PUBLISHED.getCode())
.eq(WmNews::getEnable, 1)
.between(WmNews::getPublishTime, beginDateTime, endDateTime));
resultMap.put("publishNum", publishNum);*/
// 3. 点赞数量 & 收藏数量
ResponseResult responseResult = articleClient.queryLikesAndCollections(user.getId(), beginDateTime, endDateTime);
if(responseResult.getCode().equals(200)) {
String res_json = JSON.toJSONString(responseResult.getData());
Map map = JSON.parseObject(res_json, Map.class);
resultMap.put("likesNum", map.get("likes") == null ? 0 : map.get("likes"));
resultMap.put("collectNum", map.get("collection") == null ? 0 : map.get("collections"));
resultMap.put("publishNum", map.get("newsCount") == null ? 0 : map.get("newsCount"));
}
return ResponseResult.okResult(resultMap);
}
/**
* 分页查询图文统计
* @param dto
* @return
*/
@Override
public PageResponseResult newsPage(StatisticsDto dto) {
WmUser user = WmThreadLocalUtil.getUser();
dto.setWmUserId(user.getId());
PageResponseResult responseResult = articleClient.newPage(dto);
return responseResult;
}
}
步骤④:IArticleClient
/**
* 图文统计
* @param wmUserId
* @param beginDate
* @param endDate
* @return
*/
@GetMapping("/api/v1/article/queryLikesAndConllections")
ResponseResult queryLikesAndCollections(@RequestParam("wmUserId") Integer wmUserId, @RequestParam("beginDate") Date beginDate, @RequestParam("endDate") Date endDate);
/**
* 图文统计 分页查询
* @param dto
* @return
*/
@PostMapping("/api/v1/article/newPage")
PageResponseResult newPage(@RequestBody StatisticsDto dto);
IArticleClientFallback
@Override
public ResponseResult queryLikesAndCollections(Integer wmUserId, Date beginDate, Date endDate) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");
}
@Override
public PageResponseResult newPage(StatisticsDto dto) {
PageResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),0);
responseResult.setCode(501);
responseResult.setErrorMessage("获取数据失败");
return responseResult;
}
步骤⑤:ArticleClient
/**
* 图文统计
* @param wmUserId
* @param beginDate
* @param endDate
* @return
*/
@GetMapping("/api/v1/article/queryLikesAndCollections")
@Override
public ResponseResult queryLikesAndCollections(@RequestParam("wmUserId") Integer wmUserId, @RequestParam("beginDate") Date beginDate, @RequestParam("endDate") Date endDate) {
return apArticleService.queryLikesAndCollections(wmUserId, beginDate, endDate);
}
/**
* 分页图文统计
* @param dto
* @return
*/
@PostMapping("/api/v1/article/newPage")
@Override
public PageResponseResult newPage(@RequestBody StatisticsDto dto) {
return apArticleService.newPage(dto);
}
步骤⑥:ApArticleService
/**
* 图文统计
* @param wmUserId
* @param beginDate
* @param endDate
* @return
*/
ResponseResult queryLikesAndCollections(Integer wmUserId, Date beginDate, Date endDate);
/**
* 分页查询 图文统计
* @param dto
* @return
*/
PageResponseResult newPage(StatisticsDto dto);
步骤⑦:ApArticleServiceImpl
/**
* 图文统计
* @param wmUserId
* @param beginDate
* @param endDate
* @return
*/
@Override
public ResponseResult queryLikesAndCollections(Integer wmUserId, Date beginDate, Date endDate) {
Map map = apArticleMapper.queryLikesAndCollections(wmUserId, beginDate, endDate);
return ResponseResult.okResult(map);
}
/**
* 图文统计 分页查询
* @param dto
* @return
*/
@Override
public PageResponseResult newPage(StatisticsDto dto) {
//类型转换
Date beginDate = DateUtils.stringToDate(dto.getBeginDate());
Date endDate = DateUtils.stringToDate(dto.getEndDate());
//检查参数
dto.checkParam();
//分页查询
IPage page = new Page(dto.getPage(), dto.getSize());
LambdaQueryWrapper<ApArticle> lambdaQueryWrapper = Wrappers.<ApArticle>lambdaQuery()
.eq(ApArticle::getAuthorId, dto.getWmUserId())
.between(ApArticle::getPublishTime,beginDate, endDate)
.select(ApArticle::getId,ApArticle::getTitle,ApArticle::getLikes,ApArticle::getCollection,ApArticle::getComment,ApArticle::getViews);
lambdaQueryWrapper.orderByDesc(ApArticle::getPublishTime);
page = page(page,lambdaQueryWrapper);
PageResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());
responseResult.setData(page.getRecords());
return responseResult;
}
步骤⑧:ApArticleMapper
/**
* 图文统计
* @param wmUserId
* @param beginDate
* @param endDate
* @return
*/
Map queryLikesAndCollections(@Param("wmUserId") Integer wmUserId, @Param("beginDate") Date beginDate, @Param("endDate") Date endDate);
步骤⑨:ApArticleMapper.xml
<select id="queryLikesAndCollections" resultType="java.util.Map">
SELECT sum(aa.likes) likes,
sum(aa.collection) collections,
count(aa.id) newsCount
FROM ap_article aa,
ap_article_config aac
WHERE aa.id = aac.article_id
AND aac.is_delete != 1
AND aac.is_down != 1
AND aa.author_id = #{wmUserId}
AND aa.publish_time <![CDATA[>=]]> #{beginDate}
AND aa.publish_time <![CDATA[<]]> #{endDate}
</select>
步骤⑩:修改heima-leadnews-wemedia.conf
upstream heima-wemedia-gateway{
server localhost:51602;
}
server {
listen 8802;
location / {
root D:/IDEA/frontProject/wemedia-web-v2/;
index index.html;
}
location ~/wemedia/MEDIA/(.*) {
proxy_pass http://heima-wemedia-gateway/$1?$args; #后端服务可以接收get请求路径上的参数
proxy_set_header HOST $host; # 不改变源请求头的值
proxy_pass_request_body on; #开启获取请求体
proxy_pass_request_headers on; #开启获取请求头
proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
}
}
替换前端项目为wemedia-web-v2