Browse Source

系统消息提示, 客户志愿者沟通

wangwl 3 months ago
parent
commit
d36c312ff7
25 changed files with 1467 additions and 7 deletions
  1. 8 0
      leromro-common/pom.xml
  2. 136 0
      leromro-core/src/main/java/com/leromro/core/controller/ConversationRecordController.java
  3. 82 0
      leromro-core/src/main/java/com/leromro/core/domain/ConversationMsgRecord.java
  4. 83 0
      leromro-core/src/main/java/com/leromro/core/domain/ConversationRecord.java
  5. 59 0
      leromro-core/src/main/java/com/leromro/core/domain/dto/ConversationMsgDTO.java
  6. 35 0
      leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationMsgRecordVO.java
  7. 17 0
      leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationRecordListVO.java
  8. 37 0
      leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationRecordVO.java
  9. 37 0
      leromro-core/src/main/java/com/leromro/core/domain/vo/SocketMsgVO.java
  10. 59 0
      leromro-core/src/main/java/com/leromro/core/domain/vo/UserMsgVO.java
  11. 19 0
      leromro-core/src/main/java/com/leromro/core/event/WebsocketEvent.java
  12. 24 0
      leromro-core/src/main/java/com/leromro/core/event/WebsocketEventListener.java
  13. 173 0
      leromro-core/src/main/java/com/leromro/core/facade/ConversationRecordFacade.java
  14. 11 0
      leromro-core/src/main/java/com/leromro/core/facade/OrdersFacade.java
  15. 19 0
      leromro-core/src/main/java/com/leromro/core/mapper/ConversationMsgRecordMapper.java
  16. 30 0
      leromro-core/src/main/java/com/leromro/core/mapper/ConversationRecordMapper.java
  17. 25 0
      leromro-core/src/main/java/com/leromro/core/service/IConversationMsgRecordService.java
  18. 35 0
      leromro-core/src/main/java/com/leromro/core/service/IConversationRecordService.java
  19. 69 0
      leromro-core/src/main/java/com/leromro/core/service/impl/ConversationMsgRecordServiceImpl.java
  20. 148 0
      leromro-core/src/main/java/com/leromro/core/service/impl/ConversationRecordServiceImpl.java
  21. 17 7
      leromro-core/src/main/java/com/leromro/core/service/impl/MainOrderServiceImpl.java
  22. 234 0
      leromro-core/src/main/java/com/leromro/core/socket/WebSocketService.java
  23. 43 0
      leromro-core/src/main/resources/mapper/core/ConversationMsgRecordMapper.xml
  24. 49 0
      leromro-core/src/main/resources/mapper/core/ConversationRecordMapper.xml
  25. 18 0
      leromro-framework/src/main/java/com/leromro/framework/config/WebSocketConfig.java

+ 8 - 0
leromro-common/pom.xml

@@ -189,6 +189,12 @@
             <artifactId>alipay-sdk-java</artifactId>
         </dependency>
 
+<!--        socket-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.fasterxml.jackson.datatype</groupId>
             <artifactId>jackson-datatype-jdk8</artifactId>
@@ -198,6 +204,8 @@
             <artifactId>spring-boot-starter-json</artifactId>
         </dependency>
 
+
+
     </dependencies>
 
 </project>

+ 136 - 0
leromro-core/src/main/java/com/leromro/core/controller/ConversationRecordController.java

@@ -0,0 +1,136 @@
+package com.leromro.core.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.leromro.common.utils.SecurityUtils;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import com.leromro.core.domain.vo.ConversationRecordListVO;
+import com.leromro.core.domain.vo.ConversationRecordVO;
+import com.leromro.core.facade.ConversationRecordFacade;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import com.leromro.common.core.domain.R;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import com.leromro.common.annotation.Log;
+import com.leromro.common.core.controller.BaseController;
+import com.leromro.common.core.domain.AjaxResult;
+import com.leromro.common.enums.BusinessType;
+import com.leromro.core.domain.ConversationRecord;
+import com.leromro.core.service.IConversationRecordService;
+import com.leromro.common.utils.poi.ExcelUtil;
+import com.leromro.common.core.page.TableDataInfo;
+
+/**
+ * 会话记录Controller
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+@RestController
+@Api(tags = "会话记录")
+@RequestMapping("/core/conversation")
+public class ConversationRecordController extends BaseController
+{
+    @Autowired
+    private IConversationRecordService conversationRecordService;
+
+    @Autowired
+    private ConversationRecordFacade conversationRecordFacade;
+
+    /**
+     * 查询会话记录列表
+     */
+    @ApiOperation(value = "查询会话记录列表", notes = "传入当前系统 1用户2志愿者3后台")
+    @PostMapping("/list")
+    public TableDataInfo<ConversationRecordListVO> list(@Validated @RequestBody ConversationMsgDTO dto)
+    {
+        startPage();
+        List<ConversationRecordListVO> list = conversationRecordService.selectConversationRecordList(dto);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 订单页面 点击沟通 获取会话记录详细信息
+     *  如果没有会话,会自动创建会话
+     *  返回值为会话信息和最新的10条聊天记录
+     */
+    @ApiOperation(value = "订单页面 点击沟通 获取会话记录详细信息", notes = "传入主订单id,mainOrderId和当前系统system")
+    @PostMapping(value = "/getOrderConversationInfo")
+    public R<ConversationRecordVO> getOrderConversationInfo(@Validated @RequestBody ConversationMsgDTO dto)
+    {
+        return conversationRecordFacade.getOrderConversationInfo(dto);
+    }
+
+    /**
+     * 消息列表页面 点击会话 获取会话记录详细信息
+     */
+    @ApiOperation("消息列表页面 点击会话 获取会话记录详细信息")
+    @GetMapping(value = "/getListConversationInfo")
+    public R<ConversationRecordVO> getListConversationInfo(@RequestParam("conversationRecordId") Long conversationRecordId)
+    {
+        return conversationRecordFacade.getListConversationInfo(conversationRecordId);
+    }
+
+    /**
+     * 获取历史聊天记录,目前设置为获取10条
+     */
+    @ApiOperation(value = "获取历史聊天记录,目前设置为获取10条",  notes = "需要会话id和消息记录id")
+    @PostMapping(value = "/getHistoryMsg")
+    public R<List<ConversationMsgRecordVO>> getHistoryMsg(@RequestBody ConversationMsgDTO dto)
+    {
+        return R.ok(conversationRecordFacade.getHistoryMsg(dto));
+    }
+
+    /**
+     * 发送聊天消息
+     */
+    @ApiOperation(value = "发送聊天消息", notes = "需要所有参数")
+    @PostMapping("/sendMsg")
+    public R<String> sendMsg(@Validated @RequestBody ConversationMsgDTO dto)
+    {
+        conversationRecordFacade.sendMsg(dto);
+        return R.ok();
+    }
+
+    /**
+     * 设置已读
+     */
+    @ApiOperation(value = "打开会话框时,设置已读",notes = "需要会话id和当前系统")
+    @PostMapping("/setRead")
+    public R setRead(@Validated @RequestBody ConversationMsgDTO dto){
+        conversationRecordFacade.setRead(dto);
+        return R.ok();
+    }
+
+    /**
+     * 删除会话记录
+     */
+    @ApiOperation("删除会话记录")
+    @Log(title = "会话记录", businessType = BusinessType.DELETE)
+	@PostMapping("/remove")
+    public R<Integer> remove(@Validated @RequestBody ConversationMsgDTO dto)
+    {
+        LambdaUpdateWrapper<ConversationRecord> wrapper = new LambdaUpdateWrapper<ConversationRecord>()
+                .eq(ConversationRecord::getConversationRecordId, dto.getConversationRecordId());
+        if (dto.getSystem() == 1){
+            //用户删除,设置用户已删除
+            wrapper.eq(ConversationRecord::getUserId, SecurityUtils.getUserId());
+            wrapper.set(ConversationRecord::getUserDelFlag, "2");
+        } else if (dto.getSystem() == 2) {
+            //志愿者删除,设置志愿者已删除
+            wrapper.eq(ConversationRecord::getVolunteerId, SecurityUtils.getUserId());
+            wrapper.set(ConversationRecord::getVolunteerDelFlag, "2");
+        } else {
+            return R.fail("目前未开通web端会话");
+        }
+        conversationRecordService.update(wrapper);
+        return R.ok();
+    }
+}

