牛客项目学习第五章

一、阻塞队列

  • BlockingQueue
    • 解决线程通信的问题。
    • 阻塞方法:put、take。
  • 生产者消费者模式
    • 生产者:产生数据的线程。
    • 消费者:使用数据的线程。
  • 实现类
    • ArrayBlockingQueue
    • LinkedBlockingQueue
    • PriorityBlockingQueue、SynchronousQueue、DelayQueue等。

在这里插入图片描述

面试常问:写一个生产者消费者实现

public class Test {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new LinkedBlockingDeque<>(10);
        Producer p = new Producer(queue);
        Consumer c = new Consumer(queue);
        new Thread(p,"producer").start();
        new Thread(c,"consumer").start();
    }
}
class Consumer implements Runnable {
    private BlockingQueue<String> queue;

    public Consumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while(true){
                Thread.sleep(20);
                System.out.println("消费者消费了:" + queue.take());
            }
        }catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}
class Producer implements Runnable{
    private BlockingQueue<String> queue;
    public Producer(BlockingQueue<String> queue){
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                String tmp = "a product " + i + " from:" + Thread.currentThread().getName();
                System.out.println("生产者生产了:" + tmp);
                queue.put(tmp);
                Thread.sleep(20);
            }
        }catch (InterruptedException e) {
                e.printStackTrace();
        }
    }
}

二、Kafka入门

  • Kafka简介
    • Kafka是一个分布式的流媒体平台。
    • 应用:消息系统、日志收集、用户行为追踪、流式处理。
  • Kafka特点
    • 高吞吐量、消息持久化、高可靠性、高扩展性。
  • Kafka术语
    • Broker、Zookeeper
    • Topic、Partition、Offset
    • Leader Replica 、Follower Replica

Kafka术语解释

  • Broker:Kafka的服务器
  • Zookeeper:管理集群
  • Topic:点对点模式中每个消费者拿到的消息都不同,发布订阅模式中消费者可能拿到同一份消息。Kafka采用发布订阅模式,生产者把消息发布到的空间(位置)就叫Topic
  • Partition:是对Topic位置的分区,如下图:
    在这里插入图片描述
  • Offset:就是消息在分区中的索引
    在这里插入图片描述
  • Leader Replica:主副本,可以处理请求
  • Follower Replica:从副本,只是用作备份

Kafka相关链接:官网

windows下更改配置

1.config目录下zookeeper.properties修改
在这里插入图片描述2.server.properties
在这里插入图片描述

window下使用Kafka

1.启动Zookeeper
在这里插入图片描述

2.启动Kafka
在这里插入图片描述
启动完成后就会出现我们设置的文件夹
在这里插入图片描述

3.创建主题
在这里插入图片描述查看所有主题判断是否创建成功
在这里插入图片描述
4.往主题上发送消息
在这里插入图片描述发两条消息
在这里插入图片描述
5.消费者接收消息
在这里插入图片描述

三、Spring整合Kafka

  • 引入依赖
    • spring-kafka
  • 配置Kafka
    • 配置server、consumer
  • 访问Kafka
    • 生产者
      kafkaTemplate.send(topic, data);
    • 消费者
      @KafkaListener(topics = {“test”})
      public void handleMessage(ConsumerRecord record) {}

在这里插入图片描述

导包

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.5.0.RELEASE</version>
</dependency>

application.properties中配置

在这里插入图片描述

