新增消息功能

1. 发布功能发送广播消息,保存public_message
2. 公告消息查询功能
This commit is contained in:
tianfeng 2025-10-14 18:02:47 +08:00
parent d93aa00fbe
commit 1dde8a11af
12 changed files with 1178 additions and 2 deletions

View File

@ -114,6 +114,16 @@ public enum GroupMessageTypeEnum {
/** /**
* 发送礼物 * 发送礼物
*/ */
SEND_GIFT SEND_GIFT,
/**
* 系统公告
*/
SYS_ANNOUNCEMENT,
/**
* 系统活动
*/
SYS_ACTIVITY,
} }

View File

@ -0,0 +1,76 @@
package com.red.circle.other.inner.model.dto.message;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 公共消息DTO.
*
* @author your_name
* @date 2025-10-14
*/
@Data
@Accessors(chain = true)
public class PublicMessageDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* MongoDB主键.
*/
private String id;
/**
* 业务消息ID.
*/
private String messageId;
/**
* 消息类型notification-通知activity-活动.
*/
private String type;
/**
* 消息标题.
*/
private String title;
/**
* 消息内容.
*/
private String content;
/**
* 消息图片URL.
*/
private String imageUrl;
/**
* 扩展字段存储额外信息.
*/
private Map<String, Object> extraData;
/**
* 创建时间.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long createdAt;
/**
* 过期时间.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long expiresAt;
/**
* 消息状态active-激活deleted-删除.
*/
private String status;
}

View File

