仿牛客网讨论社区项目—项目总结及项目常见面试题

Posted 修飞机传感器的程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仿牛客网讨论社区项目—项目总结及项目常见面试题相关的知识,希望对你有一定的参考价值。

1.项目中大部分的功能和技术

        整个技术是构建在SpringBoot上的,其他技术是依托于SpringBoot之上的。SpringBoot只是起到辅助的作用,降低其他技术的使用难度。整个技术的核心是Spring框架,在Spring之上使用了SpringMvc(解决了前后端请求处理交互的问题)、Spring Mybatis(可以访问数据库)、Spring Security(用于管理项目中的登录权限等)。SpringMvc、Spring Mybatis、Spring Security构成了项目的基石,项目中几乎所有请求是由他们完成的。 

2.权限模块

        应用了Spring Email和SpringMvc中的Interceptor(拦截器),其中拦截器能拦截所有请求,能解决通用的问题,涉及的面比较广、影响的请求比较多要重点关注。权限模块主要开发了注册、登录、退出、状态(在每个页面上怎么去显示登录用户的头像、用户名等)、设置(用户头像、修改密码等)、授权(不同类型的用户访问不同的功能,使用Security实现的)、会话管理(重点需要了解Cookie、session、项目中为什么不用session(主要是考虑分布式部署Session的问题)、不用session是如何解决的问题(把数据存在Redis中,使用了ThreadLocal))等功能。

3.核心功能

        基于SpringMvc实现的首页、帖子、评论、私信的功能,异常和日志使用到了通用的技术。重点关注敏感词是怎么实现的(前缀树算法),事务也需要重点关注(什么是事务、事物的隔离级别,怎么去管理事务的)。整个模块还用到了Advice(控制器的通知,统一处理了异常)、AOP(统一记录了日志,事实上其他的很多功能(比如事务)都应用到了AOP)、Transaction(重点)。

4.性能模块

        一些高频访问的功能(点赞、关注、统计、缓存)需要redis,redis不止能应用于这些功能还应用于统计网站的UV,活跃用户等使用了redis的两种特殊的数据类型,还使用redis用作缓存提高了性能。

5.通知模块

        应用了消息队列的Kakfa,框架的使用非常简单,重点去了解Kafka的生产消费模式。重点回顾生产消费模型,了解能够解决哪些问题。

6.搜索功能

        全文搜索:针对帖子能够进行全文搜索,使用了Elasticsearch,使用起来也是非常简单,重点了解其数据结构,其存数据的方式与数据库不同,关注其索引的结构(找一找相关的文章)

7.其他功能

        排行榜、上传、服务器缓存、线程池、缓存等。使用Quartz定时任务,重点关注Caffeine怎么提高了应用的性能,还要了解其局限性。还要重点关注线程池、缓存这两个话题。

8.面试官考察的方面

1.职业素养

程序员的基本素养。数据结构与算法、计算机基础等。

2.项目经验

技术栈的丰富度,看你来了之后能不能直接干活。

3.钻研能力

看看你的长板,看你熟悉的部分,一直问到你的底位置(不要贪多,钻研一个问题)。

9.常见面试题

主要有三个方面 

9.1.1 mysql存储引擎

        InnoDB好在他支持事务,绝大多数时候要使用InnoDB。NDB集群式部署的时候要用到。

9.1.2 MySQL事务

 很重要!!!!

9.1.3 MySQL锁

实现隔离性需要加锁

 

9.1.4 MySQL索引

9.2.1 Redis数据类型

9.2.2 Redis过期策略

 淘汰策略

 9.2.3 Redis缓存穿透

 9.2.4 Redis缓存击穿

9.2.5 Redis缓存雪崩

9.2.6 分布式锁 

 9.3.1 Spring IoC

9.3.2 Spring AOP

 9.3.3 Spring MVC

  • 客户端发出请求访问服务器时,由DispatcherServlet处理。

  • DispatcherServlet调用HandlerMapping(根据访问路径找到对应Controller)。

  • HandlerMapping给DispatcherServlet返回HandlerExecutionChain对象(封装了Controller和拦截器)。
  • DispatcherServlet调用拦截器的preHandle()方法,接着调用HandlerAdapter(内部调了Controller)。
  • HandlerAdapter返回ModelAndView给DispatcherServlet,返回后调用postHandle()方法。
  • DispatcherServlet调用ViewResolver(视图解析器)。
  • ViewResolver解析View,由模板引擎渲染,(拦截器的afterCompletion()方法)返回客户端。

至此项目完结!

项目代码及相关资源:Ming-XMU (Yiming Zhang) · GitHub

麻烦点点小星星!!!!!!

