一、课前准备
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);}