fix notify delete

This commit is contained in:
Timi
2026-08-29 00:49:50 +08:00
parent d747a0fdae
commit 2b5be16ad7
11 changed files with 99 additions and 24 deletions
@@ -176,14 +176,14 @@ public class NotifyController {
return detail; return detail;
} }
/// 删除当前用户的一条站内通知 /// 隐藏当前用户的一条站内通知
/// ///
/// @param id 通知详情 ID /// @param id 通知详情 ID
@RequireCorePermission(CorePermissionCode.NOTIFY_READ) @RequireCorePermission(CorePermissionCode.NOTIFY_READ)
@RequiredToken @RequiredToken
@PostMapping("/internal/delete") @PostMapping("/internal/delete")
public void deleteInternal(@RequestParam String id) { public void deleteInternal(@RequestParam String id) {
detailService.deleteInternalByUserId(id, userLoginService.getRequireLoginUserId()); detailService.hideInternalByUserId(id, userLoginService.getRequireLoginUserId());
} }
/// 查询当前用户未读通知数量 /// 查询当前用户未读通知数量
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
@@ -157,6 +158,10 @@ public class NotifyDetail extends UUIDEntity {
@JsonView(ResponseView.Public.class) @JsonView(ResponseView.Public.class)
private Long readAt; private Long readAt;
/// 收件人隐藏时间,仅站内通知生效
@JsonIgnore
private Long hiddenAt;
@Transient @Transient
protected Notify notify; protected Notify notify;
@@ -16,35 +16,37 @@ import java.util.List;
*/ */
public interface NotifyDetailMapper extends BaseMapper<NotifyDetail, String> { public interface NotifyDetailMapper extends BaseMapper<NotifyDetail, String> {
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<NotifyDetail> selectSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); List<NotifyDetail> 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<NotifyDetail> selectUnreadSiteByUserId(@Param("userId") String userId, @Param("offset") long offset, @Param("limit") long limit); List<NotifyDetail> 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); 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); 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); 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) @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 deleteSiteByIdAndUserId(@Param("detailId") String detailId, @Param("userId") String userId); 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); 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); int markAllSiteRead(@Param("userId") String userId, @Param("readAt") long readAt);
} }
@@ -41,11 +41,11 @@ public interface NotifyDetailService extends BaseService<NotifyDetail, String> {
/// @return 通知详情 /// @return 通知详情
NotifyDetail getInternalByUserId(String detailId, String userId); NotifyDetail getInternalByUserId(String detailId, String userId);
/// 删除当前用户拥有的站内通知详情 /// 隐藏当前用户拥有的站内通知详情
/// ///
/// @param detailId 通知详情 ID /// @param detailId 通知详情 ID
/// @param userId 用户 ID /// @param userId 用户 ID
void deleteInternalByUserId(String detailId, String userId); void hideInternalByUserId(String detailId, String userId);
/// 查询用户未读站内通知数量 /// 查询用户未读站内通知数量
/// ///
@@ -202,16 +202,16 @@ public class NotifyDetailServiceImplement extends AbstractEntityService<NotifyDe
} }
} }
/// 删除当前用户拥有的站内通知详情 /// 隐藏当前用户拥有的站内通知详情
/// ///
/// @param detailId 通知详情 ID /// @param detailId 通知详情 ID
/// @param userId 用户 ID /// @param userId 用户 ID
@Override @Override
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
public void deleteInternalByUserId(String detailId, String userId) { public void hideInternalByUserId(String detailId, String userId) {
TimiException.required(detailId, "未找到通知详情 ID"); TimiException.required(detailId, "未找到通知详情 ID");
TimiException.required(userId, "未找到用户 ID"); TimiException.required(userId, "未找到用户 ID");
int affected = mapper.deleteSiteByIdAndUserId(detailId, userId); int affected = mapper.hideSiteByIdAndUserId(detailId, userId);
TimiException.requiredTrue(0 < affected, "未找到站内通知"); TimiException.requiredTrue(0 < affected, "未找到站内通知");
} }
@@ -4,8 +4,10 @@ import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.annotation.RequireCorePermission; import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.bean.CorePermissionCode; import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.PermissionMatchMode; import com.imyeyu.api.bean.PermissionMatchMode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.Notify; import com.imyeyu.api.modules.common.entity.Notify;
import com.imyeyu.api.modules.common.entity.NotifyDetail; import com.imyeyu.api.modules.common.entity.NotifyDetail;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.NotifyService; import com.imyeyu.api.modules.common.service.NotifyService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
@@ -13,6 +15,7 @@ import com.imyeyu.api.modules.gao.entity.GaoUser;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.service.GaoUserService; import com.imyeyu.api.modules.gao.service.GaoUserService;
import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
@@ -35,7 +38,7 @@ import java.util.stream.Collectors;
/// GAO 通知接口 /// GAO 通知接口
/// ///
/// 这里的通知接收用户必须属于当前操作人的 GAO 用户范围,不能直接复用通用通知创建接口 /// 这里的通知接收用户必须属于当前操作人的 GAO 用户范围,当前登录用户本人除外
/// ///
/// @author Codex /// @author Codex
/// @since 2026-08-25 /// @since 2026-08-25
@@ -47,7 +50,9 @@ public class GaoNotifyController {
private final GaoUserService gaoUserService; private final GaoUserService gaoUserService;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final NotifyService notifyService; private final NotifyService notifyService;
private final UserLoginService userLoginService;
private final UserService userService; private final UserService userService;
private final AttachmentService attachmentService;
/// 查询可作为通知接收人的 GAO 用户 /// 查询可作为通知接收人的 GAO 用户
/// ///
@@ -64,7 +69,6 @@ public class GaoNotifyController {
public PageResult<GaoUser> recipientList(@RequestBody Page<GaoUser> page) { public PageResult<GaoUser> recipientList(@RequestBody Page<GaoUser> page) {
TimiException.required(page, "未找到分页参数"); TimiException.required(page, "未找到分页参数");
GaoUser example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser()); GaoUser example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser());
example.setEnabled(true);
if (storeScopeService.hasGlobalScope()) { if (storeScopeService.hasGlobalScope()) {
if (TimiJava.isNotEmpty(example.getStoreId())) { if (TimiJava.isNotEmpty(example.getStoreId())) {
storeScopeService.checkStoreId(example.getStoreId()); storeScopeService.checkStoreId(example.getStoreId());
@@ -74,14 +78,14 @@ public class GaoNotifyController {
} }
page.setEqualsExample(example); page.setEqualsExample(example);
PageResult<GaoUser> result = gaoUserService.page(page); PageResult<GaoUser> result = gaoUserService.pageRecipients(page);
fillUser(result.getList()); fillUser(result.getList());
return result; return result;
} }
/// 创建 GAO 站内通知 /// 创建 GAO 站内通知
/// ///
/// 只允许发送站内通知,并逐个校验接收用户是否属于当前操作人的门店范围。 /// 只允许发送站内通知,并逐个校验接收用户是否属于当前操作人的门店范围;当前用户可以接收自己发送的通知
/// ///
/// @param notify 通知及通知详情 /// @param notify 通知及通知详情
/// @return 创建后的通知 /// @return 创建后的通知
@@ -116,8 +120,12 @@ public class GaoNotifyController {
}).collect(Collectors.toSet()); }).collect(Collectors.toSet());
TimiException.requiredTrue(recipientIdSet.size() == detailList.size(), "通知接收用户不能重复"); TimiException.requiredTrue(recipientIdSet.size() == detailList.size(), "通知接收用户不能重复");
Map<String, GaoUser> gaoUserMap = gaoUserService.mapByUserIdList(recipientIdSet); String loginUserId = userLoginService.getRequireLoginUserId();
TimiException.requiredTrue(gaoUserMap.size() == recipientIdSet.size(), "包含无效的通知接收用户"); Set<String> gaoRecipientIdSet = recipientIdSet.stream()
.filter(recipientId -> !loginUserId.equals(recipientId))
.collect(Collectors.toSet());
Map<String, GaoUser> gaoUserMap = gaoUserService.mapByUserIdList(gaoRecipientIdSet);
TimiException.requiredTrue(gaoUserMap.size() == gaoRecipientIdSet.size(), "包含无效的通知接收用户");
for (GaoUser gaoUser : gaoUserMap.values()) { for (GaoUser gaoUser : gaoUserMap.values()) {
TimiException.requiredTrue(Boolean.TRUE.equals(gaoUser.getEnabled()), "通知接收用户已停用"); TimiException.requiredTrue(Boolean.TRUE.equals(gaoUser.getEnabled()), "通知接收用户已停用");
storeScopeService.checkStoreId(gaoUser.getStoreId()); storeScopeService.checkStoreId(gaoUser.getStoreId());
@@ -133,8 +141,18 @@ public class GaoNotifyController {
} }
List<String> userIdList = gaoUserList.stream().map(GaoUser::getUserId).toList(); List<String> userIdList = gaoUserList.stream().map(GaoUser::getUserId).toList();
Map<String, User> userMap = userService.mapByIdList(userIdList); Map<String, User> userMap = userService.mapByIdList(userIdList);
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
Attachment.BizType.USER,
userIdList,
User.AttachType.AVATAR.toString()
);
for (GaoUser gaoUser : gaoUserList) { 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);
} }
} }
} }
@@ -1,7 +1,9 @@
package com.imyeyu.api.modules.gao.mapper; package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.api.modules.gao.entity.GaoUser;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -14,4 +16,8 @@ import java.util.List;
public interface GaoUserMapper extends BaseMapper<GaoUser, String> { public interface GaoUserMapper extends BaseMapper<GaoUser, String> {
List<GaoUser> selectByUserIdList(Collection<String> userIdList); List<GaoUser> selectByUserIdList(Collection<String> userIdList);
List<GaoUser> selectRecipientPage(@Param("page") Page<GaoUser> page);
long countRecipients(@Param("page") Page<GaoUser> page);
} }
@@ -1,6 +1,8 @@
package com.imyeyu.api.modules.gao.service; package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoUser; 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 com.imyeyu.spring.service.BaseService;
import java.util.Collection; import java.util.Collection;
@@ -28,4 +30,10 @@ public interface GaoUserService extends BaseService<GaoUser, String> {
/// @param userIdList 核心用户 ID 列表 /// @param userIdList 核心用户 ID 列表
/// @return GAO 用户映射 /// @return GAO 用户映射
Map<String, GaoUser> mapByUserIdList(Collection<String> userIdList); Map<String, GaoUser> mapByUserIdList(Collection<String> userIdList);
/// 查询可作为通知接收人的启用用户
///
/// @param page 分页参数
/// @return 接收用户分页结果
PageResult<GaoUser> pageRecipients(Page<GaoUser> page);
} }
@@ -13,6 +13,8 @@ import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper; 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 com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -174,4 +176,12 @@ public class GaoUserServiceImplement extends AbstractEntityService<GaoUser, Stri
return mapper.selectByUserIdList(userIdList).stream() return mapper.selectByUserIdList(userIdList).stream()
.collect(Collectors.toMap(GaoUser::getUserId, Function.identity())); .collect(Collectors.toMap(GaoUser::getUserId, Function.identity()));
} }
@Override
public PageResult<GaoUser> pageRecipients(Page<GaoUser> page) {
PageResult<GaoUser> result = new PageResult<>();
result.setTotal(mapper.countRecipients(page));
result.setList(mapper.selectRecipientPage(page));
return result;
}
} }
@@ -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;
@@ -12,4 +12,26 @@
</foreach> </foreach>
AND `deleted_at` IS NULL AND `deleted_at` IS NULL
</select> </select>
<select id="selectRecipientPage" resultType="com.imyeyu.api.modules.gao.entity.GaoUser">
SELECT *
FROM `gao_user`
WHERE `enabled` = 1
AND (`deleted_at` IS NULL OR FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) &lt; `deleted_at`)
<if test="page.equalsExample != null and page.equalsExample.storeId != null and page.equalsExample.storeId != ''">
AND `store_id` = #{page.equalsExample.storeId}
</if>
ORDER BY COALESCE(`updated_at`, `created_at`) DESC
LIMIT #{page.offset}, #{page.limit}
</select>
<select id="countRecipients" resultType="long">
SELECT COUNT(*)
FROM `gao_user`
WHERE `enabled` = 1
AND (`deleted_at` IS NULL OR FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) &lt; `deleted_at`)
<if test="page.equalsExample != null and page.equalsExample.storeId != null and page.equalsExample.storeId != ''">
AND `store_id` = #{page.equalsExample.storeId}
</if>
</select>
</mapper> </mapper>