From 1dde8a11af80f52c4f780d5f469208e2ef084d73 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Tue, 14 Oct 2025 18:02:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B6=88=E6=81=AF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 发布功能发送广播消息,保存public_message 2. 公告消息查询功能 --- .../enums/message/GroupMessageTypeEnum.java | 12 +- .../model/dto/message/PublicMessageDTO.java | 76 ++++++ .../app/message/PublicMessageController.java | 108 ++++++++ .../app/message/dto/PublicMessagePageQry.java | 39 +++ .../message/PublicMessageConvertor.java | 42 +++ .../mongo/entity/message/PublicMessage.java | 90 +++++++ .../mongo/entity/message/UserMessage.java | 134 ++++++++++ .../service/message/PublicMessageService.java | 75 ++++++ .../service/message/UserMessageService.java | 112 ++++++++ .../impl/PublicMessageServiceImpl.java | 172 ++++++++++++ .../message/impl/UserMessageServiceImpl.java | 251 ++++++++++++++++++ .../impl/NoticeMessageClientServiceImpl.java | 69 ++++- 12 files changed, 1178 insertions(+), 2 deletions(-) create mode 100644 rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/message/PublicMessageDTO.java create mode 100644 rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/PublicMessageController.java create mode 100644 rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/dto/PublicMessagePageQry.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/message/PublicMessageConvertor.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/PublicMessage.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/UserMessage.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/PublicMessageService.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/UserMessageService.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/PublicMessageServiceImpl.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/UserMessageServiceImpl.java diff --git a/rc-service/rc-inner-api/external-inner/external-inner-model/src/main/java/com/red/circle/external/inner/model/enums/message/GroupMessageTypeEnum.java b/rc-service/rc-inner-api/external-inner/external-inner-model/src/main/java/com/red/circle/external/inner/model/enums/message/GroupMessageTypeEnum.java index e7395ce1..40461683 100644 --- a/rc-service/rc-inner-api/external-inner/external-inner-model/src/main/java/com/red/circle/external/inner/model/enums/message/GroupMessageTypeEnum.java +++ b/rc-service/rc-inner-api/external-inner/external-inner-model/src/main/java/com/red/circle/external/inner/model/enums/message/GroupMessageTypeEnum.java @@ -114,6 +114,16 @@ public enum GroupMessageTypeEnum { /** * 发送礼物 */ - SEND_GIFT + SEND_GIFT, + + /** + * 系统公告 + */ + SYS_ANNOUNCEMENT, + + /** + * 系统活动 + */ + SYS_ACTIVITY, } diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/message/PublicMessageDTO.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/message/PublicMessageDTO.java new file mode 100644 index 00000000..b23ed322 --- /dev/null +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/message/PublicMessageDTO.java @@ -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 extraData; + + /** + * 创建时间. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long createdAt; + + /** + * 过期时间. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long expiresAt; + + /** + * 消息状态:active-激活,deleted-删除. + */ + private String status; + +} diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/PublicMessageController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/PublicMessageController.java new file mode 100644 index 00000000..10307510 --- /dev/null +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/PublicMessageController.java @@ -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; + +/** + *

+ * 公共消息控制器. + *

+ * + * @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 pagePublicMessages(@Validated PublicMessagePageQry qry) { + log.info("分页查询公共消息, qry={}", qry); + + // 调用服务层分页查询 + Page page = publicMessageService.pagePublicMessages( + qry.getType(), + qry.getPageNo(), + qry.getPageSize() + ); + + // 转换为DTO + List dtoList = page.getContent().stream() + .map(publicMessageConvertor::toDTO) + .collect(Collectors.toList()); + + PageResult 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 listLatestMessages(@Validated PublicMessagePageQry qry) { + log.info("查询最新公共消息, qry={}", qry); + + // 如果没有传limit,默认查询10条 + Integer limit = qry.getPageSize() != null ? qry.getPageSize() : 10; + + // 调用服务层查询 + List messages = publicMessageService.listLatestMessages( + qry.getType(), + limit + ); + + // 转换为DTO + return messages.stream() + .map(publicMessageConvertor::toDTO) + .collect(Collectors.toList()); + } + +} diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/dto/PublicMessagePageQry.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/dto/PublicMessagePageQry.java new file mode 100644 index 00000000..5a051fd9 --- /dev/null +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/message/dto/PublicMessagePageQry.java @@ -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; + +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/message/PublicMessageConvertor.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/message/PublicMessageConvertor.java new file mode 100644 index 00000000..489580ed --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/message/PublicMessageConvertor.java @@ -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); + +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/PublicMessage.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/PublicMessage.java new file mode 100644 index 00000000..d76b3db2 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/PublicMessage.java @@ -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 extraData; + + /** + * 创建时间. + */ + @Field("created_at") + private Timestamp createdAt; + + /** + * 过期时间. + */ + @Field("expires_at") + private Timestamp expiresAt; + + /** + * 消息状态:active-激活,deleted-删除. + */ + @Field("status") + private String status; + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/UserMessage.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/UserMessage.java new file mode 100644 index 00000000..38cee359 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/message/UserMessage.java @@ -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 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; + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/PublicMessageService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/PublicMessageService.java new file mode 100644 index 00000000..3dcd18f3 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/PublicMessageService.java @@ -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/activity,null-查询全部) + * @param pageNo 页码(从1开始) + * @param pageSize 每页大小 + * @return 分页结果 + */ + Page pagePublicMessages(String type, Integer pageNo, Integer pageSize); + + /** + * 查询最新的N条消息. + * + * @param type 消息类型(notification/activity,null-查询全部) + * @param limit 查询数量限制 + * @return 消息列表 + */ + List 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 batchCreatePublicMessages(List messages); + + /** + * 更新消息状态. + * + * @param messageId 消息ID + * @param status 状态(active/deleted) + * @return 是否成功 + */ + Boolean updateStatus(String messageId, String status); + + /** + * 删除消息(逻辑删除). + * + * @param messageId 消息ID + * @return 是否成功 + */ + Boolean deleteMessage(String messageId); + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/UserMessageService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/UserMessageService.java new file mode 100644 index 00000000..db4a4871 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/UserMessageService.java @@ -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 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 listByActionType(Long userId, String actionType, Integer limit); + + /** + * 创建用户消息. + * + * @param message 消息实体 + * @return 创建后的消息实体 + */ + UserMessage createUserMessage(UserMessage message); + + /** + * 批量创建用户消息. + * + * @param messages 消息列表 + * @return 创建后的消息列表 + */ + List batchCreateUserMessages(List messages); + + /** + * 标记消息已读. + * + * @param messageId 消息ID + * @param userId 用户ID(校验消息归属) + * @return 是否成功 + */ + Boolean markAsRead(String messageId, Long userId); + + /** + * 批量标记消息已读. + * + * @param messageIds 消息ID列表 + * @param userId 用户ID(校验消息归属) + * @return 是否成功 + */ + Boolean batchMarkAsRead(List 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 messageIds, Long userId); + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/PublicMessageServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/PublicMessageServiceImpl.java new file mode 100644 index 00000000..010b92f2 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/PublicMessageServiceImpl.java @@ -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 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 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 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 batchCreatePublicMessages(List 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) 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("-", ""); + } + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/UserMessageServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/UserMessageServiceImpl.java new file mode 100644 index 00000000..510f06d8 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/message/impl/UserMessageServiceImpl.java @@ -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 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 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 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 batchCreateUserMessages(List 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) 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 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 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("-", ""); + } + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/sys/impl/NoticeMessageClientServiceImpl.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/sys/impl/NoticeMessageClientServiceImpl.java index c0af41c2..0e8102ee 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/sys/impl/NoticeMessageClientServiceImpl.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/sys/impl/NoticeMessageClientServiceImpl.java @@ -1,11 +1,21 @@ 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.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.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.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.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.response.CommonErrorCode; 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.other.app.inner.convertor.sys.SysNoticeInnerConvertor; 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.service.sys.NoticeMessageService; 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.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.ZonedDateTimeAsiaRiyadhUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.sql.Timestamp; +import java.time.ZonedDateTime; +import java.util.List; + /** *

* 系统公告通知消息 服务实现类. @@ -36,7 +55,10 @@ public class NoticeMessageClientServiceImpl implements NoticeMessageClientServic private final NoticeMessageService noticeMessageService; 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 public PageResult getNoticeMessage(SysNoticeMessageQryCmd query) { @@ -94,9 +116,54 @@ public class NoticeMessageClientServiceImpl implements NoticeMessageClientServic @Override public void announcement(Long 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) .set(NoticeMessage::getAnnouncementTime, LocalDateUtils.nowTimestamp()) .eq(NoticeMessage::getId, id) .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.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()") + );*/ + } }