@ -0,0 +1,108 @@
package com.red.circle.other.adapter.app.message;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.other.infra.convertor.message.PublicMessageConvertor;
import com.red.circle.other.inner.model.dto.message.PublicMessageDTO;
import com.red.circle.other.adapter.app.message.dto.PublicMessagePageQry;
import com.red.circle.other.infra.database.mongo.entity.message.PublicMessage;
import com.red.circle.other.infra.database.mongo.service.message.PublicMessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 公共消息控制器.
* </p>
*
* @author your_name
* @since 2025-10-14
* @eo.api-type http
* @eo.groupName 消息管理
* @eo.path /public-message
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/public-message", produces = MediaType.APPLICATION_JSON_VALUE)
public class PublicMessageController extends BaseController {
private final PublicMessageService publicMessageService;
private final PublicMessageConvertor publicMessageConvertor;
/**
* 分页查询公共消息列表.
*
* @param qry 查询条件
* @return 分页结果
* @eo.name 分页查询公共消息列表
* @eo.url /page
* @eo.method get
* @eo.request-type formdata
*/
@GetMapping("/page")
public PageResult<PublicMessageDTO> pagePublicMessages(@Validated PublicMessagePageQry qry) {
log.info("分页查询公共消息, qry={}", qry);
// 调用服务层分页查询
Page<PublicMessage> page = publicMessageService.pagePublicMessages(
qry.getType(),
qry.getPageNo(),
qry.getPageSize()
);
// 转换为DTO
List<PublicMessageDTO> dtoList = page.getContent().stream()
.map(publicMessageConvertor::toDTO)
.collect(Collectors.toList());
PageResult<PublicMessageDTO> pageResult = new PageResult<>();
pageResult.setSize(page.getSize());
pageResult.setCurrent(qry.getPageNo());
pageResult.setRecords(dtoList);
pageResult.setTotal(page.getTotalElements());
// 构建分页结果
return pageResult;
}
/**
* 查询最新的公共消息列表.
*
* @param qry 查询条件
* @return 消息列表
* @eo.name 查询最新的公共消息列表
* @eo.url /latest
* @eo.method get
* @eo.request-type formdata
*/
// @GetMapping("/latest")
public List<PublicMessageDTO> listLatestMessages(@Validated PublicMessagePageQry qry) {
log.info("查询最新公共消息, qry={}", qry);
// 如果没有传limit默认查询10条
Integer limit = qry.getPageSize() != null ? qry.getPageSize() : 10;
// 调用服务层查询
List<PublicMessage> messages = publicMessageService.listLatestMessages(
qry.getType(),
limit
);
// 转换为DTO
return messages.stream()
.map(publicMessageConvertor::toDTO)
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,39 @@
package com.red.circle.other.adapter.app.message.dto;
import com.red.circle.common.business.dto.PageQueryCmd;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 公共消息分页查询参数.
*
* @author your_name
* @date 2025-10-14
*/
@Data
public class PublicMessagePageQry {
/**
* 消息类型notification-通知activity-活动null-查询全部.
*/
private String type;
/**
* 页码从1开始.
*/
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小为1")
private Integer pageNo;
/**
* 每页大小.
*/
@NotNull(message = "每页大小不能为空")
@Min(value = 1, message = "每页大小最小为1")
@Max(value = 100, message = "每页大小最大为100")
private Integer pageSize;
}

View File

@ -0,0 +1,42 @@
package com.red.circle.other.infra.convertor.message;
import com.red.circle.other.infra.database.mongo.entity.message.PublicMessage;
import com.red.circle.other.inner.model.dto.message.PublicMessageDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
/**
* 公共消息转换器.
*
* @author your_name
* @date 2025-10-14
*/
@Mapper(componentModel = "spring")
public interface PublicMessageConvertor {
/**
* Entity转DTO.
*
* @param entity 实体对象
* @return DTO对象
*/
@Mappings({
@Mapping(target = "createdAt", expression = "java(entity.getCreatedAt() != null ? entity.getCreatedAt().getTime() : null)"),
@Mapping(target = "expiresAt", expression = "java(entity.getExpiresAt() != null ? entity.getExpiresAt().getTime() : null)")
})
PublicMessageDTO toDTO(PublicMessage entity);
/**
* DTO转Entity.
*
* @param dto DTO对象
* @return 实体对象
*/
@Mappings({
@Mapping(target = "createdAt", expression = "java(dto.getCreatedAt() != null ? new java.sql.Timestamp(dto.getCreatedAt()) : null)"),
@Mapping(target = "expiresAt", expression = "java(dto.getExpiresAt() != null ? new java.sql.Timestamp(dto.getExpiresAt()) : null)")
})
PublicMessage toEntity(PublicMessageDTO dto);
}

View File

@ -0,0 +1,90 @@
package com.red.circle.other.infra.database.mongo.entity.message;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Map;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* 公共消息实体类.
*
* @author tf
* @date 2025-10-14
*/
@Data
@Accessors(chain = true)
@Document("public_messages")
public class PublicMessage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* MongoDB主键.
*/
@Id
private Long id;
/**
* 业务消息ID.
*/
@Field("message_id")
private String messageId;
/**
* 消息类型notification-通知activity-活动.
*/
@Field("type")
private String type;
/**
* 消息标题.
*/
@Field("title")
private String title;
/**
* 消息内容.
*/
@Field("content")
private String content;
/**
* 消息图片URL.
*/
@Field("image_url")
private String imageUrl;
/**
* 扩展字段存储额外信息.
*/
@Field("extra_data")
private Map<String, Object> extraData;
/**
* 创建时间.
*/
@Field("created_at")
private Timestamp createdAt;
/**
* 过期时间.
*/
@Field("expires_at")
private Timestamp expiresAt;
/**
* 消息状态active-激活deleted-删除.
*/
@Field("status")
private String status;
}

View File

@ -0,0 +1,134 @@
package com.red.circle.other.infra.database.mongo.entity.message;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Map;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* 用户消息实体类.
*
* @author tf
* @date 2025-10-14
*/
@Data
@Accessors(chain = true)
@Document("user_messages")
public class UserMessage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* MongoDB主键.
*/
@Id
private Long id;
/**
* 业务消息ID.
*/
@Field("message_id")
private String messageId;
/**
* 消息类型.
*/
@Field("type")
private String type;
/**
* 发送者类型system-系统user-用户.
*/
@Field("from_type")
private String fromType;
/**
* 发送者用户ID系统消息为null.
*/
@Field("from_user_id")
@JsonSerialize(using = ToStringSerializer.class)
private Long fromUserId;
/**
* 发送者用户名.
*/
@Field("from_user_name")
private String fromUserName;
/**
* 发送者头像URL.
*/
@Field("from_user_avatar")
private String fromUserAvatar;
/**
* 接收者用户ID.
*/
@Field("to_user_id")
@JsonSerialize(using = ToStringSerializer.class)
private Long toUserId;
/**
* 消息标题.
*/
@Field("title")
private String title;
/**
* 消息内容.
*/
@Field("content")
private String content;
/**
* 消息动作类型.
*/
@Field("action_type")
private String actionType;
/**
* 动作相关数据.
*/
@Field("action_data")
private Map<String, Object> actionData;
/**
* 是否已读.
*/
@Field("is_read")
private Boolean isRead;
/**
* 读取时间.
*/
@Field("read_at")
private Date readAt;
/**
* 用户是否删除.
*/
@Field("is_deleted")
private Boolean isDeleted;
/**
* 创建时间.
*/
@Field("created_at")
private Timestamp createdAt;
/**
* 过期时间.
*/
@Field("expires_at")
private Timestamp expiresAt;
}

