Compare commits

...

9 Commits

Author SHA1 Message Date
723992f360 fix Regex update for timi-utils 2025-11-06 14:53:46 +08:00
05e15998f5 update timi-lang 2025-11-06 14:52:13 +08:00
44d55c0ed6 add SettingService.clearCache 2025-11-06 14:51:42 +08:00
06bb86ccd5 fix list comment error 2025-11-06 14:51:29 +08:00
6f1cf2083d fix article page sort 2025-11-06 14:50:44 +08:00
fd71f330d2 add Journal module 2025-11-06 14:50:28 +08:00
cdff62b3b5 update minecraft server/client 2025-11-06 14:48:58 +08:00
2ba868b3b6 support add media thumb attachment 2025-11-06 14:47:48 +08:00
c270ae177d add TempFileService 2025-11-06 14:46:26 +08:00
58 changed files with 1686 additions and 213 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
/data /data
/logs /logs
/target /target
/temp
multilingualField/ multilingualField/
!.mvn/wrapper/maven-wrapper.jar !.mvn/wrapper/maven-wrapper.jar

1
.idea/.gitignore generated vendored
View File

@ -2,3 +2,4 @@
/shelf/ /shelf/
/workspace.xml /workspace.xml
CopilotChatHistory.xml CopilotChatHistory.xml
developer-tools.xml

82
pom.xml
View File

@ -30,6 +30,57 @@
</repository> </repository>
</repositories> </repositories>
<profiles>
<profile>
<id>dev-windows</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<native.classifier>windows-x86_64</native.classifier>
</properties>
</profile>
<profile>
<id>prod-linux</id>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>7.1.1-1.5.12</version>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.11.0-1.5.12</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<classifier>windows-x86_64</classifier>
</exclude>
<exclude>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<classifier>windows-x86_64</classifier>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build> <build>
<defaultGoal>compile</defaultGoal> <defaultGoal>compile</defaultGoal>
<plugins> <plugins>
@ -57,17 +108,17 @@
<dependency> <dependency>
<groupId>com.imyeyu.spring</groupId> <groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId> <artifactId>timi-spring</artifactId>
<version>0.0.1</version> <version>0.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>
<artifactId>timi-network</artifactId> <artifactId>timi-network</artifactId>
<version>0.0.1</version> <version>0.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.lang</groupId> <groupId>com.imyeyu.lang</groupId>
<artifactId>timi-lang</artifactId> <artifactId>timi-lang</artifactId>
<version>0.0.1</version> <version>0.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -167,6 +218,25 @@
<artifactId>jcodec-javase</artifactId> <artifactId>jcodec-javase</artifactId>
<version>0.2.5</version> <version>0.2.5</version>
</dependency> </dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>7.1.1-1.5.12</version>
<scope>provided</scope>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.11.0-1.5.12</version>
<scope>provided</scope>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
@ -177,5 +247,11 @@
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.17.0</version> <version>1.17.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,15 @@
package com.imyeyu.api.bean.wechat;
import lombok.Data;
/**
* @author 夜雨
* @since 2025-09-27 11:33
*/
@Data
public class InitCodeResponse {
private String session_key;
private String openid;
}

View File

@ -7,6 +7,7 @@ import com.imyeyu.api.annotation.RequiredTokenInterceptor;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.vo.user.UserProfileView; import com.imyeyu.api.modules.common.vo.user.UserProfileView;
import com.imyeyu.api.modules.common.vo.user.UserView; import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor; import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor;
import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer;
import com.imyeyu.api.modules.mirror.vo.MirrorView; import com.imyeyu.api.modules.mirror.vo.MirrorView;
@ -41,6 +42,7 @@ public class WebConfig implements WebMvcConfigurer {
private final SystemAPIInterceptor systemAPIInterceptor; private final SystemAPIInterceptor systemAPIInterceptor;
private final GsonSerializerAdapter gsonSerializerAdapter; private final GsonSerializerAdapter gsonSerializerAdapter;
private final JournalAPIInterceptor journalAPIInterceptor;
private final RequiredTokenInterceptor requiredTokenInterceptor; private final RequiredTokenInterceptor requiredTokenInterceptor;
private final EnableSettingInterceptor enableSettingInterceptor; private final EnableSettingInterceptor enableSettingInterceptor;
private final RequestSingleParamResolver requestSingleParamResolver; private final RequestSingleParamResolver requestSingleParamResolver;
@ -55,6 +57,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH); registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH);
registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH);
registry.addInterceptor(requiredFMCServerTokenInterceptor).addPathPatterns("/fmc/server/**"); registry.addInterceptor(requiredFMCServerTokenInterceptor).addPathPatterns("/fmc/server/**");
registry.addInterceptor(requiredTokenInterceptor).addPathPatterns("/**"); registry.addInterceptor(requiredTokenInterceptor).addPathPatterns("/**");
registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**"); registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**");

View File

