Day24:私信列表、私信详情、发送私信

测试用户:用户名aaa 密码aaa

  • 查询当前用户的会话列表;
  • 每个会话只显示一条最新的私信;
  • 支持分页显示。

首先看下表结构:

image

  • conversation_id: 用from_id和to_id拼接,小的放前面去(因为两个人的对话应该在一个会话中)

DAO层

  1. 添加messgge实体类:
 public class Message {
    private int id;
    private int fromId;
    private int toId;
    private String conversationId;
    private String content;
    private int status;
    private Date createTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getFromId() {
        return fromId;
    }

    public void setFromId(int fromId) {
        this.fromId = fromId;
    }

    public int getToId() {
        return toId;
    }

    public void setToId(int toId) {
        this.toId = toId;
    }

    public String getConversationId() {
        return conversationId;
    }

    public void setConversationId(String conversationId) {
        this.conversationId = conversationId;
    }

    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 "Message{" +
                "id=" + id +
                ", fromId=" + fromId +
                ", toId=" + toId +
                ", conversationId='" + conversationId + '\'' +
                ", content='" + content + '\'' +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}
  1. message-mapper接口
@Mapper
public interface MessageMapper {
    // 1. 查询当前用户的会话列表,针对每个会话只返回一条最新的私信(分页)
    List<Message> selectConversations(int userId, int offset, int limit);
  
    // 2. 查询当前用户的会话数量
    int selectConversationCount(int userId);

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

    // 5. 查询未读私信的数量
    int selectLetterUnreadCount(int userId, String conversationId);
}
  1. 编写message-mapper.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.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>
</mapper>

业务层

@Service
public class MessageService {
    @Autowired
    private MessageMapper messageMapper;

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

}

Controller层

显示私信列表

创建MessageController:

@Controller
public class MessageController {
    @Autowired
    private MessageService messageService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;

    // 1. 查询当前用户的会话列表,针对每个会话只返回一条最新的私信(分页)
    // /letter/list
    // GET
    @RequestMapping(path = "/letter/list", method = RequestMethod.GET)
    public String getLetterList(Model model, Page page) {
        User user = hostHolder.getUser();
        // 1. 设置分页信息
        page.setLimit(5);
        page.setPath("/letter/list");
        page.setRows(messageService.findConversationCount(user.getId()));

        // 2. 查询会话列表
        List<Message> conversationList = messageService.findConversations(
                hostHolder.getUser().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()));
                //下面的逻辑是:如果当前用户是消息的接收者,那么target就是发送者,反之就是当前用户是发送者,那么target就是接收者
                int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
                map.put("target", userService.findUserById(targetId));

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

        // 3. 查询未读消息数量,查询的是所有的未读消息数量
        int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
        model.addAttribute("letterUnreadCount", letterUnreadCount);

        return "/site/letter";
    }

    // 2. 查询当前用户的会话详情
    // /letter/detail/{conversationId}
    // GET
    public String getLetterDetail() {
        return "/site/letter-detail";
    }

    // 3. 发送私信
    // /letter/send
    // POST
    public String sendLetter() {
        return "redirect:/letter/list";
    }
}

显示私信详情

 @RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET)
    public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {
        // 1. 设置分页信息(之后需要分页的地方都是这个逻辑)
        page.setLimit(5);
        page.setPath("/letter/detail/" + conversationId);
        page.setRows(messageService.findLetterCount(conversationId));

        //2. 查询私信列表
        List<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());

        //3. 完善用户信息
        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";
}

修改模版成动态

显示私信列表

  • 首先修改index.html将“消息”链接到/letter/list
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}">
    <a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger">12</span></a>
</li>
  • 修改letter.html,替换头部到首页的头部
<!-- 头部 -->
		<header class="bg-dark sticky-top" th:replace="index::header">
  • 修改朋友私信的头显示未读消息数目
<li class="nav-item">
  <a class="nav-link position-relative active" th:href="@{/letter/list}">
      朋友私信<span class="badge badge-danger" th:text="${letterUnreadCount}" th:if="${letterUnreadCount!=0}">3</span></a>
