Răsfoiți Sursa

视频上传

xiongzhu 5 ani în urmă
părinte
comite
581987a425

+ 6 - 0
pom.xml

@@ -272,6 +272,12 @@
             <artifactId>hutool-all</artifactId>
             <version>5.3.0</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv-platform</artifactId>
+            <version>1.5.4</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 69 - 6
src/main/java/com/izouma/jiashanxia/web/FileUploadController.java

@@ -5,6 +5,10 @@ import com.izouma.jiashanxia.service.storage.StorageService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.Java2DFrameConverter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,13 +16,15 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
 import java.net.URLConnection;
 import java.text.SimpleDateFormat;
 import java.util.Base64;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 
 @RestController
@@ -39,8 +45,8 @@ public class FileUploadController {
             } catch (Exception ignored) {
             }
             path = basePath + "/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8)
-                   + "." + FilenameUtils.getExtension(file.getOriginalFilename());
+                    + RandomStringUtils.randomAlphabetic(8)
+                    + "." + FilenameUtils.getExtension(file.getOriginalFilename());
         }
         InputStream is;
         try {
@@ -66,7 +72,7 @@ public class FileUploadController {
             } catch (Exception ignored) {
             }
             path = "image/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8) + ext;
+                    + RandomStringUtils.randomAlphabetic(8) + ext;
         }
         InputStream is;
         try {
@@ -77,4 +83,61 @@ public class FileUploadController {
         }
         return storageService.uploadFromInputStream(is, path);
     }