@ -38,6 +38,7 @@ import java.util.List;
"com.imyeyu.api.modules.mirror.mapper", "com.imyeyu.api.modules.mirror.mapper",
"com.imyeyu.api.modules.system.mapper", "com.imyeyu.api.modules.system.mapper",
"com.imyeyu.api.modules.common.mapper", "com.imyeyu.api.modules.common.mapper",
"com.imyeyu.api.modules.journal.mapper",
"com.imyeyu.api.modules.minecraft.mapper" "com.imyeyu.api.modules.minecraft.mapper"
}, sqlSessionFactoryRef = "timiServerSqlSessionFactory") }, sqlSessionFactoryRef = "timiServerSqlSessionFactory")
public class TimiServerDBConfig { public class TimiServerDBConfig {
@ -93,6 +94,7 @@ public class TimiServerDBConfig {
"com.imyeyu.api.modules.mirror.entity", "com.imyeyu.api.modules.mirror.entity",
"com.imyeyu.api.modules.system.entity", "com.imyeyu.api.modules.system.entity",
"com.imyeyu.api.modules.common.entity", "com.imyeyu.api.modules.common.entity",
"com.imyeyu.api.modules.journal.entity",
"com.imyeyu.api.modules.minecraft.entity" "com.imyeyu.api.modules.minecraft.entity"
}; };
String[] typeHandlers = { String[] typeHandlers = {

View File

@ -14,6 +14,12 @@ import java.util.List;
*/ */
public interface ArticleMapper extends BaseMapper<Article, Long> { public interface ArticleMapper extends BaseMapper<Article, Long> {
@Override
long count();
@Override
List<Article> list(long offset, int limit);
long countByKeyword(String keyword); long countByKeyword(String keyword);
List<Article> selectByKeyword(String keyword, Long offset, int limit); List<Article> selectByKeyword(String keyword, Long offset, int limit);

View File

@ -1,6 +1,5 @@
package com.imyeyu.api.modules.blog.service.implement; package com.imyeyu.api.modules.blog.service.implement;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.blog.entity.Article; import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.blog.entity.ArticleRanking; import com.imyeyu.api.modules.blog.entity.ArticleRanking;
@ -14,7 +13,9 @@ import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.mapper.CommentMapper; import com.imyeyu.api.modules.common.mapper.CommentMapper;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.TagService; import com.imyeyu.api.modules.common.service.TagService;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
@ -54,6 +55,14 @@ public class ArticleServiceImplement extends AbstractEntityService<Article, Long
return mapper; return mapper;
} }
@Override
public PageResult<Article> page(Page page) {
PageResult<Article> result = new PageResult<>();
result.setList(mapper.list(page.getOffset(), page.getLimit()));
result.setTotal(mapper.count());
return result;
}
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public ArticleView view(long id) { public ArticleView view(long id) {

View File

@ -0,0 +1,39 @@
package com.imyeyu.api.modules.common.bean;
import lombok.Data;
/**
* @author 夜雨
* @since 2025-10-20 15:04
*/
public class MediaAttach {
/**
* @author 夜雨
* @since 2025-09-28 02:01
*/
public enum Type {
SOURCE,
THUMB
}
/**
*
*
* @author 夜雨
* @since 2025-10-20 15:04
*/
@Data
public static class ExtData {
private Long sourceId;
private String sourceMongoId;
private boolean isImage;
private boolean isVideo;
}
}

View File

@ -32,6 +32,8 @@ public enum SettingKey {
/** 启用灰色滤镜 */ /** 启用灰色滤镜 */
ENABLE_GRAY_FILTER, ENABLE_GRAY_FILTER,
TEMP_FILE_PATH,
// ---------- ICP 备案号 ---------- // ---------- ICP 备案号 ----------
ICP_IMYEYU_COM, ICP_IMYEYU_COM,
@ -129,6 +131,16 @@ public enum SettingKey {
MUSIC_CONTROLLER_URI, MUSIC_CONTROLLER_URI,
// ---------- ----------
JOURNAL_KEY,
JOURNAL_APP_ID,
JOURNAL_APP_SECRET,
JOURNAL_TRAVEL,
// ---------- 系统 ---------- // ---------- 系统 ----------
SYSTEM_FILE_BASE, SYSTEM_FILE_BASE,

View File

@ -0,0 +1,23 @@
package com.imyeyu.api.modules.common.bean;
import lombok.Data;
import java.nio.file.Path;
/**
* @author 夜雨
* @since 2025-09-27 01:47
*/
@Data
public class TempFileMetaData {
private String id;
private String name;
private String originalName;
private Path path;
private Long lastAccessAt;
}

View File

@ -2,12 +2,6 @@ package com.imyeyu.api.modules.common.controller;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
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.api.bean.CaptchaFrom; import com.imyeyu.api.bean.CaptchaFrom;
import com.imyeyu.api.modules.common.bean.ImageType; import com.imyeyu.api.modules.common.bean.ImageType;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
@ -20,17 +14,26 @@ import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.FeedbackService; import com.imyeyu.api.modules.common.service.FeedbackService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TaskService; 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.TemplateService;
import com.imyeyu.api.modules.common.service.VersionService; import com.imyeyu.api.modules.common.service.VersionService;
import com.imyeyu.api.modules.common.vo.FeedbackRequest; import com.imyeyu.api.modules.common.vo.FeedbackRequest;
import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView; import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.api.modules.system.util.ResourceHandler; import com.imyeyu.api.modules.system.util.ResourceHandler;
import com.imyeyu.api.util.CaptchaManager; 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.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.CaptchaData; import com.imyeyu.spring.bean.CaptchaData;
import com.imyeyu.spring.bean.RequestRange;
import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream; import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSFile;
@ -39,6 +42,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika; import org.apache.tika.Tika;
@ -51,6 +55,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -58,8 +63,12 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -83,6 +92,7 @@ public class CommonController {
private final SettingService settingService; private final SettingService settingService;
private final FeedbackService feedbackService; private final FeedbackService feedbackService;
private final TemplateService templateService; private final TemplateService templateService;
private final TempFileService tempFileService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final Gson gson; private final Gson gson;
@ -171,7 +181,7 @@ public class CommonController {
@RequestRateLimit @RequestRateLimit
@PostMapping("/feedback") @PostMapping("/feedback")
public void createFeedback(@Valid @NotNull @RequestBody CaptchaData<FeedbackRequest> request) { public void createFeedback(@Valid @NotNull @RequestBody CaptchaData<FeedbackRequest> request) {
captchaManager.test(request.getCaptcha(), request.getFrom()); captchaManager.test(request.getCaptcha(), request.getCaptchaId());
feedbackService.create(request.getData()); feedbackService.create(request.getData());
} }
@ -250,12 +260,6 @@ public class CommonController {
return result.stream().collect(Collectors.toMap(Setting::getKey, Setting::getValue)); return result.stream().collect(Collectors.toMap(Setting::getKey, Setting::getValue));
} }
@RequestRateLimit
@GetMapping("/setting/flushCache")
public void settingFlushCache() {
settingService.flushCache();
}
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@GetMapping("/attachment/{mongoId}") @GetMapping("/attachment/{mongoId}")
@ -264,7 +268,6 @@ public class CommonController {
} }
@AOPLog @AOPLog
@RequestRateLimit
@IgnoreGlobalReturn @IgnoreGlobalReturn
@GetMapping("/attachment/read/{mongoId}") @GetMapping("/attachment/read/{mongoId}")
public void readAttachment( public void readAttachment(
@ -372,4 +375,72 @@ public class CommonController {
resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
} }
} }
@AOPLog
@PostMapping("/temp/file/upload")
public List<TempFileResponse> uploadFile(@RequestParam("file") List<MultipartFile> files) {
return tempFileService.store(files);
}
@AOPLog
@RequestRateLimit
@IgnoreGlobalReturn
@GetMapping("/temp/file/read/{fileId}")
public void tempFileRead(@PathVariable String fileId, HttpServletRequest req, HttpServletResponse resp) {
try {
File file = tempFileService.get(fileId);
if (TimiJava.isEmpty(file) && file.exists()) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Path filePath = file.toPath();
resp.setContentLengthLong(Files.size(filePath));
String mimeType = new Tika().detect(filePath);
if (TimiJava.isNotEmpty(mimeType)) {
resp.setContentType(mimeType);
}
req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.FILE);
req.setAttribute(ResourceHandler.ATTR_VALUE, filePath);
resourceHandler.handleRequest(req, resp);
} catch (Exception e) {
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@AOPLog
@RequestRateLimit
@IgnoreGlobalReturn
@RequestMapping("/temp/file/download/{fileId}")
public void tempFileDownload(@PathVariable String fileId, HttpServletResponse resp) {
try {
File file = tempFileService.get(fileId);
if (TimiJava.isEmpty(file) && file.exists()) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
String mimeType = new Tika().detect(file);
resp.setContentType(mimeType);
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getName()));
resp.setHeader("Accept-Ranges", "bytes");
RequestRange range = TimiSpring.requestRange(file.length());
if (range == null) {
// 完整文件
resp.setContentLengthLong(file.length());
resp.setStatus(HttpServletResponse.SC_OK);
IO.toOutputStream(resp.getOutputStream(), file);
} else {
// 分片文件
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), file.length()));
resp.setContentLengthLong(range.getLength());
@Cleanup RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(range.getStart());
IO.toOutputStream(resp.getOutputStream(), raf, range.getStart(), range.getLength());
}
} catch (Exception e) {
log.error("download error", e);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
} }

View File

@ -1,7 +1,7 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.api.bean.MultilingualHandler; import com.imyeyu.api.bean.MultilingualHandler;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.spring.entity.Entity; import com.imyeyu.spring.entity.Entity;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -44,24 +44,36 @@ public class Attachment extends Entity implements MultilingualHandler {
/** 镜像 */ /** 镜像 */
MIRROR, MIRROR,
JOURNAL,
JOURNAL_TRAVEL,
JOURNAL_MOMENT,
/** 系统 */ /** 系统 */
SYSTEM SYSTEM
} }
private BizType bizType; protected BizType bizType;
private Long bizId; protected Long bizId;
private String attachType; protected String attachType;
private String mongoId; protected String mongoId;
@MultilingualField @MultilingualField
private String title; protected String title;
private String name; protected String name;
private Long size; protected Long size;
protected String md5;
protected String ext;
protected Long destroyAt;
public void setAttachTypeValue(Enum<?> attachType) { public void setAttachTypeValue(Enum<?> attachType) {
this.attachType = attachType.toString(); this.attachType = attachType.toString();

View File

@ -1,6 +1,7 @@
package com.imyeyu.api.modules.common.mapper; package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -24,8 +25,9 @@ public interface AttachmentMapper extends BaseMapper<Attachment, Long> {
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1) @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, long bizId, Enum<?> attachType);
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND " + VALID + PAGE) List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, Page page);
List<Attachment> listByBizId(Attachment.BizType bizType, long bizId, long offset, int limit);
List<Attachment> listByAttachType(Attachment.BizType bizType, long bizId, Enum<?> ...attachTypes); long countByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes);
List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, List<String> md5s);
} }

View File