+ 82 - 0
leromro-core/src/main/java/com/leromro/core/domain/ConversationMsgRecord.java

@@ -0,0 +1,82 @@
+package com.leromro.core.domain;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import com.leromro.common.annotation.Excel;
+import com.leromro.common.core.domain.BaseEntity;
+
+/**
+ * 消息记录对象 l_conversation_msg_record
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("l_conversation_msg_record")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "ConversationMsgRecord", description = "消息记录")
+public class ConversationMsgRecord extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long conversationMsgRecordId;
+
+    @TableField("conversation_record_id")
+    @ApiModelProperty("会话id")
+    private Long conversationRecordId;
+
+    @TableField("sender_id")
+    @ApiModelProperty("发送者id,用户或者志愿者")
+    private Long senderId;
+
+    @TableField("user_id")
+    @ApiModelProperty("下单用户id")
+    private Long userId;
+
+    @TableField("volunteer_id")
+    @ApiModelProperty("志愿者id")
+    private Long volunteerId;
+
+    @TableField("msg_type")
+    @ApiModelProperty("消息类型 1文字消息 2图片消息")
+    private String msgType;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField("msg_send_time")
+    @ApiModelProperty("消息发送时间")
+    private LocalDateTime msgSendTime;
+
+    @TableField("msg_content")
+    @ApiModelProperty("消息内容")
+    private String msgContent;
+
+    @TableField("user_del_flag")
+    @ApiModelProperty("用户删除标志(0代表存在 2代表删除)")
+    private String userDelFlag;
+
+    @TableField("volunteer_del_flag")
+    @ApiModelProperty("志愿者删除标志(0代表存在 2代表删除)")
+    private String volunteerDelFlag;
+
+    @TableField("volunteer_read_flag")
+    @ApiModelProperty("志愿者读取标志(0代表未读 2代表已读)")
+    private String volunteerReadFlag;
+
+    @TableField("user_read_flag")
+    @ApiModelProperty("用户读取标志(0代表未读 2代表已读)")
+    private String userReadFlag;
+
+
+}

+ 83 - 0
leromro-core/src/main/java/com/leromro/core/domain/ConversationRecord.java

@@ -0,0 +1,83 @@
+package com.leromro.core.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import com.leromro.common.annotation.Excel;
+import com.leromro.common.core.domain.BaseEntity;
+
+import java.time.LocalDateTime;
+
+/**
+ * 会话记录对象 l_conversation_record
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("l_conversation_record")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "ConversationRecord", description = "会话记录")
+public class ConversationRecord extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long conversationRecordId;
+
+    @TableField("main_order_id")
+    @ApiModelProperty("订单id")
+    private Long mainOrderId;
+
+    @TableField("conversation_avatar")
+    @ApiModelProperty("会话图标")
+    private String conversationAvatar;
+
+    @TableField("user_id")
+    @ApiModelProperty("下单用户id")
+    private Long userId;
+
+    @TableField("user_name")
+    @ApiModelProperty("下单用户名称")
+    private String userName;
+
+    @TableField("volunteer_id")
+    @ApiModelProperty("志愿者id")
+    private Long volunteerId;
+
+    @TableField("volunteer_name")
+    @ApiModelProperty("志愿者名称")
+    private String volunteerName;
+
+    @TableField("conversation_type")
+    @ApiModelProperty("会话类型 1系统会话 2订单会话")
+    private String conversationType;
+
+    @TableField("user_del_flag")
+    @ApiModelProperty("用户删除标志(0代表存在 2代表删除)")
+    private String userDelFlag;
+
+    @TableField("volunteer_del_flag")
+    @ApiModelProperty("志愿者删除标志(0代表存在 2代表删除)")
+    private String volunteerDelFlag;
+
+    @TableField("msg_type")
+    @ApiModelProperty("消息类型 1文字消息 2图片消息")
+    private String msgType;
+
+    @TableField("newest_msg_content")
+    @ApiModelProperty("最新一条消息内容")
+    private String newestMsgContent;
+
+    @TableField("newest_msg_time")
+    @ApiModelProperty("最新一条消息时间")
+    private LocalDateTime newestMsgTime;
+
+}

+ 59 - 0
leromro-core/src/main/java/com/leromro/core/domain/dto/ConversationMsgDTO.java

@@ -0,0 +1,59 @@
+package com.leromro.core.domain.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.simpleframework.xml.core.Validate;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * 发送消息时后端接收参数
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ConversationMsgDTO {
+
+    @ApiModelProperty("会话id")
+    private Long conversationRecordId;
+
+    @ApiModelProperty("消息记录id")
+    private Long conversationMsgRecordId;
+
+    @ApiModelProperty("主订单id")
+    private Long mainOrderId;
+
+    /**
+     * 订单消息 前端传的代表对方接收,如传1用户系统,则代表希望志愿者接收到通知
+     * 系统消息 则代表为希望哪个系统的人接收,如传2,则代表希望志愿者接收到通知
+     */
+    @NotNull(message = "系统类型不能为空")
+    @ApiModelProperty(value = "当前系统 1用户2志愿者3后台", required = true)
+    private Integer system;
+
+    @ApiModelProperty("消息类型 1文字消息 2图片消息")
+    private String msgType;
+
+    @ApiModelProperty("下单用户id")
+    private Long userId;
+
+    @ApiModelProperty("志愿者id")
+    private Long volunteerId;
+
+    @ApiModelProperty("消息内容")
+    private String msgContent;
+
+    @ApiModelProperty("当前页")
+    private Integer pageNum;
+
+    @ApiModelProperty("每页大小")
+    private Integer pageSize;
+}

