首页 文章 精选 留言 我的

精选列表

搜索[解耦化],共10000篇文章
优秀的个人博客,低调大师

解Bug之路-记一次调用外网服务概率性失败问题的排查

前言 和外部联调一直是令人困扰的问题,尤其是一些基础环境配置导致的问题。笔者在一次偶然情况下解决了一个调用外网服务概率性失败的问题。在此将排查过程发出来,希望读者遇到此问题的时候,能够知道如何入手。 起因 笔者的新系统上线,需要PE执行操作。但是负责操作的PE确和另一个开发在互相纠缠,让笔者等了半个小时之久。本着加速系统上线的想法,就想着能不能帮他们快速处理掉问题,好让笔者早点发完回去coding。一打听,这个问题竟然扯了3个月之久,问题现象如下: 每个client都会以将近1/2的概率失败,而且报错都为: 着手排查 和appserver开发以及对应的PE交流发现,appserver和nginx之间是短连接,由于是socketTimeOutException,于是能够排除appserver和nginx建立连接之间的问题。去nginx上排查日志,发现一个奇异的现象,如下图所示: 所有的appserver都是调用一台nginx一直成功,而调用另一台nginx大概率失败。而两台nginx机器的配置一模一样,还有一个奇怪的点是,只有在调用出问题的对端服务器时才会失败,其它业务没有任何影响,如下图所示: 由于这两个诡异的现象导致开发和PE争执不下,按照第一个现象一台nginx好一台nginx报错那么第二台nginx有问题是合理的推断,所以开发要求换nginx。按照第二个现象,只有调用这个业务才会出错,其它业务没有问题,那么肯定是对端业务服务器的问题,PE觉得应该不是nginx的锅。争执了半天后,初步拟定方案就是扩容nginx看看效果-_-!笔者觉得这个方案并不靠谱,盲目的扩容可能会引起反效果。还是先抓包看看情况吧。 抓包 其实笔者觉得nginx作为这么通用的组件不应该出现问题,问题应该出现在对端服务器上。而根据对端开发反应,他自己curl没问题,并现场在他自己的服务器上做了N次curl也没有任何问题(由于这个问题僵持不下,他被派到我们公司来协助排查)。于是找网工在防火墙外抓包,抓包结果如下: 时间点 源ip 目的ip 协议 info 2019-07-25 16:45:41 20.1.1.1 30.1.1.1 tcp 58850->443[SYN] 2019-07-25 16:45:42 20.1.1.1 30.1.1.1 tcp [TCP Retransmission]58850->443[SYN] 2019-07-25 16:45:44 20.1.1.1 30.1.1.1 tcp [TCP Retransmission]58850->443[SYN] 由于appserver端设置的ReadTimeOut超时时间是3s,所以在2次syn重传后,对端就已经报错。如下图所示: (注:nginx所在linux服务器设置的tcp_syn_retries是2) 抓包结果分析 从抓包得出的数据来看,第二台nginx发送syn包给对端服务,对端服务没有任何响应,导致了nginx2创建连接超时,进而导致了appserver端的ReadTimeOut超时(appserver对nginx是短连接)。 按照正常推论,应该是防火墙外到对端服务的SYN丢失了。而阿里云作为一个非常稳定的服务商,应该不可能出现如此大概率的丢失现象。而从对端服务器用的是非常成熟的SpringBoot来看,也不应该出现这种bug。那么最有可能的就是对端服务器本身的设置有问题。 登陆对端服务器进行排查 由于对方的开发来到了现场,于是笔者就直接用他的电脑登录了服务所在的阿里云服务器。首先看了下dmesg,如下图所示,有一堆报错: 感觉有点关联,但是仅靠这个信息无法定位问题。紧接着,笔者运行了下netstat -s: 这条命令给出了非常关键的信息,翻译过来就是有16990个被动连接由于时间戳(time stamp)而拒绝!查了下资料发现这是由于设置了 在NAT情况下将会导致这个被动拒绝连接的问题。而为解决上面的dmesg日志,网上给出的解决方案就是设置tcp_tw_recycle=1而tcp_timestamps默认就是1,同时我们的客户端调用也是从NAT出去的,符合了这个问题的所有特征。 于是笔者尝试着将他们的tcp_timestamps设为0, 又做了几十次调用,再也没有任何报错了! linux源码分析 问题虽然解决了,但是笔者想从源码层面看一看这个问题到底是怎么回事,于是就开始研究对应的源码(基于linux-2.6.32源码)。 由于问题是发生在nginx与对端服务器第一次握手(即发送第一个syn)的时候,于是我们主要跟踪下这一处的相关源码: 关于tcp_timestamps的代码就在tcp_v4_conn_request里面,我们继续追踪(以下代码忽略了其它不必要的逻辑): inttcp_v4_conn_request(structsock*sk,structsk_buff*skb) { ...... /*VJ'sidea.Wesavelasttimestampseen *fromthedestinationinpeertable,whenentering *stateTIME-WAIT,andcheckagainstitbefore *acceptingnewconnectionrequest. *注释大意为: *我们在进入TIME_WAIT状态的时候将最后的时间戳记录到peertables中, *然后在新的连接请求进来的时候检查这个时间戳 */ //在tcp_timestamps和tcp_tw_recycle开启的情况下 if(tmp_opt.saw_tstamp&& tcp_death_row.sysctl_tw_recycle&& (dst=inet_csk_route_req(sk,req))!=NULL&& (peer=rt_get_peer((structrtable*)dst))!=NULL&& peer->v4daddr==saddr){ /**TCP_PAWS_MSL==60*/ /**TCP_PAWS_WINDOW==1*/ //以下都是针对同一个对端ip //tcp_ts_stamp对端ip的连接进入time_wait状态后记录的本机时间戳 //当前时间在上一次进入time_wait记录的实际戳后的一分钟之内 if(get_seconds()<peer->tcp_ts_stamp+TCP_PAWS_MSL&& //tcp_ts最近接收的那个数据包的时间戳(对端带过来的) //对端当前请求带过来的时间戳小于上次记录的进入time_wait状态后记录的对端时间戳 (s32)(peer->tcp_ts-req->ts_recent)> TCP_PAWS_WINDOW){ //增加被动连接拒绝的统计信息 NET_INC_STATS_BH(sock_net(sk),LINUX_MIB_PAWSPASSIVEREJECTED); //进入丢弃和释放阶段 gotodrop_and_release; } } ...... } 上述代码的核心意思即是在tcp_timestamps和tcp_tw_recycle开启的情况下,同样ip的连接,在上个连接进入time_wait状态的一分钟内,如果有新的连接进来,而且新的连接的时间戳小于上个进入time_wait状态的最后一个包的时间戳,则将这个syn丢弃,进入drop_and_release。我们继续跟踪drop_and_release: 我们继续看下如果tcp_v4_conn_request返回0的话,系统是什么表现: 从源码的跟踪可以看出,出现此种情况直接丢弃对应的syn包,对端无法获得任何响应从而进行syn重传,这点和抓包结果一致。 和问题表象一一验证 为什么会出现一台nginx一直okay,一台nginx失败的情况 由于tcp的时间戳是指的并不是当前本机用date命令给出的时间戳。这个时间戳的计算规则就在这里不展开了,只需要知道每台机器的时间戳都不相同即可(而且相差可能极大)。由于我们调用对端采用的是NAT,所以两台nginx在对端服务器看来是同一个ip,那么这两台的时间戳发送到对端服务器的时候就会混乱。nginx1的时间戳比nginx2的时间戳大,所以在一分钟之内,只要出现nginx1的连接请求(短连接),那么之后的nginx2的连接请求就会一直被丢弃。如下图所示: 为什么对端自测一直正常 因为本机调用本机的时时间戳是一台机器(本机)上的,所以不会出现混乱。 为什么nginx2调用其它服务是正常的 因为其它外部服务所在服务器并没有开启tcp_tw_recycle。这个问题事实上将tcp_tw_recycle置为0也可以解决。另外,高版本的linux内核已经去掉了tcp_tw_recycle这个参数。 总结 由于当前ip地址紧缺和DNS报文大小的限制(512字节),大部分网络架构都是采用NAT的方式去和外部交互,所以设置了tcp_tw_recycle为1基本都会出现问题。一般这种问题需要对tcp协议有一定的了解才能够顺藤摸瓜找到最终的根源。

优秀的个人博客,低调大师

对JVM还一知半解?这篇文章让你彻底搞定JVM

对于Java开发者来说,想把自身能力提升到更高层次,某些JVM相关知识应该是优先级很高的。比如说GC策略,JVM调优。 就我在工作中遇到的情况来看:接触了几年Java,现在做Java Web的开发,实际关注Java本身的知识,之前对JVM关注的较少。 ● 控制台报错,就会直接百度,然后找答案,解决问题。看了JVM方面的书,才知道控制台的信息已经告诉你问题出在哪,只要懂JVM,很快就能解决问题,根本不用百度,上网查答案之类的。 ● JVM虚拟机帮助我们做了很多事情,如果你只是会用,而不懂它,在以后的开发遇到瓶颈(会接触到性能问题,分析堆空间分配是否合适,垃圾回收机制是否合理,有没有内存溢出,线程死锁等),就很难办了。 学习JVM,对理解Java有很大帮助。而且现在待遇好的公司,面试时基本都会考核JVM相关的问题,你如果不会的话,面试通过的机会很渺茫。 所以,掌握JVM很有必要。 你是不是在默默计划着把学习JVM提上日程了?这里推荐大家关注最新的JVM在线分享(一次难得的线上学习交流的机会)。分享内容包括:JVM内存结构、GC垃圾回收算法、JVM优化。 这次的分享人是李兴华,一个很受欢迎的“网红大咖”,有8年软件开发经验,给40多家大中型企业做过JAVA EE高级培训,讲课深入浅出、通俗易懂、语言精练、好评度特别高。(不少人入门Java是学他的视频过来的) 李兴华先后出版了《初级学java》、《Java开发实战经典》、《Java Web开发实战经典》、《Android开发实战经典》、《Oracle开发实战经典》、《Java核心技术精讲》等一系列畅销书。 除JVM之外,大家还能参与其他两场在线分享,可以根据自身需求选择学习 这次在线分享活动是由开课吧策划的,为的是让更多需要进阶提升的Java技术人了解“开课吧”这一品牌。之所以给大家推荐,是因为我相信这3场分享能给大家带来切实的收获和帮助,而不是注水的广告宣传。 对于之前刚结束的“Zookeeper集群搭建及Leader选举算法源码解析”这场分享,我前后也做了充分的了解:这场分享开放报名2天,报名1000+人,得到了参与者的肯定。 原文发布时间为:2018-10-16 本文来自云栖社区合作伙伴“Java架构沉思录”,了解相关信息可以关注“Java架构沉思录”。

优秀的个人博客,低调大师

babel 用法及其 .babelrc 的配置详解,想做前端架构,拒绝一知半解...

Babel 官方介绍:将 ECMAScript 2015 及其版本以后的 javascript 代码转为旧版本浏览器或者是环境中向后兼容版本的 javascript 代码。 简而言之,就是把不兼容的 JavaScript 代码转为可兼容,可以执行的 JavaScript 代码。 功能: 语法转换 添加一些兼容性的 polyfill 功能 源码转换等 简单配置预览: { "plugins": [], // 插件配置 "presets": [] // 预设配置 } // 这里 .babelrc 配置的 // 官网建议用 module 的形式来配置,bable.config.js const presets = [] const plugins = [] module.exports = { presets, plugins } 配置介绍: presets :是某一类 plugin 的集合,包含了某一类插件的所有功能。 plugin : 将某一种需要转化的代码,转为浏览器可以执行代码。 编译的执行顺序: 1、执行 plugins 中所有的插件 2、plugins 的插件,按照顺序依赖编译 3、所有 plugins 的插件执行完成,在执行 presets 预设。 4、presets 预设,按照倒序的顺序执行。(从最后一个执行) 5、完成编译。 1、主要插件介绍 babel-preset-es2015 ( ES2015 / ES6 插件集合 ) 将 ES2015(es6) 的 javascript 代码 转化 浏览器兼容 javascript 代码 arrow-functions :箭头函数 block-scoped-functions :函数命名的方式转话,改为赋值的形式 block-scoping :let 转为 var classes : es6 的类转化 computed-properties :属性命名的转化,如:var obj = { ['x'+a]: 1 } destructuring :析构函数转化 duplicate-keys :属性优化 for-of :for of 的转化 function-name :优化函数命名 instanceof :instanceof 转化 literals :不同类型复制优化 new-target :继承 extends 转化 object-super :super 转化 parameters :params ,(a=1,b=2)=>{} , 默认参数转化 shorthand-properties :对象属性转化 {a,b} => {a:a,b:b} spread :扩展属性转化 {...other} sticky-regex :regex 优化,用 new Regex构建 template-literals :字符串模版 typeof-symbol :symbol 转化 unicode-regex :将正则的匹配字符串转为 unicode babel-preset-es2016 ( ES2016 / ES7 插件集合 ) 将 ES2016(es7) 的 javascript 代码 转为 浏览器兼容代码 exponentiation-operator :幂运算转化 x **= 3 ,x的3次幂 babel-preset-es2017( ES2017 / es8 插件集合 ) 将 ES2017(es8) 的代码进行转化 async-to-generator : 转化 async/await 转为 generator 函数 babel-preset-stage-x ( ES2018/stage 插件集合 ) 将 stage 阶段的代码转化 async-generator-function :将异步函数进行转化 dotall-regex :点号正则优化 object-rest-spread :解析构优化 optional-catch-binding :catch 加 finally 优化 unicode-proprty-regex Modules modules-amd / modules-commonjs / modules-systemjs / modules-umd 将不同的 export default x 转为不同的模块形式 amd / commonjs / systemjs / umd Experiental ( 试验阶段 ) class-properties :class 的静态属性转化 decorators :装饰器转化 do-expressions export-default-from :export 优化 export-namespace-from :export 优化 function-bind :bind 函数转化,obj::fun => fun.bind(obj) ::obj.fun => obj.fun(obj) function-sent logical-assignment-operators nullish-coalescing-operaor numeric-separator optional-chaining pipeline-opertor throw-expressions Minification( babel-minify ) 运用代码压缩优化插件预设 ... 这里自行可以去官网上查看... babel-preset-react ( react 集合 ) react 的相关编译转化... jsx 和 element 的一下转化 直接添加 react 的预设就可以了。 Other 其他一些插件 jscript :严格模式命名 object-assgin :assgin 转化 regenerator :generator 函数的转化 object-set-prototype-of-to-assign :setPrototype 转化 runtime 2、主要 presets 介绍 @babel/preset-dev 是一个智能预设。 根据浏览器和运行环境,自动的确定 babel 插件和 polyfills 。 没有任何配置的情况下,和 babel-preset-latest 一样 ( es2015/es2016/es2017一起使用 )。 { "presets": [ "dev" ] } 配置及其详解: { "presets": [ [ '@babel/preset-env', { 'target':{ "browser":["ie>=8","chrome>=62"], "node":"8.9.0", "safari":"tp" }, "modules":false, "debug":true, "uglify":true "useBuiltIns":true } ] ] } target : 描述您为项目配置的 支持/定位 的环境 browsers :浏览器的支持情况,将返回支持浏览器列表。 会被 target.ie 覆盖 [ 'ie>=8' ] 支持 ie8 的环境 "default" 默认 node :指定是 node 环境,且可以指定版本 safari : safari 版本 modules : 启用将 es6 转为其他模块 debug : 是否启用 console.log include / exclude : 必须启用的 plugin 功能 / 不启用的 plugin 功能 uglify : 压缩代码 useBuiltIns : false : 引用所有的 babel-polyfill , 在 webpack 中添加 babel-polyfill 入口处: entry:[ 'babel-polyfill' , 'index.js' ] , 引用所有的 polyfill,体积变大 true : 引用部分,根据配置的 preset-env 环境,引用 polyfill , 在入口文件中要引用 babel-polyfill @babel/preset-react 这都是一些 react 相关的集合 不需要介绍了,有需要自己去官网浏览下. Stage-x 草案中的预设.. 该插件集合不存在 preset-env 集合中,所以需要引用.... 3、babel-polyfill 兼容方法 babel的 preset-dev 中只会转化新的 Javascript 语法,不会转化新的 api。 例如:Generator / Set / Map 等 就是实现 babel 转化后的代码中,仍有不兼容的代码的转化。 原理: babel-folyfill 是修改全局的对象的原型,添加不兼容的 api 方法,或者修改不兼容的 api 方法。 用法: 1、在入口文件的顶部添加: require('@babel-polyfill'); import '@babel-polyfill' 2、webpack 中添加 { entry:['@babel-polyfill','./app.js'] } 在 webpack 的配置文件中添加。 3、直接在 html 中引用,在打包 js 的文件之前 <script src = './babel-polyfill.js' ></script> 4、transform-runtime 插件 对 babel 转化后的代码,进行再次转化,与 babel-polyfill 一样,解决不兼容的全局 api 。 与 babel-polyfill 不同是它不是添加/修改全局对象。 它是对不兼容的方法进行特殊处理,也就是添加辅助方法来做兼容。 并且 transform-runtime 是在需要进行兼容转化时候引用。 transform-runtime 是依赖 babel-runtime ,且辅助方法都是引用的 babel-runtime 特点: 不会污染全局变量,因为不会修改全局对象 没有修改实例的方法,所以有些方法不能实现。 ["transform-runtime", { "corejs":false, "helpers": false, "polyfill": false, "regenerator": true, "moduleName": "babel-runtime" }] helpers : 是否使用 @babel-runtime/helpers 来代替内部的 helpers coresjs : 是否用 @babel-runtime/corejs 中的辅助方法来替换 Map / Set 等方法 polyfill : 是否用 @babel-runtime 的辅助函数来代替 polyfill regenerator : 是否用 辅助函数来代替 async / generator 函数 moduleName : 引用时候名字 5、@babel-register 通过 @babel-register 来注册当前所有的代码需要转码 var fs = require('fs'); var babelConfig = JSON.parse(fs.readFileSync('./.babelrc')); require('babel-register')(babelConfig); 很显然,这里可以传入一个 .babelrc 配置文件来进行转码时候的配置。 只需要添加到入口文件中。 node 端就可以这这样实现 babel 的转码。 6、tools babel-parser : babel 的解析器,用于解析 Javascript / jsx / Typescript / flow 等代码 babel-core : babel 的核心文件,用于转码的 babel-generator : 代码的转化,源码和转化代码 babel-code-frame : 代码转化位置等转码映射 babel-helpers : 辅助器方法 babel-runtime : 提供了一些列的辅助方法,与 polyfill 类似 ... @bable/babel-cli 命令行工具 用于命令行编译文件,可以进行配置以及输出等操作。 用法: babel input.js -options // 编译 input.js 文件 配置: -o : 输出路径 -w :监听编译文件,实时编译 -s :输出source-map ,可以内敛 -d :当编译一个目录时候,-d 输出目录 --ignore : 忽略的文件 --plugins : 使用插件,用 , 隔开 --presets : 使用预设,用 , 隔开 --config-file : 使用 .babelrc 文件路径 .babelrc 配置详解 配置转化起 babel 三种不同作用: 1、用于语法转义的配置。 preset-env / stage-x 主要用于 es 的语法转义, 2、用于补丁的配置,即 polyfill 的配置。主要是 transform-runtime 。 3、用于不同框架的配置,如:react的jsx /flow 等 例子: { "presets":[ "react", ["env",{...}], "stage-0" ], "plugins":[ "pluginName", ["transform-runtime",{...}] ], "ignore":["path",'file'], "miniied":boolean, "comments":boolean, "env":{ "test":{ "presets":[], "plugins":[] } }, ... } 主要几个配置 presets : 预设,插件的集合,倒序执行 plugins : 插件,先执行插件,在执行预设,顺序执行 ignore : 忽略的文件 minify : 压缩代码 common : 是否需要注释 env : 设置不同的环境,应用不同的配置,配置取值:BABEL_ENV,如若没有取 NODE_ENV 的值,默认为 development. 其他可以自行在官网浏览... 常见预设 env : 用于替换 es2015 / es2016 / es2017 的预设。根据环境引入插件 react :react的插件集合 react-optimize : react 代码优化,如去除 propsType 减少生产上面代码 stage-x :草案代码插件集合 flow : flow 插件集合 minify : 代码优化的集合 typescript : typescript 插件集合 常用插件 transform-async-to-generator : 异步函数 async/await 插件 transform-decorators-leagacy : 装饰器插件 syntax-dynamic-import :import() 插件 transform-runtime : 辅助器插件,用于ployfill transform-object-rest-spread : 用于合并 var test = {a:1,b2};var t = {...test,n:1} transform-funciton-bind : 用于编译 obj::fun => fun.bind(obj) transform-es2015-modules-commonjs :es6 module 在 node 使用 https://www.cnblogs.com/jiebba/p/9613248.html 我的博客 :XiaoLong's Blog 博客园小结巴巴:https://www.cnblogs.com/jiebba

优秀的个人博客,低调大师

Electron 开发实战(八):多媒体处理全解|音视频播放、录屏、FFmpeg 实战

大家好,本章是 Electron 实战系列第八章。前面我们掌握了网络通信、本地文件存储、系统原生交互,已经可以开发常规桌面工具类应用。而**多媒体处理**是进阶桌面应用的核心能力,适用于播放器、录屏工具、会议客户端、音频剪辑、媒体格式转换等商用项目。 Electron 依托 Chromium 内核 \+ Node\.js 生态,天然支持音视频解码、设备调用、屏幕捕获,同时可无缝集成 FFmpeg 实现专业级媒体编辑,是跨平台多媒体桌面应用的最优技术方案。 本章从零落地五大核心能力:音视频基础播放、录音录像、屏幕共享捕获、FFmpeg 媒体处理、媒体元数据读写,所有代码精简可复用,适配 Windows/Mac 跨平台,规避常见兼容性坑。 ![8.png](https://developer.qcloudimg.com/http-save/yehe-7806860/f89a43cfe075a6770811542b1ace3768.png) 参考前置:[Electron 屏幕捕获官方文档](https://www.electronjs.org/zh/docs/latest/api/desktop-capturer)、[Electron 媒体权限官方规范](https://www.electronjs.org/zh/docs/latest/api/system-preferences) ## 8\.1 音视频播放基础 Electron 渲染进程完全兼容 HTML5 原生 `\<audio\>`、`\<video\>` 标签,支持绝大多数主流媒体格式(MP4、MP3、WebM、WAV 等),无需额外解码库,开箱即用,满足日常播放需求。 ### 8\.1\.1 基础播放实现 零依赖实现本地/网络音视频播放,支持暂停、进度调节、音量控制、循环播放: ```html ``` ### 8\.1\.2 JS 动态控制播放 通过 JS 精细化控制播放逻辑,适配自定义播放器 UI 场景: ```javascript // 获取播放器实例 const video = document.getElementById('videoPlayer') // 播放、暂停 video.play() video.pause() // 音量控制 0-1 video.volume = 0.8 // 跳转播放进度(秒) video.currentTime = 30 // 播放结束监听 video.onended = () => console.log('视频播放完成') ``` ### 8\.1\.3 Electron 专属播放优势 - 支持**本地绝对路径媒体文件**,不受浏览器跨域和路径限制 - 可结合文件对话框,实现用户自选本地媒体文件播放 - 支持后台播放、窗口置顶播放,体验优于网页端 ### 8\.1\.4 格式兼容说明 Chromium 内核默认不支持 FLV、AVI、MKV 等非主流格式,如需播放需集成 FFmpeg 解码能力,对应本章 8\.4 小节内容。 ## 8\.2 录音与录像功能实现 Electron 可直接调用系统麦克风、摄像头设备,基于 WebRTC 实现**音频录制、视频录制、本地保存**,无需依赖第三方 SDK,适配录音工具、会议录制、视频拍摄场景。 引用来源:[Electron 媒体设备权限规范](https://www.electronjs.org/zh/docs/latest/api/system-preferences) ### 8\.2\.1 权限前置配置 Mac 系统必须主动申请媒体设备权限,否则录制功能失效,Windows 无需额外配置: ```javascript // main.js 主进程 const { systemPreferences } = require('electron') // Mac 申请麦克风、摄像头权限 if (process.platform === 'darwin') { systemPreferences.askForMediaAccess('microphone') systemPreferences.askForMediaAccess('camera') } ``` ### 8\.2\.2 音频录音极简实现 ```javascript let mediaRecorder = null let audioChunks = [] // 开始录音 async function startRecordAudio() { // 获取麦克风媒体流 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) mediaRecorder = new MediaRecorder(stream) audioChunks = [] mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data) mediaRecorder.onstop = saveAudioFile mediaRecorder.start() } // 停止录音并保存 function saveAudioFile() { const blob = new Blob(audioChunks, { type: 'audio/wav' }) const url = URL.createObjectURL(blob) // 可结合fs模块保存到本地 const a = document.createElement('a') a.href = url a.download = `record-${Date.now()}.wav` a.click() } ``` ### 8\.2\.3 摄像头录像实现 ```javascript async function startRecordVideo() { // 获取摄像头+麦克风数据流 const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) // 页面预览摄像头画面 document.querySelector('video').srcObject = stream mediaRecorder = new MediaRecorder(stream) mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data) mediaRecorder.onstop = () => { const blob = new Blob(audioChunks, { type: 'video/mp4' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `video-${Date.now()}.mp4` a.click() } mediaRecorder.start() } ``` ### 8\.2\.4 停止录制通用方法 ```javascript function stopRecord() { if (mediaRecorder) mediaRecorder.stop() } ``` ## 8\.3 屏幕共享与捕获 屏幕捕获是桌面端独有核心能力,Electron 内置 `desktopCapturer` 模块,支持**全屏捕获、指定窗口捕获、屏幕共享推流**,适配录屏工具、远程协助、会议屏幕共享、直播推流场景。 引用来源:[Electron desktopCapturer 官方 API](https://www.electronjs.org/zh/docs/latest/api/desktop-capturer) ### 8\.3\.1 获取屏幕/窗口源列表 获取所有可捕获的屏幕、应用窗口,实现用户手动选择捕获目标: ```javascript const { desktopCapturer } = require('electron') // 获取所有捕获源 async function getCaptureSources() { const sources = await desktopCapturer.getSources({ types: ['screen', 'window'], // 捕获屏幕+窗口 thumbnailSize: { width: 1920, height: 1080 } // 缩略图分辨率 }) return sources } ``` ### 8\.3\.2 全屏屏幕录制 ```javascript async function startScreenRecord() { const sources = await getCaptureSources() // 选取主屏 const screenSource = sources.find(item => item.name === '整个屏幕') const stream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: screenSource.id } } }) // 页面预览屏幕画面 document.querySelector('video').srcObject = stream } ``` ### 8\.3\.3 屏幕共享最佳实践 - 优先捕获指定窗口,减少画面范围、降低资源占用 - 录制时关闭不必要的后台进程,避免音画卡顿 - 结合 MediaRecorder 实现录屏保存,结合 WebSocket 实现实时屏幕共享推流 ## 8\.4 使用FFmpeg处理媒体文件 原生 Electron 仅支持基础播放、录制能力,无法实现**格式转换、视频裁剪、分辨率压缩、码率调整、异常格式解码**等专业操作。**FFmpeg 作为多媒体瑞士军刀**,可完美补齐 Electron 媒体处理短板,是商用多媒体桌面应用的必备方案 。 引用来源:[掘金 FFmpeg 音视频处理实战](https://juejin.cn/post/7634367613500162090) ### 8\.4\.1 环境部署 使用 `fluent\-ffmpeg` 封装库,简化 Node\.js 调用 FFmpeg 命令: ```bash # 安装依赖 npm install fluent-ffmpeg --save ``` 环境要求:项目打包时内置 FFmpeg 二进制文件,避免用户电脑未配置环境变量导致功能失效。 ### 8\.4\.2 常用媒体处理实战代码 基于 FFmpeg 实现 3 个高频商用功能,代码极简可直接复用: #### 1\. 视频格式转换(MP4 → WebM) ```javascript const ffmpeg = require('fluent-ffmpeg') // 格式转换 ffmpeg('./input.mp4') .output('./output.webm') .on('end', () => console.log('格式转换完成')) .on('error', (err) => console.error('转换失败:', err)) .run() ``` #### 2\. 视频裁剪(截取指定时间段) ```javascript // 从第10秒开始,截取10秒视频 ffmpeg('./input.mp4') .setStartTime(10) .setDuration(10) .output('./clip.mp4') .run() ``` #### 3\. 视频分辨率压缩 ```javascript // 压缩为720P分辨率,无损音频 ffmpeg('./input.mp4') .videoFilters('scale=1280:720') .audioCodec('copy') .output('./720p-output.mp4') .run() ``` ### 8\.4\.3 跨平台打包适配 开发环境需本地安装 FFmpeg,生产打包需将 ffmpeg\.exe / 可执行文件打包进应用资源,通过代码指定二进制路径,保证用户端正常使用。 ```javascript // 绑定打包后的ffmpeg路径 const path = require('path') ffmpeg.setFfmpegPath(path.join(__dirname, './ffmpeg.exe')) ``` ## 8\.5 媒体元数据读取与编辑 媒体元数据包含**时长、分辨率、码率、帧率、作者、创建时间、标题**等信息,常用于媒体库管理、文件筛选、信息展示场景。本节使用 `music\-metadata` 库实现元数据读写,轻量高效、适配所有主流媒体格式。 ### 8\.5\.1 安装依赖 ```bash npm install music-metadata --save ``` ### 8\.5\.2 读取媒体元数据 ```javascript const fs = require('fs') const mm = require('music-metadata') async function getMediaMeta(filePath) { const stream = fs.createReadStream(filePath) const meta = await mm.parseStream(stream) // 核心媒体信息 console.log('时长:', meta.format.duration) console.log('分辨率:', meta.format.video.resolution) console.log('帧率:', meta.format.fps) console.log('编码格式:', meta.format.codec) return meta } ``` ### 8\.5\.3 媒体信息编辑与重写 支持修改媒体标题、作者、专辑、备注等自定义元数据: ```javascript async function updateMediaMeta(filePath) { const meta = await getMediaMeta(filePath) // 自定义修改信息 meta.common.title = 'Electron测试视频' meta.common.artist = '开发者' // 重写元数据 await mm.save(filePath, meta) } ``` ### 8\.5\.4 业务场景落地 - 媒体库自动分类:根据时长、分辨率、格式筛选文件 - 自定义媒体信息:批量修改视频/音频标题、作者信息 - 媒体参数校验:过滤损坏、分辨率异常的媒体文件 ## 本章总结 本章全覆盖 Electron 多媒体开发核心能力,从原生基础功能到专业 FFmpeg 处理方案,补齐桌面多媒体应用开发短板,核心知识点复盘: 1. 掌握 HTML5 原生音视频播放、JS 精细化控制,适配基础媒体播放场景 2. 实现麦克风录音、摄像头录像、本地文件保存,兼容 Windows/Mac 权限适配 3. 熟练使用 desktopCapturer 实现屏幕捕获、窗口录制、屏幕共享核心能力 4. 基于 FFmpeg 实现媒体格式转换、裁剪、压缩,落地专业级媒体编辑功能 5. 掌握媒体元数据读取与编辑,实现媒体库管理、参数校验、批量处理功能 结合本章内容,可独立开发**桌面播放器、录屏工具、音频剪辑软件、会议共享客户端、媒体资源管理器**等商用级多媒体应用。 ## 参考来源 \[1\] Electron 官方文档:desktopCapturer 屏幕捕获 [https://www\.electronjs\.org/zh/docs/latest/api/desktop\-capturer](https://www.electronjs.org/zh/docs/latest/api/desktop-capturer) \[2\] Electron 官方文档:媒体设备权限配置 [https://www\.electronjs\.org/zh/docs/latest/api/system\-preferences](https://www.electronjs.org/zh/docs/latest/api/system-preferences)

优秀的个人博客,低调大师

禅道开源版 22.3.stable 发布,优化 Jira 数据 API 导入机制,解已知 Bug

大家好,禅道开源版22.3.stable发布啦!本次发布优化了Jira数据API导入机制,修复已知Bug。 新版本将为用户带来更好的使用体验和更高的工作效率,感谢大家一直以来的支持和反馈,我们将继续努力提供更优秀的产品和服务! 新增功能点 优化Jira数据API导入机制: 将Jira项目获取的接口改为临时数据存储 将Jira构建获取的接口改为临时数据存储 将Jira Issue获取的接口改为临时数据存储 将Jira Issue类型获取的接口改为临时数据存储 将Jira Issue关联关系获取的接口改为临时数据存储 将Jira用户获取的接口改为临时数据存储 将Jira优先级获取的接口改为临时数据存储 将Jira解决方案获取的接口改为临时数据存储 将Jira状态获取的接口改为临时数据存储 将Jira 自定义字段获取的接口改为临时数据存储 将Jira自定义字段选项获取的接口改为临时数据存储 将Jira工作流动作获取的接口改为临时数据存储 将Jira 迭代冲刺 获取的接口改为临时数据存储 将Jira项目成员获取的接口改为临时数据存储 将原本从接口获取数据的方式修改为从临时存储的文件获取 调整API导入流程,先获取API数据再进行导入 禅道本次发布数据如下: 本期优化的全部需求和Bug:请点击查看 下载链接 Windows集成环境 内置Apache、MySQL和PHP,一键启用 安装版(amd64)免安装版(amd64) Linux集成环境 内置Apache、MySQL和PHP,一键启用。必须解压到 /opt 目录下 免安装版(amd64)免安装版(arm64) 源码包(tar.xz) 可以通过tar命令或者解压工具解压 php7.0php7.1php7.2_7.4php8.1 源码包(zip) php7.0php7.1php7.2_7.4php8.1 禅道即时通讯客户端 20人以下免费,20人以上联系右侧客服获取试用 Windows10+ 安装版(amd64)免安装版(amd64) Linux(amd64) 安装版免安装版 (.tar.gz)免安装版 (.zip) Linux(arm64) 安装版 macOS(amd64) 安装版免安装版 macOS(arm64) 安装版 禅道即时通讯服务 20人以下免费,20人以上联系右侧客服获取试用 WindowsLinuxmacOS Docker镜像:点击这里 帮助手册 安装文档:https://www.zentao.net/book/zentaopms/455.html 升级文档:https://www.zentao.net/book/zentaopms/460.html 持续优化,定期更新,禅道一直在路上。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册