@ -17,6 +17,12 @@ import java.util.List;
*/ */
public interface CommentMapper extends BaseMapper<Comment, Long> { public interface CommentMapper extends BaseMapper<Comment, Long> {
@Override
long count();
@Override
List<Comment> list(long offset, int limit);
@Select("SELECT * FROM comment WHERE id = #{id}" + NOT_DELETE) @Select("SELECT * FROM comment WHERE id = #{id}" + NOT_DELETE)
Comment select(Long id); Comment select(Long id);

View File

@ -1,12 +1,16 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest; import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView; import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.DeletableService; import com.imyeyu.spring.service.DeletableService;
import com.imyeyu.spring.service.DestroyableService; import com.imyeyu.spring.service.DestroyableService;
import com.imyeyu.spring.service.GettableService; import com.imyeyu.spring.service.GettableService;
import com.imyeyu.spring.service.PageableService;
import com.imyeyu.spring.service.UpdatableService;
import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSFile;
import java.io.InputStream; import java.io.InputStream;
@ -20,7 +24,7 @@ import java.util.List;
* @author 夜雨 * @author 夜雨
* @since 2023-08-15 10:21 * @since 2023-08-15 10:21
*/ */
public interface AttachmentService extends GettableService<Attachment, Long>, DeletableService<Long>, DestroyableService<Long> { public interface AttachmentService extends GettableService<Attachment, Long>, PageableService<Attachment>, UpdatableService<Attachment>, DeletableService<Long>, DestroyableService<Long> {
/** /**
* *
@ -29,6 +33,16 @@ public interface AttachmentService extends GettableService<Attachment, Long>, De
*/ */
void create(AttachmentRequest request); void create(AttachmentRequest request);
/**
* 创建媒体附件,同步创建缩略图
*
* @param request 附件请求
* @return 缩略图附件
*/
Attachment createMedia(AttachmentRequest request) throws TimiException;
void deleteMedia(Long thumbId) throws TimiException;
Attachment getByBizId(Attachment.BizType bizType, long bizId); Attachment getByBizId(Attachment.BizType bizType, long bizId);
Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum<?> attachType); Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum<?> attachType);
@ -46,11 +60,26 @@ public interface AttachmentService extends GettableService<Attachment, Long>, De
/** /**
* 根据业务获取所有附件 * 根据业务获取所有附件
* *
* @param bizType 业务类型 * @param bizType 业务类型
* @param bizId 业务 ID * @param bizId 业务 ID,可为 null
* @param attachTypes * @param attachTypes 附件类型,可为 null
* @return 所有附件 * @return 所有附件
* @throws TimiException 服务异常 * @throws TimiException 服务异常
*/ */
List<Attachment> listByBizId(Attachment.BizType bizType, long bizId, Enum<?> ...attachTypes); List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, Enum<?> ...attachTypes);
/**
* 根据业务获取所有附件
*
* @param bizType 业务类型
* @param bizId 业务 ID可为 null
* @param attachTypes 附件类型,可为 null
* @return 附件数量
* @throws TimiException 服务异常
*/
long countByBizId(Attachment.BizType bizType, Long bizId, Enum<?> ...attachTypes);
List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, List<String> md5s) throws TimiException;
PageResult<Attachment> pageByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, Page page) throws TimiException;
} }

View File

@ -74,5 +74,7 @@ public interface SettingService extends UpdatableService<Setting> {
List<Setting> listAll(); List<Setting> listAll();
void clearCache(SettingKey key);
void flushCache(); void flushCache();
} }

View File

@ -0,0 +1,22 @@
package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.java.bean.timi.TimiException;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-27 01:35
*/
public interface TempFileService {
List<TempFileResponse> store(List<MultipartFile> files) throws TimiException;
File get(String id) throws TimiException;
TempFileMetaData metadata(String id) throws TimiException;
}

View File