CSDN下载需要积分基于SpringBoot仿牛客网讨论社区项目-Java文档类资源-CSDN下载

从零开始—仿牛客网讨论社区项目(一)_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目(二)_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目(三)_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目(四)_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目(五)_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目(六)_芙蓉铁蛋的博客-CSDN博客

仿牛客网讨论社区项目—优化网站性能_芙蓉铁蛋的博客-CSDN博客

从零开始—仿牛客网讨论社区项目

Kafka 构建TB级异步消息系统

项目主要技术架构:

SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator

1.阻塞队列

 阻塞队列案例:

public class BlockingQueueTests 

    public static void main(String[] args) 
        BlockingQueue queue = new ArrayBlockingQueue(10);
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
    



class Producer implements Runnable 

    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) 
        this.queue = queue;
    

    @Override
    public void run() 
        try 
            for (int i = 0; i < 100; i++) 
                Thread.sleep(20);
                queue.put(i);
                System.out.println(Thread.currentThread().getName() + "生产:" + queue.size());
            
         catch (Exception e) 
            e.printStackTrace();
        
    



class Consumer implements Runnable 

    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) 
        this.queue = queue;
    

    @Override
    public void run() 
        try 
            while (true) 
                Thread.sleep(new Random().nextInt(1000));
                queue.take();
                System.out.println(Thread.currentThread().getName() + "消费:" + queue.size());
            
         catch (Exception e) 
            e.printStackTrace();
        
    

2.kafka

3.Spring整合kafka

        在Maven Repository搜索kafka配置文件,在resources文件包内的pom.xml文件中导入相关的配置文件依赖,并在application.properties文件中配置相关的参数。

# KafkaProperties
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=community-consumer-group
#自动提交
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=3000

测试一下Kafka

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class KafkaTests 

    @Autowired
    private KafkaProducer kafkaProducer;

    @Test
    public void testKafka() 
        kafkaProducer.sendMessage("test", "你好");
        kafkaProducer.sendMessage("test", "在吗");

        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());
    


4.发布系统通知

为什么要用消息队列:

        如果采用消息队列,那么评论、点赞、关注三类不同的事,可以定义三类不同的主题(评论、点赞、关注),发生相应的事件可以将其包装成一条消息放入对应的队列里。那么当前的线程可以继续处理下一条消息,不用处理后续的业务(后续由消费者线程处理)。面向事件驱动编程。

        在entit包下创建event类

public class Event 

    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, Object value) 
        this.data.put(key, value);
        return this;
    

在community目录下创建EventProducer、EventCnsumer类

@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 CommunityConstant 

    private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);

    @Autowired
    private MessageService messageService;

    @KafkaListener(topics = TOPIC_COMMENT, TOPIC_LIKE, 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 = new Message();
        message.setFromId(SYSTEM_USER_ID);
        message.setToId(event.getEntityUserId());
        message.setConversationId(event.getTopic());
        message.setCreateTime(new Date());

        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

@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant 

    @Autowired
    private CommentService commentService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private EventProducer eventProducer;

    @Autowired
    private DiscussPostService discussPostService;

    @RequestMapping(path = "/add/discussPostId", method = RequestMethod.POST)
    public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) 
        comment.setUserId(hostHolder.getUser().getId());
        comment.setStatus(0);
        comment.setCreateTime(new Date());
        commentService.addComment(comment);

        // 触发评论事件
        Event event = new Event()
                .setTopic(TOPIC_COMMENT)
                .setUserId(hostHolder.getUser().getId())
                .setEntityType(comment.getEntityType())
                .setEntityId(comment.getEntityId())
                .setData("postId", discussPostId);
        if (comment.getEntityType() == ENTITY_TYPE_POST) 
            DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());
            event.setEntityUserId(target.getUserId());
         else if (comment.getEntityType() == ENTITY_TYPE_COMMENT) 
            Comment target = commentService.findCommentById(comment.getEntityId());
            event.setEntityUserId(target.getUserId());
        
        eventProducer.fireEvent(event);

        return "redirect:/discuss/detail/" + discussPostId;
    


LikeController

@Controller
public class LikeController implements CommunityConstant 

    @Autowired
    private LikeService likeService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private EventProducer eventProducer;

    @RequestMapping(path = "/like", method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId, int entityUserId, int postId) 
        User user = hostHolder.getUser();

        // 点赞
        likeService.like(user.getId(), entityType, entityId, entityUserId);

        // 数量
        long likeCount = likeService.findEntityLikeCount(entityType, entityId);
        // 状态
        int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
        // 返回的结果
        Map<String, Object> map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);

        // 触发点赞事件
        if (likeStatus == 1) 
            Event event = new Event()
                    .setTopic(TOPIC_LIKE)
                    .setUserId(hostHolder.getUser().getId())
                    .setEntityType(entityType)
                    .setEntityId(entityId)
                    .setEntityUserId(entityUserId)
                    .setData("postId", postId);
            eventProducer.fireEvent(event);
        

        return CommunityUtil.getJSONString(0, null, map);
    


