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
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..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
@@ -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)
@@ -149,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());
}
/// 查询当前用户未读通知数量
@@ -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/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..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;
@@ -16,6 +17,8 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.util.List;
+
/**
* 通知详情
*
@@ -82,6 +85,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 +116,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)
@@ -138,6 +158,10 @@ public class NotifyDetail extends UUIDEntity {
@JsonView(ResponseView.Public.class)
private Long readAt;
+ /// 收件人隐藏时间,仅站内通知生效
+ @JsonIgnore
+ private Long hiddenAt;
+
@Transient
protected Notify notify;
@@ -146,4 +170,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/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/mapper/NotifyDetailMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java
index d17daf5..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} " + 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 `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} " + 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 `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} " + 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} " + 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 `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 `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/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/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/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/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/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,15 +57,22 @@ 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 +148,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,19 +167,51 @@ 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
/// @param userId 用户 ID
@Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
- public void deleteInternalByUserId(String detailId, String userId) {
+ public void hideInternalByUserId(String detailId, String userId) {
TimiException.required(detailId, "未找到通知详情 ID");
TimiException.required(userId, "未找到用户 ID");
- int affected = mapper.deleteSiteByIdAndUserId(detailId, userId);
+ int affected = mapper.hideSiteByIdAndUserId(detailId, userId);
TimiException.requiredTrue(0 < affected, "未找到站内通知");
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
index 9693566..85ae375 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
@@ -8,10 +8,13 @@ import com.imyeyu.api.modules.common.service.NotifyDetailService;
import com.imyeyu.api.modules.common.service.NotifyService;
import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService;
+import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
@@ -53,12 +56,10 @@ public class NotifyServiceImplement 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..5a1514d
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoNotifyController.java
@@ -0,0 +1,158 @@
+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.Attachment;
+import com.imyeyu.api.modules.common.entity.Notify;
+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.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.UserLoginService;
+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 UserLoginService userLoginService;
+ private final UserService userService;
+ private final AttachmentService attachmentService;
+
+ /// 查询可作为通知接收人的 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());
+ 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.pageRecipients(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(), "通知接收用户不能重复");
+
+ 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());
+ }
+ }
+
+ /// 回填 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);
+ Map> attachmentMap = attachmentService.mapByBizIdList(
+ Attachment.BizType.USER,
+ userIdList,
+ User.AttachType.AVATAR.toString()
+ );
+ for (GaoUser gaoUser : gaoUserList) {
+ 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/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/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java
index dc94f6e..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,12 @@
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;
///
/// GAO 用户 Mapper
@@ -9,4 +14,10 @@ import com.imyeyu.spring.mapper.BaseMapper;
/// @author Codex
/// @since 2026-07-30
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 61a7da2..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,8 +1,13 @@
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;
+import java.util.Map;
+
///
/// GAO 用户服务
///
@@ -19,4 +24,16 @@ 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);
+
+ /// 查询可作为通知接收人的启用用户
+ ///
+ /// @param page 分页参数
+ /// @return 接收用户分页结果
+ PageResult pageRecipients(Page page);
}
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 mapByUserIdList(Collection userIdList) {
+ if (TimiJava.isEmpty(userIdList)) {
+ return Map.of();
+ }
+ return mapper.selectByUserIdList(userIdList).stream()
+ .collect(Collectors.toMap(GaoUser::getUserId, Function.identity()));
+ }
+
+ @Override
+ public PageResult 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/V41__add_notify_detail_content_type.sql b/src/main/resources/db/migration/timiserver/V41__add_notify_detail_content_type.sql
new file mode 100644
index 0000000..30b000c
--- /dev/null
+++ b/src/main/resources/db/migration/timiserver/V41__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 '内容';
diff --git a/src/main/resources/db/migration/timiserver/V42__fix_gao_customer_invited_count.sql b/src/main/resources/db/migration/timiserver/V42__fix_gao_customer_invited_count.sql
new file mode 100644
index 0000000..c0c2d2d
--- /dev/null
+++ b/src/main/resources/db/migration/timiserver/V42__fix_gao_customer_invited_count.sql
@@ -0,0 +1,9 @@
+UPDATE `gao_customer` AS `customer`
+SET `invited_count` = (
+ SELECT COUNT(1)
+ FROM `gao_customer` AS `invitee`
+ WHERE
+ `invitee`.`introducer_customer_id` = `customer`.`id`
+ AND `invitee`.`deleted_at` IS NULL
+)
+WHERE `customer`.`deleted_at` IS NULL;
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/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/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}
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..18e84a3
--- /dev/null
+++ b/src/main/resources/mapper/timi-server/gao/GaoUserMapper.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+