View File

@ -0,0 +1,75 @@
package com.red.circle.other.infra.database.mongo.service.message;
import com.red.circle.other.infra.database.mongo.entity.message.PublicMessage;
import java.util.List;
import org.springframework.data.domain.Page;
/**
* 公共消息服务接口.
*
* @author tf
* @date 2025-10-14
*/
public interface PublicMessageService {
/**
* 分页查询公共消息列表.
*
* @param type 消息类型notification/activitynull-查询全部
* @param pageNo 页码从1开始
* @param pageSize 每页大小
* @return 分页结果
*/
Page<PublicMessage> pagePublicMessages(String type, Integer pageNo, Integer pageSize);
/**
* 查询最新的N条消息.
*
* @param type 消息类型notification/activitynull-查询全部
* @param limit 查询数量限制
* @return 消息列表
*/
List<PublicMessage> listLatestMessages(String type, Integer limit);
/**
* 根据消息ID查询.
*
* @param messageId 消息ID
* @return 消息实体
*/
PublicMessage getByMessageId(String messageId);
/**
* 创建公共消息.
*
* @param message 消息实体
* @return 创建后的消息实体
*/
PublicMessage createPublicMessage(PublicMessage message);
/**
* 批量创建公共消息.
*
* @param messages 消息列表
* @return 创建后的消息列表
*/
List<PublicMessage> batchCreatePublicMessages(List<PublicMessage> messages);
/**
* 更新消息状态.
*
* @param messageId 消息ID
* @param status 状态active/deleted
* @return 是否成功
*/
Boolean updateStatus(String messageId, String status);
/**
* 删除消息逻辑删除.
*
* @param messageId 消息ID
* @return 是否成功
*/
Boolean deleteMessage(String messageId);
}

View File