@ -1,15 +1,22 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.google.gson.Gson;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.MediaAttach;
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.attachment.AttachmentRequest;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.api.util.JavaCV;
import com.imyeyu.io.IO; import com.imyeyu.io.IO;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.network.Network;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.spring.bean.Page;
import com.imyeyu.api.modules.common.mapper.AttachmentMapper; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
@ -17,6 +24,8 @@ import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSFile;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.tika.Tika;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -24,9 +33,14 @@ import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID;
/** /**
* @author 夜雨 * @author 夜雨
@ -37,8 +51,11 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class AttachmentServiceImplement extends AbstractEntityService<Attachment, Long> implements AttachmentService { public class AttachmentServiceImplement extends AbstractEntityService<Attachment, Long> implements AttachmentService {
private final SettingService settingService;
private final AttachmentMapper mapper; private final AttachmentMapper mapper;
private final Gson gson;
private final GridFSBucket gridFSBucket; private final GridFSBucket gridFSBucket;
private final GridFsTemplate gridFsTemplate; private final GridFsTemplate gridFsTemplate;
@ -52,11 +69,11 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
public void destroy(Long id) { public void destroy(Long id) {
try { try {
Attachment attachment = get(id); Attachment attachment = get(id);
if (!attachment.isDeleted()) {
delete(id);
}
mapper.destroy(attachment.getId());
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attachment.getMongoId()))); gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attachment.getMongoId())));
attachment.setDeletedAt(Time.now());
attachment.setDestroyAt(Time.now());
mapper.update(attachment);
} catch (Exception e) { } catch (Exception e) {
log.error("delete mongo file error", e); log.error("delete mongo file error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error"); throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error");
@ -81,12 +98,12 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
} }
mongoName.append(request.getName()); mongoName.append(request.getName());
mongoId = gridFsTemplate.store(is, mongoName.toString()).toString(); mongoId = gridFsTemplate.store(request.getInputStream(), mongoName.toString()).toString();
Attachment attachment = new Attachment(); GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(mongoId)));
BeanUtils.copyProperties(request, attachment); request.setMongoId(mongoId);
attachment.setMongoId(mongoId); request.setSize(gridFSFile.getLength());
attachment.setCreatedAt(Time.now()); request.setMd5(IO.md5(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())));
mapper.insert(attachment); mapper.insert(request);
} catch (Exception e) { } catch (Exception e) {
if (mongoId != null) { if (mongoId != null) {
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(mongoId))); gridFsTemplate.delete(Query.query(Criteria.where("_id").is(mongoId)));
@ -96,6 +113,74 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
} }
} }
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public Attachment createMedia(AttachmentRequest request) throws TimiException {
TimiException.required(request.getName(), "not found name");
TimiException.required(request.getBizType(), "not found bizType");
TimiException.required(request.getInputStream(), "not found inputStream");
try {
// 储存源文件
request.setAttachTypeValue(MediaAttach.Type.SOURCE);
create(request);
// 生成缩略图
InputStream mimeStream = getInputStreamByMongoId(request.getMongoId());
String mimeType = new Tika().detect(mimeStream);
boolean isImage = false;
InputStream sourceStream = getInputStreamByMongoId(request.getMongoId());
ByteArrayOutputStream thumbStream = new ByteArrayOutputStream();
switch (mimeType) {
case "image/png", "image/bmp", "image/jpeg" -> {
isImage = true;
Thumbnails.of(sourceStream).width(256).keepAspectRatio(true).toOutputStream(thumbStream);
}
case "video/mp4", "video/quicktime" -> {
log.info("capturing thumbnail: {}", request.getName());
long start = Time.now();
File tempFile = IO.file("temp/%s_%s".formatted(UUID.randomUUID().toString(), request.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", request.getName(), Time.now() - start);
} finally {
IO.destroy(tempFile);
}
}
}
MediaAttach.ExtData extData = new MediaAttach.ExtData();
extData.setImage(isImage);
extData.setVideo(!isImage);
extData.setSourceId(request.getId());
extData.setSourceMongoId(request.getMongoId());
AttachmentRequest thumbAttach = new AttachmentRequest();
thumbAttach.setName(Network.simpleURIFileName(request.getName()) + ".png");
thumbAttach.setBizType(request.getBizType());
thumbAttach.setBizId(request.getBizId());
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
thumbAttach.setExt(gson.toJson(extData));
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
create(thumbAttach);
return get(thumbAttach.getId());
} catch (Exception e) {
log.error("create media attachment error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO create media attachment error");
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void deleteMedia(Long thumbId) throws TimiException {
Attachment attachment = get(thumbId);
delete(attachment.getId());
MediaAttach.ExtData data = gson.fromJson(attachment.getExt(), MediaAttach.ExtData.class);
delete(data.getSourceId());
}
@Override @Override
public Attachment getByBizId(Attachment.BizType bizType, long bizId) { public Attachment getByBizId(Attachment.BizType bizType, long bizId) {
return mapper.selectByBizId(bizType, bizId); return mapper.selectByBizId(bizType, bizId);
@ -144,7 +229,25 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
} }
@Override @Override
public List<Attachment> listByBizId(Attachment.BizType bizType, long bizId, Enum<?>... attachTypes) { public List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, Enum<?>... attachTypes) {
return mapper.listByAttachType(bizType, bizId, attachTypes); return mapper.listByBizId(bizType, bizId, Arrays.asList(attachTypes), null);
}
@Override
public long countByBizId(Attachment.BizType bizType, Long bizId, Enum<?>... attachTypes) {
return mapper.countByBizId(bizType, bizId, Arrays.asList(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 PageResult<Attachment> pageByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, Page page) throws TimiException {
PageResult<Attachment> result = new PageResult<>();
result.setList(mapper.listByBizId(bizType, bizId, attachTypeList, page));
result.setTotal(mapper.countByBizId(bizType, bizId, attachTypeList));
return result;
} }
} }

View File

@ -6,13 +6,13 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.imyeyu.java.TimiJava;
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.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.mapper.SettingMapper; import com.imyeyu.api.modules.common.mapper.SettingMapper;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
@ -144,6 +144,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
return mapper.listAll(); return mapper.listAll();
} }
@Override
public void clearCache(SettingKey key) {
redisSetting.destroy(key.toString());
}
@Override @Override
public void flushCache() { public void flushCache() {
redisSetting.flushAll(); redisSetting.flushAll();

View File

@ -0,0 +1,132 @@
package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TempFileService;
import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.io.IO;
import com.imyeyu.io.IOSize;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.utils.Time;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 夜雨
* @since 2025-09-27 01:47
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TempFileServiceImplement implements TempFileService {
private static final Long TTL = Time.H * 6;
private static final Long LIMIT = IOSize.GB * 10;
private final SettingService settingService;
private final AttachmentService attachmentService;
private Path storagePath;
private final Map<String, TempFileMetaData> metadataMap = new ConcurrentHashMap<>();
private final Map<String, Long> usageMap = new ConcurrentHashMap<>();
@PostConstruct
public void init() throws IOException {
storagePath = Paths.get(settingService.getAsString(SettingKey.TEMP_FILE_PATH));
IO.destroy(storagePath.toFile());
Files.createDirectories(storagePath);
}
public List<TempFileResponse> store(List<MultipartFile> files) throws TimiException {
long newFileSize = files.stream().mapToLong(MultipartFile::getSize).sum();
long currentUsage = usageMap.getOrDefault(TimiSpring.getRequestIP(), 0L);
TimiException.requiredTrue(currentUsage + newFileSize < LIMIT, "out of storage limit(10 GB)");
try {
List<TempFileResponse> result = new ArrayList<>();
for (int i = 0; i < files.size(); i++) {
MultipartFile file = files.get(i);
String fileId = UUID.randomUUID().toString();
String fileName = fileId;
if (TimiJava.isNotEmpty(file.getOriginalFilename())) {
fileName = fileId + "." + Network.uriFileExtension(file.getOriginalFilename());
}
Path filePath = storagePath.resolve(fileName);
Files.copy(file.getInputStream(), filePath);
// 创建元数据
TempFileMetaData metadata = new TempFileMetaData();
metadata.setId(fileId);
metadata.setPath(filePath);
metadata.setName(fileName);
metadata.setOriginalName(file.getOriginalFilename());
metadata.setLastAccessAt(Time.now());
metadataMap.put(metadata.getId(), metadata);
TempFileResponse resp = new TempFileResponse();
resp.setId(metadata.getId());
resp.setExpireAt(metadata.getLastAccessAt() + TTL);
usageMap.put(TimiSpring.getRequestIP(), currentUsage + newFileSize);
result.add(resp);
}
return result;
} catch (Exception e) {
log.error("store temp file error", e);
throw new TimiException(TimiCode.ERROR, "store temp file error", e);
}
}
@Override
public File get(String id) throws TimiException {
TempFileMetaData metaData = metadataMap.get(id);
TimiException.required(metaData, "not found temp file");
metaData.setLastAccessAt(Time.now());
return metaData.getPath().toFile();
}
@Override
public TempFileMetaData metadata(String id) throws TimiException {
return metadataMap.get(id);
}
@Scheduled(fixedRate = 3600000)
public void cleanup() {
List<String> expiredIds = metadataMap.values()
.stream()
.filter(metadata -> metadata.getLastAccessAt() + TTL < Time.now())
.map(TempFileMetaData::getId)
.toList();
for (int i = 0; i < expiredIds.size(); i++) {
TempFileMetaData removed = metadataMap.remove(expiredIds.get(i));
if (TimiJava.isNotEmpty(removed)) {
File file = removed.getPath().toFile();
IO.destroy(file);
usageMap.computeIfPresent(TimiSpring.getRequestIP(), (ip, usage) -> usage - file.length());
}
}
}
}

View File

@ -3,17 +3,17 @@ package com.imyeyu.api.modules.common.task;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
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.java.ref.Ref;
import com.imyeyu.network.FormMap;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
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.java.ref.Ref;
import com.imyeyu.network.ArgMap;
import com.imyeyu.utils.Digest; import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@ -134,7 +134,7 @@ public class MultilingualTranslateTask {
String appId = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_APP_ID); String appId = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_APP_ID);
String key = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_KEY); String key = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_KEY);
FormMap<String, Object> args = new FormMap<>(); ArgMap<String, Object> args = new ArgMap<>();
args.put("q", text); args.put("q", text);
args.put("from", BaiduLanguage.ZH.toString().toLowerCase()); args.put("from", BaiduLanguage.ZH.toString().toLowerCase());
args.put("to", to.toString().toLowerCase()); args.put("to", to.toString().toLowerCase());
@ -143,7 +143,7 @@ public class MultilingualTranslateTask {
args.put("sign", Digest.md5(appId + text + random + key)); args.put("sign", Digest.md5(appId + text + random + key));
String response = Request.post(settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_API)) String response = Request.post(settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_API))
.bodyForm(args.build()) .bodyForm(args.toNameValuePair())
.execute() .execute()
.returnContent() .returnContent()
.asString(); .asString();

View File

@ -1,9 +1,9 @@
package com.imyeyu.api.modules.common.validation.validtor; package com.imyeyu.api.modules.common.validation.validtor;
import com.imyeyu.java.TimiJava;
import com.imyeyu.api.modules.common.validation.UserName; import com.imyeyu.api.modules.common.validation.UserName;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.util.AbstractValidator; import com.imyeyu.spring.util.AbstractValidator;
import com.imyeyu.utils.Text; import com.imyeyu.utils.Regex;
/** /**
* 用户名基本验证 * 用户名基本验证
@ -24,10 +24,10 @@ public class UserNameValidator extends AbstractValidator<UserName, String> {
if (userName.contains("@")) { if (userName.contains("@")) {
return "user.name.contains_at"; return "user.name.contains_at";
} }
if (Text.testReg("^[0-9]+.?[0-9]*$", userName)) { if (Regex.isMatch("^[0-9]+.?[0-9]*$", userName)) {
return "user.name.only_number"; return "user.name.only_number";
} }
if (!Text.testReg("^[A-Za-z0-9_一-龥]+$", userName)) { if (Regex.isNotMatch("^[A-Za-z0-9_一-龥]+$", userName)) {
return "user.name.not_match_regex"; return "user.name.not_match_regex";
} }
return null; return null;

View File

@ -1,9 +1,9 @@
package com.imyeyu.api.modules.common.validation.validtor; package com.imyeyu.api.modules.common.validation.validtor;
import com.imyeyu.java.TimiJava;
import com.imyeyu.api.modules.common.validation.UserPassword; import com.imyeyu.api.modules.common.validation.UserPassword;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.util.AbstractValidator; import com.imyeyu.spring.util.AbstractValidator;
import com.imyeyu.utils.Text; import com.imyeyu.utils.Regex;
/** /**
* 用户密码基本验证 * 用户密码基本验证
@ -24,7 +24,7 @@ public class UserPasswordValidator extends AbstractValidator<UserPassword, Strin
if (20 < password.length()) { if (20 < password.length()) {
return "user.password.too_long"; return "user.password.too_long";
} }
if (!Text.testReg("(?=.*([a-zA-Z].*))(?=.*[0-9].*)[a-zA-Z0-9-*/+.~!@#$%^&*()]{6,20}$", password)) { if (Regex.isNotMatch("(?=.*([a-zA-Z].*))(?=.*[0-9].*)[a-zA-Z0-9-*/+.~!@#$%^&*()]{6,20}$", password)) {
return "user.password.not_match_regex"; return "user.password.not_match_regex";
} }
return null; return null;

View File

@ -0,0 +1,15 @@
package com.imyeyu.api.modules.common.vo;
import lombok.Data;
/**
* @author 夜雨
* @since 2025-09-27 01:37
*/
@Data
public class TempFileResponse {
private String id;
private Long expireAt;
}

View File

