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

news2024/12/23 11:02:06

测试用户:用户名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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用剪映专业版剪辑视频

1.操作界面功能介绍 2.时间线的使用 拖动前端后端缩减时长&#xff0c;有多个素材可以拖动调节前后顺序拼接。 分割视频 删除

新零售SaaS架构:客户管理系统的应用架构设计

客户管理系统的应用架构设计 应用层定义了软件系统的应用功能&#xff0c;负责接收用户的请求&#xff0c;协调领域层能力来执行任务&#xff0c;并将结果返回给用户&#xff0c;功能模块包括&#xff1a; 客户管理&#xff1a;核心功能模块&#xff0c;负责收集和更新客户信息…

FPGA时钟资源详解(3)——全局时钟资源

FPGA时钟系列文章总览&#xff1a;FPGA原理与结构&#xff08;14&#xff09;——时钟资源https://ztzhang.blog.csdn.net/article/details/132307564 一、概述 全局时钟是 FPGA 中的一种专用互连网络&#xff0c;旨在将时钟信号分配到 FPGA 内各种资源的时钟输入处。这种设计…

探索数据库--------------mysql主从复制和读写分离

目录 前言 为什么要主从复制&#xff1f; 主从复制谁复制谁&#xff1f; 数据放在什么地方&#xff1f; 一、mysql支持的复制类型 1.1STATEMENT&#xff1a;基于语句的复制 1.2ROW&#xff1a;基于行的复制 1.3MIXED&#xff1a;混合类型的复制 二、主从复制的工作过程 三个重…

IMU评估产后腹直肌分离康复训练

腹直肌分离&#xff08;Diastasis Recti Abdominis&#xff0c;DRA&#xff09;是由腹部纤维联结组织——腹白线的过度伸张所致&#xff0c;这项研究的目标是通过惯性测量单元&#xff08;IMU&#xff09;感应器信号来分析产后康复。传统的康复方式通常包括针对性的物理疗法&am…

《QT实用小工具·一》电池电量组件

1、概述 项目源码放在文章末尾 本项目实现了一个电池电量控件&#xff0c;包含如下功能&#xff1a; 可设置电池电量&#xff0c;动态切换电池电量变化。可设置电池电量警戒值。可设置电池电量正常颜色和报警颜色。可设置边框渐变颜色。可设置电量变化时每次移动的步长。可设置…

CleanMyMac X 4.15.1 for Mac 最新中文破解版 系统优化垃圾清理工具

CleanMyMac X for Mac 是一款功能更加强大的系统优化清理工具&#xff0c;相比于 CleanMyMac 4.15.1来说&#xff0c;功能增加了不少&#xff0c;此版本为4.15.1官方最新中英文正式破解版本&#xff0c;永久使用&#xff0c;解决了打开软件崩溃问题&#xff0c;最新版4.15.1版本…

neo4j相同查询语句一次查询特慢再次查询比较快。

现象&#xff1a; neo4j相同查询语句一次查询特慢再次查询比较快。 分析&#xff1a; 查询语句 //查询同名方法match(path:Method) where id(path) in [244333030] and NOT path:Constructor//是rpc的方法match(rpc_method:Method)<-[:DECLARES]-(rpc_method_cls:Class) -…

2024年智能版控费系统方案卓健易控

2024年智能版控费系统方案卓健易控 详细可咨询&#xff1a;19138173009 设备智能卓健易控ZJ-V8.0控费方案在科学和技术不断发展的背景下&#xff0c;逐渐实现了更新和迭代。现如今&#xff0c;感应技术、生物识别技术、智能图像识别技术、过程记录技术、监管控制技术等方面的…

Pytorch的hook函数

hook函数是勾子函数&#xff0c;用于在不改变原始模型结构的情况下&#xff0c;注入一些新的代码用于调试和检验模型&#xff0c;常见的用法有保留非叶子结点的梯度数据&#xff08;Pytorch的非叶子节点的梯度数据在计算完毕之后就会被删除&#xff0c;访问的时候会显示为None&…

解析为什么使用celery的task装饰就有delay属性

分析task装饰器原理 from celery.task import periodic_task task源码如下 def task(*args, **kwargs):"""Deprecated decorator, please use :func:celery.task."""return current_app.task(*args, **dict({base: Task}, **kwargs))这里回调…

JavaSE day14笔记

第十四天课堂笔记 课上: 适当做笔记课下 : 总结 , 读代码 , 反复敲代码 , 做练习 数组★★★ 数组 : 存储多个 同一类型 的容器格式 :数组类型 : 引用数据类型, new运算符在堆中 分配一块连续的存储空间 , 系统会给数组元素默认初始化 , 将该数组的引用赋值给数组名 引用数据…

如何在Win10使用IIS服务搭建WebDAV网站并实现无公网IP访问内网文件内容

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav&#xff0c;并且结合cpolar的内网穿透工具实现在公网访…

腾讯云4核8G服务器价格,12M带宽一年646元,送3个月

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…

ArcGIS矢量裁剪矢量

一、利用相交工具 Arctoolbox工具一分析工具一叠加分析一相交

Flink-CDC 无法增量抽取SQLServer数据

1.问题 因部署在WindowsServer服务器SQLServer发生过期后重启&#xff0c;Flink-CDC同步进行作业重启&#xff0c;启动后无报错信息&#xff0c;数据正常抽取。但是观察几天后发现当天数据计算指标无法展示 2.定位 因为没用进行任何修改&#xff0c;故初步判断不是因Flink-C…

【Java面试题】Redis上篇(基础、持久化、底层数据结构)

文章目录 基础1.什么是Redis?2.Redis可以用来干什么&#xff1f;3.Redis的五种基本数据结构&#xff1f;4.Redis为什么这么快&#xff1f;5.什么是I/O多路复用&#xff1f;6.Redis6.0为什么使用了多线程&#xff1f; 持久化7.Redis的持久化方式&#xff1f;区别&#xff1f;8.…

vsqt更改ui后cpp文件报错 原理

操作&#xff1a;我在ui界面改了一下&#xff0c;然后选中cpp文件右键编译->重新扫描&#xff08;或者全部重新生成&#xff09; 现象&#xff1a;这里一直红着&#xff0c;在UI命名空间&#xff0c;找不到PresetEvent 这个类 private:Ui::PresetEvent ui;//Ui::preset_eve…

机器学习入门:概念、步骤、分类与实践

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【JavaWeb】Day24.Web入门——HTTP协议(一)

HTTP协议——概述 1.介绍 HTTP&#xff1a;Hyper Text Transfer Protocol(超文本传输协议)&#xff0c;规定了浏览器与服务器之间数据传输的规则。 http是互联网上应用最为广泛的一种网络协议http协议要求&#xff1a;浏览器在向服务器发送请求数据时&#xff0c;或是服务器在…