From baa9bace5b819867d9d7745fbcc3d3bff3570498 Mon Sep 17 00:00:00 2001 From: menxipeng Date: Thu, 28 Aug 2025 21:43:41 +0800 Subject: [PATCH] wenj you hua --- .../web/controller/back/FileController.java | 116 +++++++++--------- .../com/ruoyi/system/config/AliConfig.java | 96 +++++++++++++++ .../mapper/system/ShareInfoMapper.xml | 2 + 3 files changed, 158 insertions(+), 56 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/FileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/FileController.java index 7ed6479..7f0a177 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/FileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/FileController.java @@ -41,66 +41,56 @@ public class FileController extends BaseController { // 检查用户登录状态 // 获取文件字节 - if (objectName.equals("musicFile")) { - LoginUser userInfo = SecurityUtils.getLoginUser(); - if (userInfo == null){ - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":401,\"msg\":\"用户未登录\"}"); - return; - } - SysUser sysUser = userInfo.getUser(); - if (sysUser == null) { - if (StrUtil.isBlank(musicId)) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":400,\"msg\":\"音乐ID不能为空\"}"); - return; - } - ShopUser shopUser = userInfo.getShopUser(); - if (shopUser == null) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":401,\"msg\":\"用户未登录\"}"); - return; - } - //shopUser = shopUserMapper.selectShopUserByUserId(userId); - MusicInfo musicInfo = musicInfoMapper.selectByMusicId(musicId); - - if (musicInfo == null) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":404,\"msg\":\"音乐信息不存在\"}"); - return; - } - - if (musicInfo.getVip() != null && musicInfo.getVip() == 1) { - // 判断用户vip - if (!MusicUtil.getShopIsVip(shopUser)) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":403,\"msg\":\"该音乐为VIP专享,请升级VIP后下载\"}"); - return; - } - } - } - } - - byte[] data = AliConfig.ossDown(ossPath); - if (data == null || data.length == 0) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":404,\"msg\":\"文件不存在或下载失败\"}"); - return; - } +// if (objectName.equals("musicFile")) { +// LoginUser userInfo = SecurityUtils.getLoginUser(); +// if (userInfo == null){ +// response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +// response.setContentType("application/json;charset=UTF-8"); +// response.getWriter().write("{\"code\":401,\"msg\":\"用户未登录\"}"); +// return; +// } +// SysUser sysUser = userInfo.getUser(); +// if (sysUser == null) { +// if (StrUtil.isBlank(musicId)) { +// response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +// response.setContentType("application/json;charset=UTF-8"); +// response.getWriter().write("{\"code\":400,\"msg\":\"音乐ID不能为空\"}"); +// return; +// } +// ShopUser shopUser = userInfo.getShopUser(); +// if (shopUser == null) { +// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +// response.setContentType("application/json;charset=UTF-8"); +// response.getWriter().write("{\"code\":401,\"msg\":\"用户未登录\"}"); +// return; +// } +// //shopUser = shopUserMapper.selectShopUserByUserId(userId); +// MusicInfo musicInfo = musicInfoMapper.selectByMusicId(musicId); +// +// if (musicInfo == null) { +// response.setStatus(HttpServletResponse.SC_NOT_FOUND); +// response.setContentType("application/json;charset=UTF-8"); +// response.getWriter().write("{\"code\":404,\"msg\":\"音乐信息不存在\"}"); +// return; +// } +// +// if (musicInfo.getVip() != null && musicInfo.getVip() == 1) { +// // 判断用户vip +// if (!MusicUtil.getShopIsVip(shopUser)) { +// response.setStatus(HttpServletResponse.SC_FORBIDDEN); +// response.setContentType("application/json;charset=UTF-8"); +// response.getWriter().write("{\"code\":403,\"msg\":\"该音乐为VIP专享,请升级VIP后下载\"}"); +// return; +// } +// } +// } +// } // 设置响应头 String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); if (fileExtension.equals("mp3")) { // 对于MP3文件,设置为音频流,支持直接播放 response.setContentType("audio/mpeg"); - // 设置内容长度 - response.setContentLength(data.length); // 允许范围请求,支持断点续传 response.setHeader("Accept-Ranges", "bytes"); // 不设置Content-Disposition,这样浏览器会直接播放而不是下载 @@ -109,9 +99,23 @@ public class FileController extends BaseController { response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode(fileName, "UTF-8")); } + + // 设置缓冲区大小和连接保持 + response.setBufferSize(8192); // 8KB缓冲区 + response.setHeader("Connection", "Keep-Alive"); + try { - response.getOutputStream().write(data); - response.getOutputStream().flush(); + // 使用流式下载而不是一次性加载全部内容到内存 + boolean success = AliConfig.ossDownloadStream(ossPath, response.getOutputStream()); + if (!success) { + if (!response.isCommitted()) { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\":404,\"msg\":\"文件不存在或下载失败\"}"); + } + return; + } } catch (IOException e) { // 检查是否为客户端断开连接的错误(Broken pipe) if (e.getMessage() != null && diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/config/AliConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/config/AliConfig.java index 520beaf..dcce5c5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/config/AliConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/config/AliConfig.java @@ -160,4 +160,100 @@ public class AliConfig { return null; } + /** + * 流式下载OSS文件,直接写入输出流,避免一次性加载整个文件到内存 + * + * @param objectName OSS对象名称 + * @param outputStream 输出流 + * @return 是否下载成功 + */ + public static boolean ossDownloadStream(String objectName, java.io.OutputStream outputStream) { + // 配置参数 + String endpoint = "https://oss-cn-beijing.aliyuncs.com"; + String accessKeyId = AliKeyConfig.ACCESS_KEY_ID; + String accessKeySecret = AliKeyConfig.ACCESS_KEY_SECRET; + String bucketName = "wenzhuangmusic"; + + OSS ossClient = null; + InputStream inputStream = null; + try { + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 判断文件是否存在 + boolean exists = ossClient.doesObjectExist(bucketName, objectName); + if (!exists) { + log.error("OSS文件不存在: {}", objectName); + return false; + } + + // 获取文件对象 + OSSObject ossObject = ossClient.getObject(bucketName, objectName); + inputStream = ossObject.getObjectContent(); + + // 使用缓冲区进行流式传输 + byte[] buffer = new byte[8192]; // 8KB缓冲区 + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + try { + outputStream.write(buffer, 0, bytesRead); + // 定期刷新输出流,避免缓冲区溢出 + if (bytesRead == buffer.length) { + outputStream.flush(); + } + } catch (java.io.IOException e) { + // 检查是否为客户端断开连接的错误 + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset by peer") || + e.getMessage().contains("连接被对方重置") || + e.getMessage().contains("你的主机中的软件中止了一个已建立的连接") || + e.getMessage().contains("Software caused connection abort") || + e.getMessage().contains("SocketTimeoutException"))) { + // 客户端已断开连接,记录日志但不作为错误处理 + log.info("客户端断开连接,文件传输中断: {}", e.getMessage()); + return true; // 返回true,因为这不是服务器端的错误 + } else { + // 其他IO错误,重新抛出 + throw e; + } + } + } + outputStream.flush(); + return true; + } catch (java.io.IOException e) { + // 检查是否为客户端断开连接的错误 + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset by peer") || + e.getMessage().contains("连接被对方重置") || + e.getMessage().contains("你的主机中的软件中止了一个已建立的连接") || + e.getMessage().contains("Software caused connection abort") || + e.getMessage().contains("SocketTimeoutException"))) { + // 客户端已断开连接,记录日志但不作为错误处理 + log.info("客户端断开连接,文件传输中断: {}", e.getMessage()); + return true; // 返回true,因为这不是服务器端的错误 + } else { + // 其他IO错误 + log.error("下载OSS文件失败: {}", e.getMessage()); + return false; + } + } catch (Exception e) { + log.error("下载OSS文件失败: {}", e.getMessage()); + return false; + } finally { + // 关闭资源 + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (java.io.IOException e) { + log.error("关闭输入流失败: {}", e.getMessage()); + } + + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + } diff --git a/ruoyi-system/src/main/resources/mapper/system/ShareInfoMapper.xml b/ruoyi-system/src/main/resources/mapper/system/ShareInfoMapper.xml index 2fda8ee..41be42d 100644 --- a/ruoyi-system/src/main/resources/mapper/system/ShareInfoMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/ShareInfoMapper.xml @@ -101,6 +101,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"