@ -1,7 +1,9 @@
package com.imyeyu.api.modules.forevermc.bean; package com.imyeyu.api.modules.forevermc.bean;
import com.imyeyu.api.modules.forevermc.entity.Server; import com.imyeyu.api.modules.forevermc.entity.Server;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -17,6 +19,8 @@ import java.util.List;
@Data @Data
public class ServerStatus implements Cloneable { public class ServerStatus implements Cloneable {
@Valid
@NotBlank
private String id; private String id;
/** true 为维护中 */ /** true 为维护中 */
@ -60,7 +64,7 @@ public class ServerStatus implements Cloneable {
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public static class BaseInfo extends Server implements Cloneable { public static class BaseInfo extends Server {
/** 核心 */ /** 核心 */
private String core; private String core;
@ -76,14 +80,6 @@ public class ServerStatus implements Cloneable {
/** 游戏版本 */ /** 游戏版本 */
private String version; private String version;
// @Override
// protected BaseInfo clone() throws CloneNotSupportedException {
// BaseInfo clone = (BaseInfo) super.clone();
// clone.core = core;
// clone.bootAt = bootAt;
// }
} }
/** /**

View File

@ -0,0 +1,42 @@
package com.imyeyu.api.modules.forevermc.controller;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc;
import com.imyeyu.api.modules.forevermc.service.ClientPackService;
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 lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author 夜雨
* @since 2025-07-24 21:43
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/fmc/client")
public class ClientPackController {
private final ClientPackService service;
@AOPLog
@RequestRateLimit
@RequestMapping("/list")
public PageResult<ClientPack> listClientPack(@RequestBody Page page) {
return service.page(page);
}
@AOPLog
@RequestRateLimit
@RequestMapping("/src/{clientId}")
public List<ClientPackSrc> listClientPackSrc(@PathVariable("clientId") long clientId) {
return service.listClientPackSrc(clientId);
}
}

View File

@ -1,15 +1,19 @@
package com.imyeyu.api.modules.forevermc.controller; package com.imyeyu.api.modules.forevermc.controller;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.forevermc.bean.ServerStatus; import com.imyeyu.api.modules.forevermc.bean.ServerStatus;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.forevermc.service.ClientPackService;
import com.imyeyu.api.modules.forevermc.service.ServerService; import com.imyeyu.api.modules.forevermc.service.ServerService;
import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerToken; import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerToken;
import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest; import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.utils.Decoder; import com.imyeyu.utils.Decoder;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -44,6 +48,7 @@ import java.util.List;
public class ServerController { public class ServerController {
private final ServerService service; private final ServerService service;
private final ClientPackService clientService;
/** /**
* 服务器状态报告,由 FMCServer 服务端插件发送 * 服务器状态报告,由 FMCServer 服务端插件发送
@ -88,6 +93,13 @@ public class ServerController {
} }
} }
@AOPLog
@RequestRateLimit
@GetMapping("/client/{serverId}")
public List<ClientPack> listClientPack(@PathVariable("serverId") String serverId) {
return service.listClientPack(serverId);
}
/** /**
* 获取服务器状态列表 * 获取服务器状态列表
* *
@ -112,4 +124,10 @@ public class ServerController {
result.sort(Comparator.comparingInt(o -> o.getBaseInfo().getOrder())); result.sort(Comparator.comparingInt(o -> o.getBaseInfo().getOrder()));
return result; return result;
} }
@RequestRateLimit
@PostMapping("/client/list")
public PageResult<ClientPack> listClient(@RequestBody Page page) {
return clientService.page(page);
}
} }

View File

@ -0,0 +1,45 @@
package com.imyeyu.api.modules.forevermc.entity;
import com.imyeyu.spring.annotation.table.Column;
import com.imyeyu.spring.entity.Entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 服务器客户端
*
* @author 夜雨
* @since 2025-01-27 20:40
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ClientPack extends Entity {
/** 名称 */
private String name;
/** 说明 */
private String description;
/** 游戏 ID */
private String gameId;
/** 游戏版本 */
private String gameVersion;
/** 客户端版本 */
private String version;
/** 需要运行时版本 */
private String runtimeVersion;
/** 文件大小 */
private Long size;
/** true 为支持 FMC 授权验证 */
@Column("can_fmc_auth")
private Boolean canFMCAuth;
/** true 为已过时 */
private boolean isDeprecated;
}

View File

@ -1,8 +1,8 @@
package com.imyeyu.api.modules.forevermc.entity; package com.imyeyu.api.modules.forevermc.entity;
import com.imyeyu.spring.entity.Entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import com.imyeyu.spring.entity.Entity;
/** /**
* 服务器客户端下载源 * 服务器客户端下载源
@ -12,7 +12,7 @@ import com.imyeyu.spring.entity.Entity;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ServerClientSrc extends Entity { public class ClientPackSrc extends Entity {
/** /**
* 下载源类型 * 下载源类型
@ -32,12 +32,9 @@ public class ServerClientSrc extends Entity {
URL URL
} }
/** 客户端 ID关联 {@link ServerClient#getId()} */ /** 客户端 ID关联 {@link ClientPack#getId()} */
private Long clientId; private Long clientId;
/** 服务器 ID关联 {@link Server#getId()} */
private String serverId;
/** 下载源类型 */ /** 下载源类型 */
private BizType bizType; private BizType bizType;

View File

@ -1,13 +1,10 @@
package com.imyeyu.api.modules.forevermc.entity; package com.imyeyu.api.modules.forevermc.entity;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Destroyable; import com.imyeyu.spring.entity.Destroyable;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.List;
/** /**
* 服务器 * 服务器
* *
@ -36,6 +33,9 @@ public class Server extends UUIDEntity implements Destroyable {
/** 上级服务器 ID */ /** 上级服务器 ID */
protected String pid; protected String pid;
/** 类型 */
protected Type type;
/** 标题 */ /** 标题 */
protected String title; protected String title;
@ -45,13 +45,6 @@ public class Server extends UUIDEntity implements Destroyable {
/** 地址 */ /** 地址 */
protected String host; protected String host;
/** 类型 */
protected Type type;
/** 排序 */ /** 排序 */
protected int order; protected int order;
/** 客户端列表 */
@Transient
protected List<ServerClient> clientList;
} }

View File

@ -1,40 +1,15 @@
package com.imyeyu.api.modules.forevermc.entity; package com.imyeyu.api.modules.forevermc.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Entity;
import java.util.List;
/** /**
* 服务器客户端
*
* @author 夜雨 * @author 夜雨
* @since 2025-01-27 20:40 * @since 2025-07-23 21:54
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) public class ServerClient {
public class ServerClient extends Entity {
/** 服务器 ID关联 {@link Server#getId()} */
private String serverId; private String serverId;
/** 文件名 */ private Long clientId;
private String fileName;
/** 客户端版本 */
private String version;
/** 默认配置 */
private String defOption;
/** 文件大小 */
private Long size;
/** true 为已过时 */
private boolean isDeprecated;
@Transient
private List<ServerClientSrc> srcList;
} }

View File

@ -0,0 +1,12 @@
package com.imyeyu.api.modules.forevermc.mapper;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.spring.mapper.BaseMapper;
/**
* @author 夜雨
* @since 2025-02-07 17:03
*/
public interface ClientPackMapper extends BaseMapper<ClientPack, Long> {
}

View File

@ -0,0 +1,17 @@
package com.imyeyu.api.modules.forevermc.mapper;
import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author 夜雨
* @since 2025-02-07 17:20
*/
public interface ClientPackSrcMapper extends BaseMapper<ClientPackSrc, Long> {
@Select("SELECT * FROM client_pack_src WHERE client_id = #{clientId}" + NOT_DELETE + " ORDER BY `order` ASC")
List<ClientPackSrc> selectByClientId(long clientId);
}

View File

@ -1,17 +1,14 @@
package com.imyeyu.api.modules.forevermc.mapper; package com.imyeyu.api.modules.forevermc.mapper;
import com.imyeyu.api.modules.forevermc.entity.ServerClient; import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List; import java.util.List;
/** /**
* @author 夜雨 * @author 夜雨
* @since 2025-02-07 17:03 * @since 2025-07-23 22:03
*/ */
public interface ServerClientMapper extends BaseMapper<ServerClient, Long> { public interface ServerClientMapper {
@Select("SELECT * FROM server_client WHERE server_id = #{serverId}" + NOT_DELETE) List<ClientPack> selectByServerId(String serverId);
List<ServerClient> selectByServerId(String serverId);
} }

View File

@ -1,17 +0,0 @@
package com.imyeyu.api.modules.forevermc.mapper;
import com.imyeyu.api.modules.forevermc.entity.ServerClientSrc;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author 夜雨
* @since 2025-02-07 17:20
*/
public interface ServerClientSrcMapper extends BaseMapper<ServerClientSrc, Long> {
@Select("SELECT * FROM server_client_src WHERE client_id = #{clientId}" + NOT_DELETE + " ORDER BY order ASC")
List<ServerClientSrc> selectByClientId(Long clientId);
}

View File

@ -0,0 +1,19 @@
package com.imyeyu.api.modules.forevermc.service;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc;
import com.imyeyu.spring.service.DeletableService;
import com.imyeyu.spring.service.GettableService;
import com.imyeyu.spring.service.PageableService;
import com.imyeyu.spring.service.UpdatableService;
import java.util.List;
/**
* @author 夜雨
* @since 2025-07-23 16:59
*/
public interface ClientPackService extends PageableService<ClientPack>, GettableService<ClientPack, Long>, UpdatableService<ClientPack>, DeletableService<Long> {
List<ClientPackSrc> listClientPackSrc(long clientId);
}

