zhaoxian 1 год назад
Родитель
Сommit
4396fc9155

+ 4 - 0
ruixuan-admin/src/main/resources/application-dev.yml

@@ -196,3 +196,7 @@ promoter:
 tessdata:
   path: /usr/local/tesseract/share/tessdata
 
+  #视频转换的音频PCM存储路径
+pcm:
+  local-path: D://pcm//data//
+

+ 4 - 0
ruixuan-admin/src/main/resources/application-prod.yml

@@ -192,3 +192,7 @@ promoter:
   #读取图片汉字字符集
 tessdata:
   path: /usr/local/tesseract/share/tessdata
+
+  #视频转换的音频PCM存储路径
+pcm:
+  local-path: /data/

+ 273 - 0
ruixuan-common/src/main/java/com/ruixuan/common/utils/file/FileUtil.java

@@ -0,0 +1,273 @@
+package com.ruixuan.common.utils.file;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Date;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @ClassName ZipUtil
+ * @Author 
+ * @date 2020-10-23 10:05
+ */
+@Slf4j
+public class FileUtil {
+
+    /**
+     * 转MultipartFile格式文件
+     *
+     * @param
+     * @return org.springframework.web.multipart.MultipartFile
+     * @throws
+     * @author 
+     */
+    public static MultipartFile getMulFileByPath(String picPath) {
+        FileItem fileItem = createFileItem(picPath);
+        MultipartFile mfile = new CommonsMultipartFile(fileItem);
+        return mfile;
+    }
+
+    private static FileItem createFileItem(String filePath) {
+        FileItemFactory factory = new DiskFileItemFactory(16, null);
+        String textFieldName = "textField";
+        int num = filePath.lastIndexOf("/");
+        String fileName = filePath.substring(num);
+        FileItem item = factory.createItem(textFieldName, "text/plain", true,
+                fileName);
+        File newfile = new File(filePath);
+        int bytesRead = 0;
+        byte[] buffer = new byte[8192];
+        try {
+            FileInputStream fis = new FileInputStream(newfile);
+            OutputStream os = item.getOutputStream();
+            while ((bytesRead = fis.read(buffer, 0, 8192))
+                    != -1) {
+                os.write(buffer, 0, bytesRead);
+            }
+            os.close();
+            fis.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return item;
+    }
+
+
+    /**
+     * 下载文件到本地
+     *
+     * @param locationPath 本地路径
+     * @param fileUrl      文件下载路径
+     * @param fileName     下载后的名称含后缀
+     * @return java.lang.String
+     * @throws
+     * @author 
+     */
+    public static String writeFiles(String locationPath, String fileUrl, String fileName) {
+        InputStream is = null;
+        OutputStream os = null;
+        Date date = new Date();
+        String realPath = null;
+        try {
+            URL url = new URL(fileUrl);
+            is = url.openStream();
+            File file = new File(locationPath);
+            if (!file.exists()) {
+                boolean mkdirFlag = file.mkdirs();
+                if (!mkdirFlag) {
+                    log.error("[ ========== 下载,创建临时文件夹失败 ========== ]");
+                    return null;
+                }
+            }
+            realPath = locationPath.concat(fileName);
+            os = new FileOutputStream(realPath);
+            int bytesRead = 0;
+            byte[] buffer = new byte[8192];
+            while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
+                os.write(buffer, 0, bytesRead);
+                os.flush();
+            }
+
+        } catch (Exception e) {
+            log.error(" ========== 文件下载到本地异常:", e);
+            return null;
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return realPath;
+    }
+
+    /**
+     * 通过MultipartFile获取本地下载路径
+     *
+     * @param
+     * @return java.lang.String
+     * @throws
+     * @author 
+     */
+    public static String approvalFile(MultipartFile filecontent, String path) {
+        OutputStream os = null;
+        InputStream inputStream = null;
+        String fileName = filecontent.getOriginalFilename();
+        try {
+            inputStream = filecontent.getInputStream();
+        } catch (IOException e) {
+            log.error("获取文件流异常");
+        }
+        try {
+            byte[] bs = new byte[1024];
+            // 读取到的数据长度
+            int len;
+            // 输出的文件流保存到本地文件
+            File tempFile = new File(path);
+            if (!tempFile.exists()) {
+                tempFile.mkdirs();
+            }
+            os = new FileOutputStream(tempFile.getPath() + "/" + File.separator + fileName);
+            // 开始读取
+            while ((len = inputStream.read(bs)) != -1) {
+                os.write(bs, 0, len);
+            }
+        } catch (Exception e) {
+            log.error("获取文件的本地路径失败", e);
+        } finally {
+            // 完毕,关闭所有链接
+            try {
+                os.close();
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return path + fileName;
+    }
+
+
+    /**
+     * @param zipSavePath 压缩好的zip包存放路径
+     * @param sourceFile  待压缩的文件(单个文件或者整个文件目录)
+     * @return
+     * @author
+     */
+    public static String zipCompress(String zipSavePath, File sourceFile) {
+        try {
+            //创建zip输出流
+            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipSavePath));
+            File[] fileList = sourceFile.listFiles();
+            //如果文件夹为空,则只需在目的地zip文件中写入一个目录进入点
+            if (fileList.length != 0) {
+                for (File file : fileList) {
+                    compress(zos, file, file.getName());
+                }
+                zos.close();
+            }
+        } catch (Exception e) {
+            log.error("压缩文件异常 : {}, zipSavePath={}, sourceFile={}", e, zipSavePath, sourceFile.getName());
+        }
+        return zipSavePath;
+    }
+
+    /**
+     * 递归压缩文件
+     *
+     * @param
+     * @return void
+     * @throws
+     * @author 
+     */
+    private static void compress(ZipOutputStream zos, File sourceFile, String fileName) throws Exception {
+        if (sourceFile.isDirectory()) {
+            //如果是文件夹,取出文件夹中的文件(或子文件夹)
+            File[] fileList = sourceFile.listFiles();
+            if (fileList.length == 0)//如果文件夹为空,则只需在目的地zip文件中写入一个目录进入点
+            {
+                zos.putNextEntry(new ZipEntry(fileName + "/"));
+            } else//如果文件夹不为空,则递归调用compress,文件夹中的每一个文件(或文件夹)进行压缩
+            {
+                for (File file : fileList) {
+                    compress(zos, file, fileName + "/" + file.getName());
+                }
+            }
+        } else {
+            if (!sourceFile.exists()) {
+                zos.putNextEntry(new ZipEntry("/"));
+                zos.closeEntry();
+            } else {
+                //单个文件,直接将其压缩到zip包中
+                zos.putNextEntry(new ZipEntry(fileName));
+                FileInputStream fis = new FileInputStream(sourceFile);
+                byte[] buf = new byte[1024];
+                int len;
+                //将源文件写入到zip文件中
+                while ((len = fis.read(buf)) != -1) {
+                    zos.write(buf, 0, len);
+                }
+                zos.closeEntry();
+                fis.close();
+            }
+        }
+    }
+    /**
+     * url资源转化为file流
+     * @param url
+     * @return
+     */
+    public static File urlToFile(URL url) {
+        InputStream is = null;
+        File file = null;
+        FileOutputStream fos = null;
+        try {
+            file = File.createTempFile("tmp", null);
+            URLConnection urlConn = null;
+            urlConn = url.openConnection();
+            is = urlConn.getInputStream();
+            fos = new FileOutputStream(file);
+            byte[] buffer = new byte[4096];
+            int length;
+            while ((length = is.read(buffer)) > 0) {
+                fos.write(buffer, 0, length);
+            }
+            return file;
+        } catch (IOException e) {
+            return null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+}

+ 23 - 0
ruixuan-live/pom.xml

@@ -82,6 +82,29 @@
             <version>4.1.1</version>
         </dependency>
 
+        <!--实现对视频文件读取-->
+        <dependency>
+            <groupId>org.mp4parser</groupId>
+            <artifactId>isoparser</artifactId>
+            <version>1.9.41</version>
+        </dependency>
+        <!--实现对ffmpeg的操作-->
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>ffmpeg</artifactId>
+            <version>4.2.2-1.5.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>ffmpeg-platform</artifactId>
+            <version>4.2.2-1.5.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-speech-sdk-java</artifactId>
+            <version>1.0.38</version>
+        </dependency>
 
     </dependencies>
 </project>

+ 36 - 0
ruixuan-live/src/main/java/com/ruixuan/data/controller/VideoTextController.java

@@ -0,0 +1,36 @@
+package com.ruixuan.data.controller;
+
+import com.ruixuan.common.core.controller.BaseController;
+import com.ruixuan.common.core.domain.ResultResponse;
+import com.ruixuan.data.service.VideoTextService;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author ruoyi
+ * @date 2022-05-31
+ */
+@RestController
+@RequestMapping("/live/videoText")
+public class VideoTextController extends BaseController {
+
+    @Autowired
+    private VideoTextService videoTextService;
+
+
+    @GetMapping(value = "addVideo")
+    @ApiOperation(value = "添加视频")
+    public ResultResponse addVideo(@ApiParam("视频URL") @RequestParam(value = "url", required = true) String url, String md5) {
+        try {
+            return videoTextService.addVideo(url, md5);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResultResponse.error("添加异常");
+        }
+    }
+}

+ 4 - 0
ruixuan-live/src/main/java/com/ruixuan/data/mapper/VideoTextServiceMapper.java

@@ -0,0 +1,4 @@
+package com.ruixuan.data.mapper;
+
+public interface VideoTextServiceMapper {
+}

+ 8 - 0
ruixuan-live/src/main/java/com/ruixuan/data/service/VideoTextService.java

@@ -0,0 +1,8 @@
+package com.ruixuan.data.service;
+
+import com.ruixuan.common.core.domain.ResultResponse;
+
+public interface VideoTextService {
+
+    ResultResponse addVideo(String url, String md5);
+}

+ 45 - 0
ruixuan-live/src/main/java/com/ruixuan/data/service/impl/VideoTextServiceImpl.java

@@ -0,0 +1,45 @@
+package com.ruixuan.data.service.impl;
+
+import com.ruixuan.common.core.domain.ResultResponse;
+import com.ruixuan.common.utils.Check;
+import com.ruixuan.common.utils.DateUtils;
+import com.ruixuan.common.utils.file.FileUtil;
+import com.ruixuan.data.service.VideoTextService;
+import com.ruixuan.data.utils.VideoToPCMTool;
+import com.ruixuan.data.utils.VoiceTool;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.nio.file.Paths;
+import java.util.Optional;
+
+@Slf4j
+@Service
+public class VideoTextServiceImpl implements VideoTextService {
+
+
+    @Value("${pcm.local-path}")
+    private String downloadPath;
+
+    @Override
+    public ResultResponse addVideo(String url, String md5) {
+        if (Check.isNull(url)) {
+            return ResultResponse.error("链接为空");
+        }
+        StringBuffer localPath = new StringBuffer();
+        localPath.append(downloadPath).append(DateUtils.dateTime()).append("/");
+        log.info("-------下载到服务器地址:{}", localPath.toString());
+        String[] split = url.split("/");
+        //本地文件内容
+        String filePath = FileUtil.writeFiles(localPath.toString(), url, split[split.length - 1]);
+
+        // 获取 PCM path
+        Optional<String> paths = VideoToPCMTool.convertMP4toPCM(Paths.get(filePath), Paths.get(localPath.toString()));
+        if (!Check.isNull(paths)) {
+            return ResultResponse.success(VoiceTool.getVideoText(paths.get()));
+        }
+        return ResultResponse.error("转文本失败");
+    }
+
+}

+ 187 - 0
ruixuan-live/src/main/java/com/ruixuan/data/utils/VideoToPCMTool.java

@@ -0,0 +1,187 @@
+package com.ruixuan.data.utils;
+
+import com.ruixuan.jiaoyang.service.IJiaoYangReportService;
+import com.tencent.SpeechClient;
+import com.tencent.asr.model.Credential;
+import com.tencent.asr.model.FlashRecognitionRequest;
+import com.tencent.asr.model.FlashRecognitionResponse;
+import com.tencent.asr.service.FlashRecognizer;
+import com.tencent.core.utils.ByteUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.bytedeco.javacpp.Loader;
+import org.mp4parser.IsoFile;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+public class VideoToPCMTool {
+    @Autowired
+    private IJiaoYangReportService jiaoYangReportService;
+
+    public static void main(String[] args) {
+        //        String filePath = "D:\\test\\d96843142a35726ab2e01197994e1bff.pcm";
+//        File file = new File(filePath);
+
+        String file = "D:\\test\\ff903858ab644495b920f89617916425.mp4";
+        String pcmdir = "D:\\test";
+        Path path = Paths.get(file);
+        convertMP4toPCM(path, Paths.get(pcmdir));
+    }
+
+    /**
+     * 将单个PM4文件,转换为PCM文件
+     */
+    public static Optional<String> convertMP4toPCM(Path mp4Path, Path pcmDir) {
+        long seconds = readDuration(mp4Path);
+        if (seconds == 0) {
+            log.warn("文件总时长为0");
+            return Optional.empty();
+        }
+        String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
+        String endTime = String.valueOf(seconds);
+        File src = mp4Path.toFile();
+        //在当前源mp4文件目录下生成临时文件
+        String mp4TempFile = src.getParent() + "\\" + System.currentTimeMillis() + ".mp4";
+        //基于ffmpeg进行截取
+        ProcessBuilder cutBuilder = new ProcessBuilder(ffmpeg, "-ss", "1", "-i", mp4Path.toAbsolutePath().toString(),
+                "-to", endTime, "-c", "copy", mp4TempFile);
+        try {
+            cutBuilder.inheritIO().start().waitFor();
+        } catch (InterruptedException | IOException e) {
+            log.error("ffmpeg截取MP4文件出错", e);
+            return Optional.empty();
+        }
+        // 基于ffmpeg进行pcm转换
+        // 基于输入路径的md5值来命名,也可以基于系统时间戳来命名
+        String pcmFile = pcmDir.resolve(DigestUtils.md5Hex(mp4Path.toString()) + ".pcm").toString();
+        ProcessBuilder pcmBuilder = new ProcessBuilder(ffmpeg, "-y", "-i", mp4TempFile, "-vn", "-acodec", "pcm_s16le",
+                "-f", "s16le", "-ac", "1", "-ar", "16000", pcmFile);
+        try {
+            //inheritIO是指将 子流程的IO与当前java流程的IO设置为相同
+            pcmBuilder.inheritIO().start().waitFor();
+        } catch (InterruptedException | IOException e) {
+            log.error("ffmpeg将mp4转换为pcm时出错", e);
+            return Optional.empty();
+        }
+        // 删除MP4临时文件
+        try {
+            Files.deleteIfExists(Paths.get(mp4TempFile));
+        } catch (IOException e) {
+            log.error("删除mp4临时文件出错", e);
+        }
+        //返回pcm文件路径
+        return Optional.of(pcmFile);
+    }
+
+    public static long readDuration(Path mp4Path) {
+        if (Files.notExists(mp4Path) || !Files.isReadable(mp4Path)) {
+            log.warn("文件路径不存在或不可读 {}", mp4Path);
+            return 0;
+        }
+        try {
+            File file = mp4Path.toFile();
+            IsoFile isoFile = new IsoFile(mp4Path.toFile());
+            long duration = isoFile.getMovieBox().getMovieHeaderBox().getDuration();
+            long timescale = isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
+            return duration / timescale;
+        } catch (IOException e) {
+            log.error("读取MP4文件时长出错", e);
+            return 0;
+        }
+    }
+
+    /**
+     * 批量将MP4文件转换为PCM文件
+     *
+     * @return 成功转换的PCM文件数
+     */
+    public int batchConvertMP4toPCM(Path rootDir, Path pcmDir) {
+        if (Files.notExists(rootDir) || !Files.isDirectory(rootDir)) {
+            log.warn("mp4文件目录{}不存在", rootDir);
+            return 0;
+        }
+
+        if (Files.notExists(pcmDir)) {
+            //级联创建目录
+            try {
+                Files.createDirectories(pcmDir);
+            } catch (IOException e) {
+                log.error("创建文件夹出错", e);
+            }
+        }
+        AtomicInteger pcmCount = new AtomicInteger(0);
+        //遍历rootdir,获取所有目录下子目录和文件
+        try {
+            Files.list(rootDir).forEach(path -> {
+                if (Files.isDirectory(path)) {
+                    //递归遍历下级目录
+                    pcmCount.getAndAdd(batchConvertMP4toPCM(path, pcmDir));
+                }
+                if (Files.isRegularFile(path) && Files.isReadable(path) && path.getFileName()
+                        .toString()
+                        .endsWith("mp4")) {
+                    Optional<String> pcmFile = this.convertMP4toPCM(path, pcmDir);
+                    if (pcmFile.isPresent()) {
+                        pcmCount.getAndIncrement();
+                    }
+                }
+            });
+        } catch (IOException e) {
+            log.error("批量将MP4文件转换为PCM文件出错", e);
+        }
+        return pcmCount.get();
+    }
+
+
+    //url转file
+    private File getFileByUrl(String fileUrl, String suffix) {
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        BufferedOutputStream stream = null;
+        InputStream inputStream = null;
+        File file = null;
+        try {
+            URL imageUrl = new URL(fileUrl);
+            HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
+            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
+            inputStream = conn.getInputStream();
+            byte[] buffer = new byte[1024];
+            int len = 0;
+            while ((len = inputStream.read(buffer)) != -1) {
+                outStream.write(buffer, 0, len);
+            }
+            file = File.createTempFile("file", suffix);
+            FileOutputStream fileOutputStream = new FileOutputStream(file);
+            stream = new BufferedOutputStream(fileOutputStream);
+            stream.write(outStream.toByteArray());
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (stream != null) {
+                    stream.close();
+                }
+                outStream.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return file;
+    }
+}

+ 110 - 0
ruixuan-live/src/main/java/com/ruixuan/data/utils/VoiceTool.java

@@ -0,0 +1,110 @@
+package com.ruixuan.data.utils;
+
+
+import com.tencent.SpeechClient;
+import com.tencent.asr.model.Credential;
+import com.tencent.asr.model.FlashRecognitionRequest;
+import com.tencent.asr.model.FlashRecognitionResponse;
+import com.tencent.asr.service.FlashRecognizer;
+import com.tencent.core.utils.ByteUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class VoiceTool {
+    private final static Charset UTF8 = StandardCharsets.UTF_8;
+    private final static String APP_ID = "1301855440";
+    private final static String SECRET_ID = "AKIDPcZGgphIcfRxCF1XmjQqzqRlnY3GCrFN";
+    private final static String SECRET_KEY = "tVqvjFUS9ZhY8RTEiJeiV1GEvMVNyJM8";
+    private final static String CT_JSON = "application/json; charset=utf-8";
+
+    public static byte[] hmac256(byte[] key, String msg) throws Exception {
+        Mac mac = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
+        mac.init(secretKeySpec);
+        return mac.doFinal(msg.getBytes(UTF8));
+    }
+
+    public static String sha256Hex(String s) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        byte[] d = md.digest(s.getBytes(UTF8));
+        return DatatypeConverter.printHexBinary(d).toLowerCase();
+    }
+
+
+    public static String getVideoText(String pcmPath) {
+        Credential credential = Credential.builder().secretId(SECRET_ID).secretKey(SECRET_KEY).build();
+        FlashRecognizer recognizer = SpeechClient.newFlashRecognizer(APP_ID, credential);
+        return runOnce(recognizer, pcmPath);
+    }
+
+
+    private static String runOnce(FlashRecognizer recognizer, String pcmPath) {
+        byte[] data = ByteUtils.inputStream2ByteArray(pcmPath);
+        //传入识别语音数据同步获取结果
+        FlashRecognitionRequest recognitionRequest = FlashRecognitionRequest.initialize();
+        recognitionRequest.setEngineType("16k_zh");
+        recognitionRequest.setFirstChannelOnly(1);
+        recognitionRequest.setVoiceFormat("pcm");
+        recognitionRequest.setSpeakerDiarization(0);
+        recognitionRequest.setFilterDirty(0);
+        recognitionRequest.setFilterModal(0);
+        recognitionRequest.setFilterPunc(0);
+        recognitionRequest.setConvertNumMode(1);
+        recognitionRequest.setWordInfo(1);
+        FlashRecognitionResponse response = recognizer.recognize(recognitionRequest, data);
+        return response.getFlashResult().get(0).getText();
+    }
+
+
+    public static void main(String[] args) throws Exception {
+        String service = "cvm";
+        String host = "cvm.tencentcloudapi.com";
+        String region = "ap-guangzhou";
+        String action = "DescribeInstances";
+        String version = "2017-03-12";
+        String algorithm = "TC3-HMAC-SHA256";
+        String timestamp = "1551113065";
+        //String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        // 注意时区,否则容易出错
+        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
+
+        // ************* 步骤 1:拼接规范请求串 *************
+        String httpRequestMethod = "POST";
+        String canonicalUri = "/";
+        String canonicalQueryString = "";
+        String canonicalHeaders = "content-type:application/json; charset=utf-8\n"
+                + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
+        String signedHeaders = "content-type;host;x-tc-action";
+
+        String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
+        String hashedRequestPayload = sha256Hex(payload);
+        String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
+                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
+
+        // ************* 步骤 2:拼接待签名字符串 *************
+        String credentialScope = date + "/" + service + "/" + "tc3_request";
+        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
+        String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
+
+        // ************* 步骤 3:计算签名 *************
+        byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date);
+        byte[] secretService = hmac256(secretDate, service);
+        byte[] secretSigning = hmac256(secretService, "tc3_request");
+        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
+        System.out.println(signature);
+
+
+    }
+
+
+}

+ 7 - 0
ruixuan-live/src/main/resources/mapper/data/VideoTextServiceMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruixuan.data.mapper.VideoTextServiceMapper">
+
+</mapper>