redis;
-
- @PostConstruct
- private void postConstruct() {
- redis = redisConfig.getRedis(redisConfig.getDatabase().getUserToken(), RedisSerializers.STRING, RedisSerializers.LONG);
- }
-
- public Long set(String token, Long userId) {
- long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_USER_TOKEN);
- // 会话
- TimiSpring.setSessionAttr(token, userId);
- // 跨站 Cookie
- Cookie cookie = Objects.requireNonNullElse(TimiSpring.getCookie("Token"), new Cookie("Token", token));
- cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT));
- cookie.setPath("/");
- cookie.setSecure(true);
- cookie.setMaxAge((int) (ttl / 1000));
- TimiSpring.addCookie(cookie);
- // Redis
- redis.set(token, userId, ttl);
- return Time.now() + ttl;
- }
-
- public Long getExpireAt(String token) {
- return Time.now() + redis.getExpire(token);
- }
-
- /**
- * 获取令牌是否有效
- *
- * @param token 令牌
- * @return true 为有效
- */
- public boolean isValid(String token) {
- return getUserId(token) != null;
- }
-
- /**
- * 获取令牌是否无效({@link #isValid(String)} 取反)
- *
- * @param token 令牌
- * @return true 为无效
- */
- public boolean isInvalid(String token) {
- return !isValid(token);
- }
-
- /**
- * 获取用户 ID
- * 一级缓存 Session,二级缓存 Redis,每次触发二级缓存获取时会刷新这个时间
- *
Session 存键为 token,值为 UserId,Redis 也相同
- *
- * @param token 令牌
- * @return 用户 ID
- * @throws TimiException 无效 token
- */
- public @Nullable Long getRequiredUserId(String token) throws TimiException {
- return TimiException.required(getUserId(token), "invalid token");
- }
-
- /**
- * 获取用户 ID
- *
一级缓存 Session,二级缓存 Redis,每次触发二级缓存获取时会刷新这个时间
- *
Session 存键为 token,值为 UserId,Redis 也相同
- *
- * @param token 令牌
- * @return 用户 ID,token 无效时为 null
- */
- public @Nullable Long getUserId(String token) {
- if (TimiJava.isEmpty(token)) {
- return null;
- }
- Long userId;
- // Session
- if (TimiSpring.getSessionAttr(token) instanceof Long sessionUserId) {
- userId = sessionUserId;
- } else {
- // Redis
- userId = redis.get(token);
- }
- if (TimiJava.isEmpty(userId)) {
- return null;
- }
- // 刷新
- set(token, userId);
- return userId;
- }
-
- public @NotNull User getUser(String token) {
- return userService.get(getRequiredUserId(token));
- }
-
- public void clear(String token) {
- // 会话
- TimiSpring.removeSessionAttr(token);
- // 清除跨站 Cookie
- Cookie cookie = new Cookie("Token", "DIED");
- cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT));
- cookie.setPath("/");
- cookie.setSecure(true);
- cookie.setMaxAge(0);
- TimiSpring.addCookie(cookie);
- // Redis
- redis.destroy(token);
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java
deleted file mode 100644
index 5400e74..0000000
--- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.imyeyu.api.modules.blog.vo.article;
-
-import com.imyeyu.api.modules.blog.entity.Article;
-import com.imyeyu.api.modules.common.entity.Attachment;
-import com.imyeyu.api.modules.common.entity.Tag;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.util.List;
-
-/**
- * @author 夜雨
- * @since 2023-08-07 17:19
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class ArticleView extends Article {
-
- private long comments;
-
- private List tagList;
-
- private List attachmentList;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java
deleted file mode 100644
index 33b5cbc..0000000
--- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.imyeyu.api.modules.blog.vo.article;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.bean.Page;
-
-/**
- * @author 夜雨
- * @since 2023-07-14 17:52
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class ClassPage extends Page {
-
- private long classId;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java
deleted file mode 100644
index 902e7c7..0000000
--- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.imyeyu.api.modules.blog.vo.article;
-
-import jakarta.validation.constraints.NotBlank;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.bean.Page;
-
-/**
- * @author 夜雨
- * @since 2023-07-14 17:53
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class KeywordPage extends Page {
-
- @NotBlank(message = "article.keyword.empty")
- private String keyword;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java
deleted file mode 100644
index 712a5fa..0000000
--- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.imyeyu.api.modules.blog.vo.article;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.bean.Page;
-
-/**
- * @author 夜雨
- * @since 2023-07-14 17:53
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class LabelPage extends Page {
-
- private long labelId;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java b/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java
deleted file mode 100644
index 4bbb82b..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.imyeyu.api.modules.common.bean;
-
-/**
- * 支持评论的实体
- *
- * @author 夜雨
- * @since 2023-10-10 11:39
- */
-public interface CommentSupport {
-
- /** @return true 为可评论 */
- boolean canComment();
-
- /** @return true 为不可评论 */
- boolean canNotComment();
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java b/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java
deleted file mode 100644
index e02fb86..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package com.imyeyu.api.modules.common.bean;
-
-/**
- * 系统设置
- *
- * @author 夜雨
- * @since 2021-07-20 21:46
- */
-public enum SettingKey {
-
- // ---------- 通用 ----------
-
- RUN_ENV,
-
- PUBLIC_RESOURCES,
-
- /** 启用注册 */
- ENABLE_REGISTER,
-
- /** 启用登录 */
- ENABLE_LOGIN,
-
- /** 启用评论 */
- ENABLE_COMMENT,
-
- /** 启用测试 */
- ENABLE_DEBUG,
-
- /** 启用账号数据更新(User 和 UserProfile) */
- ENABLE_USER_UPDATE,
-
- /** 启用灰色滤镜 */
- ENABLE_GRAY_FILTER,
-
- TEMP_FILE_PATH,
-
- // ---------- ICP 备案号 ----------
-
- ICP_IMYEYU_COM,
-
- // ---------- 域名 ----------
-
- DOMAIN_ROOT,
-
- DOMAIN_API,
-
- DOMAIN_GIT,
-
- DOMAIN_BLOG,
-
- DOMAIN_SPACE,
-
- DOMAIN_RESOURCE,
-
- DOMAIN_DOWNLOAD,
-
- DOMAIN_FOREVER_MC,
-
- // ---------- ForeverMC ----------
-
- /** 启用登录服务 */
- FMC_PLAYER_LOGIN_ENABLE,
-
- /** 最多绑定玩家数量 */
- FMC_MAX_BIND,
-
- /** 闪烁标语 */
- FMC_SPLASHES,
-
- /** 启动器背景 */
- FMC_BG,
-
- FMC_BGM,
-
- FMC_BG_SWIPER,
-
- /** JRE 列表 */
- FMC_JRE,
-
- /** 辅助登录模组 */
- FMC_LOGIN_FABRIC,
-
- /** 启用图片地图上传 */
- FMC_ENABLE_IMAGE_MAP_UPLOAD,
-
- /** 玩家登录令牌有效期(天) */
- FMC_PLAYER_LOGIN_TOKEN_TTL,
-
- /** 服务器与数据中心的通信令牌 */
- FMC_SERVER_TOKEN,
-
- // ---------- 生存时间 ----------
-
- TTL_USER_TOKEN,
-
- TTL_SETTING,
-
- TTL_MULTILINGUAL,
-
- // ---------- 多语言翻译 ----------
-
- MULTILINGUAL_TRANSLATE_API,
-
- MULTILINGUAL_TRANSLATE_APP_ID,
-
- MULTILINGUAL_TRANSLATE_KEY,
-
- // ---------- 账单 ----------
-
- BILL_API_TOKEN,
-
- // ---------- Git ----------
-
- GIT_API,
-
- GIT_ABOUT_ARTICLE,
-
- GIT_REPO_PATH,
-
- // ---------- 远程音乐 ----------
-
- MUSIC_MAX_FRAME_LENGTH,
-
- MUSIC_PLAYER_PORT,
-
- MUSIC_PLAYER_IP,
-
- MUSIC_CONTROLLER_PORT,
-
- MUSIC_CONTROLLER_IP,
-
- MUSIC_CONTROLLER_URI,
-
- // ---------- 日记 ----------
-
- JOURNAL_KEY,
-
- JOURNAL_APP_ID,
-
- JOURNAL_APP_SECRET,
-
- JOURNAL_MEMO,
-
- JOURNAL_OPEN_ID_WHITE_LIST,
-
- // ---------- 临时文件 ----------
-
- /** 临时文件最小缓存时间 */
- TEMP_FILE_TTL_MIN,
-
- /** 临时文件最长缓存时间 */
- TEMP_FILE_TTL_MAX,
-
- /** 临时文件默认缓存时间 */
- TEMP_FILE_TTL_DEFAULT,
-
- /** 已过期的临时文件保留时间 */
- TEMP_FILE_RESIDUE_TIME,
-
- /** 每个 IP 限制有效临时文件容量 */
- TEMP_FILE_LIMIT,
-
- // ---------- 系统 ----------
-
- SYSTEM_FILE_BASE,
-
- SYSTEM_FILE_TYPE,
-
- SYSTEM_FILE_SYNC,
-
- /** 文件过滤(通过密钥类型) */
- SYSTEM_FILE_FILTER,
-
- SYSTEM_STATUS_RATE,
-
- SYSTEM_STATUS_LIMIT,
-
- SYSTEM_STATUS_NETWORK_MAC,
-
- SYSTEM_TERMINAL_TTL,
-
- SYSTEM_TERMINAL_FILTERS,
-
- /** 一般密钥 */
- SYSTEM_API_KEY,
-
- /** 超级密钥 */
- SYSTEM_API_SUPER_KEY,
-
- SYSTEM_REBOOT_COMMAND,
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java b/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java
new file mode 100644
index 0000000..2086eeb
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java
@@ -0,0 +1,50 @@
+package com.imyeyu.api.modules.common.bean;
+
+import com.imyeyu.api.modules.common.entity.Setting;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设置模块
+ *
+ * @author 夜雨
+ * @since 2026-04-24 00:59
+ */
+@Getter
+@AllArgsConstructor
+public enum SettingModule {
+
+ COMMON(Setting.Module.Common.class),
+
+ DOMAIN(Setting.Module.Domain.class),
+
+ FOREVER_MC(Setting.Module.ForeverMC.class),
+
+ MULTILINGUAL(Setting.Module.Multilingual.class),
+
+ GITEA(Setting.Module.Gitea.class),
+
+ MUSIC(Setting.Module.Music.class),
+
+ JOURNAL(Setting.Module.Journal.class),
+
+ TEMP_FILE(Setting.Module.TempFile.class),
+
+ SYSTEM(Setting.Module.System.class),
+
+ SYSTEM_STATUS(Setting.Module.SystemStatus.class);
+
+ /** 模块枚举类 */
+ final Class extends Enum>> clazz;
+
+ public static > SettingModule fromClass(Class clazz) {
+ for (SettingModule value : values()) {
+ if (value.clazz.equals(clazz)) {
+ return value;
+ }
+ }
+ throw new TimiException(TimiCode.ERROR, "not found System Module: %s".formatted(clazz.getName()));
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java
new file mode 100644
index 0000000..4831872
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java
@@ -0,0 +1,28 @@
+package com.imyeyu.api.modules.common.bean.attachment;
+
+import lombok.Data;
+
+/**
+ * 附件访问密钥 VO
+ *
+ * @author 夜雨
+ * @since 2026-01-13 10:05
+ */
+@Data
+public class AccessKey {
+
+ /** 访问密钥 */
+ private String value;
+
+ /** 附件 ID */
+ private String attachmentId;
+
+ /** true 为一次性访问 */
+ private boolean isDisposable;
+
+ /** 过期时间戳 */
+ private Long expiredAt;
+
+ /** 创建时间戳 */
+ private Long createdAt;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java
similarity index 76%
rename from src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java
rename to src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java
index 25365d0..8cd48dc 100644
--- a/src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java
@@ -1,4 +1,4 @@
-package com.imyeyu.api.modules.common.bean;
+package com.imyeyu.api.modules.common.bean.attachment;
/**
* @author 夜雨
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java
similarity index 65%
rename from src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java
rename to src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java
index 84ef59d..bbd0325 100644
--- a/src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java
@@ -1,4 +1,4 @@
-package com.imyeyu.api.modules.common.bean;
+package com.imyeyu.api.modules.common.bean.attachment;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -33,10 +33,16 @@ public class Metadata {
@EqualsAndHashCode(callSuper = true)
public static class ThumbImage extends Image {
- private long sourceId;
+ private String sourceId;
private String sourceMongoId;
private String sourceMimeType;
+
+ /** 请求宽度(缓存键之一,null 表示未指定) */
+ private Integer requestWidth;
+
+ /** 请求高度(缓存键之一,null 表示未指定) */
+ private Integer requestHeight;
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java
new file mode 100644
index 0000000..f00cc42
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java
@@ -0,0 +1,18 @@
+package com.imyeyu.api.modules.common.bean.attachment;
+
+import com.imyeyu.api.modules.common.entity.Setting;
+
+import java.util.List;
+
+/**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-05-13 17:27
+ */
+public interface SettingSupport {
+
+ String getId();
+
+ List getSettingList();
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java
new file mode 100644
index 0000000..a5fe22a
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java
@@ -0,0 +1,25 @@
+package com.imyeyu.api.modules.common.bean.attachment;
+
+import lombok.Data;
+
+/**
+ * 临时文件元数据
+ *
+ * @author 夜雨
+ * @since 2025-09-27 01:47
+ */
+@Data
+public class TempFileMetaData {
+
+ /** 附件 ID */
+ private String id;
+
+ /** 文件名 */
+ private String name;
+
+ /** 原始文件名 */
+ private String originalName;
+
+ /** 储存 ID */
+ private String mongoId;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java
new file mode 100644
index 0000000..6eb7dec
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java
@@ -0,0 +1,281 @@
+package com.imyeyu.api.modules.common.controller;
+
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.api.modules.common.service.AttachmentService;
+import com.imyeyu.api.modules.common.service.TempFileService;
+import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq;
+import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq;
+import com.imyeyu.api.modules.common.vo.attach.TempFileResp;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.network.Network;
+import com.imyeyu.spring.TimiSpring;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
+import com.imyeyu.utils.Time;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+///
+///
+/// @author 夜雨
+/// @since 2026-07-27 19:26
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/attach")
+public class AttachmentController {
+
+ private final TempFileService tempFileService;
+ private final AttachmentService service;
+
+ /**
+ * 查询附件详情
+ *
+ * @param id 附件 ID
+ * @return 附件详情
+ */
+ @RequestRateLimit
+ @PostMapping("/detail")
+ public Attachment detail(@RequestParam String id) {
+ return service.get(id);
+ }
+
+ /**
+ * 创建附件
+ *
+ * @param attachment 附件
+ * @return 创建结果
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public Attachment create(Attachment attachment) {
+ service.create(attachment);
+ return attachment;
+ }
+
+ /**
+ * 更新附件
+ *
+ * @param attachment 附件
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public void update(Attachment attachment) {
+ service.update(attachment);
+ }
+
+ /**
+ * 删除附件
+ *
+ * @param id 附件 ID
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id) {
+ service.delete(id);
+ }
+
+ /**
+ * 按业务查询单个附件
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @return 附件详情
+ */
+ @RequestRateLimit
+ @GetMapping("/detail/byBiz")
+ public Attachment detailByBiz(@RequestParam Attachment.BizType bizType, @RequestParam String bizId) {
+ return service.getByBizId(bizType, bizId);
+ }
+
+ /**
+ * 按业务和附件类型查询单个附件
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param attachType 附件类型
+ * @return 附件详情
+ */
+ @RequestRateLimit
+ @GetMapping("/detail/byAttachType")
+ public Attachment detailByAttachType(
+ @RequestParam Attachment.BizType bizType,
+ @RequestParam String bizId,
+ @RequestParam String attachType
+ ) {
+ return service.getByAttachType(bizType, bizId, attachType);
+ }
+
+ /**
+ * 按业务查询附件列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param attachTypeList 附件类型列表
+ * @return 附件列表
+ */
+ @RequestRateLimit
+ @GetMapping("/list/biz")
+ public List listByBiz(
+ @RequestParam Attachment.BizType bizType,
+ @RequestParam String bizId,
+ @RequestParam(required = false) List attachTypeList
+ ) {
+ List types = TimiJava.defaultIfNull(attachTypeList, List.of());
+ return service.listByBizId(bizType, bizId, types.toArray(String[]::new));
+ }
+
+ /**
+ * 按业务统计附件数量
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param attachTypeList 附件类型列表
+ * @return 附件数量
+ */
+ @RequestRateLimit
+ @GetMapping("/count/biz")
+ public long countByBiz(
+ @RequestParam Attachment.BizType bizType,
+ @RequestParam String bizId,
+ @RequestParam(required = false) List attachTypeList
+ ) {
+ List types = TimiJava.defaultIfNull(attachTypeList, List.of());
+ return service.countByBizId(bizType, bizId, types.toArray(String[]::new));
+ }
+
+ /**
+ * 按业务分页查询附件
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param attachTypeList 附件类型列表
+ * @param page 分页参数
+ * @return 分页结果
+ */
+ @RequestRateLimit
+ @PostMapping("/page/biz")
+ public PageResult pageByBiz(
+ @RequestParam Attachment.BizType bizType,
+ @RequestParam String bizId,
+ @RequestParam(required = false) List attachTypeList,
+ @RequestBody Page page
+ ) {
+ return service.pageByBizId(bizType, bizId, attachTypeList, page);
+ }
+
+ /**
+ * 按业务差分更新附件
+ *
+ * @param req 更新请求
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/update/biz")
+ public void updateByBiz(@RequestBody BizUpdateReq req) {
+ service.updateByBiz(req);
+ }
+
+ /**
+ * 按业务删除全部附件
+ *
+ * @param req 删除请求
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/delete/biz")
+ public void deleteByBiz(@RequestBody BizDeleteReq req) {
+ service.deleteByBiz(req);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @IgnoreGlobalReturn
+ @GetMapping({"/read/{id}", "/download/{id}"})
+ public void readById(@PathVariable String id, @RequestParam(required = false) Integer thumbWidth, @RequestParam(required = false) Integer thumbHeight) throws UnsupportedEncodingException {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ Attachment attach = service.get(id);
+ if (attach == null) {
+ resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ responseAttachment(attach, thumbWidth, thumbHeight);
+ }
+
+ @AOPLog
+ @PostMapping("/temp/upload")
+ public List upload(@RequestParam("file") List files, @RequestParam(value = "ttl", required = false) String ttl) {
+ return tempFileService.create(files, Time.D);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @IgnoreGlobalReturn
+ @GetMapping({"/temp/read", "/temp/download"})
+ public void read(@RequestParam String id) throws UnsupportedEncodingException {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ Attachment attach = service.get(id);
+ if (TimiSpring.getRequest().getRequestURI().endsWith("/download")) {
+ resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
+ }
+ response(attach);
+ }
+
+ private void responseAttachment(Attachment attach, Integer thumbWidth, Integer thumbHeight) throws UnsupportedEncodingException {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ if (attach != null && TimiSpring.getRequest().getRequestURI().endsWith("/download") && attach.getName() != null) {
+ resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
+ }
+ responseThumb(attach, thumbWidth, thumbHeight);
+ }
+
+ protected void responseThumb(Attachment attachment, Integer thumbWidth, Integer thumbHeight) {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ if (TimiJava.isEmpty(attachment)) {
+ resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ if (thumbWidth == null && thumbHeight == null) {
+ // 原图
+ response(attachment);
+ return;
+ }
+ // 缩略图
+ Attachment thumb = service.fetchThumb(attachment, thumbWidth, thumbHeight);
+ response(TimiJava.defaultIfNull(thumb, attachment));
+ }
+
+ protected void response(Attachment attachment) {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ try {
+ if (TimiJava.isEmpty(attachment)) {
+ resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ resp.setContentType(attachment.getMimeType());
+ attachment.doResponse();
+ } catch (Exception e) {
+ log.error("response attachment error", e);
+ resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java b/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java
index 3bca552..975922a 100644
--- a/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java
@@ -1,87 +1,264 @@
package com.imyeyu.api.modules.common.controller;
-import com.imyeyu.api.annotation.CaptchaValid;
-import com.imyeyu.api.annotation.EnableSetting;
-import com.imyeyu.api.bean.CaptchaFrom;
-import com.imyeyu.api.modules.common.bean.SettingKey;
+import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.modules.common.entity.Comment;
-import com.imyeyu.api.modules.common.entity.CommentReply;
-import com.imyeyu.api.modules.common.service.CommentReplyService;
+import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
import com.imyeyu.api.modules.common.service.CommentService;
+import com.imyeyu.api.modules.common.vo.comment.CommentCreateReq;
+import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq;
+import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp;
+import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp;
+import com.imyeyu.api.modules.common.vo.comment.CommentReplyReq;
+import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp;
+import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp;
+import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp;
+import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
-import com.imyeyu.spring.bean.CaptchaData;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
+import com.imyeyu.spring.util.ResponseView;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 评论操作接口
- * *评论回复只依赖评论而不含业务关联,评论有关联业务,所以此接口是通用接口
*
* @author 夜雨
* @since 2021-02-23 21:36
*/
@Slf4j
+@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/comment")
public class CommentController {
private final CommentService service;
- private final CommentReplyService replyService;
+ /**
+ * 创建主评论
+ *
+ * @param req 创建请求
+ * @return 评论树详情
+ */
@AOPLog
- @CaptchaValid(CaptchaFrom.COMMENT)
- @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service")
+ @JsonView(ResponseView.Public.class)
@RequestRateLimit
@PostMapping("/create")
- public void create(@Valid @RequestBody CaptchaData captchaData) {
- service.create(captchaData.getData());
- }
-
- @RequestRateLimit
- @PostMapping("/list")
- public PageResult list(@Valid @RequestBody Page page) {
- return service.page(page);
+ public Comment create(@RequestBody @Valid CommentCreateReq req) {
+ Comment entity = new Comment();
+ entity.setBizType(req.getBizType());
+ entity.setBizId(req.getBizId());
+ entity.setUserId(req.getUserId());
+ entity.setUserNick(req.getUserNick());
+ entity.setContent(req.getContent());
+ entity.setIp(TimiSpring.getRequestIP());
+ service.create(entity);
+ return service.detailTree(entity.getId(), req.getUserId());
}
/**
- * 创建评论回复
+ * 回复评论
*
- * @param request 回复数据
+ * @param req 回复请求
+ * @return 评论树详情
*/
@AOPLog
- @CaptchaValid(CaptchaFrom.COMMENT_REPLY)
- @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service")
+ @JsonView(ResponseView.Public.class)
@RequestRateLimit
- @PostMapping("/reply/create")
- public void createReply(@Valid @RequestBody CaptchaData request) {
- replyService.create(request.getData());
+ @PostMapping("/reply")
+ public Comment reply(@RequestBody @Valid CommentReplyReq req) {
+ Comment entity = new Comment();
+ entity.setParentId(req.getParentId());
+ entity.setUserId(req.getUserId());
+ entity.setUserNick(req.getUserNick());
+ entity.setContent(req.getContent());
+ entity.setIp(TimiSpring.getRequestIP());
+ service.create(entity);
+ return service.detailTree(entity.getId(), req.getUserId());
}
/**
- * 获取回复列表
+ * 删除当前用户自己的评论
*
- * @param page 页面参数
- * @return 回复列表
+ * @param id 评论 ID
+ * @param userId 用户 ID
*/
+ @AOPLog
@RequestRateLimit
- @RequestMapping("/reply/list")
- public PageResult pageReplies(@Valid @RequestBody Page page) {
- // 通用接口,只允许查询评论的回复
- CommentReply example = new CommentReply();
- if (page.getEqualsExample() != null) {
- example.setCommentId(page.getEqualsExample().getCommentId());
- }
- page.setEqualsExample(example);
- page.setLikesExample(null);
- return replyService.page(page);
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id, @RequestParam String userId) {
+ service.deleteOwned(id, userId);
+ }
+
+ /**
+ * 点赞评论
+ *
+ * @param commentId 评论 ID
+ * @param userId 用户 ID
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/like")
+ public void like(@RequestParam String commentId, @RequestParam String userId) {
+ service.like(commentId, userId);
+ }
+
+ /**
+ * 不喜欢评论
+ *
+ * @param commentId 评论 ID
+ * @param userId 用户 ID
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/unlike")
+ public void unlike(@RequestParam String commentId, @RequestParam String userId) {
+ service.unlike(commentId, userId);
+ }
+
+ /**
+ * 查询用户点赞评论记录
+ *
+ * @param page 分页参数
+ * @param userId 用户 ID
+ * @return 点赞评论记录分页
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @PostMapping("/like/list")
+ public PageResult listLikedRecord(@RequestBody Page page, @RequestParam String userId) {
+ return service.listLikedRecord(page, userId);
+ }
+
+ /**
+ * 查询扁平主评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 扁平主评论列表
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/flat/list")
+ public FlatCommentListResp listFlatRoot(
+ @RequestParam String bizType,
+ @RequestParam String bizId,
+ @RequestParam(defaultValue = "0") int offset,
+ @RequestParam(required = false) String currentUserId
+ ) {
+ return service.listFlatRoot(bizType, bizId, offset, currentUserId);
+ }
+
+ /**
+ * 查询扁平评论线程
+ *
+ * @param commentId 评论 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 评论线程
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/flat/thread")
+ public CommentThreadResp getFlatThread(
+ @RequestParam String commentId,
+ @RequestParam(defaultValue = "0") int offset,
+ @RequestParam(required = false) String currentUserId
+ ) {
+ return service.getUserThread(commentId, offset, currentUserId);
+ }
+
+ /**
+ * 查询层级主评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 层级主评论列表
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/nested/list")
+ public NestedCommentListResp listNestedRoot(
+ @RequestParam String bizType,
+ @RequestParam String bizId,
+ @RequestParam(defaultValue = "0") int offset,
+ @RequestParam(required = false) String currentUserId
+ ) {
+ return service.listNestedRoot(bizType, bizId, offset, currentUserId);
+ }
+
+ /**
+ * 查询评论节点
+ *
+ * @param commentId 评论 ID
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 评论节点
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/nested/detail")
+ public CommentNodeResp getNestedDetail(@RequestParam String commentId, @RequestParam(required = false) String currentUserId) {
+ return service.getUserNode(commentId, currentUserId);
+ }
+
+ /**
+ * 查询直属子评论分页
+ *
+ * @param commentId 评论 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 子评论分页
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/nested/children")
+ public CommentLayerResp getNestedChildren(
+ @RequestParam String commentId,
+ @RequestParam(defaultValue = "0") int offset,
+ @RequestParam(required = false) String currentUserId
+ ) {
+ return service.getUserChildren(commentId, offset, currentUserId);
+ }
+
+ /**
+ * 查询单层评论列表
+ *
+ * @param req 查询请求
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 评论层
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @PostMapping("/layer/list")
+ public CommentLayerResp listLayer(@RequestBody CommentLayerQueryReq req, @RequestParam(required = false) String currentUserId) {
+ return service.listSdkLayer(req, currentUserId);
+ }
+
+ /**
+ * 查询评论树详情
+ *
+ * @param id 评论 ID
+ * @param currentUserId 当前用户 ID,可为 null
+ * @return 评论树详情
+ */
+ @JsonView(ResponseView.Public.class)
+ @RequestRateLimit
+ @GetMapping("/detail/tree")
+ public Comment detailTree(@RequestParam String id, @RequestParam(required = false) String currentUserId) {
+ return service.detailTree(id, currentUserId);
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java b/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java
index 526d22e..a7ce26d 100644
--- a/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java
@@ -1,77 +1,23 @@
package com.imyeyu.api.modules.common.controller;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.imyeyu.api.bean.CaptchaFrom;
-import com.imyeyu.api.modules.common.bean.ImageType;
-import com.imyeyu.api.modules.common.bean.SettingKey;
-import com.imyeyu.api.modules.common.entity.Attachment;
-import com.imyeyu.api.modules.common.entity.Setting;
-import com.imyeyu.api.modules.common.entity.Task;
-import com.imyeyu.api.modules.common.entity.Template;
-import com.imyeyu.api.modules.common.entity.Version;
-import com.imyeyu.api.modules.common.service.AttachmentService;
-import com.imyeyu.api.modules.common.service.ClipboardService;
-import com.imyeyu.api.modules.common.service.FeedbackService;
-import com.imyeyu.api.modules.common.service.SettingService;
-import com.imyeyu.api.modules.common.service.TaskService;
-import com.imyeyu.api.modules.common.service.TempFileService;
-import com.imyeyu.api.modules.common.service.TemplateService;
-import com.imyeyu.api.modules.common.service.VersionService;
-import com.imyeyu.api.modules.common.vo.ClipboardRequest;
-import com.imyeyu.api.modules.common.vo.FeedbackRequest;
-import com.imyeyu.api.modules.common.vo.TempFileResponse;
-import com.imyeyu.api.modules.system.util.ResourceHandler;
import com.imyeyu.api.util.CaptchaManager;
import com.imyeyu.io.IO;
-import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
-import com.imyeyu.java.ref.Ref;
-import com.imyeyu.network.Network;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog;
-import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
-import com.imyeyu.spring.annotation.RequestRateLimit;
-import com.imyeyu.spring.bean.CaptchaData;
-import com.imyeyu.spring.bean.RequestRange;
-import com.mongodb.client.gridfs.GridFSBucket;
-import com.mongodb.client.gridfs.GridFSDownloadStream;
-import com.mongodb.client.gridfs.model.GridFSFile;
-import jakarta.servlet.http.HttpServletRequest;
+import com.imyeyu.utils.Decoder;
+import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.tika.Tika;
-import org.springframework.http.MediaType;
-import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-import org.yaml.snakeyaml.Yaml;
import javax.imageio.ImageIO;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
/**
* 系统接口
@@ -86,36 +32,7 @@ import java.util.stream.Collectors;
@RequestMapping("/")
public class CommonController {
- private final TaskService taskService;
- private final VersionService versionService;
- private final SettingService settingService;
- private final FeedbackService feedbackService;
- private final TemplateService templateService;
- private final TempFileService tempFileService;
- private final AttachmentService attachmentService;
- private final ClipboardService clipboardService;
-
- private final ObjectMapper jackson;
- private final Yaml yaml;
- private final GridFSBucket gridFSBucket;
private final CaptchaManager captchaManager;
- private final ResourceHandler resourceHandler;
-
- private String writeJson(Object value) {
- try {
- return jackson.writeValueAsString(value);
- } catch (IOException e) {
- throw new TimiException(TimiCode.ERROR, "write json error", e);
- }
- }
-
- private Map readJsonMap(String value) {
- try {
- return jackson.readValue(value, new TypeReference<>() {});
- } catch (IOException e) {
- throw new TimiException(TimiCode.ERROR, "read json error", e);
- }
- }
@AOPLog
@RequestMapping("")
@@ -123,399 +40,56 @@ public class CommonController {
return "IT WORKING! " + TimiSpring.getRequestIP();
}
- /**
- * 获取共享剪切板内容
- *
- * @param id 会话 ID
- * @return 剪切板内容
- */
- @AOPLog
- @RequestRateLimit
- @GetMapping("/clipboard/{id}")
- public String clipboardGet(@Valid @NotBlank @PathVariable("id") String id) {
- return clipboardService.getContent(id);
- }
-
- /**
- * 设置共享剪切板内容
- *
- * @param id 会话 ID
- * @param request 请求内容
- */
- @AOPLog
- @RequestRateLimit
- @PostMapping("/clipboard/{id}")
- public void clipboardSet(@Valid @NotBlank @PathVariable("id") String id, @Valid @RequestBody ClipboardRequest request) {
- clipboardService.setContent(id, request.getContent());
- }
-
- /**
- * 订阅共享剪切板实时更新
- *
- * @param id 会话 ID
- * @return SSE 发射器
- */
- @AOPLog
- @IgnoreGlobalReturn
- @GetMapping(value = "/clipboard/stream/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
- public SseEmitter clipboardStream(@Valid @NotBlank @PathVariable("id") String id) {
- return clipboardService.subscribe(id);
- }
-
/**
* 获取验证码
*
- * @param width 宽度
- * @param height 高度
- * @param from 来源
- * @param response 返回对象
+ * @param width 宽度
+ * @param height 高度
+ * @return Base64 编码的图形验证码和唯一识别 ID
*/
- @IgnoreGlobalReturn
@GetMapping("/captcha")
- public void captcha(int width, int height, CaptchaFrom from, HttpServletResponse response) {
- // 返回图像流
- response.setHeader("Pragma", "no-cache");
- response.setHeader("Cache-Control", "no-cache"); // 禁止缓存
- response.setDateHeader("Expires", -1);
- response.setContentType("image/jpg");
+ public CaptchaManager.Result captcha(Integer width, Integer height) {
+ TimiException.requiredTrue(width != null && 64 < width, "not found width or less than 64");
+ TimiException.requiredTrue(height != null && 19 < height, "not found height or less than 19");
+ return captchaManager.generate(width, height);
+ }
+
+ /**
+ * 获取验证码(返回图形数据流,唯一识别 ID 在返回头 X-Captcha-ID)
+ *
+ * @param width 宽度
+ * @param height 高度
+ */
+ @GetMapping("/captcha/img")
+ public void captchaImg(Integer width, Integer height, HttpServletResponse resp) {
+ CaptchaManager.Result result = captchaManager.generate(width, height);
+ resp.setHeader("Pragma", "no-cache");
+ resp.setHeader("Cache-Control", "no-cache");
+ resp.setHeader("X-Captcha-ID", result.id());
+ resp.setHeader("Access-Control-Expose-Headers", "X-Captcha-ID");
+ resp.setDateHeader("Expires", -1);
+ resp.setContentType("image/png");
try {
- // 宽度
+ ServletOutputStream os = resp.getOutputStream();
if (width < 64) {
- ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "jpg", response.getOutputStream());
+ ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os);
return;
}
// 高度
if (height < 19) {
- ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "jpg", response.getOutputStream());
- return;
- }
- // 来自
- if (TimiJava.isEmpty(from)) {
- ImageIO.write(captchaManager.error(TimiCode.ARG_MISS), "jpg", response.getOutputStream());
+ ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os);
return;
}
// 输出图像流
- ImageIO.write(captchaManager.generate(from, width, height), "jpg", response.getOutputStream());
- } catch (Exception e) {
- log.error("CommonController.getCaptcha", e);
+ IO.toOutputStream(os, Decoder.base64(result.data().split(",")[1]));
+ } catch (IOException e) {
+ log.error("captcha img error", e);
+ resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try {
- ImageIO.write(captchaManager.error(TimiCode.ERROR), "jpg", response.getOutputStream());
+ ImageIO.write(captchaManager.error(TimiCode.ERROR), "png", resp.getOutputStream());
} catch (IOException subE) {
log.error("write error image fail", subE);
}
}
}
-
- /**
- * 获取软件最新版本状态
- *
- * @param name 软件名称
- * @return 最新版本状态
- * @deprecated 兼容旧程序
- */
- @AOPLog
- @GetMapping("/versions/{name}")
- @Deprecated
- public Version versions(@Valid @NotBlank @PathVariable("name") String name) {
- return versionService.getByName(name);
- }
-
- /**
- * 获取软件最新版本状态
- *
- * @param name 软件名称
- * @return 最新版本状态
- */
- @AOPLog
- @GetMapping("/version/{name}")
- public Version version(@Valid @NotBlank @PathVariable("name") String name) {
- return versionService.getByName(name);
- }
-
- @AOPLog
- @RequestRateLimit
- @PostMapping("/feedback")
- public void createFeedback(@Valid @NotNull @RequestBody CaptchaData request) {
- captchaManager.test(request.getCaptcha(), request.getCaptchaId());
- feedbackService.create(request.getData());
- }
-
- /** @return 公开任务信息 */
- @AOPLog
- @RequestRateLimit
- @GetMapping("/tasklist")
- public List getTasks() {
- return taskService.listAll4Public();
- }
-
- @RequestRateLimit
- @GetMapping("/template")
- public String viewTemplate(@RequestParam Template.BizType bizType, @RequestParam String bizCode) {
- return templateService.get(bizType, bizCode).getData();
- }
-
- @RequestRateLimit
- @GetMapping("/setting/{key}")
- public String settingByKey(@PathVariable("key") String key, @RequestParam(value = "as", required = false) Setting.Type asType) {
- Setting setting = settingService.getByKey(SettingKey.valueOf(key.toUpperCase()));
- if (setting.isPrivate()) {
- throw new TimiException(TimiCode.PERMISSION_ERROR);
- }
- String result = setting.getValue();
- if (asType == null) {
- return result;
- }
- switch (asType) {
- case JSON -> {
- if (setting.getType() == Setting.Type.YAML) {
- Map obj = yaml.load(setting.getValue());
- result = writeJson(obj);
- }
- }
- case YAML -> {
- if (setting.getType() == Setting.Type.JSON) {
- Map obj = readJsonMap(setting.getValue());
- result = yaml.dump(obj);
- }
- }
- }
- return result;
- }
-
- @RequestRateLimit
- @PostMapping("/setting/map")
- public Map mapSettingByKeys(@RequestBody Map> settingMap) {
- List result = settingService.listByKeys(new ArrayList<>(settingMap.keySet()));
- for (int i = 0; i < result.size(); i++) {
- Setting setting = result.get(i);
- if (setting.isPrivate()) {
- throw new TimiException(TimiCode.PERMISSION_ERROR);
- }
- Map args = settingMap.get(setting.getKey());
- if (args == null) {
- continue;
- }
- if (args.containsKey("as")) {
- switch (Ref.toType(Setting.Type.class, args.get("as").toString())) {
- case JSON -> {
- if (setting.getType() == Setting.Type.YAML) {
- Map obj = new Yaml().load(setting.getValue());
- setting.setValue(writeJson(obj));
- }
- }
- case YAML -> {
- if (setting.getType() == Setting.Type.JSON) {
- Map obj = readJsonMap(setting.getValue());
- setting.setValue(new Yaml().dump(obj));
- }
- }
- }
- }
- }
- return result.stream().collect(Collectors.toMap(Setting::getKey, Setting::getValue));
- }
-
- @AOPLog
- @RequestRateLimit
- @GetMapping("/attachment/{mongoId}")
- public Attachment getAttachment(@PathVariable String mongoId) {
- return attachmentService.getByMongoId(mongoId);
- }
-
- @AOPLog
- @IgnoreGlobalReturn
- @GetMapping("/attachment/read/{mongoId}")
- public void readAttachment(
- @PathVariable String mongoId,
- @RequestParam(name = "size", required = false) Integer size,
- @RequestParam(name = "type", required = false) String type,
- HttpServletRequest req,
- HttpServletResponse resp
- ) {
- try {
- GridFSFile file = attachmentService.readByMongoId(mongoId);
- if (file == null) {
- resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
- resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
- return;
- }
- GridFSDownloadStream mimeReadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- String mimeType = new Tika().detect(mimeReadStream);
- if (TimiJava.isNotEmpty(mimeType)) {
- resp.setContentType(mimeType);
- }
- if (size != null) {
- String fileType = switch (mimeType) {
- case "image/png" -> "png";
- case "image/jpeg" -> "jpg";
- default -> throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO not support Re render mineType");
- };
-
- switch (mimeType) {
- case "image/png", "image/jpeg" -> {
- // 图片缩放
- GridFSDownloadStream stream = gridFSBucket.openDownloadStream(file.getObjectId());
- byte[] bytes = IO.toBytes(stream);
- BufferedImage imgSrc = ImageIO.read(new ByteArrayInputStream(bytes));
-
- double scale;
- if (imgSrc.getHeight() < imgSrc.getWidth()) {
- // 横向
- scale = 1D * size / imgSrc.getWidth();
- } else {
- scale = 1D * size / imgSrc.getHeight();
- }
- int width = (int) (imgSrc.getWidth() * scale);
- int height = (int) (imgSrc.getHeight() * scale);
- BufferedImage imgResult = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- Graphics2D g = imgResult.createGraphics();
- if (ImageType.PIXELATED == Ref.toType(ImageType.class, type)) {
- g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
- } else {
- g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
- g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
- g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
- g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- }
- g.drawImage(imgSrc, 0, 0, width, height, null);
- ImageIO.write(imgResult, fileType, resp.getOutputStream());
- }
- default -> throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO not support Re render mineType");
- }
- } else {
- GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- GridFsResource gridFsResource = new GridFsResource(file, downloadStream);
- req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.MONGO);
- req.setAttribute(ResourceHandler.ATTR_VALUE, gridFsResource);
- resourceHandler.handleRequest(req, resp);
- }
- } catch (Exception e) {
- log.error("read attachment error", e);
- resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
- }
- }
-
- @AOPLog
- @RequestRateLimit
- @IgnoreGlobalReturn
- @GetMapping("/attachment/download/{mongoId}")
- public void downloadAttachment(@PathVariable String mongoId, HttpServletResponse resp) {
- try {
- Attachment attachment = attachmentService.getByMongoId(mongoId);
- GridFSFile file = attachmentService.readByMongoId(mongoId);
- if (file == null) {
- resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
- resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
- return;
- }
- {
- GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- String mimeType = new Tika().detect(downloadStream);
- if (TimiJava.isNotEmpty(mimeType)) {
- resp.setContentType(mimeType);
- }
- }
- GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getFilename()));
- resp.setHeader("Content-Range", String.valueOf(attachment.getSize()));
- resp.setHeader("Accept-Ranges", "bytes");
- resp.setContentLengthLong(attachment.getSize());
- IO.toOutputStream(downloadStream, resp.getOutputStream());
- resp.flushBuffer();
- } catch (Exception e) {
- log.error("read attachment error", e);
- resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
- }
- }
-
- /**
- * 上传临时文件
- *
- * @param files 文件列表
- * @param ttl 缓存时长(毫秒),最长 3 天(259200000ms)
- * @return 临时文件响应列表
- */
- @AOPLog
- @PostMapping("/temp/file/upload")
- public List tempFileUpload(@RequestParam("file") List files, @RequestParam(value = "ttl", required = false) Long ttl) {
- return tempFileService.store(files, ttl);
- }
-
- /**
- * 读取临时文件
- *
- * @param mongoId mongoId
- * @param req 请求
- * @param resp 返回
- */
- @AOPLog
- @RequestRateLimit
- @IgnoreGlobalReturn
- @GetMapping("/temp/file/read/{mongoId}")
- public void tempFileRead(@PathVariable String mongoId, HttpServletRequest req, HttpServletResponse resp) {
- try {
- Attachment attach = attachmentService.getByMongoId(mongoId);
- if (TimiJava.isEmpty(attach)) {
- resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
- GridFSFile file = attachmentService.readByMongoId(attach.getMongoId());
- GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- GridFsResource gridFsResource = new GridFsResource(file, downloadStream);
- req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.MONGO);
- req.setAttribute(ResourceHandler.ATTR_VALUE, gridFsResource);
-
- resp.setContentType(attach.getMimeType());
- resourceHandler.handleRequest(req, resp);
- } catch (Exception e) {
- resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- }
-
- /**
- * 下载临时文件
- *
- * @param mongoId
- * @param resp
- */
- @AOPLog
- @RequestRateLimit
- @IgnoreGlobalReturn
- @RequestMapping("/temp/file/download/{mongoId}")
- public void tempFileDownload(@PathVariable String mongoId, HttpServletResponse resp) {
- try {
- Attachment attach = attachmentService.getByMongoId(mongoId);
- if (TimiJava.isEmpty(attach)) {
- resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
- resp.setContentType(attach.getMimeType());
- resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
- resp.setHeader("Accept-Ranges", "bytes");
-
- GridFSFile file = attachmentService.readByMongoId(mongoId);
- @Cleanup
- GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
- RequestRange range = TimiSpring.getRequestRange(attach.getSize());
- if (range == null) {
- // 完整文件
- resp.setContentLengthLong(attach.getSize());
- resp.setStatus(HttpServletResponse.SC_OK);
- IO.toOutputStream(downloadStream, resp.getOutputStream());
- } else {
- // 分片文件
- resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
- resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), attach.getSize()));
- resp.setContentLengthLong(range.getLength());
- IO.toOutputStream(downloadStream, resp.getOutputStream(), range.getStart(), range.getEnd());
- resp.flushBuffer();
- }
- } catch (Exception e) {
- log.error("download error", e);
- resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- }
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java b/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java
index 2c23c04..0fcb6db 100644
--- a/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java
@@ -1,26 +1,19 @@
package com.imyeyu.api.modules.common.controller;
-import com.imyeyu.api.modules.common.entity.Icon;
import com.imyeyu.api.modules.common.service.IconService;
-import com.imyeyu.api.modules.common.vo.icon.AllResponse;
-import com.imyeyu.api.modules.common.vo.icon.NamePage;
-import com.imyeyu.api.modules.common.vo.icon.UnicodePage;
-import com.imyeyu.spring.annotation.AOPLog;
-import com.imyeyu.spring.annotation.RequestRateLimit;
-import com.imyeyu.spring.bean.Page;
-import com.imyeyu.spring.bean.PageResult;
-import jakarta.validation.Valid;
+import com.imyeyu.network.Network;
+import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
-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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.io.IOException;
import java.util.List;
+import java.util.Map;
/**
* timi-icon 前端接口
@@ -38,60 +31,38 @@ public class IconController {
private final IconService service;
/**
- * 获取图标列表
+ * 查询全部图标名称
*
- * @param page 查询列表参数
- * @return 图标列表
+ * @return 图标名称列表
*/
- @RequestRateLimit
- @PostMapping("/list")
- public PageResult list(@RequestBody Page page) {
- return service.page(page);
+ @GetMapping("/name/list")
+ public List listName() {
+ return service.listName();
+ }
+
+ @GetMapping("/svg/map")
+ public Map mapSvg() {
+ return service.mapSvg();
+ }
+
+ @GetMapping("/unicode/map")
+ public Map mapUnicode() {
+ return service.mapUnicode();
}
/**
- * 获取所有图标,为了减小传输数据,此接口只返回 name 名称、Unicode 代码和 SVG 路径
+ * 导出 TTF 字体文件
*
- * @param latest 请求缓存的最新数据时间
- * @return 所有图标,如果请求缓存的最新时间等于数据库的最新数据时间,不返回任何数据
+ * @param resp HTTP 响应
+ * @throws IOException 写出字体文件失败
*/
- @RequestRateLimit
- @GetMapping("/list/all")
- public AllResponse listAll(@Valid @RequestParam Long latest) {
- AllResponse resp = service.listAll(latest);
- List icons = resp.getIcons();
- for (int i = 0; i < icons.size(); i++) {
- Icon icon = icons.get(i);
- icon.setId(null);
- icon.setCreatedAt(null);
- icon.setUpdatedAt(null);
- }
- return resp;
- }
-
- /**
- * 根据名称获取查询列表参数列表
- *
- * @param page 查询列表参数
- * @return 图标列表
- */
- @AOPLog
- @RequestRateLimit
- @PostMapping("/list/name")
- public PageResult listByName(@Valid @RequestBody NamePage page) {
- return service.pageByName(page);
- }
-
- /**
- * 根据 Unicode 获取图标列表
- *
- * @param page 查询列表参数
- * @return 图标列表
- */
- @AOPLog
- @RequestRateLimit
- @PostMapping("/list/unicode")
- public PageResult listByUnicode(@Valid @RequestBody UnicodePage page) {
- return service.pageByUnicode(page);
+ @IgnoreGlobalReturn
+ @GetMapping(value = "/export/ttf", produces = "font/ttf")
+ public void exportTtf(HttpServletResponse resp) throws IOException {
+ byte[] body = service.exportTtf();
+ resp.setContentType("font/ttf");
+ resp.setContentLength(body.length);
+ resp.setHeader("Content-Disposition", Network.getFileDownloadHeader("timi-icon.ttf"));
+ resp.getOutputStream().write(body);
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java
new file mode 100644
index 0000000..4fc539f
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java
@@ -0,0 +1,35 @@
+package com.imyeyu.api.modules.common.controller;
+
+import com.imyeyu.api.modules.system.entity.Logger;
+import com.imyeyu.api.modules.system.service.LoggerService;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequiredToken;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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;
+
+/**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-06-13 09:31
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/log")
+public class LoggerController {
+
+ private final LoggerService service;
+
+ @AOPLog
+ @RequiredToken
+ @PostMapping("/create")
+ public void create(@Valid @RequestBody Logger logger) {
+ service.create(logger);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java
new file mode 100644
index 0000000..db002a3
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java
@@ -0,0 +1,94 @@
+package com.imyeyu.api.modules.common.controller;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.imyeyu.api.annotation.RequireCorePermission;
+import com.imyeyu.api.annotation.RequireCoreRole;
+import com.imyeyu.api.bean.CorePermissionCode;
+import com.imyeyu.api.bean.CoreRoleCode;
+import com.imyeyu.api.modules.common.entity.Multilingual;
+import com.imyeyu.api.modules.common.service.MultilingualService;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.util.ResponseView;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 多语言管理接口
+ *
+ * @author Codex
+ * @since 2026-07-27
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/system/multilingual")
+public class MultilingualController {
+
+ private final MultilingualService service;
+
+ /**
+ * 查询多语言详情
+ *
+ * @param id 多语言 ID
+ * @return 多语言详情
+ */
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCoreRole(CoreRoleCode.ADMIN)
+ @RequireCorePermission(CorePermissionCode.MULTILINGUAL_READ)
+ @PostMapping("/detail")
+ public Multilingual detail(@RequestParam String id) {
+ return service.get(id);
+ }
+
+ /**
+ * 创建多语言
+ *
+ * @param multilingual 多语言
+ * @return 创建结果
+ */
+ @AOPLog
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCoreRole(CoreRoleCode.ADMIN)
+ @RequireCorePermission(CorePermissionCode.MULTILINGUAL_CREATE)
+ @PostMapping("/create")
+ public Multilingual create(@RequestBody Multilingual multilingual) {
+ service.create(multilingual);
+ return multilingual;
+ }
+
+ /**
+ * 更新多语言
+ *
+ * @param multilingual 多语言
+ */
+ @AOPLog
+ @RequestRateLimit
+ @RequireCoreRole(CoreRoleCode.ADMIN)
+ @RequireCorePermission(CorePermissionCode.MULTILINGUAL_UPDATE)
+ @PostMapping("/update")
+ public void update(@RequestBody Multilingual multilingual) {
+ service.update(multilingual);
+ }
+
+ /**
+ * 删除多语言
+ *
+ * @param id 多语言 ID
+ */
+ @AOPLog
+ @RequestRateLimit
+ @RequireCoreRole(CoreRoleCode.ADMIN)
+ @RequireCorePermission(CorePermissionCode.MULTILINGUAL_DELETE)
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id) {
+ service.delete(id);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java b/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java
new file mode 100644
index 0000000..eeb1b6c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java
@@ -0,0 +1,100 @@
+package com.imyeyu.api.modules.common.controller;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.imyeyu.api.annotation.RequireCorePermission;
+import com.imyeyu.api.bean.CorePermissionCode;
+import com.imyeyu.api.modules.common.entity.Setting;
+import com.imyeyu.api.modules.common.service.SettingService;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
+import com.imyeyu.spring.util.ResponseView;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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;
+
+/**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-23 16:31
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/system/setting")
+public class SettingController {
+
+ private final SettingService service;
+
+ /**
+ * 查询配置列表
+ *
+ * @param page 分页查询
+ * @return 配置列表
+ */
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.SETTING_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody Page page) {
+ return service.page(page);
+ }
+
+ /**
+ * 查询配置详情
+ *
+ * @param id 配置 ID
+ * @return 配置详情
+ */
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @PostMapping("/detail")
+ public Setting detail(@RequestBody String id) {
+ return service.get(id);
+ }
+
+ /**
+ * 创建配置
+ *
+ * @param setting 配置
+ * @return 配置
+ */
+ @AOPLog
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @PostMapping("/create")
+ public Setting create(@RequestBody Setting setting) {
+ service.create(setting);
+ return setting;
+ }
+
+ /**
+ * 更新配置
+ *
+ * @param setting 配置
+ */
+ @AOPLog
+ @RequestRateLimit
+ @PostMapping("/update")
+ public void update(@RequestBody Setting setting) {
+ service.update(setting);
+ }
+
+ /**
+ * 删除配置
+ *
+ * @param id 配置 ID
+ */
+ @AOPLog
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.SETTING_DELETE)
+ @PostMapping("/delete")
+ public void delete(@RequestBody String id) {
+ service.delete(id);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java b/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java
deleted file mode 100644
index d7bb4e8..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java
+++ /dev/null
@@ -1,316 +0,0 @@
-package com.imyeyu.api.modules.common.controller;
-
-import com.imyeyu.api.annotation.CaptchaValid;
-import com.imyeyu.api.annotation.EnableSetting;
-import com.imyeyu.api.bean.CaptchaFrom;
-import com.imyeyu.api.modules.common.bean.SettingKey;
-import com.imyeyu.api.modules.common.entity.Comment;
-import com.imyeyu.api.modules.common.entity.CommentReply;
-import com.imyeyu.api.modules.common.entity.UserConfig;
-import com.imyeyu.api.modules.common.entity.UserPrivacy;
-import com.imyeyu.api.modules.common.service.CommentReplyService;
-import com.imyeyu.api.modules.common.service.CommentService;
-import com.imyeyu.api.modules.common.service.UserConfigService;
-import com.imyeyu.api.modules.common.service.UserPrivacyService;
-import com.imyeyu.api.modules.common.service.UserProfileService;
-import com.imyeyu.api.modules.common.service.UserService;
-import com.imyeyu.api.modules.common.vo.user.EmailVerifyCallbackRequest;
-import com.imyeyu.api.modules.common.vo.user.LoginRequest;
-import com.imyeyu.api.modules.common.vo.user.LoginResponse;
-import com.imyeyu.api.modules.common.vo.user.RegisterRequest;
-import com.imyeyu.api.modules.common.vo.user.UpdatePasswordByKeyRequest;
-import com.imyeyu.api.modules.common.vo.user.UpdatePasswordRequest;
-import com.imyeyu.api.modules.common.vo.user.UserRequest;
-import com.imyeyu.api.modules.common.vo.user.UserView;
-import com.imyeyu.java.TimiJava;
-import com.imyeyu.java.bean.timi.TimiException;
-import com.imyeyu.spring.annotation.AOPLog;
-import com.imyeyu.spring.annotation.RequestRateLimit;
-import com.imyeyu.spring.annotation.RequiredToken;
-import com.imyeyu.spring.bean.CaptchaData;
-import com.imyeyu.spring.bean.Page;
-import com.imyeyu.spring.bean.PageResult;
-import com.imyeyu.utils.Time;
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.Min;
-import jakarta.validation.constraints.NotNull;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PathVariable;
-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;
-
-/**
- * 用户接口
- *
- * @author 夜雨
- * @since 2021-02-23 21:38
- */
-@Slf4j
-@Validated
-@RestController
-@RequiredArgsConstructor
-@RequestMapping("/user")
-public class UserController implements TimiJava {
-
- private final UserService service;
- private final CommentService commentService;
- private final UserConfigService configService;
- private final UserProfileService profileService;
- private final UserPrivacyService privacyService;
- private final CommentReplyService commentReplyService;
-
- /**
- * 注册。执行成功会自动登录
- *
- * @param request 注册请求
- * @return 登录数据
- */
- @AOPLog
- @CaptchaValid(CaptchaFrom.REGISTER)
- @EnableSetting(value = SettingKey.ENABLE_REGISTER, message = "user.register.off_service")
- @RequestRateLimit(value = 1, lifeCycle = 60)
- @PostMapping("/register")
- public LoginResponse register(@Valid @RequestBody CaptchaData request) {
- return service.register(request.getData());
- }
-
- /**
- * 登录
- *
- * @param request 登录请求
- * @return 登录数据
- */
- @AOPLog
- @CaptchaValid(CaptchaFrom.LOGIN)
- @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service")
- @RequestRateLimit
- @PostMapping("/login")
- public LoginResponse login(@Valid @RequestBody CaptchaData request) {
- return service.login(request.getData());
- }
-
- /**
- * 根据令牌登录,请求头携带 Token 参数,通常用于延续登录令牌
- *
- * @return 登录数据
- */
- @AOPLog
- @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service")
- @RequestRateLimit
- @PostMapping("/login/token")
- public LoginResponse login4Token() {
- return service.login4Token();
- }
-
- /** 登出 */
- @AOPLog
- @RequestRateLimit
- @PostMapping("/logout")
- public void logout() {
- service.logout();
- }
-
- /** 发送邮箱验证邮件 */
- @AOPLog
- @RequiredToken
- @RequestRateLimit(value = 1, lifeCycle = 50)
- @PostMapping("/email/verify")
- public void sendEmailVerify() {
- service.sendEmailVerify();
- }
-
- /**
- * 邮箱验证邮件回调,验证请求的密钥来源于 {@link #sendEmailVerify()} 接口发送的邮件
- *
- * @param request 邮箱验证请求
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit(value = 1, lifeCycle = 50)
- @PostMapping("/email/verify/callback")
- public void emailVerifyCallback(@Valid @RequestBody EmailVerifyCallbackRequest request) {
- service.emailVerifyCallback(request.getKey());
- }
-
- /**
- * 修改密码,需要已登录状态,使用旧密码修改
- *
- * @param request 修改密码请求
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/password/update")
- public void updatePassword(@Valid @RequestBody UpdatePasswordRequest request) {
- service.updatePassword(request.getOldValue(), request.getNewValue());
- }
-
- /**
- * 发送用于重置密码的忘记密码邮件,入参数据可能是 UID、邮箱或用户名,该数据目标用户的邮箱需要通过验证
- *
- * @param request 忘记密码邮件请求
- */
- @AOPLog
- @CaptchaValid(CaptchaFrom.RESET_PASSWORD)
- @RequestRateLimit(value = 1, lifeCycle = 50)
- @PostMapping("/password/forget")
- public void sendPasswordForgetVerify(@Valid @RequestBody CaptchaData request) {
- service.sendPasswordForgetVerify(request.getData());
- }
-
- /**
- * 修改密码,不需要登录状态,入参数据的密钥来源于 {@link #sendPasswordForgetVerify(CaptchaData)} 接口发送的邮件
- *
- * @param request 重置密码请求
- */
- @AOPLog
- @RequestRateLimit(value = 1, lifeCycle = 50)
- @PostMapping("/password/reset")
- public void resetPasswordByKey(@Valid @RequestBody UpdatePasswordByKeyRequest request) {
- service.resetPasswordByKey(request.getKey(), request.getNewPassword());
- }
-
- /**
- * 注销账号,此操作将会标记此用户的所有数据为删除状态
- *
- * @param password 密码
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/cancel")
- public void cancel(@RequestBody String password) {
- service.cancel(password);
- }
-
- /**
- * 获取用户资料
- *
- * @param userId 目标用户 ID
- * @return 用户资料
- */
- @AOPLog
- @RequestRateLimit
- @PostMapping("/view/{userId}")
- public UserView view(@Min(1) @NotNull @PathVariable Long userId) throws Exception {
- return service.view(userId).doFilter();
- }
-
- /**
- * 更新用户数据
- *
- * @param data 用户数据(包括账号数据)
- */
- @AOPLog
- @RequiredToken
- @EnableSetting(value = SettingKey.ENABLE_USER_UPDATE, message = "user.data.off_service")
- @RequestRateLimit
- @PostMapping("/profile/update")
- public void updateProfile(@Valid UserRequest data) {
- profileService.update(data);
- }
-
- /**
- * 获取用户隐私控制
- *
- * @return 用户资料
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/privacy")
- public UserPrivacy privacy() {
- return privacyService.get(service.getLoginUser().getId());
- }
-
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/privacy/update")
- public void updatePrivacy(@Valid @RequestBody UserPrivacy privacy) {
- privacyService.update(privacy);
- }
-
- /**
- * 获取用户设置
- *
- * @return 用户设置
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/config")
- public UserConfig config() {
- return configService.get(service.getLoginUser().getId());
- }
-
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/config/update")
- public void updateConfig(@Valid @RequestBody UserConfig config) {
- configService.update(config);
- }
-
- /**
- * 获取用户评论
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/comment/list")
- public PageResult listComment(@Valid @RequestBody Page page) {
- Comment example = new Comment();
- example.setUserId(service.getLoginUser().getId());
- page.setEqualsExample(example);
- return commentService.page(page);
- }
-
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/comment/delete")
- public void deleteComment(@RequestBody Long commentId) {
- commentService.get(commentId);
- commentService.delete(commentId);
- }
-
- /**
- * 获取用户被回复的评论
- */
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/comment/reply/list")
- public PageResult listCommentReply(@Valid @RequestBody Page page) {
- CommentReply example = new CommentReply();
- example.setReceiverId(service.getLoginUser().getId());
- page.setEqualsExample(example);
- return commentReplyService.page(page);
- }
-
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/comment/reply/delete")
- public void deleteCommentReply(@RequestBody Long replyId) {
- CommentReply reply = commentReplyService.get(replyId);
- TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner");
- commentReplyService.delete(replyId);
- }
-
- @AOPLog
- @RequiredToken
- @RequestRateLimit
- @PostMapping("/comment/reply/ignore")
- public void ignoreCommentReply(@RequestBody Long replyId) {
- CommentReply reply = commentReplyService.get(replyId);
- TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner");
- reply.setIgnoredAt(Time.now());
- commentReplyService.update(reply);
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Article.java b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java
new file mode 100644
index 0000000..ce3353a
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java
@@ -0,0 +1,82 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.api.modules.common.bean.attachment.SettingSupport;
+import com.imyeyu.spring.annotation.table.PageIgnore;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 文章
+ *
+ * @author 夜雨
+ * @since 2021-03-01 17:10
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class Article extends UUIDEntity implements SettingSupport {
+
+ /**
+ * 类型
+ *
+ * @author 夜雨
+ * @since 2026-06-05 15:55
+ */
+ public enum BizType {
+
+ /** 博客 */
+ BLOG
+ }
+
+ /**
+ * 正文类型
+ *
+ * @author 夜雨
+ * @since 2026-06-05 15:56
+ */
+ public enum ContentType {
+
+ /** 纯文本 */
+ TEXT,
+
+ /** Markdown */
+ MARKDOWN,
+
+ /** TipTap 富文本 */
+ TIP_TAP
+ }
+
+ /** 业务类型 */
+ protected BizType bizType;
+
+ /** 业务 ID */
+ protected String bizId;
+
+ /** 标题 */
+ protected String title;
+
+ /** 摘要 */
+ protected String digest;
+
+ /** 正文类型 */
+ protected ContentType contentType;
+
+ /** 内容 */
+ @PageIgnore
+ protected String content;
+
+ /** 阅读数量 */
+ protected Integer reads;
+
+ /** 喜欢数量 */
+ protected Integer likes;
+
+ @Transient
+ protected List settingList;
+
+ @Transient
+ protected List commentList;
+}
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 470c6e5..6b1c10d 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
@@ -2,16 +2,17 @@ package com.imyeyu.api.modules.common.entity;
import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.TimiServerAPI;
-import com.imyeyu.api.bean.MultilingualHandler;
+import com.imyeyu.api.modules.common.bean.attachment.AccessKey;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.java.ref.Ref;
+import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.table.Transient;
-import com.imyeyu.spring.entity.Entity;
-import lombok.AllArgsConstructor;
+import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
-import lombok.Getter;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.IOException;
import java.io.InputStream;
/**
@@ -20,61 +21,33 @@ import java.io.InputStream;
*/
@Data
@EqualsAndHashCode(callSuper = true)
-public class Attachment extends Entity implements MultilingualHandler {
+public class Attachment extends UUIDEntity {
- /**
- * 附件类型
- *
- * @author 夜雨
- * @since 2023-08-21 16:32
- */
- @Getter
- @AllArgsConstructor
+ ///
+ ///
+ /// @author 夜雨
+ /// @since 2026-07-17 20:52
public enum BizType {
- /** 用户 */
USER,
- /** 文章 */
- ARTICLE,
+ GAO_CUSTOMER,
- /** Git */
- GIT,
+ GAO_REGISTRATION_EVENT_RECORD,
- /** 歌词 */
- LYRIC,
+ TEMP_FILE,
- /** ForeverMC */
- FMC,
-
- /** 镜像 */
- MIRROR,
-
- /** 日记 */
- JOURNAL,
-
- /** 日记出行 */
- JOURNAL_TRAVEL,
-
- /** 日记瞬间 */
- JOURNAL_MOMENT,
-
- /** 系统 */
- SYSTEM,
-
- /** 临时文件 */
- TEMP_FILE
+ MIRROR
}
protected BizType bizType;
- protected Long bizId;
+ protected String bizId;
protected String attachType;
protected String mongoId;
- @MultilingualField
protected String title;
protected String name;
@@ -93,9 +66,22 @@ public class Attachment extends Entity implements MultilingualHandler {
protected Long destroyAt;
+ /** 数据流 */
@Transient
protected InputStream inputStream;
+ /** 保存自临时文件 ID */
+ @Transient
+ protected String tempFileId;
+
+ /** 访问密钥 */
+ @Transient
+ protected AccessKey accessKey;
+
+ /** 请求文件 */
+ @Transient
+ protected MultipartFile file;
+
public InputStream openInputStream() {
AttachmentService service = TimiServerAPI.applicationContext.getBean(AttachmentService.class);
return service.getInputStreamByMongoId(mongoId);
@@ -108,4 +94,9 @@ public class Attachment extends Entity implements MultilingualHandler {
public > T getAttachTypeValue(Class attachTypeClass) {
return Ref.toType(attachTypeClass, attachType);
}
+
+ /** 执行接口流式返回 */
+ public void doResponse() throws IOException {
+ TimiSpring.responseRangeStream(openInputStream(), size);
+ }
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java
index df4315c..c5c1834 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java
@@ -1,20 +1,11 @@
package com.imyeyu.api.modules.common.entity;
-import com.imyeyu.api.modules.blog.entity.Article;
-import com.imyeyu.api.modules.blog.service.implement.ArticleServiceImplement;
-import com.imyeyu.api.modules.common.bean.CommentSupport;
-import com.imyeyu.api.modules.common.vo.user.UserView;
-import com.imyeyu.api.modules.git.bean.gitea.Repository;
-import com.imyeyu.api.modules.git.service.implement.IssueServiceImplement;
-import com.imyeyu.api.modules.git.service.implement.MergeServiceImplement;
+import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.annotation.table.Transient;
-import com.imyeyu.spring.entity.Entity;
-import com.imyeyu.spring.service.GettableService;
+import com.imyeyu.spring.entity.UUIDEntity;
import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
-import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@@ -23,71 +14,94 @@ import java.util.List;
* 评论
*
* @author 夜雨
- * @since 2021-02-25 14:46
+ * @since 2026-05-27 17:20
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
-public class Comment extends Entity {
-
+public class Comment extends UUIDEntity {
/**
- * 关联业务类型
- *
- * TODO 添加模块名称以便区别邮件通知推送来源,使用多语言键
+ * 评论状态
*
* @author 夜雨
- * @since 2023-08-06 23:42
+ * @since 2026-05-27 17:20
*/
- @Getter
- @AllArgsConstructor
- public enum BizType {
+ public enum Status {
- ARTICLE(ArticleServiceImplement.class),
+ /** 正常 */
+ NORMAL,
- GIT_ISSUE(IssueServiceImplement.class),
-
- GIT_MERGE(MergeServiceImplement.class);
-
- final Class extends GettableService extends CommentSupport, Long>> serviceClass;
+ /** 隐藏 */
+ HIDDEN
}
- /** 关联业务类型 */
- private BizType bizType;
+ /** 业务类型 */
+ protected String bizType;
- /** 关联业务 ID */
- private Long bizId;
+ /** 业务 ID */
+ protected String bizId;
- /** 发送用户 ID,登录用户评论有值,游客无 */
- private Long userId;
+ /** 父评论 ID,主评论为 null */
+ protected String parentId;
- /** 发送用户昵称,游客评论有值,登录用户无 */
- private String nick;
+ /** 根评论 ID,主评论为自身 ID */
+ protected String rootId;
- /** 评论数据 */
+ /** 评论用户 ID */
+ protected String userId;
+
+ /** 评论用户昵称,游客评论时使用 */
+ protected String userNick;
+
+ /** 被回复用户 ID */
+ protected String replyUserId;
+
+ /** 被回复用户昵称,被回复人为游客时使用 */
+ protected String replyUserNick;
+
+ /** 评论层级,1 为主评论 */
+ protected Integer level;
+
+ /** 评论内容 */
@NotBlank
- private String content;
+ protected String content;
- /** 发送用户 IP */
- private String ip;
+ /** IP 地址 */
+ protected String ip;
- /** 回复数量 */
+ /** 回复数 */
+ protected Integer replyCount;
+
+ /** 点赞数 */
+ protected Integer likeCount;
+
+ /** 评论状态 */
+ protected Status status;
+
+ /** true 为置顶 */
+ protected Boolean isTop;
+
+ /** 最后回复时间 */
+ protected Long lastReplyAt;
+
+ /** 评论用户信息 */
@Transient
- private long repliesLength;
+ protected User user;
- /** 发送用户 */
+ /** 被回复用户信息 */
@Transient
- private UserView user;
+ protected User replyUser;
- /** 关联文章 */
+ /** 子评论列表 */
@Transient
- private Article article;
+ protected List replyList;
- /** 关联仓库 */
+ /** true 为当前用户已点赞 */
@Transient
- private Repository repository;
+ protected Boolean liked;
- /** 回复列表 */
+ /** true 为当前用户已不喜欢 */
@Transient
- private List replies;
+ protected Boolean unliked;
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java b/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java
deleted file mode 100644
index ddada70..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import com.imyeyu.api.modules.common.vo.user.UserView;
-import com.imyeyu.spring.annotation.table.Transient;
-import com.imyeyu.spring.entity.Entity;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * 评论回复
- *
- * @author 夜雨
- * @since 2021-03-01 17:11
- */
-@Data
-@NoArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class CommentReply extends Entity {
-
- /** 所属评论 ID */
- private Long commentId;
-
- /** 被回复的回复,回复主评论时为 NULL */
- private Long replyId;
-
- /** 发送用户 ID,登录用户回复有值,游客无 */
- private Long senderId;
-
- /** 回复用户 ID,系统用户回复有值,游客无 */
- private Long receiverId;
-
- /** 发送用户昵称,游客回复有值,登录用户无 */
- private String senderNick;
-
- /** 回复用户昵称,游客回复有值,系统用户无 */
- private String receiverNick;
-
- /** 回复数据 */
- private String content;
-
- /** 发送用户 IP */
- private String ip;
-
- /** 被回复用户忽略该回复的时间 */
- private Long ignoredAt;
-
- /** 所属评论 */
- @Transient
- private Comment comment;
-
- /** 发送用户 */
- @Transient
- private UserView sender;
-
- /** 回复用户 */
- @Transient
- private UserView receiver;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java b/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java
new file mode 100644
index 0000000..c8bdc78
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java
@@ -0,0 +1,47 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 数据字典
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:40
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DataDict extends UUIDEntity {
+
+ /** 业务类型 */
+ private String bizType;
+
+ /** 代码 */
+ private String code;
+
+ /** 父级代码 */
+ private String parentCode;
+
+ /** 字典名称语言键 */
+ private String nameLangId;
+
+ /** 字典名称(读取) */
+ @Transient
+ protected String name;
+
+ /** 搜索名称 */
+ @Transient
+ protected String searchName;
+
+ /** 字典中文名称(写入) */
+ @Transient
+ protected String zhCN;
+
+ /** 选项列表 */
+ @Transient
+ protected List dataDictOptionList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java b/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java
new file mode 100644
index 0000000..e1c2df2
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java
@@ -0,0 +1,50 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 数据字典选项
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:40
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DataDictOption extends UUIDEntity {
+
+ /** 所属字典 ID */
+ private String dictId;
+
+ /** 值代码 */
+ private String valueCode;
+
+ /** 父级值代码 */
+ private String valueParentCode;
+
+ /** 值语言键 */
+ private String valueLangId;
+
+ /** 值(读取) */
+ @Transient
+ protected String value;
+
+ /** 中文值(写入) */
+ @Transient
+ protected String zhCN;
+
+ @Transient
+ protected String searchValue;
+
+ /** 所属字典 */
+ @Transient
+ protected DataDict dataDict;
+
+ /** 附加参数设置 */
+ @Transient
+ protected List settingList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java
new file mode 100644
index 0000000..fb7080b
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java
@@ -0,0 +1,34 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.DeleteColumn;
+import com.imyeyu.spring.annotation.table.Id;
+import com.imyeyu.spring.entity.Creatable;
+import com.imyeyu.spring.entity.Deletable;
+import com.imyeyu.spring.entity.IDEntity;
+import lombok.Data;
+
+/**
+ * 邮件过滤
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:40
+ */
+@Data
+public class EmailFilter implements IDEntity, Creatable, Deletable {
+
+ /** 主键 */
+ @Id
+ @AutoUUID
+ private String id;
+
+ /** 邮箱 */
+ private String email;
+
+ /** 创建时间 */
+ private Long createdAt;
+
+ /** 删除时间 */
+ @DeleteColumn
+ private Long deletedAt;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java
deleted file mode 100644
index f422502..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import com.imyeyu.spring.annotation.table.AutoUUID;
-import com.imyeyu.spring.annotation.table.Id;
-import lombok.Data;
-
-/**
- * 邮件队列
- *
- * @author 夜雨
- * @since 2021-08-24 14:59
- */
-@Data
-public class EmailQueue {
-
- /**
- * 业务类型
- *
- * @author 夜雨
- * @since 2021-08-24 15:54
- */
- public enum BizType {
-
- /** 回复提醒 */
- REPLY_REMINAD,
-
- /** 邮箱验证 */
- EMAIL_VERIFY,
-
- /** 重置密码 */
- RESET_PASSWORD
- }
-
- @Id
- @AutoUUID
- private String UUID;
-
- private BizType bizType;
-
- private Long bizId;
-
- private Long sendAt;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java
deleted file mode 100644
index 25ef10c..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
-
-/**
- * @author 夜雨
- * @since 2021-08-24 18:00
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class EmailQueueLog extends Entity {
-
- private String UUID;
- private EmailQueue.BizType bizType;
- private Long bizId;
- private String sendTo;
- private Long sendAt;
-
- private Boolean isSent;
- private String exceptionMsg;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java b/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java
deleted file mode 100644
index 90b5ea1..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
-
-/**
- * 反馈
- *
- * @author 夜雨
- * @since 2021-11-16 22:22
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class Feedback extends Entity {
-
- private String from;
- private String email;
- private String data;
- private String ip;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java b/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java
index bf1bb16..794c2e6 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java
@@ -2,7 +2,7 @@ package com.imyeyu.api.modules.common.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
+import com.imyeyu.spring.entity.UUIDEntity;
/**
* 字体图标
@@ -12,7 +12,7 @@ import com.imyeyu.spring.entity.Entity;
*/
@Data
@EqualsAndHashCode(callSuper = true)
-public class Icon extends Entity {
+public class Icon extends UUIDEntity {
/** 名称 */
private String name;
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java b/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java
index 82e81fc..19d59fd 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java
@@ -1,28 +1,43 @@
package com.imyeyu.api.modules.common.entity;
import com.imyeyu.java.bean.Language;
-import com.imyeyu.spring.entity.Creatable;
-import com.imyeyu.spring.entity.Deletable;
-import com.imyeyu.spring.entity.IDEntity;
-import com.imyeyu.spring.entity.Updatable;
+import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
-import java.io.Serializable;
-
/**
* @author 夜雨
* @since 2023-10-24 16:41
*/
@Data
@EqualsAndHashCode(callSuper = true)
-public class Multilingual extends Language implements Serializable, IDEntity, Creatable, Updatable, Deletable {
+public class Multilingual extends UUIDEntity {
- protected Long id;
+ protected String zhCN;
- protected Long createdAt;
+ protected String zhTW;
- protected Long updatedAt;
+ protected String enUS;
- protected Long deletedAt;
+ protected String ruRU;
+
+ protected String jaJP;
+
+ protected String deDE;
+
+ protected String koKR;
+
+ protected Long lastAccessAt;
+
+ public String getValue(Language.Enum language) {
+ return switch (language) {
+ case zh_CN -> zhCN;
+ case zh_TW -> zhTW;
+ case en_US -> enUS;
+ case ru_RU -> ruRU;
+ case ja_JP -> jaJP;
+ case de_DE -> deDE;
+ case ko_KR -> koKR;
+ };
+ }
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java b/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java
new file mode 100644
index 0000000..286f6a9
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java
@@ -0,0 +1,44 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.Id;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 通知
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:40
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class Notify extends UUIDEntity {
+
+ /** 主键 */
+ @Id
+ @AutoUUID
+ private String id;
+
+ /** 业务类型 */
+ private String bizType;
+
+ /** 提醒业务类型 */
+ private String bizRemindedType;
+
+ /** 提醒业务 ID */
+ private String bizRemindedId;
+
+ /** 参数 */
+ private String args;
+
+ /** 创建者 */
+ private String createdBy;
+
+ @Transient
+ protected List detailList;
+}
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
new file mode 100644
index 0000000..48615ed
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java
@@ -0,0 +1,97 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 通知详情
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:40
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class NotifyDetail extends UUIDEntity {
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2025-10-28 15:53
+ */
+ @AllArgsConstructor
+ public enum MsgType {
+
+ SMS,
+
+ MAIL,
+
+ HTTP,
+
+ WECHAT;
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2025-10-28 15:55
+ */
+ public enum Status {
+
+ WAITING,
+
+ REMINDED,
+
+ TIMEOUT,
+
+ CANCEL,
+
+ FAIL
+ }
+
+ /** 所属通知 ID */
+ private String notifyId;
+
+ /** 消息类型 */
+ private MsgType msgType;
+
+ /** 模板 ID */
+ private String templateId;
+
+ /** 标题 */
+ private String subject;
+
+ /** 内容 */
+ private String data;
+
+ /** 参数 */
+ private String args;
+
+ /** 发送来源 */
+ private String sendFrom;
+
+ /** 发送去向 */
+ private String sendTo;
+
+ /** 发送时间 */
+ private Long sendAt;
+
+ /** 重试计数 */
+ private Integer retry;
+
+ /** 超时时间 */
+ private Long timeoutAt;
+
+ /** 结果描述 */
+ private String resultDesc;
+
+ /** 状态 */
+ private Status status;
+
+ @Transient
+ protected Notify notify;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java b/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java
index 7cb7af2..2800b0b 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java
@@ -1,10 +1,20 @@
package com.imyeyu.api.modules.common.entity;
-import com.imyeyu.api.modules.common.bean.SettingKey;
-import com.imyeyu.spring.annotation.table.Id;
-import com.imyeyu.spring.entity.Creatable;
-import com.imyeyu.spring.entity.Updatable;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.imyeyu.api.TimiServerAPI;
+import com.imyeyu.api.modules.common.bean.SettingModule;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import com.imyeyu.spring.util.ResponseView;
+import com.imyeyu.utils.Time;
import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
/**
* 系统配置
@@ -13,7 +23,9 @@ import lombok.Data;
* @since 2021-07-20 21:46
*/
@Data
-public class Setting implements Creatable, Updatable {
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class Setting extends UUIDEntity {
/**
*
@@ -21,31 +33,486 @@ public class Setting implements Creatable, Updatable {
* @author 夜雨
* @since 2025-01-10 17:08
*/
- public enum Type {
-
- INTEGER,
+ public enum ValueType {
+ /** 字符串 */
STRING,
- JSON,
+ /** 单选 */
+ SELECT_RADIO,
- YAML,
+ /** 多选 */
+ SELECT_CHECKBOX,
+
+ /** 整数 */
+ INTEGER,
+
+ /** 十六进制颜色 */
+ COLOR,
+
+ /** 浮点数 */
+ FLOAT,
+
+ /** 日期 */
+ DATE,
+
+ /** 日期时间 */
+ DATETIME,
+
+ /** 时间 */
+ TIME,
+
+ /** 时长 */
+ DURATION,
+
+ /** 布尔值 */
+ BOOLEAN,
+
+ /** JSON 列表 */
+ JSON_LIST,
+
+ /** JSON 对象 */
+ JSON_OBJECT
}
- @Id
- private SettingKey key;
+ @JsonView(ResponseView.Public.class)
+ private String bizType;
+ @JsonView(ResponseView.Public.class)
+ private String bizId;
+
+ @JsonView(ResponseView.Public.class)
+ private String code;
+
+ @JsonView(ResponseView.Public.class)
+ private String description;
+
+ @JsonView(ResponseView.Public.class)
private String value;
- private Type type;
+ @JsonView(ResponseView.Public.class)
+ private ValueType valueType;
- private boolean isPrivate;
+ @JsonView(ResponseView.Public.class)
+ private JsonNode valueArgs;
- private Long createdAt;
+ @JsonView(ResponseView.Admin.class)
+ private Boolean isPrivate;
- private Long updatedAt;
+ @Transient
+ private Enum> keyRaw;
- public boolean isPublic() {
- return !isPrivate;
+ public Setting(Enum> key) {
+ setKeyRaw(key);
+ }
+
+ public Setting(Enum> key, Object value, ValueType valueType) {
+ setKeyRaw(key);
+ this.value = value.toString();
+ this.valueType = valueType;
+ }
+
+ /**
+ * 以字符串形式返回配置值
+ *
+ * @return 配置字符串值
+ */
+ public String asString() {
+ return value;
+ }
+
+ public Long asTime() {
+ return Time.parseToMS(asString());
+ }
+
+ /**
+ * 以整数形式返回配置值
+ *
+ * @return 配置整数值
+ * @throws NumberFormatException 配置值不是合法整数
+ */
+ public Integer asInteger() {
+ return Integer.parseInt(value);
+ }
+
+ /**
+ * 以长整数形式返回配置值
+ *
+ * @return 配置长整数值
+ * @throws NumberFormatException 配置值不是合法长整数
+ */
+ public Long asLong() {
+ return Long.parseLong(value);
+ }
+
+ /**
+ * 以双精度浮点数形式返回配置值
+ *
+ * @return 配置双精度浮点数值
+ * @throws NumberFormatException 配置值不是合法双精度浮点数
+ */
+ public Double asDouble() {
+ return Double.parseDouble(value);
+ }
+
+ /**
+ * 以布尔值形式返回配置值
+ *
+ * @return 配置布尔值
+ */
+ public Boolean asBoolean() {
+ return Boolean.parseBoolean(value);
+ }
+
+ public boolean isTrue() {
+ return asBoolean();
+ }
+
+ public boolean isFalse() {
+ return !isTrue();
+ }
+
+ /**
+ * 以 JSON 节点形式返回配置值
+ *
+ * @return 配置 JSON 节点
+ * @throws IllegalArgumentException 配置值不是合法 JSON
+ */
+ public JsonNode asJsonNode() {
+ return asJsonNode(JsonNode.class);
+ }
+
+ /**
+ * 以 JSON 数组形式返回配置值
+ *
+ * @return 配置 JSON 数组
+ * @throws IllegalArgumentException 配置值不是合法 JSON 数组
+ */
+ public ArrayNode asArrayNode() {
+ return asJsonNode(ArrayNode.class);
+ }
+
+ /**
+ * 以 JSON 对象形式返回配置值
+ *
+ * @return 配置 JSON 对象
+ * @throws IllegalArgumentException 配置值不是合法 JSON 对象
+ */
+ public ObjectNode asObjectNode() {
+ return asJsonNode(ObjectNode.class);
+ }
+
+ /**
+ * 解析配置 JSON 值
+ *
+ * @param type 目标类型
+ * @param 目标类型
+ * @return 解析后的 JSON 节点
+ * @throws IllegalArgumentException 配置值不是合法 JSON
+ */
+ public T asJsonNode(Class type) {
+ if (value == null) {
+ return null;
+ }
+ try {
+ ObjectMapper mapper = TimiServerAPI.applicationContext.getBean(ObjectMapper.class);
+ return mapper.readValue(value, type);
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("setting value is not valid json: %s".formatted(value), e);
+ }
+ }
+
+ public void setKeyRaw(Enum> key) {
+ this.keyRaw = key;
+ this.bizType = SettingModule.fromClass(key.getClass()).toString();
+ this.code = key.toString();
+ }
+
+ public boolean is(Enum> key) {
+ return SettingModule.fromClass(key.getClass()).toString().equals(bizType) && key.toString().equals(code);
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:23
+ */
+ public static final class Module {
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:39
+ */
+ public enum Common {
+
+ /** 微信收款码 */
+ WECHAT_RECEIVE_QR_CODE
+ }
+
+ /**
+ * 域名
+ *
+ * @author 夜雨
+ * @since 2026-04-26 17:18
+ */
+ public enum Domain {
+
+ ROOT,
+
+ API,
+
+ GIT,
+
+ BLOG,
+
+ SPACE,
+
+ FOREVER_MC
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:14
+ */
+ public enum Blog {
+
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:03
+ */
+ public enum User {
+
+ /** 启用登录 */
+ ENABLE_LOGIN,
+
+ /** 启用注册 */
+ ENABLE_REGISTER,
+
+ /** 允许数据更新 */
+ ENABLE_PROFILE_UPDATE,
+
+ /** 令牌有效期 */
+ TOKEN_TTL
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:21
+ */
+ public enum Comment {
+
+ /** 启用服务 */
+ ENABLE
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:03
+ */
+ public enum ICP {
+
+ IMYEYU_COM
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 15:01
+ */
+ public enum ForeverMC {
+
+ PLAYER_BIND_MAX,
+
+ LOGIN_TOKEN_TTL
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:57
+ */
+ public enum Multilingual {
+
+ /** 翻译 API */
+ TRANSLATE_API,
+
+ /** 翻译 AppId */
+ TRANSLATE_APP_ID,
+
+ /** 翻译密钥 */
+ TRANSLATE_KEY,
+
+ /** 翻译计划任务表达式 */
+ TRANSLATE_CORN,
+
+ /** 缓存时长 */
+ CACHE_TTL
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:55
+ */
+ public enum Gitea {
+
+ /** 接口地址 */
+ API,
+
+ /** 关于页面所属文章 ID */
+ ABOUT_ARTICLE_ID,
+
+ /** 仓库文件地址 */
+ REPOSITORY_PATH
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:52
+ */
+ public enum Music {
+
+ /** 启用服务 */
+ ENABLE,
+
+ /** 通信消息最大帧长度 */
+ MAX_FRAME_LENGTH,
+
+ /** 受控端 IP */
+ PLAYER_IP,
+
+ /** 受控端端口 */
+ PLAYER_PORT,
+
+ /** 控制端 IP */
+ CONTROLLER_IP,
+
+ /** 控制端端口 */
+ CONTROLLER_PORT,
+
+ /** 控制端入口地址 */
+ CONTROLLER_URI,
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:49
+ */
+ public enum Journal {
+
+ /** 启用服务 */
+ ENABLE,
+
+ /** 审核中 */
+ REVIEWING,
+
+ /** 访问密钥 */
+ KEY,
+
+ /** 微信小程序 AppId */
+ APP_ID,
+
+ /** 微信小程序 AppSecret */
+ APP_SECRET,
+
+ /** 备忘录内容 */
+ MEMO,
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:47
+ */
+ public enum TempFile {
+
+ /** 启用服务 */
+ ENABLE,
+
+ /** 最小缓存时长 */
+ TTL_MIN,
+
+ /** 默认缓存时长 */
+ TTL_DEFAULT,
+
+ /** 最大缓存时长 */
+ TTL_MAX,
+
+ /** 已过期文件残留时长 */
+ RESIDUE_TTL,
+
+ /** 每个 IP 限制有效临时文件容量 */
+ LIMIT_SIZE_OF_IP
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-27 14:35
+ */
+ public enum FileSync {
+
+ ENABLE,
+
+ CRON,
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:23
+ */
+ public enum System {
+
+ /** 文件系统基准路径 */
+ FILE_BASE_PATH,
+
+ /** 设置缓存时长 */
+ SETTING_TTL,
+
+ /** 哀悼滤镜 */
+ ENABLE_MOURN_FILTER,
+ }
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @since 2026-04-24 14:23
+ */
+ public enum SystemStatus {
+
+ /** 状态采集数据量 */
+ LIMIT,
+
+ /** 状态采集网卡 */
+ NETWORK_MAC,
+
+ /** 状态采集频率 */
+ RATE,
+ }
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java b/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java
index 547b88f..eb2bb7e 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java
@@ -1,7 +1,7 @@
package com.imyeyu.api.modules.common.entity;
-import com.imyeyu.api.bean.MultilingualHandler;
-import com.imyeyu.spring.entity.Entity;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -11,29 +11,13 @@ import lombok.EqualsAndHashCode;
*/
@Data
@EqualsAndHashCode(callSuper = true)
-public class Tag extends Entity implements MultilingualHandler {
+public class Tag extends UUIDEntity {
- /**
- *
- *
- * @author 夜雨
- * @since 2024-08-28 14:26
- */
- public enum BizType {
+ protected String langKey;
- ARTICLE,
-
- MUSIC,
-
- SERVER_FILE,
-
- WALLPAPER
- }
-
- protected BizType bizType;
-
- protected String bizID;
-
- @MultilingualHandler.MultilingualField
+ @Transient
protected String value;
+
+ @Transient
+ protected String zhCN;
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java b/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java
new file mode 100644
index 0000000..b2d9059
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java
@@ -0,0 +1,25 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 标签应用
+ *
+ * @author 夜雨
+ * @since 2025-12-02 17:01
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TagApply extends UUIDEntity {
+
+ /** 业务类型 */
+ protected String bizType;
+
+ /** 业务 ID */
+ protected String bizId;
+
+ /** 标签 ID */
+ protected String tagId;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Task.java b/src/main/java/com/imyeyu/api/modules/common/entity/Task.java
deleted file mode 100644
index 2039648..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Task.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import com.imyeyu.spring.entity.Entity;
-
-import java.util.List;
-
-/**
- * 开发任务
- *
- * @author 夜雨
- * @since 2022-02-26 11:12
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class Task extends Entity {
-
- /**
- * 任务状态
- *
- * @author 夜雨
- * @since 2022-02-26 11:53
- */
- @Getter
- public enum Status {
-
- UPDATE, WITH, WAIT, KEEP, DIE;
-
- final int sort = ordinal();
- }
-
- private String name;
- private Status status;
- private String digest;
-
- // 关联数据
- private List details;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java b/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java
deleted file mode 100644
index 508e59b..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import com.imyeyu.spring.entity.Entity;
-
-/**
- * 开发任务详细信息
- *
- * @author 夜雨
- * @since 2022-02-27 17:58
- */
-@Data
-@NoArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class TaskDetail extends Entity {
-
- /**
- * 类型
- *
- * @author 夜雨
- * @since 2022-02-27 18:03
- */
- @Getter
- public enum Type {
-
- BUG, FEATURE
- }
-
- /**
- * 状态
- *
- * @author 夜雨
- * @since 2022-02-27 18:06
- */
- @Getter
- public enum Status {
-
- UPDATE, WAIT, FINISH, CLOSE
- }
-
- private Long taskId;
- private Type type;
- private Status status;
- private String digest;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Template.java b/src/main/java/com/imyeyu/api/modules/common/entity/Template.java
deleted file mode 100644
index e4ffdee..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Template.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
-
-/**
- * @author 夜雨
- * @since 2023-09-21 00:53
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class Template extends Entity {
-
- /**
- *
- *
- * @author 夜雨
- * @since 2023-09-22 16:38
- */
- public enum BizType {
-
- GIT,
-
- FOREVER_MC
- }
-
- private BizType bizType;
-
- private String bizCode;
-
- private String data;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/User.java b/src/main/java/com/imyeyu/api/modules/common/entity/User.java
deleted file mode 100644
index 58a3968..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/User.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
-import com.imyeyu.utils.Time;
-
-/**
- * 用户
- *
- * @author 夜雨
- * @since 2021-03-01 17:11
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class User extends Entity {
-
- /**
- *
- *
- * @author 夜雨
- * @since 2024-02-21 14:48
- */
- public enum AttachType {
-
- AVATAR,
-
- WRAPPER,
-
- LICENSE,
-
- DEFAULT_AVATAR,
-
- DEFAULT_WRAPPER
- }
-
- /** 用户名 */
- protected String name;
-
- /** 密码 */
- protected String password;
-
- /** 邮箱 */
- protected String email;
-
- /** 邮箱验证时间 */
- protected Long emailVerifyAt;
-
- /** 解除禁言时间 */
- protected Long unmuteAt;
-
- /** 解除封禁时间 */
- protected Long unbanAt;
-
- /** @return true 为禁言中 */
- public boolean isMuting() {
- return unmuteAt != null && Time.now() < unmuteAt;
- }
-
- /** @return true 为封禁中 */
- public boolean isBanning() {
- return unbanAt != null && Time.now() < unbanAt;
- }
-
- public boolean emailVerified() {
- return emailVerifyAt != null;
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java
deleted file mode 100644
index 0eafd0b..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import com.imyeyu.spring.annotation.table.Id;
-import com.imyeyu.spring.entity.Updatable;
-import jakarta.validation.constraints.Min;
-import lombok.Data;
-
-/**
- * 用户设置
- *
- * @author 夜雨
- * @since 2021-08-12 15:06
- */
-@Data
-public class UserConfig implements Updatable {
-
- @Min(1)
- @Id
- private Long userId;
-
- private Boolean emailReplyRemind;
-
- private Long updatedAt;
-
- public UserConfig(Long uid) {
- this.userId = uid;
- emailReplyRemind = true;
- }
-
- public Boolean isEmailReplyRemind() {
- return emailReplyRemind;
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java
deleted file mode 100644
index 0e4e882..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import com.imyeyu.java.ref.Ref;
-import com.imyeyu.spring.annotation.table.Id;
-import com.imyeyu.spring.entity.Updatable;
-import jakarta.validation.constraints.Min;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.lang.reflect.Field;
-import java.util.List;
-
-/**
- * 用户隐私控制
- *
- * @author 夜雨
- * @since 2021-07-27 16:51
- */
-@Data
-@NoArgsConstructor
-public class UserPrivacy implements Updatable {
-
- @Min(1)
- @Id
- private Long userId;
-
- private boolean email;
- private boolean sex;
- private boolean birthdate;
- private boolean qq;
- private boolean lastLoginAt;
- private boolean createdAt;
-
- private Long updatedAt;
-
- public UserPrivacy(Long uid) {
- this.userId = uid;
- }
-
- /** @return 过滤字段列表 */
- public List listFilterFields() {
- return Ref.listFields(getClass()).stream().filter(f -> {
- try {
- f.setAccessible(true);
- return boolean.class.isAssignableFrom(f.getType()) && !(boolean) f.get(this);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }).map(Field::getName).toList();
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java
deleted file mode 100644
index f78303c..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import com.imyeyu.api.modules.common.bean.ImageType;
-import com.imyeyu.spring.annotation.table.Id;
-import com.imyeyu.spring.entity.Updatable;
-import jakarta.validation.constraints.Max;
-import jakarta.validation.constraints.Min;
-import jakarta.validation.constraints.Pattern;
-import jakarta.validation.constraints.Size;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 用户数据
- *
- * @author 夜雨
- * @since 2021-05-29 15:58
- */
-@Data
-@NoArgsConstructor
-public class UserProfile implements Updatable {
-
- /** 用户 ID */
- @Min(1)
- @Id
- protected Long userId;
-
- /** 封面类型 */
- protected ImageType wrapperType;
-
- /** 头像类型 */
- protected ImageType avatarType;
-
- /** 经验值 */
- protected Integer exp;
-
- /** 性别 */
- @Max(1)
- @Min(0)
- protected Byte sex;
-
- /** 出生日期 */
- @Min(0)
- protected Long birthdate;
-
- /** QQ */
- @Pattern(regexp = "[1-9]\\d{4,14}")
- protected String qq;
-
- /** 说明 */
- @Size(max = 240)
- protected String description;
-
- /** 最近登录 IP */
- protected String lastLoginIP;
-
- /** 最近登录时间 */
- protected Long lastLoginAt;
-
- /** 修改时间 */
- protected Long updatedAt;
-
- public UserProfile(Long userId) {
- this.userId = userId;
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Version.java b/src/main/java/com/imyeyu/api/modules/common/entity/Version.java
deleted file mode 100644
index 455b034..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Version.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.imyeyu.api.modules.common.entity;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import com.imyeyu.spring.entity.Entity;
-
-/**
- * 版本管理
- *
- * @author 夜雨
- * @since 2021-06-10 16:01
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class Version extends Entity {
-
- private String name;
- private String version;
- private String content;
- private String url;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java
new file mode 100644
index 0000000..a6ea683
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java
@@ -0,0 +1,20 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.Article;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 文章
+ *
+ * @author 夜雨
+ * @since 2021-02-23 21:34
+ */
+public interface ArticleMapper extends BaseMapper {
+
+ @Select("UPDATE `article` SET `likes` = `likes` + 1 WHERE `id` = #{articleId}")
+ void like(Long articleId);
+
+ @Select("UPDATE `article` SET `reads` = `reads` + 1 WHERE `id` = #{articleId}")
+ void read(Long articleId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
index 6ffce84..8c4a12a 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
@@ -12,25 +12,46 @@ import java.util.List;
* @author 夜雨
* @since 2023-08-15 10:22
*/
-public interface AttachmentMapper extends BaseMapper, RawMapper {
+public interface AttachmentMapper extends BaseMapper, RawMapper {
/** 有效条件,非删除和销毁 */
String VALID = NOT_DELETE + " AND (`destroy_at` IS NULL OR " + UNIX_TIME + " < `destroy_at`)";
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID + LIMIT_1)
- Attachment selectByBizId(Attachment.BizType bizType, long bizId);
+ Attachment selectByBizId(Attachment.BizType bizType, String bizId);
+
+ @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID)
+ List selectAllByBizId(Attachment.BizType bizType, String bizId);
@Select("SELECT * FROM attachment WHERE mongo_id = #{mongoId} " + VALID + LIMIT_1)
Attachment selectByMongoId(String mongoId);
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1)
- Attachment selectByAttachType(Attachment.BizType bizType, long bizId, Enum> attachType);
+ Attachment selectByAttachType(Attachment.BizType bizType, String bizId, String attachType);
- List listByBizId(Attachment.BizType bizType, Long bizId, List> attachTypes, Page page);
+ List selectByBizId(Attachment.BizType bizType, String bizId, List attachTypes, Page page);
- long countByBizId(Attachment.BizType bizType, Long bizId, List> attachTypes);
+ long countByBizId(Attachment.BizType bizType, String bizId, List attachTypes);
- List listByMd5s(Attachment.BizType bizType, Long bizId, List> attachTypes, List md5s);
+ /**
+ * 按源附件 ID 与请求尺寸查询已缓存缩略图
+ *
+ * @param bizType 源附件业务类型
+ * @param sourceId 源附件 ID
+ * @param requestWidth 请求宽度,{@code null} 表示未指定
+ * @param requestHeight 请求高度,{@code null} 表示未指定
+ * @return 缩略图附件,不存在返回 {@code null}
+ */
+ Attachment selectThumb(Attachment.BizType bizType, String sourceId, Integer requestWidth, Integer requestHeight);
+
+ /**
+ * 查询源附件的所有未销毁缩略图(含已软删除)
+ *
+ * @param sourceId 源附件 ID
+ * @return 缩略图列表
+ */
+ @Select("SELECT * FROM attachment WHERE biz_id = #{sourceId} AND attach_type = 'THUMB' AND is_destroyed = FALSE")
+ List selectThumbsBySourceId(String sourceId);
@Select("SELECT * FROM attachment WHERE `is_destroyed` = FALSE AND `destroy_at` < " + UNIX_TIME)
List selectNeedDestroy();
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java
index de30895..2548d15 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java
@@ -2,18 +2,154 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
+import java.util.List;
+
/**
- * 评论
+ * 评论 Mapper
*
* @author 夜雨
- * @since 2021-2-23 21:33
+ * @since 2026-05-27 17:20
*/
-public interface CommentMapper extends BaseMapper {
+public interface CommentMapper extends BaseMapper {
- long countAll(Comment.BizType bizType, Long bizId);
+ /**
+ * 根据 ID 列表查询评论
+ *
+ * @param idList 评论 ID 列表
+ * @return 评论列表
+ */
+ List listByIdList(List idList);
- @Update("UPDATE comment SET deleted_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE user_id = #{userId} ")
- void deleteByUserId(Long userId);
+ /**
+ * 查询业务下的主评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param offset 起始偏移量
+ * @param limit 查询条数
+ * @return 主评论列表
+ */
+ List selectRootList(String bizType, String bizId, int offset, int limit);
+
+ /**
+ * 统计业务下的主评论数量
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @return 主评论数量
+ */
+ long countRoot(String bizType, String bizId);
+
+ /**
+ * 查询多个根评论下的回复预览列表
+ *
+ * @param rootIdList 根评论 ID 列表
+ * @param limit 每个根评论最多返回条数
+ * @return 回复列表
+ */
+ List selectReplyPreviewByRootIds(List rootIdList, int limit);
+
+ /**
+ * 查询父评论下的回复列表
+ *
+ * @param parentId 父评论 ID
+ * @param offset 起始偏移量
+ * @param limit 查询条数
+ * @return 回复列表
+ */
+ List selectReplyListByParentId(String parentId, int offset, int limit);
+
+ /**
+ * 查询根评论下的回复列表
+ *
+ * @param rootId 根评论 ID
+ * @param offset 起始偏移量
+ * @param limit 查询条数
+ * @return 回复列表
+ */
+ List selectReplyListByRootId(String rootId, int offset, int limit);
+
+ /**
+ * 统计父评论下的回复数量
+ *
+ * @param parentId 父评论 ID
+ * @return 回复数量
+ */
+ long countReplyByParentId(String parentId);
+
+ /**
+ * 统计根评论下的回复数量
+ *
+ * @param rootId 根评论 ID
+ * @return 回复数量
+ */
+ long countReplyByRootId(String rootId);
+
+ /**
+ * 查询多个根评论下的全部回复列表
+ *
+ * @param rootIdList 根评论 ID 列表
+ * @return 回复列表
+ */
+ List selectAllReplyListByRootIds(List rootIdList);
+
+ /**
+ * 查询根评论下的全部回复列表
+ *
+ * @param rootId 根评论 ID
+ * @return 回复列表
+ */
+ List selectAllReplyListByRootId(String rootId);
+
+ /**
+ * 查询单层评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param parentId 父评论 ID
+ * @param offset 起始偏移量
+ * @param limit 查询条数
+ * @return 评论列表
+ */
+ List selectLayerList(String bizType, String bizId, String parentId, int offset, int limit);
+
+ /**
+ * 统计单层评论数量
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param parentId 父评论 ID
+ * @return 评论数量
+ */
+ long countLayer(String bizType, String bizId, String parentId);
+
+ /**
+ * 查询根评论最新回复时间
+ *
+ * @param rootId 根评论 ID
+ * @return 最新回复时间
+ */
+ @Select("SELECT created_at FROM comment WHERE root_id = #{rootId} AND parent_id IS NOT NULL" + NOT_DELETE + "ORDER BY created_at DESC LIMIT 1")
+ Long selectLatestReplyAtByRootId(String rootId);
+
+ /**
+ * 调整回复数
+ *
+ * @param id 评论 ID
+ * @param delta 变更值
+ */
+ @Update("UPDATE comment SET reply_count = GREATEST(reply_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE)
+ void increaseReplyCount(String id, int delta);
+
+ /**
+ * 调整点赞数
+ *
+ * @param id 评论 ID
+ * @param delta 变更值
+ */
+ @Update("UPDATE comment SET like_count = GREATEST(like_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE)
+ void increaseLikeCount(String id, int delta);
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java
deleted file mode 100644
index 89db088..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.util.List;
-
-/**
- * 评论回复提醒队列
- *
- * @author 夜雨
- * @since 2021-08-25 00:15
- */
-public interface CommentRemindQueueMapper extends BaseMapper {
-
- @Select("SELECT * FROM comment_remind_queue WHERE user_id = #{userId}")
- List listByUserId(Long userId);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java
deleted file mode 100644
index 315d0bd..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-
-import com.imyeyu.api.modules.common.entity.CommentReply;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * 评论回复
- *
- * @author 夜雨
- * @since 2021-08-24 10:36
- */
-public interface CommentReplyMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java
new file mode 100644
index 0000000..06b397e
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.DataDict;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/**
+ * 数据字典 Mapper
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface DataDictMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java
new file mode 100644
index 0000000..156ecd2
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.DataDictOption;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/**
+ * 数据字典选项 Mapper
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface DataDictOptionMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java
new file mode 100644
index 0000000..9913684
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.EmailFilter;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/**
+ * 邮件过滤 Mapper
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface EmailFilterMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java
deleted file mode 100644
index c845017..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.EmailQueueLog;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * @author 夜雨
- * @since 2023-08-10 10:38
- */
-public interface EmailQueueLogMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java
deleted file mode 100644
index cb7fc92..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.EmailQueue;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.util.List;
-
-/**
- * 邮件推送队列
- *
- * @author 夜雨
- * @since 2021-08-24 16:22
- */
-public interface EmailQueueMapper extends BaseMapper {
-
- @Select("SELECT * FROM email_queue WHERE biz_type = #{bizType} AND biz_id = #{bizId}")
- EmailQueue query(EmailQueue.BizType bizType, Long bizId);
-
- @Select("SELECT * FROM email_queue")
- List listAll();
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java
deleted file mode 100644
index 9714af1..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.Feedback;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * 反馈
- *
- * @author 夜雨
- * @since 2021-11-16 22:28
- */
-public interface FeedbackMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java
index 33e6622..c4311f0 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java
@@ -2,9 +2,6 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Icon;
import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.util.List;
/**
* 图标
@@ -12,24 +9,5 @@ import java.util.List;
* @author 夜雨
* @since 2022-09-15 00:02
*/
-public interface IconMapper extends BaseMapper {
-
- @Select("SELECT * FROM icon WHERE 1 = 1" + NOT_DELETE)
- List listAll();
-
- @Select("SELECT COUNT(1) FROM icon WHERE name LIKE CONCAT('%', #{name}, '%')" + NOT_DELETE)
- long countByName(String name);
-
- @Select("SELECT * FROM icon WHERE name LIKE CONCAT('%', #{name}, '%')" + PAGE)
- List listByName(String name, long offset, long limit);
-
- long countByLabel(String lang, String label);
-
- List listByLabel(String lang, String label, long offset, long limit);
-
- @Select("SELECT COUNT(1) FROM icon WHERE unicode = #{unicode}" + NOT_DELETE)
- long countByUnicode(String unicode);
-
- @Select("SELECT * FROM icon WHERE unicode = #{unicode}" + PAGE)
- List listByUnicode(String unicode, long offset, long limit);
+public interface IconMapper extends BaseMapper {
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java
index ff68c7b..75394e7 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java
@@ -2,7 +2,6 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
import java.util.List;
@@ -10,23 +9,7 @@ import java.util.List;
* @author 夜雨
* @since 2023-10-25 10:47
*/
-public interface MultilingualMapper extends BaseMapper {
+public interface MultilingualMapper extends BaseMapper {
- // 以下临时
-
- @Select("SELECT * FROM multilingual WHERE key LIKE CONCAT('%', #{key}, '%')" + NOT_DELETE)
- List selectByKeyLike(String key);
-
- List selectByKeyList(List keys);
-
- // 以上临时
-
- @Select("SELECT * FROM multilingual WHERE zh_cn = #{zhCN}" + NOT_DELETE + LIMIT_1)
- Multilingual selectByZhCN(String zhCN);
-
- @Select("SELECT * FROM multilingual WHERE en_US IS NULL OR en_US = ''" + NOT_DELETE)
- List selectByNotTranslate();
-
- @Select("SELECT * FROM multilingual WHERE `key` = #{key}" + NOT_DELETE + LIMIT_1)
- Multilingual selectByKey(String key);
+ List selectByIdList(List idList);
}
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
new file mode 100644
index 0000000..b4ada09
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.NotifyDetail;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/**
+ * 通知详情 Mapper
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface NotifyDetailMapper extends BaseMapper {
+}
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
new file mode 100644
index 0000000..6126211
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.Notify;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/**
+ * 通知 Mapper
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface NotifyMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java
index a93d8fa..df3e36c 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java
@@ -1,11 +1,7 @@
package com.imyeyu.api.modules.common.mapper;
-import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.util.List;
/**
* 系统配置
@@ -14,10 +10,4 @@ import java.util.List;
* @since 2021-07-20 22:26
*/
public interface SettingMapper extends BaseMapper {
-
- @Select("SELECT * FROM `setting` WHERE `key` = #{key}")
- Setting selectByKey(SettingKey key);
-
- @Select("SELECT * FROM `setting`")
- List listAll();
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java
index 538d2e5..fcca350 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java
@@ -7,5 +7,5 @@ import com.imyeyu.spring.mapper.BaseMapper;
* @author 夜雨
* @since 2025-05-30 22:48
*/
-public interface TagMapper extends BaseMapper {
+public interface TagMapper extends BaseMapper {
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java
deleted file mode 100644
index be47b61..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.Task;
-
-import java.util.List;
-
-/**
- * 任务服务
- *
- * @author 夜雨
- * @since 2022-04-03 15:37
- */
-public interface TaskMapper {
-
- List listAll4Public();
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java
deleted file mode 100644
index 010f258..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.Template;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-/**
- * @author 夜雨
- * @since 2023-09-22 16:41
- */
-public interface TemplateMapper extends BaseMapper {
-
- @Select("SELECT * FROM template WHERE biz_type = #{bizType} AND biz_code = #{bizCode}" + NOT_DELETE)
- Template query(Template.BizType bizType, String bizCode);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java
deleted file mode 100644
index e87ec35..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.UserConfig;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * 用户设置
- *
- * @author 夜雨
- * @since 2021-08-12 16:36
- */
-public interface UserConfigMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java
deleted file mode 100644
index ecb65e8..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.User;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-/**
- * 用户
- *
- * @author 夜雨
- * @since 2021-02-23 21:33
- */
-public interface UserMapper extends BaseMapper {
-
- @Select("SELECT * FROM user WHERE BINARY name = #{name}" + NOT_DELETE + LIMIT_1)
- User selectByName(String name);
-
- @Select("SELECT * FROM user WHERE BINARY email = #{email} AND email_verify_at IS NOT NULL" + NOT_DELETE + LIMIT_1)
- User selectByEmail(String email);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java
deleted file mode 100644
index d6cddf3..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.UserPrivacy;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * 用户隐私控制
- *
- * @author 夜雨
- * @since 2021-07-27 17:18
- */
-public interface UserPrivacyMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java
deleted file mode 100644
index 4a98276..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.UserProfile;
-import com.imyeyu.spring.mapper.BaseMapper;
-
-/**
- * 用户数据
- *
- * @author 夜雨
- * @since 2021-07-27 17:04
- */
-public interface UserProfileMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java
deleted file mode 100644
index cea1e28..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.imyeyu.api.modules.common.mapper;
-
-import com.imyeyu.api.modules.common.entity.Version;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Select;
-
-/**
- * 版本管理
- *
- * @author 夜雨
- * @since 2021-06-10 16:08
- */
-public interface VersionMapper extends BaseMapper {
-
- @Select("SELECT * FROM version WHERE name = #{name}" + NOT_DELETE)
- Version queryByName(String name);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java
new file mode 100644
index 0000000..8590a20
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.Article;
+import com.imyeyu.spring.service.BaseService;
+
+/**
+ * 文章服务
+ *
+ * @author 夜雨
+ * @since 2021-02-23 21:33
+ */
+public interface ArticleService extends BaseService {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java
index 0d72edc..aa0e137 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java
@@ -1,9 +1,11 @@
package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Attachment;
-import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq;
+import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService;
-import com.mongodb.client.gridfs.model.GridFSFile;
import java.io.InputStream;
import java.util.List;
@@ -11,28 +13,32 @@ import java.util.List;
/**
* 附件服务
*
- * 删除和销毁都为数据库软删除,但删除不删除 MongoDB 文件,而销毁则删除 MongoDB 文件
- *
* @author 夜雨
* @since 2023-08-15 10:21
*/
-public interface AttachmentService extends BaseService {
+public interface AttachmentService extends BaseService {
- Attachment createMedia(Attachment attachment);
+ /**
+ * 按需获取或创建缩略图附件。
+ * 当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存,
+ * 否则生成并持久化后返回。不支持缩略图的附件类型返回 {@code null}。
+ *
+ * @param source 源附件(必须已持久化,mimeType 已填充)
+ * @param requestWidth 请求宽度,{@code null} 表示未指定
+ * @param requestHeight 请求高度,{@code null} 表示未指定
+ * @return 缩略图附件,不支持时返回 {@code null}
+ */
+ Attachment fetchThumb(Attachment source, Integer requestWidth, Integer requestHeight);
- void deleteMedia(Long thumbId) throws TimiException;
+ Attachment getByBizId(Attachment.BizType bizType, String bizId);
- Attachment getByBizId(Attachment.BizType bizType, long bizId);
+ Attachment getByAttachType(Attachment.BizType bizType, String bizId, String attachType);
- Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum> attachType);
+ Attachment getByMongoId(String MongoId);
- Attachment getByMongoId(String mongoId);
+ InputStream getInputStreamByMongoId(String MongoId);
- GridFSFile readByMongoId(String mongoId);
-
- InputStream getInputStreamByMongoId(String mongoId);
-
- byte[] readAllByMongoId(String mongoId);
+ byte[] readAllByMongoId(String MongoId);
/**
* 根据业务获取所有附件
@@ -41,9 +47,8 @@ public interface AttachmentService extends BaseService {
* @param bizId 业务 ID,可为 null
* @param attachTypes 附件类型,可为 null
* @return 所有附件
- * @throws TimiException 服务异常
*/
- List listByBizId(Attachment.BizType bizType, Long bizId, Enum> ...attachTypes);
+ List listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes);
/**
* 根据业务获取所有附件
@@ -52,13 +57,26 @@ public interface AttachmentService extends BaseService {
* @param bizId 业务 ID,可为 null
* @param attachTypes 附件类型,可为 null
* @return 附件数量
- * @throws TimiException 服务异常
*/
- long countByBizId(Attachment.BizType bizType, Long bizId, Enum> ...attachTypes);
+ long countByBizId(Attachment.BizType bizType, String bizId, String... attachTypes);
- List listByMd5s(Attachment.BizType bizType, Long bizId, List> attachTypeList, List md5s);
-
- void deleteByBizId(Attachment.BizType bizType, long bizId, Enum> ...attachTypes);
+ PageResult pageByBizId(Attachment.BizType bizType, String bizId, List attachTypeList, Page page);
List listNeedDestroy();
+
+ /**
+ * 根据业务差分保存附件列表。
+ *
+ *
+ * - 请求有、数据库无 → 新增({@link Attachment#getTempFileId()} 必填)
+ * - 数据库有、请求无 → 删除
+ * - 请求有、数据库有、无 tempFileId → 仅更新元数据
+ * - 请求有、数据库有、有 tempFileId → 更新文件与元数据
+ *
+ *
+ * @param req 差分更新请求
+ */
+ void updateByBiz(BizUpdateReq req);
+
+ void deleteByBiz(BizDeleteReq req);
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java b/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java
deleted file mode 100644
index 49185b7..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.imyeyu.api.modules.common.service;
-
-import com.imyeyu.api.modules.common.entity.CommentReply;
-import com.imyeyu.spring.service.BaseService;
-
-/**
- * 评论回复服务
- *
- * @author 夜雨
- * @since 2021-08-24 10:33
- */
-public interface CommentReplyService extends BaseService {
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java b/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java
index ba56441..9a63a6d 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java
@@ -1,13 +1,124 @@
package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Comment;
+import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
+import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq;
+import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp;
+import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp;
+import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp;
+import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp;
+import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService;
/**
* 评论服务
*
* @author 夜雨
- * @since 2021-02-23 21:32
+ * @since 2026-05-27 17:20
*/
-public interface CommentService extends BaseService {
+public interface CommentService extends BaseService {
+
+ /**
+ * 点赞评论
+ *
+ * @param commentId 评论 ID
+ * @param userId 用户 ID
+ */
+ void like(String commentId, String userId);
+
+ /**
+ * 不喜欢评论
+ *
+ * @param commentId 评论 ID
+ * @param userId 用户 ID
+ */
+ void unlike(String commentId, String userId);
+
+ /**
+ * 查询当前用户点赞评论记录
+ *
+ * @param page 分页参数
+ * @param userId 用户 ID
+ * @return 点赞评论记录分页
+ */
+ PageResult listLikedRecord(Page page, String userId);
+
+ /**
+ * 查询扁平模式主评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID
+ * @return 主评论列表
+ */
+ FlatCommentListResp listFlatRoot(String bizType, String bizId, int offset, String currentUserId);
+
+ /**
+ * 查询层级模式主评论列表
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID
+ * @return 主评论列表
+ */
+ NestedCommentListResp listNestedRoot(String bizType, String bizId, int offset, String currentUserId);
+
+ /**
+ * 查询用户侧评论线程
+ *
+ * @param commentId 评论 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID
+ * @return 评论线程
+ */
+ CommentThreadResp getUserThread(String commentId, int offset, String currentUserId);
+
+ /**
+ * 查询 SDK 评论层
+ *
+ * @param req 查询请求
+ * @param currentUserId 当前用户 ID
+ * @return 评论层
+ */
+ CommentLayerResp listSdkLayer(CommentLayerQueryReq req, String currentUserId);
+
+ /**
+ * 查询用户侧评论节点
+ *
+ * @param commentId 评论 ID
+ * @param currentUserId 当前用户 ID
+ * @return 评论节点
+ */
+ CommentNodeResp getUserNode(String commentId, String currentUserId);
+
+ /**
+ * 查询用户侧直属子评论分页
+ *
+ * @param commentId 评论 ID
+ * @param offset 起始偏移量
+ * @param currentUserId 当前用户 ID
+ * @return 子评论分页
+ */
+ CommentLayerResp getUserChildren(String commentId, int offset, String currentUserId);
+
+ /**
+ * 查询评论树详情
+ *
+ * @param id 评论 ID
+ * @param currentUserId 当前用户 ID
+ * @return 评论树详情
+ */
+ Comment detailTree(String id, String currentUserId);
+
+ /**
+ * 删除当前用户自己的评论
+ *
+ * @param id 评论 ID
+ * @param userId 用户 ID
+ */
+ void deleteOwned(String id, String userId);
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java b/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java
new file mode 100644
index 0000000..5e273da
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.DataDictOption;
+import com.imyeyu.spring.service.BaseService;
+
+/**
+ * 数据字典选项服务
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface DataDictOptionService extends BaseService {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java b/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java
new file mode 100644
index 0000000..85318d7
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.DataDict;
+import com.imyeyu.spring.service.BaseService;
+
+/**
+ * 数据字典服务
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface DataDictService extends BaseService {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java b/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java
new file mode 100644
index 0000000..8288cd8
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java
@@ -0,0 +1,13 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.EmailFilter;
+import com.imyeyu.spring.service.BaseService;
+
+/**
+ * 邮件过滤服务
+ *
+ * @author 夜雨
+ * @since 2026-05-13 14:46
+ */
+public interface EmailFilterService extends BaseService {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java b/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java
deleted file mode 100644
index d5ba74f..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.imyeyu.api.modules.common.service;
-
-import com.imyeyu.java.bean.timi.TimiException;
-import com.imyeyu.api.modules.common.entity.EmailQueue;
-import com.imyeyu.api.modules.common.entity.EmailQueueLog;
-import com.imyeyu.spring.service.CreatableService;
-import com.imyeyu.spring.service.DestroyableService;
-
-import java.util.List;
-
-/**
- * 邮件推送队列服务
- *
- * @author 夜雨
- * @since 2021-08-24 16:20
- */
-public interface EmailQueueService extends CreatableService, DestroyableService {
-
- /**
- * 根据推送类型和数据 ID 获取推送对象
- *
- * @param type 推送类型
- * @param dataId 数据 ID
- * @return 推送对象
- * @throws TimiException 服务异常
- */
- EmailQueue get(EmailQueue.BizType type, Long dataId);
-
- /**
- * 移出队列
- *
- * @param UUID UUID
- * @throws TimiException 服务异常
- */
- void destroy(String UUID);
-
- /**
- * 添加推送日志
- *
- * @param log 推送日志
- * @throws TimiException 服务异常
- */
- void addLog(EmailQueueLog log);
-
- /**
- * 遍历推送队列
- *
- * @return 推送队列
- * @throws TimiException 服务异常
- */
- List listAll();
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java b/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java
deleted file mode 100644
index 5055cae..0000000
--- a/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.imyeyu.api.modules.common.service;
-
-import com.imyeyu.api.modules.common.vo.FeedbackRequest;
-
-/**
- * 反馈服务
- *
- * @author 夜雨
- * @since 2021-11-16 22:27
- */
-public interface FeedbackService {
-
- void create(FeedbackRequest request);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/IconService.java b/src/main/java/com/imyeyu/api/modules/common/service/IconService.java
index 9a801df..7ac78b6 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/IconService.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/IconService.java
@@ -1,12 +1,11 @@
package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Icon;
-import com.imyeyu.api.modules.common.vo.icon.AllResponse;
-import com.imyeyu.api.modules.common.vo.icon.NamePage;
-import com.imyeyu.api.modules.common.vo.icon.UnicodePage;
-import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.PageableService;
+import java.util.List;
+import java.util.Map;
+
/**
* 图标服务
*
@@ -15,9 +14,21 @@ import com.imyeyu.spring.service.PageableService;
*/
public interface IconService extends PageableService {
- AllResponse listAll(Long latest);
+ /**
+ * 查询全部图标名称
+ *
+ * @return 图标名称列表
+ */
+ List listName();
- PageResult