广告
返回顶部
首页 > 资讯 > 后端开发 > Python >SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统
  • 781
分享到

SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统

2024-04-02 19:04:59 781人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录想法实现工程pom配置文件TranscodeConfig,用于控制转码的一些参数Mediainfo,封装视频的一些基础信息FFmpegUtils,工具类封装FFmpeg的一些操作

想法

客户端上传视频到服务器,服务器对视频进行切片后,返回m3u8,封面等访问路径。可以在线的播放。 服务器可以对视频做一些简单的处理,例如裁剪,封面的截取时间。

视频转码文件夹的定义


喜羊羊与灰太狼  // 文件夹名称就是视频标题
  |-index.m3u8  // 主m3u8文件,里面可以配置多个码率的播放地址
  |-poster.jpg  // 截取的封面图片
  |-ts      // 切片目录
    |-index.m3u8  // 切片播放索引
    |-key   // 播放需要解密的AES KEY

实现

需要先在本机安装FFmpeg,并且添加到PATH环境变量,如果不会先通过搜索引擎找找资料

工程

pom


<project xmlns="Http://Maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.demo</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>


	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-WEB</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-Tomcat</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-undertow</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>
		<dependency>
			<groupId>com.Google.code.gson</groupId>
			<artifactId>gson</artifactId>
		</dependency>

	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<executable>true</executable>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

配置文件


server:
  port: 80


app:
  # 存储转码视频的文件夹地址
  video-folder: "C:\\Users\\Administrator\\Desktop\\tmp"

spring:
  servlet:
    multipart:
      enabled: true
      # 不限制文件大小
      max-file-size: -1
      # 不限制请求体大小
      max-request-size: -1
      # 临时IO目录
      location: "${java.io.tmpdir}"
      # 不延迟解析
      resolve-lazily: false
      # 超过1Mb,就IO到临时目录
      file-size-threshold: 1MB
  web:
    resources:
      static-locations:
        - "classpath:/static/"
        - "file:${app.video-folder}" # 把视频文件夹目录,添加到静态资源目录列表

TranscodeConfig,用于控制转码的一些参数


package com.demo.ffmpeg;

public class TranscodeConfig {
	private String poster;				// 截取封面的时间			HH:mm:ss.[SSS]
	private String tsSeconds;			// ts分片大小,单位是秒
	private String cutStart;			// 视频裁剪,开始时间		HH:mm:ss.[SSS]
	private String cutEnd;				// 视频裁剪,结束时间		HH:mm:ss.[SSS]
	public String getPoster() {
		return poster;
	}

	public void setPoster(String poster) {
		this.poster = poster;
	}

	public String getTsSeconds() {
		return tsSeconds;
	}

	public void setTsSeconds(String tsSeconds) {
		this.tsSeconds = tsSeconds;
	}

	public String getCutStart() {
		return cutStart;
	}

	public void setCutStart(String cutStart) {
		this.cutStart = cutStart;
	}

	public String getCutEnd() {
		return cutEnd;
	}

	public void setCutEnd(String cutEnd) {
		this.cutEnd = cutEnd;
	}

	@Override
	public String toString() {
		return "TranscodeConfig [poster=" + poster + ", tsSeconds=" + tsSeconds + ", cutStart=" + cutStart + ", cutEnd="
				+ cutEnd + "]";
	}
}

MediaInfo,封装视频的一些基础信息


package com.demo.ffmpeg;

import java.util.List;

import com.google.gson.annotations.SerializedName;

public class MediaInfo {
	public static class FORMat {
		@SerializedName("bit_rate")
		private String bitRate;
		public String getBitRate() {
			return bitRate;
		}
		public void setBitRate(String bitRate) {
			this.bitRate = bitRate;
		}
	}

	public static class Stream {
		@SerializedName("index")
		private int index;

		@SerializedName("codec_name")
		private String codecName;

		@SerializedName("codec_long_name")
		private String codecLongame;

		@SerializedName("profile")
		private String profile;
	}
	
	// ----------------------------------

	@SerializedName("streams")
	private List<Stream> streams;

	@SerializedName("format")
	private Format format;

	public List<Stream> getStreams() {
		return streams;
	}

	public void setStreams(List<Stream> streams) {
		this.streams = streams;
	}

	public Format getFormat() {
		return format;
	}

	public void setFormat(Format format) {
		this.format = format;
	}
}

FFmpegUtils,工具类封装FFmpeg的一些操作


package com.demo.ffmpeg;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.NIO.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.KeyGenerator;