View File

@ -1,9 +1,10 @@
package com.imyeyu.api.modules.forevermc.service; package com.imyeyu.api.modules.forevermc.service;
import com.imyeyu.api.modules.forevermc.bean.ServerStatus;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.forevermc.bean.ServerStatus;
import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest;
import java.util.List; import java.util.List;
@ -23,6 +24,8 @@ public interface ServerService extends TimiJava {
*/ */
void report(ReportRequest request); void report(ReportRequest request);
List<ClientPack> listClientPack(String serverId);
String getIcon(String serverId); String getIcon(String serverId);
/** /**

View File

@ -0,0 +1,35 @@
package com.imyeyu.api.modules.forevermc.service.implement;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc;
import com.imyeyu.api.modules.forevermc.mapper.ClientPackMapper;
import com.imyeyu.api.modules.forevermc.mapper.ClientPackSrcMapper;
import com.imyeyu.api.modules.forevermc.service.ClientPackService;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 夜雨
* @since 2025-07-23 16:59
*/
@Service
@RequiredArgsConstructor
public class ClientPackServiceImplement extends AbstractEntityService<ClientPack, Long> implements ClientPackService {
private final ClientPackMapper mapper;
private final ClientPackSrcMapper srcMapper;
@Override
protected BaseMapper<ClientPack, Long> mapper() {
return mapper;
}
@Override
public List<ClientPackSrc> listClientPackSrc(long clientId) {
return srcMapper.selectByClientId(clientId);
}
}

View File

@ -1,16 +1,15 @@
package com.imyeyu.api.modules.forevermc.service.implement; package com.imyeyu.api.modules.forevermc.service.implement;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.forevermc.bean.ServerStatus; import com.imyeyu.api.modules.forevermc.bean.ServerStatus;
import com.imyeyu.api.modules.forevermc.entity.ClientPack;
import com.imyeyu.api.modules.forevermc.entity.Server; import com.imyeyu.api.modules.forevermc.entity.Server;
import com.imyeyu.api.modules.forevermc.entity.ServerClient;
import com.imyeyu.api.modules.forevermc.mapper.ServerClientMapper; import com.imyeyu.api.modules.forevermc.mapper.ServerClientMapper;
import com.imyeyu.api.modules.forevermc.mapper.ServerClientSrcMapper;
import com.imyeyu.api.modules.forevermc.mapper.ServerMapper; import com.imyeyu.api.modules.forevermc.mapper.ServerMapper;
import com.imyeyu.api.modules.forevermc.service.ServerService; import com.imyeyu.api.modules.forevermc.service.ServerService;
import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest; import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@ -36,7 +35,6 @@ public class ServerServiceImplement implements ServerService {
private final ServerMapper serverMapper; private final ServerMapper serverMapper;
private final ServerClientMapper serverClientMapper; private final ServerClientMapper serverClientMapper;
private final ServerClientSrcMapper serverClientSrcMapper;
private final Map<String, ServerStatus> serverMap = new HashMap<>(); private final Map<String, ServerStatus> serverMap = new HashMap<>();
@ -50,13 +48,6 @@ public class ServerServiceImplement implements ServerService {
} }
Server server = serverMapper.select(item.getKey()); Server server = serverMapper.select(item.getKey());
TimiException.required(server, "not found server"); TimiException.required(server, "not found server");
List<ServerClient> clientList = serverClientMapper.selectByServerId(server.getId());
for (int i = 0; i < clientList.size(); i++) {
ServerClient client = clientList.get(i);
client.setSrcList(serverClientSrcMapper.selectByClientId(client.getId()));
}
server.setClientList(clientList);
BeanUtils.copyProperties(server, item.getValue().getBaseInfo()); BeanUtils.copyProperties(server, item.getValue().getBaseInfo());
} }
} }
@ -74,6 +65,11 @@ public class ServerServiceImplement implements ServerService {
serverMap.put(request.getId(), request); serverMap.put(request.getId(), request);
} }
@Override
public List<ClientPack> listClientPack(String serverId) {
return serverClientMapper.selectByServerId(serverId);
}
@Override @Override
public String getIcon(String serverId) { public String getIcon(String serverId) {
ServerStatus status = serverMap.get(serverId); ServerStatus status = serverMap.get(serverId);

View File

@ -0,0 +1,59 @@
package com.imyeyu.api.modules.journal.bean;
import lombok.Data;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-30 11:51
*/
@Data
public class Travel {
private Luggage luggage;
private List<Guide> guides;
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:52
*/
@Data
public static class Luggage {
private List<Item> gao;
private List<Item> yu;
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:52
*/
@Data
public static class Item {
private String name;
private boolean isTaken;
}
}
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:54
*/
@Data
public static class Guide {
private String title;
private List<String> images;
}
}

View File

@ -0,0 +1,167 @@
package com.imyeyu.api.modules.journal.controller;
import com.google.gson.Gson;
import com.imyeyu.api.bean.wechat.InitCodeResponse;
import com.imyeyu.api.modules.common.bean.MediaAttach;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.journal.bean.Travel;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.service.JournalService;
import com.imyeyu.api.modules.journal.vo.AppendRequest;
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.JournalPage;
import com.imyeyu.api.modules.journal.vo.JournalRequest;
import com.imyeyu.api.modules.journal.vo.JournalResponse;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.ArgMap;
import com.imyeyu.network.GsonRequest;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.bean.PageResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
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.RestController;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-26 15:55
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/journal")
public class JournalController {
private final Gson gson;
private final JournalService service;
private final SettingService settingService;
private final AttachmentService attachmentService;
@AOPLog
@RequestRateLimit
@PostMapping("/openid")
public String initOpenId(@RequestSingleParam String code) {
try {
ArgMap<String, String> args = new ArgMap<>();
args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET));
args.put("js_code", code);
args.put("grant_type", "authorization_code");
InitCodeResponse resp = GsonRequest.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")).resultAs(InitCodeResponse.class);
return resp.getOpenid();
} catch (Exception e) {
log.error("init WeChat openId error", e);
throw new TimiException(TimiCode.ERROR, "init openId error", e);
}
}
@AOPLog
@RequestRateLimit
@PostMapping("/create")
public void create(@RequestBody JournalRequest request) {
service.create(request);
}
@PostMapping("/append")
public void append(@RequestBody AppendRequest request) {
service.appendItems(request);
}
@AOPLog
@PostMapping("/delete")
public void delete(@RequestBody Long thumbId) {
attachmentService.deleteMedia(thumbId);
}
@AOPLog
@RequestRateLimit
@RequestMapping("/list")
public PageResult<JournalResponse> list(@RequestBody JournalPage page) {
PageResult<Journal> pageResult = service.page(page);
PageResult<JournalResponse> result = new PageResult<>();
result.setTotal(pageResult.getTotal());
result.setList(pageResult.getList().stream().map(item -> {
JournalResponse resp = new JournalResponse();
resp.setItems(attachmentService.listByBizId(Attachment.BizType.JOURNAL, item.getId()));
BeanUtils.copyProperties(item, resp);
return resp;
}).toList());
return result;
}
@AOPLog
@RequestRateLimit
@GetMapping("/list/date")
public Long[] listDate() {
return service.listDate();
}
@RequestRateLimit
@GetMapping("/total")
public long total() {
long journal = attachmentService.countByBizId(Attachment.BizType.JOURNAL, null, MediaAttach.Type.SOURCE);
long journalTravel = attachmentService.countByBizId(Attachment.BizType.JOURNAL_TRAVEL, null, MediaAttach.Type.SOURCE);
return journal + journalTravel;
}
@RequestRateLimit
@GetMapping("/travel")
public Travel getTravel() {
return service.getTravel();
}
@AOPLog
@RequestRateLimit
@PostMapping("/travel/luggage/update")
public void updateTravel(@RequestBody Travel.Luggage luggage) {
service.updateTravelLuggage(luggage);
}
@AOPLog
@RequestRateLimit
@PostMapping("/moment/create")
public List<Attachment> createMoment(@RequestBody String[] tempFileIds) {
return service.createMoment(tempFileIds);
}
@AOPLog
@RequestRateLimit
@PostMapping("/moment/list")
public List<Attachment> listMoment() {
return service.listMoment();
}
@AOPLog
@RequestRateLimit
@PostMapping("/moment/filter")
public String[] filterExistMoment(@RequestBody String[] md5s) {
return service.filterExistMoment(md5s);
}
@AOPLog
@RequestRateLimit
@PostMapping("/moment/delete")
public void deleteMoment(@RequestBody Long[] thumbIds) {
service.deleteMoment(thumbIds);
}
@AOPLog
@RequestRateLimit
@PostMapping("/moment/archive")
public void archiveMoment(@RequestBody ArchiveRequest request) {
service.archiveMoment(request);
}
}