</li>
  • 修改私信列表的迭代:
<!-- 私信列表 -->
  <ul class="list-unstyled">
      <li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:each="map:${conversations}">
          <span class="badge badge-danger" th:text="${map.unreadCount}" th:if="${map.unreadCount!=0}">3</span>
          <a href="profile.html">
              <img th:src="${map.target.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" >
          </a>
          <div class="media-body">
              <h6 class="mt-0 mb-3">
                  <span class="text-success" th:utext="${map.target.username}">落基山脉下的闲人</span>
                  <span class="float-right text-muted font-size-12" th:text="${#dates.format(map.conversation.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
              </h6>
              <div>
                  <a th:href="@{|/letter/detail/${map.conversation.conversationId}|}" th:utext="${map.conversation.content}">米粉车, 你来吧!</a>
                  <ul class="d-inline font-size-12 float-right">
                      <li class="d-inline ml-2"><a href="#" class="text-primary"><i th:text="${map.letterCount}">5</i>条会话</a></li>
                  </ul>
              </div>
          </div>
      </li>
  </ul>

${conversations}是一个在后端代码中设置的模型属性,它包含了一组会话数据。map是迭代过程中的变量,它在每次迭代时都会被设置为当前元素的值。

  • 复用之前的分页逻辑:
<!-- 分页 -->
      <nav class="mt-5" th:replace="index::pagination">

显示私信详情

  • letter.html链接到详情页面:
<div>
    <a th:href="@{|/letter/detail/${map.conversation.conversationId}|}" th:utext="${map.conversation.content}">米粉车, 你来吧!</a>
    <ul class="d-inline font-size-12 float-right">
        <li class="d-inline ml-2"><a href="#" class="text-primary"><i th:text="${map.letterCount}">5</i>条会话</a></li>
    </ul>
</div>
  • 修改letter-detail.html
<div class="col-8">
  <h6><b class="square"></b> 来自 <i class="text-success" th:utext="${target.username}">落基山脉下的闲人</i> 的私信</h6>
</div>
  • 设置th:each
<!-- 私信列表 -->
      <ul class="list-unstyled mt-4">
          <li class="media pb-3 pt-3 mb-2" th:each="map:${letters}">
  • 设置返回重定向回去:使用js
<button type="button" class="btn btn-secondary btn-sm" onclick="back();">返回</button>


  ...

<script>
	function back() {
		location.href = CONTEXT_PATH + "/letter/list";
	}
</script>

发送私信

数据访问层

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

// 修改消息的状态(多个未读变成已读)
int updateStatus(List<Integer> ids, int status);

修改mapper的xml:

<sql id="insertFields">
    from_id, to_id, conversation_id, content, status, create_time
</sql>
...
<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>
  • 语法:

它用于在SQL查询中插入一个动态的列表。在这个例子中,collection=“ids"表示输入参数中应该包含一个名为ids的集合,item=“id"表示在每次迭代时,集合中的当前元素将被赋值给变量id。 open=”(”,separator=",“和close=”)"这些属性用于定义生成的列表的格式。open和close定义了列表的开头和结尾,separator定义了列表中的元素之间的分隔符.

业务层

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

Controller层

异步请求,要用@ResponseBody返回json

@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());
        //设置会话id
        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);
        //给页面返回一个状态(0表示成功,1表示失败)
        return CommunityUtil.getJsonString(0);


    }

  • 这里的findUserByName还没有实现,要在UserService中实现一下:
public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }
  • 修改使消息可以已读:
...
        List<Integer> letterIds = getLetterIds(letterList);

        if(!letterIds.isEmpty()){
            messageService.readMessage(letterIds);
        }


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;
}

修改letter.html模版

  1. 修改letter.js接收json:
function send_letter() {
	$("#sendModal").modal("hide");

	var toName = $("#recipient-name").val();
	var content = $("#message-text").val();
	$.post(
	    CONTEXT_PATH + "/letter/send",
	    {"toName":toName,"content":content},
	    function(data) {
	        data = $.parseJSON(data);
	        if(data.code == 0) {
	            $("#hintBody").text("发送成功!");
	        } else {
	            $("#hintBody").text(data.msg);
	        }

	        $("#hintModal").modal("show");
            setTimeout(function(){
                $("#hintModal").modal("hide");
                location.reload();
            }, 2000);
	    }
	);
}
  1. 修改letter-detail.html
<div class="toast-header">
    <strong class="mr-auto" th:utext="${map.fromUser.username}">落基山脉下的闲人</strong>
    <small th:text="${#dates.format(map.letter.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-25 15:49:32</small>
    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
        <span aria-hidden="true">&times;</span>
    </button>
</div>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux:详解TCP报头类型

文章目录 温习序号的意义序号和确认序号报文的类型 TCP报头类型详解ACK: 确认号是否有效SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段FIN: 通知对方, 本端要关闭了PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走RST: 对方要求重新建立连接; 我们把携带RST标识的称…

【学习】软件企业何时会选择第三方软件测试机构

近年来&#xff0c;随着软件行业的迅猛发展&#xff0c;软件企业对软件测试的需求也越来越大。为了保证软件的质量和稳定性&#xff0c;许多企业选择寻找第三方软件测试机构来进行软件测试。第三方软件测试机构是独立于软开发企业的专业机构&#xff0c;主要从事软件测试和质量…

【SpringBoot从入门到精通】02_SpringBoot快速上手

二、SpringBoot快速上手 环境准备&#xff1a; Java8及以上 Maven3.5 https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/getting-started.html#getting-started SpringBoot 2.x 最新版 开发工具&#xff1a; IDEA 2022 2.1 开发第一个SpringBoot应用程序 …

什么是土壤墒情检测站?它在农业生产中有什么作用?

土壤墒情检测站是一种专门用于监测土壤水分状况和土壤水力性质的设备。它由多个传感器和数据采集单元组成&#xff0c;能够实时监测土壤中的水分含量、土壤温度等参数&#xff0c;并收集和记录相关的数据&#xff0c;提供土壤墒情&#xff08;即土壤水分状态&#xff09;的详细…

|行业洞察·趋势报告|《2024旅游度假市场简析报告-17页》

报告的主要内容解读&#xff1a; 居民收入提高推动旅游业发展&#xff1a;报告指出&#xff0c;随着人均GDP的提升&#xff0c;居民的消费能力增强&#xff0c;旅游需求从传统的观光游向休闲、度假游转变&#xff0c;国内人均旅游消费持续增加。 政府政策促进旅游市场复苏&…

代码随想录——移除元素(Leetcode27)

题目链接 暴力&#xff1a;&#xff08;没有改变元素相对位置&#xff09; class Solution {public int removeElement(int[] nums, int val) {int len nums.length;for(int i 0; i < len; i){if(nums[i] val){for(int j i 1; j < len; j){nums[j-1] nums[j];}i…

C#自定义最大化、最小化和关闭按钮

目录 1.资源文件 2.读取资源文件中的图片 3.WindowState属性 4. 示例 用户在制作应用程序时&#xff0c;为了使用户界面更加美观&#xff0c;一般都自己设计窗体的外观&#xff0c;以及窗体的最大化、最小化和关闭按钮。本例通过资源文件来存储窗体的外观&#xff0c;以及最…

【设计模式】中介者模式的应用

文章目录 1.概述2.中介者模式的适用场景2.1.用户界面事件2.2.分布式架构多模块通信 3.总结 1.概述 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;它用于解决对象间复杂、过度耦合的问题。当多个对象&#xff08;一般是两个以上的对象&…

腾讯云邮件推送功能有哪些?如何有效使用?

腾讯云邮件推送如何设置&#xff1f;怎么用邮件推送做高效营销&#xff1f; 腾讯云作为业界领先的云服务提供商&#xff0c;其邮件推送功能在便捷性、稳定性和安全性上都有着出色的表现。那么&#xff0c;腾讯云邮件推送功能究竟有哪些呢&#xff1f;让AokSend来探个究竟。 腾…