import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.google.gson.Gson;


public class FFmpegUtils {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(FFmpegUtils.class);
	
	
	// 跨平台换行符
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
	
	
	private static byte[] genAesKey ()  {
		try {
			KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
			keyGenerator.init(128);
			return keyGenerator.generateKey().getEncoded();
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
	}
	
	
	private static Path genKeyInfo(String folder) throws IOException {
		// AES 密钥
		byte[] aesKey = genAesKey();
		// AES 向量
		String iv = Hex.encodeHexString(genAesKey());
		
		// key 文件写入
		Path keyFile = Paths.get(folder, "key");
		Files.write(keyFile, aesKey, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

		// key_info 文件写入
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("key").append(LINE_SEPARATOR);					// m3u8加载key文件网络路径
		stringBuilder.append(keyFile.toString()).append(LINE_SEPARATOR);	// FFmeg加载key_info文件路径
		stringBuilder.append(iv);											// ASE 向量
		
		Path keyInfo = Paths.get(folder, "key_info");
		
		Files.write(keyInfo, stringBuilder.toString().getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
		
		return keyInfo;
	}
	
	
	private static void genIndex(String file, String indexPath, String bandWidth) throws IOException {
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("#EXTM3U").append(LINE_SEPARATOR);
		stringBuilder.append("#EXT-X-STREAM-INF:BANDWIDTH=" + bandWidth).append(LINE_SEPARATOR);  // 码率
		stringBuilder.append(indexPath);
		Files.write(Paths.get(file), stringBuilder.toString().getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
	}
	
	
	public static void transcodeToM3u8(String source, String destFolder, TranscodeConfig config) throws IOException, InterruptedException {
		
		// 判断源视频是否存在
		if (!Files.exists(Paths.get(source))) {
			throw new IllegalArgumentException("文件不存在:" + source);
		}
		
		// 创建工作目录
		Path workDir = Paths.get(destFolder, "ts");
		Files.createDirectories(workDir);
		
		// 在工作目录生成KeyInfo文件
		Path keyInfo = genKeyInfo(workDir.toString());
		
		// 构建命令
		List<String> commands = new ArrayList<>();
		commands.add("ffmpeg");			
		commands.add("-i")						;commands.add(source);					// 源文件
		commands.add("-c:v")					;commands.add("libx264");				// 视频编码为H264
		commands.add("-c:a")					;commands.add("copy");					// 音频直接copy
		commands.add("-hls_key_info_file")		;commands.add(keyInfo.toString());		// 指定密钥文件路径
		commands.add("-hls_time")				;commands.add(config.getTsSeconds());	// ts切片大小
		commands.add("-hls_playlist_type")		;commands.add("vod");					// 点播模式
		commands.add("-hls_segment_filename")	;commands.add("%06d.ts");				// ts切片文件名称
		
		if (StringUtils.hasText(config.getCutStart())) {
			commands.add("-ss")					;commands.add(config.getCutStart());	// 开始时间
		}
		if (StringUtils.hasText(config.getCutEnd())) {
			commands.add("-to")					;commands.add(config.getCutEnd());		// 结束时间
		}
		commands.add("index.m3u8");														// 生成m3u8文件
		
		// 构建进程
		Process process = new ProcessBuilder()
			.command(commands)
			.directory(workDir.toFile())
			.start()
			;
		
		// 读取进程标准输出
		new Thread(() -> {
			try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
					LOGGER.info(line);
				}
			} catch (IOException e) {
			}
		}).start();
		
		// 读取进程异常输出
		new Thread(() -> {
			try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
					LOGGER.info(line);
				}
			} catch (IOException e) {
			}
		}).start();
		
		
		// 阻塞直到任务结束
		if (process.waitFor() != 0) {
			throw new RuntimeException("视频切片异常");
		}
		
		// 切出封面
		if (!screenShots(source, String.join(File.separator, destFolder, "poster.jpg"), config.getPoster())) {
			throw new RuntimeException("封面截取异常");
		}
		
		// 获取视频信息
		MediaInfo mediaInfo = getMediaInfo(source);
		if (mediaInfo == null) {
			throw new RuntimeException("获取媒体信息异常");
		}
		
		// 生成index.m3u8文件
		genIndex(String.join(File.separator, destFolder, "index.m3u8"), "ts/index.m3u8", mediaInfo.getFormat().getBitRate());
		
		// 删除keyInfo文件
		Files.delete(keyInfo);
	}
	
	
	public static MediaInfo getMediaInfo(String source) throws IOException, InterruptedException {
		List<String> commands = new ArrayList<>();
		commands.add("ffprobe");	
		commands.add("-i")				;commands.add(source);
		commands.add("-show_format");
		commands.add("-show_streams");
		commands.add("-print_format")	;commands.add("JSON");
		
		Process process = new ProcessBuilder(commands)
				.start();
		 
		MediaInfo mediaInfo = null;
		
		try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
			mediaInfo = new Gson().fromjson(bufferedReader, MediaInfo.class);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if (process.waitFor() != 0) {
			return null;
		}
		
		return mediaInfo;
	}
	
	
	public static boolean screenShots(String source, String file, String time) throws IOException, InterruptedException {
		
		List<String> commands = new ArrayList<>();
		commands.add("ffmpeg");	
		commands.add("-i")				;commands.add(source);
		commands.add("-ss")				;commands.add(time);
		commands.add("-y");
		commands.add("-q:v")			;commands.add("1");
		commands.add("-frames:v")		;commands.add("1");
		commands.add("-f");				;commands.add("image2");
		commands.add(file);
		
		Process process = new ProcessBuilder(commands)
					.start();
		
		// 读取进程标准输出
		new Thread(() -> {
			try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
					LOGGER.info(line);
				}
			} catch (IOException e) {
			}
		}).start();
		
		// 读取进程异常输出
		new Thread(() -> {
			try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
					LOGGER.error(line);
				}
			} catch (IOException e) {
			}
		}).start();
		
		return process.waitFor() == 0;
	}
}


