一、文件列表功能的实现
1、接口的定义
**接口地址:**GET /api/v1/files/list
**接口描述:**分页查询文件列表,支持多条条件筛选。
请求头:
Authorization: Bearer {token}// 必填
请求参数:
-
pageNum(Integer,可选,默认1):页码 -
pageSize(Integer,可选,默认10):每页大小 -
fileName(String,可选):文件名(模糊查询) -
uploadStatus(Integer,可选):上传状态
响应实例:
{
"code": 200,
"message": "查询成功",
"data": {
"records": [
{
"fileId": 10000001,
"userId": 10000001,
"fileName": "example.xlsx",
"filePath": "/files/xxx.xlsx",
"fileSize": 1024000,
"fileUrl": "https://oss.example.com/files/xxx.xlsx",
"ossKey": "files/xxx.xlsx",
"uploadStatus": 1,
"fileType": "application/vnd.openxmlformatsofficedocument.spreadsheetml.sheet",
"fileExtension": "xlsx"
}
]
}
}
2、具体实现步骤
1、在 FilesController 中声明接口并声明dto
java
// 分页查询当前用户的文件列表
@GetMapping("/list")
@LogOperation("文件列表查询")
public Result<IPage<FileInfoResponse>> list(
@RequestHeader("Authorization")String authorization,
@Valid FileListRequest request
) {
}
java
/**
* 文件列表响应类
*/
@Data
@Builder
public class FileInfoResponse {
/**
* 文件ID
*/
private Long fileId;
/**
* 用户ID
*/
private Long userId;
/**
* 原始文件名称
*/
private String fileName;
/**
* 文件存储路径
*/
private String filePath;
/**
* 文件大小
*/
private Long fileSize;
/**
* 文件访问url
*/
private String fileUrl;
/**
* oss存储的key
*/
private String ossKey;
/**
* 上传状态
*/
private Integer uploadStatus;
/**
* 文件类型
*/
private String fileType;
/**
* 文件扩展名
*/
private String fileExtension;
}
java
/**
* 文件查询请求类
*/
@Data
@Builder
public class FileListRequest {
/**
* 用户ID
*/
private Long userId;
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 每页的大小
*/
private Integer pageSize = 10;
/**
* 文件名
*/
private String fileName;
/**
* 上传状态
*/
private Integer uploadStatus;
}
2、服务层实现
1、 FileService 接口中的方法声明
java
IPage<FileInfoResponse> list(FileListRequest request);
2、FileServiceImpl 实现类中的方法实现:
java
@Override
public IPage<FileInfoResponse> list(FileListRequest request) {
// 1. 构建查询对象
QueryWrapper<FilesEntity> queryWrapper = new QueryWrapper<>();
// 2. 在查询对象中间去构建请求参数
queryWrapper.eq("user_id", request.getUserId());
if (StringUtils.isNotBlank(request.getFileName())) {
queryWrapper.like("file_name", request.getFileName());
}
if (request.getUploadStatus() != null) {
queryWrapper.eq("upload_status", request.getUploadStatus());
}
queryWrapper.orderByDesc("id");
// 3. 查询出结构,创建分页对象
long current = request.getPageNum() != null ? request.getPageNum() : 1;
long size = request.getPageSize() != null ? request.getPageSize() : 10;
Page<FilesEntity> page = new Page<>(current, size);
IPage<FilesEntity> entityIPage = filesMapper.selectPage(page, queryWrapper);
// 4. 构造响应
List<FileInfoResponse> responseList = entityIPage.getRecords().stream()
.map(this::convert)
.collect(Collectors.toList());
Page<FileInfoResponse> responsePage = new Page<>(current, size);
responsePage.setRecords(responseList);
responsePage.setTotal(entityIPage.getTotal());
responsePage.setPages(entityIPage.getPages());
return responsePage;
}
// 转换对象的函数
private FileInfoResponse convert(FilesEntity filesEntity) {
String fileName = filesEntity.getFileName();
return FileInfoResponse.builder()
.fileId(filesEntity.getId())
.userId(filesEntity.getUserId())
.fileName(filesEntity.getFileName())
.filePath(filesEntity.getFilePath())
.fileSize(filesEntity.getFileSize())
.fileUrl(filesEntity.getOssKey())
.ossKey(filesEntity.getOssKey())
.uploadStatus(filesEntity.getUploadStatus())
.fileExtension(fileName.substring(fileName.lastIndexOf(".")))
.fileType(FileValidationUtil.getContentType(fileName))
.build();
}
IPage 是接口,Page 是实现类
Service 用 IPage 而不是 Page,是因为 Service 层应该依赖抽象(接口),而不是具体实现(类)。这样未来更换分页实现时,Controller 和调用方完全不需要改动,符合"面向接口编程"和"开闭原则"。
Page<FilesEntity> page--- 作为"查询参数"
java
Page<FilesEntity> page = new Page<>(current, size);
此时它只有 current 和 size 有值,其他字段(如 records、total)都是空的。它就像一个**"查询请求单"**,告诉 MyBatis-Plus:"请给我第 X 页,每页 Y 条数据"。
entityIPage--- 作为"查询结果"
java
IPage<FilesEntity> entityIPage = filesMapper.selectPage(page, queryWrapper);
selectPage 方法复用 了传入的 page 对象,但执行后:
-
MyBatis-Plus 自动查询数据库
-
将查询到的数据填充到
page.records中 -
将总记录数填充到
page.total中 -
此时这个
page对象已经变成了"结果对象"
所以 entityIPage 实际上就是执行完查询后的 page 对象 (因为 selectPage 返回的就是传入的 Page 实例)。
java
Page<FilesEntity> page = new Page<>(); // ✅ 具体类可以实例化
IPage<FilesEntity> entityIPage = page; // ✅ 接口指向具体对象
entityIPage 这个变量,存的是对象的引用(内存地址),而不是数据本身。

