黑马头条Day09-用户行为

一、课前准备

1. long类型精度丢失问题

解决方案:

  • 当后端响应给前端的数据中包含了id或者特殊标识(可自定义)的时候,把当前数据进行转换为String类型
  • 当前端传递给后端的dto中有id或者特殊标识(可自定义)的时候,把当前数据转为Integer或Long类型。

解决步骤:

①把课前资料里的jackson包拷贝放到heima-leadnews-common模块下

序列化和反序列化说明:

  • ConfusionSerializer:用于序列化自增数字的混淆
public class ConfusionSerializer extends JsonSerializer<Object> {@Overridepublic  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;}@Overridepublic  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 {@Overridepublic 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 {@Overridepublic 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);@Overridepublic String getModuleName() {return MODULE_NAME;}@Overridepublic Version version() {return VERSION;}@Overridepublic 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 {@Beanpublic 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@IdEncryptInteger authorId;// 文章id@IdEncryptLong 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 {@Autowiredprivate 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 {@Autowiredprivate CacheService cacheService;/*** 用户关注:0 / 取消关注:1* @param dto* @return*/@Overridepublic 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:fansInteger 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 {@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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-behaviorcloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-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 {@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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 {// 文章、动态、评论等IDLong 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 {@Autowiredprivate ApLikesBehaviorService apLikesBehaviorService;/*** 点赞* @param dto* @return*/@PostMappingpublic 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 {@Autowiredprivate CacheService cacheService;@Autowiredprivate KafkaTemplate<String, String> kafkaTemplate;/*** 点赞或取消点赞* @param dto 0:点赞 1:取消点赞* @return*/@Overridepublic 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, "已点赞");}// 保存当前keylog.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 {// 取消点赞,删除当前keylog.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 {@Beanpublic 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:1return 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*/@Overridepublic String apply() {return "COLLECTION:0,COMMENT:0,LIKES:0,VIEWS:0";}// 真正的聚合操作,返回值是消息的value}, new Aggregator<String, String, String>() {/*** @param key* @param value* @param aggValue* @return*/@Overridepublic 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:1String[] 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:0String[] 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 {@Autowiredprivate 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*/@Overridepublic 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);}}//缓存到redishotArticleVoList = 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 {// 文章、动态、评论等IDLong 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 {@Autowiredprivate ApReadBehaviorService apReadBehaviorService;/*** 用户行为-阅读* @param dto* @return*/@PostMappingpublic 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 {@Autowiredprivate CacheService cacheService;@Autowiredprivate KafkaTemplate<String, String> kafkaTemplate;/*** 用户行为 - 阅读* @param dto* @return*/@Overridepublic 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. 保存当前keylog.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@IdEncryptLong 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 {@Autowiredprivate ApUnlikesBehaviorService apUnlikesBehaviorService;/*** 用户行为 - 不喜欢* @param dto* @return*/@PostMappingpublic 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 {@Autowiredprivate CacheService cacheService;/*** 用户行为 - 不喜欢* @param dto* @return*/@Overridepublic 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@IdEncryptLong 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 {@Autowiredprivate ApCollectionService apCollectionService;/*** 文章收藏* @param dto* @return*/@PostMappingpublic 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 {@Autowiredprivate CacheService cacheService;@Overridepublic 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 {@Autowiredprivate 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*/@Overridepublic 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 需求分析

app用户端:

  • 加载评论
  • 可以对文章进行评论
  • 对评论进行点赞
  • 回复评论
  • 点赞回复某条评论的评论
  • 加载回复评论的评论

Wemedia自媒体端:

  • 查看文章评论列表
  • 打开或关闭评论
  • 查询评论列表
  • 删除评论
  • 删除评论回复
  • 点赞评论
  • 回复评论
  • 更新评论状



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

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

8.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.130port: 27017database: leadnews-commentkafka:bootstrap-servers: 192.168.200.130:9092producer:retries: 10key-serializer: org.apache.kafka.common.serialization.StringSerializervalue-serializer: org.apache.kafka.common.serialization.StringSerializerapplication:name: leadnews-commentcloud:nacos:discovery:server-addr: 192.168.200.130:8848config:server-addr: 192.168.200.130:8848file-extension: ymlautoconfigure:exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

④在nacos配置中心添加leadnews-comment.yml

spring:autoconfigure:exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationdata:mongodb:host: 192.168.200.130port: 27017database: 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*/@IdEncryptprivate 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*/@Idprivate 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 {@Autowiredprivate 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 {@Autowiredprivate IUserClient userClient;@Autowiredprivate IArticleClient articleClient;@Autowiredprivate MongoTemplate mongoTemplate;@Autowiredprivate KafkaTemplate<String, String> kafkaTemplate;@Autowiredprivate IWemediaClient wemediaClient;/*** 保存评论* @param dto* @return*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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);}
}

⑨查询文章配置 -> 是否可评论

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 {@Autowiredprivate ApArticleService apArticleService;@Autowiredprivate ApArticleConfigService apArticleConfigService;@Override@PostMapping("/api/v1/article/save")public ResponseResult saveArticle(@RequestBody ArticleDto dto) {return apArticleService.saveArticle(dto);}@GetMapping("/api/v1/article/findArticleConfigByArticleId/{articleId}")@Overridepublic 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 {@Autowiredprivate WmUserService wmUserService;@Autowiredprivate 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);}@Autowiredprivate WmChannelService wmChannelService;@GetMapping("/api/v1/channel/list")@Overridepublic 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

    @Autowiredprivate WmSensitiveMapper wmSensitiveMapper;@Overridepublic 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);}

⑪测试

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 {@Autowiredprivate 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 {@Autowiredprivate IUserClient userClient;@Autowiredprivate IWemediaClient wemediaClient;@Autowiredprivate MongoTemplate mongoTemplate;/*** 加载回复的评论** @param dto* @return*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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*/@Idprivate 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 {@Autowiredprivate 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 {@Autowiredprivate MongoTemplate mongoTemplate;@Autowiredprivate IArticleClient articleClient;@Autowiredprivate IUserClient userClient;@Autowiredprivate WmUserMapper wmUserMapper;@Autowiredprivate IWemediaClient wemediaClient;/*** 查询评论回复列表* @param dto* @return*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic PageResponseResult findNewsComments(ArticleCommentDto dto) {// 1. 获取登录用户WmUser user = WmThreadLocalUtil.getUser();dto.setWmUserId(user.getId());// 2. 远程调用return articleClient.findNewsComments(dto);}/*** 修改文章评论配置 打开或关闭评论* @param dto* @return*/@Overridepublic ResponseResult updateCommentStatus(CommentConfigDto dto) {// 1. 获取当前登录用户WmUser wmUser = WmThreadLocalUtil.getUser();// 2. app端用户idWmUser 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*/@Overridepublic 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*/@Overridepublic 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 {@Overridepublic ResponseResult saveArticle(ArticleDto dto) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");}@Overridepublic ResponseResult findArticleConfigByArticleId(Long articleId) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");}@Overridepublic PageResponseResult findNewsComments(ArticleCommentDto dto) {PageResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),0);responseResult.setCode(501);responseResult.setErrorMessage("获取数据失败");return responseResult;}@Overridepublic 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 {@Autowiredprivate ApArticleService apArticleService;@Autowiredprivate ApArticleConfigService apArticleConfigService;// ... .../*** 查询文章的评论列表* @param dto* @return*/@PostMapping("/api/v1/article/findNewsComments")@Overridepublic PageResponseResult findNewsComments(@RequestBody ArticleCommentDto dto) {return apArticleService.findNewsComments(dto);}/*** 更新文章的评论设置->打开或关闭评论* @param dto* @return*/@PostMapping("/api/v1/article/updateCommentStatus")@Overridepublic ResponseResult updateCommentStatus(@RequestBody CommentConfigDto dto) {return apArticleConfigService.updateCommentStatus(dto);}
}

ApArticleService

    /*** 查询文章的评论列表* @param dto* @return*/PageResponseResult findNewsComments(ArticleCommentDto dto);

ApArticleServiceImpl

    /*** 查询文章的评论统计* @param dto* @return*/@Overridepublic 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">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and 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 desclimit #{dto.size}</select><select id="findArticleListByLast5days" resultMap="resultMap">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and 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) newsCountFROM ap_article aa,ap_article_config aacWHERE aa.id = aac.article_idAND aac.is_delete != 1AND aac.is_down != 1AND 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 publishTimeFROM ap_article aa,ap_article_config aacWHERE aa.id = aac.article_idAND aa.author_id = #{dto.wmUserId}AND aa.publish_time <![CDATA[>=]]> #{dto.beginDate}AND aa.publish_time <![CDATA[<]]> #{dto.endDate}order by aa.publish_timelimit #{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 aacWHERE aa.id = aac.article_idAND 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*/@Overridepublic ResponseResult updateCommentStatus(CommentConfigDto dto) {update(Wrappers.<ApArticleConfig>lambdaUpdate().eq(ApArticleConfig::getArticleId, dto.getArticleId()).set(ApArticleConfig::getIsComment, dto.getOperation()));return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/50245.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows环境下安装docker、配置Ubuntu容器并使用vscode ssh连接到容器

目录 一、Windows环境下安装docker二、配置Ubuntu三、在容器中安装ssh服务参考文章 一、Windows环境下安装docker 在任务栏中搜索**“Windows功能”** -将适用于Linux的Windows子系统和虚拟机平台选上 然后按照提示重启电脑。然后开始安装WSL。通过cmd以管理员身份打开命令提…

OSPF概述

OSPF OSPF属于内部网关路由协议【IGP】 用于单一自治系统【Autonomous System-AS】内决策路由 自治系统【AS】 执行统一路由策略的一组网络设备的组合 OSPF概述 为了适应大型的网络&#xff0c;OSPF在AS内划分多个区域 每个OSPF路由器只维护所在区域的完整的链路状态信息 …

ufw命令简介

正文共&#xff1a;3999 字 41 图&#xff0c;预估阅读时间&#xff1a;4 分钟 我们之前在CentOS系统中研究了iptables命令的使用方法&#xff08;iptables命令简介&#xff09;&#xff0c;还做了简单的应用实验&#xff08;如何通过iptables配置URL过滤黑名单&#xff1f;、如…

Vue3 + Vite 打包引入图片错误

1. 具体报错 报错信息 报错代码 2. 解决方法 改为import引入&#xff0c;注意src最好引用为符引入&#xff0c;不然docker部署的时候可能也会显示不了 <template><img :src"loginBg" alt""> </template><script langts setup> …

LinkedList与链表(1万多字超级详细版本)

一. ArrayList的缺陷 上个博客已经熟悉了ArrayList的使用&#xff0c;并且进行了简单模拟实现。通过源码知道&#xff0c;ArrayList底层使用数组来存储元素&#xff1a; public class ArrayList < E > extends AbstractList < E > implements List < E &g…

国际奥委会主席巴赫:阿里AI技术将巴黎奥运转播带到新高度

7月26日&#xff0c;巴黎奥运会开幕在即&#xff0c;国际奥委会主席巴赫在国际转播中心举行的活动中表示&#xff0c;2024巴黎奥运会展现了奥运转播的未来&#xff0c;广泛应用的阿里AI技术正在将巴黎奥运转播带到新的高度。 &#xff08;国际奥委会主席巴赫&#xff09; 在巴黎…

SQLite基础:简介、安装和命令

文章目录 1. SQLite简介1.1 SQL 和 SQLite 之间的差异1.2 SQLite 特性 2. SQLite安装3. SQLite命令4. SQLite点命令 1. SQLite简介 SQLite 是嵌入式关系数据库管理系统。它是自包含、无服务器、零配置和事务性 SQL 数据库引擎。其与大多数其他 SQL 数据库不同&#xff0c;SQLi…

视觉SLAM第一讲

第一讲-预备知识 SLAM是什么&#xff1f; SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;是同时定位与地图构建。 它是指搭载特定传感器的主体&#xff0c;在没有环境先验信息的情况下&#xff0c;于运动过程中建立环境的模型&#xff0c;同时估计自己…

长效IP有哪些应用场景?

不同于经常重置的短效IP&#xff0c;长效IP以其长久稳定的特性&#xff0c;为各行各业提供了更为广阔的应用舞台。今天&#xff0c;就让我们一起探索极光http这一长效IP服务的魅力&#xff0c;看看它是如何成为众多领域首选的。 1. 远程工作的稳定后盾 在远程办公日益普及的今…

昇思25天学习打卡营第23天|LSTM+CRF序列标注

Mindspore框架CRF条件随机场概率图模型实现文本序列命名实体标注|&#xff08;一&#xff09;序列标注与条件随机场的关系 Mindspore框架CRF条件随机场概率图模型实现文本序列命名实体标注|&#xff08;二&#xff09;CRF模型构建 Mindspore框架CRF条件随机场概率图模型实现文本…

【Beyond Compare】Beyond Compare下载、安装与使用详细教程

目录 &#x1f33a;1 概述 &#x1f384;2 Beyond Compare 安装包下载 &#x1f33c;3 安装详细教程 &#x1f342;4 免费注册 &#x1f30d;5 使用详情 &#x1f33a;1 概述 Beyond Compare 是一款强大的文件和文件夹比较工具&#xff0c;广泛应用于软件开发、文档管理和…

Web动画(lottie篇)

一、Lottie简介 Lottie是一个库&#xff0c;可以解析使用AE制作的动画&#xff08;需要用bodymovin导出为json格式&#xff09;&#xff0c;支持web、ios、android和react native。在web侧&#xff0c;lottie-web库可以解析导出的动画json文件&#xff0c;并将其以svg或者canva…

electron调试

electron 调试 electron 的调试分两步&#xff0c;界面的调试&#xff0c;和主进程的调试。 界面调试类似浏览器F12&#xff0c;可是调试不到主进程。 主进程调试有vscode、命令行提示和外部调试器调试。 本篇记录的练习是vscode调试。命令行和外部调试器的方式可以参考官网&a…

CJS与ESM:CJS

模块化方案 历史上&#xff0c;JavaScript 一直没有模块&#xff08;module&#xff09;体系&#xff0c;无法将一个大程序拆分成互相依赖的小文件&#xff0c;再用简单的方法拼装起来。其他语言都有这项功能&#xff0c;比如 Ruby 的require、Python 的import&#xff0c;甚至…

PEFT LoRA 介绍(LoRA微调使用的参数及方法)

一 PEFT LoRA 介绍 官网简介如下图&#xff1a; 翻译过来是&#xff1a;低秩自适应(LoRA)是一种PEFT方法&#xff0c;它将一个大矩阵在注意层分解成两个较小的低秩矩阵。这大大减少了需要微调的参数数量。 说的只是针对注意力层&#xff0c;其实我自己平时微调操作注意力层多…

nacos2.x作为配置中心和服务注册和发现以及springcloud使用

目录 一、nacos是什么 二、windows下安装配置nacos 1、准备 2、安装nacos 3、配置nacos 4、启动并且访问nacos 三、springcloud使用nacos作为配置中心 四、springcloud使用nacos进行服务注册与发现 五、springcloud使用nacos进行服务消费 六、nacos的一些高级配置 1…

Ubuntu上编译多个版本的frida

准备工作 Ubuntu20(WSL) 略 安装依赖 sudo apt update sudo apt-get install build-essential git lib32stdc-9-dev libc6-dev-i386 -y nodejs 去官网[1]下载nodejs&#xff0c;版本的话我就选的20.15.1&#xff1a; tar -xf node-v20.15.1-linux-x64.tar.xz 下载源码 …

AbutionGraph时序(流式)图数据库开发文档地址

AbutionGraph-时序(流式)图数据库&#xff0c;官方开发文档(API)地址&#xff1a; http://www.thutmose.cn

JavaSE从零开始到精通(九) - 双列集合

1.前言 Java 中的双列集合主要指的是可以存储键值对的集合类型&#xff0c;其中最常用的包括 Map 接口及其实现类。这些集合允许你以键值对的形式存储和管理数据&#xff0c;提供了便捷的按键访问值的方式。 2. HashMap HashMap 是基于哈希表实现的 Map 接口的类&#xff0c…

java算法day23

java算法day23 121买卖股票的最佳时机55 跳跃游戏45 跳跃游戏Ⅱ763划分子母区间 121买卖股票的最佳时机 最容易想的应该就是两个for暴力枚举。但是超时 本题用贪心做应该是最快的。 先看清楚题&#xff0c;题目要求在某一天买入&#xff0c;然后在某一天卖出&#xff0c;要求…