+ 35 - 0
leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationMsgRecordVO.java

@@ -0,0 +1,35 @@
+package com.leromro.core.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 沟通时,返回的聊天记录vo
+ */
+@Data
+public class ConversationMsgRecordVO {
+
+    @ApiModelProperty("会话id")
+    private Long conversationRecordId;
+
+    @ApiModelProperty("消息记录id")
+    private Long conversationMsgRecordId;
+
+    @ApiModelProperty("发送者id,用户或者志愿者")
+    private Long senderId;
+
+    @ApiModelProperty("消息类型 1文字消息 2图片消息")
+    private String msgType;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("消息发送时间")
+    private LocalDateTime msgSendTime;
+
+    @ApiModelProperty("消息内容")
+    private String msgContent;
+}

+ 17 - 0
leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationRecordListVO.java

@@ -0,0 +1,17 @@
+package com.leromro.core.domain.vo;
+
+import com.leromro.core.domain.ConversationRecord;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ConversationRecordListVO extends ConversationRecord {
+
+    @ApiModelProperty("会话标题")
+    private String conversationTitle;
+
+    @ApiModelProperty("未读消息数量")
+    private Integer msgUnreadCount;
+}

+ 37 - 0
leromro-core/src/main/java/com/leromro/core/domain/vo/ConversationRecordVO.java

@@ -0,0 +1,37 @@
+package com.leromro.core.domain.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 点击会话时显示的VO
+ */
+@Data
+public class ConversationRecordVO {
+
+    @ApiModelProperty("会话id")
+    private Long conversationRecordId;
+
+    @ApiModelProperty("会话标题")
+    private String conversationTitle;
+
+    @ApiModelProperty("会话图标")
+    private String conversationAvatar;
+
+    @ApiModelProperty("主订单id")
+    private Long mainOrderId;
+
+    @ApiModelProperty("下单用户id")
+    private Long userId;
+
+    @ApiModelProperty("志愿者id")
+    private Long volunteerId;
+
+    @ApiModelProperty("消息类型 1 订单消息")
+    private String msgType;
+
+    @ApiModelProperty("消息内容")
+    private List<ConversationMsgRecordVO> vos;
+}

+ 37 - 0
leromro-core/src/main/java/com/leromro/core/domain/vo/SocketMsgVO.java

@@ -0,0 +1,37 @@
+package com.leromro.core.domain.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Param   推送到前端的消息格式
+ * @Author wangwl
+ * @Date 2024/7/17 14:08
+ **/
+@Data
+@Builder
+public class SocketMsgVO<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 服务器响应给前端的消息类型:
+     *      msgUnreadCount:未读消息数量  data为未读消息数量
+     *      msgNew:有新消息 data为 "conversationRecordId"会话id ,和消息详细信息
+     */
+    @ApiModelProperty("消息类型")
+    private String type;
+    @ApiModelProperty("承载数据")
+    private T data;
+
+    public SocketMsgVO() {
+    }
+
+    public SocketMsgVO(String type, T data) {
+        this.type = type;
+        this.data = data;
+    }
+}

+ 59 - 0
leromro-core/src/main/java/com/leromro/core/domain/vo/UserMsgVO.java

@@ -0,0 +1,59 @@
+package com.leromro.core.domain.vo;
+
+import com.alipay.api.domain.UserInfoVO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @Param
+ * @Author wangwl
+ * @Date 2024/7/18 16:26
+ **/
+@Data
+@Builder
+public class UserMsgVO implements Serializable {
+
+    @ApiModelProperty("用户id")
+    private Long userId;
+
+    @ApiModelProperty("系统标识:1用户系统2志愿者系统")
+    private Integer system;
+
+    @ApiModelProperty("msg")
+    private String msg;
+
+    public UserMsgVO() {
+    }
+
+    public UserMsgVO(Long userId) {
+        this.userId = userId;
+    }
+
+    public UserMsgVO(Long userId, Integer system) {
+        this.userId = userId;
+        this.system = system;
+    }
+
+    public UserMsgVO(Long userId, Integer system, String msg) {
+        this.userId = userId;
+        this.system = system;
+        this.msg = msg;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        UserMsgVO that = (UserMsgVO) o;
+        return Objects.equals(userId, that.userId) && Objects.equals(msg, that.msg);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(userId, msg);
+    }
+}

+ 19 - 0
leromro-core/src/main/java/com/leromro/core/event/WebsocketEvent.java

@@ -0,0 +1,19 @@
+package com.leromro.core.event;
+
+
+import com.leromro.core.domain.vo.UserMsgVO;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+@Setter
+public class WebsocketEvent extends ApplicationEvent {
+ 
+    private UserMsgVO userMsgVO;
+
+    public WebsocketEvent(Object source) {
+        super(source);
+    }
+
+}

+ 24 - 0
leromro-core/src/main/java/com/leromro/core/event/WebsocketEventListener.java

@@ -0,0 +1,24 @@
+package com.leromro.core.event;
+
+import com.leromro.core.facade.ConversationRecordFacade;
+import com.leromro.core.service.IConversationMsgRecordService;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+/**
+ * 连接时,自动推送相关信息
+ */
+@Async
+@Component
+@RequiredArgsConstructor
+public class WebsocketEventListener implements ApplicationListener<WebsocketEvent> {
+
+    private final IConversationMsgRecordService conversationMsgRecordService;
+    @Override
+    public void onApplicationEvent(@NonNull WebsocketEvent event) {
+        conversationMsgRecordService.getUnreadMsgCountByUserId(event.getUserMsgVO().getSystem(),  event.getUserMsgVO().getUserId());
+    }
+}

+ 173 - 0
leromro-core/src/main/java/com/leromro/core/facade/ConversationRecordFacade.java