+
+    @PostMapping("/video")
+    public Map<String, String> uploadVideo(@RequestParam("file") MultipartFile file) {
+        String basePath = "application";
+        try {
+            basePath = file.getContentType().split("/")[0];
+        } catch (Exception ignored) {
+        }
+        String videoPath = basePath + "/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                + RandomStringUtils.randomAlphabetic(8)
+                + "." + FilenameUtils.getExtension(file.getOriginalFilename());
+
+        InputStream originInputstream;
+        try {
+            originInputstream = file.getInputStream();
+            if (StringUtils.isEmpty(file.getContentType()) || !file.getContentType().startsWith("video")) {
+                throw new BusinessException(file.getContentType() + " 非视频文件");
+            }
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            byte[] buffer = new byte[1024];
+            int len;
+            while ((len = originInputstream.read(buffer)) > -1) {
+                baos.write(buffer, 0, len);
+            }
+            baos.flush();
+
+            InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
+            String videoUrl = storageService.uploadFromInputStream(is1, videoPath);
+
+            InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
+
+            FFmpegFrameGrabber g = new FFmpegFrameGrabber(is2);
+            g.start();
+
+            Frame frame = g.grabKeyFrame();
+            Java2DFrameConverter converter = new Java2DFrameConverter();
+            BufferedImage image = converter.convert(frame);
+            ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+            ImageIO.write(image, "jpeg", baos1);
+            String posterPath = String.format("application/image/%s.jpg",
+                    new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                            + RandomStringUtils.randomAlphabetic(8));
+            String posterUrl = storageService
+                    .uploadFromInputStream(new ByteArrayInputStream(baos1.toByteArray()), posterPath);
+            g.stop();
+
+            return new HashMap<String, String>() {{
+                put("src", videoUrl);
+                put("poster", posterUrl);
+            }};
+        } catch (IOException e) {
+            log.error("上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
 }

+ 126 - 0
src/main/vue/src/components/VideoUpload.vue

@@ -0,0 +1,126 @@
+<template>
+    <div v-if="value && value.src" class="video-wrapper">
+        <video :src="value.src" :poster="value.poster" controls></video>
+        <div class="icon-close" @click="remove">
+            <i class="el-icon-close"></i>
+        </div>
+    </div>
+    <el-upload
+        v-else
+        class="video-upload"
+        :action="videoUploadUrl"
+        :before-upload="beforeUpload"
+        :on-progress="onProgress"
+        :on-success="onSuccess"
+        :on-error="onError"
+        :headers="headers"
+        :show-file-list="false"
+        accept="video/*"
+        :disabled="uploading"
+    >
+        <div class="progress-wrapper" v-if="uploading">
+            <i class="el-icon-loading"></i>
+            <el-progress :stroke-width="4" :percentage="progress"></el-progress>
+        </div>
+        <el-button v-else type="primary" size="mini">上传 </el-button>
+    </el-upload>
+</template>
+<script>
+import resolveUrl from 'resolve-url';
+export default {
+    props: ['value'],
+    data() {
+        return {
+            videoUploadUrl: '',
+            uploading: false,
+            progress: 0
+        };
+    },
+    created() {
+        this.videoUploadUrl = resolveUrl(this.$baseUrl, 'upload/video');
+    },
+    computed: {
+        headers() {
+            return {
+                Authorization: 'Bearer ' + localStorage.getItem('token')
+            };
+        }
+    },
+    methods: {
+        beforeUpload(file) {
+            console.log(file);
+            const isVideo = /video\/.*/.test(file.type);
+            const isLt100M = file.size / 1024 / 1024 < 100;
+
+            if (!isVideo) {
+                this.$message.error('请上传视频文件!');
+            }
+            if (!isLt100M) {
+                this.$message.error('上传视频大小不能超过 100MB!');
+            }
+            const allow = isVideo && isLt100M;
+            if (allow) {
+                this.progress = 0;
+                this.uploading = true;
+            }
+            return allow;
+        },
+        onSuccess(res, file, fileList) {
+            console.log(res, file, fileList);
+            this.$emit('input', res);
+            this.uploading = false;
+        },
+        onError(err) {
+            console.log(err);
+            this.uploading = false;
+        },
+        onProgress(e) {
+            console.log(e);
+        },
+        remove() {
+            this.$emit('input', null);
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.video-wrapper {
+    width: 220px;
+    height: 140px;
+    position: relative;
+    .icon-close {
+        width: 25px;
+        height: 25px;
+        border-radius: 50%;
+        color: white;
+        background: #f56c6c;
+        position: absolute;
+        right: -7px;
+        top: -7px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.08);
+    }
+    video {
+        width: 100%;
+        height: 100%;
+        outline: none;
+        border-radius: 6px;
+        object-fit: cover;
+        overflow: hidden;
+        background: black;
+    }
+}
+.video-upload {
+    .progress-wrapper {
+        display: flex;
+        height: 28px;
+        align-items: center;
+        .el-progress {
+            width: 350px;
+        }
+    }
+}
+</style>

+ 2 - 0
src/main/vue/src/main.js

@@ -9,6 +9,7 @@ import SortableHeader from '@/components/SortableHeader';
 import MultiUpload from '@/components/MultiUpload';
 import SingleUpload from '@/components/SingleUpload';
 import FileUpload from '@/components/FileUpload';
+import VideoUpload from '@/components/VideoUpload';
 import RichText from '@/components/RichText';
 import CropUpload from '@/components/CropUpload';
 import DistrictChoose from '@/components/DistrictChoose';
@@ -45,6 +46,7 @@ Vue.component('sortable-header', SortableHeader);
 Vue.component('multi-upload', MultiUpload);
 Vue.component('single-upload', SingleUpload);
 Vue.component('file-upload', FileUpload);
+Vue.component('video-upload', VideoUpload);
 Vue.component('rich-text', RichText);
 Vue.component('crop-upload', CropUpload);
 Vue.component('district-choose', DistrictChoose);

+ 12 - 2
src/main/vue/src/views/ArticleEdit.vue

@@ -30,6 +30,9 @@
                         <el-form-item label="描述" prop="desc">
                             <el-input v-model="item.desc" :rows="5" type="textarea"></el-input>
                         </el-form-item>
+                        <el-form-item label="视频" prop="video">
+                            <video-upload v-model="item.video"></video-upload>
+                        </el-form-item>
                         <el-form-item label="图片" prop="img">
                             <multi-upload v-model="item.img"></multi-upload>
                         </el-form-item>
@@ -90,9 +93,16 @@ export default {
             }
         };
     },
+    computed: {
+        headers() {
+            return {
+                Authorization: 'Bearer ' + localStorage.getItem('token')
+            };
+        }
+    },
     methods: {
         onSave() {
-            if (this.formData.name === '可玩项目') {
+            if (this.formData.name === '玩项目') {
                 Promise.all(this.$refs.subform.map(i => i.validate())).then(() => {
                     this.$refs.form.validate(valid => {
                         if (valid) {
@@ -114,7 +124,7 @@ export default {
         },
         submit() {
             let data = { ...this.formData };
-            if (this.formData.name === '玩项目') {
+            if (this.formData.name === '玩项目') {
                 data.content = JSON.stringify(this.list);
             }
             this.saving = true;

+ 42 - 16
src/test/java/com/izouma/jiashanxia/CommonTest.java

@@ -5,6 +5,10 @@ import com.izouma.jiashanxia.domain.User;
 import com.izouma.jiashanxia.web.BaseController;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.text.CaseUtils;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.FrameGrabber;
+import org.bytedeco.javacv.Java2DFrameConverter;
 import org.junit.Test;
 import org.reflections.ReflectionUtils;
 import org.reflections.Reflections;
@@ -14,9 +18,13 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
@@ -52,7 +60,8 @@ public class CommonTest {
         List<Map<String, String>> entities = new ArrayList<>();
         Reflections classReflections = new Reflections(this.getClass().getPackage().getName());
         Set<Class<? extends BaseController>> controllers = classReflections.getSubTypesOf(BaseController.class);
-        Set<Class<? extends BaseController>> list = ReflectionUtils.getAll(controllers, ReflectionUtils.withAnnotation(RestController.class));
+        Set<Class<? extends BaseController>> list = ReflectionUtils
+                .getAll(controllers, ReflectionUtils.withAnnotation(RestController.class));
         System.out.println(list);
 
         for (Class<? extends BaseController> aClass : list) {
@@ -62,7 +71,8 @@ public class CommonTest {
                 GetMapping getMapping = method.getAnnotation(GetMapping.class);
                 System.out.println(getMapping.value()[0]);
             }
-            for (Method method : ReflectionUtils.getMethods(aClass, ReflectionUtils.withAnnotation(PostMapping.class))) {
+            for (Method method : ReflectionUtils
+                    .getMethods(aClass, ReflectionUtils.withAnnotation(PostMapping.class))) {
                 PostMapping postMapping = method.getAnnotation(PostMapping.class);
                 System.out.println(postMapping.value()[0]);
             }
@@ -78,13 +88,15 @@ public class CommonTest {
     public void testMeasureText() throws IOException, FontFormatException {
         AffineTransform affinetransform = new AffineTransform();
         FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
-        Font font = Font.createFont(Font.TRUETYPE_FONT, this.getClass().getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
+        Font font = Font.createFont(Font.TRUETYPE_FONT, this.getClass()
+                .getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
         System.out.println((int) (font.deriveFont(14f).getStringBounds("aaa", frc).getWidth()));
     }
 
     @Test
     public void testIdNoRegexp() {
-        boolean b = Pattern.matches("^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}[0-9xX]$", "32100219950830461X");
+        boolean b = Pattern
+                .matches("^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}[0-9xX]$", "32100219950830461X");
         System.out.println(b);
     }
 
@@ -94,23 +106,34 @@ public class CommonTest {
         Set<Class<? extends Enum>> entitySet = reflections.getSubTypesOf(Enum.class);
         StringBuilder idxJs = new StringBuilder();
         for (Class<? extends Enum> entity : entitySet) {
-            idxJs.append("import ").append(entity.getSimpleName()).append(" from \"./").append(entity.getSimpleName()).append("\";\n");
+            idxJs.append("import ").append(entity.getSimpleName()).append(" from \"./").append(entity.getSimpleName())
+                    .append("\";\n");
             StringBuilder str = new StringBuilder("export default {\n");
             for (Enum enumConstant : entity.getEnumConstants()) {
-                str.append("    ").append(enumConstant.name()).append(": \"").append(enumConstant.name()).append("\",\n");
+                str.append("    ").append(enumConstant.name()).append(": \"").append(enumConstant.name())
+                        .append("\",\n");
             }
             str.append("}");
-            Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src", "constants", entity.getSimpleName() + ".js"), str.toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
-            Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "zmj_mp", "src", "constants", entity.getSimpleName() + ".js"), str.toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+            Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src", "constants", entity
+                    .getSimpleName() + ".js"), str.toString()
+                    .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+            Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "zmj_mp", "src", "constants", entity
+                    .getSimpleName() + ".js"), str.toString()
+                    .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
         }
         idxJs.append("export default {\n");
         for (Class<? extends Enum> entity : entitySet) {
-            idxJs.append("    ").append(entity.getSimpleName()).append(": ").append(entity.getSimpleName()).append(",\n");
+            idxJs.append("    ").append(entity.getSimpleName()).append(": ").append(entity.getSimpleName())
+                    .append(",\n");
         }
         idxJs.append("}");
         System.out.println(idxJs.toString());
-        Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src", "constants", "index.js"), idxJs.toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
-        Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "zmj_mp", "src", "constants", "index.js"), idxJs.toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+        Files.write(Paths
+                .get(System.getProperty("user.dir"), "src", "main", "vue", "src", "constants", "index.js"), idxJs
+                .toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+        Files.write(Paths
+                .get(System.getProperty("user.dir"), "src", "main", "zmj_mp", "src", "constants", "index.js"), idxJs
+                .toString().getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
     }
 
     @Test
@@ -126,10 +149,13 @@ public class CommonTest {
     }
 
     @Test
-    public void testEnum() {
-        System.out.println(String.class.getName());
-        System.out.println(String.class.getSimpleName());
-        System.out.println(String.class.getCanonicalName());
-        System.out.println(String.class.getTypeName());
+    public void testEnum() throws IOException {
+        FFmpegFrameGrabber g = new FFmpegFrameGrabber("/Users/drew/Desktop/1.mp4");
+        g.start();
+        Frame frame = g.grab();
+        Java2DFrameConverter converter = new Java2DFrameConverter();
+        BufferedImage image = converter.convert(frame);
+        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+        ImageIO.write(image, "jpeg", new File("/Users/drew/Desktop/1.jpg"));
     }
 }