@ -0,0 +1,112 @@
package com.red.circle.other.infra.database.mongo.service.message;
import com.red.circle.other.infra.database.mongo.entity.message.UserMessage;
import java.util.List;
import org.springframework.data.domain.Page;
/**
* 用户消息服务接口.
*
* @author tf
* @date 2025-10-14
*/
public interface UserMessageService {
/**
* 分页查询用户消息列表.
*
* @param userId 用户ID
* @param isRead 是否已读null-全部true-已读false-未读
* @param pageNo 页码从1开始
* @param pageSize 每页大小
* @return 分页结果
*/
Page<UserMessage> pageUserMessages(Long userId, Boolean isRead, Integer pageNo, Integer pageSize);
/**
* 查询未读消息数量.
*
* @param userId 用户ID
* @return 未读消息数量
*/
Long countUnreadMessages(Long userId);
/**
* 根据消息ID查询.
*
* @param messageId 消息ID
* @return 消息实体
*/
UserMessage getByMessageId(String messageId);
/**
* 根据动作类型查询消息列表.
*
* @param userId 用户ID
* @param actionType 动作类型
* @param limit 查询数量限制
* @return 消息列表
*/
List<UserMessage> listByActionType(Long userId, String actionType, Integer limit);
/**
* 创建用户消息.
*
* @param message 消息实体
* @return 创建后的消息实体
*/
UserMessage createUserMessage(UserMessage message);
/**
* 批量创建用户消息.
*
* @param messages 消息列表
* @return 创建后的消息列表
*/
List<UserMessage> batchCreateUserMessages(List<UserMessage> messages);
/**
* 标记消息已读.
*
* @param messageId 消息ID
* @param userId 用户ID校验消息归属
* @return 是否成功
*/
Boolean markAsRead(String messageId, Long userId);
/**
* 批量标记消息已读.
*
* @param messageIds 消息ID列表
* @param userId 用户ID校验消息归属
* @return 是否成功
*/
Boolean batchMarkAsRead(List<String> messageIds, Long userId);
/**
* 标记所有消息已读.
*
* @param userId 用户ID
* @return 是否成功
*/
Boolean markAllAsRead(Long userId);
/**
* 删除消息逻辑删除.
*
* @param messageId 消息ID
* @param userId 用户ID校验消息归属
* @return 是否成功
*/
Boolean deleteMessage(String messageId, Long userId);
/**
* 批量删除消息.
*
* @param messageIds 消息ID列表
* @param userId 用户ID校验消息归属
* @return 是否成功
*/
Boolean batchDeleteMessages(List<String> messageIds, Long userId);
}

View File

