feat(笔记预览): 实现大文件分块加载功能
添加分块加载API接口及前端实现,支持大文件(>500KB)的分页加载,提升大文件预览体验 后端实现分块逻辑并添加权限检查,前端添加加载提示和滚动加载功能
This commit is contained in:
@@ -3,6 +3,7 @@ package com.test.bijihoudaun.controller;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.test.bijihoudaun.common.response.R;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
import com.test.bijihoudaun.service.MarkdownFileService;
|
||||
import com.test.bijihoudaun.util.SecurityUtil;
|
||||
@@ -117,4 +118,26 @@ public class MarkdownController {
|
||||
return R.success(files);
|
||||
}
|
||||
|
||||
@Operation(summary = "分块加载Markdown文件内容", description = "用于大文件(> 500KB)的分页加载")
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "文件ID", required = true),
|
||||
@Parameter(name = "chunkIndex", description = "块索引(从0开始)", required = false),
|
||||
@Parameter(name = "chunkSize", description = "块大小(字符数),默认10000", required = false)
|
||||
})
|
||||
@GetMapping("/{id}/chunk")
|
||||
public R<MarkdownFileChunk> getMarkdownChunk(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(defaultValue = "0") int chunkIndex,
|
||||
@RequestParam(defaultValue = "10000") int chunkSize) {
|
||||
|
||||
// 获取当前认证状态
|
||||
boolean isAuthenticated = SecurityUtil.isUserAuthenticated();
|
||||
|
||||
MarkdownFileChunk chunk = markdownFileService.getMarkdownChunk(id, chunkIndex, chunkSize, isAuthenticated);
|
||||
if (ObjectUtil.isNotNull(chunk)) {
|
||||
return R.success(chunk);
|
||||
}
|
||||
return R.fail("文件未找到");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.test.bijihoudaun.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Markdown文件分块加载响应实体
|
||||
* 用于大文件分页加载
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Markdown文件分块")
|
||||
public class MarkdownFileChunk implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "当前块的内容")
|
||||
private String chunk;
|
||||
|
||||
@Schema(description = "当前块索引(从0开始)")
|
||||
private int chunkIndex;
|
||||
|
||||
@Schema(description = "总块数")
|
||||
private int totalChunks;
|
||||
|
||||
@Schema(description = "是否还有更多块")
|
||||
private boolean hasMore;
|
||||
|
||||
@Schema(description = "笔记ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
private Long fileId;
|
||||
|
||||
@Schema(description = "笔记标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否私密 0-公开 1-私密")
|
||||
private Integer isPrivate;
|
||||
|
||||
@Schema(description = "总字符数")
|
||||
private long totalLength;
|
||||
|
||||
@Schema(description = "当前块字符数")
|
||||
private int chunkLength;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.test.bijihoudaun.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -66,4 +67,15 @@ public interface MarkdownFileService extends IService<MarkdownFile> {
|
||||
* @return 文件列表
|
||||
*/
|
||||
List<MarkdownFileVO> getRecentFiles(int limit);
|
||||
|
||||
/**
|
||||
* 分块加载Markdown文件内容
|
||||
* 用于大文件(> 500KB)的分页加载
|
||||
* @param id 文件ID
|
||||
* @param chunkIndex 块索引(从0开始)
|
||||
* @param chunkSize 块大小(字符数),默认10000
|
||||
* @param isAuthenticated 是否已认证
|
||||
* @return 文件块对象
|
||||
*/
|
||||
MarkdownFileChunk getMarkdownChunk(Long id, int chunkIndex, int chunkSize, boolean isAuthenticated);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.test.bijihoudaun.entity.ImageName;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
import com.test.bijihoudaun.mapper.ImageNameMapper;
|
||||
import com.test.bijihoudaun.mapper.MarkdownFileMapper;
|
||||
@@ -156,4 +157,92 @@ public class MarkdownFileServiceImpl
|
||||
return markdownFileMapper.selectRecentWithGrouping(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkdownFileChunk getMarkdownChunk(Long id, int chunkIndex, int chunkSize, boolean isAuthenticated) {
|
||||
// 获取文件基本信息(不包含内容)
|
||||
MarkdownFileVO fileVO = markdownFileMapper.selectByIdWithGrouping(id);
|
||||
if (fileVO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查权限:私密笔记需要登录
|
||||
if (fileVO.getIsPrivate() != null && fileVO.getIsPrivate() == 1 && !isAuthenticated) {
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(fileVO.getTitle());
|
||||
chunk.setIsPrivate(fileVO.getIsPrivate());
|
||||
chunk.setChunk("该笔记为私密笔记,请登录后查看");
|
||||
chunk.setChunkIndex(0);
|
||||
chunk.setTotalChunks(1);
|
||||
chunk.setHasMore(false);
|
||||
chunk.setTotalLength(0);
|
||||
chunk.setChunkLength(0);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// 获取完整内容
|
||||
MarkdownFile file = this.getById(id);
|
||||
if (file == null || file.getContent() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fullContent = file.getContent();
|
||||
int totalLength = fullContent.length();
|
||||
|
||||
// 如果文件小于 500KB(约50万字符),直接返回全部内容
|
||||
if (totalLength <= 500000) {
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(file.getTitle());
|
||||
chunk.setIsPrivate(file.getIsPrivate());
|
||||
chunk.setChunk(fullContent);
|
||||
chunk.setChunkIndex(0);
|
||||
chunk.setTotalChunks(1);
|
||||
chunk.setHasMore(false);
|
||||
chunk.setTotalLength(totalLength);
|
||||
chunk.setChunkLength(totalLength);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// 大文件分页加载
|
||||
// 计算总块数
|
||||
int totalChunks = (int) Math.ceil((double) totalLength / chunkSize);
|
||||
|
||||
// 确保 chunkIndex 在有效范围内
|
||||
if (chunkIndex < 0) {
|
||||
chunkIndex = 0;
|
||||
}
|
||||
if (chunkIndex >= totalChunks) {
|
||||
chunkIndex = totalChunks - 1;
|
||||
}
|
||||
|
||||
// 计算当前块的起止位置
|
||||
int start = chunkIndex * chunkSize;
|
||||
int end = Math.min(start + chunkSize, totalLength);
|
||||
|
||||
// 截取内容
|
||||
String chunkContent = fullContent.substring(start, end);
|
||||
|
||||
// 如果不是第一块,尝试从完整的换行处开始
|
||||
if (chunkIndex > 0 && start < totalLength) {
|
||||
// 找到第一个换行符,从下一行开始显示
|
||||
int firstNewLine = chunkContent.indexOf('\n');
|
||||
if (firstNewLine > 0) {
|
||||
chunkContent = chunkContent.substring(firstNewLine + 1);
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(file.getTitle());
|
||||
chunk.setIsPrivate(file.getIsPrivate());
|
||||
chunk.setChunk(chunkContent);
|
||||
chunk.setChunkIndex(chunkIndex);
|
||||
chunk.setTotalChunks(totalChunks);
|
||||
chunk.setHasMore(chunkIndex < totalChunks - 1);
|
||||
chunk.setTotalLength(totalLength);
|
||||
chunk.setChunkLength(chunkContent.length());
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user