鸿蒙OSUniApp 实现的语音输入与语音识别功能#三方框架 #Uniapp

UniApp 实现的语音输入与语音识别功能

最近在开发跨平台应用时,客户要求添加语音输入功能以提升用户体验。经过一番调研和实践,我成功在UniApp项目中实现了语音输入与识别功能,现将过程和方法分享出来,希望对有类似需求的开发者有所帮助。

为什么需要语音输入功能?

随着移动设备的普及,语音交互已成为一种高效的人机交流方式。与传统的文字输入相比,语音输入具有以下优势:

  1. 操作便捷:免去键盘敲击,尤其适合单手操作或行走等场景
  2. 输入高效:语音输入速度通常快于手动输入
  3. 提升体验:为特定人群(如老年人、视障人士)提供便利
  4. 解放双手:适用于驾车、做家务等无法腾出手打字的场景

在商业应用中,语音输入可以显著降低用户的操作门槛,提高转化率和用户留存。

技术方案选型

在UniApp环境中实现语音识别,主要有三种方案:

  1. 使用原生插件:调用各平台的原生语音识别能力
  2. 对接云服务:接入第三方语音识别API(如百度、讯飞等)
  3. Web API:在H5平台利用Web Speech API

经过对比和测试,我最终采用了混合方案:

  • 在App平台使用原生插件获取最佳体验
  • 在微信小程序使用微信自带的语音识别能力
  • 在H5平台尝试使用Web Speech API,不支持时降级为云服务API

实现步骤

1. App端实现(基于原生插件)

首先需要安装语音识别插件。我选择了市场上比较成熟的speech-baidu插件,这是基于百度语音识别SDK封装的UniApp插件。

安装插件后,在manifest.json中配置:

"app-plus": {"plugins": {"speech": {"baidu": {"appid": "你的百度语音识别AppID","apikey": "你的API Key","secretkey": "你的Secret Key"}}},"distribute": {"android": {"permissions": ["<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>","<uses-permission android:name=\"android.permission.INTERNET\"/>"]}}
}

接下来创建语音识别组件:

<template><view class="voice-input-container"><view class="voice-btn" :class="{ 'recording': isRecording }"@touchstart="startRecord" @touchend="stopRecord"@touchcancel="cancelRecord"><image :src="isRecording ? '/static/mic-active.png' : '/static/mic.png'" mode="aspectFit"></image><text>{{ isRecording ? '松开结束' : '按住说话' }}</text></view><view v-if="isRecording" class="recording-tip"><text>正在聆听...</text><view class="wave-container"><view v-for="(item, index) in waveItems" :key="index" class="wave-item":style="{ height: item + 'rpx' }"></view></view></view></view>
</template><script>
// #ifdef APP-PLUS
const speechPlugin = uni.requireNativePlugin('speech-baidu');
// #endifexport default {name: 'VoiceInput',data() {return {isRecording: false,timer: null,waveItems: [10, 15, 20, 25, 30, 25, 20, 15, 10]}},props: {lang: {type: String,default: 'zh'  // zh: 中文, en: 英文},maxDuration: {type: Number,default: 60  // 最长录音时间,单位秒}},methods: {startRecord() {if (this.isRecording) return;// 申请录音权限uni.authorize({scope: 'scope.record',success: () => {this.isRecording = true;this.startWaveAnimation();// #ifdef APP-PLUSspeechPlugin.start({vadEos: 3000,  // 静音超时时间language: this.lang === 'zh' ? 'zh-cn' : 'en-us'}, (res) => {if (res.errorCode === 0) {// 识别结果this.$emit('result', res.result);} else {uni.showToast({title: `识别失败: ${res.errorCode}`,icon: 'none'});}this.isRecording = false;this.stopWaveAnimation();});// #endif// 设置最长录制时间this.timer = setTimeout(() => {if (this.isRecording) {this.stopRecord();}}, this.maxDuration * 1000);},fail: () => {uni.showToast({title: '请授权录音权限',icon: 'none'});}});},stopRecord() {if (!this.isRecording) return;// #ifdef APP-PLUSspeechPlugin.stop();// #endifclearTimeout(this.timer);this.isRecording = false;this.stopWaveAnimation();},cancelRecord() {if (!this.isRecording) return;// #ifdef APP-PLUSspeechPlugin.cancel();// #endifclearTimeout(this.timer);this.isRecording = false;this.stopWaveAnimation();},// 波形动画startWaveAnimation() {this.waveAnimTimer = setInterval(() => {this.waveItems = this.waveItems.map(() => Math.floor(Math.random() * 40) + 10);}, 200);},stopWaveAnimation() {clearInterval(this.waveAnimTimer);this.waveItems = [10, 15, 20, 25, 30, 25, 20, 15, 10];}},beforeDestroy() {this.cancelRecord();}
}
</script><style scoped>
.voice-input-container {width: 100%;
}.voice-btn {width: 200rpx;height: 200rpx;border-radius: 100rpx;background-color: #f5f5f5;display: flex;flex-direction: column;align-items: center;justify-content: center;margin: 0 auto;
}.voice-btn.recording {background-color: #e1f5fe;box-shadow: 0 0 20rpx rgba(0, 120, 255, 0.5);
}.voice-btn image {width: 80rpx;height: 80rpx;margin-bottom: 10rpx;
}.recording-tip {margin-top: 30rpx;text-align: center;
}.wave-container {display: flex;justify-content: center;align-items: flex-end;height: 80rpx;margin-top: 20rpx;
}.wave-item {width: 8rpx;background-color: #1890ff;margin: 0 5rpx;border-radius: 4rpx;transition: height 0.2s;
}
</style>

