Chat2Excel 文件服务模块剩余功能开发

一、文件列表功能的实现

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 和调用方完全不需要改动,符合"面向接口编程"和"开闭原则"。

  1. Page<FilesEntity> page --- 作为"查询参数"
java 复制代码
Page<FilesEntity> page = new Page<>(current, size);

此时它只有 currentsize 有值,其他字段(如 recordstotal)都是空的。它就像一个**"查询请求单"**,告诉 MyBatis-Plus:"请给我第 X 页,每页 Y 条数据"。

  1. 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 这个变量,存的是对象的引用(内存地址),而不是数据本身。

  1. responsePage --- 转换后的"结果对象"
java 复制代码
Page<FileInfoResponse> responsePage = new Page<>(current, size);
responsePage.setRecords(responseList);  // 设置转换后的数据
responsePage.setTotal(entityIPage.getTotal());  // 复制分页元信息

因为 entityIPagerecordsFilesEntity 类型,需要转换成 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);
    }
相关推荐
张小姐的猫1 小时前
【AI大模型接入SDK】 —— Ollama本地接入Deepseek
java·linux·开发语言·网络·c++·人工智能
谢亮_vipxieliang1 小时前
ValidX与Maven/Gradle集成配置指南
java·spring boot·spring·maven·hibernate
Nuanyt1 小时前
JUC常见核心知识梳理01 线程 并发 JMM volatile 管程 锁 synchronized
java·开发语言·网络·jvm
kakakahahahaha1 小时前
Windows DRIVER_IRQL_NOT_LESS_OR_EQUAL NETIO.SYS 蓝屏排查流程
windows·电脑·笔记本电脑·内容运营·软件需求
我命由我123451 小时前
Android 控件 - ListAdapter
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
en.en..1 小时前
深入解析 fork:一次调用,两次返回的核心原理
linux·运维·服务器
CodeStats1 小时前
【Java 类加载器】Java 类加载器完整体系深度拆解(中):URLClassLoader 能力剖析与继承委派辨析
java·jvm·classloader·类加载器
不爱学英文的码字机器1 小时前
让 Live2D 角色真正开始对话:Open-LLM-VTuber 从本地部署到远程访问
服务器·opencv·微信·电脑
Terra.K1 小时前
JAVA职业探索和学习----中间件开发目标
java·学习·中间件