io流转换为Multipart文件
io流转换为Multipart文件
个人的话是运用到了minio文件服务器保存文件,前端(vue)异步上传文件后,由于要提升用户体验效果,先上传文件到后台服务器,返回视频在文件服务器的link()参数,然后保存该文件到数据库,然后运用java 定时任务之一 @Scheduled注解(如何使用自行www.baidu.com),定时进行在后台转码相关视频文件。运用到的转码工具是FFmpeg.exe。
详细如下:
- 新建一份工具类文件,进行转码操作(亲测转码各参数都有用),个人的话是统一转码为MP4文件。
package org.springblade.common.utils; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.config.loadFileConfig;
import org.springblade.common.service.ResourceService;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.api.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile; import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; import static jodd.io.FileUtil.deleteFile; /**
* Created by liuminghui on 2020/12/30 22:14
*
* @annotate:
* @Version 1.0
*/
public class DisposeVideoUtils {
private static final Logger logger = LoggerFactory.getLogger(DisposeVideoUtils.class); // 转码ffmpeg.exe工具路径
private static final String FFMPEG_PATH = "F:\\gugexaizai\\acutvideo\\ffmpeg.exe"; // 转码mencoder.exe工具路径
private static final String MENCODER_PATH = "F:\\gugexaizai\\acutvideo\\mencoder.exe"; // 临时的视频存储路径,转码完成后可删除
private static final String TEMPORARY_VIDEO_PATH = "F:\\Images\\"; // 转码成功mp4视频存放路径 private static final String uploadFolder="F:\\ImagesPath\\"; // 转码后台的视频访问路径
private String videoUrl; // 视频大小
private String size;
private String filerealname;
private String PATH;
// 视频截图路径
private String videoImg; public DisposeVideoUtils() {
} //重构构造方法,传入视频存放路径
public DisposeVideoUtils(String path) {
videoUrl = path;
} //set和get方法传递path
public String getVideoPath() {
return videoUrl;
} public String getSize() {
return size;
} public void setSize(String size) {
this.size = size;
} public void setVideoPath(String path) {
videoUrl = path;
} public String getVideoImg() {
return videoImg;
} public void setVideoImg(String videoImg) {
this.videoImg = videoImg;
} /**
* 转码、截图和删除源文件功能
* @param tempPath 临时源视频文件路径
*/
// public String runConver(String tempPath) {
// logger.info("=================转码过程开始=====================");
// try {
// String targetExtension = ".mp4"; // 设置转换的格式
// boolean isDelSourseFile = true;
// // 转码、截图和删除源文件
// boolean beginConver = beginVideoConver(tempPath, targetExtension, isDelSourseFile);
// if (beginConver) {
// logger.info("=================转码过程彻底结束=====================");
// return videoUrl;
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// return null;
// } /**
* 视频转码、截图和删除原视频处理
* @param tempPath 临时源视频路径
* @param targetExtension 转码成功的视频后缀名
* @param isDelSourseFile 转换完成后是否删除源文件
* @return
*/
// private boolean beginVideoConver(String tempPath, String targetExtension, boolean isDelSourseFile) {
// File file = new File(tempPath);
// String fileName = file.getName(); //获取文件名+后缀名
// String fileNameSuffix = fileName.substring(fileName.lastIndexOf(".") + 1); // 获取需要转码的文件后缀
// //执行转码机制
// if (process(tempPath, fileNameSuffix, targetExtension)) {
// //删除临时视频或转码原视频
// if (isDelSourseFile) {
// // 删除临时文件
// File fileDelete = new File(TEMPORARY_VIDEO_PATH);
// String[] tempList = fileDelete.list();
// File temp = null;
// for (int i = 0; i < tempList.length; i++) {
// if (TEMPORARY_VIDEO_PATH.endsWith(File.separator)) {
// temp = new File(TEMPORARY_VIDEO_PATH + tempList[i]);
// } else {
// temp = new File(TEMPORARY_VIDEO_PATH + File.separator + tempList[i]);
// }
// if (temp.isFile() || temp.isDirectory()) {
// temp.delete(); // 删除文件夹里面的文件
// }
// }
// }
//
// return true;
// } else {
// return false;
// }
// } /**
* 实际转换视频格式的方法
* @param tempPath 临时源视频文件路径
* @param fileNameSuffix 源视频后缀名
* @param targetExtension 转码成功的视频后缀名
* @return
*/
// private boolean process(String tempPath, String fileNameSuffix, String targetExtension) {
// //先判断视频的类型-返回状态码
// int type = checkVideoSuffix(fileNameSuffix);
// boolean status = false;
//
// //根据状态码处理
// if (type == 0) {
// logger.info("ffmpeg可以转换,统一转为mp4文件");
// String status = processVideoFormat(tempPath, targetExtension);//可以指定转换为什么格式的视频
// } else if (type == 1) {
// //如果type为1,将其他文件先转换为avi,然后在用ffmpeg转换为指定格式
// logger.info("ffmpeg不可以转换,先调用mencoder转码avi");
// String avifilepath = processAVI(tempPath);
//
// if (avifilepath == null){
// // 转码失败--avi文件没有得到
// logger.info("mencoder转码失败,未生成AVI文件");
// return false;
// }else {
// logger.info("生成AVI文件成功,ffmpeg开始转码:");
// String status = processVideoFormat(avifilepath, targetExtension);
// }
// }
// return status; //执行完成返回布尔类型true
// } /**
* 转换为指定格式
* ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
* @param oldfilepath 临时源视频文件路径
* @param targetExtension 转码成功的视频后缀名 .xxx
* @return
*/
public String processVideoFormat(String oldfilepath, String targetExtension) {
logger.info("调用了ffmpeg.exe工具"); // 先确保保存转码后的视频的mp4文件夹存在:TRANSCODE_VIDEO_MP4PATH-保存路径
String mp4Path = uploadFolder + Long.toString(new Date().getTime()) + targetExtension;
File tempFile = new File(uploadFolder);
if (tempFile.exists()) {
if (tempFile.isDirectory()) {
logger.info("该转码后的文件夹已存在");
} else {
logger.info("同名的转码后的文件存在,不能创建文件夹");
}
} else {
// 创建目录
if (tempFile.mkdirs()) {
logger.info("创建目录转码后的文件夹:" + uploadFolder + ",成功!");
} else {
logger.info("创建目录转码后的文件夹:" + uploadFolder + ",失败!");
}
}
List<String> commend = new ArrayList<String>();
commend.add(FFMPEG_PATH); // 添加转换工具路径
commend.add("-i"); // 添加参数"-i",该参数指定要转换的文件
commend.add(oldfilepath); // 添加要转换格式的视频文件的路径
commend.add("-vcodec");
commend.add("libx264");
commend.add("-acodec");
commend.add("aac");
commend.add("-ab");
commend.add("128k");
commend.add("-ar");
commend.add("16k");
commend.add("-ac");
commend.add("2");
commend.add("-f"); // 添加参数"-y",该参数指定将覆盖已存在的文件
commend.add("mp4"); // 添加参数"-y",该参数指定将覆盖已存在的文件
commend.add("-y"); // 添加参数"-y",该参数指定将覆盖已存在的文件
commend.add(mp4Path); // 打印命令
StringBuffer test = new StringBuffer();
for (int i = 0; i < commend.size(); i++) {
test.append(commend.get(i) + " ");
}
logger.info("ffmpeg输入的命令:" + test); try {
String suffix = oldfilepath.substring(oldfilepath.lastIndexOf(".") + 1);
if(checkVideoSuffix(suffix)==0){
// 多线程处理加快速度-解决rmvb数据丢失builder名称要相同
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
builder.redirectErrorStream(true);
Process process = builder.start(); // 多线程处理加快速度-解决数据丢失
// 线程处理
threadSyn(process);
process.waitFor(); // 进程等待机制,必须要有,否则不生成mp4!!!
logger.info("生成mp4视频为:{}", mp4Path);
// 生成mp4视频保存路径
setVideoPath(mp4Path); logger.info("===============视频转码结束================="); return mp4Path;
}
else if(checkVideoSuffix(suffix)==200){
return oldfilepath; }
else{
return ("该文件暂不支持转换");
} } catch (Exception e) {
e.printStackTrace();
return mp4Path;
}
} /**
* 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
* 可以先用(mencoder)转换为avi(ffmpeg能解析的)格式.再用ffmpeg解析为指定格式
* @param oldfilepath 临时源视频文件路径
* @return
*/
private String processAVI(String oldfilepath) {
logger.info("调用了mencoder.exe工具");
String tempPath = TEMPORARY_VIDEO_PATH + Long.toString(new Date().getTime());
List<String> commend = new ArrayList<String>();
commend.add(MENCODER_PATH); // 指定mencoder.exe工具的位置
commend.add(oldfilepath); // 指定源视频的位置
commend.add("-oac");
commend.add("mp3lame"); // lavc 原mp3lame
commend.add("-lameopts");
commend.add("preset=64");
commend.add("-ovc");
commend.add("xvid"); // mpg4(xvid),AVC(h.264/x264),只有h264才是公认的MP4标准编码,如果ck播放不了,就来调整这里
commend.add("-xvidencopts"); // xvidencopts或x264encopts
commend.add("bitrate=600"); // 600或440
commend.add("-of");
commend.add("avi");
commend.add("-o");
commend.add(tempPath + ".avi"); // 存放路径+名称,生成.avi视频 // 打印出转换命令
StringBuffer test = new StringBuffer();
for (int i = 0; i < commend.size(); i++) {
test.append(commend.get(i) + " ");
}
logger.info("mencoder输入的命令:" + test);
try {
// 调用线程命令启动转码
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
Process process = builder.start(); // 多线程处理加快速度-解决数据丢失
// 线程处理
threadSyn(process);
// 等Mencoder进程转换结束,再调用ffmepg进程非常重要!!!
process.waitFor();
logger.info("Mencoder进程结束");
return tempPath + ".avi"; // 返回转为AVI以后的视频地址 } catch (Exception e) {
e.printStackTrace();
return null;
}
} /**
* 视频截图功能
* @param sourceVideoPath 需要被截图的视频路径(包含文件名和后缀名)
* @return
*/
public boolean processImg(String sourceVideoPath) {
String imageFile = "images/videoFile/";
String fileImpPath = ResourceService.rb.getString("filePath");
// 先确保保存截图的文件夹存在
File tempFile = new File(fileImpPath + imageFile);
if (tempFile.exists()) {
if (tempFile.isDirectory()) {
logger.info("该截图保存文件夹存在。");
} else {
logger.info("同名的截图保存文件存在,不能创建文件夹。");
}
} else {
logger.info("截图保存文件夹不存在,创建该文件夹。");
tempFile.mkdir();
} File file = new File(sourceVideoPath);
String fileName = file.getName(); // 获取视频文件的名称。
String fileRealName = fileName.substring(0, fileName.lastIndexOf(".")); // 获取不加后缀名的视频名
imageFile = imageFile + fileRealName + ".jpg";
List<String> commend = new ArrayList<String>();
// 第一帧: 00:00:01
// 截图命令:time ffmpeg -ss 00:00:01 -i test1.flv -f image2 -y test1.jpg
commend.add(FFMPEG_PATH); // 指定ffmpeg工具的路径
commend.add("-ss");
commend.add("00:00:03"); // 3是代表第3秒的时候截图
commend.add("-i");
commend.add(sourceVideoPath); // 截图的视频路径
commend.add("-f");
commend.add("image2");
commend.add("-y");
commend.add(fileImpPath + imageFile); // 生成截图xxx.jpg // 打印截图命令
StringBuffer test = new StringBuffer();
for (int i = 0; i < commend.size(); i++) {
test.append(commend.get(i) + " ");
}
logger.info("截图命令:" + test); // 转码后完成截图功能-还是得用线程来解决
try {
// 调用线程处理命令
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
Process process = builder.start();
// 线程处理
threadSyn(process);
// 等Mencoder进程转换结束,再调用ffmepg进程非常重要!!!
process.waitFor();
setVideoImg(imageFile);
// 视频大小
String videoSize = FileUploadToolUtil.getSize(file);
setSize(videoSize);
logger.info("截图进程结束,视频大小:{}", getSize());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 多线程处理
* @param process
*/
public void threadSyn(Process process) {
// 获取进程的标准输入流
final InputStream is1 = process.getInputStream();
// 获取进程的错误流
final InputStream is2 = process.getErrorStream();
// 启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
new Thread() {
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(is1));
try {
String lineB = null;
while ((lineB = br.readLine()) != null) {
if (lineB != null) {
logger.info(lineB); //必须取走线程信息避免堵塞
}
}
} catch (IOException e) {
e.printStackTrace();
}
// 关闭流
finally {
try {
is1.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}.start();
new Thread() {
public void run() {
BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
try {
String lineC = null;
while ((lineC = br2.readLine()) != null) {
if (lineC != null) {
logger.info(lineC); //必须取走线程信息避免堵塞
}
}
} catch (IOException e) {
e.printStackTrace();
} // 关闭流
finally {
try {
is2.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}.start();
} /**
* 检查文件类型,检查非MP4后缀
* asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等使用ffmpeg能解析
* wmv9,rm,rmvb等ffmpeg无法解析,先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式
* @param suffixName 后缀名
* @return
*/
public int checkVideoSuffix(String suffixName) {
if (suffixName.equals("avi")) {
return 0;
} else if (suffixName.equals("mpg")) {
return 0;
} else if (suffixName.equals("wmv")) {
return 0;
} else if (suffixName.equals("3gp")) {
return 0;
} else if (suffixName.equals("mov")) {
return 0;
} else if (suffixName.equals("asf")) {
return 0;
} else if (suffixName.equals("asx")) {
return 0;
} else if (suffixName.equals("flv")) {
return 0;
} else if (suffixName.equals("mkv")) {
return 0;
} else if (suffixName.equals("wmv9")) {
return 1;
} else if (suffixName.equals("rm")) {
return 1;
} else if (suffixName.equals("rmvb")) {
return 1;
} else if (suffixName.equals("mp4")) { // MP4不转码
return 200;
}
return 9;
} /**
* 视频写入本地磁盘/服务器
* @param file 上传文件
* @param filePath 存储位置
* @param fileName 文件名称
* @return
*/
public boolean uploadVideo(MultipartFile file, String filePath, String fileName) {
// 上传到本地磁盘/服务器
try {
logger.info("上传的视频写入本地磁盘/服务器");
InputStream is = file.getInputStream();
OutputStream os = new FileOutputStream(new File(filePath, fileName));
int len = 0;
byte[] buffer = new byte[2048]; while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.close();
os.flush();
is.close();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
// public void run() {
// try {
// // 转换并截图
// String filePath = "D:\\video\\old\\test.avi";
// DisposeVideoUtils cv = new DisposeVideoUtils(filePath);
// cv.beginConver();
//
// // 仅截图
// // ProcessFlvImg pfi = new ProcessFlvImg();
// // pfi.processImg("D:\\video\\old\\test.avi");
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
private boolean processFLV(String oldfilepath) { if (!checkfile(PATH)) {
System.out.println(oldfilepath + " is not file");
return false;
} List commend = new java.util.ArrayList();
commend.add(FFMPEG_PATH);
commend.add("-i");
commend.add(oldfilepath);
commend.add("-vcodec");
commend.add("libx264");
commend.add("-acodec");
commend.add("aac");
commend.add("-s");
commend.add("1280x720");
commend.add("-vprofile");
commend.add("high");
// commend.add("-vlevel");
// commend.add("3.1");
// commend.add("-coder");
// commend.add("1");
// -vcodec libx264 -acodec aac -ab 128k -ar 16k -ac 2 -f mp4 -y
// commend.add("-movflags");
// commend.add("faststart");
// commend.add("-force_key_frames");
// commend.add("1");
// commend.add("-strict");
// commend.add("experimental");
commend.add("-r");
commend.add("25");
commend.add("-g");
commend.add("30");
commend.add("-bf");
commend.add("2");
commend.add("-ab");
commend.add("128k");
commend.add("-ar");
commend.add("44100");
commend.add("-ac");
commend.add("2");
// commend.add("-b:v");
// commend.add("1.6M");
// commend.add("-sc_threshold");
// commend.add("0");
commend.add("-f"); // 添加参数"-y",该参数指定将覆盖已存在的文件
commend.add("mp4"); // 添加参数"-y",该参数指定将覆盖已存在的文件
commend.add("-y");
commend.add(uploadFolder + filerealname + ".mp4");
try {
ProcessBuilder builder = new ProcessBuilder();
String cmd = commend.toString();
builder.command(commend);
Process p = builder.start();
doWaitFor(p);
p.destroy();
deleteFile(oldfilepath);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void deleteFile(String filepath) {
File file = new File(filepath);
if (PATH.equals(filepath)) {
if (file.delete()) {
System.out.println("文件" + filepath + "已删除");
}
} else {
if (file.delete()) {
System.out.println("文件" + filepath + "已删除 ");
}
File filedelete2 = new File(PATH);
if (filedelete2.delete()) {
System.out.println("文件" + PATH + "已删除");
}
}
}
private boolean checkfile(String path) {
File file = new File(path);
if (!file.isFile()) {
return false;
} else {
return true;
}
}
public int doWaitFor(Process p) {
InputStream in = null;
InputStream err = null;
int exitValue = -1; // returned to caller when p is finished
try {
System.out.println("comeing");
in = p.getInputStream();
err = p.getErrorStream();
boolean finished = false; // Set to true when p is finished while (!finished) {
try {
while (in.available() > 0) {
Character c = new Character((char) in.read());
System.out.print(c);
}
while (err.available() > 0) {
Character c = new Character((char) err.read());
System.out.print(c);
} exitValue = p.exitValue();
finished = true; } catch (IllegalThreadStateException e) {
Thread.currentThread().sleep(500);
}
}
} catch (Exception e) {
System.err.println("doWaitFor();: unexpected exception - "
+ e.getMessage());
} finally {
try {
if (in != null) {
in.close();
} } catch (IOException e) {
System.out.println(e.getMessage());
}
if (err != null) {
try {
err.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
return exitValue;
} }
2. 将视频文件转码保存在本地之后,因为没有前端操作,一切都是在后台默默的进行,所以我们要读取该文件(保存在了本地磁盘),这必将是通过io流读取文件并获得,但是由于minio的上传方法要求是Multipart文件,所以将io流转换为Multipart文件就可以了。
/**
*
* @Description io流转换为MultipartFile---------返回MultipartFile文件
* @return org.springframework.web.multipart.MultipartFile
* @date 2020/12/30
* @auther liuminghui
*/
public static MultipartFile getFile( String filePath) throws IOException { File file = new File(filePath);
FileItem fileItem = new DiskFileItem("copyfile.txt", Files.probeContentType(file.toPath()),false,file.getName(),(int)file.length(),file.getParentFile());
byte[] buffer = new byte[4096];
int n;
try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()){
while ( (n = inputStream.read(buffer,0,4096)) != -1){
os.write(buffer,0,n);
}
//也可以用IOUtils.copy(inputStream,os);
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
System.out.println(multipartFile.getName());
return multipartFile;
}catch (IOException e){
e.printStackTrace();
}
return null;
}
3, 大致就是这样,还有很多文件的参数的方法都可以在各大网站上搜索到的,这里纯属是记录一下工作中遇到的各种问题和最终解决,感谢平台!
io流转换为Multipart文件的更多相关文章
- Java 基础(四)| IO 流之使用文件流的正确姿势
为跳槽面试做准备,今天开始进入 Java 基础的复习.希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆. 一.什么是 IO 流? 想象一个场景 ...
- IO流-批量修改文件名称案例
/* * 源文件名: 桌面-我们今天学习IO流了哈哈哈哈-001.jpg * 修改后文件名: 桌面-000x.jpg */public class File_listFiles_upda ...
- Java IO流之普通文件流和随机读写流区别
普通文件流和随机读写流区别 普通文件流:http://blog.csdn.net/baidu_37107022/article/details/71056011 FileInputStream和Fil ...
- IO流-复制多极文件夹(递归实现)
package com.io.test; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import ...
- java Io流输出指定文件的内容
package com.hp.io; import java.io.*; public class BufferedReaderTest{ /** *@param 想想 */ public st ...
- IO流(五)__文件的递归、Properties、打印流PrintStream与PrintWriter、序列流SequenceInputStream
一.文件的遍历 1.需求:对指定目录进行所有的内容的列出(包含子目录的内容)-文件的深度遍历 思想:递归的思想,在递归的时候要记住递归的层次. public class FileTest { publ ...
- C#常用IO流与读写文件
.文件系统 ()文件系统类的介绍 文件操作类大都在System.IO命名空间里.FileSystemInfo类是任何文件系统类的基类:FileInfo与File表示文件系统中的文件:Directory ...
- Java笔记(二十七)……IO流中 File文件对象与Properties类
File类 用来将文件或目录封装成对象 方便对文件或目录信息进行处理 File对象可以作为参数传递给流进行操作 File类常用方法 创建 booleancreateNewFile():创建新文件,如果 ...
- JAVA IO流编程 实现文件的写入、写出以及拷贝
一.流的概念 流:数据在数据源(文件)和程序(内存)之间经历的路径. 输入流:数据从数据源(文件)到程序(内存)的路径. 输出流:数据从程序(内存)到数据源(文件)的路径. 以内存为参照,如果数据向内 ...
- C#常用IO流与读写文件 (转)
源自https://www.cnblogs.com/liyangLife/p/4797583.html 谢谢 1.文件系统 (1)文件系统类的介绍 文件操作类大都在System.IO命名空间里.Fil ...
随机推荐
- 今日实际操作----Dart Mac开发与运行环境配置 配置.bash_profile
Mac 打开.编辑 .bash_profile 文件 一般在Mac上配置环境变量时经常要创建.编辑 .bash_profile文件.创建该文件时一般都会选择在当前用户目录下,即Mac下的.bash_p ...
- ubuntu20.04 gnome桌面系统添加开机自启动GUI程序
在终端执行 gnome-session-properties,点击添加自己的脚本或执行文件,便可以在用户登录后自动执行.
- 【分析笔记】SiliconLabs EFR32BG22 Bluetooth Mesh SensorClient 源码分析
硬件环境: SLTB010A(BRD4184A Rev A02 / EFR32BG22C224F512IM40) 软件环境: SimplicityStudio5/gecko_sdk_3.2.3 分析工 ...
- 【学习日志】volatile关键字的作用
消除指令重排序 保证了不同线程对变量进行操作时的可见性,cpu对变量值修改后,其他线程读取变量信息时从内存读取而非cpu缓存
- (原创)【B4A】一步一步入门03:APP名称、图标等信息修改
一.前言 上篇 (原创)[B4A]一步一步入门02:可视化界面设计器.控件的使用 中我们已经了解了B4A程序的基本框架,现在我们还进一步讲解. 本篇文章会讲解如何修改APP的名称.图标等信息,以让一个 ...
- ROS入门:话题
1.listener.cpp #include "ros/ros.h" #include "std_msgs/String.h" //回调函数,接收到话题后进入 ...
- 【TS】object类型
object是一个对象,在ts中定义对象类型的语法为:let 变量名 :object = { } 在object类型中,对象内部定义的值是不受类型约束的,只要是一个object类型即可,例如: let ...
- 搭建Git服务器教程(整理自腾讯云开发者实验室)
搭建Git服务器教程(整理自腾讯云开发者实验室) 下载安装 Git Git 是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. 此实验以 CentOS 7.2 x64 的系统 ...
- JZOJ 6799. 【2014广州市选day2】game
题目 思路 呵呵,正解并不是什么神奇的方法 而是最原始的最粗暴的最有用的最万能的----搜索 依题模拟即可 \(Code\) #include<cstdio> #include<cs ...
- JZOJ 5353. 【NOIP2017提高A组模拟9.9】村通网
题目 为了加快社会主义现代化,建设新农村,农夫约(Farmer Jo)决定给农庄里每座建筑都连上互联网,方便未来随时随地网购农药. 他的农庄很大,有N 座建筑,但地理位置偏僻,网络信号很差. 一座建筑 ...