2. 微信小程序实现

微信小程序提供了原生的语音识别API,使用非常方便:

// 在小程序环境下的代码
startRecord() {// #ifdef MP-WEIXINthis.isRecording = true;this.startWaveAnimation();const recorderManager = wx.getRecorderManager();recorderManager.onStart(() => {console.log('录音开始');});recorderManager.onStop((res) => {this.isRecording = false;this.stopWaveAnimation();// 将录音文件发送到微信后台识别wx.showLoading({ title: '识别中...' });const { tempFilePath } = res;wx.uploadFile({url: 'https://api.weixin.qq.com/cgi-bin/media/voice/translatecontent',filePath: tempFilePath,name: 'media',formData: {access_token: this.accessToken,format: 'mp3',voice_id: Date.now(),lfrom: this.lang === 'zh' ? 'zh_CN' : 'en_US',lto: 'zh_CN'},success: (uploadRes) => {wx.hideLoading();const data = JSON.parse(uploadRes.data);if (data.errcode === 0) {this.$emit('result', data.result);} else {uni.showToast({title: `识别失败: ${data.errmsg}`,icon: 'none'});}},fail: () => {wx.hideLoading();uni.showToast({title: '语音识别失败',icon: 'none'});}});});recorderManager.start({duration: this.maxDuration * 1000,sampleRate: 16000,numberOfChannels: 1,encodeBitRate: 48000,format: 'mp3'});// #endif
},stopRecord() {// #ifdef MP-WEIXINwx.getRecorderManager().stop();// #endif// ...与App端相同的代码...
}

需要注意的是,微信小程序的语音识别需要获取access_token,这通常需要在后端实现并提供接口。

3. H5端实现

在H5端,我们可以利用Web Speech API来实现语音识别,当浏览器不支持时则降级为云服务API:

startRecord() {// #ifdef H5this.isRecording = true;this.startWaveAnimation();// 检查浏览器是否支持Speech Recognitionif ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;this.recognition = new SpeechRecognition();this.recognition.lang = this.lang === 'zh' ? 'zh-CN' : 'en-US';this.recognition.continuous = false;this.recognition.interimResults = false;this.recognition.onresult = (event) => {const result = event.results[0][0].transcript;this.$emit('result', result);};this.recognition.onerror = (event) => {uni.showToast({title: `识别错误: ${event.error}`,icon: 'none'});};this.recognition.onend = () => {this.isRecording = false;this.stopWaveAnimation();};this.recognition.start();} else {// 不支持Web Speech API,调用云服务APIthis.useCloudSpeechAPI();}// #endif// 设置最长录制时间this.timer = setTimeout(() => {if (this.isRecording) {this.stopRecord();}}, this.maxDuration * 1000);
},stopRecord() {// #ifdef H5if (this.recognition) {this.recognition.stop();}// #endif// ...与App端相同的代码...
},useCloudSpeechAPI() {// 这里实现降级方案,调用后端接口进行语音识别uni.chooseFile({count: 1,type: 'file',extension: ['.mp3', '.wav'],success: (res) => {const tempFilePath = res.tempFilePaths[0];// 上传音频文件到后端进行识别uni.uploadFile({url: this.apiBaseUrl + '/speech/recognize',filePath: tempFilePath,name: 'audio',formData: {lang: this.lang},success: (uploadRes) => {const data = JSON.parse(uploadRes.data);if (data.code === 0) {this.$emit('result', data.result);} else {uni.showToast({title: `识别失败: ${data.msg}`,icon: 'none'});}},complete: () => {this.isRecording = false;this.stopWaveAnimation();}});}});
}

