Bootstrap

仿牛客网项目(五)

发布帖子

功能分析

用户发布帖子之后,页面局部刷新显示帖子内容;
不必刷新浏览器的情况下,实现与服务器之间通讯;
本项目采用AJAX技术实现,局部刷新;

  • AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即是用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。

  • Ajax实际上是下面这几种技术的融合:

    • XHTML和CSS的基于标准的标识技术
    • DOM进行动态显示和交互
    • XML和XSLT进行数据交换和处理
    • XMLHttpRequest进行异步数据检索
    • Javascript将以上技术融合在一起
  • 客户端与服务器,可以在【不必刷新整个浏览器】的情况下,与服务器进行异步通讯的技术

  • 同步交互和异步交互

    • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
    • 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
  • Ajax核心(XMLHttpRequest)
    XMLHttpRequest对象是Ajax中最重要的一个对象。使用Ajax更多的是编写客户端代码,而不是服务端的代码。

  • XMLHttpRequest 工作原理
    传统的web前端与后端的交互中,浏览器直接访问Tomcat的Servlet来获取数据。Servlet通过转发把数据发送给浏览器。
    当我们使用AJAX之后,浏览器是先把请求发送到XMLHttpRequest异步对象之中,异步对象对请求进行封装,然后再与发送给服务器。服务器并不是以转发的方式响应,而是以流的方式把数据返回给浏览器
    XMLHttpRequest异步对象会不停监听服务器状态的变化,得到服务器返回的数据,就写到浏览器上~因为不是转发的方式,所以是无刷新就能够获取服务器端的数据
    在这里插入图片描述

功能实现

为了获取异步传输JSON格式数据,在 CommunityUtil 类中添加getJSONString方法及其重载方法,代码如下:
添加依赖:

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.83</version>
		</dependency>
public static String getJSONString(int code, String msg, Map<String, Object> map) {
        JSONObject json = new JSONObject();
        json.put("code", code);
        json.put("msg", msg);
        if (map != null) {
            for (String key : map.keySet()) {
                json.put(key, map.get(key));
            }
        }
        return json.toJSONString();
    }

    public static String getJSONString(int code, String msg) {
        return getJSONString(code, msg, null);
    }

    public static String getJSONString(int code) {
        return  getJSONString(code, null, null);
    }

在 DiscussPostMapper 类中添加 insertDiscussPost 方法,代码如下:

 	/**
     * 新增帖子
     * @param discussPost
     * @return
     */
    int insertDiscussPost(DiscussPost discussPost);

对应DiscussPostMapper.xml文件中新增如下代码:

	 <sql id="insertFields">
        user_id,title,content,type,status,create_time,comment_count,score
    </sql>
  	<insert id="insertDiscussPost" parameterType="DiscussPost">
        insert into discuss_post(<include refid="insertFields"></include>)
        values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
    </insert>

service层,在DiscussPostService文件中新增如下代码:

  /**
     * 新增帖子
     * @param post
     * @return
     */
    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        //将字符串转换为html标签
        post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
        post.setContent(HtmlUtils.htmlEscape(post.getContent()));
        // 过滤敏感词
        post.setTitle(sensitiveFilter.filter(post.getTitle()));
        post.setContent(sensitiveFilter.filter(post.getContent()));
        return discussPostMapper.insertDiscussPost(post);
    }

controller层,新建DiscussPostController文件,代码如下:

@Controller
@RequestMapping("/discuss")
public class DiscussPostController {
    @Autowired
    private DiscussPostService discussPostService;
    @Autowired
    private HostHolder hostHolder;
    @Autowired
    private UserService userService;
    @PostMapping("/add")
    @ResponseBody //异步请求需要加上该注解
    public String addDiscussPost(String title, String content) {
        User user = hostHolder.getUser();
        if (user == null) {
            return CommunityUtil.getJSONString(403,"你还没有登录哦!");
        }
        DiscussPost post = new DiscussPost();
        post.setUserId(user.getId());
        post.setTitle(title);
        post.setContent(content);
        post.setCreateTime(new Date());
        discussPostService.addDiscussPost(post);
        // 报错的情况,会统一处理
        return CommunityUtil.getJSONString(0, "发布成功!");
    }
}

