合并bug修复

This commit is contained in:
xiao12feng8 2026-01-08 16:01:50 +08:00
parent fb1b493fc5
commit 11bd48dedc
9 changed files with 291 additions and 4 deletions

View File

@ -0,0 +1,68 @@
package com.zbkj.common.model.chat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("eb_private_message_burn")
@Entity
@Table(name = "eb_private_message_burn", indexes = {
@Index(name = "uk_message_id", columnList = "message_id", unique = true),
@Index(name = "idx_conversation_id", columnList = "conversation_id"),
@Index(name = "idx_burn_at", columnList = "burn_at")
})
@ApiModel(value = "PrivateMessageBurn对象", description = "阅后即焚消息状态")
public class PrivateMessageBurn implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID")
@TableId(value = "id", type = IdType.AUTO)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "消息ID")
@Column(name = "message_id", nullable = false)
private Long messageId;
@ApiModelProperty(value = "会话ID")
@Column(name = "conversation_id", nullable = false)
private Long conversationId;
@ApiModelProperty(value = "销毁秒数")
@Column(name = "burn_seconds")
private Integer burnSeconds;
@ApiModelProperty(value = "首次查看时间")
@Column(name = "viewed_at")
private Date viewedAt;
@ApiModelProperty(value = "销毁时间")
@Column(name = "burn_at")
private Date burnAt;
@ApiModelProperty(value = "是否已销毁")
@Column(name = "burned", columnDefinition = "TINYINT(1) DEFAULT 0")
private Boolean burned;
@ApiModelProperty(value = "创建时间")
@Column(name = "create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@Column(name = "update_time")
private Date updateTime;
}

View File

@ -28,6 +28,9 @@ public class SendMessageRequest implements Serializable {
@ApiModelProperty(value = "语音时长(可选)")
private Integer duration;
@ApiModelProperty(value = "阅后即焚秒数可选仅messageType=burn_image时使用")
private Integer burnSeconds;
// 兼容旧字段
public String getMessage() {
return content;

View File

@ -0,0 +1,32 @@
package com.zbkj.common.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "BurnViewResponse对象", description = "阅后即焚图片查看响应")
public class BurnViewResponse implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "消息ID")
private Long messageId;
@ApiModelProperty(value = "媒体URL可为空已销毁或无权限时不返回")
private String mediaUrl;
@ApiModelProperty(value = "销毁秒数")
private Integer burnSeconds;
@ApiModelProperty(value = "首次查看时间戳(毫秒)")
private Long viewedAt;
@ApiModelProperty(value = "销毁时间戳(毫秒)")
private Long burnAt;
@ApiModelProperty(value = "是否已销毁")
private Boolean burned;
}

View File

@ -57,6 +57,18 @@ public class ChatMessageResponse implements Serializable {
@ApiModelProperty(value = "语音时长(秒)")
private Integer duration;
@ApiModelProperty(value = "阅后即焚秒数仅messageType=burn_image时")
private Integer burnSeconds;
@ApiModelProperty(value = "首次查看时间戳毫秒仅messageType=burn_image时")
private Long viewedAt;
@ApiModelProperty(value = "销毁时间戳毫秒仅messageType=burn_image时")
private Long burnAt;
@ApiModelProperty(value = "是否已销毁仅messageType=burn_image时")
private Boolean burned;
@ApiModelProperty(value = "是否已读")
private Boolean isRead;

View File

@ -2,6 +2,7 @@ package com.zbkj.front.controller;
import com.zbkj.common.model.chat.Conversation;
import com.zbkj.common.request.SendMessageRequest;
import com.zbkj.common.response.BurnViewResponse;
import com.zbkj.common.response.ChatMessageResponse;
import com.zbkj.common.response.ConversationResponse;
import com.zbkj.common.result.CommonResult;
@ -157,4 +158,12 @@ public class ConversationController {
Integer userId = userService.getUserIdException();
return CommonResult.success(conversationService.recallMessage(id, userId));
}
@ApiOperation(value = "查看阅后即焚图片(触发计时)")
@ApiImplicitParam(name = "id", value = "消息ID", required = true)
@PostMapping("/messages/{id}/burn/view")
public CommonResult<BurnViewResponse> viewBurnImage(@PathVariable Long id) {
Integer userId = userService.getUserIdException();
return CommonResult.success(conversationService.viewBurnImage(id, userId));
}
}

View File