测试一波

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class KafkaTest {
    @Autowired
    KafkaProducer kafkaProducer;
    @Autowired
    KafkaConsumer kafkaConsumer;
    @Test
    public void testKafka(){
        kafkaProducer.sendMessage("test","hello world");
        kafkaProducer.sendMessage("test","I love java");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
@Component
class KafkaProducer{
    @Autowired
    private KafkaTemplate kafkaTemplate;
    public void sendMessage(String topic,String content){
        kafkaTemplate.send(topic,content);
    }
}
@Component
class KafkaConsumer{
    @KafkaListener(topics = {"test"})
    public void handleMessage(ConsumerRecord record){
        System.out.println(record.value());
    }
}

生产者主动发消息,消费者被动接收消息

四、发送系统通知

  • 触发事件
    • 评论后,发布通知
    • 点赞后,发布通知
    • 关注后,发布通知
  • 处理事件
    • 封装事件对象
    • 开发事件的生产者
    • 开发事件的消费者

在这里插入图片描述

封装事件对象

public class Event {
//张三给李四点赞---userId是张三,entityUserId是李四
    private String topic;
    private int userId;
    private int entityType;
    private int entityId;
    private int entityUserId;
    private Map<String,Object> data = new HashMap<>();
    
    public String getTopic() {
        return topic;
    }

    public Event setTopic(String topic) {
        this.topic = topic;
        return this;
    }

    public int getUserId() {
        return userId;
    }

    public Event setUserId(int userId) {
        this.userId = userId;
        return this;
    }

    public int getEntityType() {
        return entityType;
    }

    public Event setEntityType(int entityType) {
        this.entityType = entityType;
        return this;
    }

    public int getEntityId() {
        return entityId;
    }

    public Event setEntityId(int entityId) {
        this.entityId = entityId;
        return this;
    }

    public int getEntityUserId() {
        return entityUserId;
    }

    public Event setEntityUserId(int entityUserId) {
        this.entityUserId = entityUserId;
        return this;
    }

    public Map<String, Object> getData() {
        return data;
    }

    public Event setData(String key,String object) {
        this.data.put(key,object);
        return this;
    }
}

  • 注意set方法的修改是为了可以类似sb.append(" ").append(“1”);的操作
  • 注意setData方法的修改

开发事件的生产者

新建event包

@Component
public class EventProducer {
    @Autowired
    private KafkaTemplate kafkaTemplate;

    //处理事件,本质就是发送消息
    public  void fireEvent(Event event){
        //将事件发送到指定的主题
        kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
    }
}

开发事件的消费者

@Component
public class EventConsumer implements CommunityContant {
    //记日志
    private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
    //消息最终是要往message表中插入数据的
    @Autowired
    private MessageService messageService;

    @KafkaListener(topics = {TOPIC_LIKE,TOPIC_COMMENT,TOPIC_FOLLOW})
    public void handleCommentMessage(ConsumerRecord record){
        if(record==null||record.value()==null){
            logger.error("消息的内容为空");
            return;
        }
        Event event = JSONObject.parseObject(record.value().toString(),Event.class);
        if(event==null){
            logger.error("消息格式错误");
            return;
        }
        //发送站内通知,主要是构造message对象
        Message message = new Message();
        //User表中id为1代表系统用户
        message.setFromId(SYSTEM_ID);
        message.setToId(event.getEntityUserId());
        message.setConversationId(event.getTopic());
        message.setCreateTime(new Date());
        //为啥不直接把Event转成json存在content里边?
        Map<String,Object> content = new HashMap<>();
        content.put("userId",event.getUserId());
        content.put("entityType",event.getEntityType());
        content.put("entityId",event.getEntityId());
        if(!event.getData().isEmpty()){
            for(Map.Entry<String,Object> entry:event.getData().entrySet()){
                content.put(entry.getKey(),entry.getValue());
            }
        }
        message.setContent(JSONObject.toJSONString(content));
        messageService.addMessage(message);
    }
}

处理评论事件–CommentController

增加如下代码
在这里插入图片描述

处理点赞事件–LikeController

增加如下代码
在这里插入图片描述因为重构了方法我们处理下js传参
discuss-detail页面
在这里插入图片描述在这里插入图片描述

处理关注事件–FollowController

follow方法
在这里插入图片描述

一些小bug

1.Kafka报错: Topic(s) [xxx] is/are not present and missingTopicsFatal is true

  • 报错原因: 消费监听接口监听的主题不存在时,默认会报错

  • 解决方法: 配置文件中将listener的属性missingTopicsFatal设置为false

spring.kafka.listener.missing-topics-fatal=false

2.以前写的AOP记录日志报错
在这里插入图片描述在这里插入图片描述
因为消费者里也调了service方法所以上图红框有可能取不到request

解决方法

在这里插入图片描述

测试一波

发现message表中已经有数据了
在这里插入图片描述

五、显示系统通知

  • 通知列表
    • 显示评论、点赞、关注三种类型的通知
  • 通知详情
    • 分页显示某一类主题所包含的通知
  • 未读消息
    • 在页面头部显示所有的未读消息数量

在这里插入图片描述

列表页写MessageMapper

因为消息存到了message表里
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

列表页service层

MessageService类中
在这里插入图片描述

列表页MessageController

@RequestMapping(path="/notice/list",method = RequestMethod.GET)
    public String getNoticeList(Model model){
        User user = hostHolder.getUser();
        //查询评论类通知
        Message message = messageService.findLatestNotice(user.getId(),TOPIC_COMMENT);
        Map<String,Object> messageVo = new HashMap<>();
        if(message!=null){
            messageVo.put("message",message);
            String content = message.getContent();
            //{&quot;entityType&quot;:1,&quot;entityId&quot;:275,&quot;postId&quot;:275,&quot;userId&quot;:111}
            content = HtmlUtils.htmlUnescape(content);  //这步可以去除上方转义字符
            HashMap<String,Object> data = JSONObject.parseObject(content, HashMap.class);
            //谁发给我?
            messageVo.put("user",userService.findUserById((Integer)data.get("userId")));
            messageVo.put("entityType",data.get("entityType"));
            messageVo.put("entityId",data.get("entityId"));
            messageVo.put("postId",data.get("postId"));
            int count = messageService.findNoticeCount(user.getId(),TOPIC_COMMENT);
            messageVo.put("count",count);
            int unread = messageService.findNoticeUnreadCount(user.getId(),TOPIC_COMMENT);
            messageVo.put("unread",unread);
        }
        model.addAttribute("commentNotice",messageVo);
        //查询点赞类通知
        message = messageService.findLatestNotice(user.getId(),TOPIC_LIKE);
        messageVo = new HashMap<>();
        if(message!=null){
            messageVo.put("message",message);
            String content = message.getContent();
            //{&quot;entityType&quot;:1,&quot;entityId&quot;:275,&quot;postId&quot;:275,&quot;userId&quot;:111}
            content = HtmlUtils.htmlUnescape(content);  //这步可以去除上方转义字符
            HashMap<String,Object> data = JSONObject.parseObject(content, HashMap.class);
            //谁发给我?
            messageVo.put("user",userService.findUserById((Integer)data.get("userId")));
            messageVo.put("entityType",data.get("entityType"));
            messageVo.put("entityId",data.get("entityId"));
            messageVo.put("postId",data.get("postId"));
            int count = messageService.findNoticeCount(user.getId(),TOPIC_LIKE);
            messageVo.put("count",count);
            int unread = messageService.findNoticeUnreadCount(user.getId(),TOPIC_LIKE);
            messageVo.put("unread",unread);
        }
        model.addAttribute("likeNotice",messageVo);
        //查询关注类通知
        message = messageService.findLatestNotice(user.getId(),TOPIC_FOLLOW);
        messageVo = new HashMap<>();
        if(message!=null){
            messageVo.put("message",message);
            String content = message.getContent();
            //{&quot;entityType&quot;:1,&quot;entityId&quot;:275,&quot;postId&quot;:275,&quot;userId&quot;:111}
            content = HtmlUtils.htmlUnescape(content);  //这步可以去除上方转义字符
            HashMap<String,Object> data = JSONObject.parseObject(content, HashMap.class);
            //谁关注了我?
            messageVo.put("user",userService.findUserById((Integer)data.get("userId")));
            messageVo.put("entityType",data.get("entityType"));
            messageVo.put("entityId",data.get("entityId"));
            int count = messageService.findNoticeCount(user.getId(),TOPIC_FOLLOW);
            messageVo.put("count",count);
            int unread = messageService.findNoticeUnreadCount(user.getId(),TOPIC_FOLLOW);
            messageVo.put("unread",unread);
        }
        model.addAttribute("followNotice",messageVo);
        //查询未读消息数量
        int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(),null);
        int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(),null);
        model.addAttribute("letterUnreadCount",letterUnreadCount);
        model.addAttribute("noticeUnreadCount",noticeUnreadCount);
        return  "/site/notice";
    }
}

