阿里云 OSS
创建配置类
使用以下方法,创建 OSS 的内网或外网客户端, 并将其统一注册到 spring 里.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Configuration @Slf4j public class OssConfig {
@Value("${aliyun.oss.endpoint:}") private String endpoint;
@Value("${aliyun.oss.publicEndpoint:}") private String publicEndpoint;
@Value("${aliyun.oss.accessKeyId:}") private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret:}") private String accessKeySecret;
@Bean("internalOssClient") @Primary public OSS internalOssClient() { log.info("创建内网OSS客户端Bean: endpoint={}", endpoint); return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); }
@Bean("publicOssClient") public OSS publicOssClient() { String publicEndpointToUse = StringUtils.hasText(publicEndpoint) ? publicEndpoint : endpoint; log.info("创建外网OSS客户端Bean: endpoint={}", publicEndpointToUse); return new OSSClientBuilder().build(publicEndpointToUse, accessKeyId, accessKeySecret); }
@PreDestroy public void cleanup() { log.info("OSS客户端资源清理完成"); } }
|
一次传输
上传文件
上传文件时的入参为 MultipartFile 文件对象, 传输路径 path. 步骤如下
参数校验
1 2 3 4
| if (file == null || file.isEmpty())
if (!StringUtils.hasText(path))
|
生成对象唯一键
代码处的规则是 path/yyyyMMdd/uuid_原始文件名
1 2 3
| String dateFolder = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String uuid = UUID.randomUUID().toString().replace("-", ""); String key = String.format("%s/%s/%s_%s", path, dateFolder, uuid, file.getOriginalFilename());
|
执行上传
1 2 3 4 5 6 7 8 9
| try (InputStream inputStream = file.getInputStream()) { PutObjectRequest putObjectRequest = new PutObjectRequest( bucketName, key, inputStream ); ossClient.putObject(putObjectRequest); }
|
获取文件 url
1 2
| fileUrl = url + "/api/oss/url/get?objectKey=" + key; log.info("文件上传成功: objectKey={}, fileUrl={}", key, fileUrl);
|
关闭资源
无论如何,在打开 OSS 配置类后,都要注意关闭资源。但如果注册在 Bean 中,只需要打相关注释就好了。
1 2 3 4 5
| finally { if (ossClient != null) { ossClient.shutdown(); } }
|
下载文件
入参为文件的 key 和响应体。步骤如下
检查文件是否存在
1 2 3 4 5 6
| if (!ossClient.doesObjectExist(bucketName, objectKey)) { log.warn("文件不存在: objectKey={}", objectKey); response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.getWriter().write("文件不存在"); return; }
|
获取 OSS 文件流
1
| OSSObject ossObject = ossClient.getObject(bucketName, objectKey);
|
写入响应流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| String fileName = objectKey.substring(objectKey.lastIndexOf("/") + 1); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
try (InputStream inputStream = ossObject.getObjectContent(); OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } log.info("文件下载成功: objectKey={}", objectKey);
|
通过文件 key 获取临时访问 url (带签名, 1 小时过期)
客户端配置为: 开启了 CNAME, 使用 V4 签名, 用完关闭客户端.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public String getFileUrl(String objectKey) { OSS ossClient = createPublicOssClient();
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration(); clientBuilderConfiguration.setSupportCname(true); clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
try { Date expiration = new Date(new Date().getTime() + 3600 * 1000L); GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey, HttpMethod.GET); request.setExpiration(expiration);
URL signedUrl = ossClient.generatePresignedUrl(request); return signedUrl.toString(); } catch (Exception e) { log.error("获取 URL 数据异常", e); } finally { if (ossClient != null) { ossClient.shutdown(); } } return null; }
|
断点重传
断点重传的核心在于给文件分片,并提供相应的合并和提示机制。
系统
系统属性信息
通过 System.getProperties() 获取操作系统, JVM, 用户等基础环境信息. 直接通过键名取值即可.
1 2 3 4 5 6 7 8 9
| Properties props = System.getProperties();
System.out.println("操作系统名称: " + props.getProperty("os.name")); System.out.println("操作系统版本: " + props.getProperty("os.version")); System.out.println("操作系统架构: " + props.getProperty("os.arch")); System.out.println("Java版本: " + props.getProperty("java.version")); System.out.println("Java安装路径: " + props.getProperty("java.home")); System.out.println("用户名称: " + props.getProperty("user.name")); System.out.println("用户工作目录: " + props.getProperty("user.dir"));
|
运行时信息
通过 Runtime 获取 JVM 内存使用, CPU 核心数等运行时信息(内存、CPU 等)
1 2 3 4 5 6
| Runtime runtime = Runtime.getRuntime(); long totalMemory = runtime.totalMemory() / 1024 / 1024; long maxMemory = runtime.maxMemory() / 1024 / 1024; long freeMemory = runtime.freeMemory() / 1024 / 1024; long usedMemory = totalMemory - freeMemory; int processors = runtime.availableProcessors();
|
执行系统命令
一般使用 Runtime.getRuntime().exec(command) 函数执行系统命令, 并使用一个 Precess 对象来接收执行的结果.
以 ping 命令为例。
1 2
| String command = "ping www.baidu.com"; Process process = Runtime.getRuntime().exec(command);
|
然后通过这个 Process 对象,我们可以
- 获取该进程的输出流
- 获取该进程的错误流
- 等待进程执行完成
- 获取进程的退出码
输出流和错误流均为 InputStream,注意一定要及时读取,否则新进程的输出缓冲区可能会被占满,从而导致进程阻塞, 直接卡死, 无法结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line; while ((line = reader.readLine()) != null) { }
String errorLine; while ((errorLine = errorReader.readLine()) != null) { }
|
其他两种操作为
1 2
| process.waitFor(); int exitCode = process.exitValue();
|
正则表达式匹配
Java 中的正则必须先编译为 NFA/DFA 结构才能匹配, 若单纯使用 String.matches() 方法, 则每次调用都需要重新编译, 在大量匹配的场景下性能极差. 因此推荐提前编译 Patten 并复用.
1 2 3 4 5
| final String hz = "(?<=,)[0-9]+(?=Hz)"; final String bit_rate = "(?<=,)[0-9]+(?=kb)"; Pattern bit_rate_pattern = Pattern.compile(bit_rate); Pattern hz_pattern = Pattern.compile(hz);
|
各个 pattern 就是已经编译好的结果了,然后再直接匹配即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| String errorLine; while ((errorLine = errorReader.readLine()) != null) { errorLine = errorLine.replace(" ", ""); if (errorLine.contains("mono")) { map.put(STR_CHANNELS, 1); } Matcher bit_matcher = bit_rate_pattern.matcher(errorLine); if (bit_matcher.find()) { map.put(STR_BIT, bit_matcher.group()); } Matcher hz_matcher = hz_pattern.matcher(errorLine); if (hz_matcher.find()) { map.put(STR_HZ, hz_matcher.group()); } }
|
FFmepg
FFmpeg 通过命令行调用,整个调用过程及其考验其命令拼接能力
转换文件格式
以 .mp3 转换为 wav 文件为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static String convertMp3ToWav(String tempPath, String fileName, boolean isDelFile) { String name1 = fileName.split(".mp3")[0]; String from = tempPath + File.separator + fileName; String to = tempPath + File.separator + name1 + ".wav"; try { String command = ffmpegPath + File.separator + "ffmpeg -i " + from + " " + to; log.info("Executing command: {}", command); Process proc = Runtime.getRuntime().exec(command); proc.waitFor(); if (isDelFile) { FileUtil.del(new File(from)); } } catch (Exception ex) { log.error("Error converting MP3 to WAV: {}", fileName, ex); } return to; }
|
从视频文件提取中音频
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static String decodeVideo(File videoFile, String outPath) { try { if (!videoFile.exists()) { return null; } String fileName = videoFile.getName(); String name = fileName.substring(0, fileName.lastIndexOf(".")); String outFile = outPath + name + "_" + DateUtil.current() + ".wav"; String command = ffmpegPath + File.separator + "ffmpeg -i " + videoFile.getAbsolutePath() + " -c:a pcm_s16le " + outFile; Process proc = Runtime.getRuntime().exec(command); handleProcessOutput(proc); proc.waitFor(); return outFile;
} catch (Exception e) { log.error("视频音频提取失败", e); return null; } }
|
从视频中截取图片帧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static String extractFrames(File videoFile, String outPath) { try { if (!videoFile.exists()) { return null; } String outFile = outPath + "%05d.png"; String command = ffmpegPath + File.separator + "ffmpeg -i " + videoFile.getAbsolutePath() + " -r 1/5 " + outFile; Process proc = Runtime.getRuntime().exec(command); handleProcessOutput(proc); proc.waitFor(); return outPath; } catch (Exception e) { log.error("视频截图失败", e); return null; } }
|
剪辑视频
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static boolean cropVideo(String inputVideoPath, String outputVideoPath, int x, int y, int width, int height) { try { String[] command = { ffmpegPath + File.separator + "ffmpeg", "-i", inputVideoPath, "-filter:v", "crop=" + width + ":" + height + ":" + x + ":" + y, "-c:a", "copy", "-y", outputVideoPath };
Process process = new ProcessBuilder(command).start(); handleProcessOutput(process); int exitCode = process.waitFor(); return exitCode == 0; } catch (Exception e) { log.error("视频裁剪失败: {}", e.getMessage(), e); return false; } }
|