refactor all
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 <T extends Enum<?>> SettingModule fromClass(Class<T> 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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.imyeyu.api.modules.common.bean;
|
||||
package com.imyeyu.api.modules.common.bean.attachment;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
+8
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Setting> getSettingList();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Attachment> listByBiz(
|
||||
@RequestParam Attachment.BizType bizType,
|
||||
@RequestParam String bizId,
|
||||
@RequestParam(required = false) List<String> attachTypeList
|
||||
) {
|
||||
List<String> 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<String> attachTypeList
|
||||
) {
|
||||
List<String> 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<Attachment> pageByBiz(
|
||||
@RequestParam Attachment.BizType bizType,
|
||||
@RequestParam String bizId,
|
||||
@RequestParam(required = false) List<String> attachTypeList,
|
||||
@RequestBody Page<Attachment> 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<TempFileResp> upload(@RequestParam("file") List<MultipartFile> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 评论操作接口
|
||||
* <p>*评论回复只依赖评论而不含业务关联,评论有关联业务,所以此接口是通用接口
|
||||
*
|
||||
* @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<Comment> captchaData) {
|
||||
service.create(captchaData.getData());
|
||||
}
|
||||
|
||||
@RequestRateLimit
|
||||
@PostMapping("/list")
|
||||
public PageResult<Comment> list(@Valid @RequestBody Page<Comment> 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<CommentReply> 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<CommentReply> pageReplies(@Valid @RequestBody Page<CommentReply> 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<UserBehaviorRecord> listLikedRecord(@RequestBody Page<UserBehaviorRecord> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,23 @@
|
||||
package com.imyeyu.api.modules.common.controller;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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.data.mongodb.gridfs.GridFsResource;
|
||||
import org.springframework.http.MediaType;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 系统接口
|
||||
@@ -87,20 +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;
|
||||
|
||||
@AOPLog
|
||||
@RequestMapping("")
|
||||
@@ -108,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<FeedbackRequest> request) {
|
||||
captchaManager.test(request.getCaptcha(), request.getCaptchaId());
|
||||
feedbackService.create(request.getData());
|
||||
}
|
||||
|
||||
/** @return 公开任务信息 */
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@GetMapping("/tasklist")
|
||||
public List<Task> 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) throws JsonProcessingException {
|
||||
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<String, Object> obj = yaml.load(setting.getValue());
|
||||
result = jackson.writeValueAsString(obj);
|
||||
}
|
||||
}
|
||||
case YAML -> {
|
||||
if (setting.getType() == Setting.Type.JSON) {
|
||||
Map<String, Object> obj = jackson.readValue(setting.getValue(), new TypeReference<>() {});
|
||||
result = yaml.dump(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestRateLimit
|
||||
@PostMapping("/setting/map")
|
||||
public Map<SettingKey, String> mapSettingByKeys(@RequestBody Map<SettingKey, Map<String, Object>> settingMap) throws JsonProcessingException {
|
||||
List<Setting> 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<String, Object> 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<String, Object> obj = new Yaml().load(setting.getValue());
|
||||
setting.setValue(jackson.writeValueAsString(obj));
|
||||
}
|
||||
}
|
||||
case YAML -> {
|
||||
if (setting.getType() == Setting.Type.JSON) {
|
||||
Map<String, Object> obj = jackson.readValue(setting.getValue(), new TypeReference<>() {});
|
||||
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<TempFileResponse> tempFileUpload(@RequestParam("file") List<MultipartFile> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Icon> list(@RequestBody Page page) {
|
||||
return service.page(page);
|
||||
@GetMapping("/name/list")
|
||||
public List<String> listName() {
|
||||
return service.listName();
|
||||
}
|
||||
|
||||
@GetMapping("/svg/map")
|
||||
public Map<String, String> mapSvg() {
|
||||
return service.mapSvg();
|
||||
}
|
||||
|
||||
@GetMapping("/unicode/map")
|
||||
public Map<String, String> 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<Icon> 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<Icon> listByName(@Valid @RequestBody NamePage page) {
|
||||
return service.pageByName(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Unicode 获取图标列表
|
||||
*
|
||||
* @param page 查询列表参数
|
||||
* @return 图标列表
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/list/unicode")
|
||||
public PageResult<Icon> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Setting> list(@RequestBody Page<Setting> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<RegisterRequest> 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<LoginRequest> 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<String> 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<Comment> listComment(@Valid @RequestBody Page<Comment> 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<CommentReply> listCommentReply(@Valid @RequestBody Page<CommentReply> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<Setting> settingList;
|
||||
|
||||
@Transient
|
||||
protected List<Comment> commentList;
|
||||
}
|
||||
@@ -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 extends Enum<T>> T getAttachTypeValue(Class<T> attachTypeClass) {
|
||||
return Ref.toType(attachTypeClass, attachType);
|
||||
}
|
||||
|
||||
/** 执行接口流式返回 */
|
||||
public void doResponse() throws IOException {
|
||||
TimiSpring.responseRangeStream(openInputStream(), size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* 关联业务类型
|
||||
* <p>
|
||||
* 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<Comment> replyList;
|
||||
|
||||
/** 关联仓库 */
|
||||
/** true 为当前用户已点赞 */
|
||||
@Transient
|
||||
private Repository repository;
|
||||
protected Boolean liked;
|
||||
|
||||
/** 回复列表 */
|
||||
/** true 为当前用户已不喜欢 */
|
||||
@Transient
|
||||
private List<CommentReply> replies;
|
||||
protected Boolean unliked;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<DataDictOption> dataDictOptionList;
|
||||
}
|
||||
@@ -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<Setting> settingList;
|
||||
}
|
||||
@@ -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<String>, Creatable, Deletable {
|
||||
|
||||
/** 主键 */
|
||||
@Id
|
||||
@AutoUUID
|
||||
private String id;
|
||||
|
||||
/** 邮箱 */
|
||||
private String email;
|
||||
|
||||
/** 创建时间 */
|
||||
private Long createdAt;
|
||||
|
||||
/** 删除时间 */
|
||||
@DeleteColumn
|
||||
private Long deletedAt;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Long>, 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NotifyDetail> detailList;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 <T> 目标类型
|
||||
* @return 解析后的 JSON 节点
|
||||
* @throws IllegalArgumentException 配置值不是合法 JSON
|
||||
*/
|
||||
public <T extends JsonNode> T asJsonNode(Class<T> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<TaskDetail> details;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Article, String> {
|
||||
|
||||
@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);
|
||||
}
|
||||
@@ -12,25 +12,46 @@ import java.util.List;
|
||||
* @author 夜雨
|
||||
* @since 2023-08-15 10:22
|
||||
*/
|
||||
public interface AttachmentMapper extends BaseMapper<Attachment, Long>, RawMapper<Attachment, Long> {
|
||||
public interface AttachmentMapper extends BaseMapper<Attachment, String>, RawMapper<Attachment, String> {
|
||||
|
||||
/** 有效条件,非删除和销毁 */
|
||||
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<Attachment> 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<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, Page<Attachment> page);
|
||||
List<Attachment> selectByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypes, Page<Attachment> page);
|
||||
|
||||
long countByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes);
|
||||
long countByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypes);
|
||||
|
||||
List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, List<String> 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<Attachment> selectThumbsBySourceId(String sourceId);
|
||||
|
||||
@Select("SELECT * FROM attachment WHERE `is_destroyed` = FALSE AND `destroy_at` < " + UNIX_TIME)
|
||||
List<Attachment> selectNeedDestroy();
|
||||
|
||||
@@ -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<Comment, Long> {
|
||||
public interface CommentMapper extends BaseMapper<Comment, String> {
|
||||
|
||||
long countAll(Comment.BizType bizType, Long bizId);
|
||||
/**
|
||||
* 根据 ID 列表查询评论
|
||||
*
|
||||
* @param idList 评论 ID 列表
|
||||
* @return 评论列表
|
||||
*/
|
||||
List<Comment> listByIdList(List<String> 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<Comment> 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<Comment> selectReplyPreviewByRootIds(List<String> rootIdList, int limit);
|
||||
|
||||
/**
|
||||
* 查询父评论下的回复列表
|
||||
*
|
||||
* @param parentId 父评论 ID
|
||||
* @param offset 起始偏移量
|
||||
* @param limit 查询条数
|
||||
* @return 回复列表
|
||||
*/
|
||||
List<Comment> selectReplyListByParentId(String parentId, int offset, int limit);
|
||||
|
||||
/**
|
||||
* 查询根评论下的回复列表
|
||||
*
|
||||
* @param rootId 根评论 ID
|
||||
* @param offset 起始偏移量
|
||||
* @param limit 查询条数
|
||||
* @return 回复列表
|
||||
*/
|
||||
List<Comment> 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<Comment> selectAllReplyListByRootIds(List<String> rootIdList);
|
||||
|
||||
/**
|
||||
* 查询根评论下的全部回复列表
|
||||
*
|
||||
* @param rootId 根评论 ID
|
||||
* @return 回复列表
|
||||
*/
|
||||
List<Comment> selectAllReplyListByRootId(String rootId);
|
||||
|
||||
/**
|
||||
* 查询单层评论列表
|
||||
*
|
||||
* @param bizType 业务类型
|
||||
* @param bizId 业务 ID
|
||||
* @param parentId 父评论 ID
|
||||
* @param offset 起始偏移量
|
||||
* @param limit 查询条数
|
||||
* @return 评论列表
|
||||
*/
|
||||
List<Comment> 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);
|
||||
}
|
||||
|
||||
@@ -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<CommentRemindQueue, String> {
|
||||
|
||||
@Select("SELECT * FROM comment_remind_queue WHERE user_id = #{userId}")
|
||||
List<CommentRemindQueue> listByUserId(Long userId);
|
||||
}
|
||||
@@ -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<CommentReply, Long> {
|
||||
}
|
||||
@@ -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<DataDict, String> {
|
||||
}
|
||||
@@ -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<DataDictOption, String> {
|
||||
}
|
||||
@@ -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<EmailFilter, String> {
|
||||
}
|
||||
@@ -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<EmailQueueLog, Long> {
|
||||
}
|
||||
@@ -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<EmailQueue, String> {
|
||||
|
||||
@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<EmailQueue> listAll();
|
||||
}
|
||||
@@ -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<Feedback, Long> {
|
||||
}
|
||||
@@ -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<Icon, Long> {
|
||||
|
||||
@Select("SELECT * FROM icon WHERE 1 = 1" + NOT_DELETE)
|
||||
List<Icon> 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<Icon> listByName(String name, long offset, long limit);
|
||||
|
||||
long countByLabel(String lang, String label);
|
||||
|
||||
List<Icon> 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<Icon> listByUnicode(String unicode, long offset, long limit);
|
||||
public interface IconMapper extends BaseMapper<Icon, String> {
|
||||
}
|
||||
|
||||
@@ -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<Multilingual, Long> {
|
||||
public interface MultilingualMapper extends BaseMapper<Multilingual, String> {
|
||||
|
||||
// 以下临时
|
||||
|
||||
@Select("SELECT * FROM multilingual WHERE key LIKE CONCAT('%', #{key}, '%')" + NOT_DELETE)
|
||||
List<Multilingual> selectByKeyLike(String key);
|
||||
|
||||
List<Multilingual> selectByKeyList(List<String> 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<Multilingual> selectByNotTranslate();
|
||||
|
||||
@Select("SELECT * FROM multilingual WHERE `key` = #{key}" + NOT_DELETE + LIMIT_1)
|
||||
Multilingual selectByKey(String key);
|
||||
List<Multilingual> selectByIdList(List<String> idList);
|
||||
}
|
||||
|
||||
@@ -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<NotifyDetail, String> {
|
||||
}
|
||||
@@ -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<Notify, String> {
|
||||
}
|
||||
@@ -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<Setting, String> {
|
||||
|
||||
@Select("SELECT * FROM `setting` WHERE `key` = #{key}")
|
||||
Setting selectByKey(SettingKey key);
|
||||
|
||||
@Select("SELECT * FROM `setting`")
|
||||
List<Setting> listAll();
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ import com.imyeyu.spring.mapper.BaseMapper;
|
||||
* @author 夜雨
|
||||
* @since 2025-05-30 22:48
|
||||
*/
|
||||
public interface TagMapper extends BaseMapper<Tag, Long> {
|
||||
public interface TagMapper extends BaseMapper<Tag, String> {
|
||||
}
|
||||
|
||||
@@ -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<Task> listAll4Public();
|
||||
}
|
||||
@@ -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<Template, String> {
|
||||
|
||||
@Select("SELECT * FROM template WHERE biz_type = #{bizType} AND biz_code = #{bizCode}" + NOT_DELETE)
|
||||
Template query(Template.BizType bizType, String bizCode);
|
||||
}
|
||||
@@ -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<UserConfig, Long> {
|
||||
}
|
||||
@@ -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<User, Long> {
|
||||
|
||||
@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);
|
||||
}
|
||||
@@ -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<UserPrivacy, Long> {
|
||||
}
|
||||
@@ -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<UserProfile, Long> {
|
||||
}
|
||||
@@ -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<Version, String> {
|
||||
|
||||
@Select("SELECT * FROM version WHERE name = #{name}" + NOT_DELETE)
|
||||
Version queryByName(String name);
|
||||
}
|
||||
@@ -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<Article, String> {
|
||||
}
|
||||
@@ -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;
|
||||
/**
|
||||
* 附件服务
|
||||
*
|
||||
* <p>删除和销毁都为数据库软删除,但删除不删除 MongoDB 文件,而销毁则删除 MongoDB 文件
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-08-15 10:21
|
||||
*/
|
||||
public interface AttachmentService extends BaseService<Attachment, Long> {
|
||||
public interface AttachmentService extends BaseService<Attachment, String> {
|
||||
|
||||
Attachment createMedia(Attachment attachment);
|
||||
/**
|
||||
* 按需获取或创建缩略图附件。
|
||||
* <p>当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存,
|
||||
* 否则生成并持久化后返回。不支持缩略图的附件类型返回 {@code null}。</p>
|
||||
*
|
||||
* @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<Attachment, Long> {
|
||||
* @param bizId 业务 ID,可为 null
|
||||
* @param attachTypes 附件类型,可为 null
|
||||
* @return 所有附件
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, Enum<?> ...attachTypes);
|
||||
List<Attachment> listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes);
|
||||
|
||||
/**
|
||||
* 根据业务获取所有附件
|
||||
@@ -52,13 +57,26 @@ public interface AttachmentService extends BaseService<Attachment, Long> {
|
||||
* @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<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, List<String> md5s);
|
||||
|
||||
void deleteByBizId(Attachment.BizType bizType, long bizId, Enum<?> ...attachTypes);
|
||||
PageResult<Attachment> pageByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypeList, Page<Attachment> page);
|
||||
|
||||
List<Attachment> listNeedDestroy();
|
||||
|
||||
/**
|
||||
* 根据业务差分保存附件列表。
|
||||
*
|
||||
* <ul>
|
||||
* <li>请求有、数据库无 → 新增({@link Attachment#getTempFileId()} 必填)</li>
|
||||
* <li>数据库有、请求无 → 删除</li>
|
||||
* <li>请求有、数据库有、无 tempFileId → 仅更新元数据</li>
|
||||
* <li>请求有、数据库有、有 tempFileId → 更新文件与元数据</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param req 差分更新请求
|
||||
*/
|
||||
void updateByBiz(BizUpdateReq req);
|
||||
|
||||
void deleteByBiz(BizDeleteReq req);
|
||||
}
|
||||
|
||||
@@ -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<CommentReply, Long> {
|
||||
}
|
||||
@@ -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<Comment, Long> {
|
||||
public interface CommentService extends BaseService<Comment, String> {
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
*
|
||||
* @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<UserBehaviorRecord> listLikedRecord(Page<UserBehaviorRecord> 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);
|
||||
}
|
||||
|
||||
@@ -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<DataDictOption, String> {
|
||||
}
|
||||
@@ -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<DataDict, String> {
|
||||
}
|
||||
@@ -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<EmailFilter, String> {
|
||||
}
|
||||
@@ -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<EmailQueue>, DestroyableService<String> {
|
||||
|
||||
/**
|
||||
* 根据推送类型和数据 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<EmailQueue> listAll();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Icon> {
|
||||
|
||||
AllResponse listAll(Long latest);
|
||||
/**
|
||||
* 查询全部图标名称
|
||||
*
|
||||
* @return 图标名称列表
|
||||
*/
|
||||
List<String> listName();
|
||||
|
||||
PageResult<Icon> pageByName(NamePage page);
|
||||
Map<String, String> mapSvg();
|
||||
|
||||
PageResult<Icon> pageByUnicode(UnicodePage page);
|
||||
Map<String, String> mapUnicode();
|
||||
|
||||
/**
|
||||
* 导出 TTF 字体文件
|
||||
*
|
||||
* @return TTF 字体文件字节
|
||||
*/
|
||||
byte[] exportTtf();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.java.bean.Language;
|
||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -10,15 +9,16 @@ import java.util.List;
|
||||
* @author 夜雨
|
||||
* @since 2023-10-25 10:47
|
||||
*/
|
||||
public interface MultilingualService extends UpdatableService<Multilingual> {
|
||||
public interface MultilingualService extends BaseService<Multilingual, String> {
|
||||
|
||||
Long create(String key, String zhCN);
|
||||
default String create(String zhCN) {
|
||||
Multilingual multilingual = new Multilingual();
|
||||
multilingual.setZhCN(zhCN);
|
||||
create(multilingual);
|
||||
return multilingual.getId();
|
||||
}
|
||||
|
||||
Long createIfNotExist(String key, String zhCN);
|
||||
String createIfDifferent(String id, String zhCN);
|
||||
|
||||
String get(Language.Enum language, Long id);
|
||||
|
||||
String getByKey(Language.Enum language, String key);
|
||||
|
||||
List<Multilingual> listByNotTranslate();
|
||||
List<Multilingual> listByIdList(List<String> idList);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.NotifyDetail;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
/**
|
||||
* 通知详情服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-05-13 14:46
|
||||
*/
|
||||
public interface NotifyDetailService extends BaseService<NotifyDetail, String> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Notify;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
/**
|
||||
* 通知服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-05-13 14:46
|
||||
*/
|
||||
public interface NotifyService extends BaseService<Notify, String> {
|
||||
}
|
||||
@@ -1,80 +1,47 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.bean.attachment.SettingSupport;
|
||||
import com.imyeyu.api.modules.common.entity.Setting;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
import com.imyeyu.utils.Text;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统配置服务
|
||||
* 配置服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-07-20 22:06
|
||||
*/
|
||||
public interface SettingService extends UpdatableService<Setting> {
|
||||
public interface SettingService extends BaseService<Setting, String> {
|
||||
|
||||
default List<Setting> listByKeys(SettingKey... keys) {
|
||||
return listByKeys(Arrays.asList(keys));
|
||||
Setting getByCode(String bizType, String bizId, String code);
|
||||
|
||||
default Setting getSystem(Enum<?> bizType) {
|
||||
return getByCode(Text.bizClassName(bizType.getClass()), TimiJava.ZERO_UUID, bizType.toString());
|
||||
}
|
||||
|
||||
List<Setting> listByKeys(List<SettingKey> keyList);
|
||||
List<Setting> listByBizId(String bizType, String bizId);
|
||||
|
||||
Setting getByKey(SettingKey key);
|
||||
default List<Setting> listBySettingSupport(SettingSupport settingSupport) {
|
||||
String bizType = settingSupport.getClass().getSimpleName().toUpperCase();
|
||||
return listByBizId(bizType, settingSupport.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型配置值字符串
|
||||
* 差分更新设置
|
||||
*
|
||||
* @param key 键
|
||||
* @return 配置值
|
||||
* @param bizType 业务类型
|
||||
* @param bizId 业务 ID,可为空
|
||||
* @param list 设置列表
|
||||
*/
|
||||
String getAsString(SettingKey key);
|
||||
void updateByBizId(String bizType, String bizId, List<Setting> list);
|
||||
|
||||
int getAsInt(SettingKey key);
|
||||
default void updateBySettingSupport(SettingSupport settingSupport) {
|
||||
String bizType = settingSupport.getClass().getSimpleName().toUpperCase();
|
||||
updateByBizId(bizType, settingSupport.getId(), settingSupport.getSettingList());
|
||||
}
|
||||
|
||||
long getAsLong(SettingKey key);
|
||||
|
||||
double getAsDouble(SettingKey key);
|
||||
|
||||
/**
|
||||
* 获取为布尔值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 配置值
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
boolean is(SettingKey key);
|
||||
|
||||
/**
|
||||
* 获取为布尔值,并取反
|
||||
*
|
||||
* @param key 键
|
||||
* @return 配置值
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
boolean not(SettingKey key);
|
||||
|
||||
JsonNode getAsJsonNode(SettingKey key);
|
||||
|
||||
ObjectNode getAsJsonObject(SettingKey key);
|
||||
|
||||
ArrayNode getAsArrayNode(SettingKey key);
|
||||
|
||||
<T> T fromJson(SettingKey key, Class<T> clazz);
|
||||
|
||||
<T> T fromJson(SettingKey key, TypeReference<T> typeReference);
|
||||
|
||||
<T> T fromYaml(SettingKey key, Class<T> clazz);
|
||||
|
||||
List<Setting> listAll();
|
||||
|
||||
void clearCache(SettingKey key);
|
||||
|
||||
void flushCache();
|
||||
void deleteByBizId(String bizType, String bizId);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.common.entity.Tag;
|
||||
import com.imyeyu.spring.service.CreatableService;
|
||||
import com.imyeyu.spring.service.DeletableService;
|
||||
|
||||
import java.util.List;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-05-30 22:46
|
||||
*/
|
||||
public interface TagService extends CreatableService<Tag>, DeletableService<Long> {
|
||||
|
||||
List<Tag> listByBizID(Tag.BizType bizType, String bizID) throws TimiException;
|
||||
public interface TagService extends BaseService<Tag, String> {
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.common.entity.Task;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-04-03 15:36
|
||||
*/
|
||||
public interface TaskService {
|
||||
|
||||
/**
|
||||
* 查询所有公开任务
|
||||
*
|
||||
* @return 公开任务列表
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
List<Task> listAll4Public();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.vo.TempFileResponse;
|
||||
import com.imyeyu.api.modules.common.vo.attach.TempFileResp;
|
||||
import com.imyeyu.api.modules.common.vo.attach.TempFileSaveReq;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,5 +12,14 @@ import java.util.List;
|
||||
*/
|
||||
public interface TempFileService {
|
||||
|
||||
List<TempFileResponse> store(List<MultipartFile> files, Long ttl);
|
||||
/**
|
||||
* 存储临时文件
|
||||
*
|
||||
* @param files 文件列表
|
||||
* @param ttl 存活时间(毫秒)
|
||||
* @return 临时文件响应列表
|
||||
*/
|
||||
List<TempFileResp> create(List<MultipartFile> files, Long ttl);
|
||||
|
||||
void save(TempFileSaveReq request);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Template;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2023-09-22 16:38
|
||||
*/
|
||||
public interface TemplateService {
|
||||
|
||||
Template get(Template.BizType bizType, String bizCode);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.UserConfig;
|
||||
import com.imyeyu.spring.service.CreatableService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
|
||||
/**
|
||||
* 用户设置服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-08-12 16:23
|
||||
*/
|
||||
public interface UserConfigService extends GettableService<UserConfig, Long>, CreatableService<UserConfig>, UpdatableService<UserConfig> {
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.UserPrivacy;
|
||||
import com.imyeyu.spring.service.CreatableService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
|
||||
/**
|
||||
* 用户隐私控制服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-07-27 17:18
|
||||
*/
|
||||
public interface UserPrivacyService extends GettableService<UserPrivacy, Long>, CreatableService<UserPrivacy>, UpdatableService<UserPrivacy> {
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.UserProfile;
|
||||
import com.imyeyu.api.modules.common.vo.user.UserRequest;
|
||||
import com.imyeyu.spring.service.CreatableService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.spring.service.UpdatableService;
|
||||
|
||||
/**
|
||||
* 用户数据服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-07-27 17:05
|
||||
*/
|
||||
public interface UserProfileService extends GettableService<UserProfile, Long>, CreatableService<UserProfile>, UpdatableService<UserProfile> {
|
||||
|
||||
void update(UserRequest request);
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.common.entity.User;
|
||||
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.UserView;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
|
||||
/**
|
||||
* 用户管理服务
|
||||
* <p>操作任何用户数据前应保证调用 find,以确保用户存在且没有注销(不需要判断,不存在时 find 会抛异常),如果不需要返回数据,可以调用
|
||||
* 一次 exist(),也不需要判断
|
||||
* <p>经过令牌验证的操作也不需要检验用户是否存在,不存在的用户无法登录也无法生成正确的令牌
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-23 21:32
|
||||
*/
|
||||
public interface UserService extends GettableService<User, Long> {
|
||||
|
||||
/**
|
||||
* 注册用户,传入参 User.password 是明文
|
||||
*
|
||||
* @param request 注册数据
|
||||
* @return 登录返回数据
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
LoginResponse register(RegisterRequest request);
|
||||
|
||||
/**
|
||||
* 执行登录
|
||||
*
|
||||
* @param request 登录数据
|
||||
* @return 登录返回数据
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
LoginResponse login(LoginRequest request);
|
||||
|
||||
/**
|
||||
* 用户视图,未经过权限过滤和隐私过滤数据
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
* @throws TimiException
|
||||
*/
|
||||
UserView view(Long userId);
|
||||
|
||||
/**
|
||||
* 密码验证
|
||||
*
|
||||
* @param digestPassword 摘要密码
|
||||
* @param password 明文密码
|
||||
* @return true 为无效密码
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
boolean isInvalidPassword(String digestPassword, String password);
|
||||
|
||||
/**
|
||||
* 令牌登录
|
||||
*
|
||||
* @return 登录结果
|
||||
*/
|
||||
LoginResponse login4Token();
|
||||
|
||||
/** 退出登录 */
|
||||
void logout();
|
||||
|
||||
/**
|
||||
* 查找用户
|
||||
*
|
||||
* @param user UID、邮箱和用户名
|
||||
* @return 用户
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
User get(String user);
|
||||
|
||||
boolean isLogged();
|
||||
|
||||
User getLoginUser();
|
||||
|
||||
/**
|
||||
* 根据用户名查找
|
||||
*
|
||||
* @param name 用户名
|
||||
* @return 账号数据
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
User getByName(String name);
|
||||
|
||||
/**
|
||||
* 根据邮箱查找
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @return 账号数据
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
User getByEmail(String email);
|
||||
|
||||
/**
|
||||
* 发送邮箱验证邮件
|
||||
*
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void sendEmailVerify();
|
||||
|
||||
/**
|
||||
* 邮箱验证回调
|
||||
*
|
||||
* @param key 邮件密钥(非登录令牌)
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void emailVerifyCallback(String key);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
* @param oldValue 旧密码
|
||||
* @param newValue 新密码
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void updatePassword(String oldValue, String newValue);
|
||||
|
||||
|
||||
/**
|
||||
* 发送找回密码验证邮件
|
||||
*
|
||||
* @param user UID、用户名或邮箱
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void sendPasswordForgetVerify(String user);
|
||||
|
||||
/**
|
||||
* 重置密码(需要密钥,由找回密码 sendPasswordForgetVerify 接口通过发送邮件分发)
|
||||
*
|
||||
* @param key 密钥
|
||||
* @param password 明文密码
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void resetPasswordByKey(String key, String password);
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*
|
||||
* @param password 密码校验
|
||||
* @throws TimiException 服务异常
|
||||
*/
|
||||
void cancel(String password);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Version;
|
||||
|
||||
/**
|
||||
* 版本管理服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-06-10 16:06
|
||||
*/
|
||||
public interface VersionService {
|
||||
|
||||
Version getByName(String name);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Article;
|
||||
import com.imyeyu.api.modules.common.mapper.ArticleMapper;
|
||||
import com.imyeyu.api.modules.common.service.ArticleService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 文章服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-17 17:48
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleServiceImplement extends AbstractEntityService<Article, String> implements ArticleService {
|
||||
|
||||
private final ArticleMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Article, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
+275
-111
@@ -3,19 +3,24 @@ package com.imyeyu.api.modules.common.service.implement;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||
import com.imyeyu.api.modules.common.bean.Metadata;
|
||||
import com.imyeyu.api.modules.common.bean.attachment.MediaAttach;
|
||||
import com.imyeyu.api.modules.common.bean.attachment.Metadata;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.mapper.AttachmentMapper;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq;
|
||||
import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq;
|
||||
import com.imyeyu.api.util.JavaCV;
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
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.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import com.imyeyu.utils.Time;
|
||||
@@ -38,9 +43,11 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
@@ -49,9 +56,7 @@ import java.util.UUID;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AttachmentServiceImplement extends AbstractEntityService<Attachment, Long> implements AttachmentService {
|
||||
|
||||
private final SettingService settingService;
|
||||
public class AttachmentServiceImplement extends AbstractEntityService<Attachment, String> implements AttachmentService {
|
||||
|
||||
private final AttachmentMapper mapper;
|
||||
|
||||
@@ -60,21 +65,59 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
private final GridFsTemplate gridFsTemplate;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Attachment, Long> mapper() {
|
||||
protected BaseMapper<Attachment, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
mapper.selectThumbsBySourceId(id).forEach(thumb -> super.delete(thumb.getId()));
|
||||
super.delete(id);
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void destroy(String id) {
|
||||
CallbackArg<Attachment> doDestroy = attach -> {
|
||||
try {
|
||||
if (attach.getMongoId() != null) {
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attach.getMongoId())));
|
||||
}
|
||||
if (!attach.isDeleted()) {
|
||||
attach.setDeletedAt(Time.now());
|
||||
}
|
||||
attach.setIsDestroyed(true);
|
||||
attach.setDestroyAt(Time.now());
|
||||
mapper.update(attach);
|
||||
|
||||
if (Ref.toType(MediaAttach.Type.class, attach.getAttachType()) == MediaAttach.Type.THUMB) {
|
||||
// 缩略图记录物理删除
|
||||
mapper.destroy(attach.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("delete mongo file error, id={}", attach.getId(), e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error");
|
||||
}
|
||||
};
|
||||
mapper.selectThumbsBySourceId(id).forEach(doDestroy::handler);
|
||||
doDestroy.handler(mapper.selectRaw(id));
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void create(Attachment attachment) {
|
||||
TimiException.required(attachment.getBizType(), "not found attachment.bizType");
|
||||
TimiException.required(attachment.getBizType(), "not found String");
|
||||
TimiException.required(attachment.getName(), "not found attachment.name");
|
||||
String mongoId = null;
|
||||
try {
|
||||
if (attachment.getFile() != null) {
|
||||
attachment.setInputStream(attachment.getFile().getInputStream());
|
||||
}
|
||||
InputStream is = attachment.getInputStream();
|
||||
TimiException.required(is, "not found attachment.inputStream");
|
||||
|
||||
StringBuilder mongoName = new StringBuilder(attachment.getBizType().toString());
|
||||
StringBuilder mongoName = new StringBuilder(attachment.getBizType().name());
|
||||
if (TimiJava.isNotEmpty(attachment.getAttachType())) {
|
||||
mongoName.append("_").append(attachment.getAttachType().toUpperCase()).append("_");
|
||||
}
|
||||
@@ -91,108 +134,154 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
if (attachment.getMimeType().startsWith("image")) {
|
||||
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
|
||||
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode()));
|
||||
ObjectNode metadata = (ObjectNode) attachment.getMetadata();
|
||||
metadata.put("width", image.getWidth());
|
||||
metadata.put("height", image.getHeight());
|
||||
if (attachment.getMetadata() instanceof ObjectNode obj) {
|
||||
obj.put("width", image.getWidth());
|
||||
obj.put("height", image.getHeight());
|
||||
}
|
||||
}
|
||||
mapper.insert(attachment);
|
||||
} catch (Exception e) {
|
||||
if (mongoId != null) {
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(mongoId)));
|
||||
}
|
||||
log.error("create attachment error", e);
|
||||
log.error("create error", e);
|
||||
throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO read attachment input stream error");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void destroy(Long id) {
|
||||
public void update(Attachment attachment) {
|
||||
String newMongoId = null;
|
||||
try {
|
||||
Attachment attach = mapper.selectRaw(id);
|
||||
GridFSFile file = readByMongoId(attach.getMongoId());
|
||||
if (file != null) {
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attach.getMongoId())));
|
||||
if (attachment.getFile() != null) {
|
||||
attachment.setInputStream(attachment.getFile().getInputStream());
|
||||
}
|
||||
if (!attach.isDeleted()) {
|
||||
attach.setDeletedAt(Time.now());
|
||||
InputStream stream = attachment.getInputStream();
|
||||
if (stream == null) {
|
||||
// 无新文件,仅更新元数据字段
|
||||
super.update(attachment);
|
||||
return;
|
||||
}
|
||||
attach.setIsDestroyed(true);
|
||||
attach.setDestroyAt(Time.now());
|
||||
mapper.update(attach);
|
||||
} catch (Exception e) {
|
||||
log.error("delete mongo file error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error");
|
||||
}
|
||||
}
|
||||
// 有新文件时,先查旧记录获取旧 mongoId 和兜底字段
|
||||
Attachment old = mapper.selectRaw(attachment.getId());
|
||||
// 构建存储名,优先取调用方设置的字段,否则沿用旧值
|
||||
Attachment.BizType bizType = TimiJava.defaultIfNull(attachment.getBizType(), old.getBizType());
|
||||
String attachType = TimiJava.defaultIfEmpty(attachment.getAttachType(), old.getAttachType());
|
||||
String name = TimiJava.defaultIfEmpty(attachment.getName(), old.getName());
|
||||
StringBuilder mongoName = new StringBuilder(bizType.name());
|
||||
if (TimiJava.isNotEmpty(attachType)) {
|
||||
mongoName.append("_").append(attachType.toUpperCase()).append("_");
|
||||
}
|
||||
mongoName.append(name);
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public Attachment createMedia(Attachment attachment) throws TimiException {
|
||||
try {
|
||||
// 储存源文件
|
||||
attachment.setAttachTypeValue(MediaAttach.Type.SOURCE);
|
||||
create(attachment);
|
||||
|
||||
// 生成缩略图
|
||||
InputStream mimeStream = attachment.openInputStream();
|
||||
String mimeType = new Tika().detect(mimeStream);
|
||||
|
||||
InputStream sourceStream = attachment.openInputStream();
|
||||
ByteArrayOutputStream thumbStream = new ByteArrayOutputStream();
|
||||
switch (mimeType) {
|
||||
case "image/png", "image/bmp", "image/jpeg" -> Thumbnails.of(sourceStream).width(256).keepAspectRatio(true).toOutputStream(thumbStream);
|
||||
case "video/mp4", "video/quicktime" -> {
|
||||
log.info("capturing thumbnail: {}", attachment.getName());
|
||||
long start = Time.now();
|
||||
File tempFile = IO.file("temp/%s_%s".formatted(UUID.randomUUID().toString(), attachment.getName()));
|
||||
try {
|
||||
IO.toFile(tempFile, sourceStream);
|
||||
ByteArrayOutputStream baos = JavaCV.captureThumbnail(IO.getInputStream(tempFile), 2);
|
||||
Thumbnails.of(IO.toInputStream(baos)).width(256).keepAspectRatio(true).toOutputStream(thumbStream);
|
||||
log.info("captured thumbnail: {} at {} ms", attachment.getName(), Time.now() - start);
|
||||
} finally {
|
||||
IO.destroy(tempFile);
|
||||
}
|
||||
// 存储新文件
|
||||
newMongoId = gridFsTemplate.store(stream, mongoName.toString()).toString();
|
||||
GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(newMongoId)));
|
||||
attachment.setMongoId(newMongoId);
|
||||
attachment.setSize(gridFSFile.getLength());
|
||||
attachment.setMd5(IO.md5(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())));
|
||||
attachment.setMimeType(new Tika().detect(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())));
|
||||
if (attachment.getMimeType().startsWith("image")) {
|
||||
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
|
||||
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode()));
|
||||
if (attachment.getMetadata() instanceof ObjectNode obj) {
|
||||
obj.put("width", image.getWidth());
|
||||
obj.put("height", image.getHeight());
|
||||
}
|
||||
}
|
||||
Metadata.ThumbImage thumbMetadata = new Metadata.ThumbImage();
|
||||
thumbMetadata.setSourceId(attachment.getId());
|
||||
thumbMetadata.setSourceMongoId(attachment.getMongoId());
|
||||
thumbMetadata.setSourceMimeType(mimeType);
|
||||
|
||||
Attachment thumbAttach = new Attachment();
|
||||
thumbAttach.setName(Network.simpleURIFileName(attachment.getName()) + ".png");
|
||||
thumbAttach.setBizType(attachment.getBizType());
|
||||
thumbAttach.setBizId(attachment.getBizId());
|
||||
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
|
||||
thumbAttach.setMetadata(jackson.valueToTree(thumbMetadata));
|
||||
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
|
||||
create(thumbAttach);
|
||||
|
||||
return get(thumbAttach.getId());
|
||||
// 更新数据库后删除旧 GridFS 文件
|
||||
super.update(attachment);
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(old.getMongoId())));
|
||||
} catch (Exception e) {
|
||||
log.error("create media attachment error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO create media attachment error");
|
||||
// 新文件已上传但后续操作失败,清理新文件防止孤儿数据
|
||||
if (newMongoId != null) {
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(newMongoId)));
|
||||
}
|
||||
log.error("update error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO update file error");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void deleteMedia(Long thumbId) throws TimiException {
|
||||
Attachment attachment = get(thumbId);
|
||||
delete(attachment.getId());
|
||||
Metadata.ThumbImage thumbMetadata = jackson.convertValue(attachment.getMetadata(), Metadata.ThumbImage.class);
|
||||
delete(thumbMetadata.getSourceId());
|
||||
public Attachment fetchThumb(Attachment source, Integer requestWidth, Integer requestHeight) {
|
||||
TimiException.requiredTrue(requestWidth != null || requestHeight != null, "required width or height");
|
||||
String mimeType = source.getMimeType();
|
||||
boolean isImage = mimeType != null && Set.of("image/png", "image/bmp", "image/jpeg").contains(mimeType);
|
||||
boolean isVideo = mimeType != null && Set.of("video/mp4", "video/quicktime").contains(mimeType);
|
||||
if (!isImage && !isVideo) {
|
||||
return null;
|
||||
}
|
||||
// 命中缓存直接返回
|
||||
Attachment cached = mapper.selectThumb(source.getBizType(), source.getId(), requestWidth, requestHeight);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream thumbStream = new ByteArrayOutputStream();
|
||||
CallbackArg<InputStream> applyThumbSize = inputStream -> {
|
||||
try {
|
||||
Thumbnails.Builder<? extends InputStream> builder = Thumbnails.of(inputStream);
|
||||
if (requestWidth != null && requestHeight != null) {
|
||||
builder.size(requestWidth, requestHeight).keepAspectRatio(false).toOutputStream(thumbStream);
|
||||
} else if (requestWidth != null) {
|
||||
builder.width(requestWidth).keepAspectRatio(true).toOutputStream(thumbStream);
|
||||
} else {
|
||||
builder.height(requestHeight).keepAspectRatio(true).toOutputStream(thumbStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("applyThumbSize error for fetchThumb", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
if (isImage) {
|
||||
applyThumbSize.handler(source.openInputStream());
|
||||
} else {
|
||||
String name = source.getName();
|
||||
File tempFile = IO.file("temp/%s_%s".formatted(UUID.randomUUID(), name));
|
||||
try {
|
||||
IO.toFile(tempFile, source.openInputStream());
|
||||
ByteArrayOutputStream baos = JavaCV.captureThumbnail(IO.getInputStream(tempFile), 2);
|
||||
applyThumbSize.handler(IO.toInputStream(baos));
|
||||
} finally {
|
||||
IO.destroy(tempFile);
|
||||
}
|
||||
}
|
||||
Metadata.ThumbImage thumbMeta = new Metadata.ThumbImage();
|
||||
thumbMeta.setSourceId(source.getId());
|
||||
thumbMeta.setSourceMongoId(source.getMongoId());
|
||||
thumbMeta.setSourceMimeType(mimeType);
|
||||
thumbMeta.setRequestWidth(requestWidth);
|
||||
thumbMeta.setRequestHeight(requestHeight);
|
||||
|
||||
Attachment thumb = new Attachment();
|
||||
thumb.setName(Network.simpleURIFileName(source.getName()) + ".png");
|
||||
thumb.setBizType(source.getBizType());
|
||||
thumb.setBizId(source.getId());
|
||||
thumb.setAttachType(MediaAttach.Type.THUMB.toString());
|
||||
thumb.setMetadata(jackson.valueToTree(thumbMeta));
|
||||
thumb.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
|
||||
// 缩略图 7 天后过期,额外 1 天宽限供后台任务销毁
|
||||
long expireAt = Time.now() + 7 * Time.D;
|
||||
thumb.setDeletedAt(expireAt);
|
||||
thumb.setDestroyAt(expireAt + Time.D);
|
||||
create(thumb);
|
||||
return thumb;
|
||||
} catch (Exception e) {
|
||||
log.error("create thumbnail error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("exception.attachment.create.error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment getByBizId(Attachment.BizType bizType, long bizId) {
|
||||
public Attachment getByBizId(Attachment.BizType bizType, String bizId) {
|
||||
return mapper.selectByBizId(bizType, bizId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum<?> attachType) {
|
||||
public Attachment getByAttachType(Attachment.BizType bizType, String bizId, String attachType) {
|
||||
return mapper.selectByAttachType(bizType, bizId, attachType);
|
||||
}
|
||||
|
||||
@@ -201,59 +290,134 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
return mapper.selectByMongoId(mongoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridFSFile readByMongoId(String mongoId) {
|
||||
Attachment attach = mapper.selectByMongoId(mongoId);
|
||||
TimiException.required(attach, "not found attachment: %s".formatted(mongoId));
|
||||
return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(attach.getMongoId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStreamByMongoId(String mongoId) {
|
||||
return gridFSBucket.openDownloadStream(readByMongoId(mongoId).getObjectId());
|
||||
GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(mongoId)));
|
||||
return gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAllByMongoId(String mongoId) {
|
||||
try {
|
||||
return IO.toBytes(getByMongoId(mongoId).openInputStream());
|
||||
return IO.toBytes(getInputStreamByMongoId(mongoId));
|
||||
} catch (IOException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "TODO 读取失败");
|
||||
throw new TimiException(TimiCode.ERROR, "read error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, Enum<?>... attachTypes) {
|
||||
return mapper.listByBizId(bizType, bizId, Arrays.asList(attachTypes), null);
|
||||
public List<Attachment> listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes) {
|
||||
return mapper.selectByBizId(bizType, bizId, List.of(attachTypes), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByBizId(Attachment.BizType bizType, Long bizId, Enum<?>... attachTypes) {
|
||||
return mapper.countByBizId(bizType, bizId, Arrays.asList(attachTypes));
|
||||
public long countByBizId(Attachment.BizType bizType, String bizId, String... attachTypes) {
|
||||
return mapper.countByBizId(bizType, bizId, List.of(attachTypes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, List<String> md5s) throws TimiException {
|
||||
return mapper.listByMd5s(bizType, bizId, attachTypeList, md5s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByBizId(Attachment.BizType bizType, long bizId, Enum<?>... attachTypes) {
|
||||
Attachment example = new Attachment();
|
||||
example.setBizType(bizType);
|
||||
example.setBizId(bizId);
|
||||
if (TimiJava.isEmpty(attachTypes)) {
|
||||
mapper.deleteAllByExample(example);
|
||||
} else {
|
||||
for (int i = 0; i < attachTypes.length; i++) {
|
||||
example.setAttachTypeValue(attachTypes[i]);
|
||||
mapper.deleteAllByExample(example);
|
||||
}
|
||||
}
|
||||
public PageResult<Attachment> pageByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypeList, Page<Attachment> page) throws TimiException {
|
||||
PageResult<Attachment> result = new PageResult<>();
|
||||
result.setList(mapper.selectByBizId(bizType, bizId, attachTypeList, page));
|
||||
result.setTotal(mapper.countByBizId(bizType, bizId, attachTypeList));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Attachment> listNeedDestroy() {
|
||||
return mapper.selectNeedDestroy();
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void updateByBiz(BizUpdateReq req) {
|
||||
Attachment.BizType bizType = req.getBizType();
|
||||
String bizId = req.getBizId();
|
||||
List<Attachment> items = req.getItems();
|
||||
|
||||
// 数据库现有附件
|
||||
List<Attachment> dbList = mapper.selectAllByBizId(bizType, bizId);
|
||||
Set<String> dbIds = dbList.stream().map(Attachment::getId).collect(Collectors.toSet());
|
||||
// 请求附件 ID
|
||||
Set<String> reqIds = items.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
// 数据库有、请求无:删除
|
||||
for (Attachment attach : dbList) {
|
||||
if (!reqIds.contains(attach.getId())) {
|
||||
delete(attach.getId());
|
||||
}
|
||||
}
|
||||
|
||||
for (Attachment item : items) {
|
||||
boolean isNew = item.getId() == null || !dbIds.contains(item.getId());
|
||||
String tempFileId = item.getTempFileId();
|
||||
|
||||
if (isNew) {
|
||||
// 请求有、数据库无:将临时文件记录直接转存为永久附件
|
||||
if (TimiJava.isEmpty(tempFileId)) {
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("exception.attachment.file.required");
|
||||
}
|
||||
Attachment tempFile = mapper.selectRaw(tempFileId);
|
||||
tempFile.setBizType(bizType);
|
||||
tempFile.setBizId(bizId);
|
||||
tempFile.setAttachType(item.getAttachType());
|
||||
if (TimiJava.isNotEmpty(item.getName())) {
|
||||
tempFile.setName(item.getName());
|
||||
}
|
||||
if (item.getMetadata() != null) {
|
||||
tempFile.setMetadata(item.getMetadata());
|
||||
}
|
||||
// 清除过期标记,使其成为永久附件
|
||||
tempFile.setDeletedAt(null);
|
||||
tempFile.setDestroyAt(null);
|
||||
mapper.update(tempFile);
|
||||
} else if (TimiJava.isNotEmpty(tempFileId)) {
|
||||
// 请求有、数据库有、有 tempFileId → 更新文件与元数据
|
||||
// 将临时文件的 GridFS 存储转移至当前永久记录,废弃临时记录
|
||||
Attachment current = mapper.selectRaw(item.getId());
|
||||
Attachment tempFile = mapper.selectRaw(tempFileId);
|
||||
// 删除旧 GridFS 文件
|
||||
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(current.getMongoId())));
|
||||
// 将存储信息从临时记录迁移到永久记录
|
||||
current.setMongoId(tempFile.getMongoId());
|
||||
current.setSize(tempFile.getSize());
|
||||
current.setMd5(tempFile.getMd5());
|
||||
current.setMimeType(tempFile.getMimeType());
|
||||
current.setName(TimiJava.defaultIfEmpty(item.getName(), tempFile.getName()));
|
||||
if (item.getAttachType() != null) {
|
||||
current.setAttachType(item.getAttachType());
|
||||
}
|
||||
if (item.getMetadata() != null) {
|
||||
current.setMetadata(item.getMetadata());
|
||||
}
|
||||
mapper.update(current);
|
||||
// 废弃临时文件记录:清空 mongoId 防止后台任务误删已转移的 GridFS 文件
|
||||
tempFile.setMongoId(null);
|
||||
tempFile.setIsDestroyed(true);
|
||||
tempFile.setDestroyAt(Time.now());
|
||||
mapper.update(tempFile);
|
||||
} else {
|
||||
// 请求有、数据库有、无 tempFileId → 仅更新元数据
|
||||
Attachment current = mapper.selectRaw(item.getId());
|
||||
if (item.getAttachType() != null) {
|
||||
current.setAttachType(item.getAttachType());
|
||||
}
|
||||
if (TimiJava.isNotEmpty(item.getName())) {
|
||||
current.setName(item.getName());
|
||||
}
|
||||
if (item.getMetadata() != null) {
|
||||
current.setMetadata(item.getMetadata());
|
||||
}
|
||||
mapper.update(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void deleteByBiz(BizDeleteReq req) {
|
||||
List<Attachment> attachmentList = mapper.selectAllByBizId(req.getBizType(), req.getBizId());
|
||||
for (Attachment attachment : attachmentList) {
|
||||
delete(attachment.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
public class ClipboardServiceImplement implements ClipboardService {
|
||||
|
||||
/** Redis key 前缀 */
|
||||
private static final String KEY_PREFIX = "clipboard:";
|
||||
private static final String KEY_PREFIX = "CLIPBOARD:";
|
||||
|
||||
/** 剪切板内容最大长度 */
|
||||
private static final int MAX_CONTENT_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
|
||||
import com.imyeyu.api.modules.blog.service.CommentRemindQueueService;
|
||||
import com.imyeyu.api.modules.common.bean.CommentSupport;
|
||||
import com.imyeyu.api.modules.common.entity.Comment;
|
||||
import com.imyeyu.api.modules.common.entity.CommentReply;
|
||||
import com.imyeyu.api.modules.common.entity.EmailQueue;
|
||||
import com.imyeyu.api.modules.common.entity.User;
|
||||
import com.imyeyu.api.modules.common.entity.UserConfig;
|
||||
import com.imyeyu.api.modules.common.mapper.CommentReplyMapper;
|
||||
import com.imyeyu.api.modules.common.service.CommentReplyService;
|
||||
import com.imyeyu.api.modules.common.service.CommentService;
|
||||
import com.imyeyu.api.modules.common.service.EmailQueueService;
|
||||
import com.imyeyu.api.modules.common.service.UserConfigService;
|
||||
import com.imyeyu.api.modules.common.service.UserService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 评论回复服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-08-24 10:34
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
|
||||
public class CommentReplyServiceImplement extends AbstractEntityService<CommentReply, Long> implements CommentReplyService {
|
||||
|
||||
private final UserService userService;
|
||||
private final CommentService commentService;
|
||||
private final UserConfigService userConfigService;
|
||||
private final EmailQueueService emailQueueService;
|
||||
private final CommentRemindQueueService commentRemindQueueService;
|
||||
|
||||
private final CommentReplyMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<CommentReply, Long> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CommentReply> page(Page<CommentReply> page) {
|
||||
PageResult<CommentReply> result = super.page(page);
|
||||
for (CommentReply reply : result.getList()) {
|
||||
Comment comment = commentService.get(reply.getCommentId());
|
||||
if (TimiJava.isNotEmpty(comment.getUserId())) {
|
||||
comment.setUser(userService.view(comment.getUserId()));
|
||||
}
|
||||
reply.setComment(comment);
|
||||
if (TimiJava.isNotEmpty(reply.getSenderId())) {
|
||||
reply.setSender(userService.view(reply.getSenderId()));
|
||||
}
|
||||
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
|
||||
reply.setReceiver(userService.view(reply.getReceiverId()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void create(CommentReply commentReply) {
|
||||
Comment comment = commentService.get(commentReply.getCommentId());
|
||||
|
||||
Class<? extends GettableService<? extends CommentSupport, Long>> serviceClass = comment.getBizType().getServiceClass();
|
||||
GettableService<? extends CommentSupport, Long> service = TimiServerAPI.applicationContext.getBean(serviceClass);
|
||||
CommentSupport commentSupport = service.get(comment.getBizId());
|
||||
if (commentSupport.canNotComment()) {
|
||||
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("评论已关闭");
|
||||
}
|
||||
|
||||
CommentReply dbReply = new CommentReply();
|
||||
dbReply.setCommentId(comment.getId());
|
||||
|
||||
// 发送者
|
||||
String token = TimiSpring.getToken();
|
||||
if (TimiJava.isNotEmpty(token)) {
|
||||
User senderUser = userService.getLoginUser();
|
||||
if (senderUser.isBanning() || senderUser.isMuting()) {
|
||||
throw new TimiException(TimiCode.RESULT_BAN).msgKey("comment.banded");
|
||||
}
|
||||
dbReply.setSenderId(senderUser.getId());
|
||||
dbReply.setSenderNick(null);
|
||||
} else {
|
||||
if (TimiJava.isEmpty(commentReply.getSenderNick())) {
|
||||
throw new TimiException(TimiCode.ARG_MISS).msgKey("comment.nick.empty");
|
||||
}
|
||||
dbReply.setSenderNick(commentReply.getSenderNick());
|
||||
}
|
||||
// 被回复
|
||||
dbReply.setReplyId(commentReply.getReplyId());
|
||||
if (commentReply.getReplyId() == null) {
|
||||
// 回复主评论
|
||||
dbReply.setReceiverId(comment.getUserId());
|
||||
if (TimiJava.isEmpty(comment.getUserId())) {
|
||||
dbReply.setReceiverNick(comment.getNick());
|
||||
}
|
||||
} else {
|
||||
// 回复其他回复
|
||||
CommentReply targetReply = get(commentReply.getReplyId());
|
||||
dbReply.setReceiverId(targetReply.getSenderId());
|
||||
if (TimiJava.isEmpty(targetReply.getSenderId())) {
|
||||
dbReply.setReceiverNick(targetReply.getSenderNick());
|
||||
}
|
||||
}
|
||||
// 创建回复
|
||||
dbReply.setContent(commentReply.getContent());
|
||||
dbReply.setIp(TimiSpring.getRequestIP());
|
||||
super.create(dbReply);
|
||||
// 被回复为注册用户时处理通知和邮件
|
||||
{
|
||||
if (TimiJava.isEmpty(dbReply.getReceiverId())) {
|
||||
return;
|
||||
}
|
||||
if (TimiJava.isNotEmpty(dbReply.getSenderId()) && dbReply.getSenderId().equals(dbReply.getReceiverId())) {
|
||||
return;
|
||||
}
|
||||
// 被回复账号
|
||||
User receiverUser = userService.get(dbReply.getReceiverId());
|
||||
UserConfig userConfig = userConfigService.get(dbReply.getReceiverId());
|
||||
if (!receiverUser.emailVerified() || !userConfig.isEmailReplyRemind()) {
|
||||
return;
|
||||
}
|
||||
// 添加提醒队列
|
||||
CommentRemindQueue remindQueue = new CommentRemindQueue();
|
||||
remindQueue.setUUID(UUID.randomUUID().toString());
|
||||
remindQueue.setUserId(receiverUser.getId());
|
||||
remindQueue.setReplyId(dbReply.getId());
|
||||
commentRemindQueueService.create(remindQueue);
|
||||
|
||||
// 邮件队列
|
||||
EmailQueue emailQueue = emailQueueService.get(EmailQueue.BizType.REPLY_REMINAD, receiverUser.getId());
|
||||
if (emailQueue == null) {
|
||||
emailQueue = new EmailQueue();
|
||||
emailQueue.setBizType(EmailQueue.BizType.REPLY_REMINAD);
|
||||
emailQueue.setBizId(receiverUser.getId());
|
||||
long H10 = Time.H * 10;
|
||||
if (Time.now() < Time.today() + H10) {
|
||||
emailQueue.setSendAt(Time.today() + H10);
|
||||
} else {
|
||||
emailQueue.setSendAt(Time.tomorrow() + H10);
|
||||
}
|
||||
emailQueueService.create(emailQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void delete(Long crId) {
|
||||
super.delete(crId);
|
||||
commentRemindQueueService.destroyByReplyId(crId);
|
||||
}
|
||||
}
|
||||
+582
-89
@@ -1,135 +1,628 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
|
||||
import com.imyeyu.api.modules.common.bean.CommentSupport;
|
||||
import com.imyeyu.api.modules.common.entity.Comment;
|
||||
import com.imyeyu.api.modules.common.entity.CommentReply;
|
||||
import com.imyeyu.api.modules.common.entity.User;
|
||||
import com.imyeyu.api.modules.user.entity.User;
|
||||
import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
|
||||
import com.imyeyu.api.modules.common.mapper.CommentMapper;
|
||||
import com.imyeyu.api.modules.common.mapper.CommentRemindQueueMapper;
|
||||
import com.imyeyu.api.modules.common.mapper.CommentReplyMapper;
|
||||
import com.imyeyu.api.modules.user.mapper.UserMapper;
|
||||
import com.imyeyu.api.modules.common.service.CommentService;
|
||||
import com.imyeyu.api.modules.common.service.UserService;
|
||||
import com.imyeyu.api.modules.user.service.UserBehaviorRecordService;
|
||||
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.FlatCommentListItemResp;
|
||||
import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp;
|
||||
import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 评论操作服务实现
|
||||
*
|
||||
* 评论服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-23 21:41
|
||||
* @since 2026-05-27 17:20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
|
||||
public class CommentServiceImplement extends AbstractEntityService<Comment, Long> implements CommentService {
|
||||
@RequiredArgsConstructor
|
||||
public class CommentServiceImplement extends AbstractEntityService<Comment, String> implements CommentService {
|
||||
|
||||
private final UserService userService;
|
||||
private static final int USER_ROOT_LIMIT = 20;
|
||||
private static final int USER_REPLY_LIMIT = 6;
|
||||
private static final int SDK_LAYER_LIMIT = 100;
|
||||
private static final String BEHAVIOR_BIZ_TYPE = "USER";
|
||||
private static final String BEHAVIOR_TARGET_TYPE_COMMENT = "COMMENT";
|
||||
private static final String BEHAVIOR_ACTION_TYPE_LIKE = "LIKE";
|
||||
private static final String BEHAVIOR_ACTION_TYPE_UNLIKE = "UNLIKE";
|
||||
|
||||
private final UserMapper userMapper;
|
||||
private final UserBehaviorRecordService userBehaviorRecordService;
|
||||
private final CommentMapper mapper;
|
||||
private final CommentReplyMapper replyMapper;
|
||||
private final CommentRemindQueueMapper remindQueueMapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Comment, Long> mapper() {
|
||||
protected BaseMapper<Comment, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public PageResult<Comment> page(Page<Comment> page) {
|
||||
PageResult<Comment> result = super.page(page);
|
||||
for (Comment comment : result.getList()) {
|
||||
public void create(Comment entity) {
|
||||
TimiException.required(entity, "not found entity");
|
||||
if (TimiJava.isEmpty(entity.getParentId())) {
|
||||
createRoot(entity);
|
||||
return;
|
||||
}
|
||||
createReply(entity, entity.getParentId());
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void update(Comment comment) {
|
||||
TimiException.required(comment, "not found comment");
|
||||
TimiException.required(comment.getId(), "not found comment.id");
|
||||
Comment dbComment = get(comment.getId());
|
||||
TimiException.required(dbComment, "not found comment");
|
||||
|
||||
dbComment.setContent(TimiJava.defaultIfEmpty(comment.getContent(), dbComment.getContent()));
|
||||
dbComment.setIp(TimiJava.defaultIfEmpty(comment.getIp(), dbComment.getIp()));
|
||||
dbComment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), dbComment.getStatus()));
|
||||
dbComment.setIsTop(TimiJava.defaultIfNull(comment.getIsTop(), dbComment.getIsTop()));
|
||||
super.update(dbComment);
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
deleteInternal(id, null);
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void like(String commentId, String userId) {
|
||||
Comment comment = get(commentId);
|
||||
TimiException.required(comment, "not found comment");
|
||||
TimiException.required(userId, "not found userId");
|
||||
userBehaviorRecordService.cancel(BEHAVIOR_BIZ_TYPE, userId, BEHAVIOR_TARGET_TYPE_COMMENT, commentId, BEHAVIOR_ACTION_TYPE_UNLIKE);
|
||||
boolean created = createBehaviorRecord(comment, userId, BEHAVIOR_ACTION_TYPE_LIKE);
|
||||
if (created) {
|
||||
mapper.increaseLikeCount(commentId, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void unlike(String commentId, String userId) {
|
||||
Comment comment = get(commentId);
|
||||
TimiException.required(comment, "not found comment");
|
||||
TimiException.required(userId, "not found userId");
|
||||
boolean canceled = userBehaviorRecordService.cancel(BEHAVIOR_BIZ_TYPE, userId, BEHAVIOR_TARGET_TYPE_COMMENT, commentId, BEHAVIOR_ACTION_TYPE_LIKE);
|
||||
if (canceled) {
|
||||
mapper.increaseLikeCount(commentId, -1);
|
||||
}
|
||||
createBehaviorRecord(comment, userId, BEHAVIOR_ACTION_TYPE_UNLIKE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<UserBehaviorRecord> listLikedRecord(Page<UserBehaviorRecord> page, String userId) {
|
||||
TimiException.required(userId, "not found userId");
|
||||
if (page == null) {
|
||||
page = new Page<>();
|
||||
}
|
||||
UserBehaviorRecord equalsExample = page.getEqualsExample();
|
||||
if (equalsExample == null) {
|
||||
equalsExample = new UserBehaviorRecord();
|
||||
page.setEqualsExample(equalsExample);
|
||||
}
|
||||
equalsExample.setBizType(BEHAVIOR_BIZ_TYPE);
|
||||
equalsExample.setUserId(userId);
|
||||
equalsExample.setTargetType(BEHAVIOR_TARGET_TYPE_COMMENT);
|
||||
equalsExample.setActionType(BEHAVIOR_ACTION_TYPE_LIKE);
|
||||
|
||||
PageResult<UserBehaviorRecord> result = userBehaviorRecordService.page(page);
|
||||
fillBehaviorRecordComment(result.getList());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlatCommentListResp listFlatRoot(String bizType, String bizId, int offset, String currentUserId) {
|
||||
TimiException.required(bizType, "not found bizType");
|
||||
TimiException.required(bizId, "not found bizId");
|
||||
int validOffset = Math.max(offset, 0);
|
||||
List<Comment> rootList = mapper.selectRootList(bizType, bizId, validOffset, USER_ROOT_LIMIT);
|
||||
List<FlatCommentListItemResp> itemList = buildFlatRootPreview(rootList, currentUserId);
|
||||
FlatCommentListResp resp = new FlatCommentListResp();
|
||||
resp.setTotal(mapper.countRoot(bizType, bizId));
|
||||
resp.setList(itemList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestedCommentListResp listNestedRoot(String bizType, String bizId, int offset, String currentUserId) {
|
||||
TimiException.required(bizType, "not found bizType");
|
||||
TimiException.required(bizId, "not found bizId");
|
||||
int validOffset = Math.max(offset, 0);
|
||||
List<Comment> rootList = mapper.selectRootList(bizType, bizId, validOffset, USER_ROOT_LIMIT);
|
||||
List<CommentNodeResp> itemList = buildNestedRootPreview(rootList, currentUserId);
|
||||
NestedCommentListResp resp = new NestedCommentListResp();
|
||||
resp.setTotal(mapper.countRoot(bizType, bizId));
|
||||
resp.setList(itemList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentThreadResp getUserThread(String commentId, int offset, String currentUserId) {
|
||||
TimiException.required(commentId, "not found commentId");
|
||||
int validOffset = Math.max(offset, 0);
|
||||
Comment current = get(commentId);
|
||||
TimiException.required(current, "not found comment");
|
||||
String rootId = TimiJava.defaultIfEmpty(current.getRootId(), current.getId());
|
||||
Comment root = get(rootId);
|
||||
TimiException.required(root, "not found root comment");
|
||||
List<Comment> replyList = mapper.selectReplyListByRootId(rootId, validOffset, USER_ROOT_LIMIT);
|
||||
fillUser(replyList);
|
||||
fillLiked(replyList, currentUserId);
|
||||
fillUser(List.of(root));
|
||||
fillLiked(List.of(root), currentUserId);
|
||||
CommentThreadResp resp = new CommentThreadResp();
|
||||
resp.setRoot(root);
|
||||
resp.setReplyTotal(mapper.countReplyByRootId(rootId));
|
||||
resp.setReplyList(replyList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentLayerResp listSdkLayer(CommentLayerQueryReq req, String currentUserId) {
|
||||
TimiException.required(req, "not found req");
|
||||
TimiException.required(req.getBizType(), "not found req.bizType");
|
||||
TimiException.required(req.getBizId(), "not found req.bizId");
|
||||
|
||||
int validOffset = Math.max(TimiJava.defaultIfNull(req.getOffset(), 0), 0);
|
||||
int validLimit = TimiJava.defaultIfNull(req.getLimit(), SDK_LAYER_LIMIT);
|
||||
validLimit = Math.min(Math.max(validLimit, 1), SDK_LAYER_LIMIT);
|
||||
|
||||
List<Comment> commentList = mapper.selectLayerList(req.getBizType(), req.getBizId(), req.getParentId(), validOffset, validLimit);
|
||||
fillUser(commentList);
|
||||
fillLiked(commentList, currentUserId);
|
||||
CommentLayerResp resp = new CommentLayerResp();
|
||||
resp.setTotal(mapper.countLayer(req.getBizType(), req.getBizId(), req.getParentId()));
|
||||
resp.setList(commentList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentNodeResp getUserNode(String commentId, String currentUserId) {
|
||||
TimiException.required(commentId, "not found commentId");
|
||||
Comment comment = get(commentId);
|
||||
TimiException.required(comment, "not found comment");
|
||||
|
||||
fillUser(List.of(comment));
|
||||
fillLiked(List.of(comment), currentUserId);
|
||||
List<Comment> replyList = mapper.selectReplyListByParentId(commentId, 0, USER_REPLY_LIMIT);
|
||||
fillUser(replyList);
|
||||
fillLiked(replyList, currentUserId);
|
||||
|
||||
CommentNodeResp resp = new CommentNodeResp();
|
||||
resp.setComment(comment);
|
||||
resp.setReplyTotal(mapper.countReplyByParentId(commentId));
|
||||
resp.setReplyList(replyList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentLayerResp getUserChildren(String commentId, int offset, String currentUserId) {
|
||||
TimiException.required(commentId, "not found commentId");
|
||||
int validOffset = Math.max(offset, 0);
|
||||
Comment comment = get(commentId);
|
||||
TimiException.required(comment, "not found comment");
|
||||
List<Comment> replyList = mapper.selectReplyListByParentId(commentId, validOffset, USER_ROOT_LIMIT);
|
||||
fillUser(replyList);
|
||||
fillLiked(replyList, currentUserId);
|
||||
CommentLayerResp resp = new CommentLayerResp();
|
||||
resp.setTotal(mapper.countReplyByParentId(commentId));
|
||||
resp.setList(replyList);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comment detailTree(String id, String currentUserId) {
|
||||
Comment current = get(id);
|
||||
TimiException.required(current, "not found comment");
|
||||
|
||||
String rootId = current.getRootId();
|
||||
if (TimiJava.isEmpty(rootId)) {
|
||||
rootId = current.getId();
|
||||
}
|
||||
Comment root = get(rootId);
|
||||
TimiException.required(root, "not found root comment");
|
||||
List<Comment> replyList = mapper.selectAllReplyListByRootId(rootId);
|
||||
return attachTree(List.of(root), replyList, currentUserId).getFirst();
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void deleteOwned(String id, String userId) {
|
||||
TimiException.required(userId, "not found userId");
|
||||
deleteInternal(id, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建主评论
|
||||
*
|
||||
* @param comment 评论
|
||||
*/
|
||||
private void createRoot(Comment comment) {
|
||||
TimiException.required(comment.getBizType(), "not found comment.bizType");
|
||||
TimiException.required(comment.getBizId(), "not found comment.bizId");
|
||||
requireCommentUser(comment);
|
||||
TimiException.required(comment.getContent(), "not found comment.content");
|
||||
|
||||
comment.setParentId(null);
|
||||
comment.setRootId(null);
|
||||
comment.setReplyUserId(null);
|
||||
comment.setReplyUserNick(null);
|
||||
comment.setLevel(1);
|
||||
comment.setReplyCount(TimiJava.defaultIfNull(comment.getReplyCount(), 0));
|
||||
comment.setLikeCount(TimiJava.defaultIfNull(comment.getLikeCount(), 0));
|
||||
comment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), Comment.Status.NORMAL));
|
||||
comment.setIsTop(TimiJava.defaultIfNull(comment.getIsTop(), false));
|
||||
comment.setLastReplyAt(Time.now());
|
||||
|
||||
super.create(comment);
|
||||
comment.setRootId(comment.getId());
|
||||
super.update(comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建回复评论
|
||||
*
|
||||
* @param comment 评论
|
||||
* @param parentId 父评论 ID
|
||||
*/
|
||||
private void createReply(Comment comment, String parentId) {
|
||||
requireCommentUser(comment);
|
||||
TimiException.required(comment.getContent(), "not found comment.content");
|
||||
|
||||
Comment parent = get(parentId);
|
||||
TimiException.required(parent, "not found parent comment");
|
||||
|
||||
String rootId = TimiJava.defaultIfEmpty(parent.getRootId(), parent.getId());
|
||||
comment.setBizType(parent.getBizType());
|
||||
comment.setBizId(parent.getBizId());
|
||||
comment.setParentId(parent.getId());
|
||||
comment.setRootId(rootId);
|
||||
comment.setReplyUserId(TimiJava.defaultIfEmpty(comment.getReplyUserId(), parent.getUserId()));
|
||||
comment.setReplyUserNick(TimiJava.defaultIfEmpty(comment.getReplyUserNick(), parent.getUserNick()));
|
||||
comment.setLevel(TimiJava.defaultIfNull(parent.getLevel(), 1) + 1);
|
||||
comment.setReplyCount(TimiJava.defaultIfNull(comment.getReplyCount(), 0));
|
||||
comment.setLikeCount(TimiJava.defaultIfNull(comment.getLikeCount(), 0));
|
||||
comment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), Comment.Status.NORMAL));
|
||||
comment.setIsTop(false);
|
||||
comment.setLastReplyAt(null);
|
||||
|
||||
super.create(comment);
|
||||
mapper.increaseReplyCount(rootId, 1);
|
||||
if (!rootId.equals(parent.getId())) {
|
||||
mapper.increaseReplyCount(parent.getId(), 1);
|
||||
}
|
||||
Comment root = new Comment();
|
||||
root.setId(rootId);
|
||||
root.setLastReplyAt(Time.now());
|
||||
super.update(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验评论用户
|
||||
*
|
||||
* @param comment 评论
|
||||
*/
|
||||
private void requireCommentUser(Comment comment) {
|
||||
TimiException.requiredTrue(TimiJava.isNotEmpty(comment.getUserId()) || TimiJava.isNotEmpty(comment.getUserNick()), "not found comment.userId or comment.userNick");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论及其子树
|
||||
*
|
||||
* @param id 评论 ID
|
||||
* @param requiredUserId 要求归属的用户 ID,为 null 时不校验
|
||||
*/
|
||||
private void deleteInternal(String id, String requiredUserId) {
|
||||
Comment current = get(id);
|
||||
TimiException.required(current, "not found comment");
|
||||
if (requiredUserId != null) {
|
||||
TimiException.requiredTrue(requiredUserId.equals(current.getUserId()), "comment not belong to current user");
|
||||
}
|
||||
String rootId = TimiJava.defaultIfEmpty(current.getRootId(), current.getId());
|
||||
List<Comment> replyList = mapper.selectAllReplyListByRootId(rootId);
|
||||
Map<String, List<Comment>> childMap = groupByParentId(replyList);
|
||||
List<Comment> deleteList = new ArrayList<>();
|
||||
collectDeleteComment(current, childMap, deleteList);
|
||||
for (Comment comment : deleteList) {
|
||||
super.delete(comment.getId());
|
||||
}
|
||||
if (TimiJava.isEmpty(current.getParentId())) {
|
||||
return;
|
||||
}
|
||||
int deleteReplyCount = deleteList.size();
|
||||
mapper.increaseReplyCount(rootId, -deleteReplyCount);
|
||||
if (!rootId.equals(current.getParentId())) {
|
||||
mapper.increaseReplyCount(current.getParentId(), -1);
|
||||
}
|
||||
Comment root = new Comment();
|
||||
root.setId(rootId);
|
||||
root.setLastReplyAt(mapper.selectLatestReplyAtByRootId(rootId));
|
||||
super.update(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装评论树
|
||||
*
|
||||
* @param rootList 根评论列表
|
||||
* @param replyList 回复列表
|
||||
* @param currentUserId 当前用户 ID
|
||||
* @return 评论树列表
|
||||
*/
|
||||
private List<Comment> attachTree(List<Comment> rootList, List<Comment> replyList, String currentUserId) {
|
||||
List<Comment> allCommentList = new ArrayList<>(rootList);
|
||||
allCommentList.addAll(replyList);
|
||||
fillUser(allCommentList);
|
||||
Set<String> likedCommentIdSet = getBehaviorCommentIdSet(currentUserId, allCommentList, BEHAVIOR_ACTION_TYPE_LIKE);
|
||||
Set<String> unlikedCommentIdSet = getBehaviorCommentIdSet(currentUserId, allCommentList, BEHAVIOR_ACTION_TYPE_UNLIKE);
|
||||
for (Comment root : rootList) {
|
||||
root.setReplyList(new ArrayList<>());
|
||||
root.setLiked(likedCommentIdSet.contains(root.getId()));
|
||||
root.setUnliked(unlikedCommentIdSet.contains(root.getId()));
|
||||
}
|
||||
Map<String, Comment> commentMap = new LinkedHashMap<>();
|
||||
for (Comment root : rootList) {
|
||||
commentMap.put(root.getId(), root);
|
||||
}
|
||||
replyList.sort(Comparator.comparing(Comment::getCreatedAt, Comparator.nullsLast(Long::compareTo)));
|
||||
for (Comment reply : replyList) {
|
||||
reply.setReplyList(new ArrayList<>());
|
||||
reply.setLiked(likedCommentIdSet.contains(reply.getId()));
|
||||
reply.setUnliked(unlikedCommentIdSet.contains(reply.getId()));
|
||||
commentMap.put(reply.getId(), reply);
|
||||
Comment parent = commentMap.get(reply.getParentId());
|
||||
if (parent != null) {
|
||||
parent.getReplyList().add(reply);
|
||||
}
|
||||
}
|
||||
return rootList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充点赞状态
|
||||
*
|
||||
* @param commentList 评论列表
|
||||
* @param currentUserId 当前用户 ID
|
||||
*/
|
||||
private void fillLiked(List<Comment> commentList, String currentUserId) {
|
||||
if (TimiJava.isEmpty(commentList)) {
|
||||
return;
|
||||
}
|
||||
Set<String> likedCommentIdSet = getBehaviorCommentIdSet(currentUserId, commentList, BEHAVIOR_ACTION_TYPE_LIKE);
|
||||
Set<String> unlikedCommentIdSet = getBehaviorCommentIdSet(currentUserId, commentList, BEHAVIOR_ACTION_TYPE_UNLIKE);
|
||||
for (Comment comment : commentList) {
|
||||
comment.setLiked(likedCommentIdSet.contains(comment.getId()));
|
||||
comment.setUnliked(unlikedCommentIdSet.contains(comment.getId()));
|
||||
comment.setReplyList(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充评论用户信息
|
||||
*
|
||||
* @param commentList 评论列表
|
||||
*/
|
||||
private void fillUser(List<Comment> commentList) {
|
||||
if (TimiJava.isEmpty(commentList)) {
|
||||
return;
|
||||
}
|
||||
Set<String> userIdSet = new LinkedHashSet<>();
|
||||
for (Comment comment : commentList) {
|
||||
if (TimiJava.isNotEmpty(comment.getUserId())) {
|
||||
comment.setUser(userService.view(comment.getUserId()));
|
||||
userIdSet.add(comment.getUserId());
|
||||
}
|
||||
Page<CommentReply> replyPage = new Page<>();
|
||||
replyPage.setIndex(0);
|
||||
replyPage.setSize(6);
|
||||
{
|
||||
CommentReply exampleReply = new CommentReply();
|
||||
exampleReply.setCommentId(comment.getId());
|
||||
replyPage.setEqualsExample(exampleReply);
|
||||
}
|
||||
PageResult<CommentReply> replyResult = replyMapper.selectPageResult(replyPage);
|
||||
comment.setReplies(replyResult.getList());
|
||||
comment.setRepliesLength(replyResult.getTotal());
|
||||
for (CommentReply reply : replyResult.getList()) {
|
||||
if (TimiJava.isNotEmpty(reply.getSenderId())) {
|
||||
reply.setSender(userService.view(reply.getSenderId()));
|
||||
}
|
||||
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
|
||||
reply.setReceiver(userService.view(reply.getReceiverId()));
|
||||
}
|
||||
if (TimiJava.isNotEmpty(comment.getReplyUserId())) {
|
||||
userIdSet.add(comment.getReplyUserId());
|
||||
}
|
||||
}
|
||||
if (userIdSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, User> userMap = new LinkedHashMap<>();
|
||||
for (User user : userMapper.listByIdList(new ArrayList<>(userIdSet))) {
|
||||
userMap.put(user.getId(), sanitizeUser(user));
|
||||
}
|
||||
for (Comment comment : commentList) {
|
||||
comment.setUser(userMap.get(comment.getUserId()));
|
||||
comment.setReplyUser(userMap.get(comment.getReplyUserId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理用户敏感字段
|
||||
*
|
||||
* @param user 用户
|
||||
* @return 用户
|
||||
*/
|
||||
private User sanitizeUser(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
user.setPassword(null);
|
||||
user.setLastLoginIP(null);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户行为评论 ID 集合
|
||||
*
|
||||
* @param currentUserId 当前用户 ID
|
||||
* @param commentList 评论列表
|
||||
* @param actionType 行为类型
|
||||
* @return 评论 ID 集合
|
||||
*/
|
||||
private Set<String> getBehaviorCommentIdSet(String currentUserId, List<Comment> commentList, String actionType) {
|
||||
if (TimiJava.isEmpty(commentList)) {
|
||||
return Set.of();
|
||||
}
|
||||
return userBehaviorRecordService.getActiveTargetIdSet(BEHAVIOR_BIZ_TYPE, currentUserId, BEHAVIOR_TARGET_TYPE_COMMENT, actionType, commentList.stream().map(Comment::getId).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建评论行为记录
|
||||
*
|
||||
* @param comment 评论
|
||||
* @param userId 用户 ID
|
||||
* @param actionType 行为类型
|
||||
* @return true 为新增记录
|
||||
*/
|
||||
private boolean createBehaviorRecord(Comment comment, String userId, String actionType) {
|
||||
UserBehaviorRecord record = new UserBehaviorRecord();
|
||||
record.setBizType(BEHAVIOR_BIZ_TYPE);
|
||||
record.setUserId(userId);
|
||||
record.setTargetType(BEHAVIOR_TARGET_TYPE_COMMENT);
|
||||
record.setTargetId(comment.getId());
|
||||
record.setActionType(actionType);
|
||||
return userBehaviorRecordService.record(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充行为记录评论信息
|
||||
*
|
||||
* @param recordList 行为记录列表
|
||||
*/
|
||||
private void fillBehaviorRecordComment(List<UserBehaviorRecord> recordList) {
|
||||
if (TimiJava.isEmpty(recordList)) {
|
||||
return;
|
||||
}
|
||||
List<String> commentIdList = recordList.stream().map(UserBehaviorRecord::getTargetId).filter(TimiJava::isNotEmpty).distinct().toList();
|
||||
if (commentIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Comment> commentList = mapper.listByIdList(commentIdList);
|
||||
fillUser(commentList);
|
||||
Map<String, Comment> commentMap = new LinkedHashMap<>();
|
||||
for (Comment comment : commentList) {
|
||||
commentMap.put(comment.getId(), comment);
|
||||
}
|
||||
for (UserBehaviorRecord record : recordList) {
|
||||
record.setComment(commentMap.get(record.getTargetId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建扁平主评论预览
|
||||
*
|
||||
* @param rootList 主评论列表
|
||||
* @param currentUserId 当前用户 ID
|
||||
* @return 预览列表
|
||||
*/
|
||||
private List<FlatCommentListItemResp> buildFlatRootPreview(List<Comment> rootList, String currentUserId) {
|
||||
if (rootList == null || rootList.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
fillUser(rootList);
|
||||
fillLiked(rootList, currentUserId);
|
||||
List<String> rootIdList = rootList.stream().map(Comment::getId).toList();
|
||||
List<Comment> previewList = mapper.selectReplyPreviewByRootIds(rootIdList, USER_REPLY_LIMIT);
|
||||
fillUser(previewList);
|
||||
fillLiked(previewList, currentUserId);
|
||||
Map<String, List<Comment>> replyMap = new LinkedHashMap<>();
|
||||
for (Comment reply : previewList) {
|
||||
replyMap.computeIfAbsent(reply.getRootId(), key -> new ArrayList<>()).add(reply);
|
||||
}
|
||||
|
||||
List<FlatCommentListItemResp> result = new ArrayList<>();
|
||||
for (Comment root : rootList) {
|
||||
FlatCommentListItemResp item = new FlatCommentListItemResp();
|
||||
item.setRoot(root);
|
||||
item.setReplyPreviewTotal(mapper.countReplyByRootId(root.getId()));
|
||||
item.setReplyPreviewList(replyMap.getOrDefault(root.getId(), List.of()));
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void create(Comment comment) {
|
||||
Class<? extends GettableService<? extends CommentSupport, Long>> serviceClass = comment.getBizType().getServiceClass();
|
||||
GettableService<? extends CommentSupport, Long> service = TimiServerAPI.applicationContext.getBean(serviceClass);
|
||||
CommentSupport commentSupport = service.get(comment.getBizId());
|
||||
TimiException.requiredTrue(commentSupport.canComment(), "comment.not.can.comment");
|
||||
|
||||
// 令牌和账号验证
|
||||
if (TimiJava.isNotEmpty(TimiSpring.getToken())) {
|
||||
User commentUser = userService.getLoginUser();
|
||||
TimiException.requiredTrue(!commentUser.isBanning(), "comment.user.banned");
|
||||
TimiException.requiredTrue(!commentUser.isMuting(), "comment.user.muting");
|
||||
comment.setUserId(commentUser.getId());
|
||||
comment.setNick(null);
|
||||
} else {
|
||||
TimiException.required(comment.getNick(), "comment.nick.empty");
|
||||
/**
|
||||
* 构建层级主评论预览
|
||||
*
|
||||
* @param rootList 主评论列表
|
||||
* @param currentUserId 当前用户 ID
|
||||
* @return 预览列表
|
||||
*/
|
||||
private List<CommentNodeResp> buildNestedRootPreview(List<Comment> rootList, String currentUserId) {
|
||||
if (rootList == null || rootList.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
// 内容
|
||||
TimiException.required(comment.getContent(), "comment.data.empty");
|
||||
comment.setIp(TimiSpring.getRequestIP());
|
||||
super.create(comment);
|
||||
fillUser(rootList);
|
||||
fillLiked(rootList, currentUserId);
|
||||
List<CommentNodeResp> result = new ArrayList<>();
|
||||
for (Comment root : rootList) {
|
||||
List<Comment> replyList = mapper.selectReplyListByParentId(root.getId(), 0, USER_REPLY_LIMIT);
|
||||
fillUser(replyList);
|
||||
fillLiked(replyList, currentUserId);
|
||||
CommentNodeResp item = new CommentNodeResp();
|
||||
item.setComment(root);
|
||||
item.setReplyTotal(mapper.countReplyByParentId(root.getId()));
|
||||
item.setReplyList(replyList);
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void delete(Long commentId) {
|
||||
User user = userService.getLoginUser();
|
||||
|
||||
Comment comment = get(commentId);
|
||||
if (!comment.getUserId().equals(user.getId())) {
|
||||
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("token.illegal");
|
||||
}
|
||||
{
|
||||
// 移除被回复者的回复提醒队列
|
||||
CommentReply example = new CommentReply();
|
||||
example.setSenderId(user.getId());
|
||||
List<CommentReply> replies = replyMapper.selectAllByExample(example);
|
||||
for (CommentReply reply : replies) {
|
||||
CommentRemindQueue destroyRemindExample = new CommentRemindQueue();
|
||||
destroyRemindExample.setReplyId(reply.getReplyId());
|
||||
remindQueueMapper.destroyAllByExample(destroyRemindExample);
|
||||
/**
|
||||
* 按父评论 ID 分组
|
||||
*
|
||||
* @param commentList 评论列表
|
||||
* @return 分组结果
|
||||
*/
|
||||
private Map<String, List<Comment>> groupByParentId(List<Comment> commentList) {
|
||||
Map<String, List<Comment>> result = new LinkedHashMap<>();
|
||||
for (Comment comment : commentList) {
|
||||
if (TimiJava.isEmpty(comment.getParentId())) {
|
||||
continue;
|
||||
}
|
||||
result.computeIfAbsent(comment.getParentId(), key -> new ArrayList<>()).add(comment);
|
||||
}
|
||||
{
|
||||
// 删除回复
|
||||
CommentReply example = new CommentReply();
|
||||
example.setCommentId(commentId);
|
||||
replyMapper.deleteAllByExample(example);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集待删除评论
|
||||
*
|
||||
* @param current 当前评论
|
||||
* @param childMap 子评论分组
|
||||
* @param result 结果列表
|
||||
*/
|
||||
private void collectDeleteComment(Comment current, Map<String, List<Comment>> childMap, List<Comment> result) {
|
||||
result.add(current);
|
||||
List<Comment> childList = childMap.get(current.getId());
|
||||
if (childList == null || childList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Comment child : childList) {
|
||||
collectDeleteComment(child, childMap, result);
|
||||
}
|
||||
// 删除评论
|
||||
super.delete(commentId);
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.DataDictOption;
|
||||
import com.imyeyu.api.modules.common.mapper.DataDictOptionMapper;
|
||||
import com.imyeyu.api.modules.common.service.DataDictOptionService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据字典选项服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-05-13 14:46
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DataDictOptionServiceImplement extends AbstractEntityService<DataDictOption, String> implements DataDictOptionService {
|
||||
|
||||
private final DataDictOptionMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<DataDictOption, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.DataDict;
|
||||
import com.imyeyu.api.modules.common.mapper.DataDictMapper;
|
||||
import com.imyeyu.api.modules.common.service.DataDictService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据字典服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-05-13 14:46
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DataDictServiceImplement extends AbstractEntityService<DataDict, String> implements DataDictService {
|
||||
|
||||
private final DataDictMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<DataDict, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.EmailFilter;
|
||||
import com.imyeyu.api.modules.common.mapper.EmailFilterMapper;
|
||||
import com.imyeyu.api.modules.common.service.EmailFilterService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 邮件过滤服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-05-13 14:46
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EmailFilterServiceImplement extends AbstractEntityService<EmailFilter, String> implements EmailFilterService {
|
||||
|
||||
private final EmailFilterMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<EmailFilter, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.entity.EmailQueue;
|
||||
import com.imyeyu.api.modules.common.entity.EmailQueueLog;
|
||||
import com.imyeyu.api.modules.common.mapper.EmailQueueLogMapper;
|
||||
import com.imyeyu.api.modules.common.mapper.EmailQueueMapper;
|
||||
import com.imyeyu.api.modules.common.service.EmailQueueService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 邮件推送队列服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-08-24 16:21
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EmailQueueServiceImplement extends AbstractEntityService<EmailQueue, String> implements EmailQueueService {
|
||||
|
||||
private final EmailQueueMapper mapper;
|
||||
private final EmailQueueLogMapper logMapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<EmailQueue, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmailQueue get(EmailQueue.BizType type, Long dataId) {
|
||||
return mapper.query(type, dataId);
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void addLog(EmailQueueLog log) {
|
||||
logMapper.insert(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmailQueue> listAll() {
|
||||
return mapper.listAll();
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.entity.Feedback;
|
||||
import com.imyeyu.api.modules.common.mapper.FeedbackMapper;
|
||||
import com.imyeyu.api.modules.common.service.FeedbackService;
|
||||
import com.imyeyu.api.modules.common.vo.FeedbackRequest;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 反馈服务实现
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-11-16 22:27
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FeedbackServiceImplement extends AbstractEntityService<Feedback, Long> implements FeedbackService {
|
||||
|
||||
private final FeedbackMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Feedback, Long> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void create(FeedbackRequest request) {
|
||||
Feedback feedback = new Feedback();
|
||||
feedback.setFrom(request.getFrom());
|
||||
feedback.setEmail(request.getEmail());
|
||||
feedback.setData(request.getData());
|
||||
super.create(feedback);
|
||||
}
|
||||
}
|
||||
+45
-38
@@ -3,16 +3,21 @@ package com.imyeyu.api.modules.common.service.implement;
|
||||
import com.imyeyu.api.modules.common.entity.Icon;
|
||||
import com.imyeyu.api.modules.common.mapper.IconMapper;
|
||||
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.bean.PageResult;
|
||||
import com.imyeyu.font.icon.IconFontBuilder;
|
||||
import com.imyeyu.font.icon.model.GlyphAlignment;
|
||||
import com.imyeyu.font.icon.model.IconGlyph;
|
||||
import com.imyeyu.font.icon.model.IconFontOptions;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 图标服务实现
|
||||
@@ -22,53 +27,55 @@ import java.util.List;
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class IconServiceImplement extends AbstractEntityService<Icon, Long> implements IconService {
|
||||
public class IconServiceImplement extends AbstractEntityService<Icon, String> implements IconService {
|
||||
|
||||
private static final IconFontOptions TTF_OPTIONS = createTtfOptions();
|
||||
|
||||
private final IconMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Icon, Long> mapper() {
|
||||
protected BaseMapper<Icon, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AllResponse listAll(Long latest) {
|
||||
List<Icon> list = mapper.listAll();
|
||||
long dbLatest = -1;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (dbLatest < list.get(i).getCreatedAt()) {
|
||||
dbLatest = list.get(i).getCreatedAt();
|
||||
}
|
||||
if (list.get(i).getUpdatedAt() != null && dbLatest < list.get(i).getUpdatedAt()) {
|
||||
dbLatest = list.get(i).getUpdatedAt();
|
||||
}
|
||||
}
|
||||
AllResponse response = new AllResponse();
|
||||
response.setLatest(dbLatest);
|
||||
if (latest < dbLatest) {
|
||||
// 存在更新
|
||||
response.setIcons(list);
|
||||
}
|
||||
return response;
|
||||
public List<String> listName() {
|
||||
return allValid().map(Icon::getName).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<Icon> pageByName(NamePage page) {
|
||||
PageResult<Icon> result = new PageResult<>();
|
||||
result.setList(mapper.listByName(page.getName(), page.getOffset(), page.getLimit()));
|
||||
result.setTotal(mapper.countByName(page.getName()));
|
||||
return result;
|
||||
public Map<String, String> mapSvg() {
|
||||
return allValid().collect(Collectors.toMap(Icon::getName, Icon::getSvg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<Icon> pageByUnicode(UnicodePage page) {
|
||||
String unicode = page.getUnicode().toLowerCase();
|
||||
if (unicode.startsWith("0x") || unicode.startsWith("&#") || unicode.startsWith("\\u")) {
|
||||
unicode = unicode.substring(2);
|
||||
}
|
||||
PageResult<Icon> result = new PageResult<>();
|
||||
result.setList(mapper.listByUnicode(unicode, page.getOffset(), page.getLimit()));
|
||||
result.setTotal(mapper.countByUnicode(unicode));
|
||||
return result;
|
||||
public Map<String, String> mapUnicode() {
|
||||
return allValid().collect(Collectors.toMap(Icon::getName, Icon::getUnicode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportTtf() {
|
||||
List<IconGlyph> icons = allValid().map(icon -> IconGlyph.of(icon.getName(), icon.getUnicode(), icon.getSvg())).toList();
|
||||
return IconFontBuilder.build(TTF_OPTIONS, icons);
|
||||
}
|
||||
|
||||
private static IconFontOptions createTtfOptions() {
|
||||
IconFontOptions options = IconFontOptions.defaults("timi-icon")
|
||||
.withSourceBounds(0, 0, 16, 16)
|
||||
.withAlignment(GlyphAlignment.GLYPH_CENTER, GlyphAlignment.GLYPH_CENTER)
|
||||
.withSnapOffsetToGrid(true);
|
||||
options.setGlyphPadding(0);
|
||||
options.setAscent(960);
|
||||
options.setDescent(-64);
|
||||
return options;
|
||||
}
|
||||
|
||||
private Stream<Icon> allValid() {
|
||||
return mapper.selectAll()
|
||||
.stream()
|
||||
.filter(icon -> TimiJava.isNotEmpty(icon.getName()))
|
||||
.filter(icon -> TimiJava.isNotEmpty(icon.getUnicode()))
|
||||
.filter(icon -> TimiJava.isNotEmpty(icon.getSvg()))
|
||||
.sorted(Comparator.comparing(Icon::getName));
|
||||
}
|
||||
}
|
||||
|
||||
+39
-52
@@ -1,14 +1,8 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.Language;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||
import com.imyeyu.api.modules.common.mapper.MultilingualMapper;
|
||||
import com.imyeyu.api.modules.common.service.MultilingualService;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import com.imyeyu.spring.util.Redis;
|
||||
@@ -17,6 +11,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -26,70 +21,62 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MultilingualServiceImplement extends AbstractEntityService<Multilingual, Long> implements MultilingualService {
|
||||
public class MultilingualServiceImplement extends AbstractEntityService<Multilingual, String> implements MultilingualService {
|
||||
|
||||
private final SettingService settingService;
|
||||
private static final String KEY_PREFIX = "MULTILINGUAL:";
|
||||
|
||||
private final MultilingualMapper mapper;
|
||||
|
||||
private final Redis<String, Long> redisLanguageMap;
|
||||
private final Redis<Long, Multilingual> redisLanguage;
|
||||
private final Redis<String, Multilingual> redisLanguage;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Multilingual, Long> mapper() {
|
||||
protected BaseMapper<Multilingual, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long create(String key, String zhCN) {
|
||||
Multilingual multilingual = new Multilingual();
|
||||
multilingual.setKey(key);
|
||||
multilingual.setZhCN(zhCN);
|
||||
mapper.insert(multilingual);
|
||||
public Multilingual get(String id) {
|
||||
Multilingual result = redisLanguage.get(getKey(id));
|
||||
if (result == null) {
|
||||
result = super.get(id);
|
||||
redisLanguage.set(getKey(id), result, Time.D * 7);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createIfDifferent(String id, String zhCN) {
|
||||
Multilingual multilingual = mapper.select(id);
|
||||
if (!multilingual.getZhCN().equals(zhCN)) {
|
||||
return create(zhCN);
|
||||
}
|
||||
return multilingual.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createIfNotExist(String key, String zhCN) {
|
||||
Multilingual existItem = mapper.selectByZhCN(zhCN);
|
||||
if (TimiJava.isNotEmpty(existItem)) {
|
||||
return existItem.getId();
|
||||
}
|
||||
return create(key, zhCN);
|
||||
}
|
||||
public List<Multilingual> listByIdList(List<String> idList) {
|
||||
List<Multilingual> result = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String get(Language.Enum language, Long id) {
|
||||
Multilingual result = redisLanguage.get(id);
|
||||
if (result == null) {
|
||||
result = mapper.select(id);
|
||||
if (result == null) {
|
||||
throw new TimiException(TimiCode.RESULT_NULL).msgKey("TODO not found language");
|
||||
List<String> notInCacheIdList = new ArrayList<>();
|
||||
for (String id : idList) {
|
||||
Multilingual cache = redisLanguage.get(getKey(id));
|
||||
if (cache == null) {
|
||||
notInCacheIdList.add(id);
|
||||
} else {
|
||||
result.add(cache);
|
||||
}
|
||||
redisLanguage.set(id, result, Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL));
|
||||
}
|
||||
return result.getValue(language);
|
||||
|
||||
List<Multilingual> notInCache = mapper.selectByIdList(notInCacheIdList);
|
||||
for (Multilingual multilingual : notInCache) {
|
||||
redisLanguage.set(getKey(multilingual.getId()), multilingual, Time.D * 7);
|
||||
}
|
||||
result.addAll(notInCache);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getByKey(Language.Enum language, String key) {
|
||||
Long languageId = redisLanguageMap.get(key);
|
||||
if (languageId == null) {
|
||||
Multilingual result = mapper.selectByKey(key);
|
||||
if (result == null) {
|
||||
log.warn("not found language for key: {}", key);
|
||||
return key;
|
||||
}
|
||||
long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL);
|
||||
redisLanguage.set(result.getId(), result, ttl);
|
||||
redisLanguageMap.set(result.getKey(), result.getId(), ttl);
|
||||
return result.getValue(language);
|
||||
}
|
||||
return get(language, languageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Multilingual> listByNotTranslate() {
|
||||
return mapper.selectByNotTranslate();
|
||||
private String getKey(String id) {
|
||||
return "%s%s".formatted(KEY_PREFIX, id);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user