responsePage--- 转换后的"结果对象"
java
Page<FileInfoResponse> responsePage = new Page<>(current, size);
responsePage.setRecords(responseList); // 设置转换后的数据
responsePage.setTotal(entityIPage.getTotal()); // 复制分页元信息
因为 entityIPage 的 records 是 FilesEntity 类型,需要转换成 FileInfoResponse 返回给前端。
所以创建一个新的 Page 对象,把转换后的数据和原分页元信息复制进去。
FileValidationUtil 的 getContentType 实现
java
public static String getContentType(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf("."));
switch (extension) {
case ".xls":
return "application/vnd.ms-excel";
case ".xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
default:
return "";
}
}
3、在 FilesController 控制器中调用服务层方法
java
// 分页查询当前用户的文件列表
@GetMapping("/list")
@LogOperation("文件列表查询")
public Result<IPage<FileInfoResponse>> list(
@RequestHeader("Authorization")String authorization,
@Valid FileListRequest request
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
request.setUserId(userId);
IPage<FileInfoResponse> result = filesService.list(request);
return Result.success("查询成功", result);
}
二、文件下载功能的实现
1、接口定义

2、具体实现步骤
1、在 FilesController 中声明接口
java
// 下载文件
@GetMapping("/download")
@LogOperation("文件下载")
public void downloadFile(
@RequestHeader("Authorization")String authorization,
@RequestParam("fileId") Long fileId,
HttpServletResponse response
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
try {
response.getWriter().write("{\"code\":401,\"message\":\"无效的令牌\"}");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2、服务层实现
1、 FileService 接口中的方法声明
java
void downloadFile(Long fileId, HttpServletResponse response, Long userId);
2、FileServiceImpl 实现类中的方法实现:
java
@Override
public void downloadFile(Long fileId, HttpServletResponse response, Long userId) {
// 1 查询文件信息
FilesEntity filesEntity = filesMapper.selectById(fileId);
if (fileId == null) {
log.error("文件不存在 {}", fileId);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
try {
response.getWriter().write("{\"error\":\"文件不存在\"}");
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
// 2 检查用户是否拥有文件的权限
if (!filesEntity.getUserId().equals(userId)){
log.error("用户没权限下载当前文件{}", fileId);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
try {
response.getWriter().write("{\"error\":\"用户没权限\"}");
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
// 3 从oss上去拉文件
response.reset();
response.setContentType(FileValidationUtil.getContentType(filesEntity.getFileName()));
response.setCharacterEncoding("UTF-8");
OSSObject ossObject = ossService.getObject(filesEntity.getOssKey());
// 设置文件大小
long contentLength = ossObject.getObjectMetadata().getContentLength();
response.setContentLengthLong(contentLength);
response.setBufferSize(65536);
try {
InputStream inputStream = ossObject.getObjectContent();
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[65536];
int byteRead = 0;
long totalByteRead = 0;
while ((byteRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, byteRead);
totalByteRead += byteRead;
if (totalByteRead % (1024 * 1024) == 0) {
outputStream.flush();
}
}
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在 OssService 中声明获取对象的方法并在实现类中实现
java
/**
* 根据oss的key获取对象
* @param objectKey oss的key
* @return oss对象
*/
OSSObject getObject(String objectKey);
java
@Override
public OSSObject getObject(String objectKey) {
return ossClient.getObject(ossConfig.getBucketName(), objectKey);
}
3、在控制器调用上述方法
java
// 下载文件
@GetMapping("/download")
@LogOperation("文件下载")
public void downloadFile(
@RequestHeader("Authorization")String authorization,
@RequestParam("fileId") Long fileId,
HttpServletResponse response
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
try {
response.getWriter().write("{\"code\":401,\"message\":\"无效的令牌\"}");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
filesService.downloadFile(fileId, response, userId);
}
三、文件预览功能的实现
1、接口定义
接口地址: GET /api/v1/files/excel/preview/{fileId}
接口描述:预览Excel文件内容,支持分页和多sheet。
请求头:
- Authorization: Bearer {token} // 必填
路径参数:
- fileId (Long, 必填):文件ID
请求参数:
-
page(Integer, 可选,默认1):页码 -
pageSize(Integer, 可选,默认20):每页大小 -
sheetIndex(Integer, 可选):Sheet索引(多sheet时使用)
响应实例:

2、具体实现步骤
1、在 FilesController 中声明接口并声明dto
java
// 文件预览
@GetMapping("/excel/preview/{fileId}")
@LogOperation("Excel文件预览")
public Result<ExcelPreviewResponse> previewExcel(
@PathVariable Long fileId,
@RequestHeader("Authorization")String authorization,
@Valid ExcelPreviewRequest excelPreviewRequest
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
return Result.success("excel预览成功", response);
}
java
/**
* 文件预览的请求
*/
@Data
@Builder
public class ExcelPreviewRequest {
/**
* 页码
*/
private Integer page = 1;
/**
* 每页的行数
*/
private Integer pageSize = 20;
/**
* sheet索引
*/
private Integer sheetIndex;
}
java
/**
* 文件预览响应
*/
@Data
@Builder
public class ExcelPreviewResponse {
private ExcelInfo excelInfo;
private List<SheetInfo> sheets;
private Integer currentSheetIndex;
private List<ColumnHeader> headers;
private List<Map<String, Object>> dataRows;
private PaginationInfo paginationInfo;
@Data
@Builder
public static class ExcelInfo {
// 文件ID
private Long fileId;
// 文件名
private String fileName;
// 文件大小
private Long fileSize;
// 总行数
private Long totalRows;
// 总列数
private Long totalColumns;
}
@Data
@Builder
public static class SheetInfo {
// sheet索引
private Integer sheetIndex;
// sheet名称
private String sheetName;
// 对应的mysql表名
private String tableName;
// 总行数
private Long totalRows;
// 总列数
private Long totalColumns;
}
@Data
@Builder
public static class ColumnHeader {
// 数据库字段名
private String dbFieldName;
// 原始excel列名
private String originalHeader;
}
@Data
@Builder
public static class PaginationInfo {
// 当前页
private Integer currentPage;
// 每页的大小
private Integer pageSize;
// 总页数
private Long totalPages;
// 总记录数
private Long totalRecords;
// 是否有下一页
private Boolean hasNext;
// 是否有上一页
private Boolean hasPrevious;
}
}
2、服务层实现
1、 FileService 接口中的方法声明
java
PreviewResponse previewExcel(Long fileId, Long userId, Integer page, Integer pageSize, Integer sheetIndex);
2、FileServiceImpl 实现类中的方法实现:
校验权限时会用到
java
/**
* 文件访问数据库的 mapper
*/
@Mapper
public interface FilesMapper extends BaseMapper<FilesEntity> {
@Select("select *from files where user_id = #{userId} and id = #{fileId}")
FilesEntity selectByUserIdAndFileId(@Param("userId") Long userId, @Param("fileId") Long fileId);
}
根据 fileId 找到对应的 tableNames, 在 FileTableMappingService 中实现对应方法
FileTableMappingService
java
List<String> getTableNamesByFileId(Long fileId);
FileTableMappingServiceImpl
java
@Override
public List<String> getTableNamesByFileId(Long fileId) {
QueryWrapper<FileTableMappingEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("file_id", fileId).orderByAsc("sheet_index");
return fileTableMappingMapper.selectList(queryWrapper).stream().map(FileTableMappingEntity::getTableName).collect(Collectors.toList());
}
记录表总记录数的方法(在 FileServcieImpl 中实现)
java
private Long getTotalRecords(String tableName) {
String sql = "select count(1) from " + tableName;
return jdbcTemplate.queryForObject(sql, Long.class);
}
获取文件元信息的方法(在 FileServcieImpl 中实现)
java
public ExcelPreviewResponse.ExcelInfo getExcelInfo(Long fileId) {
FilesEntity filesEntity = filesMapper.selectById(fileId);
List<String> tableNames = fileTableMappingService.getTableNamesByFileId(fileId);
String fistTableName = tableNames.get(0);
Long totalRows = getTotalRecords(fistTableName);
Long totalColumns = getTotalColumns(fistTableName);
return ExcelPreviewResponse.ExcelInfo
.builder()
.fileId(fileId)
.fileName(filesEntity.getFileName())
.fileSize(filesEntity.getFileSize())
.totalRows(totalRows)
.totalColumns(totalColumns)
.build();
}
private Long getTotalColumns(String tableName) {
String sql = "describe " + tableName;
List<Map<String, Object>> columns = jdbcTemplate.queryForList(sql);
return (long) (columns.size() - 1);
}
getTotalColumns 方法中 减 1 是为了去掉建表时额外加的主键列 id
获取 Sheet 信息方法
java
private List<ExcelPreviewResponse.SheetInfo> buildSheetInfoList(List<String> tableNames) {
List<ExcelPreviewResponse.SheetInfo> sheetInfos = new ArrayList<>();
for (int i =0; i <tableNames.size(); i++) {
String tableName = tableNames.get(i);
Long totalRows = getTotalRecords(tableName);
Long totalColumns = getTotalColumns(tableName);
String sheetName = "sheet_"+i;
ExcelPreviewResponse.SheetInfo sheetInfo = ExcelPreviewResponse.SheetInfo.builder()
.sheetIndex(i)
.sheetName(sheetName)
.tableName(tableName)
.totalColumns(totalColumns)
.totalRows(totalRows)
.build();
sheetInfos.add(sheetInfo);
}
return sheetInfos;
}
实现表头的获取
1、获取映射关系
FilldMappingService 内方法声明
java
Map<String, String> getMappingMapByTableName(String tableName);
FilldMappingServiceImpl 内方法实现
java
@Override
public Map<String, String> getMappingMapByTableName(String tableName) {
QueryWrapper<FieldMappingEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("table_name", tableName);
List<FieldMappingEntity> fieldMappingEntities = fieldMappingMapper.selectList(queryWrapper);
Map<String, String> map = new LinkedHashMap<>();
for (FieldMappingEntity fieldMappingEntity :fieldMappingEntities) {
map.put(fieldMappingEntity.getDbFieldName(), fieldMappingEntity.getOriginalHeader());
}
return map;
}
2、转换格式获取 header 信息
java
private List<ExcelPreviewResponse.ColumnHeader> getColumnHeaders(String tableName) {
Map<String, String> fieldMappings = fieldMappingService.getMappingMapByTableName(tableName);
List<ExcelPreviewResponse.ColumnHeader> headers = new ArrayList<>();
for (String key :fieldMappings.keySet()) {
String dbFieldName = key;
String originalHeader = fieldMappings.getOrDefault(dbFieldName, dbFieldName);
ExcelPreviewResponse.ColumnHeader header = ExcelPreviewResponse.ColumnHeader.builder()
.dbFieldName(dbFieldName)
.originalHeader(originalHeader)
.build();
headers.add(header);
}
return headers;
}
获取分页数据方法的实现
java
private List<Map<String, Object>> getPageData(String tableName, Integer page, Integer pageSize) {
int offset = (page - 1) * pageSize;
String sql = "SELECT * FROM " + tableName + " ORDER BY id LIMIT ? OFFSET ?";
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, pageSize, offset);
return rows.stream()
.map(row -> {
Map<String, Object> newRow = new HashMap<>(row);
newRow.remove("id");
return newRow;
})
.collect(Collectors.toList());
}
获取分页信息方法的实现
java
private ExcelPreviewResponse.PaginationInfo getPaginationInfo(Integer page, Integer pageSize, Long totalRecords) {
long totalPages = (long) Math.ceil((double) totalRecords / pageSize);
return ExcelPreviewResponse.PaginationInfo.builder()
.currentPage(page)
.pageSize(pageSize)
.totalPages(totalPages)
.totalRecords(totalRecords)
.hasNext(page <totalPages)
.hasPrevious(page >1)
.build();
}
java
@Override
public ExcelPreviewResponse previewExcel(Long fileId, Long userId, Integer page, Integer pageSize, Integer sheetIndex) {
// 1. 校验文件权限
FilesEntity filesEntity = filesMapper.selectByUserIdAndFileId(userId, fileId);
if (filesEntity == null) {
throw new IllegalArgumentException("文件不存在或者用户无权限");
}
// 2. 根据fileId获取所有的表
List<String> tableNames = fileTableMappingService.getTableNamesByFileId(fileId);
String currentTableName = tableNames.get(sheetIndex);
Long totalRecords = getTotalRecords(currentTableName);
// 3. 从表中获取数据,然后构建响应
return ExcelPreviewResponse.builder()
.excelInfo(getExcelInfo(fileId))
.sheets(tableNames.size() > 1 ? buildSheetInfoList(tableNames) : null)
.currentSheetIndex(sheetIndex)
.headers(getColumnHeaders(currentTableName))
.dataRows(getPageData(currentTableName, page, pageSize))
.paginationInfo(getPaginationInfo(page, pageSize, totalRecords))
.build();
}
3、在 FilesController 控制器中调用服务层方法
java
// 文件预览
@GetMapping("/excel/preview/{fileId}")
@LogOperation("Excel文件预览")
public Result<ExcelPreviewResponse> previewExcel(
@PathVariable Long fileId,
@RequestHeader("Authorization")String authorization,
@Valid ExcelPreviewRequest excelPreviewRequest
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
ExcelPreviewResponse response = filesService.previewExcel(
fileId,
userId,
excelPreviewRequest.getPage(),
excelPreviewRequest.getPageSize(),
excelPreviewRequest.getSheetIndex()
);
return Result.success("excel预览成功", response);
}
四、获取文件信息功能的实现
文件预览其实也可以获取文件信息,但是太"重'了,后面的方法实现还会频繁用到此功能,所以我们单拎出来实现(其实就是把上一步的 getExcelInfo 再封装成一个方法)
1、控制器层的代码
java
// 获取文件信息
@GetMapping("/excel/info/{fileId}")
@LogOperation("Excel文件信息")
public Result<ExcelPreviewResponse.ExcelInfo> getExcelInfo(
@PathVariable Long fileId,
@RequestHeader("Authorization")String authorization
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
return Result.success("excel信息获取成功", filesService.getExcelInfo(fileId));
}
2、服务层的代码
FileService
java
ExcelPreviewResponse.ExcelInfo getExcelInfo(Long fileId);
FileServiceImpl 中 getExcelInfo 方法在上一步就已经实现过了
五、一键复原功能的实现
1、接口声明

2、具体实现步骤
1、在 FileController 中声明方法
java
// 一键复原文件
@PostMapping("/restore/{fileId}")
@LogOperation("一键复原excel数据")
public Result<Boolean> restoreFileData(
@PathVariable Long fileId,
@RequestHeader("Authorization")String authorization
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
return Result.success("文件复原成功", filesService.restoreFileData(fileId, userId));
}
2、服务器实现
java
boolean restoreFileData(Long fileId, Long userId);
java
@Override
public boolean restoreFileData(Long fileId, Long userId) {
// 1. 查询文件信息
FilesEntity filesEntity = filesMapper.selectByUserIdAndFileId(userId, fileId);
if (filesEntity == null) {
throw new IllegalArgumentException("文件不存在或者用户无权限");
}
// 2. 选择需要复原的mysql表
List<String> tableNameList = fileTableMappingService.getTableNamesByFileId(fileId);
// 3. 获取原始excel文件
MultipartFile file = downloadFileFromOss(filesEntity.getOssKey());
if (file == null) {
throw new RuntimeException("无法获取有效的文件");
}
for (int i = 0; i <tableNameList.size(); i++) {
String tableName = tableNameList.get(i);
// 4. 清空mysql表
String sql = "TRUNCATE TABLE `" + tableName + "`";
jdbcTemplate.update(sql);
// 5. 插入数据
excelToTableService.insertData(tableName, file, i);
}
return true;
}
从 OSS 上下载文件方法的实现 downloadFileFromOss
java
private MultipartFile downloadFileFromOss(String ossKey) {
try {
// 从OSS获取文件流
InputStream inputStream = ossService.getObject(ossKey).getObjectContent();
if (inputStream == null) {
log.error("无法从OSS获取文件流,OSS Key:{}", ossKey);
return null;
}
// 读取文件内容到字节数组
byte[] fileBytes = inputStream.readAllBytes();
inputStream.close();
// 从OSS Key中提取文件名
String fileName = ossKey.substring(ossKey.lastIndexOf('/') + 1);
// 创建MultipartFile实现
return new MultipartFile() {
@Override
public @org.springframework.lang.NonNull String getName() {
return "file";
}
@Override
public String getOriginalFilename() {
return fileName;
}
@Override
public String getContentType() {
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
@Override
public boolean isEmpty() {
return fileBytes.length == 0;
}
@Override
public long getSize() {
return fileBytes.length;
}
@Override
public @org.springframework.lang.NonNull byte[] getBytes() {
return fileBytes;
}
@Override
public @org.springframework.lang.NonNull java.io.InputStream getInputStream() {
return new ByteArrayInputStream(fileBytes);
}
@Override
public void transferTo(@org.springframework.lang.NonNull java.io.File dest) throws java.io.IOException {
Files.write(dest.toPath(), fileBytes);
}
};
} catch (Exception e) {
log.error("从OSS下载文件失败,OSS Key:{},错误:{}", ossKey, e.getMessage(), e);
return null;
}
}
ExcelToTableService 与 ExcelToTableServiceImpl
java
/**
* 一键复原数据
*/
void insertData(String tableName, MultipartFile file, int sheetIndex);
ExcelToTableServiceImpl 的 insertData 在前几步已经实现
3、控制器调用
java
// 一键复原文件
@PostMapping("/restore/{fileId}")
@LogOperation("一键复原excel数据")
public Result<Boolean> restoreFileData(
@PathVariable Long fileId,
@RequestHeader("Authorization")String authorization
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
return Result.success("文件复原成功", filesService.restoreFileData(fileId, userId));
}
六、文件删除功能的实现
1、接口声明

2、具体实现步骤
1、控制器接口声明与dto声明
java
// 批量删除文件
@DeleteMapping("/delete")
@LogOperation("文件删除")
public Result<Boolean> deleteFiles(
@RequestHeader("Authorization")String authorization,
@RequestBody @Valid FileDeleteRequest fileDeleteRequest
) {
Long userId = jwtUtil.getUserIdByAuthorization(authorization);
if (userId == null) {
return Result.badRequest("无效的令牌");
}
return Result.success("删除成功", filesService.deleteFiles(fileDeleteRequest, userId));
}
java
/**
* 文件删除请求类
*/
@Data
public class FileDeleteRequest {
/**
* 文件ID列表
*/
@NotEmpty(message = "需要删除的文件不能为空")
private List<Long> fileIds;
}
2、服务层实现
FileService 与 FileServiceImpl
java
Boolean deleteFiles(@Valid FileDeleteRequest fileDeleteRequest, Long userId);
java
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteFiles(FileDeleteRequest fileDeleteRequest, Long userId) {
// 1. 遍历处理文件ID
for (Long fileId : fileDeleteRequest.getFileIds()) {
// 2. 判断权限
FilesEntity filesEntity = filesMapper.selectByUserIdAndFileId(userId, fileId);
if (filesEntity == null) {
log.error("文件不存在或者无权限删除 {} {}", fileId, userId);
continue; // 继续循环
}
filesMapper.deleteById(fileId);
// 3. 删除衍生出来的表
List<String> tableNames = fileTableMappingService.getTableNamesByFileId(fileId);
for (String tableName :tableNames) {
String sql = "DROP TABLE IF EXISTS `" + tableName + "`";
jdbcTemplate.execute(sql);
log.info("mysql表删除成功 {}", tableName);
}
// 4. 删除file_table_mappings的记录
fileTableMappingService.deleteByFileId(fileId);
// 5. 删除field_mappings的记录
fieldMappingService.deleteByFileId(fileId);
// 6 删除oss记录
ossService.deleteFile(filesEntity.getOssKey());
}
return true;
}
FileTableMappingService 与 FileTableMappingServiceImpl
java
void deleteByFileId(Long fileId);
java
@Override
public void deleteByFileId(Long fileId) {
LambdaQueryWrapper<FileTableMappingEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FileTableMappingEntity::getFileId, fileId);
fileTableMappingMapper.delete(queryWrapper);
}
FieldMappingService 与 FieldMappingServicelmpl
java
void deleteByFileId(Long fileId);
java
@Override
public void deleteByFileId(Long fileId) {
LambdaQueryWrapper<FieldMappingEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FieldMappingEntity::getFileId, fileId);
fieldMappingMapper.delete(queryWrapper);
}
OssService 与 OssServiceImpl
java
void deleteFile(String ossKey);
java
@Override
public void deleteFile(String ossKey) {
ossClient.deleteObject(ossConfig.getBucketName(), ossKey);
}