From 2b11bdf65ee0cd0e4f0be4b860a7d8967936dd51 Mon Sep 17 00:00:00 2001 From: Timi Date: Mon, 24 Aug 2026 16:50:23 +0800 Subject: [PATCH 1/6] tiptap content for notify detail --- .../api/modules/common/entity/Attachment.java | 2 + .../common/entity/BaseQueueEntity.java | 4 +- .../modules/common/entity/NotifyDetail.java | 26 ++++++++- .../implement/MailQueueServiceImplement.java | 2 +- .../NotifyDetailServiceImplement.java | 54 +++++++++++++++++-- .../V40__add_notify_detail_content_type.sql | 15 ++++++ 6 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/db/migration/timiserver/V40__add_notify_detail_content_type.sql diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java index 8b829bb..6b45bbf 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java @@ -32,6 +32,8 @@ public class Attachment extends UUIDEntity { ARTICLE, + NOTIFY_DETAIL, + USER, GAO_CUSTOMER, diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/BaseQueueEntity.java b/src/main/java/com/imyeyu/api/modules/common/entity/BaseQueueEntity.java index 2b8a32f..2dccd25 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/BaseQueueEntity.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/BaseQueueEntity.java @@ -26,7 +26,7 @@ public class BaseQueueEntity implements IDEntity, Creatable, Destroyable protected String notifyDetailId; /** 内容 */ - protected String data; + protected String content; /** 参数 */ protected JsonNode args; @@ -51,7 +51,7 @@ public class BaseQueueEntity implements IDEntity, Creatable, Destroyable /// @param detail 通知详情 public BaseQueueEntity(NotifyDetail detail) { notifyDetailId = detail.getId(); - data = detail.getData(); + content = detail.getContent(); args = detail.getArgs(); sendTo = detail.getSendTo(); sendAt = detail.getSendAt(); diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java index 7087cb0..ac1fdce 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java @@ -16,6 +16,8 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + /** * 通知详情 * @@ -82,6 +84,19 @@ public class NotifyDetail extends UUIDEntity { FAIL } + /// 内容类型 + /// + /// @author 夜雨 + /// @since 2026-08-24 + public enum ContentType { + + /** 纯文本 */ + TEXT, + + /** TipTap 富文本 */ + TIP_TAP + } + /** 所属通知 ID */ @JsonView(ResponseView.Public.class) private String notifyId; @@ -100,7 +115,11 @@ public class NotifyDetail extends UUIDEntity { /** 内容 */ @JsonView(ResponseView.Public.class) - private String data; + private String content; + + /// 内容类型 + @JsonView(ResponseView.Public.class) + private ContentType contentType; /** 参数 */ @JsonView(ResponseView.Public.class) @@ -146,4 +165,9 @@ public class NotifyDetail extends UUIDEntity { @Transient protected User toUser; + + /// 媒体附件列表 + @JsonView(ResponseView.Public.class) + @Transient + protected List attachmentList; } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/MailQueueServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/MailQueueServiceImplement.java index ac23345..b0d06a7 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/MailQueueServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/MailQueueServiceImplement.java @@ -60,7 +60,7 @@ public class MailQueueServiceImplement extends AbstractQueueService implements NotifyDetailService { private final NotifyMapper notifyMapper; + private final AttachmentService attachmentService; private final NotifyDetailMapper mapper; @@ -53,6 +57,9 @@ public class NotifyDetailServiceImplement extends AbstractEntityService listByNotifyId(String notifyId) { NotifyDetail example = new NotifyDetail(); example.setNotifyId(notifyId); - return mapper.selectAllByExample(example); + List detailList = mapper.selectAllByExample(example); + loadAttachments(detailList); + return detailList; } /// 删除通知详情 @@ -135,9 +147,11 @@ public class NotifyDetailServiceImplement extends AbstractEntityService detailList = unreadOnly ? mapper.selectUnreadSiteByUserId(userId, validPage.getOffset(), validPage.getLimit()) - : mapper.selectSiteByUserId(userId, validPage.getOffset(), validPage.getLimit())); + : mapper.selectSiteByUserId(userId, validPage.getOffset(), validPage.getLimit()); + loadAttachments(detailList); + result.setList(detailList); return result; } @@ -152,9 +166,41 @@ public class NotifyDetailServiceImplement extends AbstractEntityService detailList) { + List detailIdList = detailList.stream() + .filter(detail -> detail.getMsgType() == NotifyDetail.MsgType.INTERNAL) + .map(NotifyDetail::getId) + .toList(); + if (detailIdList.isEmpty()) { + return; + } + Map> attachmentMap = attachmentService.mapByBizIdList( + Attachment.BizType.NOTIFY_DETAIL, + detailIdList + ); + for (NotifyDetail detail : detailList) { + if (detail.getMsgType() == NotifyDetail.MsgType.INTERNAL) { + detail.setAttachmentList(attachmentMap.getOrDefault(detail.getId(), List.of())); + } + } + } + + /// 回填通知详情的媒体附件 + /// + /// @param detail 通知详情 + private void loadAttachments(NotifyDetail detail) { + if (detail.getMsgType() == NotifyDetail.MsgType.INTERNAL) { + detail.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.NOTIFY_DETAIL, detail.getId())); + } + } + /// 删除当前用户拥有的站内通知详情 /// /// @param detailId 通知详情 ID diff --git a/src/main/resources/db/migration/timiserver/V40__add_notify_detail_content_type.sql b/src/main/resources/db/migration/timiserver/V40__add_notify_detail_content_type.sql new file mode 100644 index 0000000..30b000c --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V40__add_notify_detail_content_type.sql @@ -0,0 +1,15 @@ +ALTER TABLE `notify_detail` + CHANGE COLUMN `data` `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容', + ADD COLUMN `content_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容类型' AFTER `content`; + +ALTER TABLE `sms_queue` + CHANGE COLUMN `data` `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容'; + +ALTER TABLE `mail_queue` + CHANGE COLUMN `data` `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容'; + +ALTER TABLE `http_queue` + CHANGE COLUMN `data` `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容'; + +ALTER TABLE `wechat_queue` + CHANGE COLUMN `data` `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容'; -- 2.54.0 From 0239697a2b19eee14f82fb24a26c47ba97b85db9 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 25 Aug 2026 17:57:54 +0800 Subject: [PATCH 2/6] fix customer invited count --- .../api/modules/gao/mapper/GaoCustomerMapper.java | 8 ++++++++ .../implement/GaoCustomerServiceImplement.java | 12 +++++------- ...e.sql => V41__add_notify_detail_content_type.sql} | 0 .../V42__fix_gao_customer_invited_count.sql | 9 +++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) rename src/main/resources/db/migration/timiserver/{V40__add_notify_detail_content_type.sql => V41__add_notify_detail_content_type.sql} (100%) create mode 100644 src/main/resources/db/migration/timiserver/V42__fix_gao_customer_invited_count.sql diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java index 22824b9..367ac32 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java @@ -6,6 +6,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerPage; import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; import java.util.Collection; import java.util.List; @@ -48,4 +49,11 @@ public interface GaoCustomerMapper extends BaseMapper { /// @param page 查询条件 /// @return 客户列表 List selectByQuery(GaoCustomerPage page); + + /// 原子调整邀请客户数 + /// + /// @param customerId 客户 ID + /// @param delta 变化量 + @Update("UPDATE `gao_customer` SET `invited_count` = GREATEST(IFNULL(`invited_count`, 0) + #{delta}, 0), `updated_at` = " + UNIX_TIME + " WHERE `id` = #{customerId}" + NOT_DELETE) + void updateInvitedCountDelta(@Param("customerId") String customerId, @Param("delta") Long delta); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index 98ad6d5..a3f45e7 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -117,12 +117,10 @@ public class GaoCustomerServiceImplement extends AbstractEntityService Date: Tue, 25 Aug 2026 22:54:55 +0800 Subject: [PATCH 3/6] keep alive attachment variant --- .../mapper/AttachmentVariantMapper.java | 8 +++++ .../AttachmentVariantServiceImplement.java | 29 +++++++++++++++++-- .../common/AttachmentVariantMapper.xml | 7 +++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentVariantMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentVariantMapper.java index a4f2ab5..1e84ba3 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentVariantMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentVariantMapper.java @@ -71,6 +71,14 @@ public interface AttachmentVariantMapper { /// @param variant 变体 void insert(AttachmentVariant variant); + /// 延长缩略图变体的过期时间 + /// + /// @param id 变体 ID + /// @param now 当前时间 + /// @param expireAt 新的过期时间 + /// @return 更新记录数 + int updateExpireAt(@Param("id") String id, @Param("now") long now, @Param("expireAt") long expireAt); + /// 删除变体 /// /// @param id 变体 ID diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentVariantServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentVariantServiceImplement.java index a6520df..fe0bb3e 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentVariantServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentVariantServiceImplement.java @@ -112,7 +112,10 @@ public class AttachmentVariantServiceImplement implements AttachmentVariantServi now ); if (cached != null) { - return cached; + Attachment refreshed = refreshCachedThumb(cached); + if (refreshed != null) { + return refreshed; + } } String lockKey = buildThumbLockKey(source, width, height, fitMode); @@ -131,7 +134,10 @@ public class AttachmentVariantServiceImplement implements AttachmentVariantServi Time.now() ); if (cached != null) { - return cached; + Attachment refreshed = refreshCachedThumb(cached); + if (refreshed != null) { + return refreshed; + } } AttachmentVariant oldVariant = variantMapper.selectByKey( source.getId(), @@ -197,7 +203,10 @@ public class AttachmentVariantServiceImplement implements AttachmentVariantServi Time.now() ); if (concurrent != null) { - return concurrent; + Attachment refreshed = refreshCachedThumb(concurrent); + if (refreshed != null) { + return refreshed; + } } throw e; } @@ -223,6 +232,20 @@ public class AttachmentVariantServiceImplement implements AttachmentVariantServi } } + /// 延长已命中缩略图缓存的有效期 + /// + /// @param cached 已命中的缩略图 + /// @return 更新成功时返回刷新后的缩略图,记录已不存在或已过期时返回 `null` + private Attachment refreshCachedThumb(Attachment cached) { + long now = Time.now(); + long expireAt = now + THUMB_CACHE_TTL; + if (!(0 < variantMapper.updateExpireAt(cached.getId(), now, expireAt))) { + return null; + } + cached.setDestroyAt(expireAt); + return cached; + } + private String buildThumbLockKey(Attachment source, int width, int height, ThumbnailFitMode fitMode) { return "%s%s:%s:%s:%s:%s".formatted( THUMB_LOCK_KEY_PREFIX, diff --git a/src/main/resources/mapper/timi-server/common/AttachmentVariantMapper.xml b/src/main/resources/mapper/timi-server/common/AttachmentVariantMapper.xml index 625fd59..9c5a4ee 100644 --- a/src/main/resources/mapper/timi-server/common/AttachmentVariantMapper.xml +++ b/src/main/resources/mapper/timi-server/common/AttachmentVariantMapper.xml @@ -98,6 +98,13 @@ ) + + UPDATE `attachment_variant` + SET `expire_at` = #{expireAt} + WHERE `id` = #{id} + AND `expire_at` > #{now} + + DELETE FROM `attachment_variant` WHERE `id` = #{id} -- 2.54.0 From d747a0fdaeb4f343ab472a89037c595680fadaf5 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 25 Aug 2026 22:55:18 +0800 Subject: [PATCH 4/6] add gao user notify --- .../common/controller/NotifyController.java | 91 +++++++++--- .../common/mapper/NotifyDetailMapper.java | 16 +- .../modules/common/mapper/NotifyMapper.java | 25 ++++ .../modules/common/service/NotifyService.java | 29 ++++ .../NotifyDetailServiceImplement.java | 7 +- .../implement/NotifyServiceImplement.java | 58 +++++++- .../common/vo/NotifyManagementItem.java | 57 +++++++ .../gao/controller/GaoNotifyController.java | 140 ++++++++++++++++++ .../api/modules/gao/mapper/GaoUserMapper.java | 5 + .../modules/gao/service/GaoUserService.java | 9 ++ .../implement/GaoUserServiceImplement.java | 14 ++ .../V43__add_notify_management_indexes.sql | 6 + .../timi-server/common/NotifyMapper.xml | 82 ++++++++++ .../mapper/timi-server/gao/GaoUserMapper.xml | 15 ++ 14 files changed, 520 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/NotifyManagementItem.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoNotifyController.java create mode 100644 src/main/resources/db/migration/timiserver/V43__add_notify_management_indexes.sql create mode 100644 src/main/resources/mapper/timi-server/common/NotifyMapper.xml create mode 100644 src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java b/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java index 8621182..8e11c24 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java @@ -8,6 +8,7 @@ import com.imyeyu.api.modules.common.entity.NotifyDetail; import com.imyeyu.api.modules.common.service.NotifyDetailService; import com.imyeyu.api.modules.common.service.NotifyService; import com.imyeyu.api.modules.common.service.SmsService; +import com.imyeyu.api.modules.common.vo.NotifyManagementItem; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserService; @@ -70,25 +71,31 @@ public class NotifyController { @RequiredToken @RequestMapping("/detail") public Notify detail(@RequestParam String id) { - Notify notify = service.get(id); - { - List detailList = detailService.listByNotifyId(id); - Set userIdSet = detailList.stream() - .filter(item -> item.getMsgType() == NotifyDetail.MsgType.INTERNAL) - .flatMap(item -> Stream.of(item.getSendFrom(), item.getSendTo())) - .collect(Collectors.toSet()); - Map userMap = userService.mapByIdList(userIdSet); - for (NotifyDetail detail : detailList) { - if (TimiJava.isNotEmpty(detail.getSendFrom())) { - detail.setFromUser(userMap.get(detail.getSendFrom())); - } - if (TimiJava.isNotEmpty(detail.getSendTo())) { - detail.setToUser(userMap.get(detail.getSendTo())); - } - } - notify.setDetailList(detailList); - } - return notify; + return fillDetailList(service.get(id)); + } + + /// 查询管理端通知列表 + /// + /// @param page 分页参数 + /// @return 管理端通知列表 + @JsonView(ResponseView.Admin.class) + @RequireCorePermission(CorePermissionCode.NOTIFY_READ) + @RequiredToken + @PostMapping("/management/list") + public PageResult managementList(@RequestBody(required = false) Page page) { + return service.pageManagement(page, userLoginService.getRequireLoginUserId()); + } + + /// 查询管理端通知详情及收件人 + /// + /// @param id 通知 ID + /// @return 通知详情及收件人 + @JsonView(ResponseView.Admin.class) + @RequireCorePermission(CorePermissionCode.NOTIFY_READ) + @RequiredToken + @PostMapping("/management/detail") + public Notify managementDetail(@RequestParam String id) { + return fillDetailList(service.getManagement(id, userLoginService.getRequireLoginUserId())); } /// 删除通知及其通知详情 @@ -111,6 +118,26 @@ public class NotifyController { service.cancel(id); } + /// 删除管理端通知及其通知详情 + /// + /// @param id 通知 ID + @RequireCorePermission(CorePermissionCode.NOTIFY_DELETE) + @RequiredToken + @PostMapping("/management/delete") + public void managementDelete(@RequestParam String id) { + service.deleteManagement(id, userLoginService.getRequireLoginUserId()); + } + + /// 取消管理端尚未成功发送的通知 + /// + /// @param id 通知 ID + @RequireCorePermission(CorePermissionCode.NOTIFY_CANCEL) + @RequiredToken + @PostMapping("/management/cancel") + public void managementCancel(@RequestParam String id) { + service.cancelManagement(id, userLoginService.getRequireLoginUserId()); + } + @AOPLog @CaptchaValid @RequestRateLimit(value = 1, inSeconds = 59) @@ -186,4 +213,30 @@ public class NotifyController { public void markAllSiteRead() { detailService.markAllSiteRead(userLoginService.getRequireLoginUserId()); } + + /// 回填通知详情中的用户信息 + /// + /// @param notify 通知 + /// @return 已回填用户信息的通知 + private Notify fillDetailList(Notify notify) { + if (notify == null) { + return null; + } + List detailList = detailService.listByNotifyId(notify.getId()); + Set userIdSet = detailList.stream() + .filter(item -> item.getMsgType() == NotifyDetail.MsgType.INTERNAL) + .flatMap(item -> Stream.of(item.getSendFrom(), item.getSendTo())) + .collect(Collectors.toSet()); + Map userMap = userService.mapByIdList(userIdSet); + for (NotifyDetail detail : detailList) { + if (TimiJava.isNotEmpty(detail.getSendFrom())) { + detail.setFromUser(userMap.get(detail.getSendFrom())); + } + if (TimiJava.isNotEmpty(detail.getSendTo())) { + detail.setToUser(userMap.get(detail.getSendTo())); + } + } + notify.setDetailList(detailList); + return notify; + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java index d17daf5..c7d4103 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java @@ -17,34 +17,34 @@ import java.util.List; public interface NotifyDetailMapper extends BaseMapper { /** 查询用户的站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} " + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") + @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") List selectSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); /** 查询用户的未读站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `read_at` IS NULL " + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") + @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") List selectUnreadSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); /** 统计用户的站内通知数量 */ - @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} " + NOT_DELETE) + @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE) long countSiteByUserId(@Param("userId") String userId); /** 统计用户的未读站内通知数量 */ - @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `read_at` IS NULL " + NOT_DELETE) + @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) long countUnreadSiteByUserId(@Param("userId") String userId); /** 查询用户拥有的站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} " + NOT_DELETE + " LIMIT 1") + @Select("SELECT * FROM `notify_detail` WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE + " LIMIT 1") NotifyDetail selectSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); /** 删除用户的站内通知 */ - @Update("UPDATE `notify_detail` SET `deleted_at` = " + UNIX_TIME + ", `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} " + NOT_DELETE) + @Update("UPDATE `notify_detail` SET `deleted_at` = " + UNIX_TIME + ", `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE) int deleteSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); /** 标记站内通知已读 */ - @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `read_at` IS NULL " + NOT_DELETE) + @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) int markSiteRead(@Param("detailId") String detailId, @Param("userId") String userId, @Param("readAt") long readAt); /** 标记用户全部站内通知已读 */ - @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `read_at` IS NULL " + NOT_DELETE) + @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) int markAllSiteRead(@Param("userId") String userId, @Param("readAt") long readAt); } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java index 6126211..10ff654 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java @@ -1,8 +1,12 @@ package com.imyeyu.api.modules.common.mapper; import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.api.modules.common.vo.NotifyManagementItem; +import org.apache.ibatis.annotations.Param; import com.imyeyu.spring.mapper.BaseMapper; +import java.util.List; + /** * 通知 Mapper * @@ -10,4 +14,25 @@ import com.imyeyu.spring.mapper.BaseMapper; * @since 2026-05-13 14:46 */ public interface NotifyMapper extends BaseMapper { + + /// 分页查询管理端通知 + /// + /// @param userId 管理员用户 ID + /// @param offset 分页偏移量 + /// @param limit 分页大小 + /// @return 通知列表 + List selectManagementPage(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); + + /// 统计管理端通知数量 + /// + /// @param userId 管理员用户 ID + /// @return 通知数量 + long countManagement(@Param("userId") String userId); + + /// 查询管理员可管理的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 管理员用户 ID + /// @return 通知 + Notify selectManagement(@Param("notifyId") String notifyId, @Param("userId") String userId); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java index 5f4da6a..4b41082 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java @@ -1,6 +1,9 @@ package com.imyeyu.api.modules.common.service; import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.api.modules.common.vo.NotifyManagementItem; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; /** @@ -11,6 +14,32 @@ import com.imyeyu.spring.service.BaseService; */ public interface NotifyService extends BaseService { + /// 分页查询当前用户创建的通知 + /// + /// @param page 分页参数 + /// @param userId 用户 ID + /// @return 管理端通知列表 + PageResult pageManagement(Page page, String userId); + + /// 查询当前用户可管理的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + /// @return 通知 + Notify getManagement(String notifyId, String userId); + + /// 删除当前用户创建的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + void deleteManagement(String notifyId, String userId); + + /// 取消当前用户创建的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + void cancelManagement(String notifyId, String userId); + /// 取消通知中尚未成功发送的通知详情 /// /// @param notifyId 通知 ID diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java index c3dc2ee..8fc55c9 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java @@ -64,9 +64,10 @@ public class NotifyDetailServiceImplement extends AbstractEntityService pageManagement(Page page, String userId) { + TimiException.required(userId, "未找到用户 ID"); + Page validPage = TimiJava.defaultIfNull(page, new Page<>()); + PageResult result = new PageResult<>(); + result.setTotal(mapper.countManagement(userId)); + result.setList(mapper.selectManagementPage(userId, validPage.getOffset(), validPage.getLimit())); + return result; + } + + /// 查询当前用户可管理的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + /// @return 通知 + @Override + public Notify getManagement(String notifyId, String userId) { + TimiException.required(notifyId, "未找到通知 ID"); + TimiException.required(userId, "未找到用户 ID"); + Notify notify = mapper.selectManagement(notifyId, userId); + TimiException.required(notify, "未找到可管理的通知"); + return notify; + } + + /// 删除当前用户创建的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + @Override + public void deleteManagement(String notifyId, String userId) { + getManagement(notifyId, userId); + delete(notifyId); + } + + /// 取消当前用户创建的通知 + /// + /// @param notifyId 通知 ID + /// @param userId 用户 ID + @Override + public void cancelManagement(String notifyId, String userId) { + getManagement(notifyId, userId); + cancel(notifyId); + } + /// 删除通知并级联软删除通知详情 /// /// @param notifyId 通知 ID diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/NotifyManagementItem.java b/src/main/java/com/imyeyu/api/modules/common/vo/NotifyManagementItem.java new file mode 100644 index 0000000..6bebf4a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/NotifyManagementItem.java @@ -0,0 +1,57 @@ +package com.imyeyu.api.modules.common.vo; + +import com.imyeyu.api.modules.common.entity.NotifyDetail; +import lombok.Data; + +/// 管理端通知列表项 +/// +/// @author 夜雨 +/// @since 2026-08-25 +@Data +public class NotifyManagementItem { + + /// 通知 ID + private String id; + + /// 创建者用户 ID + private String createdBy; + + /// 创建时间 + private Long createdAt; + + /// 更新时间 + private Long updatedAt; + + /// 通知标题 + private String subject; + + /// 通知内容 + private String content; + + /// 内容类型 + private NotifyDetail.ContentType contentType; + + /// 发送时间 + private Long sendAt; + + /// 接收用户数量 + private Long recipientCount; + + /// 已读用户数量 + private Long readCount; + + /// 等待发送数量 + private Long waitingCount; + + /// 已发送数量 + private Long remindedCount; + + /// 超时数量 + private Long timeoutCount; + + /// 已取消数量 + private Long cancelCount; + + /// 失败数量 + private Long failCount; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoNotifyController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoNotifyController.java new file mode 100644 index 0000000..cf1ccae --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoNotifyController.java @@ -0,0 +1,140 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.PermissionMatchMode; +import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.api.modules.common.entity.NotifyDetail; +import com.imyeyu.api.modules.common.service.NotifyService; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; +import com.imyeyu.api.modules.gao.service.GaoUserService; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.annotation.RequiredToken; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/// GAO 通知接口 +/// +/// 这里的通知接收用户必须属于当前操作人的 GAO 用户范围,不能直接复用通用通知创建接口。 +/// +/// @author Codex +/// @since 2026-08-25 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/notify") +public class GaoNotifyController { + + private final GaoUserService gaoUserService; + private final GaoStoreScopeService storeScopeService; + private final NotifyService notifyService; + private final UserService userService; + + /// 查询可作为通知接收人的 GAO 用户 + /// + /// 门店管理范围可以查询全部门店或指定门店,店长只能查询当前门店。 + /// + /// @param page 分页参数 + /// @return 可发送用户分页结果 + @JsonView(ResponseView.Public.class) + @RequiredToken + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.NOTIFY_CREATE) + @RequireGaoPermission(value = {GaoPermissionCode.USER_READ, GaoPermissionCode.STORE_READ}, mode = PermissionMatchMode.ANY) + @PostMapping("/recipient/list") + public PageResult recipientList(@RequestBody Page page) { + TimiException.required(page, "未找到分页参数"); + GaoUser example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser()); + example.setEnabled(true); + if (storeScopeService.hasGlobalScope()) { + if (TimiJava.isNotEmpty(example.getStoreId())) { + storeScopeService.checkStoreId(example.getStoreId()); + } + } else { + example.setStoreId(storeScopeService.resolveStoreId(example.getStoreId())); + } + page.setEqualsExample(example); + + PageResult result = gaoUserService.page(page); + fillUser(result.getList()); + return result; + } + + /// 创建 GAO 站内通知 + /// + /// 只允许发送站内通知,并逐个校验接收用户是否属于当前操作人的门店范围。 + /// + /// @param notify 通知及通知详情 + /// @return 创建后的通知 + @RequiredToken + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.NOTIFY_CREATE) + @RequireGaoPermission(value = {GaoPermissionCode.USER_READ, GaoPermissionCode.STORE_READ}, mode = PermissionMatchMode.ANY) + @PostMapping("/create") + public Notify create(@RequestBody Notify notify) { + checkRecipientBoundary(notify); + notifyService.create(notify); + return notify; + } + + /// 校验通知接收用户边界 + /// + /// @param notify 通知 + private void checkRecipientBoundary(Notify notify) { + TimiException.required(notify, "未找到通知"); + List detailList = notify.getDetailList(); + TimiException.required(detailList, "未找到通知详情列表"); + TimiException.requiredTrue(!detailList.isEmpty(), "通知详情列表不能为空"); + + Set recipientIdSet = detailList.stream().map(detail -> { + TimiException.required(detail, "未找到通知详情"); + TimiException.requiredTrue(detail.getMsgType() == NotifyDetail.MsgType.INTERNAL, "GAO 通知仅支持站内通知"); + TimiException.required(detail.getSendTo(), "未找到通知接收用户 ID"); + if (detail.getSendAt() != null) { + TimiException.requiredTrue(Time.now() < detail.getSendAt(), "定时发送时间必须晚于当前时间"); + } + return detail.getSendTo(); + }).collect(Collectors.toSet()); + TimiException.requiredTrue(recipientIdSet.size() == detailList.size(), "通知接收用户不能重复"); + + Map gaoUserMap = gaoUserService.mapByUserIdList(recipientIdSet); + TimiException.requiredTrue(gaoUserMap.size() == recipientIdSet.size(), "包含无效的通知接收用户"); + for (GaoUser gaoUser : gaoUserMap.values()) { + TimiException.requiredTrue(Boolean.TRUE.equals(gaoUser.getEnabled()), "通知接收用户已停用"); + storeScopeService.checkStoreId(gaoUser.getStoreId()); + } + } + + /// 回填 GAO 用户的核心用户资料 + /// + /// @param gaoUserList GAO 用户列表 + private void fillUser(List gaoUserList) { + if (TimiJava.isEmpty(gaoUserList)) { + return; + } + List userIdList = gaoUserList.stream().map(GaoUser::getUserId).toList(); + Map userMap = userService.mapByIdList(userIdList); + for (GaoUser gaoUser : gaoUserList) { + gaoUser.setUser(userMap.get(gaoUser.getUserId())); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java index dc94f6e..21e4662 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java @@ -3,10 +3,15 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.spring.mapper.BaseMapper; +import java.util.Collection; +import java.util.List; + /// /// GAO 用户 Mapper /// /// @author Codex /// @since 2026-07-30 public interface GaoUserMapper extends BaseMapper { + + List selectByUserIdList(Collection userIdList); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java index 61a7da2..327c004 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java @@ -3,6 +3,9 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.spring.service.BaseService; +import java.util.Collection; +import java.util.Map; + /// /// GAO 用户服务 /// @@ -19,4 +22,10 @@ public interface GaoUserService extends BaseService { /// /// @param gaoUser GAO 用户,必须包含 ID 和目标门店 ID void transferStore(GaoUser gaoUser); + + /// 根据核心用户 ID 映射 GAO 用户 + /// + /// @param userIdList 核心用户 ID 列表 + /// @return GAO 用户映射 + Map mapByUserIdList(Collection userIdList); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java index 6ce98b7..4affc95 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java @@ -19,6 +19,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + /// /// GAO 用户服务实现 /// @@ -160,4 +165,13 @@ public class GaoUserServiceImplement extends AbstractEntityService mapByUserIdList(Collection userIdList) { + if (TimiJava.isEmpty(userIdList)) { + return Map.of(); + } + return mapper.selectByUserIdList(userIdList).stream() + .collect(Collectors.toMap(GaoUser::getUserId, Function.identity())); + } } diff --git a/src/main/resources/db/migration/timiserver/V43__add_notify_management_indexes.sql b/src/main/resources/db/migration/timiserver/V43__add_notify_management_indexes.sql new file mode 100644 index 0000000..4c922e0 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V43__add_notify_management_indexes.sql @@ -0,0 +1,6 @@ +ALTER TABLE `notify` + ADD INDEX `idx_created_by_created`(`created_by` ASC, `created_at` DESC) USING BTREE; + +ALTER TABLE `notify_detail` + ADD INDEX `idx_notify_send_from_deleted`(`notify_id` ASC, `send_from` ASC, `deleted_at` ASC) USING BTREE, + ADD INDEX `idx_internal_user_send_at`(`msg_type` ASC, `send_to` ASC, `send_at` ASC, `deleted_at` ASC) USING BTREE; diff --git a/src/main/resources/mapper/timi-server/common/NotifyMapper.xml b/src/main/resources/mapper/timi-server/common/NotifyMapper.xml new file mode 100644 index 0000000..0e518c9 --- /dev/null +++ b/src/main/resources/mapper/timi-server/common/NotifyMapper.xml @@ -0,0 +1,82 @@ + + + + + + + + + diff --git a/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml new file mode 100644 index 0000000..8d24732 --- /dev/null +++ b/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml @@ -0,0 +1,15 @@ + + + + + -- 2.54.0 From 2b5be16ad70d8723ee12fd0f1caeec83ac8894e8 Mon Sep 17 00:00:00 2001 From: Timi Date: Sat, 29 Aug 2026 00:49:50 +0800 Subject: [PATCH 5/6] fix notify delete --- .../common/controller/NotifyController.java | 4 +-- .../modules/common/entity/NotifyDetail.java | 5 +++ .../common/mapper/NotifyDetailMapper.java | 22 +++++++------ .../common/service/NotifyDetailService.java | 4 +-- .../NotifyDetailServiceImplement.java | 6 ++-- .../gao/controller/GaoNotifyController.java | 32 +++++++++++++++---- .../api/modules/gao/mapper/GaoUserMapper.java | 6 ++++ .../modules/gao/service/GaoUserService.java | 8 +++++ .../implement/GaoUserServiceImplement.java | 10 ++++++ .../V44__add_notify_detail_hidden_at.sql | 4 +++ .../mapper/timi-server/gao/GaoUserMapper.xml | 22 +++++++++++++ 11 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 src/main/resources/db/migration/timiserver/V44__add_notify_detail_hidden_at.sql diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java b/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java index 8e11c24..605e2f8 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/NotifyController.java @@ -176,14 +176,14 @@ public class NotifyController { return detail; } - /// 删除当前用户的一条站内通知 + /// 隐藏当前用户的一条站内通知 /// /// @param id 通知详情 ID @RequireCorePermission(CorePermissionCode.NOTIFY_READ) @RequiredToken @PostMapping("/internal/delete") public void deleteInternal(@RequestParam String id) { - detailService.deleteInternalByUserId(id, userLoginService.getRequireLoginUserId()); + detailService.hideInternalByUserId(id, userLoginService.getRequireLoginUserId()); } /// 查询当前用户未读通知数量 diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java index ac1fdce..700a5e5 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.common.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.JsonNode; import com.imyeyu.api.TimiServerAPI; @@ -157,6 +158,10 @@ public class NotifyDetail extends UUIDEntity { @JsonView(ResponseView.Public.class) private Long readAt; + /// 收件人隐藏时间,仅站内通知生效 + @JsonIgnore + private Long hiddenAt; + @Transient protected Notify notify; diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java index c7d4103..85515a3 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java @@ -16,35 +16,37 @@ import java.util.List; */ public interface NotifyDetailMapper extends BaseMapper { + String NOT_HIDDEN = " AND `hidden_at` IS NULL"; + /** 查询用户的站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") + @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_HIDDEN + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") List selectSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); /** 查询用户的未读站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") + @Select("SELECT * FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_HIDDEN + NOT_DELETE + " ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}") List selectUnreadSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); /** 统计用户的站内通知数量 */ - @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE) + @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_HIDDEN + NOT_DELETE) long countSiteByUserId(@Param("userId") String userId); /** 统计用户的未读站内通知数量 */ - @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) + @Select("SELECT COUNT(*) FROM `notify_detail` WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_HIDDEN + NOT_DELETE) long countUnreadSiteByUserId(@Param("userId") String userId); /** 查询用户拥有的站内通知 */ - @Select("SELECT * FROM `notify_detail` WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE + " LIMIT 1") + @Select("SELECT * FROM `notify_detail` WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_HIDDEN + NOT_DELETE + " LIMIT 1") NotifyDetail selectSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); - /** 删除用户的站内通知 */ - @Update("UPDATE `notify_detail` SET `deleted_at` = " + UNIX_TIME + ", `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_DELETE) - int deleteSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); + /** 隐藏用户的站内通知 */ + @Update("UPDATE `notify_detail` SET `hidden_at` = " + UNIX_TIME + ", `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + NOT_HIDDEN + NOT_DELETE) + int hideSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); /** 标记站内通知已读 */ - @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) + @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `id` = #{detailId} AND `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_HIDDEN + NOT_DELETE) int markSiteRead(@Param("detailId") String detailId, @Param("userId") String userId, @Param("readAt") long readAt); /** 标记用户全部站内通知已读 */ - @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_DELETE) + @Update("UPDATE `notify_detail` SET `read_at` = #{readAt}, `updated_at` = " + UNIX_TIME + " WHERE `msg_type` = 'INTERNAL' AND `send_to` = #{userId} AND `send_at` < " + UNIX_TIME + " AND `read_at` IS NULL " + NOT_HIDDEN + NOT_DELETE) int markAllSiteRead(@Param("userId") String userId, @Param("readAt") long readAt); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java b/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java index 0380a52..a6ee10e 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java @@ -41,11 +41,11 @@ public interface NotifyDetailService extends BaseService { /// @return 通知详情 NotifyDetail getInternalByUserId(String detailId, String userId); - /// 删除当前用户拥有的站内通知详情 + /// 隐藏当前用户拥有的站内通知详情 /// /// @param detailId 通知详情 ID /// @param userId 用户 ID - void deleteInternalByUserId(String detailId, String userId); + void hideInternalByUserId(String detailId, String userId); /// 查询用户未读站内通知数量 /// diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java index 8fc55c9..8e5c05b 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java @@ -202,16 +202,16 @@ public class NotifyDetailServiceImplement extends AbstractEntityService recipientList(@RequestBody Page page) { TimiException.required(page, "未找到分页参数"); GaoUser example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser()); - example.setEnabled(true); if (storeScopeService.hasGlobalScope()) { if (TimiJava.isNotEmpty(example.getStoreId())) { storeScopeService.checkStoreId(example.getStoreId()); @@ -74,14 +78,14 @@ public class GaoNotifyController { } page.setEqualsExample(example); - PageResult result = gaoUserService.page(page); + PageResult result = gaoUserService.pageRecipients(page); fillUser(result.getList()); return result; } /// 创建 GAO 站内通知 /// - /// 只允许发送站内通知,并逐个校验接收用户是否属于当前操作人的门店范围。 + /// 只允许发送站内通知,并逐个校验接收用户是否属于当前操作人的门店范围;当前用户可以接收自己发送的通知。 /// /// @param notify 通知及通知详情 /// @return 创建后的通知 @@ -116,8 +120,12 @@ public class GaoNotifyController { }).collect(Collectors.toSet()); TimiException.requiredTrue(recipientIdSet.size() == detailList.size(), "通知接收用户不能重复"); - Map gaoUserMap = gaoUserService.mapByUserIdList(recipientIdSet); - TimiException.requiredTrue(gaoUserMap.size() == recipientIdSet.size(), "包含无效的通知接收用户"); + String loginUserId = userLoginService.getRequireLoginUserId(); + Set gaoRecipientIdSet = recipientIdSet.stream() + .filter(recipientId -> !loginUserId.equals(recipientId)) + .collect(Collectors.toSet()); + Map gaoUserMap = gaoUserService.mapByUserIdList(gaoRecipientIdSet); + TimiException.requiredTrue(gaoUserMap.size() == gaoRecipientIdSet.size(), "包含无效的通知接收用户"); for (GaoUser gaoUser : gaoUserMap.values()) { TimiException.requiredTrue(Boolean.TRUE.equals(gaoUser.getEnabled()), "通知接收用户已停用"); storeScopeService.checkStoreId(gaoUser.getStoreId()); @@ -133,8 +141,18 @@ public class GaoNotifyController { } List userIdList = gaoUserList.stream().map(GaoUser::getUserId).toList(); Map userMap = userService.mapByIdList(userIdList); + Map> attachmentMap = attachmentService.mapByBizIdList( + Attachment.BizType.USER, + userIdList, + User.AttachType.AVATAR.toString() + ); for (GaoUser gaoUser : gaoUserList) { - gaoUser.setUser(userMap.get(gaoUser.getUserId())); + User user = userMap.get(gaoUser.getUserId()); + if (user == null) { + continue; + } + user.setAttachmentList(attachmentMap.getOrDefault(user.getId(), List.of())); + gaoUser.setUser(user); } } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java index 21e4662..9ad780e 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java @@ -1,7 +1,9 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; import java.util.Collection; import java.util.List; @@ -14,4 +16,8 @@ import java.util.List; public interface GaoUserMapper extends BaseMapper { List selectByUserIdList(Collection userIdList); + + List selectRecipientPage(@Param("page") Page page); + + long countRecipients(@Param("page") Page page); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java index 327c004..538d7a1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java @@ -1,6 +1,8 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; import java.util.Collection; @@ -28,4 +30,10 @@ public interface GaoUserService extends BaseService { /// @param userIdList 核心用户 ID 列表 /// @return GAO 用户映射 Map mapByUserIdList(Collection userIdList); + + /// 查询可作为通知接收人的启用用户 + /// + /// @param page 分页参数 + /// @return 接收用户分页结果 + PageResult pageRecipients(Page page); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java index 4affc95..187ecec 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java @@ -13,6 +13,8 @@ import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -174,4 +176,12 @@ public class GaoUserServiceImplement extends AbstractEntityService pageRecipients(Page page) { + PageResult result = new PageResult<>(); + result.setTotal(mapper.countRecipients(page)); + result.setList(mapper.selectRecipientPage(page)); + return result; + } } diff --git a/src/main/resources/db/migration/timiserver/V44__add_notify_detail_hidden_at.sql b/src/main/resources/db/migration/timiserver/V44__add_notify_detail_hidden_at.sql new file mode 100644 index 0000000..c7b7361 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V44__add_notify_detail_hidden_at.sql @@ -0,0 +1,4 @@ +ALTER TABLE `notify_detail` + ADD COLUMN `hidden_at` BIGINT(20) COMMENT '收件人隐藏时间' AFTER `read_at`, + DROP INDEX `idx_internal_user_send_at`, + ADD INDEX `idx_internal_user_send_at`(`msg_type` ASC, `send_to` ASC, `hidden_at` ASC, `send_at` ASC, `deleted_at` ASC) USING BTREE; diff --git a/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml index 8d24732..18e84a3 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml @@ -12,4 +12,26 @@ AND `deleted_at` IS NULL + + + + -- 2.54.0 From 35ec38ed9c18551056ed95b1458c6cfbd1613eee Mon Sep 17 00:00:00 2001 From: Timi Date: Sat, 29 Aug 2026 00:50:26 +0800 Subject: [PATCH 6/6] v1.0.26 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f63f313..4b26d3f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.imyeyu.timiserverapi TimiServerAPI - 1.0.25 + 1.0.26 jar TimiServerAPI imyeyu.com API -- 2.54.0