4. 通用接口封装

为了让调用方便,我封装了一个统一的API:

// 在 utils/speech.js 中
const Speech = {// 开始语音识别startRecognize(options) {const { lang = 'zh', success, fail, complete } = options;// #ifdef APP-PLUSconst speechPlugin = uni.requireNativePlugin('speech-baidu');speechPlugin.start({vadEos: 3000,language: lang === 'zh' ? 'zh-cn' : 'en-us'}, (res) => {if (res.errorCode === 0) {success && success(res.result);} else {fail && fail(res);}complete && complete();});return {stop: () => speechPlugin.stop(),cancel: () => speechPlugin.cancel()};// #endif// #ifdef MP-WEIXIN// 微信小程序实现逻辑// ...// #endif// #ifdef H5// H5实现逻辑// ...// #endif}
};export default Speech;

实战案例:聊天应用中的语音输入

现在,我们来看一个实际应用场景 - 在聊天应用中添加语音输入功能:

<template><view class="chat-input-container"><view class="chat-tools"><image :src="isVoiceMode ? '/static/keyboard.png' : '/static/mic.png'" @tap="toggleInputMode"></image><image src="/static/emoji.png" @tap="showEmojiPicker"></image></view><view v-if="!isVoiceMode" class="text-input"><textareav-model="message"auto-heightplaceholder="请输入消息...":focus="textFocus"@focus="onFocus"@blur="onBlur"></textarea></view><view v-else class="voice-input"><voice-input @result="onVoiceResult"></voice-input></view><button class="send-btn" :disabled="!message.trim()" @tap="sendMessage">发送</button></view>
</template><script>
import VoiceInput from '@/components/voice-input/voice-input.vue';export default {components: {VoiceInput},data() {return {message: '',isVoiceMode: false,textFocus: false};},methods: {toggleInputMode() {this.isVoiceMode = !this.isVoiceMode;if (!this.isVoiceMode) {this.$nextTick(() => {this.textFocus = true;});}},onVoiceResult(result) {this.message = result;this.isVoiceMode = false;},sendMessage() {if (!this.message.trim()) return;this.$emit('send', this.message);this.message = '';},onFocus() {this.textFocus = true;},onBlur() {this.textFocus = false;},showEmojiPicker() {// 显示表情选择器}}
};
</script><style>
.chat-input-container {display: flex;align-items: center;padding: 20rpx;border-top: 1rpx solid #eee;background-color: #fff;
}.chat-tools {display: flex;margin-right: 20rpx;
}.chat-tools image {width: 60rpx;height: 60rpx;margin-right: 20rpx;
}.text-input {flex: 1;background-color: #f5f5f5;border-radius: 10rpx;padding: 10rpx 20rpx;
}.text-input textarea {width: 100%;min-height: 60rpx;max-height: 240rpx;
}.voice-input {flex: 1;display: flex;justify-content: center;
}.send-btn {width: 140rpx;height: 80rpx;line-height: 80rpx;font-size: 28rpx;margin-left: 20rpx;padding: 0;background-color: #1890ff;color: #fff;
}.send-btn[disabled] {background-color: #ccc;
}
</style>

性能优化和注意事项

在实际开发中,我遇到了一些需要特别注意的问题:

1. 权限处理

语音识别需要麦克风权限,不同平台的权限处理方式不同:

// 统一请求录音权限
requestAudioPermission() {return new Promise((resolve, reject) => {// #ifdef APP-PLUSconst permissions = ['android.permission.RECORD_AUDIO'];plus.android.requestPermissions(permissions,function(e) {if (e.granted.length === permissions.length) {resolve();} else {reject(new Error('未授予录音权限'));}},function(e) {reject(e);});// #endif// #ifdef MP-WEIXIN || MP-BAIDUuni.authorize({scope: 'scope.record',success: () => resolve(),fail: (err) => reject(err)});// #endif// #ifdef H5if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {navigator.mediaDevices.getUserMedia({ audio: true }).then(() => resolve()).catch(err => reject(err));} else {reject(new Error('浏览器不支持录音功能'));}// #endif});
}

2. 流量控制

语音识别需要上传音频数据,在移动网络下会消耗流量:

// 检查网络环境并提示用户
checkNetwork() {uni.getNetworkType({success: (res) => {if (res.networkType === '2g' || res.networkType === '3g') {uni.showModal({title: '流量提醒',content: '当前处于移动网络环境,语音识别可能消耗较多流量,是否继续?',success: (confirm) => {if (confirm.confirm) {this.startSpeechRecognition();}}});} else {this.startSpeechRecognition();}}});
}

3. 性能优化

长时间语音识别会增加内存和电量消耗,需要做好优化:

// 设置最大录音时长和自动结束
setupMaxDuration() {if (this.timer) {clearTimeout(this.timer);}this.timer = setTimeout(() => {if (this.isRecording) {uni.showToast({title: '录音时间过长,已自动结束',icon: 'none'});this.stopRecord();}}, this.maxDuration * 1000);
}// 空闲自动停止
setupVAD() {// 监测静音,如果用户停止说话3秒,自动结束录音let lastAudioLevel = 0;let silenceCounter = 0;this.vadTimer = setInterval(() => {// 获取当前音量const currentLevel = this.getAudioLevel();if (Math.abs(currentLevel - lastAudioLevel) < 0.05) {silenceCounter++;if (silenceCounter > 30) { // 3秒 (30 * 100ms)this.stopRecord();}} else {silenceCounter = 0;}lastAudioLevel = currentLevel;}, 100);
}

增强功能:语音合成(TTS)

除了语音识别外,语音合成(Text-to-Speech)也是很有用的功能,可以将文本转换为语音:

// 语音合成
textToSpeech(text, options = {}) {const { lang = 'zh', speed = 5, volume = 5 } = options;// #ifdef APP-PLUSconst speechPlugin = uni.requireNativePlugin('speech-baidu');return new Promise((resolve, reject) => {speechPlugin.textToSpeech({text,language: lang === 'zh' ? 'zh-cn' : 'en-us',speed,volume}, (res) => {if (res.errorCode === 0) {resolve(res);} else {reject(new Error(`语音合成失败: ${res.errorCode}`));}});});// #endif// #ifdef H5return new Promise((resolve, reject) => {if ('speechSynthesis' in window) {const speech = new SpeechSynthesisUtterance();speech.text = text;speech.lang = lang === 'zh' ? 'zh-CN' : 'en-US';speech.rate = speed / 10;speech.volume = volume / 10;speech.onend = () => {resolve();};speech.onerror = (err) => {reject(err);};window.speechSynthesis.speak(speech);} else {reject(new Error('当前浏览器不支持语音合成'));}});// #endif
}

踩坑记录与解决方案

开发过程中,我遇到了一些常见问题与解决方法,分享如下:

  1. 百度语音插件初始化失败:检查API密钥配置和网络环境,特别是HTTPS限制
  2. H5录音无法使用:多数浏览器要求必须在HTTPS环境下才能使用麦克风
  3. 识别结果不准确:尝试调整录音参数,如采样率、声道数等,或者使用更专业的噪声抑制算法
  4. 微信小程序调用失败:检查access_token是否有效,注意token有效期
  5. 不同设备体验差异大:针对低端设备优化,如减少动画效果、降低采样率等

我们的解决方案是进行兼容性检测,并根据设备性能自动调整参数:

// 检测设备性能并调整参数
detectDevicePerformance() {const platform = uni.getSystemInfoSync().platform;const brand = uni.getSystemInfoSync().brand;const model = uni.getSystemInfoSync().model;// 低端安卓设备优化if (platform === 'android') {// 特定型号的优化if (brand === 'samsung' && model.includes('SM-J')) {return {sampleRate: 8000,quality: 'low',useVAD: false // 禁用语音活动检测,降低CPU占用};}}// 默认配置return {sampleRate: 16000,quality: 'high',useVAD: true};
}

总结与展望

通过本文,我们探讨了在UniApp中实现语音输入与识别功能的多种方案,并提供了具体的代码实现。这些实现方案已在实际项目中得到验证,能够满足大多数应用场景的需求。

语音技术在移动应用中的重要性不断提升,未来可以探索更多高级功能:

  1. 离线语音识别:降低网络依赖,提高响应速度
  2. 多语言支持:增加更多语言的识别能力
  3. 声纹识别:通过语音实现用户身份验证
  4. 情感分析:从语音中识别用户情绪

希望本文对你在UniApp中实现语音功能有所帮助!如有问题欢迎在评论区交流讨论。

参考资料

  1. UniApp官方文档
  2. 百度语音识别API文档
  3. Web Speech API

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年卫星遥感行业最新发展趋势深度分析

一、国内发展趋势&#xff1a;政策引领与技术突破双轮驱动 &#xff08;一&#xff09;政策体系持续完善&#xff0c;顶层设计深化行业发展 国家级战略与标准体系构建 中国政府将卫星遥感产业纳入“十四五”规划核心战略&#xff0c;明确构建“通导遥”一体化空间基础设施。20…

SIP协议栈--osip源码梳理

文章目录 osiposip主体结构体code main函数 状态机转化结构体code状态转换 sip事务结构体code osip_dialog结构体code 创建并发送200 OK响应 osip_message结构体code osip_eventcode 打印接收到的SIP消息 osip OSIP&#xff08;Open Source Implementation of SIP&#xff09;…

Linux之Yum源与Nginx服务篇

1.Yum源知识理论总结概括 Yum源概述 Yum 源 即软件仓库的标识&#xff0c;里面承载着软件包集合 Yum源组成 包含模块 【OS】、【everything】、【EPOL】、【debuginfo】、【source】、【update-source】 【os】:简称operator system 它内部包含操作系统的核心组件&#x…

从单体架构到微服务:架构演进之路

引言&#xff1a;当“大货车”遇上“集装箱运输” 在软件开发领域&#xff0c;单体架构曾像一辆载满货物的大货车&#xff0c;将所有功能打包在一个应用中。但随着业务复杂度飙升&#xff0c;这辆“大货车”逐渐陷入泥潭&#xff1a;启动慢如蜗牛、故障波及全局、升级如履薄冰……

AM32电调学习解读九:ESC上电启动关闭全流程波形分析

这是第九篇&#xff0c;前面的文章把各个模块的实现都介绍了一轮&#xff0c;本章是从运行的角度结合波形图&#xff0c;把整个流程走一遍。 先看下一运行的配置&#xff0c;我把一些配置关闭了&#xff0c;这样跑起来会好分析一些&#xff0c;不同配置跑起来效果会有差异。使用…

全球宠物经济新周期下的亚马逊跨境采购策略革新——宠物用品赛道成本优化三维路径

在全球"孤独经济"与"银发经济"双轮驱动下&#xff0c;宠物用品市场正经历结构性增长。Euromonitor数据显示&#xff0c;2023年全球市场规模突破1520亿美元&#xff0c;其中中国供应链贡献度达38%&#xff0c;跨境电商出口增速连续三年超25%。在亚马逊流量红…

reshape/view/permute的原理

在pytorch中&#xff0c;Tensor的存储是行主序的&#xff0c;也就是意味着最后一个维度的元素的存储时连续的&#xff0c;reshape和view并不改变元素存储的内存&#xff0c;仅仅改变访问的间隔&#xff0c;下面举例说明&#xff1b; 比如一个23的Tensor在内存中的存储是连续的&…

upload-labs靶场通关详解:第11关

一、分析源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array("php","php5","php4","php3","php2","html","htm","phtml"…

L1-7 最短字母串【保姆级详细讲解】

请你设计一个程序&#xff0c;该程序接受起始字母和目标字母作为输入&#xff0c;通过在字母表中向前或向后移动来计算两个给定字母之间的最短路径。然后&#xff0c;程序会沿着最短路径打印出从起始字母到目标字母的所有字母。例如&#xff0c;如果输入“c”和“k”作为起始字…

项目QT+ffmpeg+rtsp(三)——延迟巨低的项目+双屏显示

文章目录 前言双屏显示widget.cppwidget.h前言 对于复现情况,分为两种情况 第一种,对于我而言,是直接解压后,就能直接运行了 第二种,对于师兄而言,需要你构建debug后,会产生这个文件夹,执行的时候,地址应该在这,我猜的,这里面没有dll,exe程序就找不到dll这些库,你…

ansible进阶06

复杂的循环结构 循环基础 [studentworktest myansible]$ cat users.yml --- - name: create usershosts: serveratasks:- name: create some usersuser:name: "{{item}}"password: "{{123456|password_hash(sha512)}}"state: presentloop:- zhangsan- li…

Go 模块版本管理

Go 模块版本管理指南 1、创建带注释的 Git 标签 基本命令 # 创建带注释的标签 git tag -a v1.0.0 -m "Release version 1.0.0 - initial stable release" -a&#xff1a;创建带注释的标签 -m&#xff1a;添加标签注释信息 # 推送标签到远程仓库 git push origin v…

Java—— IO流 第一期

什么是IO流 存储和读取数据的解决方案 I&#xff1a;input O&#xff1a;output 流&#xff1a;像水流一样传输数据 IO流的作用 用于读写数据(本地文件&#xff0c;网络) IO流的分类 按照流向分类 输出流&#xff1a;程序 --> 文件 输入流&#xff1a;文件 --> 程序 按照…

物联网安全技术的最新进展与挑战

随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备被连接到互联网&#xff0c;从智能家居设备到工业控制系统&#xff0c;物联网正在深刻改变我们的生活和生产方式。然而&#xff0c;物联网的安全问题也日益凸显&#xff0c;成为制约其发展的关键…

【深度学习基础】损失函数与优化算法详解:从理论到实践

【深度学习基础】损失函数与优化算法详解&#xff1a;从理论到实践 一、引言 1. 损失函数与优化算法在深度学习中的核心作用 在深度学习中&#xff0c;模型训练的本质是通过不断调整参数&#xff0c;使模型输出尽可能接近真实值。这一过程的核心驱动力是损失函数&#xff08;…

mvc-review

review&#xff1a; 1.Servlet生命周期中初始化方法&#xff1a;init(),init(config) public void init(ServletConfig config) throws ServletException { this.config config; this.init(); } 因此&#xff0c;如果我们需要…

YouTube视频字幕转成文章算重复内容吗?

很多创作者误以为「自己说的话不算抄袭」&#xff0c;却不知道YouTube自动生成的字幕早已被搜索引擎存档。 去年就有案例&#xff1a;某美食博主将教程视频字幕转为图文&#xff0c;结果原创度检测仅42%&#xff0c;导致页面权重暴跌。 本文揭秘5个实操技巧&#xff1a;从删除…

R语言数据可视化

R note book 文档–输出html格式文档&#xff0c;plotly不能生成PDF文件 --- title: "R语言数据可视化" output: html_notebook ---在R语言中进行数据可视化是数据分析和呈现的重要环节&#xff0c;R提供了多种强大的绘图系统和工具。以下是常见的数据可视化方法和示…

Axure难点解决分享:垂直菜单展开与收回(4大核心问题与专家级解决方案)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢!如有帮助请订阅专栏! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:垂直菜单展开与收回 主要内容:超长菜单实现、展开与收回bug解释、Axure9版本限制等问题解…

云原生攻防2(Docker基础补充)

Docker基础入门 容器介绍 Docker是什么 Docker是基于Linux内核实现,最早是采用了 LXC技术,后来Docker自己研发了runc技术运行容器。 它基于Google Go语言实现,采用客户端/服务端架构,使用API来管理和创建容器。 虚拟机 VS Docker Namespace 内核命名空间属于容器非常核…