Followcontroller

@Controller
public class FollowController implements CommunityConstant 

    @Autowired
    private FollowService followService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;

    @Autowired
    private EventProducer eventProducer;

    @RequestMapping(path = "/follow", method = RequestMethod.POST)
    @ResponseBody
    public String follow(int entityType, int entityId) 
        User user = hostHolder.getUser();

        followService.follow(user.getId(), entityType, entityId);

        // 触发关注事件
        Event event = new Event()
                .setTopic(TOPIC_FOLLOW)
                .setUserId(hostHolder.getUser().getId())
                .setEntityType(entityType)
                .setEntityId(entityId)
                .setEntityUserId(entityId);
        eventProducer.fireEvent(event);

        return CommunityUtil.getJSONString(0, "已关注!");
    

    @RequestMapping(path = "/unfollow", method = RequestMethod.POST)
    @ResponseBody
    public String unfollow(int entityType, int entityId) 
        User user = hostHolder.getUser();

        followService.unfollow(user.getId(), entityType, entityId);

        return CommunityUtil.getJSONString(0, "已取消关注!");
    

    @RequestMapping(path = "/followees/userId", method = RequestMethod.GET)
    public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) 
        User user = userService.findUserById(userId);
        if (user == null) 
            throw new RuntimeException("该用户不存在!");
        
        model.addAttribute("user", user);

        page.setLimit(5);
        page.setPath("/followees/" + userId);
        page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));

        List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
        if (userList != null) 
            for (Map<String, Object> map : userList) 
                User u = (User) map.get("user");
                map.put("hasFollowed", hasFollowed(u.getId()));
            
        
        model.addAttribute("users", userList);

        return "/site/followee";
    

    @RequestMapping(path = "/followers/userId", method = RequestMethod.GET)
    public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) 
        User user = userService.findUserById(userId);
        if (user == null) 
            throw new RuntimeException("该用户不存在!");
        
        model.addAttribute("user", user);

        page.setLimit(5);
        page.setPath("/followers/" + userId);
        page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));

        List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
        if (userList != null) 
            for (Map<String, Object> map : userList) 
                User u = (User) map.get("user");
                map.put("hasFollowed", hasFollowed(u.getId()));
            
        
        model.addAttribute("users", userList);

        return "/site/follower";
    

    private boolean hasFollowed(int userId) 
        if (hostHolder.getUser() == null) 
            return false;
        

        return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
    

5.显示系统通知

更新messagemapper

@Mapper
public interface MessageMapper 

    // 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.
    List<Message> selectConversations(int userId, int offset, int limit);

    // 查询当前用户的会话数量.
    int selectConversationCount(int userId);

    // 查询某个会话所包含的私信列表.
    List<Message> selectLetters(String conversationId, int offset, int limit);

    // 查询某个会话所包含的私信数量.
    int selectLetterCount(String conversationId);

    // 查询未读私信的数量
    int selectLetterUnreadCount(int userId, String conversationId);

    // 新增消息
    int insertMessage(Message message);

    // 修改消息的状态
    int updateStatus(List<Integer> ids, int status);

    // 查询某个主题下最新的通知
    Message selectLatestNotice(int userId, String topic);

    // 查询某个主题所包含的通知数量
    int selectNoticeCount(int userId, String topic);

    // 查询未读的通知的数量
    int selectNoticeUnreadCount(int userId, String topic);

    // 查询某个主题所包含的通知列表
    List<Message> selectNotices(int userId, String topic, int offset, int limit);

meaage-mapper