前端需要对应修改index.html内容,详情见源代码。

功能测试

启动项目,登录成功后点击我要发布;
在这里插入图片描述
在这里插入图片描述

查看帖子详情

功能分析

查看帖子详情,根据帖子id查询帖子的作者、帖子的内容;
前端页面跳转至帖子详情页;

功能实现

dao层,新增selectDiscussPosyById方法,代码如下:

  	/**
     * 根据id查询帖子对象
     * @param id
     * @return
     */
    DiscussPost selectDiscussPostById(int id);

DiscussPostMapper.xml文件中新增代码如下:

	<select id="selectDiscussPostById" resultType="com.ahtoh.community.entity.DiscussPost">
        select <include refid="selectFields"></include>
        from discuss_post
        where id=#{iod}
    </select>

service层,DiscussPostService中新增findDiscussPostById方法,代码如下:

 	/**
     * 根据id查找帖子对象
     * @param id
     * @return
     */
    public DiscussPost findDiscussPostById(int id) {
        return discussPostMapper.selectDiscussPostById(id);
    }

controller层,新增getDiscussPost方法,代码如下:

	 /**
     * 查看帖子详情
     * @param discussPostId
     * @param model
     * @return
     */
    @GetMapping( "/detail/{discussPostId}")
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model) {
        // 查询帖子
        DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
        model.addAttribute("post", post);
        // 查找作者
        User user = userService.findUserById(post.getUserId());
        model.addAttribute("user", user);

        return "/site/discuss-detail";
    }

前端需要对应修改index.html和 discuss-detail.html内容,详情见源代码。

功能测试

启动项目,登录用户,查看帖子详情;
在这里插入图片描述

显示评论

功能分析

新增实体类记录回帖信息和对回帖的回复信息;
根据帖子类型(回帖,回帖的回复)封装回帖对象;
查询不同类型帖子的数量;

功能实现

在 CommunityConstant 接口中添加两个字段:帖子和评论,代码如下:

	/**
     * 实体类型: 帖子
     */
    int ENTITY_TYPE_POST = 1;

    /**
     * 实体类型: 评论
     */
    int ENTITY_TYPE_COMMENT = 2;

entity包下新增Comment实体类,代码如下:

public class Comment {
    /**评论id**/
    private int id;
    /**评论人的id**/
    private int userId;
    /**评论类型 1:帖子 2:评论**/
    private int entityType;
    /**评论对象id 针对帖子的评论:帖子的id 针对评论的评论:评论的id**/
    private int entityId;
    /**a回复b,针对评论的评论,评论对象b的user_id,方便前端快速展示b的用户名**/
    private int targetId;
    /**评论人的内容**/
    private String content;
    /**评论人状态**/
    private int status;
    /**评论人的时间**/
    private Date createTime;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public int getEntityType() {
        return entityType;
    }
    public void setEntityType(int entityType) {
        this.entityType = entityType;
    }
    public int getEntityId() {
        return entityId;
    }
    public void setEntityId(int entityId) {
        this.entityId = entityId;
    }
    public int getTargetId() {
        return targetId;
    }
    public void setTargetId(int targetId) {
        this.targetId = targetId;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", userId=" + userId +
                ", entityType=" + entityType +
                ", entityId=" + entityId +
                ", targetId=" + targetId +
                ", content='" + content + '\'' +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}

dao层,新建CommentMapper接口,代码如下:

@Mapper
public interface CommentMapper {
    /**
     * 根据帖子类型和帖子id查找帖子列表
     * @param entityType
     * @param entityId
     * @param offset
     * @param limit
     * @return
     */
    List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
    /***
     * 根据帖子类型和帖子id查找帖子数量
     * @param entityType
     * @param entityId
     * @return
     */
    int selectCountByEntity(int entityType, int entityId);
}

对应的CommentMapper.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.ahtoh.community.dao.CommentMapper">