@@ -0,0 +1,173 @@
+package com.leromro.core.facade;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONConfig;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.leromro.common.core.domain.R;
+import com.leromro.common.core.domain.entity.SysUser;
+import com.leromro.common.utils.SecurityUtils;
+import com.leromro.core.domain.ConversationMsgRecord;
+import com.leromro.core.domain.ConversationRecord;
+import com.leromro.core.domain.MainOrders;
+import com.leromro.core.domain.VolunteerInfo;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import com.leromro.core.domain.vo.ConversationRecordVO;
+import com.leromro.core.domain.vo.SocketMsgVO;
+import com.leromro.core.service.IConversationMsgRecordService;
+import com.leromro.core.service.IConversationRecordService;
+import com.leromro.core.service.IMainOrderService;
+import com.leromro.core.service.IVolunteerInfoService;
+import com.leromro.core.socket.WebSocketService;
+import com.leromro.system.service.ISysUserService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ConversationRecordFacade {
+
+    private final ISysUserService userService;
+
+    private final IVolunteerInfoService volunteerInfoService;
+
+    private final IMainOrderService mainOrderService;
+
+    private final IConversationRecordService conversationRecordService;
+
+    private final IConversationMsgRecordService conversationMsgRecordService;
+
+    private final WebSocketService webSocketService;
+
+
+    /**
+     * 获取会话记录详细信息
+     *  如果没有会话,会自动创建会话
+     *  返回值为会话信息和最新的10条聊天记录
+     */
+    public R<ConversationRecordVO> getOrderConversationInfo(ConversationMsgDTO dto) {
+        Long mainOrderId = dto.getMainOrderId();
+        ConversationRecordVO vo = new ConversationRecordVO();
+        ConversationRecord conversationRecord = conversationRecordService.getOne(new LambdaQueryWrapper<ConversationRecord>()
+                .eq(ConversationRecord::getMainOrderId, mainOrderId));
+        if (ObjectUtil.isNull(conversationRecord)){
+            //不存在则创建会话,并保存
+            MainOrders mainOrders = mainOrderService.getById(mainOrderId);
+            if (ObjectUtil.isNull(mainOrders)){
+                return R.fail("订单不存在");
+            }
+            //获取用户和志愿者信息,目的为了获取名称和图标
+            SysUser user = userService.getById(mainOrders.getUserId());
+            VolunteerInfo volunteerInfo = volunteerInfoService.getById(mainOrders.getVolunteerInfoId());
+            conversationRecord = ConversationRecord.builder()
+                    .mainOrderId(mainOrderId)
+                    .conversationAvatar(volunteerInfo.getVolunteerPicture())
+                    .userId(mainOrders.getUserId())
+                    .userName(user.getNickName())
+                    .volunteerId(mainOrders.getVolunteerId())
+                    .volunteerName(volunteerInfo.getName())
+                    .conversationType("2")
+                    .build();
+            conversationRecordService.save(conversationRecord);
+        }
+        BeanUtil.copyProperties(conversationRecord,vo);
+        if (dto.getSystem().equals(1)){
+            vo.setConversationTitle(conversationRecord.getVolunteerName());
+        }else if (dto.getSystem().equals(2)){
+            vo.setConversationTitle(conversationRecord.getUserName());
+        }
+        vo.setVos(conversationMsgRecordService.getNewestMsg(vo.getConversationRecordId(),null));
+        return R.ok(vo);
+    }
+
+    public R<ConversationRecordVO> getListConversationInfo(Long conversationRecordId) {
+        ConversationRecordVO vo = new ConversationRecordVO();
+        ConversationRecord conversationRecord = conversationRecordService.getOne(new LambdaQueryWrapper<ConversationRecord>()
+                .eq(ConversationRecord::getConversationRecordId, conversationRecordId));
+        BeanUtil.copyProperties(conversationRecord,vo);
+        vo.setVos(conversationMsgRecordService.getNewestMsg(conversationRecordId,null));
+        return R.ok(vo);
+    }
+
+
+    /**
+     * 发送聊天消息
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void sendMsg(ConversationMsgDTO dto) {
+        Long userId = SecurityUtils.getUserId();
+        //保存消息
+        ConversationMsgRecord conversationMsgRecord = ConversationMsgRecord.builder()
+                .conversationRecordId(dto.getConversationRecordId())
+                .senderId(userId)
+                .userId(dto.getUserId())
+                .volunteerId(dto.getVolunteerId())
+                .msgType(dto.getMsgType())
+                //设置为当前时间
+                .msgSendTime(LocalDateTime.now())
+                .msgContent(dto.getMsgContent())
+                .build();
+        LambdaUpdateWrapper<ConversationRecord> wrapper = new LambdaUpdateWrapper<ConversationRecord>()
+                .eq(ConversationRecord::getConversationRecordId, dto.getConversationRecordId())
+                .set(ConversationRecord::getNewestMsgTime, conversationMsgRecord.getMsgSendTime())
+                .set(ConversationRecord::getNewestMsgContent, conversationMsgRecord.getMsgContent());
+        //接收人
+        Long receiver = null;
+        if (dto.getSystem() == 1){
+            //用户发送,设置用户已读
+            conversationMsgRecord.setUserReadFlag("2");
+            receiver = dto.getVolunteerId();
+            wrapper.set(ConversationRecord::getVolunteerDelFlag, "0");
+        } else if (dto.getSystem() == 2) {
+            //志愿者发送,设置志愿者已读
+            conversationMsgRecord.setVolunteerReadFlag("2");
+            receiver = dto.getUserId();
+            wrapper.set(ConversationRecord::getUserDelFlag, "0");
+        }
+        //保存消息
+        conversationMsgRecordService.save(conversationMsgRecord);
+        //修改接收方会话为显示,并修改最后一条消息数量,及其时间
+        this.conversationRecordService.update(wrapper);
+
+        //设置格式 发送消息
+        SocketMsgVO<ConversationMsgRecordVO> msgVO = SocketMsgVO.<ConversationMsgRecordVO>builder().type("msgNew").data(BeanUtil.copyProperties(conversationMsgRecord, ConversationMsgRecordVO.class)).build();
+        webSocketService.sendMessage(receiver,JSONUtil.toJsonStr(msgVO,new JSONConfig().setDateFormat("yyyy-MM-dd HH:mm:ss")));
+        //推送总数
+        conversationMsgRecordService.getUnreadMsgCountByUserId(dto.getSystem(), userId);
+    }
+
+    /**
+     * 设置已读
+     */
+    public void setRead(ConversationMsgDTO dto) {
+        LambdaUpdateWrapper<ConversationMsgRecord> wrapper = new LambdaUpdateWrapper<ConversationMsgRecord>()
+                .eq(ConversationMsgRecord::getConversationRecordId, dto.getConversationRecordId());
+        if (dto.getSystem() == 1){
+            //用户发送,设置用户已读
+            wrapper.eq(ConversationMsgRecord::getUserId, SecurityUtils.getUserId());
+            wrapper.set(ConversationMsgRecord::getUserReadFlag, "2");
+        } else if (dto.getSystem() == 2) {
+            //志愿者发送,设置志愿者已读
+            wrapper.eq(ConversationMsgRecord::getVolunteerId, SecurityUtils.getUserId());
+            wrapper.set(ConversationMsgRecord::getVolunteerReadFlag, "2");
+        }
+        conversationMsgRecordService.update(wrapper);
+        //推送总数
+        conversationMsgRecordService.getUnreadMsgCountByUserId(dto.getSystem(), SecurityUtils.getUserId());
+    }
+
+
+    public List<ConversationMsgRecordVO> getHistoryMsg(ConversationMsgDTO dto) {
+        return conversationMsgRecordService.getNewestMsg(dto.getConversationRecordId(),dto.getConversationMsgRecordId());
+    }
+}

+ 11 - 0
leromro-core/src/main/java/com/leromro/core/facade/OrdersFacade.java

@@ -15,6 +15,8 @@ import com.leromro.common.core.domain.R;
 import com.leromro.common.exception.ServiceException;
 import com.leromro.core.domain.MainOrders;
 import com.leromro.core.domain.SecondOrder;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.service.IConversationRecordService;
 import com.leromro.core.service.IMainOrderService;
 import com.leromro.core.service.IOrderRefundService;
 import com.leromro.core.service.ISecondOrderService;