<?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.nowcoder.community.dao.MessageMapper">

    <sql id="selectFields">
        id, from_id, to_id, conversation_id, content, status, create_time
    </sql>

    <sql id="insertFields">
        from_id, to_id, conversation_id, content, status, create_time
    </sql>

    <select id="selectConversations" resultType="Message">
        select <include refid="selectFields"></include>
        from message
        where id in (
            select max(id) from message
            where status != 2
            and from_id != 1
            and (from_id = #userId or to_id = #userId)
            group by conversation_id
        )
        order by id desc
        limit #offset, #limit
    </select>

    <select id="selectConversationCount" resultType="int">
        select count(m.maxid) from (
            select max(id) as maxid from message
            where status != 2
            and from_id != 1
            and (from_id = #userId or to_id = #userId)
            group by conversation_id
        ) as m
    </select>

    <select id="selectLetters" resultType="Message">
        select <include refid="selectFields"></include>
        from message
        where status != 2
        and from_id != 1
        and conversation_id = #conversationId
        order by id desc
        limit #offset, #limit
    </select>

    <select id="selectLetterCount" resultType="int">
        select count(id)
        from message
        where status != 2
        and from_id != 1
        and conversation_id = #conversationId
    </select>

    <select id="selectLetterUnreadCount" resultType="int">
        select count(id)
        from message
        where status = 0
        and from_id != 1
        and to_id = #userId
        <if test="conversationId!=null">
            and conversation_id = #conversationId
        </if>
    </select>

    <insert id="insertMessage" parameterType="Message" keyProperty="id">
        insert into message(<include refid="insertFields"></include>)
        values(#fromId,#toId,#conversationId,#content,#status,#createTime)
    </insert>

    <update id="updateStatus">
        update message set status = #status
        where id in
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #id
        </foreach>
    </update>

    <select id="selectLatestNotice" resultType="Message">
        select <include refid="selectFields"></include>
        from message
        where id in (
            select max(id) from message
            where status != 2
            and from_id = 1
            and to_id = #userId
            and conversation_id = #topic
        )
    </select>

    <select id="selectNoticeCount" resultType="int">
        select count(id) from message
        where status != 2
        and from_id = 1
        and to_id = #userId
        and conversation_id = #topic
    </select>

    <select id="selectNoticeUnreadCount" resultType="int">
        select count(id) from message
        where status = 0
        and from_id = 1
        and to_id = #userId
        <if test="topic!=null">
            and conversation_id = #topic
        </if>
    </select>

    <select id="selectNotices" resultType="Message">
        select <include refid="selectFields"></include>
        from message
        where status != 2
        and from_id = 1
        and to_id = #userId
        and conversation_id = #topic
        order by create_time desc
        limit #offset, #limit
    </select>

</mapper>

message service

@Service
public class MessageService 

    @Autowired
    private MessageMapper messageMapper;

    @Autowired
    private SensitiveFilter sensitiveFilter;

    public List<Message> findConversations(int userId, int offset, int limit) 
        return messageMapper.selectConversations(userId, offset, limit);
    

    public int findConversationCount(int userId) 
        return messageMapper.selectConversationCount(userId);
    

    public List<Message> findLetters(String conversationId, int offset, int limit) 
        return messageMapper.selectLetters(conversationId, offset, limit);
    

    public int findLetterCount(String conversationId) 
        return messageMapper.selectLetterCount(conversationId);
    

    public int findLetterUnreadCount(int userId, String conversationId) 
        return messageMapper.selectLetterUnreadCount(userId, conversationId);
    

    public int addMessage(Message message) 
        message.setContent(HtmlUtils.htmlEscape(message.getContent()));
        message.setContent(sensitiveFilter.filter(message.getContent()));
        return messageMapper.insertMessage(message);
    

    public int readMessage(List<Integer> ids) 
        return messageMapper.updateStatus(ids, 1);
    

    public Message findLatestNotice(int userId, String topic) 
        return messageMapper.selectLatestNotice(userId, topic);
    

    public int findNoticeCount(int userId, String topic) 
        return messageMapper.selectNoticeCount(userId, topic);
    

    public int findNoticeUnreadCount(int userId, String topic) 
        return messageMapper.selectNoticeUnreadCount(userId, topic);
    

    public List<Message> findNotices(int userId, String topic, int offset, int limit) 
        return messageMapper.selectNotices(userId, topic, offset, limit);
    


message controller 并修改相应的html文件

@Controller
public class MessageController implements CommunityConstant 

    @Autowired
    private MessageService messageService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;

    // 私信列表
    @RequestMapping(path = "/letter/list", method = RequestMethod.GET)
    public String getLetterList(Model model, Page page) 
        User user = hostHolder.getUser();
        // 分页信息
        page.setLimit(5);
        page.setPath("/letter/list");
        page.setRows(messageService.findConversationCount(user.getId()));

        // 会话列表
        List<Message> conversationList = messageService.findConversations(
                user.getId(), page.getOffset(), page.getLimit());
        List<Map<String, Object>> conversations = new ArrayList<>();
        if (conversationList != null) 
            for (Message message : conversationList) 
                Map<String, Object> map = new HashMap<>();
                map.put("conversation", message);
                map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
                map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
                int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
                map.put("target", userService.findUserById(targetId));

                conversations.add(map);
            
        
        model.addAttribute("conversations", conversations);

        // 查询未读消息数量
        int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
        model.addAttribute("letterUnreadCount", letterUnreadCount);
        int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
        model.addAttribute("noticeUnreadCount", noticeUnreadCount);

        return "/site/letter";
    

    @RequestMapping(path = "/letter/detail/conversationId", method = RequestMethod.GET)
    public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) 
        // 分页信息
        page.setLimit(5);
        page.setPath("/letter/detail/" + conversationId);
        page.setRows(messageService.findLetterCount(conversationId));

        // 私信列表
        List<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
        List<Map<String, Object>> letters = new ArrayList<>();
        if (letterList != null) 
            for (Message message : letterList) 
                Map<String, Object> map = new HashMap<>();
                map.put("letter", message);
                map.put("fromUser", userService.findUserById(message.getFromId()));
                letters.add(map);
            
        
        model.addAttribute("letters", letters);

        // 私信目标
        model.addAttribute("target", getLetterTarget(conversationId));

        // 设置已读
        List<Integer> ids = getLetterIds(letterList);
        if (!ids.isEmpty()) 
            messageService.readMessage(ids);
        

        return "/site/letter-detail";
    

    private User getLetterTarget(String conversationId) 
        String[] ids = conversationId.split("_");
        int id0 = Integer.parseInt(ids[0]);
        int id1 = Integer.parseInt(ids[1]);

        if (hostHolder.getUser().getId() == id0) 
            return userService.findUserById(id1);
         else 
            return userService.findUserById(id0);
        
    

    private List<Integer> getLetterIds(List<Message> letterList) 
        List<Integer> ids = new ArrayList<>();

        if (letterList != null) 
            for (Message message : letterList) 
                if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) 
                    ids.add(message.getId());
                
            
        

        return ids;
    

    @RequestMapping(path = "/letter/send", method = RequestMethod.POST)
    @ResponseBody
    public String sendLetter(String toName, String content) 
        User target = userService.findUserByName(toName);
        if (target == null) 
            return CommunityUtil.getJSONString(1, "目标用户不存在!");
        

        Message message = new Message();
        message.setFromId(hostHolder.getUser().getId());
        message.setToId(target.getId());
        if (message.getFromId() < message.getToId()) 
            message.setConversationId(message.getFromId() + "_" + message.getToId());
         else 
            message.setConversationId(message.getToId() + "_" + message.getFromId());
        
        message.setContent(content);
        message.setCreateTime(new Date());
        messageService.addMessage(message);

        return CommunityUtil.getJSONString(0);
    

    @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 = HtmlUtils.htmlUnescape(message.getContent());
            Map<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 = HtmlUtils.htmlUnescape(message.getContent());
            Map<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 = HtmlUtils.htmlUnescape(message.getContent());
            Map<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);
        model.addAttribute("letterUnreadCount", letterUnreadCount);
        int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
        model.addAttribute("noticeUnreadCount", noticeUnreadCount);

        return "/site/notice";
    

    @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.setPath("/notice/detail/" + topic);
        page.setRows(messageService.findNoticeCount(user.getId(), 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 = HtmlUtils.htmlUnescape(notice.getContent());
                Map<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";
    


消息未读使用拦截器实现,因为每个请求都需要记录次数。

@Component
public class MessageInterceptor 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();
        if (user != null && modelAndView != null) 
            int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
            int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
            modelAndView.addObject("allUnreadCount", letterUnreadCount + noticeUnreadCount);
        
    

配置拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer 

    @Autowired
    private AlphaInterceptor alphaInterceptor;

    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;

    @Autowired
    private MessageInterceptor messageInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(alphaInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .addPathPatterns("/register", "/login");

        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");

        registry.addInterceptor(loginRequiredInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");

        registry.addInterceptor(messageInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    

第五部分

https://blog.csdn.net/qq_43351888/article/details/123959047?spm=1001.2014.3001.5502

项目代码及相关资源:https://github.com/Ming-XMU

麻烦点点小星星!!!!!!

 CSDN下载需要积分基于SpringBoot仿牛客网讨论社区项目-Java文档类资源-CSDN下载

以上是关于仿牛客网讨论社区项目—项目总结及项目常见面试题的主要内容,如果未能解决你的问题,请参考以下文章

Java牛客项目课_仿牛客网讨论区_第六章

Java牛客项目课_仿牛客网讨论区_第八章

Java牛客项目课_仿牛客网讨论区_第八章

Java牛客项目课_仿牛客网讨论区_第二章

仿牛客网项目总结

Java牛客项目课_仿牛客网讨论区_运行牛客讨论区项目要做的