    <sql id="selectFields">
        id, user_id, entity_type, entity_id, target_id, content, status, create_time
    </sql>

    <select id="selectCommentsByEntity" resultType="com.ahtoh.community.entity.Comment">
        select <include refid="selectFields"></include>
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
        order by create_time asc
        limit #{offset}, #{limit}
    </select>

    <select id="selectCountByEntity" resultType="int">
        select count(id)
        from comment
        where status = 0
          and entity_type = #{entityType}
          and entity_id = #{entityId}
    </select>

</mapper>

service层,在service包下新建CommentService,代码如下:

@Service
public class CommentService {
    @Autowired
    private CommentMapper commentMapper;
    /**
     * 根据帖子类型和帖子id查找帖子列表
     * @param entityType 帖子类型
     * @param entityId 帖子id
     * @param offset 偏移量
     * @param limit 上限
     * @return
     */
    public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
        return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
    }
    /**
     * 根据帖子类型和帖子id查找帖子数量
     * @param entityType 帖子类型
     * @param entityId 帖子id
     * @return
     */
    public int findCommentCount(int entityType, int entityId) {
        return commentMapper.selectCountByEntity(entityType, entityId);
    }
}

controller层,在DiscussPostController中修改getDiscussPost方法,代码如下:

	/**
     * 查看帖子详情
     * @param discussPostId
     * @param model
     * @return
     */
    @GetMapping( "/detail/{discussPostId}")
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
        // 查询帖子
        DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
        model.addAttribute("post", post);
        // 查找作者
        User user = userService.findUserById(post.getUserId());
        model.addAttribute("user", user);
        // 评论分页信息
        page.setLimit(5);
        page.setPath("/discuss/detail/" + discussPostId);
        page.setRows(post.getCommentCount());
        // 评论: 给帖子的评论
        // 回复: 给评论的评论
        // 评论列表
        List<Comment> commentList = commentService.findCommentsByEntity(
                ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
        // 评论VO列表
        List<Map<String, Object>> commentVoList = new ArrayList<>();
        if (commentList != null) {
            for (Comment comment : commentList) {
                // 评论VO
                Map<String, Object> commentVo = new HashMap<>();
                // 评论
                commentVo.put("comment", comment);
                // 查询到评论的作者
                commentVo.put("user", userService.findUserById(comment.getUserId()));
                // 回复列表
                List<Comment> replyList = commentService.findCommentsByEntity(
                        ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
                // 回复VO列表
                List<Map<String, Object>> replyVoList = new ArrayList<>();
                if (replyList != null) {
                    for (Comment reply : replyList) {
                        Map<String, Object> replyVo = new HashMap<>();
                        // 回复
                        replyVo.put("reply", reply);
                        // 作者
                        replyVo.put("user", userService.findUserById(reply.getUserId()));
                        // 回复目标
                        User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
                        replyVo.put("target", target);
                        replyVoList.add(replyVo);
                    }
                }
                commentVo.put("replys", replyVoList);
                // 回复数量
                int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("replyCount", replyCount);
                commentVoList.add(commentVo);
            }
        }
        model.addAttribute("comments", commentVoList);
        return "/site/discuss-detail";
    }

前端需要对应修改index.html和 discuss-detail.html内容,详情见源代码。

功能测试

启动项目,登录用户,查看帖子详情;
在这里插入图片描述
在这里插入图片描述

添加评论

功能分析

新增帖子数据;
增加帖子数量;

功能实现

dao层,在CommentMapper新增insertComment接口,代码如下:

    /**
     * 新增帖子
     * @param comment
     * @return
     */
    int insertComment(Comment comment);

在DiscussPostMapper新增updateCommentCount接口,代码如下:

   /**
     * 更新帖子数量
     * @param id
     * @param commentCount
     * @return
     */
    int updateCommentCount(int id, int commentCount);

对应的CommentMapper.xml文件新增代码如下:

  <sql id="insertFields">
        user_id, entity_type, entity_id, target_id, content, status, create_time
    </sql>
    <insert id="insertComment" parameterType="Comment">
        insert into comment(<include refid="insertFields"></include>)
        values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
    </insert>

对应的DiscussPostMapper.xml新增代码如下:

  <update id="updateCommentCount">
        update discuss_post set comment_count = #{commentCount} where id = #{id}
    </update>

service层,在DiscussPostService类中添加updateCommentCount方法,代码如下:

	/**
     * 更新评论数量
     * @param id
     * @param commentCount
     * @return
     */
    public int updateCommentCount(int id, int commentCount) {
        return discussPostMapper.updateCommentCount(id, commentCount);
    }

在 CommentService 类中添加 addComment 方法,此方法用到了事务注解,涉及到两张表的操作,代码如下:

    @Autowired
    private SensitiveFilter sensitiveFilter;
    @Autowired
    private DiscussPostService discussPostService;
     /**
     * 新增帖子
     * @param comment
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int addComment(Comment comment) {
        if (comment == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        // 添加评论
        comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
        comment.setContent(sensitiveFilter.filter(comment.getContent()));
        int rows = commentMapper.insertComment(comment);
        // 更新帖子评论数量
        if (comment.getEntityType() == ENTITY_TYPE_POST) {
            int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
            discussPostService.updateCommentCount(comment.getEntityId(), count);
        }
        return  rows;
    }

controller层,新建CommentController类,代码如下:

@Controller
@RequestMapping("/comment")
public class CommentController {
    @Autowired
    private CommentService commentService;
    @Autowired
    private HostHolder hostHolder;
    /**
     * 新增评论
     * @param discussPostId
     * @param comment
     * @return
     */
    @PostMapping( "/add/{discussPostId}")
    public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
        comment.setUserId(hostHolder.getUser().getId());
        comment.setStatus(0);
        comment.setCreateTime(new Date());
        commentService.addComment(comment);
        return "redirect:/discuss/detail/" + discussPostId;
    }
}