@@ -55,6 +57,9 @@ public class OrdersFacade {
     @Autowired
     private RedissonClient redissonClient;
 
+    @Autowired
+    private IConversationRecordService conversationRecordService;
+
 
     /**
      * 支付超时或取消
@@ -102,6 +107,7 @@ public class OrdersFacade {
                 MainOrders mainOrders = mainOrderService.getOne(new QueryWrapper<MainOrders>().eq("out_trade_no", notifyResult.getOutTradeNo()));
                 if (ObjectUtil.isNull(mainOrders)){
                     log.error("微信支付回调结果异常1,异常原因:订单号无法找到对应订单{}",notifyResult.getOutTradeNo() );
+                    return WxPayNotifyResponse.fail("code:9999微信支付回调结果异常,异常原因:订单号无法找到对应订单");
                 }
                 //判断支付结果
                 if ("SUCCESS".equals(notifyResult.getResultCode())){
@@ -117,6 +123,11 @@ public class OrdersFacade {
                             .set(SecondOrder::getOrderStatus, 1)
                             .eq(SecondOrder::getMainOrderId, mainOrders.getMainOrderId()));
                     //向志愿者推送消息,并且插入未读消息
+                    conversationRecordService.sendSystemMsg(ConversationMsgDTO.builder()
+                            .system(2).userId(mainOrders.getVolunteerId()).volunteerId(mainOrders.getVolunteerId())
+                            .msgContent("您有新的订单,请及时查看")
+                            .build());
+
                 }else if ("FAIL".equals(notifyResult.getResultCode())){
                     //调用支付取消接口
                     this.PayCancel(mainOrders);

+ 19 - 0
leromro-core/src/main/java/com/leromro/core/mapper/ConversationMsgRecordMapper.java

@@ -0,0 +1,19 @@
+package com.leromro.core.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.leromro.core.domain.ConversationMsgRecord;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 消息记录Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+public interface ConversationMsgRecordMapper extends BaseMapper<ConversationMsgRecord>
+{
+
+    List<ConversationMsgRecordVO> getNewestMsg(@Param("conversationRecordId")Long conversationRecordId,@Param("conversationMsgRecordId") Long conversationMsgRecordId);
+}

+ 30 - 0
leromro-core/src/main/java/com/leromro/core/mapper/ConversationRecordMapper.java

@@ -0,0 +1,30 @@
+package com.leromro.core.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.leromro.core.domain.ConversationRecord;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.domain.vo.ConversationRecordListVO;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 会话记录Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+public interface ConversationRecordMapper extends BaseMapper<ConversationRecord>
+{
+
+
+    /**
+     * 查询会话记录列表
+     * 
+     * @param dto 会话记录
+     * @return 会话记录集合
+     */
+    public List<ConversationRecordListVO> selectConversationRecordList(@Param("dto") ConversationMsgDTO dto);
+
+
+
+}

+ 25 - 0
leromro-core/src/main/java/com/leromro/core/service/IConversationMsgRecordService.java

@@ -0,0 +1,25 @@
+package com.leromro.core.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.leromro.core.domain.ConversationMsgRecord;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 消息记录Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+public interface IConversationMsgRecordService extends IService<ConversationMsgRecord>
+{
+
+    //初始获取最新20条聊天记录
+    List<ConversationMsgRecordVO> getNewestMsg(Long conversationRecordId, Long conversationMsgRecordId);
+
+    //获取当前系统用户所有未读消息
+    public void getUnreadMsgCountByUserId(Integer system, Long userId);
+
+
+}

+ 35 - 0
leromro-core/src/main/java/com/leromro/core/service/IConversationRecordService.java

@@ -0,0 +1,35 @@
+package com.leromro.core.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.leromro.core.domain.ConversationRecord;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.domain.vo.ConversationRecordListVO;
+
+/**
+ * 会话记录Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+public interface IConversationRecordService extends IService<ConversationRecord>
+{
+
+
+    /**
+     * 查询会话记录列表
+     * 
+     * @param dto 会话记录
+     * @return 会话记录集合
+     */
+    public List<ConversationRecordListVO> selectConversationRecordList(ConversationMsgDTO dto);
+
+    /**
+     * 发送系统消息,如果没创建过会话则会自动创建,
+     *  这里的系统类型则代表为希望哪个系统的人接收,如传2,则代表希望志愿者接收到通知
+     */
+    public void sendSystemMsg(ConversationMsgDTO dto);
+
+
+
+}

+ 69 - 0
leromro-core/src/main/java/com/leromro/core/service/impl/ConversationMsgRecordServiceImpl.java

@@ -0,0 +1,69 @@
+package com.leromro.core.service.impl;
+
+import java.util.List;
+
+import cn.hutool.json.JSONConfig;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import com.leromro.core.domain.vo.SocketMsgVO;
+import com.leromro.core.mapper.ConversationMsgRecordMapper;
+import com.leromro.common.utils.DateUtils;
+import com.leromro.core.socket.WebSocketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import com.leromro.core.mapper.ConversationMsgRecordMapper;
+import com.leromro.core.domain.ConversationMsgRecord;
+import com.leromro.core.service.IConversationMsgRecordService;
+
+/**
+ * 消息记录Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+@Service
+public class ConversationMsgRecordServiceImpl extends ServiceImpl<ConversationMsgRecordMapper, ConversationMsgRecord> implements IConversationMsgRecordService
+{
+    @Autowired
+    private ConversationMsgRecordMapper conversationMsgRecordMapper;
+
+    @Autowired
+    private WebSocketService webSocketService;
+
+
+    @Override
+    public List<ConversationMsgRecordVO> getNewestMsg(Long conversationRecordId,Long conversationMsgRecordId) {
+        List<ConversationMsgRecordVO> vos = baseMapper.getNewestMsg(conversationRecordId,  conversationMsgRecordId);
+        //集合按照msg_send_time从小到达排序,msg_send_time为LocalDateTime类型
+//        vos.sort((o1, o2) -> o1.getMsgSendTime().compareTo(o2.getMsgSendTime()));
+        return vos;
+    }
+
+    /**
+     * 获取当前系统用户所有未读消息
+     * @param system
+     * @param userId
+     */
+    @Async("threadPoolTaskExecutor")
+    @Override
+    public void getUnreadMsgCountByUserId(Integer system, Long userId) {
+        LambdaQueryWrapper<ConversationMsgRecord> wrapper = new LambdaQueryWrapper<ConversationMsgRecord>();
+        if (system == 1){
+            wrapper.eq(ConversationMsgRecord::getUserId, userId).
+                    eq(ConversationMsgRecord::getUserReadFlag, "0");
+        } else if (system == 2) {
+            wrapper.eq(ConversationMsgRecord::getVolunteerId, userId).
+                    eq(ConversationMsgRecord::getVolunteerReadFlag, "0");
+        }else {
+            return;
+        }
+        //获取未读消息
+        Integer count = this.count(wrapper);
+        //设置格式 发送消息
+        SocketMsgVO<Integer> msgVO = SocketMsgVO.<Integer>builder().type("msgUnreadCount").data(count).build();
+        webSocketService.sendMessage(userId, JSONUtil.toJsonStr(msgVO,new JSONConfig().setDateFormat("yyyy-MM-dd HH:mm:ss")));
+    }
+}