UploadController,执行转码操作


package com.demo.web.controller;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.demo.ffmpeg.FFmpegUtils;
import com.demo.ffmpeg.TranscodeConfig;

@RestController
@RequestMapping("/upload")
public class UploadController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);
	
	@Value("${app.video-folder}")
	private String videoFolder;

	private Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"));
	
	
	@PostMapping
	public Object upload (@RequestPart(name = "file", required = true) MultipartFile video,
						@RequestPart(name = "config", required = true) TranscodeConfig transcodeConfig) throws IOException {
		
		LOGGER.info("文件信息:title={}, size={}", video.getOriginalFilename(), video.getSize());
		LOGGER.info("转码配置:{}", transcodeConfig);
		
		// 原始文件名称,也就是视频的标题
		String title = video.getOriginalFilename();
		
		// io到临时文件
		Path tempFile = tempDir.resolve(title);
		LOGGER.info("io到临时文件:{}", tempFile.toString());
		
		try {
			
			video.transferTo(tempFile);
			
			// 删除后缀
			title = title.substring(0, title.lastIndexOf("."));
			
			// 按照日期生成子目录
			String today = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDate.now());
			
			// 尝试创建视频目录
			Path targetFolder = Files.createDirectories(Paths.get(videoFolder, today, title));
			
			LOGGER.info("创建文件夹目录:{}", targetFolder);
			Files.createDirectories(targetFolder);
			
			// 执行转码操作
			LOGGER.info("开始转码");
			try {
				FFmpegUtils.transcodeToM3u8(tempFile.toString(), targetFolder.toString(), transcodeConfig);
			} catch (Exception e) {
				LOGGER.error("转码异常:{}", e.getMessage());
				Map<String, Object> result = new HashMap<>();
				result.put("success", false);
				result.put("message", e.getMessage());
				return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
			}
			
			// 封装结果
			Map<String, Object> videoInfo = new HashMap<>();
			videoInfo.put("title", title);
			videoInfo.put("m3u8", String.join("/", "", today, title, "index.m3u8"));
			videoInfo.put("poster", String.join("/", "", today, title, "poster.jpg"));
			
			Map<String, Object> result = new HashMap<>();
			result.put("success", true);
			result.put("data", videoInfo);
			return result;
		} finally {
			// 始终删除临时文件
			Files.delete(tempFile);
		}
	}
}

index.html,客户端