前端需要对应修改index.html和 discuss-detail.html内容,详情见源代码。

功能测试

启动项目,登录用户,评论帖子;
在这里插入图片描述
在这里插入图片描述

私信列表

功能分析

查询用户的私信列表;
分页显示私信;

功能实现

创建 Message 类,与数据库 message 表中的字段互相对应,代码如下:

@Data
public class Message {
    /**私信id*/
    private int id;
    /**发送方id*/
    private int fromId;
    /**接收方id*/
    private int toId;
    /**会话id*/
    private String conversationId;
    /**会话内容*/
    private String content;
    /**私信状态 0:未读 1:已读 2:删除*/
    private int status;
    /**私信创建时间*/
    private Date createTime;
}

dao层,新建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);
}

在mapper包下新建MessageMapper.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.ahtoh.community.dao.MessageMapper">

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

    <select id="selectConversations" resultType="com.ahtoh.community.entity.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="com.ahtoh.community.entity.Message">
        select <include refid="selectFields"></include>
        from message
        where status != 2
        and from_id != 1
        and conversation_id = #{conversationId}
        order by id asc
        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>
</mapper>

service层,新建MessageService服务类,代码如下:

@Service
public class MessageService {
    @Autowired
    private MessageMapper messageMapper;
    /**
     * 根据用户id查询私信列表
     * @param userId
     * @param offset
     * @param limit
     * @return
     */
    public List<Message> findConversations(int userId, int offset, int limit) {
        return messageMapper.selectConversations(userId, offset, limit);
    }
    /**
     * 根据用户id查询消息数量
     * @param userId
     * @return
     */
    public int findConversationCount(int userId) {
        return messageMapper.selectConversationCount(userId);
    }
    /**
     * 根据会话id查询消息列表
     * @param conversationId
     * @param offset
     * @param limit
     * @return
     */
    public List<Message> findLetters(String conversationId, int offset, int limit) {
        return messageMapper.selectLetters(conversationId, offset, limit);
    }
    /**
     * 根据会话id查询消息数量
     * @param conversationId
     * @return
     */
    public int findLetterCount(String conversationId) {
        return messageMapper.selectLetterCount(conversationId);
    }
    /**
     * 查询未读消息数量
     * @param userId
     * @param conversationId
     * @return
     */
    public int findLetterUnreadCount(int userId, String conversationId) {
        return messageMapper.selectLetterUnreadCount(userId, conversationId);
    }
}