+ 148 - 0
leromro-core/src/main/java/com/leromro/core/service/impl/ConversationRecordServiceImpl.java

@@ -0,0 +1,148 @@
+package com.leromro.core.service.impl;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONConfig;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.leromro.common.core.domain.R;
+import com.leromro.common.core.domain.entity.SysUser;
+import com.leromro.common.utils.SecurityUtils;
+import com.leromro.core.domain.ConversationMsgRecord;
+import com.leromro.core.domain.MainOrders;
+import com.leromro.core.domain.VolunteerInfo;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
+import com.leromro.core.domain.vo.ConversationMsgRecordVO;
+import com.leromro.core.domain.vo.ConversationRecordListVO;
+import com.leromro.core.domain.vo.SocketMsgVO;
+import com.leromro.core.mapper.ConversationRecordMapper;
+import com.leromro.common.utils.DateUtils;
+import com.leromro.core.service.IConversationMsgRecordService;
+import com.leromro.core.socket.WebSocketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import com.leromro.core.mapper.ConversationRecordMapper;
+import com.leromro.core.domain.ConversationRecord;
+import com.leromro.core.service.IConversationRecordService;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 会话记录Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-05-18
+ */
+@Service
+public class ConversationRecordServiceImpl extends ServiceImpl<ConversationRecordMapper, ConversationRecord> implements IConversationRecordService
+{
+    @Autowired
+    private ConversationRecordMapper conversationRecordMapper;
+
+    @Autowired
+    private IConversationMsgRecordService  conversationMsgRecordService;
+
+    @Autowired
+    private WebSocketService webSocketService;
+
+    /**
+     * 查询会话记录列表
+     * 
+     * @param dto 会话记录
+     * @return 会话记录
+     */
+    @Override
+    public List<ConversationRecordListVO> selectConversationRecordList(ConversationMsgDTO dto)
+    {
+        //临时把当前用户id,存放到下单用户id的位置进行查询
+        if (dto.getSystem().equals(1)){
+            dto.setUserId(SecurityUtils.getUserId());
+        }else if (dto.getSystem().equals(2)){
+            dto.setVolunteerId(SecurityUtils.getUserId());
+        }else {
+            return null;
+        }
+        //查询会话记录列表
+        List<ConversationRecordListVO> vos = conversationRecordMapper.selectConversationRecordList(dto);
+        //如果是用户系统,则显示志愿者名称,如果是志愿者系统则显示用户名称
+        for (ConversationRecordListVO vo : vos) {
+            if (dto.getSystem().equals(1)){
+                vo.setConversationTitle(vo.getVolunteerName());
+            }else if (dto.getSystem().equals(2)){
+                vo.setConversationTitle(vo.getUserName());
+            }
+        }
+        return vos;
+    }
+
+    @Async("threadPoolTaskExecutor")
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void sendSystemMsg(ConversationMsgDTO dto) {
+        Integer system = dto.getSystem();
+        //消息记录基础信息
+        ConversationMsgRecord conversationMsgRecord = ConversationMsgRecord.builder()
+                .msgType("1")
+                .msgContent(dto.getMsgContent())
+                .msgSendTime(LocalDateTime.now())
+                .build();
+        //会话的修改条件
+        LambdaUpdateWrapper<ConversationRecord> updateWrapper =  new LambdaUpdateWrapper<ConversationRecord>()
+                .set(ConversationRecord::getNewestMsgTime, conversationMsgRecord.getMsgSendTime())
+                .set(ConversationRecord::getNewestMsgContent, conversationMsgRecord.getMsgContent());
+        //接收人
+        Long receiver = null;
+        //查询指定系统用户,是否存在系统消息会话
+        LambdaQueryWrapper<ConversationRecord> wrapper = new LambdaQueryWrapper<ConversationRecord>();
+        if (system == 1){
+            receiver = dto.getUserId();
+            wrapper.eq(ConversationRecord::getUserId, dto.getUserId()).
+                    eq(ConversationRecord::getConversationType, "1");
+            conversationMsgRecord.setUserId(dto.getUserId());
+            updateWrapper.set(ConversationRecord::getUserDelFlag, "0");
+        } else if (system == 2) {
+            receiver = dto.getVolunteerId();
+            wrapper.eq(ConversationRecord::getVolunteerId, dto.getVolunteerId()).
+                    eq(ConversationRecord::getConversationType, "1");
+            conversationMsgRecord.setVolunteerId(dto.getVolunteerId());
+            updateWrapper.set(ConversationRecord::getVolunteerDelFlag, "0");
+        }else {
+            return;
+        }
+        ConversationRecord conversationRecord = conversationRecordMapper.selectOne(wrapper);
+        //存在则创建
+        if (ObjectUtil.isNull(conversationRecord)){
+            //不存在则创建会话,并保存
+            conversationRecord = ConversationRecord.builder()
+                    .conversationType("1")
+                    .newestMsgTime(conversationMsgRecord.getMsgSendTime())
+                    .newestMsgContent(conversationMsgRecord.getMsgContent())
+                    .build();
+            if (system == 1){
+                conversationRecord.setUserId(dto.getUserId());
+            } else {
+                conversationRecord.setVolunteerId(dto.getVolunteerId());
+            }
+            this.save(conversationRecord);
+        }else {
+            //存在则修改最新的消息时间和内容
+            updateWrapper.eq(ConversationRecord::getConversationRecordId, conversationRecord.getConversationRecordId());
+            this.update(updateWrapper);
+        }
+        //把系统消息插入消息记录表
+        conversationMsgRecord.setConversationRecordId(conversationRecord.getConversationRecordId());
+        conversationMsgRecordService.save(conversationMsgRecord);
+        //设置格式 发送消息
+        SocketMsgVO<ConversationMsgRecordVO> msgVO = SocketMsgVO.<ConversationMsgRecordVO>builder().type("msgNew").data(BeanUtil.copyProperties(conversationMsgRecord, ConversationMsgRecordVO.class)).build();
+        webSocketService.sendMessage(receiver, JSONUtil.toJsonStr(msgVO,new JSONConfig().setDateFormat("yyyy-MM-dd HH:mm:ss")));
+        //推送总数
+        conversationMsgRecordService.getUnreadMsgCountByUserId(dto.getSystem(), receiver);
+    }
+
+
+}

+ 17 - 7
leromro-core/src/main/java/com/leromro/core/service/impl/MainOrderServiceImpl.java

@@ -15,10 +15,12 @@ import com.leromro.common.exception.ServiceException;
 import com.leromro.common.utils.GeoUtils;
 import com.leromro.common.utils.SecurityUtils;
 import com.leromro.core.domain.*;
