重构我的小屎山代码

2024 年 12 月 5 日 星期四
/ , , ,
26

重构我的小屎山代码

我的屎山代码

在写tgDrive的初期,总是觉得怎么简单怎么来,结果就是后期的维护难受的一批。

请好好地,看着我的屎山(请勿展开,展开辣眼睛):

//TODO: need refraction
    @Override
    public ResponseEntity<StreamingResponseBody> downloadFile(String fileID) {
        try {
            // 从 botService 获取文件的下载路径和文件名
            String fileUrl = botService.getFullDownloadPath(fileID);
            String filename = fileMapper.getFileNameByFileId(fileID);
            Long fullSize = fileMapper.getFullSizeByFileId(fileID);
            if (filename == null) {
                filename = botService.getFileNameByID(fileID);
            }
            // 上传到tg的gif会被转换为MP4
            if (filename.endsWith(".gif")) {
                filename = filename.substring(0, filename.length() - 4) + ".mp4";
            }
            if (filename == null || filename.isEmpty()) {
                filename = botService.getFileNameByID(fileID);
            }

            // 设置响应头
            HttpHeaders headers = setHeaders(filename, fullSize);

            // 创建 OkHttp请求
            Request request = new Request.Builder()
                    .url(fileUrl)
                    .get()
                    .build();

            // 执行请求
            Call call = okHttpClient.newCall(request);
            Response response = call.execute();

            // 请求失败
            if (!response.isSuccessful()) {
                log.error("无法下载文件,响应码:" + response.code());
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
            }

            ResponseBody responseBody = response.body();
            if (responseBody == null) {
                log.error("响应体为空");
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
            }

            InputStream inputStream = responseBody.byteStream();
            // 尝试解析为 BigFileInfo 并检查 isRecordFile 标识
            boolean isRecordFile = false;
            BigFileInfo record = null;
            try {
                String fileContent = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
                record = JSON.parseObject(fileContent, BigFileInfo.class);
                isRecordFile = record.isRecordFile();
                // 重新获取输入流,因为 readAllBytes 已经读取了流
                responseBody.close();
            } catch (Exception e) {
                log.info("文件不是 BigFileInfo类型,作为普通文件处理");
            }

            if (isRecordFile && record != null) {
                log.info("文件名为:" + record.getFileName());
                log.info("检测到记录文件,开始下载并合并分片文件...");

                List<String> partFileIds = record.getFileIds();

                StreamingResponseBody streamingResponseBody = outputStream -> {
                    int maxConcurrentDownloads = 3; // 最大并发下载数
                    ExecutorService executorService = Executors.newFixedThreadPool(maxConcurrentDownloads);

                    // 使用列表存储 PipedInputStream,索引对应分片顺序
                    List<PipedInputStream> pipedInputStreams = new ArrayList<>(partFileIds.size());
                    CountDownLatch latch = new CountDownLatch(partFileIds.size());

                    try {
                        // 初始化 PipedInputStream 列表
                        for (int i = 0; i < partFileIds.size(); i++) {
                            pipedInputStreams.add(new PipedInputStream());
                        }

                        // 提交下载任务
                        //TODO: 多次尝试下载
                        for (int i = 0; i < partFileIds.size(); i++) {
                            final int index = i;
                            final String partFileId = partFileIds.get(i);
                            final PipedInputStream pipedInputStream = pipedInputStreams.get(index);
                            final PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
                            executorService.submit(() -> {
                                try (InputStream partInputStream = downloadFileByte(partFileId).byteStream();
                                     OutputStream pos = pipedOutputStream) {
                                    byte[] buffer = new byte[8192];
                                    int bytesRead;
                                    while ((bytesRead = partInputStream.read(buffer)) != -1) {
                                        pos.write(buffer, 0, bytesRead);
                                        pos.flush();
                                    }
                                } catch (IOException e) {
                                    log.error("分片文件下载失败:{}", partFileId, e);
                                    // 下载失败,关闭流
                                    try {
                                        pipedOutputStream.close();
                                    } catch (IOException ex) {
                                        log.error("关闭 PipedOutputStream 失败", ex);
                                    }
                                } finally {
                                    latch.countDown();
                                }
                            });
                        }

                        // 按顺序读取并写入输出流
                        for (int i = 0; i < partFileIds.size(); i++) {
                            try (InputStream pis = pipedInputStreams.get(i)) {
                                byte[] buffer = new byte[8192];
                                int bytesRead;
                                while ((bytesRead = pis.read(buffer)) != -1) {
                                    outputStream.write(buffer, 0, bytesRead);
                                    outputStream.flush();
                                }
                            } catch (IOException e) {
                                String message = e.getMessage();
                                if (message != null && (message.contains("An established connection was aborted") || message.contains("你的主机中的软件中止了一个已建立的连接"))) {
                                    log.info("客户端中止了连接:{}", message);
                                    return;
                                } else {
                                    log.error("读取 PipedInputStream 失败", e);
                                    throw new RuntimeException(e);
                                }
                            }
                        }

                        latch.await();
                    } catch (Exception e) {
                        log.error("文件下载终止:{}", e.getMessage(), e);
                    } finally {
                        executorService.shutdown();
                    }
                };

                return ResponseEntity.ok()
                        .headers(headers)
                        .contentType(MediaType.parseMediaType(getContentTypeFromFilename(filename)))
                        .body(streamingResponseBody);
            } else {
                // 解析失败,说明这是普通文件,直接返回文件内容
                log.info("文件不是记录文件,直接下载文件...");
                StreamingResponseBody streamingResponseBody = outputStream -> {
                    try (InputStream is = downloadFileByte(fileID).byteStream()) {
                        byte[] buffer = new byte[4096];
                        int byteRead;
                        while ((byteRead = is.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, byteRead);
                        }
                    } catch (IOException e) {
                        String message = e.getMessage();
                        if (message != null && (message.contains("An established connection was aborted") || message.contains("你的主机中的软件中止了一个已建立的连接"))) {
                            // 客户端中止了连接,记录信息级别的日志或忽略
                            log.info("客户端中止了连接:{}", message);
                        } else {
                            // 其他 IOException,可能需要进一步处理
                            log.error("写入输出流时发生 IOException", e);
                            throw new RuntimeException(e);
                        }
                    } catch (Exception e) {
                        log.info("文件下载终止");
                        log.info(e.getMessage(), e);
                    }
                };

                return ResponseEntity.ok()
                        .headers(headers)
                        .contentType(MediaType.parseMediaType(getContentTypeFromFilename(filename)))
                        .body(streamingResponseBody);
            }
        } catch (Exception e) {
            log.error("下载文件失败:" + e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }
    }

一个方法,写了近200行,可读性、可维护性差的一批,每次看到这个方法,我就头大,特别是涉及到这个方法的修改时,一看人都麻了,一点改的动力都没有,最后,我还是决定对这份屎山动刀子。

如何重构?

捋顺结构,分步进行

对一个非常长的方法进行重构,首先就要搞懂这个方法的每一步都在干什么,把每一步想清楚后,把这每一步都拆分成一个独立的方法,再在原来的方法调用这些每一步的方法,就能完成重构了。

拿我的这份史码为例,主要分为3步

  • 根据fileID下载文件并获取inputstream
  • 尝试将inputstream解析为BigFileInfo
  • 根据是否为record来选择下载方式

依照分步,抽象方法

分好了步后,就是根据分步来写函数了,例如,①根据fileID下载文件并获取inputstream,就可以把原代码中的这一部分的内容抽象出来,形成一个新的方法:

private InputStream downloadFileInputStream(String fileID) throws IOException {
        File file = botService.getFile(fileID);
        String fileUrl = botService.getFullDownloadPath(file);

        Request request = new Request.Builder()
                .url(fileUrl)
                .get()
                .build();

        Response response = okHttpClient.newCall(request).execute();

        if (!response.isSuccessful()) {
            log.error("无法下载文件,响应码:" + response.code());
            throw new IOException("无法下载文件,响应码:" + response.code());
        }

        ResponseBody responseBody = response.body();
        if (responseBody == null) {
            log.error("响应体为空");
            throw new IOException("响应体为空");
        }

        return responseBody.byteStream();
    }

接下来的每一步,都照此步骤进行,就能完成原先的代码的“瘦身”了:

public ResponseEntity<StreamingResponseBody> downloadFile(String fileID) {
        try {
            // Step 1: 根据`fileID`下载文件并获取`inputstream`
            InputStream inputStream = downloadFileInputStream(fileID);

            // Step 2: 尝试将`inputstream`解析为`BigFileInfo`
            BigFileInfo record = parseBigFileInfo(inputStream);

            // Step 3: 根据是否为`record`来选择下载方式
            if (record != null && record.isRecordFile()) {
                return handleRecordFile(fileID, record);
            } else {
                return handleRegularFile(fileID, inputStream);
            }
        } catch (Exception e) {
            log.error("下载文件失败:" + e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }
    }

这对比一开始的代码,简直是赏心悦目。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...