View File

@ -0,0 +1,41 @@
package com.imyeyu.api.modules.journal.entity;
import com.imyeyu.spring.entity.Entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2025-09-26 15:48
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class Journal extends Entity {
/**
*
*
* @author 夜雨
* @since 2025-10-09 18:44
*/
public enum Type {
NORMAL,
PORTFOLIO
}
private Type type;
private String idea;
private Double lat;
private Double lng;
private String location;
private String weather;
private String pusher;
}

View File

@ -0,0 +1,23 @@
package com.imyeyu.api.modules.journal.mapper;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-26 15:54
*/
public interface JournalMapper extends BaseMapper<Journal, Long> {
@Select("SELECT COUNT(1) FROM `journal` WHERE `type` = #{type}")
long countByType(Journal.Type type);
@Select("SELECT * FROM `journal` WHERE `type` = #{type} AND `deleted_at` IS NULL ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}")
List<Journal> listByType(Journal.Type type, long offset, int limit);
@Select("SELECT created_at FROM `journal` WHERE `deleted_at` IS NULL")
Long[] listDate();
}

View File

@ -0,0 +1,43 @@
package com.imyeyu.api.modules.journal.service;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.journal.bean.Travel;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.vo.AppendRequest;
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.JournalPage;
import com.imyeyu.api.modules.journal.vo.JournalRequest;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.DeletableService;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-26 15:53
*/
public interface JournalService extends DeletableService<Long> {
PageResult<Journal> page(JournalPage page) throws TimiException;
Long[] listDate() throws TimiException;
void create(JournalRequest request) throws TimiException;
void appendItems(AppendRequest request) throws TimiException;
String[] filterExistMoment(String[] md5s) throws TimiException;
List<Attachment> createMoment(String[] tempFileIds) throws TimiException;
List<Attachment> listMoment() throws TimiException;
void deleteMoment(Long[] thumbIds) throws TimiException;
void archiveMoment(ArchiveRequest request) throws TimiException;
Travel getTravel() throws TimiException;
void updateTravelLuggage(Travel.Luggage luggage) throws TimiException;
}

View File

@ -0,0 +1,230 @@
package com.imyeyu.api.modules.journal.service.implement;
import com.google.gson.Gson;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.MediaAttach;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TempFileService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.journal.bean.Travel;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.mapper.JournalMapper;
import com.imyeyu.api.modules.journal.service.JournalService;
import com.imyeyu.api.modules.journal.vo.AppendRequest;
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.JournalPage;
import com.imyeyu.api.modules.journal.vo.JournalRequest;
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.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author 夜雨
* @since 2025-09-26 15:54
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService {
private final Gson gson;
private final SettingService settingService;
private final TempFileService tempFileService;
private final AttachmentService attachmentService;
private final JournalMapper mapper;
@Override
protected BaseMapper<Journal, Long> mapper() {
return mapper;
}
@Override
public PageResult<Journal> page(JournalPage page) {
PageResult<Journal> result = new PageResult<>();
result.setTotal(mapper.countByType(page.getType()));
result.setList(mapper.listByType(page.getType(), page.getOffset(), page.getLimit()));
return result;
}
@Override
public Long[] listDate() throws TimiException {
return mapper.listDate();
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(JournalRequest request) throws TimiException {
try {
Journal journal = new Journal();
BeanUtils.copyProperties(request, journal);
super.create(journal);
String[] tempFileIds = request.getTempFileIds();
if (TimiJava.isEmpty(tempFileIds)) {
return;
}
for (int i = 0; i < tempFileIds.length; i++) {
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]);
File file = tempFileService.get(tempFileIds[i]);
AttachmentRequest sourceAttach = new AttachmentRequest();
sourceAttach.setName(metadata.getOriginalName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId());
sourceAttach.setInputStream(IO.getInputStream(file));
attachmentService.createMedia(sourceAttach);
}
} catch (Exception e) {
log.error("create journal error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO create journal error");
}
}
@Override
public void appendItems(AppendRequest request) throws TimiException {
TimiException.required(request.getTempFileIds(), "not found or empty tempFileIds");
try {
Journal journal = get(request.getId());
String[] tempFileIds = request.getTempFileIds();
for (int i = 0; i < tempFileIds.length; i++) {
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]);
File file = tempFileService.get(tempFileIds[i]);
AttachmentRequest sourceAttach = new AttachmentRequest();
sourceAttach.setName(metadata.getOriginalName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId());
sourceAttach.setInputStream(IO.getInputStream(file));
attachmentService.createMedia(sourceAttach);
}
} catch (Exception e) {
log.error("create journal error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO create journal error");
}
}
@Override
public String[] filterExistMoment(String[] md5s) throws TimiException {
if (TimiJava.isEmpty(md5s)) {
return null;
}
Set<String> dbMD5 = attachmentService.listByMd5s(Attachment.BizType.JOURNAL_MOMENT, null, List.of(MediaAttach.Type.SOURCE), Arrays.asList(md5s))
.stream()
.map(Attachment::getMd5)
.collect(Collectors.toSet());
return Arrays.stream(md5s).filter(md5 -> !dbMD5.contains(md5)).toArray(String[]::new);
}
@Override
public List<Attachment> createMoment(String[] tempFileIds) throws TimiException {
if (TimiJava.isEmpty(tempFileIds)) {
return null;
}
try {
List<Attachment> thumbResult = new ArrayList<>();
for (int i = 0; i < tempFileIds.length; i++) {
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]);
File file = tempFileService.get(tempFileIds[i]);
AttachmentRequest sourceAttach = new AttachmentRequest();
sourceAttach.setName(metadata.getOriginalName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL_MOMENT);
sourceAttach.setBizId(0L);
sourceAttach.setAttachTypeValue(MediaAttach.Type.SOURCE);
sourceAttach.setSize(file.length());
sourceAttach.setInputStream(IO.getInputStream(file));
thumbResult.add(attachmentService.createMedia(sourceAttach));
}
return thumbResult;
} catch (Exception e) {
log.error("create moment error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO create moment error");
}
}
@Override
public List<Attachment> listMoment() throws TimiException {
Page page = new Page(0, Integer.MAX_VALUE);
LinkedHashMap<String, BaseMapper.OrderType> orderMap = new LinkedHashMap<>();
orderMap.put("created_at", BaseMapper.OrderType.DESC);
page.setOrderMap(orderMap);
return attachmentService.pageByBizId(Attachment.BizType.JOURNAL_MOMENT, 0L, List.of(MediaAttach.Type.THUMB), page).getList();
}
@Override
public void deleteMoment(Long[] thumbIds) throws TimiException {
for (int i = 0; i < thumbIds.length; i++) {
attachmentService.deleteMedia(thumbIds[i]);
}
}
@Override
public void archiveMoment(ArchiveRequest request) throws TimiException {
try {
Journal journal = new Journal();
BeanUtils.copyProperties(request, journal);
super.create(journal);
Long[] thumbIds = request.getThumbIds();
if (TimiJava.isEmpty(thumbIds)) {
return;
}
for (int i = 0; i < thumbIds.length; i++) {
Attachment thumbAttach = attachmentService.get(thumbIds[i]);
thumbAttach.setBizType(Attachment.BizType.JOURNAL);
thumbAttach.setBizId(journal.getId());
attachmentService.update(thumbAttach);
MediaAttach.ExtData extData = gson.fromJson(thumbAttach.getExt(), MediaAttach.ExtData.class);
Attachment sourceAttach = attachmentService.get(extData.getSourceId());
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId());
attachmentService.update(sourceAttach);
}
} catch (Exception e) {
log.error("archive journal error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO archive journal error");
}
}
@Override
public Travel getTravel() throws TimiException {
return gson.fromJson(settingService.getAsJsonObject(SettingKey.JOURNAL_TRAVEL), Travel.class);
}
@Override
public void updateTravelLuggage(Travel.Luggage luggage) throws TimiException {
Travel dbTravel = getTravel();
dbTravel.setLuggage(luggage);
Setting setting = settingService.getByKey(SettingKey.JOURNAL_TRAVEL);
setting.setValue(gson.toJson(dbTravel));
settingService.update(setting);
settingService.clearCache(SettingKey.JOURNAL_TRAVEL);
}
}

View File