map与set容器常见操作详解(含示例代码及注意事项)

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

类与对象中C++

加油&#xff01;&#xff01;&#xff01; 文章目录 前言 一、类的6个默认成员函数 ​编辑 二、构造函数 1.概念 三、析构函数 1.概念 2.特性 四、拷贝构造函数 1.概念 2.特征 拷贝构造函数典型调用场景 五、赋值运算符重载 1.运算符重载 2.赋值运算符重载 赋值运算符重载格式…

【Qt】:坐标

坐标 一.常用快捷键二.使用帮助文档三.Qt坐标体系1.理论2.代码 一.常用快捷键 注释&#xff1a;ctrl / • 运⾏&#xff1a;ctrl R • 编译&#xff1a;ctrl B • 字体缩放&#xff1a;ctrl ⿏标滑轮 • 查找&#xff1a;ctrl F 比特就业课 • 整⾏移动&#xff1a;ctrl …

【Linux】体验一款开源的Linux服务器运维管理工具

今天为大家介绍一款开源的 Linux 服务器运维管理工具 - 1panel。 一、安装 根据官方那个提供的在线文档&#xff0c;这款工具的安装需要执行在线安装&#xff0c; # Redhat / CentOScurl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start…

.NET CORE使用Redis分布式锁续命(续期)问题

结合上一期 .NET CORE 分布式事务(三) DTM实现Saga及高并发下的解决方案(.NET CORE 分布式事务(三) DTM实现Saga及高并发下的解决方案-CSDN博客)。有的小伙伴私信说如果锁内锁定的程序或者资源未在上锁时间内执行完&#xff0c;造成的使用资源冲突&#xff0c;需要如何解决。本…

原创度检测工具分享,文章质量检测方便又简单

文章检测有利于我们了解文章内容的质量高低&#xff0c;而在以往我们检测文章只能依靠手动去检测&#xff0c;这是相当消耗工作时间的&#xff0c;但是在原创度检测工具出来之后&#xff0c;很多人开始检测文章质量就改用原创度检测工具了&#xff0c;因为使用原创度检测工具是…

ES学习日记(三)-------第三方插件选择

前言 在学习和使用Elasticsearch的过程中&#xff0c;必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求&#xff0c;未免太过麻烦&#xff0c;而且也不够人性化。 目前我了解的比较主流的插件就三个,head,cerebor和elasticHD 1.head 老牌插件,功能…

vant checkbox 复选框 样式改写

修改前 修改后 基于 vant&#xff1a; 4.8.3 unocss: 0.53.4 <van-checkbox-group v-model"query.zczb" shape"square" class"text-16 w-100% flex flex-wrap"><template v-for"item in registerCapitalOption"><v…

伪原创文章生成软件:自媒体文章写作好神器

自媒体的红利时代&#xff0c;许多人都纷纷参于其中&#xff0c;而文章写作是做自媒体的基本技能&#xff0c;但是随着技术的发展&#xff0c;如今&#xff0c;既使不会写作能力一样可以做起自媒体&#xff0c;方法就是利用伪原创文章生成软件来做内容的输出&#xff0c;其实伪…

PowerBI和Tableau之间该怎么选择?

最近经常看到朋友询问&#xff0c;最近想学习数据分析工具&#xff0c;但是PowerBI和Tableau之间不知道怎么选择? 其实可以从下面几个方面进行参考&#xff0c;Power BI和Tableau哪个更适合你&#xff1f; 共同点&#xff1a; Power BI和Tableau都是强大的数据分析和数据可…

Node爬虫:原理简介

在数字化时代&#xff0c;网络爬虫作为一种自动化收集和分析网络数据的技术&#xff0c;得到了广泛的应用。Node.js&#xff0c;以其异步I/O模型和事件驱动的特性&#xff0c;成为实现高效爬虫的理想选择。然而&#xff0c;爬虫在收集数据时&#xff0c;往往面临着诸如反爬虫机…