<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
    </head>
    <body>
        选择转码文件: <input name="file" type="file" accept="video/*" onchange="upload(event)">
        <hr/>
		<video id="video"  width="500" height="400" controls="controls"></video>
    </body>
    <script>
    
   		const video = document.getElementById('video');
    	
        function upload (e){
            let files = e.target.files
            if (!files) {
                return
            }
            
            // TODO 转码配置这里固定死了
            var transCodeConfig = {
            	poster: "00:00:00.001", // 截取第1毫秒作为封面
            	tsSeconds: 15,				
            	cutStart: "",
            	cutEnd: ""
            }
            
            // 执行上传
            let formData = new FormData();
            formData.append("file", files[0])
            formData.append("config", new Blob([JSON.stringify(transCodeConfig)], {type: "application/json; charset=utf-8"}))

            fetch('/upload', {
                method: 'POST',
                body: formData
            })
            .then(resp =>  resp.json())
            .then(message => {
            	if (message.success){
            		// 设置封面
            		video.poster = message.data.poster;
            		
            		// 渲染到播放器
            		var hls = new Hls();
        		    hls.loadSource(message.data.m3u8);
        		    hls.attachMedia(video);
            	} else {
            		alert("转码异常,详情查看控制台");
            		console.log(message.message);
            	}
            })
            .catch(err => {
            	alert("转码异常,详情查看控制台");
                throw err
            })
        }
    </script>
</html>

使用

  1. 在配置文件中,配置到本地视频目录后启动
  2. 打开页面 localhost
  3. 点击【选择文件】,选择一个视频文件进行上传,等待执行完毕(没有做加载动画)
  4. 后端转码完成后,会自动把视频信息加载到播放器,此时可以手动点击播放按钮进行播放

可以打开控制台,查看上传进度,以及播放时的网络加载信息

以上就是SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统的详细内容,更多关于SpringBoot 实现M3U8切片转码的资料请关注编程网其它相关文章!

--结束END--

本文标题: SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统

