上传图片接口
接口接收图片文件和布尔类型的是否生成缩略图参数。
- 生成保存图片文件的文件夹,文件夹的命名为上传图片的日期“根目录\file\cover\202504”,如果文件夹已存在则不生成。
- 接下来拼接文件名,生成30位的随机数拼接到原文件名防止文件名相同的情况,保存图片到目标目录。
- 通过第二个参数判断是否需要生成缩略图,如果需要则执行createThumbnail(String filePath)
(下面会介绍这个函数)。 - 最后返回保存图片的文件路径。
@RequestMapping("/uploadImage")
public ResponseVO uploadImage(@NotNull MultipartFile file,@NotNull Boolean thumbnail) throws IOException, BusinessException {
String month = DateUtils.format(new Date(), DateTimePatternEnum.YYYYMM.getPattern());
// 根目录/file/cover/日期
String folder = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_COVER + month;
File folderFile = new File(folder);
if(!folderFile.exists()){
folderFile.mkdirs();
}
String fileName = file.getOriginalFilename();
String fileSuffix = StringTools.getFileSuffix(fileName);
String realFileName = StringTools.getRandomNumber(30) + fileSuffix;
String filePath = folder + "/" + realFileName;
file.transferTo(new File(filePath));
if(thumbnail){
fFmpegUtils.createThumbnail(filePath);
}
// cover/日期/realFileName
return getSuccessResponseVO(Constants.FILE_COVER + month + "/" + realFileName);
}
createThumbnail(String filePath) 负责拼接ffmpeg指令,把指令丢给 executeCommand(String cmd, Boolean showLog) 函数执行命令。
public void createThumbnail(String filePath) throws BusinessException {
String CMD = "ffmpeg -i \"%s\" -vf scale=200:-1 \"%s\" ";
CMD = String.format(CMD,filePath,filePath+ Constants.IMAGE_THUMBNAIL_SUFFIX);
ProcessUtils.executeCommand(CMD,false);
}
executeCommand 函数传入指令和是否打印日志这两个参数:
- 判断当前系统类型是Windows还是Linux,Windows:直接执行命令,Linux:用 sh -c 包装命令。
- 异步读取输出流和错误流,必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住。
- 等待ffmpeg命令执行完,拼接输出的结果,如果需要打印日志则输出。
- 在finally里摧毁FFmpeg进程。
ublic class ProcessUtils {
private static final Logger logger = LoggerFactory.getLogger(ProcessUtils.class);
private static final String osName = System.getProperty("os.name").toLowerCase();
public static String executeCommand(String cmd, Boolean showLog) throws BusinessException {
if (StringTools.isEmpty(cmd)) {
return null;
}
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
//判断操作系统
if (osName.contains("win")) {
process = Runtime.getRuntime().exec(cmd);
} else {
process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});
}
// 取出输出流和错误流的信息
PrintStream errorStream = new PrintStream(process.getErrorStream());
PrintStream inputStream = new PrintStream(process.getInputStream());
errorStream.start();
inputStream.start();
// 等待ffmpeg命令执行完
process.waitFor();
// 获取执行结果字符串
String result = errorStream.stringBuffer.append(inputStream.stringBuffer + "\n").toString();
// 输出执行的命令信息
if (showLog) {
logger.info("执行命令{}结果{}", cmd, result);
}
return result;
} catch (Exception e) {
logger.error("执行命令失败cmd{}失败:{} ", cmd, e.getMessage());
throw new BusinessException("视频转换失败");
} finally {
if (null != process) {
ProcessKiller ffmpegKiller = new ProcessKiller(process);
runtime.addShutdownHook(ffmpegKiller);
}
}
}
/**
* 在程序退出前结束已有的FFmpeg进程
*/
private static class ProcessKiller extends Thread {
private Process process;
public ProcessKiller(Process process) {
this.process = process;
}
@Override
public void run() {
this.process.destroy();
}
}
/**
* 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
*/
static class PrintStream extends Thread {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
StringBuffer stringBuffer = new StringBuffer();
public PrintStream(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public void run() {
try {
if (null == inputStream) {
return;
}
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
} catch (Exception e) {
logger.error("读取输入流出错了!错误信息:" + e.getMessage());
} finally {
try {
if (null != bufferedReader) {
bufferedReader.close();
}
if (null != inputStream) {
inputStream.close();
}
} catch (IOException e) {
logger.error("调用PrintStream读取输出流后,关闭流时出错!");
}
}
}
}
}
运行结果
获取图片接口
传入图片路径参数。
- 判断路径是否为空或者路径格式不正确。
- 设置响应类型,设置缓存策略。
- 从本地磁盘中读取文件并写入到 response 输出流中。
- 关闭文件流和输出流。
@RequestMapping("/getResource")
public void getResource(HttpServletResponse response,@NotNull String sourceName) throws BusinessException, IOException {
if(!StringTools.pathIsOk(sourceName)){
throw new BusinessException(ResponseCodeEnum.CODE_600);
}
String suffix = StringTools.getFileSuffix(sourceName);
response.setContentType("image/"+suffix.replace(".",""));
response.setHeader("Cache-Control","max-age=2592000");
readFile(response,sourceName);
}
// filePath = cover/23001/realFileName
protected void readFile(HttpServletResponse response,String filePath) throws IOException {
// 根目录/file/cover/23001/realFileName
File file = new File(appConfig.getProjectFolder() + Constants.FILE_FOLDER + filePath);
if(!file.exists()){
return;
}
try(OutputStream out = response.getOutputStream(); FileInputStream in = new FileInputStream(file)){
byte[] byteData = new byte[1024];
int len = 0;
while ((len = in.read(byteData)) != -1) {
out.write(byteData, 0, len);
}
out.flush();
}catch (Exception e){
log.error("读取文件异常",e);
}
}