@ -0,0 +1,47 @@
package com.imyeyu.api.modules.journal.util;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
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 jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* @author 夜雨
* @version 2023-11-23 17:09
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class JournalAPIInterceptor implements HandlerInterceptor {
public static final String PATH = "/journal/**";
private final SettingService settingService;
private String[] keys;
@PostConstruct
private void postConstruct() {
keys = settingService.getAsString(SettingKey.JOURNAL_KEY).split(",");
}
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
String key = TimiJava.firstNotEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(key)) {
return true;
}
}
throw new TimiException(TimiCode.ARG_MISS).msgKey("invalid.key");
}
}

View File

@ -0,0 +1,15 @@
package com.imyeyu.api.modules.journal.vo;
import lombok.Data;
/**
* @author 夜雨
* @since 2025-09-30 20:37
*/
@Data
public class AppendRequest {
private Long id;
private String[] tempFileIds;
}

View File

@ -0,0 +1,16 @@
package com.imyeyu.api.modules.journal.vo;
import com.imyeyu.api.modules.journal.entity.Journal;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2025-10-20 15:29
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ArchiveRequest extends Journal {
private Long[] thumbIds;
}

View File

@ -0,0 +1,17 @@
package com.imyeyu.api.modules.journal.vo;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.spring.bean.Page;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2025-10-09 18:48
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class JournalPage extends Page {
private Journal.Type type;
}

View File

@ -0,0 +1,18 @@
package com.imyeyu.api.modules.journal.vo;
import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.spring.annotation.table.Transient;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2025-09-26 15:56
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class JournalRequest extends Journal {
@Transient
private String[] tempFileIds;
}

View File

@ -0,0 +1,19 @@
package com.imyeyu.api.modules.journal.vo;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.journal.entity.Journal;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-26 15:56
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class JournalResponse extends Journal {
private List<Attachment> items;
}

View File

@ -1,13 +1,13 @@
package com.imyeyu.api.modules.system.controller; package com.imyeyu.api.modules.system.controller;
import com.imyeyu.io.IO;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest; import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.system.bean.ServerStatus; import com.imyeyu.api.modules.system.bean.ServerStatus;
import com.imyeyu.api.modules.system.service.SystemService; import com.imyeyu.api.modules.system.service.SystemService;
import com.imyeyu.api.modules.system.vo.TempAttachRequest; import com.imyeyu.api.modules.system.vo.TempAttachRequest;
import com.imyeyu.io.IO;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -47,7 +47,8 @@ public class GsonSerializerAdapter implements JsonSerializer<Object> {
if (TimiJava.isNotEmpty(multiLangId)) { if (TimiJava.isNotEmpty(multiLangId)) {
Long langId = Long.parseLong(multiLangId); Long langId = Long.parseLong(multiLangId);
if (redisMultilingual.map(TimiServerAPI.getUserLanguage()) instanceof RedisLanguage rl) { if (redisMultilingual.map(TimiServerAPI.getUserLanguage()) instanceof RedisLanguage rl) {
Ref.setFieldValue(value, field, rl.textArgs(langId, (Object) multiField.args())); // TODO 支持插值参数
Ref.setFieldValue(value, field, rl.text(langId));
} }
} }
} }

View File

@ -3,18 +3,18 @@ package com.imyeyu.api.util;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.bean.ServerFile; import com.imyeyu.api.modules.system.bean.ServerFile;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
import com.imyeyu.spring.util.GlobalReturnHandler; import com.imyeyu.spring.util.GlobalReturnHandler;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.api.ArchiveCommand;
import org.eclipse.jgit.archive.TarFormat; import org.eclipse.jgit.archive.TarFormat;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -81,9 +81,9 @@ public class InitApplication implements ApplicationRunner {
private void initMultilingual() { private void initMultilingual() {
// redisLanguage.flushAll(); // redisLanguage.flushAll();
globalReturnHandler.setMultilingualHeader(key -> { globalReturnHandler.setMultilingualHeader(mapping -> {
AbstractLanguageMapper map = redisMultilingual.map(TimiServerAPI.getUserLanguage()); AbstractLanguageMapper map = redisMultilingual.map(TimiServerAPI.getUserLanguage());
return map.text(key); return map.textArgs(mapping.getMsgKey(), mapping.getMsgArgs());
}); });
} }

View File

@ -0,0 +1,42 @@
package com.imyeyu.api.util;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
*
*
* @author 夜雨
* @since 2025-10-23 17:05
*/
public class JavaCV {
public static ByteArrayOutputStream captureThumbnail(InputStream stream, double targetSeconds) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(stream)) {
grabber.start();
long targetMillis = (long) (targetSeconds * 1000);
grabber.setTimestamp(targetMillis);
Frame frame;
while ((frame = grabber.grabImage()) != null) {
if (grabber.getTimestamp() >= targetMillis) {
Java2DFrameConverter converter = new Java2DFrameConverter();
try (converter) {
BufferedImage bi = converter.getBufferedImage(frame);
if (bi != null) {
ImageIO.write(bi, "png", outStream);
break;
}
}
}
}
}
return outStream;
}
}

View File

@ -1,14 +1,10 @@
package com.imyeyu.api.util; package com.imyeyu.api.util;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.Language;
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.java.bean.Language;
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
import org.jcodec.api.NotSupportedException; import org.jcodec.api.NotSupportedException;
import org.springframework.lang.Nullable;
import java.util.Arrays;
/** /**
* @author 夜雨 * @author 夜雨
@ -45,33 +41,7 @@ public class RedisLanguage extends AbstractLanguageMapper {
} }
} }
@Override
public String text(String key, String def) {
String result = text(key);
return result.equals(key) ? def : result;
}
@Override
public String textArgs(String key, Object... args) {
String result = text(key);
if (result.equals(key)) {
// 没有映射值
return result + Arrays.toString(args);
}
FORMAT.applyPattern(result);
return FORMAT.format(args);
}
public String text(Long id) { public String text(Long id) {
return TimiServerAPI.applicationContext.getBean(MultilingualService.class).get(language, id); return TimiServerAPI.applicationContext.getBean(MultilingualService.class).get(language, id);
} }
public String textArgs(Long id, @Nullable Object... args) {
String result = text(id);
if (TimiJava.isEmpty(args)) {
return result;
}
FORMAT.applyPattern(result);
return FORMAT.format(args);
}
} }

View File

@ -2,15 +2,47 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.common.mapper.AttachmentMapper"> <mapper namespace="com.imyeyu.api.modules.common.mapper.AttachmentMapper">
<sql id="table">attachment</sql> <sql id="table">attachment</sql>
<select id="listByAttachType" resultType="com.imyeyu.api.modules.common.entity.Attachment"> <select id="listByBizId" resultType="com.imyeyu.api.modules.common.entity.Attachment">
SELECT SELECT
* *
FROM FROM
<include refid="table" /> attachment
WHERE WHERE
biz_type = #{bizType} biz_type = #{bizType}
AND biz_id = #{bizId} <if test="bizId != null">
<if test="attachTypes != null and 0 &lt; attachTypes.length"> AND biz_id = #{bizId}
</if>
<if test="attachTypes != null and 0 &lt; attachTypes.size()">
AND attach_type IN
<foreach collection="attachTypes" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
AND deleted_at IS NULL
AND destroy_at IS NULL
<if test="page != null">
<if test="page.orderMap != null and !page.orderMap.isEmpty()">
ORDER BY
<foreach collection="page.orderMap" index="key" item="value" separator=",">
${key} ${value}
</foreach>
</if>
LIMIT
#{page.offset},
#{page.limit}
</if>
</select>
<select id="countByBizId" resultType="long">
SELECT
COUNT(1)
FROM
attachment
WHERE
biz_type = #{bizType}
<if test="bizId != null">
AND biz_id = #{bizId}
</if>
<if test="attachTypes != null and 0 &lt; attachTypes.size()">
AND attach_type IN AND attach_type IN
<foreach collection="attachTypes" item="item" open="(" separator="," close=")"> <foreach collection="attachTypes" item="item" open="(" separator="," close=")">
#{item} #{item}
@ -19,4 +51,23 @@
AND deleted_at IS NULL AND deleted_at IS NULL
AND destroy_at IS NULL AND destroy_at IS NULL
</select> </select>
</mapper> <select id="listByMd5s" resultType="com.imyeyu.api.modules.common.entity.Attachment">
SELECT
*
FROM
attachment
WHERE
biz_type = #{bizType}
<if test="bizId != null">
AND biz_id = #{bizId}
</if>
<if test="attachTypes != null and 0 &lt; attachTypes.size()">
AND attach_type IN
<foreach collection="attachTypes" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
AND deleted_at IS NULL
AND destroy_at IS NULL
</select>
</mapper>