本文链接: https://www.lsjlt.com/news/126288.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SpringBoot + FFmpeg实现一个简单的M3U8切片转码系统
    目录想法实现工程pom配置文件TranscodeConfig,用于控制转码的一些参数MediaInfo,封装视频的一些基础信息FFmpegUtils,工具类封装FFmpeg的一些操作...
    99+
    2022-11-12
  • 【Python】实现一个简单的区块链系统
    本文章利用 Python 实现一个简单的功能较为完善的区块链系统(包括区块链结构、账户、钱包、转账),采用的共识机制是 POW。 一、区块与区块链结构 Block.py import hashlibfrom datetime import ...
    99+
    2023-09-22
    区块链 python 共识算法
  • 用Python实现一个简单的用户系统
    目录前言正文总结前言  如标题所说,这是一个非常简单的程序,并不涉及任何高深的学问,更适合一些刚入手Python的新人研究一下基础内容的用法,此案列对于有些编程经验的人来讲...
    99+
    2022-11-13
  • 基于C++实现一个简单的音乐系统
    目录一、前言二、实现步骤三、代码实现四、讲解程序一、前言 2022临近尾声,2023即将来临。 过去的一年,我们同努力,我们共欢笑.。 每一次成功都蕴藏着我们辛勤的劳动。 新的一年即...
    99+
    2022-12-29
    C++音乐系统 C++声音系统 C++ Beep
  • 基于Python实现一个简单的学生管理系统
    目录序言代码实战效果展示序言 小学妹说要毕业了,学了一学期Python等于没学,现在要做毕设做不出来,让我帮帮她,晚上去她家吃夜宵。 当时我心想,这不是分分钟的事情,还要去她家,男孩...
    99+
    2022-12-31
    Python实现学生管理系统 Python学生管理系统 Python管理系统
  • 如何使用MySQL和Python实现一个简单的博客系统
    要使用MySQL和Python实现一个简单的博客系统,可以按照以下步骤进行:1. 安装MySQL数据库和Python的MySQL库:...
    99+
    2023-10-20
    MySQL
  • 如何使用MySQL和Ruby实现一个简单的投票系统
    如何使用MySQL和Ruby实现一个简单的投票系统投票系统是一种常见的在线应用程序,用于收集用户对某个问题或主题的意见。在本文中,将介绍如何使用MySQL数据库和Ruby编程语言来实现一个简单的投票系统。首先,我们需要准备环境。确保已经安装...
    99+
    2023-10-22
    MySQL Ruby 投票系统
  • 如何使用PHP实现一个简单的在线问答系统
    随着互联网的普及和发展,各种在线问答系统也应运而生,成为人们获取信息和解决问题的有效途径之一。本文将介绍如何使用PHP语言来实现一个简单的在线问答系统,并提供具体的代码示例供读者参考。一、系统需求分析在开始开发之前,我们首先需要明确系统的需...
    99+
    2023-10-21
    PHP实现 简单 问答系统
  • 如何利用C++实现一个简单的在线考试系统?
    如何利用C++实现一个简单的在线考试系统?随着网络技术和计算机科学的快速发展,在线教育和远程学习越来越受到人们的重视。而在线考试系统则成为了教育机构和企业用于评估学生和员工能力的重要工具。本文将介绍如何利用C++编程语言实现一个简单的在线考...
    99+
    2023-11-03
    C++ 系统实现 在线考试
  • 如何使用C++实现一个简单的文件管理系统?
    如何使用C++实现一个简单的文件管理系统?概述:文件管理系统是计算机中非常重要的一个功能模块,它负责对计算机中的文件进行创建、修改、删除等操作。本文将介绍如何使用C++编程语言实现一个简单的文件管理系统,通过该系统,可以实现对文件的基本管理...
    99+
    2023-11-02
    C++ 实现 文件管理系统
  • 如何利用C++实现一个简单的电影评分系统?
    如何利用C++实现一个简单的电影评分系统?电影评分系统是一个能够让用户对所观看的电影进行评分和评论的系统。在这个系统中,用户可以选择电影并针对其进行评分,同时也可以查看其他用户的评分和评论。在这篇文章中,我们将介绍如何使用C++编程语言实现...
    99+
    2023-11-02
    C++电影评分系统
  • 如何利用C++实现一个简单的飞机订票系统?
    如何利用C++实现一个简单的飞机订票系统?随着空中交通的发展和人们对舒适旅行的需求增加,飞机订票系统变得越来越重要。在这篇文章中,我们将学习如何利用C++编程语言来实现一个简单的飞机订票系统。这个系统将允许用户查询航班信息、选择座位、预订和...
    99+
    2023-11-02
    C++ 飞机 订票系统
  • 如何利用C++实现一个简单的航班查询系统?
    如何利用C ++实现一个简单的航班查询系统?航班查询系统是一个广泛应用于航空公司和旅行社等行业的软件系统。通过这个系统,用户可以查询航班的相关信息,包括航班号、出发时间、到达时间、航班公司等。利用C++语言,我们可以实现一个简单而功能完善的...
    99+
    2023-11-02
    C++ 系统实现 航班查询
  • 如何利用C++实现一个简单的餐厅点餐系统?
    如何利用C++实现一个简单的餐厅点餐系统?餐厅点餐系统是现代餐饮行业中非常重要的一环。通过使用计算机程序来管理和处理点餐、结算等操作,可以提高餐厅的效率和服务质量。本文将介绍如何利用C++编程语言实现一个简单的餐厅点餐系统。首先,我们需要定...
    99+
    2023-11-02
    系统 餐厅 点餐
  • 如何利用C++实现一个简单的火车票订购系统?
    随着人们工作和生活方式的变化,越来越多的人选择乘坐火车出行。因此,实现一个简单的火车票订购系统可以方便用户预订车票,同时也可以提高工作效率,减少人力投入。本文将介绍如何使用C++实现一个简单的火车票订购系统,以方便读者学习和实践。一、需求分...
    99+
    2023-11-03
    C++ 火车票 订购系统
  • 怎么用VUE实现一个简单的学生信息管理系统
    本篇内容主要讲解“怎么用VUE实现一个简单的学生信息管理系统”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用VUE实现一个简单的学生信息管理系统”吧!一、主要功能本次任务主要是使用 VUE ...
    99+
    2023-06-27
  • 如何使用C++实现一个简单的图书馆管理系统?
    如何使用C++实现一个简单的图书馆管理系统?图书馆是一个重要的知识和文化传播场所,而一个高效的图书馆管理系统能够提升图书馆的运作效率,方便读者借阅图书和管理图书馆资源。本文将介绍如何使用C++编程语言实现一个简单的图书馆管理系统。首先,我们...
    99+
    2023-11-02
    图书馆 C++ 管理系统
  • HTML+JavaScript+Servlet+MySQL实现一个简单的学生信息管理系统
    话不多说,先上效果图 1、登录界面 学生信息管理界面 展示信息 添加信息 修改信息 3、课程信息管理界面 4、成绩信息管理界面 部分代码 登录 学生信...
    99+
    2023-10-11
    servlet mysql html javascript
  • 怎么使用PHP和数据库实现一个简单的队列系统
    本篇内容介绍了“怎么使用PHP和数据库实现一个简单的队列系统”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、数据库队列的基本原理数据库队列...
    99+
    2023-07-06
  • Python使用multiprocessing实现一个最简单的分布式作业调度系统
    mutilprocess像线程一样管理进程,这个是mutilprocess的核心,他与threading很是相像,对多核CPU的利用率会比threading好的多。 介绍 Python的multiproc...
    99+
    2022-06-04
    作业 分布式 最简单
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作