package com.ruoyi.system.file; import com.ruoyi.common.config.LegacySystemConfig; import com.ruoyi.common.utils.http.HttpUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.net.HttpURLConnection; import java.net.URL; import java.util.*; import java.io.*; @Service public class FileUploadServiceImpl implements IFileUploadService { private static final Logger log = LoggerFactory.getLogger(FileUploadServiceImpl.class); @Autowired private LegacySystemConfig legacyConfig; @Override public FileUploadResponse uploadLocalFile(File file, String targetPath) { if (file == null || !file.exists()) { return FileUploadResponse.error("文件不存在"); } try (FileInputStream fis = new FileInputStream(file)) { return uploadInputStream(fis, file.getName(), targetPath); } catch (IOException e) { log.error("读取本地文件失败: {}", file.getAbsolutePath(), e); return FileUploadResponse.error("读取文件失败: " + e.getMessage()); } } @Override public FileUploadResponse uploadMultipartFile(MultipartFile multipartFile, String targetPath) { if (multipartFile == null || multipartFile.isEmpty()) { return FileUploadResponse.error("文件为空"); } try (InputStream inputStream = multipartFile.getInputStream()) { return uploadInputStream(inputStream, multipartFile.getOriginalFilename(), targetPath); } catch (IOException e) { log.error("读取MultipartFile失败", e); return FileUploadResponse.error("读取文件失败: " + e.getMessage()); } } @Override public FileUploadResponse uploadBytes(byte[] fileBytes, String fileName, String targetPath) { if (fileBytes == null || fileBytes.length == 0) { return FileUploadResponse.error("文件字节数组为空"); } try (ByteArrayInputStream bis = new ByteArrayInputStream(fileBytes)) { return uploadInputStream(bis, fileName, targetPath); } catch (IOException e) { log.error("处理字节数组失败", e); return FileUploadResponse.error("处理文件失败: " + e.getMessage()); } } @Override public FileUploadResponse uploadInputStream(InputStream inputStream, String fileName, String targetPath) { if (inputStream == null) { return FileUploadResponse.error("输入流为空"); } try { // 构建请求参数 Map params = new HashMap<>(); params.put("file", inputStream); params.put("uploadFileName", targetPath); log.info("开始上传文件到PHP接口: fileName={}, targetPath={}", fileName, targetPath); // 调用PHP上传接口 String response = HttpUtils.postFile(legacyConfig.getFileUploadUrl(), params, fileName); log.info("PHP接口响应: {}", response); // 解析响应 return parseUploadResponse(response); } catch (Exception e) { log.error("上传文件到PHP接口失败: fileName={}, targetPath={}", fileName, targetPath, e); return FileUploadResponse.error("上传失败: " + e.getMessage()); } } @Override public FileUploadResponse uploadFromUrl(String fileUrl, String targetPath) { if (fileUrl == null || fileUrl.trim().isEmpty()) { return FileUploadResponse.error("文件URL为空"); } try { // 从URL下载文件 byte[] fileBytes = downloadFromUrl(fileUrl); if (fileBytes == null || fileBytes.length == 0) { return FileUploadResponse.error("从URL下载文件失败"); } // 从URL中提取文件名 String fileName = extractFileNameFromUrl(fileUrl); // 上传文件 return uploadBytes(fileBytes, fileName, targetPath); } catch (Exception e) { log.error("从URL上传文件失败: fileUrl={}, targetPath={}", fileUrl, targetPath, e); return FileUploadResponse.error("从URL上传失败: " + e.getMessage()); } } @Override public FileUploadResponse uploadFromWechat(String accessToken, String mediaId, String targetPath) { if (accessToken == null || mediaId == null) { return FileUploadResponse.error("微信参数为空"); } try { // 从微信API下载文件 byte[] fileBytes = downloadFromWechat(accessToken, mediaId); if (fileBytes == null || fileBytes.length == 0) { return FileUploadResponse.error("从微信下载文件失败"); } // 生成文件名 String fileName = "wechat_" + mediaId + ".jpg"; // 上传文件 return uploadBytes(fileBytes, fileName, targetPath); } catch (Exception e) { log.error("从微信上传文件失败: mediaId={}, targetPath={}", mediaId, targetPath, e); return FileUploadResponse.error("从微信上传失败: " + e.getMessage()); } } @Override public boolean fileExists(String filePath) { if (filePath == null || filePath.trim().isEmpty()) { return false; } try { // 构建完整的文件路径 String fullPath = legacyConfig.getFileServerUrl() + "/" + filePath; File file = new File(fullPath); return file.exists() && file.isFile(); } catch (Exception e) { log.error("检查文件是否存在失败: {}", filePath, e); return false; } } @Override public boolean deleteFile(String filePath) { if (filePath == null || filePath.trim().isEmpty()) { return false; } try { // 构建完整的文件路径 String fullPath = legacyConfig.getFileServerUrl() + "/" + filePath; File file = new File(fullPath); if (file.exists() && file.isFile()) { return file.delete(); } return false; } catch (Exception e) { log.error("删除文件失败: {}", filePath, e); return false; } } @Override public String getFileUrl(String filePath) { if (filePath == null || filePath.trim().isEmpty()) { return null; } // 构建文件访问URL return legacyConfig.getFileServerUrl() + "/" + filePath; } /** * 从URL下载文件 */ private byte[] downloadFromUrl(String fileUrl) throws IOException { URL url = new URL(fileUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(30000); try (InputStream inputStream = connection.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } return outputStream.toByteArray(); } finally { connection.disconnect(); } } /** * 从微信API下载文件 */ private byte[] downloadFromWechat(String accessToken, String mediaId) throws IOException { String wechatUrl = String.format( "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s", accessToken, mediaId ); return downloadFromUrl(wechatUrl); } /** * 从URL中提取文件名 */ private String extractFileNameFromUrl(String fileUrl) { try { String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1); // 移除查询参数 if (fileName.contains("?")) { fileName = fileName.substring(0, fileName.indexOf('?')); } return fileName.isEmpty() ? "downloaded_file" : fileName; } catch (Exception e) { return "downloaded_file"; } } /** * 解析上传响应 */ private FileUploadResponse parseUploadResponse(String response) { if (response == null || response.trim().isEmpty()) { return FileUploadResponse.error("PHP接口返回空响应"); } try { log.info("开始解析PHP响应: {}", response); // 根据PHP接口的实际返回格式进行解析 // PHP返回格式: {"success": true, "message": "文件上传成功", "data": {...}} if (response.contains("\"success\":true") || response.contains("success")) { // 提取文件路径和缩略图路径 String filePath = extractFilePathFromResponse(response); String thumbnailPath = extractThumbnailPathFromResponse(response); log.info("解析结果 - filePath: {}, thumbnailPath: {}", filePath, thumbnailPath); // 创建响应对象 FileUploadResponse uploadResponse = FileUploadResponse.success(filePath, "上传成功"); if (thumbnailPath != null && !thumbnailPath.isEmpty()) { uploadResponse.setThumbnailPath(thumbnailPath); } return uploadResponse; } else { // 提取错误信息 String errorMessage = extractErrorMessageFromResponse(response); log.error("上传失败,错误信息: {}", errorMessage); return FileUploadResponse.error(errorMessage != null ? errorMessage : "上传失败"); } } catch (Exception e) { log.error("解析上传响应失败: {}", response, e); return FileUploadResponse.error("解析响应失败: " + e.getMessage()); } } /** * 从响应中提取文件路径 */ private String extractFilePathFromResponse(String response) { // 解析PHP接口返回的JSON格式: {"success": true, "data": {"filePath": "..."}} try { if (response.contains("\"data\":")) { // 找到data对象 int dataStart = response.indexOf("\"data\":{") + 7; if (dataStart > 6) { // 在data对象中查找filePath String dataSection = response.substring(dataStart); if (dataSection.contains("\"filePath\":")) { int start = dataSection.indexOf("\"filePath\":\"") + 12; int end = dataSection.indexOf("\"", start); if (start > 11 && end > start) { String filePath = dataSection.substring(start, end); // 去除转义字符 filePath = filePath.replace("\\", ""); log.info("提取到文件路径: {}", filePath); return filePath; } } } } // 兼容旧格式,直接查找filePath if (response.contains("\"filePath\":")) { int start = response.indexOf("\"filePath\":\"") + 12; int end = response.indexOf("\"", start); if (start > 11 && end > start) { String filePath = response.substring(start, end); log.info("提取到文件路径(旧格式): {}", filePath); return filePath; } } } catch (Exception e) { log.error("提取文件路径失败: {}", response, e); } return null; } /** * 从响应中提取缩略图路径 */ private String extractThumbnailPathFromResponse(String response) { // 解析PHP接口返回的JSON格式: {"success": true, "data": {"thumbnailPath": "..."}} try { if (response.contains("\"data\":")) { // 找到data对象 int dataStart = response.indexOf("\"data\":{") + 7; if (dataStart > 6) { // 在data对象中查找thumbnailPath String dataSection = response.substring(dataStart); if (dataSection.contains("\"thumbnailPath\":")) { int start = dataSection.indexOf("\"thumbnailPath\":\"") + 17; int end = dataSection.indexOf("\"", start); if (start > 16 && end > start) { String thumbnailPath = dataSection.substring(start, end); log.info("提取到缩略图路径: {}", thumbnailPath); thumbnailPath = thumbnailPath.replace("\\", ""); // 去除转义字符 return thumbnailPath; } } } } // 兼容旧格式,直接查找thumbnailPath if (response.contains("\"thumbnailPath\":")) { int start = response.indexOf("\"thumbnailPath\":\"") + 17; int end = response.indexOf("\"", start); if (start > 16 && end > start) { String thumbnailPath = response.substring(start, end); log.info("提取到缩略图路径(旧格式): {}", thumbnailPath); thumbnailPath = thumbnailPath.replace("\\", ""); // 去除转义字符 return thumbnailPath; } } } catch (Exception e) { log.error("提取缩略图路径失败: {}", response, e); } return null; } /** * 从响应中提取错误信息 */ private String extractErrorMessageFromResponse(String response) { // 解析PHP接口返回的JSON格式: {"success": false, "message": "错误信息"} try { if (response.contains("\"message\":")) { int start = response.indexOf("\"message\":\"") + 11; int end = response.indexOf("\"", start); if (start > 10 && end > start) { String errorMessage = response.substring(start, end); log.info("提取到错误信息: {}", errorMessage); return errorMessage; } } } catch (Exception e) { log.error("提取错误信息失败: {}", response, e); } return null; } /** * 生成缩略图 * * @param sourcePath 源图片路径 * @param targetPath 目标缩略图路径 * @param width 宽度 * @param height 高度(0表示按比例缩放) * @return 是否生成成功 */ private boolean createThumbnail(String sourcePath, String targetPath, int width, int height) { try { // 读取源图片 BufferedImage sourceImage = ImageIO.read(new File(sourcePath)); if (sourceImage == null) { log.error("无法读取源图片:{}", sourcePath); return false; } // 计算缩略图尺寸 int sourceWidth = sourceImage.getWidth(); int sourceHeight = sourceImage.getHeight(); if (height == 0) { height = (int) Math.floor((double) width / sourceWidth * sourceHeight); } else if (width == 0) { width = (int) Math.floor((double) height / sourceHeight * sourceWidth); } // 创建缩略图 BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = thumbnailImage.createGraphics(); // 设置渲染质量 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 绘制缩略图 g2d.drawImage(sourceImage, 0, 0, width, height, null); g2d.dispose(); // 保存缩略图 String extension = getFileExtension(sourcePath); return ImageIO.write(thumbnailImage, extension, new File(targetPath)); } catch (Exception e) { log.error("生成缩略图失败:sourcePath={}, targetPath={}", sourcePath, targetPath, e); return false; } } /** * 获取文件扩展名 */ private String getFileExtension(String filePath) { int lastDotIndex = filePath.lastIndexOf('.'); if (lastDotIndex > 0) { return filePath.substring(lastDotIndex + 1).toLowerCase(); } return "jpg"; // 默认扩展名 } /** * 本地文件上传(包含缩略图生成) */ public FileUploadResponse uploadLocalFileWithThumbnail(File file, String targetPath) { if (file == null || !file.exists()) { return FileUploadResponse.error("文件不存在"); } try { // 检查是否为图片文件 String fileName = file.getName().toLowerCase(); boolean isImage = fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png") || fileName.endsWith(".gif"); // 上传原文件 FileUploadResponse uploadResponse = uploadLocalFile(file, targetPath); if (!uploadResponse.isSuccess()) { return uploadResponse; } log.info("上传文件成功 文件:{} 缩略:{}",uploadResponse.getFilePath(),uploadResponse.getThumbnailPath()); return uploadResponse; } catch (Exception e) { log.error("上传文件并生成缩略图失败:{}", file.getAbsolutePath(), e); return FileUploadResponse.error("上传失败:" + e.getMessage()); } } /** * MultipartFile上传(包含缩略图生成) */ public FileUploadResponse uploadMultipartFileWithThumbnail(MultipartFile multipartFile, String targetPath) { if (multipartFile == null || multipartFile.isEmpty()) { return FileUploadResponse.error("文件为空"); } try { // 检查是否为图片文件 String originalFilename = multipartFile.getOriginalFilename(); if (originalFilename != null) { String fileName = originalFilename.toLowerCase(); boolean isImage = fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png") || fileName.endsWith(".gif"); // 上传原文件 FileUploadResponse uploadResponse = uploadMultipartFile(multipartFile, targetPath); if (!uploadResponse.isSuccess()) { return uploadResponse; } return uploadResponse; } return uploadMultipartFile(multipartFile, targetPath); } catch (Exception e) { log.error("上传MultipartFile并生成缩略图失败", e); return FileUploadResponse.error("上传失败:" + e.getMessage()); } } /** * 生成缩略图路径 */ private String generateThumbnailPath(String originalPath) { if (originalPath == null || originalPath.isEmpty()) { return null; } // 在文件名前添加 "s_" 前缀 int lastSlashIndex = originalPath.lastIndexOf('/'); if (lastSlashIndex >= 0) { String directory = originalPath.substring(0, lastSlashIndex + 1); String fileName = originalPath.substring(lastSlashIndex + 1); return directory + "s_" + fileName; } else { return "s_" + originalPath; } } }