@ -0,0 +1,172 @@
package com.red.circle.other.infra.database.mongo.service.message.impl;
import com.red.circle.other.infra.database.mongo.entity.message.PublicMessage;
import java.sql.Timestamp;
import java.util.List;
import java.util.UUID;
import com.red.circle.other.infra.database.mongo.service.message.PublicMessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* 公共消息服务实现类.
*
* @author tf
* @date 2025-10-14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PublicMessageServiceImpl implements PublicMessageService {
private final MongoTemplate mongoTemplate;
@Override
public Page<PublicMessage> pagePublicMessages(String type, Integer pageNo, Integer pageSize) {
log.info("分页查询公共消息, type={}, pageNo={}, pageSize={}", type, pageNo, pageSize);
// 构建查询条件
Criteria criteria = Criteria.where("status").is("active");
if (StringUtils.hasText(type)) {
criteria.and("type").is(type);
}
Query query = new Query(criteria);
// 分页和排序
Pageable pageable = PageRequest.of(pageNo - 1, pageSize,
Sort.by(Sort.Direction.DESC, "created_at"));
query.with(pageable);
// 查询数据
List<PublicMessage> messages = mongoTemplate.find(query, PublicMessage.class);
// 查询总数
long total = mongoTemplate.count(Query.of(query).limit(-1).skip(-1), PublicMessage.class);
return PageableExecutionUtils.getPage(messages, pageable, () -> total);
}
@Override
public List<PublicMessage> listLatestMessages(String type, Integer limit) {
log.info("查询最新公共消息, type={}, limit={}", type, limit);
// 构建查询条件
Criteria criteria = Criteria.where("status").is("active");
if (StringUtils.hasText(type)) {
criteria.and("type").is(type);
}
Query query = new Query(criteria)
.with(Sort.by(Sort.Direction.DESC, "created_at"))
.limit(limit);
return mongoTemplate.find(query, PublicMessage.class);
}
@Override
public PublicMessage getByMessageId(String messageId) {
log.info("根据消息ID查询, messageId={}", messageId);
if (!StringUtils.hasText(messageId)) {
return null;
}
Query query = new Query(Criteria.where("message_id").is(messageId));
return mongoTemplate.findOne(query, PublicMessage.class);
}
@Override
public PublicMessage createPublicMessage(PublicMessage message) {
log.info("创建公共消息, type={}, title={}", message.getType(), message.getTitle());
// 生成消息ID
if (!StringUtils.hasText(message.getMessageId())) {
message.setMessageId(generateMessageId());
}
// 设置默认状态
if (!StringUtils.hasText(message.getStatus())) {
message.setStatus("active");
}
// 设置创建时间
Timestamp now = new Timestamp(System.currentTimeMillis());
message.setCreatedAt(now);
// 保存到数据库
return mongoTemplate.insert(message);
}
@Override
public List<PublicMessage> batchCreatePublicMessages(List<PublicMessage> messages) {
log.info("批量创建公共消息, count={}", messages.size());
if (CollectionUtils.isEmpty(messages)) {
return messages;
}
Timestamp now = new Timestamp(System.currentTimeMillis());
// 批量设置默认值
messages.forEach(message -> {
if (!StringUtils.hasText(message.getMessageId())) {
message.setMessageId(generateMessageId());
}
if (!StringUtils.hasText(message.getStatus())) {
message.setStatus("active");
}
message.setCreatedAt(now);
});
// 批量插入
return (List<PublicMessage>) mongoTemplate.insertAll(messages);
}
@Override
public Boolean updateStatus(String messageId, String status) {
log.info("更新消息状态, messageId={}, status={}", messageId, status);
Query query = new Query(Criteria.where("message_id").is(messageId));
Update update = new Update().set("status", status);
return mongoTemplate.updateFirst(query, update, PublicMessage.class)
.getModifiedCount() > 0;
}
@Override
public Boolean deleteMessage(String messageId) {
log.info("删除公共消息, messageId={}", messageId);
Query query = new Query(Criteria.where("message_id").is(messageId));
Update update = new Update().set("status", "deleted");
return mongoTemplate.updateFirst(query, update, PublicMessage.class)
.getModifiedCount() > 0;
}
/**
* 生成消息ID.
*
* @return 消息ID
*/
private String generateMessageId() {
return "msg_" + UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -0,0 +1,251 @@
package com.red.circle.other.infra.database.mongo.service.message.impl;
import com.red.circle.other.infra.database.mongo.entity.message.UserMessage;
import java.sql.Timestamp;
import java.util.List;
import java.util.UUID;
import com.red.circle.other.infra.database.mongo.service.message.UserMessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* 用户消息服务实现类.
*
* @author tf
* @date 2025-10-14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserMessageServiceImpl implements UserMessageService {
private final MongoTemplate mongoTemplate;
@Override
public Page<UserMessage> pageUserMessages(Long userId, Boolean isRead, Integer pageNo,
Integer pageSize) {
log.info("分页查询用户消息, userId={}, isRead={}, pageNo={}, pageSize={}",
userId, isRead, pageNo, pageSize);
// 构建查询条件
Criteria criteria = Criteria.where("to_user_id").is(userId)
.and("is_deleted").is(false);
if (isRead != null) {
criteria.and("is_read").is(isRead);
}
Query query = new Query(criteria);
// 分页和排序
Pageable pageable = PageRequest.of(pageNo - 1, pageSize,
Sort.by(Sort.Direction.DESC, "created_at"));
query.with(pageable);
// 查询数据
List<UserMessage> messages = mongoTemplate.find(query, UserMessage.class);
// 查询总数
long total = mongoTemplate.count(Query.of(query).limit(-1).skip(-1), UserMessage.class);
return PageableExecutionUtils.getPage(messages, pageable, () -> total);
}
@Override
public Long countUnreadMessages(Long userId) {
log.info("查询未读消息数量, userId={}", userId);
Criteria criteria = Criteria.where("to_user_id").is(userId)
.and("is_read").is(false)
.and("is_deleted").is(false);
Query query = new Query(criteria);
return mongoTemplate.count(query, UserMessage.class);
}
@Override
public UserMessage getByMessageId(String messageId) {
log.info("根据消息ID查询, messageId={}", messageId);
if (!StringUtils.hasText(messageId)) {
return null;
}
Query query = new Query(Criteria.where("message_id").is(messageId));
return mongoTemplate.findOne(query, UserMessage.class);
}
@Override
public List<UserMessage> listByActionType(Long userId, String actionType, Integer limit) {
log.info("根据动作类型查询消息列表, userId={}, actionType={}, limit={}",
userId, actionType, limit);
Criteria criteria = Criteria.where("to_user_id").is(userId)
.and("action_type").is(actionType)
.and("is_deleted").is(false);
Query query = new Query(criteria)
.with(Sort.by(Sort.Direction.DESC, "created_at"))
.limit(limit);
return mongoTemplate.find(query, UserMessage.class);
}
@Override
public UserMessage createUserMessage(UserMessage message) {
log.info("创建用户消息, toUserId={}, fromType={}",
message.getToUserId(), message.getFromType());
// 生成消息ID
if (!StringUtils.hasText(message.getMessageId())) {
message.setMessageId(generateMessageId());
}
// 设置默认值
if (message.getIsRead() == null) {
message.setIsRead(false);
}
if (message.getIsDeleted() == null) {
message.setIsDeleted(false);
}
// 设置创建时间
Timestamp now = new Timestamp(System.currentTimeMillis());
message.setCreatedAt(now);
// 保存到数据库
return mongoTemplate.insert(message);
}
@Override
public List<UserMessage> batchCreateUserMessages(List<UserMessage> messages) {
log.info("批量创建用户消息, count={}", messages.size());
if (CollectionUtils.isEmpty(messages)) {
return messages;
}
Timestamp now = new Timestamp(System.currentTimeMillis());
// 批量设置默认值
messages.forEach(message -> {
if (!StringUtils.hasText(message.getMessageId())) {
message.setMessageId(generateMessageId());
}
if (message.getIsRead() == null) {
message.setIsRead(false);
}
if (message.getIsDeleted() == null) {
message.setIsDeleted(false);
}
message.setCreatedAt(now);
});
// 批量插入
return (List<UserMessage>) mongoTemplate.insertAll(messages);
}
@Override
public Boolean markAsRead(String messageId, Long userId) {
log.info("标记消息已读, messageId={}, userId={}", messageId, userId);
Criteria criteria = Criteria.where("message_id").is(messageId)
.and("to_user_id").is(userId);
Update update = new Update()
.set("is_read", true)
.set("read_at", new Timestamp(System.currentTimeMillis()));
return mongoTemplate.updateFirst(new Query(criteria), update, UserMessage.class)
.getModifiedCount() > 0;
}
@Override
public Boolean batchMarkAsRead(List<String> messageIds, Long userId) {
log.info("批量标记消息已读, userId={}, count={}", userId, messageIds.size());
if (CollectionUtils.isEmpty(messageIds)) {
return false;
}
Criteria criteria = Criteria.where("message_id").in(messageIds)
.and("to_user_id").is(userId);
Update update = new Update()
.set("is_read", true)
.set("read_at", new Timestamp(System.currentTimeMillis()));
return mongoTemplate.updateMulti(new Query(criteria), update, UserMessage.class)
.getModifiedCount() > 0;
}
@Override
public Boolean markAllAsRead(Long userId) {
log.info("标记所有消息已读, userId={}", userId);
Criteria criteria = Criteria.where("to_user_id").is(userId)
.and("is_read").is(false)
.and("is_deleted").is(false);
Update update = new Update()
.set("is_read", true)
.set("read_at", new Timestamp(System.currentTimeMillis()));
return mongoTemplate.updateMulti(new Query(criteria), update, UserMessage.class)
.getModifiedCount() > 0;
}
@Override
public Boolean deleteMessage(String messageId, Long userId) {
log.info("删除消息, messageId={}, userId={}", messageId, userId);
Criteria criteria = Criteria.where("message_id").is(messageId)
.and("to_user_id").is(userId);
Update update = new Update().set("is_deleted", true);
return mongoTemplate.updateFirst(new Query(criteria), update, UserMessage.class)
.getModifiedCount() > 0;
}
@Override
public Boolean batchDeleteMessages(List<String> messageIds, Long userId) {
log.info("批量删除消息, userId={}, count={}", userId, messageIds.size());
if (CollectionUtils.isEmpty(messageIds)) {
return false;
}
Criteria criteria = Criteria.where("message_id").in(messageIds)
.and("to_user_id").is(userId);
Update update = new Update().set("is_deleted", true);
return mongoTemplate.updateMulti(new Query(criteria), update, UserMessage.class)
.getModifiedCount() > 0;
}
/**
* 生成消息ID.
*
* @return 消息ID
*/
private String generateMessageId() {
return "msg_" + UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -1,11 +1,21 @@
package com.red.circle.other.app.inner.service.sys.impl; package com.red.circle.other.app.inner.service.sys.impl;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.external.inner.endpoint.message.ImGroupClient;
import com.red.circle.external.inner.endpoint.message.NewsletterClient;
import com.red.circle.external.inner.endpoint.message.OfficialNoticeClient;
import com.red.circle.external.inner.endpoint.push.GoogleFirebasePushClient; import com.red.circle.external.inner.endpoint.push.GoogleFirebasePushClient;
import com.red.circle.external.inner.model.cmd.message.BroadcastGroupMsgBodyCmd;
import com.red.circle.external.inner.model.cmd.message.NewsletterResultCmd;
import com.red.circle.external.inner.model.cmd.message.google.GooglePushCmd; import com.red.circle.external.inner.model.cmd.message.google.GooglePushCmd;
import com.red.circle.external.inner.model.cmd.message.google.PushMsgBodyCmd; import com.red.circle.external.inner.model.cmd.message.google.PushMsgBodyCmd;
import com.red.circle.external.inner.model.cmd.message.notice.official.NoticeExtTemplateTypeCmd;
import com.red.circle.external.inner.model.enums.PushNoticeType; import com.red.circle.external.inner.model.enums.PushNoticeType;
import com.red.circle.external.inner.model.enums.message.GroupMessageTypeEnum;
import com.red.circle.external.inner.model.enums.message.NewsletterEvent;
import com.red.circle.external.inner.model.enums.message.OfficialNoticeTypeEnum;
import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode; import com.red.circle.framework.core.response.ResponseErrorCode;
@ -13,15 +23,24 @@ import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.mybatis.constant.PageConstant; import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.app.inner.convertor.sys.SysNoticeInnerConvertor; import com.red.circle.other.app.inner.convertor.sys.SysNoticeInnerConvertor;
import com.red.circle.other.app.inner.service.sys.NoticeMessageClientService; import com.red.circle.other.app.inner.service.sys.NoticeMessageClientService;
import com.red.circle.other.app.service.user.user.UserProfileService;
import com.red.circle.other.infra.database.mongo.entity.message.PublicMessage;
import com.red.circle.other.infra.database.mongo.service.message.PublicMessageService;
import com.red.circle.other.infra.database.rds.entity.sys.NoticeMessage; import com.red.circle.other.infra.database.rds.entity.sys.NoticeMessage;
import com.red.circle.other.infra.database.rds.service.sys.NoticeMessageService; import com.red.circle.other.infra.database.rds.service.sys.NoticeMessageService;
import com.red.circle.other.inner.model.cmd.sys.SysNoticeMessageCmd; import com.red.circle.other.inner.model.cmd.sys.SysNoticeMessageCmd;
import com.red.circle.other.inner.model.cmd.sys.SysNoticeMessageQryCmd; import com.red.circle.other.inner.model.cmd.sys.SysNoticeMessageQryCmd;
import com.red.circle.other.inner.model.dto.sys.SysNoticeMessageDTO; import com.red.circle.other.inner.model.dto.sys.SysNoticeMessageDTO;
import com.red.circle.tool.core.collection.MapBuilder;
import com.red.circle.tool.core.date.LocalDateUtils; import com.red.circle.tool.core.date.LocalDateUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.time.ZonedDateTime;
import java.util.List;
/** /**
* <p> * <p>
* 系统公告通知消息 服务实现类. * 系统公告通知消息 服务实现类.
@ -36,7 +55,10 @@ public class NoticeMessageClientServiceImpl implements NoticeMessageClientServic
private final NoticeMessageService noticeMessageService; private final NoticeMessageService noticeMessageService;
private final SysNoticeInnerConvertor sysNoticeInnerConvertor; private final SysNoticeInnerConvertor sysNoticeInnerConvertor;
private final GoogleFirebasePushClient googleFirebasePushClient; private final ImGroupClient imGroupClient;
private final OfficialNoticeClient officialNoticeClient;
private final NewsletterClient newsletterClient;
private final PublicMessageService publicMessageService;
@Override @Override
public PageResult<SysNoticeMessageDTO> getNoticeMessage(SysNoticeMessageQryCmd query) { public PageResult<SysNoticeMessageDTO> getNoticeMessage(SysNoticeMessageQryCmd query) {
@ -94,9 +116,54 @@ public class NoticeMessageClientServiceImpl implements NoticeMessageClientServic
@Override @Override
public void announcement(Long id) { public void announcement(Long id) {
ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, id); ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, id);
// 查询公告详情
NoticeMessage noticeMessage = noticeMessageService.getById(id);
if (noticeMessage == null) {
throw new RuntimeException("Announcement does not exist.");
}
// 更新公告状态
noticeMessageService.update().set(NoticeMessage::getAnnouncement, Boolean.TRUE) noticeMessageService.update().set(NoticeMessage::getAnnouncement, Boolean.TRUE)
.set(NoticeMessage::getAnnouncementTime, LocalDateUtils.nowTimestamp()) .set(NoticeMessage::getAnnouncementTime, LocalDateUtils.nowTimestamp())
.eq(NoticeMessage::getId, id) .eq(NoticeMessage::getId, id)
.execute(); .execute();
// 发送IM群组广播
imGroupClient.sendMessageBroadcast(
BroadcastGroupMsgBodyCmd.builder()
.toPlatform(SysOriginPlatformEnum.LIKEI)
.type(GroupMessageTypeEnum.SYS_ANNOUNCEMENT)
.data("issued an announcement")
.build());
// 创建公共消息到MongoDB
PublicMessage publicMessage = new PublicMessage();
publicMessage.setId(IdWorker.getId());
publicMessage.setType("notification");
publicMessage.setTitle(noticeMessage.getTitle());
publicMessage.setContent(noticeMessage.getDescription());
publicMessage.setImageUrl(noticeMessage.getCover());
publicMessage.setExtraData(MapBuilder.<String, Object>builder()
.put("notice_id", noticeMessage.getId())
.put("link", noticeMessage.getLink())
.put("sys_origin", noticeMessage.getSysOrigin())
.build());
publicMessage.setStatus("active");
// 设置过期时间30天后过期
ZonedDateTime zonedDateTime = ZonedDateTimeAsiaRiyadhUtils.now().plusDays(30);
Timestamp timestamp = new Timestamp(zonedDateTime.toInstant().toEpochMilli());
publicMessage.setExpiresAt(timestamp);
publicMessageService.createPublicMessage(publicMessage);
/*newsletterClient.send(new NewsletterResultCmd()
.setUserIds(List.of(1954021558348390402L))
.setEvent(NewsletterEvent.DYNAMIC_MESSAGE_QUANTITY)
.setData("list.size()")
);*/
} }
} }