controller层,新建MessageController控制类,代码如下:

@Controller
public class MessageController {
    @Autowired
    private MessageService messageService;
    @Autowired
    private HostHolder hostHolder;
    @Autowired
    private UserService userService;
    /**
     * 获取私信列表
     * @param model
     * @param page
     * @return
     */
    @GetMapping("/letter/list")
    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);
        return "/site/letter";
    }
    /**
     * 私信列表详情
     * @param conversationId
     * @param page
     * @param model
     * @return
     */
    @GetMapping(path = "/letter/detail/{conversationId}")
    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));
        return "/site/letter-detail";
    }
    /**
     * 获取私信目标对象
     * @param conversationId
     * @return
     */
    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);
        }
    }
}

前端需要对应修改index.html和 discuss-detail.html内容,详情见源代码。

功能测试

启动项目,登录用户,查看私信;
在这里插入图片描述

发送私信

功能分析

新增私信内容,更新私信数量;
异步方式发送私信,成功后自动刷新私信列表,显示到前端;

功能实现

dao层,在MessageMapper中新增isnertMessage 方法和 updateStatus 方法,代码如下:

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

对应的MessageMapper.xml中新增代码如下:

	<sql id="insertFields">
        from_id, to_id, conversation_id, content, status, create_time
    </sql>
  <insert id="insertMessage" parameterType="com.ahtoh.community.entity.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>

service层,在 UserService 类中添加 findUserByName 方法;在 MessageService 类中添加 addMessage 和 readMessage 方法;代码如下:

	/**
     * 根据姓名查询用户
     * @param username
     * @return
     */
    public User findUserByName(String username){
        return userMapper.selectByName(username);
    }
	@Autowired
    private SensitiveFilter sensitiveFilter;
   /**
     * 新增私信
     * @param message
     * @return
     */
    public int addMessage(Message message) {
        message.setContent(HtmlUtils.htmlEscape(message.getContent()));
        message.setContent(sensitiveFilter.filter(message.getContent()));
        return messageMapper.insertMessage(message);
    }
    /**
     * 读取私信
     * @param ids
     * @return
     */
    public int readMessage(List<Integer> ids) {
        return  messageMapper.updateStatus(ids, 1);
    }

controller层,新增发送私信和设置私信为已读方法,代码如下:

/**
     * 私信列表详情
     * @param conversationId
     * @param page
     * @param model
     * @return
     */
    @GetMapping(path = "/letter/detail/{conversationId}")
    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";
    }

    /**
     * 获取未读私信的所有id
     * @param letterList
     * @return
     */
    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;
    }
    /**
     * 获取私信目标对象
     * @param conversationId
     * @return
     */
    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);
        }
    }

    /**
     * 发送私信
     * @param toName
     * @param content
     * @return
     */
    @PostMapping(path = "/letter/send")
    @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.getId()) {
            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);
    }

前端修改letter-detai.html 和letter.js文件,详情见源代码;

功能测试

启动项目,登录用户,发送私信;
查看私信;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

;