+import com.leromro.core.domain.dto.ConversationMsgDTO;
 import com.leromro.core.domain.dto.OrderRequestDTO;
 import com.leromro.core.domain.dto.WebMainOrdersDTO;
 import com.leromro.core.domain.vo.*;
 import com.leromro.core.mapper.*;
+import com.leromro.core.service.IConversationRecordService;
 import com.leromro.core.service.IMainOrderService;
 import com.leromro.core.service.IOrderSettlementApplicationService;
 import com.leromro.core.utils.BigDecimalUtil;
@@ -78,6 +80,9 @@ public class MainOrderServiceImpl extends ServiceImpl<MainOrdersMapper, MainOrde
     @Autowired
     private DeptRegionManagementMapper deptRegionManagementMapper;
 
+    @Autowired
+    private IConversationRecordService conversationRecordService;
+
     /**
      * @param userId
      * @return
@@ -342,11 +347,7 @@ public class MainOrderServiceImpl extends ServiceImpl<MainOrdersMapper, MainOrde
             volunteerWorkDates.add(workDate);
         }
 
-        //调用方法,向平台流水中添加数据
-        if(!orders.getOrderStatus().equals("0")){
-            // 0 是待支付,
-            changePlatformFinance(seconderIdList,orders);
-        }
+
 
 
         //新增志愿者订单小表N个  得修改这种
@@ -354,9 +355,18 @@ public class MainOrderServiceImpl extends ServiceImpl<MainOrdersMapper, MainOrde
         //新增志愿者预约时间表
         volunteerReservationTimeService.insertVolunteerReservationTimeList(reservationTimes);
 
-      //修改志愿者排班日期表
+        //修改志愿者排班日期表
         workDateMapper.updateVolunteerWorkDatebyVolunteerId(volunteerWorkDates);
-
+        //调用方法,向平台流水中添加数据
+        if(!orders.getOrderStatus().equals("0")){
+            // 0 是待支付,
+            changePlatformFinance(seconderIdList,orders);
+            //异步推送消息给志愿者
+            conversationRecordService.sendSystemMsg(ConversationMsgDTO.builder()
+                    .system(2).userId(orders.getVolunteerId()).volunteerId(orders.getVolunteerId())
+                    .msgContent("您有新的订单,请及时查看")
+                    .build());
+        }
         return R.ok(vo);
     }
 

+ 234 - 0
leromro-core/src/main/java/com/leromro/core/socket/WebSocketService.java

@@ -0,0 +1,234 @@
+package com.leromro.core.socket;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alipay.api.domain.UserInfoVO;
+import com.leromro.core.domain.vo.UserMsgVO;
+import com.leromro.core.event.WebsocketEvent;
+import lombok.Data;
+import lombok.NonNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Data
+@Component
+@ServerEndpoint(value = "/websocket/{system}/{userId}")
+public class WebSocketService implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+        WebSocketService.applicationContext = applicationContext;
+    }
+
+
+    /**日志*/
+    private static final Logger log = LoggerFactory.getLogger(WebSocketService.class);
+
+    /**静态变量,用来记录当前在线用户数。应该把它设计成线程安全的。*/
+    private static int onlineUserCount = 0;
+
+    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
+    private static int onlineLinkCount = 0;
+
+    /**用户锁的映射表*/
+    private static final ConcurrentHashMap<Long, ReentrantLock> locks = new ConcurrentHashMap<>();
+
+    /**concurrent包的线程安全Set,用来存放每个用户对应的session对象*/
+    public static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Session>> userSessionMap =  new ConcurrentHashMap<>();
+
+    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
+    private Session session;
+    /**用户id*/
+    private Long userId;
+
+
+    /**
+     * 连接建立成功调用的方法  system 1为用户端2为志愿者端
+     */
+    @OnOpen
+    public void onOpen(Session session,@PathParam("system") Integer system, @PathParam("userId") Long userId) {
+        ReentrantLock lock = locks.computeIfAbsent(userId, k -> new ReentrantLock());
+        lock.lock();
+        CopyOnWriteArrayList<Session> sessions = userSessionMap.computeIfAbsent(userId, k -> new CopyOnWriteArrayList<>());
+        if (sessions.isEmpty()){
+            addOnlineUserCount();
+        }
+        addOnlineLinkCount();
+        sessions.add(session);
+        this.session = session;
+        this.userId= userId;
+        log.info("----------------------建立连接-------------------------------");
+        log.info(StrUtil.format("用户{}连接成功,当前用户总连接数:{}", userId, sessions.size()));
+        log.info(StrUtil.format("当前在线人数为:{},当前总连接数:{}", getOnlineUserCount(),getOnlineLinkCount()));
+        UserMsgVO vo = UserMsgVO.builder().userId(userId).system(system).build();
+        try {
+            WebsocketEvent event = new WebsocketEvent("websocketService");
+            event.setUserMsgVO(vo);
+            applicationContext.publishEvent(event);
+        }catch (Exception e){
+            log.error("用户:"+userId+",网络异常:"+e.getMessage());
+        }finally {
+            lock.unlock();
+        }
+
+    }
+
+    /**
+     * 连接关闭调用的方法
+     */
+    @OnClose
+    public void onClose() {
+        log.info("----------------------关闭连接-------------------------------");
+        ReentrantLock lock = locks.computeIfAbsent(userId, k -> new ReentrantLock());
+        lock.lock();
+        try {
+            CopyOnWriteArrayList<Session> sessions = userSessionMap.get(userId);
+            if (CollUtil.isEmpty(sessions)) {
+                throw new RuntimeException("sessions中未找到当前连接");
+            }
+            sessions.remove(session);
+            subOnlineLinkCount();
+            if (CollUtil.isEmpty(sessions)){
+                userSessionMap.remove(userId);
+                subOnlineUserCount();
+                log.info(StrUtil.format("用户{}退出,当前在线人数为:{}", userId, getOnlineUserCount()));
+            }else {
+                log.info(StrUtil.format("用户{}断开连接,用户当前剩余连接:{},当前在线人数为:{}", userId, sessions.size(),getOnlineUserCount()));
+            }
+        }catch (Exception e){
+            log.info("用户"+userId + "断开连接时发生异常:"+e.getMessage());
+        }finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * 收到客户端消息后调用的方法
+     * @param message 客户端发送过来的消息*/
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        log.info("收到用户消息:"+userId+",报文:"+message);
+    }
+
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
+        error.printStackTrace();
+    }
+
+    /**
+     * 单个消息推送
+     */
+    public void sendMessage(Long userId, String message){
+        List<Session> sessions = userSessionMap.get(userId);
+        if(sessions != null && !sessions.isEmpty()){
+            /** 此处不能使用普通集合,否则在推送消息时,当前用户又开启一个页面,集合发生变化迭代器会报错ConcurrentModificationException*/
+            for (Session s : sessions) {
+                if (s == null){
+                    continue;
+                }
+                RemoteEndpoint.Basic basicRemote = s.getBasicRemote();
+                /** 推送消息时可能因为nginx配置的连接时间,或者用户刚好断开连接,
+                 * 导致通道关闭,但是数组中使用的是快照,还存在这个连接,此时跳过推送给这个窗口*/
+                if (basicRemote != null){
+                    try {
+                        basicRemote.sendText(message);
+                    } catch (IOException e) {
+                        log.error(userId+"推送消息失败,消息内容["+ message+"],原因:"+e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 单个消息推送
+     * @param vo
+     */
+    public void sendMessage(UserMsgVO vo){
+        List<Session> sessions = userSessionMap.get(vo.getUserId());
+        if(sessions != null && !sessions.isEmpty()){
+            /** 此处不能使用普通集合,否则在推送消息时,当前用户又开启一个页面,集合发生变化迭代器会报错ConcurrentModificationException*/
+            for (Session s : sessions) {
+                if (s == null){
+                    continue;
+                }
+                RemoteEndpoint.Basic basicRemote = s.getBasicRemote();
+                /** 推送消息时可能因为nginx配置的连接时间,或者用户刚好断开连接,
+                 * 导致通道关闭,但是数组中使用的是快照,还存在这个连接,此时跳过推送给这个窗口*/
+                if (basicRemote != null){
+                    try {
+                        basicRemote.sendText(vo.getMsg());
+                    } catch (IOException e) {
+                        log.error(vo.getUserId()+"推送消息失败,消息内容["+ vo.getMsg()+"],原因:"+e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 批量消息推送
+     */
+    public void sendMessage(List<UserMsgVO> vos){
+        for (UserMsgVO vo : vos) {
+            List<Session> sessions = userSessionMap.get(vo.getUserId());
+            if (sessions != null && !sessions.isEmpty()) {
+                for (Session s : sessions) {
+                    if (s == null) {
+                        continue;
+                    }
+                    RemoteEndpoint.Basic basicRemote = s.getBasicRemote();
+                    if (basicRemote != null) {
+                        try {
+                            basicRemote.sendText(vo.getMsg());
+                        } catch (IOException e) {
+                            log.error(vo.getUserId() + "推送消息失败,消息内容[" + vo.getMsg() + "],原因:" + e.getMessage());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public static synchronized void addOnlineUserCount() {
+        WebSocketService.onlineUserCount++;
+    }
+
+    public static synchronized void subOnlineUserCount() {
+        WebSocketService.onlineUserCount--;
+    }
+
+
+    public static synchronized void addOnlineLinkCount() {
+        WebSocketService.onlineLinkCount++;
+    }
+
+    public static synchronized void subOnlineLinkCount() {
+        WebSocketService.onlineLinkCount--;
+    }
+
+    public static synchronized int getOnlineUserCount() {
+        return onlineUserCount;
+    }
+
+    public static synchronized int getOnlineLinkCount() {
+        return onlineLinkCount;
+    }
+}

+ 43 - 0
leromro-core/src/main/resources/mapper/core/ConversationMsgRecordMapper.xml

@@ -0,0 +1,43 @@
+<?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.leromro.core.mapper.ConversationMsgRecordMapper">
+    
+    <resultMap type="ConversationMsgRecord" id="ConversationMsgRecordResult">
+        <result property="conversationMsgRecordId"    column="conversation_msg_record_id"    />
+        <result property="conversationRecordId"    column="conversation_record_id"    />
+        <result property="senderId"    column="sender_id"    />
+        <result property="mainOrderId"    column="main_order_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="volunteerId"    column="volunteer_id"    />
+        <result property="msgType"    column="msg_type"    />
+        <result property="msgSendTime"    column="msg_send_time"    />
+        <result property="msgContent"    column="msg_content"    />
+        <result property="userDelFlag"    column="user_del_flag"    />
+        <result property="volunteerDelFlag"    column="volunteer_del_flag"    />
+        <result property="volunteerReadFlag"    column="volunteer_read_flag"    />
+        <result property="userReadFlag"    column="user_read_flag"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectConversationMsgRecordVo">
+        select conversation_msg_record_id, conversation_record_id, sender_id, main_order_id, user_id, volunteer_id, msg_type, msg_send_time, msg_content, user_del_flag, volunteer_del_flag, volunteer_read_flag, user_read_flag, create_by, create_time, update_by, update_time, remark from l_conversation_msg_record
+    </sql>
+    <select id="getNewestMsg" resultType="com.leromro.core.domain.vo.ConversationMsgRecordVO">
+        select *
+        from l_conversation_msg_record
+        where conversation_record_id = #{conversationRecordId}
+        <if test="conversationMsgRecordId != null">
+            and conversation_msg_record_id &lt; #{conversationMsgRecordId}
+        </if>
+        order by msg_send_time desc
+        limit 10
+    </select>
+
+
+</mapper>

+ 49 - 0
leromro-core/src/main/resources/mapper/core/ConversationRecordMapper.xml

@@ -0,0 +1,49 @@
+<?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.leromro.core.mapper.ConversationRecordMapper">
+    
+    <resultMap type="ConversationRecord" id="ConversationRecordResult">
+        <result property="conversationRecordId"    column="conversation_record_id"    />
+        <result property="mainOrderId"    column="main_order_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="volunteerId"    column="volunteer_id"    />
+        <result property="msgType"    column="msg_type"    />
+        <result property="userDelFlag"    column="user_del_flag"    />
+        <result property="volunteerDelFlag"    column="volunteer_del_flag"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectConversationRecordVo">
+        select conversation_record_id, main_order_id, user_id, volunteer_id, msg_type, user_del_flag, volunteer_del_flag, create_by, create_time, update_by, update_time, remark from l_conversation_record
+    </sql>
+    <select id="selectConversationRecordList" resultType="com.leromro.core.domain.vo.ConversationRecordListVO">
+        select lcr.*,
+        COUNT(
+        CASE
+        WHEN #{dto.userId} IS NOT NULL AND lcmr.user_read_flag = '0' THEN lcmr.conversation_msg_record_id
+        WHEN #{dto.volunteerId} IS NOT NULL AND lcmr.volunteer_read_flag = '0' THEN lcmr.conversation_msg_record_id
+        ELSE NULL
+        END
+        ) AS msgUnreadCount
+        from l_conversation_record lcr left join l_conversation_msg_record lcmr on lcr.conversation_record_id = lcmr.conversation_record_id
+        where 1=1
+        <if test="dto.userId != null">
+            and lcr.user_id = #{dto.userId}
+            and lcr.user_del_flag = '0'
+        </if>
+        <if test="dto.volunteerId != null">
+            and lcr.volunteer_id = #{dto.volunteerId}
+            and lcr.volunteer_del_flag = '0'
+        </if>
+        group by lcr.conversation_record_id
+        order by lcr.newest_msg_time desc
+    </select>
+
+
+</mapper>

+ 18 - 0
leromro-framework/src/main/java/com/leromro/framework/config/WebSocketConfig.java

@@ -0,0 +1,18 @@
+package com.leromro.framework.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+}