还有一点修改时在letter页面上也得显示系统通知的数量所以在/message/list路径下增加如下代码,然后处理下letter.html的页面
在这里插入图片描述

列表页notice的页面

先修改成Thymeleaf,其他填数据
在这里插入图片描述评论的模板填充,点赞和关注同理
在这里插入图片描述

测试一波
在这里插入图片描述

详情页MessageMapper

在这里插入图片描述

详情页MessageService

在这里插入图片描述

详情页MessageController

@RequestMapping(path = "/notice/detail/{topic}",method = RequestMethod.GET)
    public String getNoticeDetail(@PathVariable("topic")String topic,Page page,Model model){
        User user = hostHolder.getUser();
        //处理分页
        page.setLimit(5);
        page.setRows(messageService.findNoticeCount(user.getId(),topic));
        page.setPath("/notice/detail/"+topic);
        List<Message> noticeList = messageService.findNotices(user.getId(),topic,page.getOffset(),page.getLimit());
        List<Map<String,Object>> noticeVoList = new ArrayList<>();
        if(noticeList!=null){
            for(Message notice:noticeList){
                Map<String,Object> map = new HashMap<>();
                //通知
                map.put("notice",notice);
                //内容
                String content = notice.getContent();
                content = HtmlUtils.htmlUnescape(content);
                HashMap<String,Object> data = JSONObject.parseObject(content, HashMap.class);
                map.put("user",userService.findUserById((Integer)data.get("userId")));
                map.put("entityType",data.get("entityType"));
                map.put("entityId",data.get("entityId"));
                map.put("postId",data.get("postId"));
                map.put("fromUser",userService.findUserById(notice.getFromId())); //系统名
                noticeVoList.add(map);
            }
        }
        model.addAttribute("notices",noticeVoList);
        //设置已读
        List<Integer> ids = getLetterIds(noticeList);
        if(!ids.isEmpty()){
            messageService.readMessage(ids);
        }
        return "/site/notice-detail";
    }

详情页notice-detail页面

在这里插入图片描述在这里插入图片描述

头部总消息数通知

在这里插入图片描述
每个请求处理完都要查看所以用拦截器处理

写一个拦截器

@Component
public class MessageInterator implements HandlerInterceptor {
    @Autowired
    private HostHolder hostHolder;
    @Autowired
    private MessageService messageService;

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        //user是否登录,是否模板可以携带
        if(user!=null&&modelAndView!=null){
            int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
            int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
            int unreadCount = letterUnreadCount+noticeUnreadCount;
            modelAndView.addObject("unreadCount",unreadCount);
        }
    }
}

加入config中
在这里插入图片描述index页面头部处理一下
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值