@ -0,0 +1,7 @@
package com.zbkj.service.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zbkj.common.model.chat.PrivateMessageBurn;
public interface PrivateMessageBurnDao extends BaseMapper<PrivateMessageBurn> {
}

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.zbkj.common.model.chat.Conversation;
import com.zbkj.common.model.chat.PrivateMessage;
import com.zbkj.common.request.SendMessageRequest;
import com.zbkj.common.response.BurnViewResponse;
import com.zbkj.common.response.ChatMessageResponse;
import com.zbkj.common.response.ConversationResponse;
@ -78,4 +79,6 @@ public interface ConversationService extends IService<Conversation> {
* 发送消息带详细参数
*/
PrivateMessage sendMessage(Long conversationId, Integer senderId, String messageType, String content, String mediaUrl);
BurnViewResponse viewBurnImage(Long messageId, Integer userId);
}

View File

@ -6,12 +6,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zbkj.common.exception.CrmebException;
import com.zbkj.common.model.chat.Conversation;
import com.zbkj.common.model.chat.PrivateMessage;
import com.zbkj.common.model.chat.PrivateMessageBurn;
import com.zbkj.common.model.user.User;
import com.zbkj.common.request.SendMessageRequest;
import com.zbkj.common.response.BurnViewResponse;
import com.zbkj.common.response.ChatMessageResponse;
import com.zbkj.common.response.ConversationResponse;
import com.zbkj.service.dao.ConversationDao;
import com.zbkj.service.dao.PrivateMessageDao;
import com.zbkj.service.dao.PrivateMessageBurnDao;
import com.zbkj.service.dao.UserBlacklistDao;
import com.zbkj.service.service.ConversationService;
import com.zbkj.service.service.OnlineStatusService;
@ -36,6 +39,9 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
@Autowired
private PrivateMessageDao privateMessageDao;
@Autowired
private PrivateMessageBurnDao privateMessageBurnDao;
@Autowired
private UserService userService;
@ -189,10 +195,26 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
// 检查黑名单状态
checkBlacklistStatus(userId, receiverId);
String messageType = request.getMessageType() != null ? request.getMessageType() : "text";
// 获取消息内容兼容新旧字段
String content = request.getContent() != null ? request.getContent() : request.getMessage();
if (content == null || content.trim().isEmpty()) {
throw new CrmebException("消息内容不能为空");
// 文本消息要求 content 非空媒体消息允许 content 为空
if ("text".equals(messageType)) {
if (content == null || content.trim().isEmpty()) {
throw new CrmebException("消息内容不能为空");
}
}
if ("burn_image".equals(messageType)) {
if (request.getMediaUrl() == null || request.getMediaUrl().trim().isEmpty()) {
throw new CrmebException("阅后图片mediaUrl不能为空");
}
Integer burnSeconds = request.getBurnSeconds();
if (burnSeconds == null || !(burnSeconds == 3 || burnSeconds == 5 || burnSeconds == 10)) {
throw new CrmebException("阅后即焚秒数仅支持3/5/10");
}
}
// 创建消息
@ -200,8 +222,8 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
message.setConversationId(conversationId);
message.setSenderId(userId);
message.setReceiverId(receiverId);
message.setContent(content);
message.setMessageType(request.getMessageType() != null ? request.getMessageType() : "text");
message.setContent(content != null ? content : "");
message.setMessageType(messageType);
message.setMediaUrl(request.getMediaUrl());
message.setDuration(request.getDuration());
message.setStatus("sent");
@ -209,6 +231,17 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
message.setReceiverDeleted(false);
message.setCreateTime(new Date());
privateMessageDao.insert(message);
if ("burn_image".equals(messageType)) {
PrivateMessageBurn burn = new PrivateMessageBurn();
burn.setMessageId(message.getId());
burn.setConversationId(conversationId);
burn.setBurnSeconds(request.getBurnSeconds());
burn.setBurned(false);
burn.setCreateTime(new Date());
burn.setUpdateTime(new Date());
privateMessageBurnDao.insert(burn);
}
// 更新会话的最后消息和时间
String lastMessagePreview = content;
@ -216,6 +249,8 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
lastMessagePreview = "[图片]";
} else if ("voice".equals(message.getMessageType())) {
lastMessagePreview = "[语音]";
} else if ("burn_image".equals(message.getMessageType())) {
lastMessagePreview = "[阅后图片]";
}
LambdaUpdateWrapper<Conversation> uw = new LambdaUpdateWrapper<>();
@ -236,6 +271,84 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
return convertMessageToResponse(message);
}
@Override
@Transactional(rollbackFor = Exception.class)
public BurnViewResponse viewBurnImage(Long messageId, Integer userId) {
PrivateMessage message = privateMessageDao.selectById(messageId);
if (message == null) {
throw new CrmebException("消息不存在");
}
if (!"burn_image".equals(message.getMessageType())) {
throw new CrmebException("消息类型不支持");
}
Conversation conversation = getById(message.getConversationId());
if (conversation == null ||
(!conversation.getUser1Id().equals(userId) && !conversation.getUser2Id().equals(userId))) {
throw new CrmebException("无权限查看此消息");
}
PrivateMessageBurn burn = privateMessageBurnDao.selectOne(
new LambdaQueryWrapper<PrivateMessageBurn>()
.eq(PrivateMessageBurn::getMessageId, messageId)
.last("LIMIT 1"));
if (burn == null) {
throw new CrmebException("阅后状态不存在");
}
Date now = new Date();
boolean burned = Boolean.TRUE.equals(burn.getBurned());
if (!burned && burn.getBurnAt() != null && burn.getBurnAt().getTime() <= now.getTime()) {
burn.setBurned(true);
burn.setUpdateTime(now);
privateMessageBurnDao.updateById(burn);
burned = true;
}
BurnViewResponse resp = new BurnViewResponse();
resp.setMessageId(messageId);
resp.setBurnSeconds(burn.getBurnSeconds());
resp.setViewedAt(burn.getViewedAt() != null ? burn.getViewedAt().getTime() : null);
resp.setBurnAt(burn.getBurnAt() != null ? burn.getBurnAt().getTime() : null);
resp.setBurned(burned);
if (burned) {
resp.setMediaUrl(null);
return resp;
}
boolean isSender = message.getSenderId().equals(userId);
boolean isReceiver = message.getReceiverId().equals(userId);
if (isReceiver) {
if (burn.getViewedAt() == null) {
burn.setViewedAt(now);
long burnAtMs = now.getTime() + (burn.getBurnSeconds() != null ? burn.getBurnSeconds() : 0) * 1000L;
burn.setBurnAt(new Date(burnAtMs));
burn.setUpdateTime(now);
privateMessageBurnDao.updateById(burn);
resp.setViewedAt(burn.getViewedAt().getTime());
resp.setBurnAt(burn.getBurnAt().getTime());
resp.setMediaUrl(message.getMediaUrl());
} else {
// 接收方只允许查看一次
resp.setMediaUrl(null);
}
return resp;
}
if (isSender) {
resp.setMediaUrl(message.getMediaUrl());
return resp;
}
resp.setMediaUrl(null);
return resp;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteMessage(Long messageId, Integer userId) {
@ -395,6 +508,26 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
return response;
}
private void fillBurnFields(ChatMessageResponse response, Long messageId) {
PrivateMessageBurn burn = privateMessageBurnDao.selectOne(
new LambdaQueryWrapper<PrivateMessageBurn>()
.eq(PrivateMessageBurn::getMessageId, messageId)
.last("LIMIT 1"));
if (burn == null) {
return;
}
response.setBurnSeconds(burn.getBurnSeconds());
response.setViewedAt(burn.getViewedAt() != null ? burn.getViewedAt().getTime() : null);
response.setBurnAt(burn.getBurnAt() != null ? burn.getBurnAt().getTime() : null);
boolean burned = Boolean.TRUE.equals(burn.getBurned());
if (!burned && burn.getBurnAt() != null && burn.getBurnAt().getTime() <= System.currentTimeMillis()) {
burned = true;
}
response.setBurned(burned);
}
/**
* 转换消息列表为响应对象列表
*/
@ -425,6 +558,11 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
response.setMessageType(message.getMessageType());
response.setMediaUrl(message.getMediaUrl());
response.setDuration(message.getDuration());
if ("burn_image".equals(message.getMessageType())) {
response.setMediaUrl(null);
fillBurnFields(response, message.getId());
}
// 设置是否已读
response.setIsRead("read".equals(message.getStatus()));

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `eb_private_message_burn` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`message_id` BIGINT(20) NOT NULL COMMENT '消息ID',
`conversation_id` BIGINT(20) NOT NULL COMMENT '会话ID',
`burn_seconds` INT(11) DEFAULT NULL COMMENT '销毁秒数',
`viewed_at` DATETIME DEFAULT NULL COMMENT '首次查看时间',
`burn_at` DATETIME DEFAULT NULL COMMENT '销毁时间',
`burned` TINYINT(1) DEFAULT 0 COMMENT '是否已销毁',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_message_id` (`message_id`),
KEY `idx_conversation_id` (`conversation_id`),
KEY `idx_burn_at` (`burn_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='阅后即焚消息状态表';