{"success":true,"data":[{"id":"5cbfd9aca86ae80ce64b3175","author_id":"4f447c2f0a8abae26e01b27d","tab":"share","content":"<div class=\"markdown-text\"><h2>前言</h2>\n<p>时隔一年，Node.js 12 如约而至，正式发布第一个 <a href=\"https://github.com/nodejs/Release\">Current</a> 版本。</p>\n<p>该版本带来了诸如：</p>\n<ul>\n<li>V8 更新带来好多不错的特性。</li>\n<li>HTTP 解析速度提升。</li>\n<li>启动速度大幅提升。</li>\n<li>更好的诊断报告和堆分析工具。</li>\n<li>ESM 模块更新。</li>\n</ul>\n<p>原文地址：<a href=\"https://medium.com/@nodejs/introducing-node-js-12-76c41a1b3f3f\">https://medium.com/@nodejs/introducing-node-js-12-76c41a1b3f3f</a>\n语雀地址：<a href=\"https://www.yuque.com/egg/nodejs/nodejs-12\">https://www.yuque.com/egg/nodejs/nodejs-12</a></p>\n<h2>LTS vs Current</h2>\n<p><img src=\"https://cdn.nlark.com/yuque/0/2019/png/84182/1556074709431-35af45b8-ec7a-4a81-83d8-155eb519f04a.png#align=left&amp;display=inline&amp;height=389&amp;name=image.png&amp;originHeight=500&amp;originWidth=960&amp;size=58313&amp;status=done&amp;width=746\" alt=\"image.png\"></p>\n<p>如果你不了解 Node.js 的  Long Term Support 发布策略的话，一定要看看 <a href=\"https://github.com/nodejs/Release\">https://github.com/nodejs/Release</a> 。</p>\n<p>就目前而言，Node.js 6.x 和 8.x 将在 2019 年末结束 LTS 的支持，大家尽快升级到 10.x 吧。</p>\n<h2>快速体验</h2>\n<pre class=\"prettyprint language-bash\"><code>$ nvs add node&#x2F;12\n$ nvs use 12\n$ node -v\nv12.0.0\n</code></pre><p>具体参考这篇文章：<a href=\"https://zhuanlan.zhihu.com/p/63403762\">科普文：使用 nvs 管理本地 Node.js 版本</a></p>\n<h2>V8 更新到 7.4</h2>\n<blockquote>\n<p>大部分情况下，我们不用去考虑性能问题，坐等 V8 版本更新就好了。（大雾）</p>\n</blockquote>\n<p>本次版本更新，也带来了好几个不错的特性：</p>\n<ul>\n<li><a href=\"https://v8.dev/blog/v8-release-72#async-stack-traces\">异步堆栈跟踪</a></li>\n<li><a href=\"https://v8.dev/blog/v8-release-74#faster-calls-with-arguments-mismatch\">参数调用不匹配时的调用速度优化</a></li>\n<li><a href=\"https://v8.dev/blog/v8-release-73#faster-await\">更快的 await</a></li>\n<li><a href=\"https://v8.dev/blog/v8-release-72#javascript-parsing\">更快的 JavaScript 解析速度</a></li>\n</ul>\n<p><strong>同时，跑了下我们 Egg 的一些内部测试，发现序列化有 10~20% 的性能提升，恐怖如斯！</strong></p>\n<p>另，奇丑无比的 <a href=\"https://github.com/tc39/proposal-class-fields\">Private Class Fields</a> 也能用了：</p>\n<pre class=\"prettyprint language-javascript\"><code>class IncreasingCounter {\n  #count = 0;\n  \n  get value() {\n    console.log(&#x27;Getting the current value!&#x27;);\n    return this.#count;\n  }\n  increment() {\n    this.#count++;\n  }\n}\n</code></pre><h2>HTTP 解析速度提升</h2>\n<p>默认的 HTTP 解析器切换为 <a href=\"https://github.com/nodejs/llhttp\">llhttp</a> ，性能提升恐怖如斯：</p>\n<p><img src=\"https://cdn.nlark.com/yuque/0/2019/png/84182/1556072499637-686bb0e3-c75c-424c-851f-ad88aff183a2.png#align=left&amp;display=inline&amp;height=231&amp;name=image.png&amp;originHeight=404&amp;originWidth=1302&amp;size=88775&amp;status=done&amp;width=746\" alt=\"image.png\"></p>\n<blockquote>\n<p>点评：恐怖如斯。</p>\n</blockquote>\n<h2>启动速度提升</h2>\n<p>通过 <a href=\"https://v8.dev/blog/code-caching\">v8 code cache</a> 的支持，<a href=\"https://github.com/nodejs/node/pull/27161\">在构建时提前为内置库生成代码缓存</a>，从而提升 30% 的启动耗时。\n同时，通过<a href=\"https://github.com/nodejs/node/pull/24950\">重用主进程缓存</a>，Workers Threads 的启动速度提升了 60% 。</p>\n<blockquote>\n<p>点评：恐怖如斯。</p>\n</blockquote>\n<h2>Workers Threads</h2>\n<p>在 10.x 已经引入的 <a href=\"https://nodejs.org/api/worker_threads.html\">Workers Threads</a> 特性，在 12.x 里面默认启用，无需使用 <code>--experimental-worker</code> 开启。同时基于上一条的介绍，启动的速度也得到大幅提升。</p>\n<p>相关介绍：<a href=\"https://medium.com/@Trott/using-worker-threads-in-node-js-80494136dbb6\">https://medium.com/@Trott/using-worker-threads-in-node-js-80494136dbb6</a></p>\n<h2>诊断报告</h2>\n<p>提供了新的实验性功能『诊断报告』，一个非常有用的特性。\n可用于帮助分析诸如：崩溃，性能问题，内存泄漏，高 CPU 占用等等问题。详见 <a href=\"https://medium.com/the-node-js-collection/easily-identify-problems-in-node-js-applications-with-diagnostic-report-dc82370d8029\">这篇文章</a>。</p>\n<blockquote>\n<p>点评：这也是 <a href=\"https://www.aliyun.com/product/nodejs\">AliNode</a> 之前的一个卖点之一。</p>\n</blockquote>\n<h2>Heap Dump</h2>\n<p>以前我们分析问题的时候，需要手动安装对应的类库或者使用 AliNode。</p>\n<p>在 12.x 里面内置了该功能，详见：</p>\n<ul>\n<li><a href=\"https://github.com/nodejs/node/pull/27133\">https://github.com/nodejs/node/pull/27133</a></li>\n<li><a href=\"https://github.com/nodejs/node/pull/26501\">https://github.com/nodejs/node/pull/26501</a></li>\n</ul>\n<blockquote>\n<p>点评：又一个 <a href=\"https://www.aliyun.com/product/nodejs\">AliNode</a> 的功能被内置了。但其实影响不大，AliNode 的核心在于分析平台，这块的采集能力，本来他们就打算开源回馈出去的。</p>\n</blockquote>\n<p>同时，由于上述提到的 V8 升级，现在可以按照可用内存动态调整堆大小了。</p>\n<h2>ESM 模块方案更新</h2>\n<p>ES6 模块仍然还在实验阶段，不过有了新的方式，具体参见<a href=\"https://medium.com/@nodejs/announcing-a-new-experimental-modules-1be8d2d6c2ff\">这篇文章</a>。</p>\n<blockquote>\n<p>点评：让子弹再飞一会，该特性真的不是痛点，不急。</p>\n</blockquote>\n<h2>其他更新</h2>\n<ul>\n<li>更好的原生模块支持，<a href=\"https://nodejs.org/api/n-api.html#n_api_n_api\">N-API</a> 升级为版本 4，并 backport 到 Node.js 8.x 和 10.x。详细参见<a href=\"https://medium.com/the-node-js-collection/new-features-bring-native-add-ons-close-to-being-on-par-with-js-modules-cd4f9b8e4b4\">这篇文章</a>。</li>\n<li>TLS 升级为 1.3， <a href=\"https://developer.ibm.com/blogs/openssl-111-has-landed-in-nodejs-master-and-why-its-important-for-nodejs-lts-releases/\">增强安全功能</a>。</li>\n<li>随着 C++ 编译器的更新，现在要求 <code>GCC 6</code> 和 <code>glibc 2.17</code> ，对应的操作系统 Win7 和 macOS 10，详细参见<a href=\"https://github.com/nodejs/node/blob/v12.x/BUILDING.md#platform-list\">这篇文章</a>。</li>\n</ul>\n<p>不过目前 node-gyp 的一些原生模块会编译失败：</p>\n<pre class=\"prettyprint language-bash\"><code>nunjucks@3.2.0 › chokidar@2.1.5 › fsevents@^1.2.7 optional error: Error: Run &quot;sh -c node install&quot; error, exit code 1\n    at ChildProcess.&lt;anonymous&gt; (&#x2F;Users&#x2F;tz&#x2F;.npm-global&#x2F;lib&#x2F;node_modules&#x2F;tnpm&#x2F;node_modules&#x2F;_runscript@1.3.0@runscript&#x2F;index.js:74:21)\n    at ChildProcess.emit (events.js:196:13)\n    at maybeClose (internal&#x2F;child_process.js:1000:16)\n    at Process.ChildProcess._handle.onexit (internal&#x2F;child_process.js:267:5)\n</code></pre></div>","title":"Node 12 值得关注的新特性","last_reply_at":"2019-11-10T04:47:11.371Z","good":false,"top":true,"reply_count":76,"visit_count":248752,"create_at":"2019-04-24T03:36:12.582Z","author":{"loginname":"atian25","avatar_url":"https://avatars2.githubusercontent.com/u/227713?v=4&s=120"}},{"id":"5d9c9273865a9844a301a0a5","author_id":"5d9c9150865a9844a301a09e","tab":"share","content":"<div class=\"markdown-text\"><p>D2 前端技术论坛(Designer &amp; Developer Frontend Technology Forum, 简称 D2），是由阿里经济体前端委员会主办的面向全球前端领域的技术论坛，立志于建设一个促进业内交流、引领前端领域发展的平台。目前 D2 已经成功地举办了 13 届，为国内外前端领域的开发者和设计者提供了共同探讨行业发展的机会，以技术会友、一起分享技术的乐趣。\n延续上次燃爆前端圈的盛况，第十四届 D2 又向我们招手了！\n这次，它将踩着 2019 年的尾巴，于 12 月 14 号，和你我相约在美丽的杭州，一起分享前沿与实用的前端技术，探索技术给我们带来的乐趣，共同探讨行业的发展。\n如果你对本次会议感兴趣，那就赶紧去买票吧！（扫描下图二维码或<a href=\"http://d2forum.alibaba-inc.com/?bag_track=cnodejs.org\">点此</a>直达官网）\n<img src=\"//static.cnodejs.org/FhUkRQ03BllSEFpgRxge_94Y7SsB\" alt=\"image.png\"></p>\n<p>本届 D2 由以下 6 个方向组成，每个方向有各自的专场。\n<strong>智能化</strong>\n机器学习和人工智能在前端领域如何应用？智能化将如何改变前端的工作方式？目前机器学习和人工智能在前端的应用成果有哪些？在工程和业务领域借助智能化创造技术价值？本届 D2 的前端智能化专场通过行业的应用案例和实践经验的风向，让大家对智能化改变前端有切实的感受，我们还邀请到了谷歌 Tensorflow.js 等行业知名的前端智能化团队，带来前端智能化发展趋势的最新信息。让我们在机器学习改变行业的今天，携手弄潮于技术之巅。\n<strong>Serverless</strong>\nServerless 体系不断完善会给整个研发模式、团队组织带来怎样的影响？「云+端」一体化对前端岗位的定义、技术能力要求、前后端协同的方式带来什么变化？本届 D2 的 Serverless 专场将带领大家聆听阿里前端及其他全球知名厂商的深度实践分享，助你了解当前业内 Serverless 相关的研发动态与成果，一起去感受「云+端」为整个研发链路带来的新奇变化与无限想象。\n<strong>工程化</strong>\n随着前端在各领域中攻城略地，前端的日常开发工作变得异常复杂，在这个过程中我们的工具该如何提效？流程该如何保障？生产关系该怎样优化？生产效率又该怎么提升？面对不同的组织结构、业务形态和技术架构，我们该如何选择、取舍，有哪些思考逻辑值得借鉴？本届 D2 的工程化专场将带来业内优秀的前端工程建设实践及实践背后的宝贵经验与思考，希望帮助大家能从这些成功案例中获得灵感并发掘到新方向。\n<strong>极致体验</strong>\n追求极致体验已融入到每个前端工程师的血液中，性能优化是一个经久不衰的话题，随着业务和场景不断的扩大与演变，新框架、架构、解决方案的更新与迭代, 无一不是为了更好的体验及研发效率， 如何保证研发效率的同时达到最佳的用户体验？  过去积累的经验在面对未来场景下能具备什么价值？本届 D2 极致体验专场将邀请不同业务场景的技术专家分享相关思路与实践，期待能为大家带来更多的思考和落地参考。\n<strong>语言框架</strong>\n随着前端语言的发展，出现了哪些新特性以及这些特性背后的思想是什么？在实际场景中运用视角和标准组织视角之间有什么不同？面向未来的前端语言以及框架如何从应用性和标准性双维度思考来持续演进？本届 D2 的语言框架专场将邀请各个前端框架和编程语言领域的专家，为大家深度剖析其中的思考，同时将会邀请 TC39 的核心成员为大家深入解读标准背后的方方面面。\n<strong>多样化领域</strong>\n随着不断的新场景出现，很多新领域被重新定义、突破。始终充满创新活力的前端技术也在不断拓宽自己的边界和视野，那么近年来前端技术探索了哪些新领域？如何让前端技术与新领域碰撞出来火花？在深度专业领域例如：数据可视化、IoT、Webassembly、渲染引擎以及 3D 互动游戏等领域创造出了怎样的变化？本届 D2 的多样化领域专场将邀请不同技术领域中的实践专家，为大家解密前端在各个领域中的实践之路。</p>\n<p>看完各个方(zhuan)向(chang)的介绍，你是不是也开始期待了呢？\n虽然本篇文章还未透露讲师的具体信息，但是请放心，D2 出品，绝对值得信赖，看看去年的情况便知分晓！</p>\n<p>去年人气爆满、人潮涌动的三号厅里，兼具颜价与才华的讲师为我们带来了最专业最前沿的前端分享。\n来自腾讯云的王伟嘉为我们介绍了 Node.js 性能优化相关的工程经验、开源工具和最佳实践。\n来自 Facebook 的闫畅，带我们一步步分析了 Migration 技术产生的背景，解决了什么问题，以及怎样理解使用这个新功能。\n<img src=\"//static.cnodejs.org/Fi3qBMLiPYoI4lWFWRTcIlPv2CvV\" alt=\"6c85d902ly1fywvem9wmnj20zk0k0grh.jpg\">\n来自阿里的西萌，带大家探索了前端可视化技术的前沿领域，从技术视角看到不一样的双十一，探索神秘的双十一媒体大屏背后的实现细节。一套用于优化性能、提升流畅性的解决方案和工具集，包含 WebGL2 渲染引擎、多线程加速方案、WebAssembly 加速方案、星系到城市单场景的 LBS 可视化引擎等。\n来自蚂蚁金服的燃然，为大家带来了科技感十足集“好玩”与“实用”于一身的的 WebAR 技术，为我们介绍了WebAR需要的技术储备，如何利用基本的 3D 知识，上手开发一个 WebAR 互动应用。相信有不少朋友还记得如何高效渲染摄像头并且进行图像识别，Native 识别数据又是如何在WebView 间进行高效传递的吧。</p>\n<p>就这么简单一列都觉得干货满满啊有木有，难怪会后有不少人都在社交媒体上分享了自己的参会体验。其来自中国科学院大学的硕士 <a href=\"https://www.zhihu.com/question/308177332/answer/568099824\">hijiangtao</a> 的<a href=\"https://www.zhihu.com/question/308177332/answer/568099824\">感想</a>，应该说出了不少人的心声吧：</p>\n<blockquote>\n<p>很多人都说在三号报告厅呆了一整天收获很大，但自己眼睛就一双只能盯着一个报告厅，没法分身啊也是真难受</p>\n</blockquote>\n<p>哈哈，不过小哥哥先别难受，对哪个分享最感兴趣就尽管去听好了，其他感兴趣的，有我们呢！我们将在会后第一时间整理出分享资料，尽量把你的遗憾值降到最低～</p>\n<h3>召唤锦鲤</h3>\n<p>为了给大家提供最好的参会体验，每届 D2 结束之后我们都会进行随机的问卷调查，这里也由衷地感谢为我们提出意见和想法的同学，比心心～❤️ 同时我们也从去年回答问卷的朋友中，随机抽取了四位锦鲤！承包了他们的 D2 门票！锦鲤们将免费参加第十四届的 D2, 有没有很惊喜！他们分别是 lsonlee,郑天蝉,赵春燕,GOD(唐赟杰)，获奖的同学请发送你的联系方式到 <a href=\"mailto:alibabaf2e@list.alibaba-inc.com\">alibabaf2e@list.alibaba-inc.com</a> 联系小编领取奖品，如果这里面有你认识的朋友，也可以第一时间转发这个好消息给他哦（顺便敲诈个饭，哈哈）。\n有小可爱表示不知道有这么好的福利，嘿嘿嘿，以后福利只会更多不会少，你需要的就是来参加，然后静候喜从天降啦～</p>\n<p>对本次大会的时间、地点及主题方向有了大概的了解之后，终于到了最令人期待的卖票环节了，我隔着屏幕都能看到大家在摩拳擦掌呢～\n二话不说 开门见山吧！</p>\n<p>本次大会一共设置四个类型的门票：</p>\n<ul>\n<li>早鸟票（459元）</li>\n<li>早鸟拼团票 （3 人成团，只需 399 元）</li>\n<li>普通票 （559元）（购买 3 张及以上普通票，即可享受 459 元/张）</li>\n<li>晚鸟票 （659元）</li>\n</ul>\n<p>现在就可以购买早鸟票或者早鸟拼团票啦，数量有限，购买时间也有限制，如果你对本届 D2 感兴趣，那就赶紧扫码/访问<a href=\"http://d2forum.alibaba-inc.com/?bag_track=cnodejs.org\">大会官网</a>购票吧！  晚了可就只剩下晚鸟票咯～\n<img src=\"//static.cnodejs.org/FsrqL42OIhSf-JculmZ1BidzBhOC\" alt=\"image.png\">\nPS. 上月底刚放出 D2 的消息，就有近百位手快的小伙伴买了票，看到你们对 D2 的信赖真是感动呀！\nPPS. 大会贴心地为大家准备了官方拼团群，想拼团的小可爱欢迎进群哦～\n<img src=\"//static.cnodejs.org/FuSnt_6y4PsoKrIw0HLafWhst31a\" alt=\"image.png\">\n咳咳，除了买票来看参加，今年我们还为你准备了一种特殊的参会方式！猜猜是啥？！\n哈哈，谜底揭晓！\n那就是以讲师的身份参加 D2！\n本届 D2 专门为外部讲师预留了话题，如果你对以上六大方向感兴趣，又有自己深入的研究或独特的见解，我们诚挚邀请你进行报名，与广大前端爱好者分享交流。\n报名地址：\n<img src=\"//static.cnodejs.org/Fu59Z320jyf03Cl2Lh9EEGJagu6x\" alt=\"image.png\">\n如您的话题入选，我们将安排专人与您联系，心动不如赶快行动哟。:-)</p>\n</div>","title":"12 月 14 日，技术大牛齐聚 D2，带你解锁前端新姿势","last_reply_at":"2019-10-29T02:04:51.629Z","good":false,"top":true,"reply_count":6,"visit_count":20556,"create_at":"2019-10-08T13:43:15.568Z","author":{"loginname":"jothy1023","avatar_url":"https://avatars2.githubusercontent.com/u/14975630?v=4&s=120"}},{"id":"5dba779c865a9844a301f005","author_id":"56cd160ac045c3743304beec","tab":"ask","content":"<div class=\"markdown-text\"><p>以前的腾讯云还有价格优势，但刚才看了一样，现在比起阿里云好像连价格优势都没了。懂行的来说，腾讯云主机有什么优势呢？</p>\n</div>","title":"腾讯云主机有什么优势？","last_reply_at":"2019-11-13T11:20:56.821Z","good":false,"top":false,"reply_count":10,"visit_count":13216,"create_at":"2019-10-31T05:56:44.227Z","author":{"loginname":"flamingtop","avatar_url":"https://avatars3.githubusercontent.com/u/132677?v=4&s=120"}},{"id":"5dcbe652865a9844a3021bbe","author_id":"5dcbe57bece3813ad9ba82ef","tab":"ask","content":"<div class=\"markdown-text\"><p><a href=\"https://cloud.tencent.com/act/cps/redirect?fromSource=gwzcw.3018172.3018172.3018172&amp;redirect=10140&amp;cps_key=edc132dab808c8d3dee06a2a920f636e&amp;from=activity\">腾讯云服务器配置是 1核2G1M 50G</a>配置如下：<img src=\"//static.cnodejs.org/Fp5OUqR6UFVb_PCgvIBXgXIVqAqz\" alt=\"price.png\"></p>\n<p>准备个人备案一下，不知道有什么注意事项的也可以告诉我。</p>\n<p>初步计划是搞个个人博客或者个人网站，挂个广告啥的 有点”睡“后收入最好了。。。</p>\n<p>博客做什么技术选型呀</p>\n</div>","title":"搞了一个服务器，不知道做点什么项目？有大佬推荐吗？","last_reply_at":"2019-11-13T11:17:38.888Z","good":false,"top":false,"reply_count":0,"visit_count":97,"create_at":"2019-11-13T11:17:38.888Z","author":{"loginname":"clearJSer","avatar_url":"https://avatars3.githubusercontent.com/u/29148598?v=4&s=120"}},{"id":"5dcb943eece3813ad9ba80d0","author_id":"58d83c586f8b9bf02d1d0b1d","tab":"ask","content":"<div class=\"markdown-text\"><p>人多但不团结</p>\n</div>","title":"为什么不联起手来罢工击杀996制度","last_reply_at":"2019-11-13T10:32:21.954Z","good":false,"top":false,"reply_count":6,"visit_count":273,"create_at":"2019-11-13T05:27:26.587Z","author":{"loginname":"ResJay","avatar_url":"https://avatars0.githubusercontent.com/u/26635410?v=4&s=120"}},{"id":"5dc7baa2865a9844a3020fe9","author_id":"5ab87f69320bb09d69e23201","tab":"ask","content":"<div class=\"markdown-text\"><p>我用了原生的回出现部分手机层级最高的问题\n我用了video.js然后图标显示不出来\n<img src=\"//static.cnodejs.org/FkT2NbxAJyUmtg_3RBZYFB0kr-rw\" alt=\"image.png\"></p>\n<p><img src=\"//static.cnodejs.org/FhWt_K6K0z94y5FxtQVI93J2H6iN\" alt=\"image.png\">\n有大神知道怎么回事么</p>\n</div>","title":"有什么好用的video的移动端插件么","last_reply_at":"2019-11-13T10:30:13.445Z","good":false,"top":false,"reply_count":2,"visit_count":299,"create_at":"2019-11-10T07:22:10.975Z","author":{"loginname":"jiayin3204","avatar_url":"https://avatars0.githubusercontent.com/u/31952729?v=4&s=120"}},{"id":"5dca5938865a9844a3021629","author_id":"5836f4b2bde2b59e06141f4c","tab":"share","content":"<div class=\"markdown-text\"><p>利用离职后的空档期做了款仿微软Todos UI的微信小程序：WeTodos。中间用了好多相对于自己以前了解过没实践的工具/技术，一个人做前后端的效率真爽爆了～</p>\n<p><img src=\"//static.cnodejs.org/Fg-R4yN17K4xkDfBQLrlsnGKlxTo\" alt=\"gh_10dc9935b278_258.jpg\"></p>\n<p>喜欢的点个赞，欢迎体验留下你宝贵的意见便于我改进。</p>\n<p>Github 源码传送门：<a href=\"https://github.com/zhongjixiuxing/WeTodos\">https://github.com/zhongjixiuxing/WeTodos</a></p>\n<p>技术栈：</p>\n<ul>\n<li>Nodejs10.x (Basic programming language)</li>\n<li>Apollo Graphql (API 接口标准)</li>\n<li>Github Actions (CI/CD)</li>\n<li>Serverless (FASS 函数计算简捷tool)</li>\n<li>AWS Cloud Services (Lambda、Dynamodb、Apigateway、S3、CloudFormation、CloudWatch)</li>\n<li>Cloudflare (Domain DNS Service)</li>\n<li>Wechat mini-program (微信小程序客户端)</li>\n</ul>\n</div>","title":"仿微软的TODO 小程序：WeTodos","last_reply_at":"2019-11-13T07:58:19.505Z","good":false,"top":false,"reply_count":1,"visit_count":224,"create_at":"2019-11-12T07:03:20.903Z","author":{"loginname":"zhongjixiuxing","avatar_url":"https://avatars2.githubusercontent.com/u/8935235?v=4&s=120"}},{"id":"5bf23e61e6481c5709f5d15e","author_id":"5a7a5d505321b5396004ec1e","tab":"share","content":"<div class=\"markdown-text\"><h1>nt-addon-wechatapi</h1>\n<p>基于 nest 封装的 微信公众平台 API</p>\n<h2>贡献说明</h2>\n<p>我们欢迎 Nest.js 使用者来参与这个插件的开发，作为一个贡献者，请您遵循以下原则：</p>\n<ul>\n<li>代码提交规范，参考 <a href=\"https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#\">Git Commit Message Conventions</a></li>\n<li>始终从 develop checkout 一个新分支，命名规范为 feature/xxx，xxx 必须具有可读性，如：微信-普通商户版-扫码支付 =&gt; feature/wechat-native-pay</li>\n<li>在 checkout 新分支前，先在本地 develop 分支拉取远程 develop 分支的最新代码</li>\n<li>文件命名规则请参考项目目前的命名规则，如：微信支付中，order.interface.ts 代表所有订单相关的请求参数和返回结果的定义，swipe.pay.service.ts 代表付款码支付的业务逻辑</li>\n</ul>\n<h2>功能开发</h2>\n<p>请先查阅 Roadmap，确保你想贡献的功能没有正在被实现。然后在 <strong>issue</strong> 里提交一个贡献请求，注明想要贡献的功能。</p>\n<h2>发现 Bug ？</h2>\n<p>如果你在源码中发现bug，请你先在本仓库的 <strong>issue</strong> 提交一个bug问题。在你提交完bug问题后，我们很乐意接受你提交一个 <strong>PR</strong> 来帮助我们修复这个bug。</p>\n<h2>QQ 群</h2>\n<p>322247106</p>\n<h2>Roadmap</h2>\n<ul>\n<li>[ ] 发送客服消息（文本、图片、语音、视频、音乐、图文）</li>\n<li>[ ] 菜单操作（查询、创建、删除、个性化菜单）</li>\n<li>[ ] 二维码（创建临时、永久二维码，查看二维码URL）</li>\n<li>[ ] 分组操作（查询、创建、修改、移动用户到分组）</li>\n<li>[ ] 用户信息（查询用户基本信息、获取关注者列表）</li>\n<li>[ ] 媒体文件（上传、获取）</li>\n<li>[ ] 群发消息（文本、图片、语音、视频、图文）</li>\n<li>[ ] 客服记录（查询客服记录，查看客服、查看在线客服）</li>\n<li>[ ] 群发消息</li>\n<li>[ ] 公众号支付（发货通知、订单查询）</li>\n<li>[ ] 微信小店（商品管理、库存管理、邮费模板管理、分组管理、货架管理、订单管理、功能接口）</li>\n<li>[ ] 模版消息</li>\n<li>[ ] 网址缩短</li>\n<li>[ ] 语义查询</li>\n<li>[ ] 数据分析</li>\n<li>[ ] JSSDK服务端支持</li>\n<li>[ ] 素材管理</li>\n<li>[ ] 摇一摇周边</li>\n</ul>\n<h2>项目地址</h2>\n<p><a href=\"https://github.com/notadd/nt-addon-wechatapi\">https://github.com/notadd/nt-addon-wechatapi</a>   欢迎一起来撸</p>\n</div>","title":"基于 nest.js 的微信公众平台 API  （开发中）","last_reply_at":"2019-11-13T07:20:30.792Z","good":false,"top":false,"reply_count":0,"visit_count":1346,"create_at":"2018-11-19T04:38:57.791Z","author":{"loginname":"zuohuadong","avatar_url":"https://avatars0.githubusercontent.com/u/11203929?v=4&s=120"}},{"id":"5dca85e3865a9844a302179c","author_id":"5ab852d6320bb09d69e231f1","tab":"ask","content":"<div class=\"markdown-text\"><p>最近有爬虫网页的需求,node有什么好用的框架或工具么?麻烦大家推荐一下…如果有教程那最好了,以前没接触过爬虫这部分…麻烦大家了…</p>\n</div>","title":"node.js 爬虫工具","last_reply_at":"2019-11-13T06:05:02.752Z","good":false,"top":false,"reply_count":5,"visit_count":350,"create_at":"2019-11-12T10:13:55.743Z","author":{"loginname":"iori2882","avatar_url":"https://avatars3.githubusercontent.com/u/1480587?v=4&s=120"}},{"id":"5dc950c3865a9844a30213ff","author_id":"5aebafbcadea947348e76068","tab":"ask","content":"<div class=\"markdown-text\"><p>我一直觉得Typescript挺好，也是我自己想要学习和深入的知识点。所以想请教一下如何驱动团队在新项目中使用Typescript，但是害怕到最后全部都是any</p>\n</div>","title":"如何驱动团队从JavaScript转到Typescript？","last_reply_at":"2019-11-13T05:55:23.376Z","good":false,"top":false,"reply_count":11,"visit_count":653,"create_at":"2019-11-11T12:14:59.524Z","author":{"loginname":"Rabbitzzc","avatar_url":"https://avatars0.githubusercontent.com/u/26913511?v=4&s=120"}},{"id":"5dc8028c865a9844a302105a","author_id":"5dc7e7aeece3813ad9ba7759","tab":"ask","content":"<div class=\"markdown-text\"><p>恳求大神帮忙解决下!!!\n用的express框架,mongodb的免费集群数据库,显示已经连接成功,\n<img src=\"//static.cnodejs.org/FsJMffSj7qCPb4dxIhZhbhAawnv2\" alt=\"1573387792(1).png\">\n<img src=\"//static.cnodejs.org/FiC8BAeHspuoSE6fmydrSTI5qKD3\" alt=\"1573387862(1).png\">\n<img src=\"//static.cnodejs.org/FiazGEWkFJGzQS8g5HrJ4D1TmvPx\" alt=\"1573387895(1).png\">\n我在路由api.js里用post提交数据\n<img src=\"//static.cnodejs.org/FpmW7gM6i4jaYIor3BDhEsCXmV9n\" alt=\"1573388697(1).png\">\n然后我用postman模拟数据提交,但是一直在加载中就是提交不上去是怎么回事啊?\n<img src=\"//static.cnodejs.org/FhseKCYnYhunWHXQdlhlnou15VbK\" alt=\"1573388439.png\">\n<img src=\"//static.cnodejs.org/FrWOCSgg0FhKbJCqDVs5XKbLeF7f\" alt=\"1573388480(1).png\">\n终端能打印出来数据,但是postman里提交不了.集群数据库里也没有数据\n<img src=\"//static.cnodejs.org/FhO33jHhqfY_3VqgzaMdYHS22_en\" alt=\"1573388512.png\"></p>\n</div>","title":"postman模拟node数据post提交不到mongoodb集群远程数据库怎么回事?","last_reply_at":"2019-11-13T03:32:57.937Z","good":false,"top":false,"reply_count":16,"visit_count":570,"create_at":"2019-11-10T12:29:00.320Z","author":{"loginname":"gaoxizhong","avatar_url":"https://avatars0.githubusercontent.com/u/39090789?v=4&s=120"}},{"id":"5cf718e995fcc914aa266316","author_id":"5a9783798d6e16e56bb80a34","tab":"ask","content":"<div class=\"markdown-text\"><p><img src=\"//static.cnodejs.org/FsCv5s_g7ixH7mbfA7xXg1fg41Gw\" alt=\"QQ图片20190605092113.png\"></p>\n<p>RT</p>\n</div>","title":"狼叔的《狼书（卷1）：更了不起的Node.js》有赠书吗？","last_reply_at":"2019-11-13T03:10:23.786Z","good":false,"top":false,"reply_count":27,"visit_count":5899,"create_at":"2019-06-05T01:20:41.677Z","author":{"loginname":"OXOYO","avatar_url":"https://avatars3.githubusercontent.com/u/5074207?v=4&s=120"}},{"id":"5c6cfdf5e1a81129a7ad8b7f","author_id":"5c663d23f53f161dbaeb3de4","tab":"ask","content":"<div class=\"markdown-text\"><p><strong>是《Nodejs in action》这本书吗</strong></p>\n<h4>新手求入门书籍和进阶书籍</h4>\n<blockquote>\n<p>先找到对的书， 然后拜读不是吗</p>\n</blockquote>\n<p><strong><em>求一下中文版pdf链接</em></strong></p>\n</div>","title":"狼叔说的 把《node in action》看五遍， 然后去写代码,  是认真的吗","last_reply_at":"2019-11-13T02:55:20.834Z","good":false,"top":false,"reply_count":31,"visit_count":5164,"create_at":"2019-02-20T07:12:53.928Z","author":{"loginname":"nelhu","avatar_url":"https://avatars3.githubusercontent.com/u/24311105?v=4&s=120"}},{"id":"5dcb6f33ece3813ad9ba801e","author_id":"5ce7b5f24036f24194cf6486","tab":"ask","content":"<div class=\"markdown-text\"><p>问一下大家ioredis cluster 关于 exists的命令是怎么使用呢？  使用  const client_redis = new redis.Cluster(clusterConnection);   client_redis 没有exists的命令</p>\n</div>","title":"问一下大家关于 ioredis cluster 的问题   谢谢","last_reply_at":"2019-11-13T02:49:23.452Z","good":false,"top":false,"reply_count":0,"visit_count":94,"create_at":"2019-11-13T02:49:23.452Z","author":{"loginname":"DWNEWS-weiqingtao","avatar_url":"https://avatars3.githubusercontent.com/u/49009350?v=4&s=120"}},{"id":"5d9be327ece3813ad9ba04cc","author_id":"5bf12bb1be1b120abac5a88b","tab":"share","content":"<div class=\"markdown-text\"><p><strong>2019年最新录制的Nestjs入门教程-B站免费看：</strong>  <a href=\"https://www.bilibili.com/video/av68935258\">https://www.bilibili.com/video/av68935258</a></p>\n<p>截止目前  已经20.1k star  了，预计10月底超过 sails 成为第三大框架，直逼 koa 的 27k 。 按目前增长趋势，2020年 可超过 koa 的star 数。</p>\n<p>Nest.js是一个渐进的Node.js框架，可以在TypeScript和JavaScript (ES6、ES7、ES8)之上构建高效、可伸缩的企业级服务器端应用程序。它的核心思想是提供了一个层与层直接的耦合度极小、抽象化极高的一个架构体系。Nest.js目前在行业内具有很高的关注度，所以我们有必要学习一下</p>\n<p><strong>来源</strong>:  <a href=\"https://www.itying.com/goods-1139.html\">https://www.itying.com/goods-1139.html</a></p>\n</div>","title":"Nestjs入门教程","last_reply_at":"2019-11-12T13:37:28.453Z","good":false,"top":false,"reply_count":18,"visit_count":4269,"create_at":"2019-10-08T01:15:19.231Z","author":{"loginname":"eggper","avatar_url":"https://avatars3.githubusercontent.com/u/44997460?v=4&s=120"}},{"id":"5dbab25eece3813ad9ba588e","author_id":"5d8798fc95464514f7ed90de","tab":"ask","content":"<div class=\"markdown-text\"><p>我平时冲突都是直接把代码复制出来，然后git checkout .或者git stash 然后git pull，请问大家有没有什么奇技淫巧</p>\n</div>","title":"请问git冲突了大家一般是怎么解决的呢？","last_reply_at":"2019-11-12T11:40:21.048Z","good":false,"top":false,"reply_count":8,"visit_count":2145,"create_at":"2019-10-31T10:07:26.657Z","author":{"loginname":"qiangwaikan","avatar_url":"https://avatars1.githubusercontent.com/u/55663185?v=4&s=120"}},{"id":"5dca664dece3813ad9ba7d95","author_id":"5dc8d79d865a9844a3021214","tab":"ask","content":"<div class=\"markdown-text\"><p>应为这个库没必要更新还是已经没有维护？\n<img src=\"//static.cnodejs.org/FkDrn2wdJtaOXRaiPWaAUEH0y2dr\" alt=\"屏幕快照 2019-11-12 15.58.55.png\"></p>\n</div>","title":"为什么npm上面显示koa-router上次更新是两年前","last_reply_at":"2019-11-12T11:27:20.315Z","good":false,"top":false,"reply_count":3,"visit_count":273,"create_at":"2019-11-12T07:59:09.105Z","author":{"loginname":"chenkai0520","avatar_url":"https://avatars2.githubusercontent.com/u/30174970?v=4&s=120"}},{"id":"5dc5212d865a9844a3020b59","author_id":"5cede24d4036f24194cf7669","tab":"ask","content":"<div class=\"markdown-text\"><p>小弟目前在做一个银行方面支付签名加密的功能，用的加密算法是sm2.一开始看到这个加密签名算法就很懵逼。没见过。百度了有所了解，单node能用的包比较少找了好久找到一个sm-crypto。但是签名还是和示例结果不一样。希望有大佬了解的能给小弟一点提示，谢谢。嘭嘭嘭！磕头了\n签名测试证书：MIIDJwIBATBHBgoqgRzPVQYBBAIBBgcqgRzPVQFoBDAeLV7q3Wh/fNYTNDCaupDJACCDvEV6pEwR3qCjXoimnCYeUbbyLxXTqgi883xA8mQwggLXBgoqgRzPVQYBBAIBBIICxzCCAsMwggJnoAMCAQIC\nBTADFydCMAwGCCqBHM9VAYN1BQAwKzELMAkGA1UEBhMCQ04xHDAaBgNVBAoME0NGQ0EgU00yIFRF\nU1QgT0NBMjEwHhcNMTkwNTE1MDkxODU3WhcNMjAwNTE1MDkxODU3WjB6MQswCQYDVQQGEwJDTjEN\nMAsGA1UECgwEQ01CQzESMBAGA1UECwwJQ01CQ19EQ01TMRkwFwYDVQQLDBBPcmdhbml6YXRpb25h\nbC0xMS0wKwYDVQQDDCQwMzA1QDcwNTAxNTE3MTZA6K+B5Lmm5pu05paw5rWL6K+VQDEwWTATBgcq\nhkjOPQIBBggqgRzPVQGCLQNCAATCE4w0oE40yXBUH/DmZLoI2V2g5gyHTC5LFGgOmubz/R9P2J4x\n2E1CprPpxAqWemSjZ+a0XUyZ/smiW9Tos/DYo4IBJTCCASEwHwYDVR0jBBgwFoAU4n62ELuU6xXm\nrtEVCv/o16BXOZ0wSAYDVR0gBEEwPzA9BghggRyG7yoCAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8v\nd3d3LmNmY2EuY29tLmNuL3VzL3VzLTEzLmh0bTBpBgNVHR8EYjBgMC6gLKAqhihodHRwOi8vMjEw\nLjc0LjQyLjMvT0NBMjEvU00yL2NybDE1NjAuY3JsMC6gLKAqhihodHRwOi8vMjEwLjc0LjQyLjMv\nT0NBMjEvU00yL2NybDE1NjAuY3JsMAsGA1UdDwQEAwID+DAdBgNVHQ4EFgQUgraLiaVncgA2mbji\nSMRoS1x+wukwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMAwGCCqBHM9VAYN1BQADSAAw\nRQIhALL1JIduYgpKQ0qr2jWI1byu8n/ibRr94K4ALvYCRu79AiA/tGZOT0EV5rzadajuXnqSeg7I\n6jVFUBSnMZqpA6oDrg==</p>\n<p>加密测试证书：-----BEGIN CERTIFICATE-----\nMIICvDCCAmCgAwIBAgIFMAMWgnMwDAYIKoEcz1UBg3UFADArMQswCQYDVQQGEwJD\nTjEcMBoGA1UECgwTQ0ZDQSBTTTIgVEVTVCBPQ0EyMTAeFw0xOTA1MTQwOTUzMzNa\nFw0yMDA1MTQwOTUzMzNaMHMxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKDARDTUJDMRIw\nEAYDVQQLDAlDTUJDX0RDTVMxGTAXBgNVBAsMEE9yZ2FuaXphdGlvbmFsLTExJjAk\nBgNVBAMMHTAzMDVAWjkwOEJBTktTTTJAOTA4QmFua1NtMkAxMFkwEwYHKoZIzj0C\nAQYIKoEcz1UBgi0DQgAEp4Fb7WzVx1Q6x4CUlt5Zi4zwJa4o90pYWUMctZ56UJ4p\nKQOhw/fStMukfLLImwBG0eDahY3ifDzzkA/K+ej7rqOCASUwggEhMB8GA1UdIwQY\nMBaAFOJ+thC7lOsV5q7RFQr/6NegVzmdMEgGA1UdIARBMD8wPQYIYIEchu8qAgIw\nMTAvBggrBgEFBQcCARYjaHR0cDovL3d3dy5jZmNhLmNvbS5jbi91cy91cy0xMy5o\ndG0waQYDVR0fBGIwYDAuoCygKoYoaHR0cDovLzIxMC43NC40Mi4zL09DQTIxL1NN\nMi9jcmwxNTU3LmNybDAuoCygKoYoaHR0cDovLzIxMC43NC40Mi4zL09DQTIxL1NN\nMi9jcmwxNTU3LmNybDALBgNVHQ8EBAMCA/gwHQYDVR0OBBYEFLhmRmKxGGCcMmQl\nTRxQdv/lW1x2MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAMBggqgRzP\nVQGDdQUAA0gAMEUCIQDRnl5AFJpwR70bj9f8uz6l90F5flf2wjkucrA1tzh4PgIg\nAc+miDyQZV8NhnRK8t5hDS/dH/wHD3VwayFVEcPwkXo=\n-----END CERTIFICATE-----</p>\n<p>下面是测试的</p>\n<ol>\n<li>商户后台按照要求，拼接查询信息，将查询信息拼接成 json 字符串\n{“platformId”:“3300000100016169”,“merchantNo”:“01201712081606430003”,“merSerialNo”:“tp20180116142640”,\n“fileNum”:“1”,“fileSize”:“145”,“fileMd5”:“dcc40f676cdf738e81a042ad7a95ef14”,“feeType”:“01”,“totalAmount”:“100.\n00”,“fileContent”:“MjAxNzExMjEwMDA5MjExMTIyMTJ8MTAzMzI0ODJ8546L5rSLfOWMl+S6rOS4nOWfjuWMunw\nxMTAxMTExMTkxOTgyMjM2fDEzODExMTEwMDAwfOWtpuadgui0uXwxMDAuMDB8MjAxNTA1MTF8MjAxNTEy\nMzB8MjAxNuW5tOWwj+WtpuWFtOi2o+Wfueiurei0uQ==”,“reserve”:&quot;&quot;}</li>\n<li>将上述字符串调用签名函数进行签名，得到签名值\nMEUCIGcnFdZCiltWU2UBbwXA18LiJ0oIqrLTcXFNhwgOUum0AiEAuJgohSL9ocnyBgAeRvoeW6nIaJ+pvg1AFzM\nh17YdoIs=</li>\n<li>将签名值和订单 json 字符串按如下格式拼接\n{“sign”:“MEUCIGcnFdZCiltWU2UBbwXA18LiJ0oIqrLTcXFNhwgOUum0AiEAuJgohSL9ocnyBgAeRvoeW6nIaJ+pv\ng1AFzMh17YdoIs=”,“body”:&quot;{“platformId”:“3300000100016169”,“merchantNo”:“01201712081606430003”,“\nmerSerialNo”:“tp20180116142640”,“fileNum”:“1”,“fileSize”:“145”,“fileMd5”:“dcc40f676cdf738e81a042ad7\na95ef14”,“feeType”:“01”,“totalAmount”:“100.00”,“fileContent”:“MjAxNzExMjEwMDA5MjExMTIyMTJ8MTAz\nMzI0ODJ8546L5rSLfOWMl+S6rOS4nOWfjuWMunwxMTAxMTExMTkxOTgyMjM2fDEzODExMTEwMDAwfOWtpu\nadgui0uXwxMDAuMDB8MjAxNTA1MTF8MjAxNTEyMzB8MjAxNuW5tOWwj+WtpuWFtOi2o+Wfueiurei0uQ==”,“r\neserve”:&quot;&quot;}&quot;}</li>\n<li>对 json 串加密得到请求密文\nMIIDSAYKKoEcz1UGAQQCA6CCAzgwggM0AgECMYGdMIGaAgECgBS1x/e/puEJbLQnTtwm2y/+fP1b2jANBgkq\ngRzPVQGCLQMFAARwLQyxFEhQdToL4smYyB7nOXnAtRWa3G8PvnD6rtQtcpsxZUfOfMUHjc1qt9kIkPS4XbYM\nukvRd4kG3hQESfoabibVmIvL2ZecPkrJqHkgYP5VLiiQcQ9acSeTTiBZ2QrPGj4/+EwVK9+WLyn+wr+9jzCCAo0G\nCiqBHM9VBgEEAgEwGwYHKoEcz1UBaAQQ8+Mg7nDGYdN/eIawzoF894CCAmB68MqPj2ONES0Gniy8TMmcr\nvjApZf1VB6CfF1Onl46+EqCiwpCB4uQuVZB4lQHw+1djjJeg9uiYouPvYDJyH9NeImZyw/aZXgl/jJa7d3LeFMnW2H\nBl6vCdVlfuQlxd1ouaHx9I/UYYfOEjrB1BXFx1nAw0V0jwEJ5aQh0Qftb27l7eHpblvMcTN2oDbzfgLGweHl8rBZPmO\nLxO8+FBCu3hjowvtJw8JVODgFvfZY0v414TQEqKkOPH8xXBvqTmAO/SV6gbW1GysIcIR26N9mVdkB3fqOZrXe\n2tQ8WyGl8TNGHFH9gx0CUcQM9HhhuT9dAe0gXRKpN6jhx0AXD4HBb+as9oFSN0JkXtOrkNk0vqTiwl4iDYON7\nUULmtaD1bHmL3FbO7Yg4BtDPfo8rMwVP9e/mneAismMTnFok12vNefsGgKyMUQZYy71FXfCdIczFen2ityGL6o\noSRoAlYoO5UfpfP1cXsZWmcXe7SWOZFglnzzWK9TrPtDzxclGtNMjCo/QpARULey8i9mg5ewkXFi53FeP8D8bxS\nKwyC7RUIDkyqJRNcz3enRS6Ec99zla3GXzlsy9IRx4MRgQHoG4pm8pf8rN9wTKDOI0HKoq/16jhqMT/kDN62ItIK\nAdoLcJmQJDbpxq+kpLHAKdQzgMmQjoFchSLyvx6P46NgkEF9Q2MjvkIsUVjve4qqrPaqUkP6ONZPRvH59UxdF\nQJos2Pq1/ILQWawFtufTtzHX0xXOfPj4BXosH12e2ZYS3EY60Ofqsjzy2Dqa21n2LF15UJm/Olhx2kzqW4yYbkKpX\nKbw==</li>\n</ol>\n</div>","title":"node sm2 签名加密吐血求问","last_reply_at":"2019-11-12T10:19:30.017Z","good":false,"top":false,"reply_count":11,"visit_count":1782,"create_at":"2019-11-08T08:02:53.730Z","author":{"loginname":"SKandAV","avatar_url":"https://avatars0.githubusercontent.com/u/26410064?v=4&s=120"}},{"id":"5db503e2ece3813ad9ba4622","author_id":"59662f1f8f9e3d4574dfb171","tab":"ask","content":"<div class=\"markdown-text\"><p><img src=\"//static.cnodejs.org/FuknvwXt9_25H89TilvYi-5FZ5XD\" alt=\"image.png\"></p>\n<p><img src=\"//static.cnodejs.org/FptwQXIW2f6XbBTNYClysR9tktMD\" alt=\"image.png\"></p>\n<p>是6.10版本的node\n会是什么原因造成这种区别？</p>\n</div>","title":"一个诡异的日期问题，不知道有没有人踩过","last_reply_at":"2019-11-12T07:42:43.398Z","good":false,"top":false,"reply_count":7,"visit_count":841,"create_at":"2019-10-27T02:41:38.890Z","author":{"loginname":"AllenYao","avatar_url":"https://avatars3.githubusercontent.com/u/1507528?v=4&s=120"}},{"id":"5db9022a865a9844a301eb1d","author_id":"5a4dd7e3ebc575dc49b27115","tab":"share","content":"<div class=\"markdown-text\"><p>最近在研究游戏相关的，于是就使用js撸了个a星寻路算法\n顺便撸了个算法的工作原理过程演示</p>\n<p>话不多说，直接上地址。</p>\n<p><a href=\"https://sbfkcel.github.io/fast-astar/\">https://sbfkcel.github.io/fast-astar/</a></p>\n<p><img src=\"//static.cnodejs.org/Fu3fqS1PJR-bV7j_ZhEJNz_DvCYb\" alt=\"demo.png\"></p>\n</div>","title":"a*寻路算法 for Javascript的实现及原理动画演示","last_reply_at":"2019-11-12T07:20:16.439Z","good":false,"top":false,"reply_count":4,"visit_count":3161,"create_at":"2019-10-30T03:23:22.060Z","author":{"loginname":"sbfkcel","avatar_url":"https://avatars1.githubusercontent.com/u/5469785?v=4&s=120"}},{"id":"5dca58c3865a9844a302161f","author_id":"5b0d752029e6e510415b25aa","tab":"ask","content":"<div class=\"markdown-text\"><ol>\n<li>alipay-sdk-nodejs 3.0.8</li>\n<li>使用的的沙箱环境</li>\n<li>应用私钥和支付宝公钥匹配</li>\n<li>sdk的配置沙箱环境网关，RSA2等都已经反复检查了</li>\n</ol>\n<p>使用方法：使用sdk生成一个支付url, 然后复制url到浏览器中直接访问，出现以下界面：\n<img src=\"//static.cnodejs.org/FsUxuIJW7GvoXQPrI0-mh2U6rGbT\" alt=\"image.png\"></p>\n<p>这是sdk传入的参数\n<img src=\"//static.cnodejs.org/Fs5oLiRpWmg_w1W7cJxKdfcOo8BT\" alt=\"image.png\"></p>\n<p>哪位大神能提点建议，感谢</p>\n</div>","title":"node alipay sdk 电脑网页支付，保验签出错？","last_reply_at":"2019-11-12T07:01:23.159Z","good":false,"top":false,"reply_count":0,"visit_count":118,"create_at":"2019-11-12T07:01:23.159Z","author":{"loginname":"jiayiming001","avatar_url":"https://avatars2.githubusercontent.com/u/32919372?v=4&s=120"}},{"id":"5dca2e2f865a9844a3021573","author_id":"5dc0f2e5865a9844a301ff20","tab":"share","content":"<div class=\"markdown-text\"><h3>项目地址</h3>\n<p><a href=\"https://datayi.cn/w/a9BJKX9n\">https://github.com/eolinker/goku-api-gateway</a></p>\n<h3>环境要求</h3>\n<ul>\n<li>linux系统，内核版本 2.6.23+</li>\n<li>net-tools</li>\n</ul>\n<h3>Docker安装</h3>\n<p>1.控制台docker：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console</a></p>\n<p>2.网关节点docker：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node</a></p>\n<p>Docker安装教程请查看Docker下的 <strong>Overview</strong> 页面</p>\n<h3>非Docker安装</h3>\n<p><strong>一、控制台安装</strong></p>\n<p>1.安装:</p>\n<pre class=\"prettyprint language-clike\"><code>  mkdir -p {install dir}\n  mv console-{version}.tar.gz {tmp}&#x2F;\n  cd {tmp}&#x2F;\n  tar -xzf console-{version}.tar.gz\n  cd console-{version} &amp;&amp; .&#x2F;install.sh {install dir}\n  cd {install dir}\n</code></pre><p><strong>2.首次安装进入{install dir}/console/config文件夹，编辑配置文件内容，配置语法参照yaml</strong></p>\n<p>goku.conf 如下：</p>\n<pre class=\"prettyprint language-clike\"><code>admin_bind: 绑定节点获取配置的地址，形如IP:Port，填写内网地址或本机地址\nlisten_port: 管理后台监听端口，可以开放给外网访问\ndb_type: sqlite\ndb_path: sqlite db的文件路径\n</code></pre><p><strong>3.进入{install dir}/console文件夹，运行run.sh文件以启动控制台</strong></p>\n<p>首次运行：</p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start {config file} 管理员账号 管理员密码\n</code></pre><p>示例：</p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start config&#x2F;goku.conf admin 123456\n</code></pre><p>非首次运行：</p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start|restart\n</code></pre><p><strong>4.在浏览器输入服务器IP+程序监听端口号，进入管理后台页面</strong></p>\n<p><em>注：程序监听端口号为goku.conf的配置项listen_port的值</em></p>\n<p><img src=\"https://img-blog.csdnimg.cn/2019111116532583.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlcm9ud2lsbGlhbQ==,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\"></p>\n<p>至此控制台已安装完成，在控制台新建节点后，需要通过命令行启动节点。</p>\n<h3>二、节点安装</h3>\n<p><strong>1.安装：</strong></p>\n<pre class=\"prettyprint language-clike\"><code>  mkdir -p {install dir}\n  mv goku-node-{version}.tar.gz {tmp}&#x2F;\n  cd {tmp}&#x2F;\n  tar -xzf goku-node-{version}.tar.gz\n  cd goku-node-{version} &amp;&amp; .&#x2F;install.sh {install dir}\n  cd {install dir}\n</code></pre><p><strong>2.在管理后台新建节点</strong></p>\n<p>（1）登录控制台，一级菜单选择 网关节点，创建集群：\n<img src=\"https://img-blog.csdnimg.cn/20191111165429103.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlcm9ud2lsbGlhbQ==,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\">\n（2）进入相应的集群，为不同集群 添加节点：\n<img src=\"https://img-blog.csdnimg.cn/20191111165508842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlcm9ud2lsbGlhbQ==,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\"></p>\n<p>（3）点击 <strong>新增节点</strong> 按钮，填写节点信息后点击 <strong>确定</strong>：</p>\n<p>监听地址：用于监听节点程序；监听的端口号用于节点做请求转发</p>\n<p>管理地址：用于监控组件获取监控数据和对节点做健康检查等\n<img src=\"https://img-blog.csdnimg.cn/20191111165603481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlcm9ud2lsbGlhbQ==,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\">\n<strong>3.启动节点程序：</strong></p>\n<p>命令：</p>\n<pre class=\"prettyprint language-clike\"><code>{install dir}&#x2F;run.sh {start|stop|reload|restart|force-reload} [admin url] {nodeKey}\n</code></pre><p>示例：</p>\n<pre class=\"prettyprint language-clike\"><code> {install dir}&#x2F;run.sh start 127.0.0.1:7005 6d345cafc22c1b411ed54dc5201c816a\n</code></pre><p><strong>4.检查节点是否正常运行：</strong></p>\n<p>进入节点管理页面，若节点的状态显示为运行中，则节点正常启动：\n<img src=\"https://img-blog.csdnimg.cn/20191111165742516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlcm9ud2lsbGlhbQ==,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\"></p>\n<h2>相关链接</h2>\n<p>项目地址：<a href=\"https://datayi.cn/w/a9BJKX9n\">https://github.com/eolinker/goku-api-gateway</a>\n官网地址：<a href=\"https://www.eolinker.com\">https://www.eolinker.com</a>\n教程地址：<a href=\"https://help.eolinker.com/#/tutorial/?groupID=c-376&amp;productID=19\">https://help.eolinker.com/#/tutorial/?groupID=c-376&amp;productID=19</a></p>\n</div>","title":"基于go语言的开源网关 Goku API Gateway CE 部署指南","last_reply_at":"2019-11-12T03:59:43.576Z","good":false,"top":false,"reply_count":0,"visit_count":162,"create_at":"2019-11-12T03:59:43.576Z","author":{"loginname":"Tungevaag","avatar_url":"https://avatars2.githubusercontent.com/u/57382593?v=4&s=120"}},{"id":"5dca21d1865a9844a3021534","author_id":"57cccd8ca843659126f98ca4","tab":"share","content":"<div class=\"markdown-text\"><p>showdoc 是一个非常适合 IT 团队的在线 API 文档、技术文档工具 ，详细介绍可见： <a href=\"http://www.showdoc.cc/help\">www.showdoc.cc/help</a></p>\n<p>本次更新推出手机客户端，支持在安卓和苹果手机上编辑文档。下载地址：<a href=\"http://www.showdoc.cc/app\">www.showdoc.cc/app</a></p>\n<p>如果你是使用官网在线版 showdoc，则下载 app 后直接使用即可。如果你是使用开源版 showdoc （在 github 搜索 showdoc 就可以找到开源地址 )，则需要在登录的时候，在界面右上角指定你部署 showdoc 的服务器地址。请注意确保你的服务器代码已经更新到最新版。</p>\n<p>可能有用户会觉得，写 API 等技术文档都是用电脑屏幕才比较好操作，谁会用手机端来写 api 文档啊？\n其实 ，我们依然鼓励用户使用 PC 网页端来完成 API 文档、数据字典文档的编写。但是移动 app 有它适合的其它应用场景：\n1，比如出差，比如刚好不在电脑旁边，想通过手机来修改下 showdoc 上的一些文档小错误等等。\n2，用作个人、团队乃至公司的资料库，存放各种资料性文档。对于资料库而言，易于编辑和阅读就是最核心的功能了。所以我们提供从 PC 到移动端的跨平台一致性体验，使资料更容易被编辑和阅读。</p>\n</div>","title":"showdoc 推出移动版 App","last_reply_at":"2019-11-12T03:06:57.099Z","good":false,"top":false,"reply_count":0,"visit_count":174,"create_at":"2019-11-12T03:06:57.099Z","author":{"loginname":"star7th","avatar_url":"https://avatars0.githubusercontent.com/u/2200494?v=4&s=120"}},{"id":"5d6de312a7474a231a5886bd","author_id":"5c18354b7ec239239ff57c7f","tab":"share","content":"<div class=\"markdown-text\"><p>去年和朋友一起翻译的《架构师修炼之道》最近出版了。水平有限，请大家多多指正。荣誉是大家的，有错误都算我的！</p>\n<p>这本书是写给想当架构师的童鞋看的，全书分两个部分，第一部分主要讲程序猿要掌握哪些思维方式和工作方式才能成为架构师，以及架构设计的案例。第二部分归纳若干带领团队设计架构的方法和技巧。国外读者对这本书评价很高，原书读起来也很流畅，但愿我们翻译没拖后腿。</p>\n<p>闲话少说，这里有<strong>38页试读</strong>：<a href=\"https://pan.baidu.com/s/1pk50WEv9XqjHkLtH7iXxgg\">https://pan.baidu.com/s/1pk50WEv9XqjHkLtH7iXxgg</a></p>\n<p>希望这本书对大家升职加薪脱单有帮助:)</p>\n<p><strong>天猫有售</strong>：<a href=\"https://detail.tmall.com/item.htm?spm=a230r.1.14.15.1cea1774yUneie&amp;id=601144871555\">https://detail.tmall.com/item.htm?spm=a230r.1.14.15.1cea1774yUneie&amp;id=601144871555</a></p>\n<p><img src=\"//static.cnodejs.org/Fi-NjeCWw2xxioeMNBpnWVzhyDYO\" alt=\"QQ图片20190830144122.jpg\"></p>\n</div>","title":"新书《架构师修炼之道》试读","last_reply_at":"2019-11-12T02:12:52.011Z","good":false,"top":false,"reply_count":13,"visit_count":5722,"create_at":"2019-09-03T03:50:42.832Z","author":{"loginname":"sean-xu","avatar_url":"https://avatars0.githubusercontent.com/u/430849?v=4&s=120"}},{"id":"5dc8c6f1865a9844a30211bc","author_id":"5c1ea9e976c4964062a1ca15","tab":"share","content":"<div class=\"markdown-text\"><p>github地址\n<a href=\"https://github.com/theanarkh/read-libuv-code\">https://github.com/theanarkh/read-libuv-code</a></p>\n<p>文件监听的原理是，第一次先执行stat函数获取文件基本信息，然后在stat的回调函数里设置定时器，定时器超时后会执行stat，然后获取stat信息，再次执行stat回调函数重新设置定时器，如此反复，如果stat不一样就执行用户的回调。</p>\n<pre class=\"prettyprint language-c\"><code>#include &quot;uv.h&quot;\n#include &quot;uv-common.h&quot;\n\n#include &lt;assert.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n\nstruct poll_ctx {\n  uv_fs_poll_t* parent_handle; &#x2F;* NULL if parent has been stopped or closed *&#x2F;\n  int busy_polling;\n  unsigned int interval;\n  uint64_t start_time;\n  uv_loop_t* loop;\n  uv_fs_poll_cb poll_cb;\n  uv_timer_t timer_handle;\n  uv_fs_t fs_req; &#x2F;* TODO(bnoordhuis) mark fs_req internal *&#x2F;\n  uv_stat_t statbuf;\n  &#x2F;&#x2F; 字符串的值追加在结构体后面\n  char path[1]; &#x2F;* variable length *&#x2F;\n};\n\nstatic int statbuf_eq(const uv_stat_t* a, const uv_stat_t* b);\nstatic void poll_cb(uv_fs_t* req);\nstatic void timer_cb(uv_timer_t* timer);\nstatic void timer_close_cb(uv_handle_t* handle);\n\nstatic uv_stat_t zero_statbuf;\n\n&#x2F;&#x2F; 初始化uv_fs_poll_t结构\nint uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle) {\n  uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_POLL);\n  return 0;\n}\n\n\nint uv_fs_poll_start(uv_fs_poll_t* handle,\n                     uv_fs_poll_cb cb,\n                     const char* path,\n                     unsigned int interval) {\n  struct poll_ctx* ctx;\n  uv_loop_t* loop;\n  size_t len;\n  int err;\n\n  if (uv__is_active(handle))\n    return 0;\n\n  loop = handle-&gt;loop;\n  len = strlen(path);\n  &#x2F;&#x2F; 分配一块内存存上下文结构体和path对应的字符串\n  ctx = uv__calloc(1, sizeof(*ctx) + len);\n\n  if (ctx == NULL)\n    return UV_ENOMEM;\n  &#x2F;&#x2F; 初始化上下文结构\n  ctx-&gt;loop = loop;\n  &#x2F;&#x2F; 内容改变时的回调\n  ctx-&gt;poll_cb = cb;\n  &#x2F;&#x2F; 多久检测一次内容是否变化\n  ctx-&gt;interval = interval ? interval : 1;\n  &#x2F;&#x2F; 开始的时间点\n  ctx-&gt;start_time = uv_now(loop);\n  &#x2F;&#x2F; 上下文对应的handle结构\n  ctx-&gt;parent_handle = handle;\n  memcpy(ctx-&gt;path, path, len + 1);\n  &#x2F;&#x2F; 初始化一个定时器\n  err = uv_timer_init(loop, &amp;ctx-&gt;timer_handle);\n  if (err &lt; 0)\n    goto error;\n  &#x2F;&#x2F; 设置UV_HANDLE_INTERNAL标记位\n  ctx-&gt;timer_handle.flags |= UV_HANDLE_INTERNAL;\n  &#x2F;&#x2F;清除UV_HANDLE_REF标记\n  uv__handle_unref(&amp;ctx-&gt;timer_handle);\n  &#x2F;&#x2F; 异步获取path对应的文件的信息，获取到后执行poll_cb\n  err = uv_fs_stat(loop, &amp;ctx-&gt;fs_req, ctx-&gt;path, poll_cb);\n  if (err &lt; 0)\n    goto error;\n  &#x2F;&#x2F; 挂载上下文到handle\n  handle-&gt;poll_ctx = ctx;\n  &#x2F;&#x2F; 激活该handle，但不增加handle的active数\n  uv__handle_start(handle);\n\n  return 0;\n\nerror:\n  &#x2F;&#x2F; 出错则释放分配的内存\n  uv__free(ctx);\n  return err;\n}\n\n&#x2F;&#x2F; 停止poll\nint uv_fs_poll_stop(uv_fs_poll_t* handle) {\n  struct poll_ctx* ctx;\n\n  if (!uv__is_active(handle))\n    return 0;\n\n  ctx = handle-&gt;poll_ctx;\n  assert(ctx != NULL);\n  assert(ctx-&gt;parent_handle != NULL);\n  &#x2F;&#x2F; 解除关联\n  ctx-&gt;parent_handle = NULL;\n  handle-&gt;poll_ctx = NULL;\n\n  &#x2F;* Close the timer if it&#x27;s active. If it&#x27;s inactive, there&#x27;s a stat request\n   * in progress and poll_cb will take care of the cleanup.\n   *&#x2F;\n  &#x2F;&#x2F; 停止定时器，设置回调为time_close_cb，设置状态为closing\n  if (uv__is_active(&amp;ctx-&gt;timer_handle))\n    uv_close((uv_handle_t*)&amp;ctx-&gt;timer_handle, timer_close_cb);\n  \n  uv__handle_stop(handle);\n\n  return 0;\n}\n\n&#x2F;&#x2F; 获取path\nint uv_fs_poll_getpath(uv_fs_poll_t* handle, char* buffer, size_t* size) {\n  struct poll_ctx* ctx;\n  size_t required_len;\n\n  if (!uv__is_active(handle)) {\n    *size = 0;\n    return UV_EINVAL;\n  }\n\n  ctx = handle-&gt;poll_ctx;\n  assert(ctx != NULL);\n\n  required_len = strlen(ctx-&gt;path);\n  if (required_len &gt;= *size) {\n    *size = required_len + 1;\n    return UV_ENOBUFS;\n  }\n\n  memcpy(buffer, ctx-&gt;path, required_len);\n  *size = required_len;\n  buffer[required_len] = &#x27;\\0&#x27;;\n\n  return 0;\n}\n\n\nvoid uv__fs_poll_close(uv_fs_poll_t* handle) {\n  uv_fs_poll_stop(handle);\n}\n\n&#x2F;&#x2F; 定时器到期执行的回调\nstatic void timer_cb(uv_timer_t* timer) {\n  struct poll_ctx* ctx;\n\n  ctx = container_of(timer, struct poll_ctx, timer_handle);\n  assert(ctx-&gt;parent_handle != NULL);\n  assert(ctx-&gt;parent_handle-&gt;poll_ctx == ctx);\n  ctx-&gt;start_time = uv_now(ctx-&gt;loop);\n  &#x2F;&#x2F; 再次获取stat信息\n  if (uv_fs_stat(ctx-&gt;loop, &amp;ctx-&gt;fs_req, ctx-&gt;path, poll_cb))\n    abort();\n}\n\n&#x2F;&#x2F; 获取到stat后执行的回调\nstatic void poll_cb(uv_fs_t* req) {\n  uv_stat_t* statbuf;\n  struct poll_ctx* ctx;\n  uint64_t interval;\n\n  ctx = container_of(req, struct poll_ctx, fs_req);\n\n  if (ctx-&gt;parent_handle == NULL) { &#x2F;* handle has been stopped or closed *&#x2F;\n    uv_close((uv_handle_t*)&amp;ctx-&gt;timer_handle, timer_close_cb);\n    uv_fs_req_cleanup(req);\n    return;\n  }\n\n  if (req-&gt;result != 0) {\n    if (ctx-&gt;busy_polling != req-&gt;result) {\n      ctx-&gt;poll_cb(ctx-&gt;parent_handle,\n                   req-&gt;result,\n                   &amp;ctx-&gt;statbuf,\n                   &amp;zero_statbuf);\n      ctx-&gt;busy_polling = req-&gt;result;\n    }\n    goto out;\n  }\n\n  statbuf = &amp;req-&gt;statbuf;\n  &#x2F;&#x2F; 第一次不执行回调，因为没有可对比的stat，第二次及后续的操作才可能执行回调，因为第一次执行的时候置busy_polling=1\n  if (ctx-&gt;busy_polling != 0)\n    &#x2F;&#x2F; 出错或者stat发生了变化则执行回调\n    if (ctx-&gt;busy_polling &lt; 0 || !statbuf_eq(&amp;ctx-&gt;statbuf, statbuf))\n      ctx-&gt;poll_cb(ctx-&gt;parent_handle, 0, &amp;ctx-&gt;statbuf, statbuf);\n  &#x2F;&#x2F; 保存当前获取到的stat信息，置1\n  ctx-&gt;statbuf = *statbuf;\n  ctx-&gt;busy_polling = 1;\n\nout:\n  uv_fs_req_cleanup(req);\n\n  if (ctx-&gt;parent_handle == NULL) { &#x2F;* handle has been stopped by callback *&#x2F;\n    uv_close((uv_handle_t*)&amp;ctx-&gt;timer_handle, timer_close_cb);\n    return;\n  }\n\n  &#x2F;* Reschedule timer, subtract the delay from doing the stat(). *&#x2F;\n  &#x2F;*\n    假设在开始时间点为1，interval为10的情况下执行了stat，stat完成执行并执行poll_cb回调的时间点是\n    3，那么定时器的超时时间则为10-3=7，即7个单位后就要触发超时，而不是10，是因为stat阻塞消耗了3个单位的\n    时间，所以下次执行超时回调函数时说明从start时间点开始算，已经经历了x单位各interval，然后超时回调里又\n    执行了stat函数，再到执行stat回调，这个时间点即now=start+x单位个interval+stat消耗的时间。得出now-start\n    为interval的x倍+stat消耗，即对interval取余可得到stat消耗，所以\n    当前轮，定时器的超时时间为interval - ((now-start) % interval)\n  *&#x2F;\n  interval = ctx-&gt;interval;\n  interval -= (uv_now(ctx-&gt;loop) - ctx-&gt;start_time) % interval;\n\n  if (uv_timer_start(&amp;ctx-&gt;timer_handle, timer_cb, interval, 0))\n    abort();\n}\n\n&#x2F;&#x2F; 释放上下文结构体的内存\nstatic void timer_close_cb(uv_handle_t* handle) {\n  uv__free(container_of(handle, struct poll_ctx, timer_handle));\n}\n\n\nstatic int statbuf_eq(const uv_stat_t* a, const uv_stat_t* b) {\n  return a-&gt;st_ctim.tv_nsec == b-&gt;st_ctim.tv_nsec\n      &amp;&amp; a-&gt;st_mtim.tv_nsec == b-&gt;st_mtim.tv_nsec\n      &amp;&amp; a-&gt;st_birthtim.tv_nsec == b-&gt;st_birthtim.tv_nsec\n      &amp;&amp; a-&gt;st_ctim.tv_sec == b-&gt;st_ctim.tv_sec\n      &amp;&amp; a-&gt;st_mtim.tv_sec == b-&gt;st_mtim.tv_sec\n      &amp;&amp; a-&gt;st_birthtim.tv_sec == b-&gt;st_birthtim.tv_sec\n      &amp;&amp; a-&gt;st_size == b-&gt;st_size\n      &amp;&amp; a-&gt;st_mode == b-&gt;st_mode\n      &amp;&amp; a-&gt;st_uid == b-&gt;st_uid\n      &amp;&amp; a-&gt;st_gid == b-&gt;st_gid\n      &amp;&amp; a-&gt;st_ino == b-&gt;st_ino\n      &amp;&amp; a-&gt;st_dev == b-&gt;st_dev\n      &amp;&amp; a-&gt;st_flags == b-&gt;st_flags\n      &amp;&amp; a-&gt;st_gen == b-&gt;st_gen;\n}\n\n\n#if defined(_WIN32)\n\n#include &quot;win&#x2F;internal.h&quot;\n#include &quot;win&#x2F;handle-inl.h&quot;\n\nvoid uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle) {\n  assert(handle-&gt;flags &amp; UV_HANDLE_CLOSING);\n  assert(!(handle-&gt;flags &amp; UV_HANDLE_CLOSED));\n  uv__handle_close(handle);\n}\n\n</code></pre><p>做技术的同学，欢迎关注公众号编程杂技。分享技术，交流技术。\n<img src=\"//static.cnodejs.org/FvGxbqpRq3Qd07uGDpoZXpqQrAj0\" alt=\"qrcode.png\"></p>\n</div>","title":"libuv之文件监听---fs-poll.c","last_reply_at":"2019-11-11T02:26:57.201Z","good":false,"top":false,"reply_count":0,"visit_count":242,"create_at":"2019-11-11T02:26:57.201Z","author":{"loginname":"theanarkh","avatar_url":"https://avatars1.githubusercontent.com/u/21155906?v=4&s=120"}},{"id":"5dc5387cece3813ad9ba7302","author_id":"5b39abe057137f22415c51eb","tab":"ask","content":"<div class=\"markdown-text\"><p>现在公司有需求需要我们node调用java的dubbo相关的接口。。有大佬调用过么\n我现在用的node-zookeeper-dubbo  但是一直报101的错 求大佬帮忙</p>\n</div>","title":"有大佬用node和java的dubbo进行通信么","last_reply_at":"2019-11-11T02:25:26.759Z","good":false,"top":false,"reply_count":3,"visit_count":482,"create_at":"2019-11-08T09:42:20.524Z","author":{"loginname":"nlbydcg","avatar_url":"https://avatars2.githubusercontent.com/u/40027034?v=4&s=120"}},{"id":"5dc84427ece3813ad9ba77eb","author_id":"590d49aeba8670562a40ed64","tab":"share","content":"<div class=\"markdown-text\"><p><img src=\"//static.cnodejs.org/Fu-S-8p1DbAToY183NXuvd8Pwqld\" alt=\"1572258994631-6b1c3f68-eb01-4231-9852-034702388c13.png\"></p>\n<h1>前言</h1>\n<p>通过前两篇文章的学习，我们已经解了Libuv中的<a href=\"https://mp.weixin.qq.com/s/SBM_l59351SqwkRE3xhJ_w\">队列</a>和<a href=\"https://mp.weixin.qq.com/s/63EGeW0mUN3Ej22KlqVVkw\">线程基础</a>，为本文的学习打下基础，没有看过的同学建议先看下。下面将从生产者消费者模型和源码两个角度学习Libuv的线程池，为后面学习Libuv文件处理做铺垫。</p>\n<h1>生产者消费者模型</h1>\n<p>Node.js的文件操作支持同步调用和异步调用，根据Libuv官网的介绍，我们知道它没有跨平台的异步文件IO可以使用，所以它的异步文件IO是通过在线程池中执行同步文件IO实现的。那具体是怎么实现的呢？答案就是生产者消费者模型。Libuv的线程包括2部分，一个是主线程，一个是线程池。主线程的一部分工作是描述任务并将其提交给线程池，线程池进行处理。拿异步文件操作为例，主线程生成一个描述文件操作的对象，将其提交到任务队列；线程池从任务队列获取该对象进行处理。其中主线程是生产者，线程池中的线程是消费者，任务队列是生产者和消费者之间的桥梁，下面是一个简单的示意图：</p>\n<p><img src=\"//static.cnodejs.org/FsSLRSkB2mdmsiN5c7zjWgOP3Ypq\" alt=\"1573392692932-48bf188f-5fcb-419f-8329-a686d281007e.png\"></p>\n<p>Libuv在生产者消费者模型中多加了一步，线程池执行完任务后，将结果交给主线程，主线程拿到结果后，如果发现有回调函数需要执行，就执行。所以Libuv的线程模型如下：</p>\n<p><img src=\"//static.cnodejs.org/Fnee-fskfEJ3drhsWC8iDHh_d4SF\" alt=\"1573392929503-0af9bc4a-8590-4348-bebd-4943dfd3ea20.png\"></p>\n<h1>源码分析</h1>\n<p>Libuv线程池的代码很容易找到，就在src目录下的threadpool.c文件中。</p>\n<p><img src=\"//static.cnodejs.org/Fgrc-fFDepzDv0D3BM3zvcWODKCl\" alt=\"1573393406765-b459960c-fb44-4228-9f74-16f84b37c8f6.png\"></p>\n<p>通过上面对生产者消费者模型的介绍，该代码大致分为4部分：任务队列、主线程提交任务到任务队列（提交任务）、线程池从任务队列获取任务并执行（消费任务）、线程池执行完任务通知主线程执行回调函数（回调处理）。</p>\n<h2>任务队列</h2>\n<p>任务队列就是一个队列而已。由于任务队列会被多个线程（主线程、线程池）同时访问，为了保证线程安全，需要互斥锁。另外任务队列如果为空，线程池中的线程需要挂起，等待主线程提交任务后唤起，所以还需要条件变量。任务队列、条件变量、互斥量的定义如下所示：</p>\n<pre class=\"prettyprint language-c\"><code>...\nstatic uv_cond_t cond; &#x2F;&#x2F; 条件变量\nstatic uv_mutex_t mutex; &#x2F;&#x2F; 互斥锁\n...\nstatic QUEUE wq; &#x2F;&#x2F; 任务队列\n...\n</code></pre><h2>提交任务</h2>\n<p>主线程将任务提交到任务队列是通过uv__work_submit来实现的，让我们来看下它的代码：</p>\n<pre class=\"prettyprint language-c\"><code>struct uv__work {\n  void (*work)(struct uv__work *w);\n  void (*done)(struct uv__work *w, int status);\n  struct uv_loop_s* loop;\n  void* wq[2]; &#x2F;&#x2F; 用于将其关联到任务队列中\n};\n\nvoid uv__work_submit(uv_loop_t* loop,\n                     struct uv__work* w,\n                     enum uv__work_kind kind,\n                     void (*work)(struct uv__work* w),\n                     void (*done)(struct uv__work* w, int status)) {\n  uv_once(&amp;once, init_once); &#x2F;&#x2F; 初始化线程，无乱调用多少次，init_once只会执行一次\n  w-&gt;loop = loop; &#x2F;&#x2F; 事件循环\n  w-&gt;work = work; &#x2F;&#x2F; 线程池要执行的函数\n  w-&gt;done = done; &#x2F;&#x2F; 线程池执行结束后，通知主线程要执行的函数\n  post(&amp;w-&gt;wq, kind); &#x2F;&#x2F; 将任务提交任务队列中\n}\n</code></pre><p>uv__work_submit有4个参数：第一个参数为Libuv的事件循环，这里我们先忽略，以后会有专门的文章介绍；第二个参数是线程池执行任务的通用模型，类型为uv__work，属性work表示线程池中要执行的函数，属性done表示线程池执行完，通知主线程要执行的函数；第三、四个参数分别对应work函数和done函数。该函数主要做了两件事情：一件是通过uv_once调用init_once来初始化线程池；另一件是对w进行赋值，然后通过post将其提交到任务队列。这里需要注意，通过nv_once可以保证uv__work_submit在调用多次的情况，init_once只执行一次，nv_once底层是通过pthread_once实现的。init_once会在下一节介绍，让我们先来看下post。</p>\n<pre class=\"prettyprint language-c\"><code>static void post(QUEUE* q, enum uv__work_kind kind) {\n  &#x2F;&#x2F; 获取锁\n  uv_mutex_lock(&amp;mutex);\n  ...\n  &#x2F;&#x2F; 将任务添加到任务队列的最后\n  QUEUE_INSERT_TAIL(&amp;wq, q);\n  \n  &#x2F;&#x2F; 如果线程池中有挂起的线程，就唤起挂起的线程，让其工作\n  if (idle_threads &gt; 0)\n    uv_cond_signal(&amp;cond);\n  &#x2F;&#x2F; 释放锁\n  uv_mutex_unlock(&amp;mutex);\n}\n</code></pre><p>代码很简单，先获取锁mutex，然后将任务提交到任务队列中。如果线程池中有挂起的线程，就通过条件变量cond唤起并放弃锁mutex。</p>\n<h2>消费任务</h2>\n<p>任务队列中的任务是通过线程池进行消费的，而线程池的初始化是在uv__work_submit调用init_once实现的，先看下如何初始化线程池吧：</p>\n<pre class=\"prettyprint language-c\"><code>static void init_once(void) {\n  ...\n  init_threads();\n}\n</code></pre><p>init_once调用了init_threads，那就看下init_threads。</p>\n<pre class=\"prettyprint language-c\"><code>...\n#define MAX_THREADPOOL_SIZE 1024 &#x2F;&#x2F; 线程池的最大数量\n...\nstatic uv_thread_t* threads; &#x2F;&#x2F; 线程池\nstatic uv_thread_t default_threads[4]; &#x2F;&#x2F; 默认的线程池，线程数量为4\n...\n\n\nstatic void init_threads(void) {\n  unsigned int i;\n  const char* val;\n  ...\n      \n  &#x2F;&#x2F; 计算线程池中线程的数量，不能大于最大值MAX_THREADPOOL_SIZE\n  nthreads = ARRAY_SIZE(default_threads);\n    \n  &#x2F;&#x2F; 通过环境变量设置线程池的大小\n  val = getenv(&quot;UV_THREADPOOL_SIZE&quot;);\n  if (val != NULL)\n    nthreads = atoi(val);\n  \n  &#x2F;&#x2F; 保存线程池中最少有一个线程\n  if (nthreads == 0)\n    nthreads = 1;\n    \n  &#x2F;&#x2F; 线程池中线程数量不能超过MAX_THREADPOOL_SIZE\n  if (nthreads &gt; MAX_THREADPOOL_SIZE)\n    nthreads = MAX_THREADPOOL_SIZE;\n    \n  &#x2F;&#x2F; 初始化线程池\n  threads = default_threads;\n  if (nthreads &gt; ARRAY_SIZE(default_threads)) {\n    threads = uv__malloc(nthreads * sizeof(threads[0]));\n    if (threads == NULL) {\n      nthreads = ARRAY_SIZE(default_threads);\n      threads = default_threads;\n    }\n  }\n    \n  &#x2F;&#x2F; 创建条件变量\n  if (uv_cond_init(&amp;cond))\n    abort();\n  \n  &#x2F;&#x2F; 创建互斥量\n  if (uv_mutex_init(&amp;mutex))\n    abort();\n\n  &#x2F;&#x2F; 初始化任务队列\n  QUEUE_INIT(&amp;wq);\n  ...\n      \n  &#x2F;&#x2F; 根据线程池的数量，初始化线程池中的每个线程，并执行worker函数\n  for (i = 0; i &lt; nthreads; i++)\n    if (uv_thread_create(threads + i, worker, &amp;sem))\n      abort();\n  \n  ...\n}\n</code></pre><p>通过上面的代码可以知道init_threads先获取线程池的大小nthreads；然后初始化互斥量mutex、条件变量cond和任务队列wq；最后创建nthreads个线程，每个线程执行worker函数。worker函数的核心就是消费任务队列中的任务，让我们详细的看下它：</p>\n<pre class=\"prettyprint language-c\"><code>static void worker(void* arg) {\n  struct uv__work* w;\n  QUEUE* q;\n  \n  ...\n  arg = NULL;\n    \n  &#x2F;&#x2F; 获取互斥锁\n  uv_mutex_lock(&amp;mutex);\n    \n  &#x2F;&#x2F; 通过无限循环，保证线程一直执行\n  for (;;) {\n    \n    &#x2F;&#x2F; 如果任务队列为空，通过等待条件变量cond挂起，并释放锁mutex\n    &#x2F;&#x2F; 主线程提交任务通过uv_cond_signal唤起，并重新获取锁mutex\n    while (QUEUE_EMPTY(&amp;wq) || ...) {\n      idle_threads += 1;\n      uv_cond_wait(&amp;cond, &amp;mutex);\n      idle_threads -= 1;\n    }\n      \n    &#x2F;&#x2F; 从任务队列中获取第一个任务\n    q = QUEUE_HEAD(&amp;wq);\n    ...\n        \n    &#x2F;&#x2F; 将该任务从任务队列中删除\n    QUEUE_REMOVE(q);\n    QUEUE_INIT(q);\n      \n    ...\n        \n    &#x2F;&#x2F; 操作完任务队列，释放锁mutex\n    uv_mutex_unlock(&amp;mutex);\n\n    &#x2F;&#x2F; 获取uv__work对象，并执行work\n    w = QUEUE_DATA(q, struct uv__work, wq);\n    w-&gt;work(w);\n\n    &#x2F;&#x2F; 获取loop的互斥锁wq_mutex\n    uv_mutex_lock(&amp;w-&gt;loop-&gt;wq_mutex);\n    w-&gt;work = NULL;\n    \n    &#x2F;&#x2F; 将执行完work函数的任务挂到loop-&gt;wq队列中\n    QUEUE_INSERT_TAIL(&amp;w-&gt;loop-&gt;wq, &amp;w-&gt;wq);\n      \n    &#x2F;&#x2F; 通过uv_async_send通知主线程，当然有任务执行完了，主线程可以执行任务中的done函数。\n    uv_async_send(&amp;w-&gt;loop-&gt;wq_async);\n    uv_mutex_unlock(&amp;w-&gt;loop-&gt;wq_mutex);\n\n    &#x2F;&#x2F; 获取锁，执行任务队列中的下一个任务\n    ...\n    uv_mutex_lock(&amp;mutex);\n    ...\n  }\n}\n</code></pre><p>worker的本质就是从任务队列中获取任务，然后执行work函数。执行完后，将该任务提交到事件循环loop的wp队列中，通过uv_async_send告知主线程执行任务中的done函数。</p>\n<h2>回调处理</h2>\n<p>上面我们介绍了worker函数在执行完任务后会通过uv_async_send告知主线程执行回调函数，那这块是怎么实现的呢？这里涉及到了事件循环，这里我们就简单的介绍一下，后面会有详细的文章介绍它。事件循环loop在初始化的时候会调用uv_async_init，该函数的第三个参数是一个函数，当其他线程调用uv_async_send时，该函数就会执行。具体代码如下：</p>\n<pre class=\"prettyprint language-c\"><code>uv_async_init(loop, &amp;loop-&gt;wq_async, uv__work_done);\n\nvoid uv__work_done(uv_async_t* handle) {\n  struct uv__work* w;\n  uv_loop_t* loop;\n  QUEUE* q;\n  QUEUE wq;\n  int err;\n\n  loop = container_of(handle, uv_loop_t, wq_async);\n  uv_mutex_lock(&amp;loop-&gt;wq_mutex);\n  QUEUE_MOVE(&amp;loop-&gt;wq, &amp;wq);\n  uv_mutex_unlock(&amp;loop-&gt;wq_mutex);\n\n  while (!QUEUE_EMPTY(&amp;wq)) {\n    q = QUEUE_HEAD(&amp;wq);\n    QUEUE_REMOVE(q);\n\n    w = container_of(q, struct uv__work, wq);\n    err = (w-&gt;work == uv__cancelled) ? UV_ECANCELED : 0;\n    w-&gt;done(w, err);\n  }\n}\n</code></pre><p>uv__work_done很简单，获取loop中的wq队列，获取队列中的每个任务并调用done函数。</p>\n<h1>总结</h1>\n<p>本文首先介绍了生产者消费者模型，然后通过任务队列、提交任务、消费任务、回调处理讲解了Libuv线程池，为下一篇讲解Libuv文件处理做铺垫，如果你对Libuv系列感兴趣的话，欢迎关注我们。</p>\n<p><img src=\"//static.cnodejs.org/FlYENLtKY8oHA7-ew56dDTuVarvD\" alt=\"WechatIMG355.jpeg\"></p>\n</div>","title":"Libuv学习——线程池","last_reply_at":"2019-11-10T17:08:55.705Z","good":false,"top":false,"reply_count":0,"visit_count":261,"create_at":"2019-11-10T17:08:55.705Z","author":{"loginname":"wanglei20116527","avatar_url":"https://avatars2.githubusercontent.com/u/5023412?v=4&s=120"}},{"id":"5dc8417c865a9844a30210b9","author_id":"590d49aeba8670562a40ed64","tab":"share","content":"<div class=\"markdown-text\"><p>本文是Libuv学习系列的第二篇，主要讲线程基础，结合上篇《Libuv学习——队列》，为下篇学习libuv线程池打下基础，如果您对此有兴趣的话，欢迎关注我们。</p>\n<h1>前言</h1>\n<p><img src=\"//static.cnodejs.org/Fs3XJSgMznkpxd1Sa78Z6d42WR4J\" alt=\"2.png\"></p>\n<blockquote>\n<p>原文：Unlike network I/O, there are no platform-specific file I/O primitives libuv could rely on, so the current approach is to run blocking file I/O operations in a thread pool。</p>\n</blockquote>\n<blockquote>\n<p>翻译：不像网络IO，libuv没有特定平台的异步IO原语可以依赖，所以当前是在线程池中执行阻塞（同步）IO来实现异步的。</p>\n</blockquote>\n<p>根据libuv官网对其架构的介绍，我们可以知道它并不是单线程的，它有一个线程池，用来处理文件IO、DSN查询等操作。在介绍线程池之前，先通过POSIX Threads介绍一下线程的基本操作，为下一篇文章介绍线程池打下基础。如果您对libuv的整体架构感兴趣，可以访问下面链接<a href=\"http://docs.libuv.org/en/v1.x/design.html\">链接</a>了解，当然以后我也会写文章介绍的。</p>\n<h1>POSIX Threads</h1>\n<p>相信大家对线程的概念都有或多或少的了解，这里就不介绍了，下面将直接通过API和demo来学习。由于不同平台线程的规范和原语不一样，而我对Linux比较熟悉，所以接下来将通过Linux中的POSIX Threads来讲解。libuv线程池主要涉及到线程创建、互斥锁和条件变量3个东西，下面将分别介绍它们。</p>\n<h2>线程创建</h2>\n<p>让我们首先了解一下如何创建线程，代码和输出如下：</p>\n<pre class=\"prettyprint language-c\"><code>#include &lt;stdio.h&gt;\n#include &lt;pthread.h&gt;\n\n#define NUM_THREADS 5\n\nint sum = 0;\n\nvoid * thread_task(void * args) {\n    int max = (int)args;\n\n    for (int i = 0; i &lt;= max; ++i) {\n        sum += i;\n    }\n    printf(&quot;sum: %i\\n&quot;, sum);\n    pthread_exit(NULL);\n}\n\nint main() {\n    pthread_t threads[NUM_THREADS];\n    \n    pthread_attr_t attr;\n    pthread_attr_init(&amp;attr);\n    pthread_attr_setstacksize(&amp;attr, 8192);\n    \n    for (int i = 0; i &lt; NUM_THREADS; ++i) {\n        pthread_t thread = threads[i];\n        int result = pthread_create(&amp;thread, &amp;attr, thread_task, (void *)10);\n        if (result) {\n            printf(&quot;线程创建失败 errCode：%i&quot;, result);\n            return -1;\n        }\n    }\n    pthread_attr_destroy(&amp;attr);\n    \n    for (int i = 0; i &lt; NUM_THREADS; ++i) {\n        pthread_t thread = threads[i];\n        int result = pthread_join(thread, NULL);\n        if (result == 3) {\n            printf(&quot;线程%i已经结束了\\n&quot;, i);\n            continue;\n        }\n    }\n    \n    printf(&quot;main函数运行结束, sum: %i\\n&quot;, sum);\n    return 0;\n}\n</code></pre><p><img src=\"//static.cnodejs.org/FnCbMHPk8df1HEifob6bqSdHO-2c\" alt=\"3.jpg\"></p>\n<p>上面代码很简单，创建5个线程，每个线程将传入数据作为最大值max，然后从0，1，2，3，…，max加到sum上。接下来粗略讲解一下每行代码的含义：</p>\n<pre class=\"prettyprint language-c\"><code>#include &lt;stdio.h&gt;\n#include &lt;pthread.h&gt;\n\n#define NUM_THREADS 5\n\nint sum = 0;\n</code></pre><p>前4行代码引入了stdio.h、pthread.h两个头文件，函数printf在stdio.h中定义，线程相关的api在pthread.h中定义；定义了一个常量NUM_THREADS和一个变量sum，常量NUM_THREADS表示要创建的线程数，变量sum用来计算总和。</p>\n<pre class=\"prettyprint language-c\"><code>void * thread_task(void * args) {\n    int max = (int)args;\n\n    for (int i = 0; i &lt;= max; ++i) {\n        sum += i;\n    }\n    printf(&quot;sum: %i\\n&quot;, sum);\n    pthread_exit(NULL);\n}\n</code></pre><p>接下来定义了一个函数thread_task，该函数会被每个线程执行。函数很简单，将输入的int参数作为max，将0，1，2，3，…，max依次加到sum上，并将当前sum输出到控制台。最后执行pthread_exist结束线程。\n最后让我们看下main函数里面的内容，</p>\n<pre class=\"prettyprint language-c\"><code>int main() {\n    pthread_t threads[NUM_THREADS];\n    \n    pthread_attr_t attr;\n    pthread_attr_init(&amp;attr);\n    pthread_attr_setstacksize(&amp;attr, 8192);\n    \n    for (int i = 0; i &lt; NUM_THREADS; ++i) {\n        pthread_t thread = threads[i];\n        int result = pthread_create(&amp;thread, &amp;attr, thread_task, (void *)10);\n        if (result) {\n            printf(&quot;线程创建失败 errCode：%i&quot;, result);\n            return -1;\n        }\n    }\n    pthread_attr_destroy(&amp;attr);\n    \n    for (int i = 0; i &lt; NUM_THREADS; ++i) {\n        pthread_t thread = threads[i];\n        int result = pthread_join(thread, NULL);\n        if (result == 3) {\n            printf(&quot;线程%i已经结束了\\n&quot;, i);\n        }\n    }\n    \n    printf(&quot;main func end\\n&quot;);\n    return 0;\n}\n</code></pre><p>先定义了5个代表线程的数组threads；接着定义线程属性变量attr，将线程的栈设为8192个字节；之后通过pthread_create创建线程，每个线程将会执行thread_task函数，并通过第3个参数将10传递给thread_task；最后通过pthread_join告诉main函数等到所有线程执行完之后再继续执行。</p>\n<h2>互斥锁</h2>\n<p>如果足够仔细，相信你可能已经发现上面的输出不符合预期，按理说应该输出275才对，为啥只输出了249呢？</p>\n<p><img src=\"//static.cnodejs.org/FpJowVRtJZ8DXLnZs3BneHn3h5OP\" alt=\"4.png\"></p>\n<p>我们再运行一下程序看看，结果又正常了。</p>\n<p><img src=\"//static.cnodejs.org/FixdQYQRqJeD4O3hxO_N6MZrOv-c\" alt=\"5.png\"></p>\n<p>让我们简单通过2个线程来分析一下，假设此刻sum值为120，线程1中i循环到3，线程2循环到6，下表展示了导致sum错误的可能情况：</p>\n<p><img src=\"//static.cnodejs.org/FqzH81zLq298cVC29LYI9vUQ6Vv4\" alt=\"6.jpeg\"></p>\n<p>通过上表可以发现之所以出现问题是因为将i加到sum这个操作不是原子的，如果从读取sum、将i加到sum整个过程变成原子操作，就不会有问题了。解决该问题的常用方法之一就是互斥锁，让我们简单修改一下代码：</p>\n<pre class=\"prettyprint language-c\"><code>...\npthread_mutex_t mutex;\n\nvoid * thread_task(void * args) {\n    int max = (int)args;\n\n    for (int i = 0; i &lt;= max; ++i) {\n        pthread_mutex_lock(&amp;mutex);\n        sum += i;\n        pthread_mutex_unlock(&amp;mutex);\n    }\n    printf(&quot;sum: %i\\n&quot;, sum);\n    pthread_exit(NULL);\n}\n\nint main() {\n    pthread_mutex_init(&amp;mutex, NULL);\n    ...\n    pthread_mutex_destroy(&amp;mutex);\n}\n</code></pre><p>从代码的角度来看，修改后的代码增加了一个全局互斥锁mutex，并在main函数初始化。在sum += i；前面加了一句代码pthread_mutex_lock(&amp;mutex)，它告诉线程尝试获取锁，获取失败就挂起，等待其他线程释放锁；获取成功就继续执行代码，并通过pthread_mutex_unlock(&amp;mutex)将获取的锁给释放掉。</p>\n<h2>条件变量</h2>\n<p>互斥锁只解决了多个线程修改共享变量的问题，对于下面场景它是无法办法解决的。一个线程需要满足一个条件才能执行下去，而这个条件由另一个线程满足的。比如现在有一个变量i和2个线程，当i为0时第一个线程输出一段内容，并将i变成1；当i为1时，第二个线程输出一段内容，并将i变成0；两个线程依次交替执行。对于这个问题，我们可以通过条件变量来实现。下面是实现的代码和输出。</p>\n<pre class=\"prettyprint language-c\"><code>#include &lt;stdio.h&gt;\n#include &lt;pthread.h&gt;\n#include &lt;unistd.h&gt;\n\nint i = 0;\n\npthread_mutex_t mutex;\npthread_cond_t cond0;\npthread_cond_t cond1;\n\nvoid * thread_task0(void * args) {\n    while(1) {\n        pthread_mutex_lock(&amp;mutex);\n        while (i != 0) {\n            pthread_cond_wait(&amp;cond0, &amp;mutex);\n        }\n        sleep(1);\n\n        printf(&quot;**************thread_task0 i: %i\\n&quot;, i);\n        i = 1;\n        pthread_mutex_unlock(&amp;mutex);\n        pthread_cond_signal(&amp;cond1);\n    }\n}\n\nvoid * thread_task1(void * args) {\n    while(1) {\n        pthread_mutex_lock(&amp;mutex);\n        while (i != 1) {\n            pthread_cond_wait(&amp;cond1, &amp;mutex);\n\n        }\n        sleep(1);\n        printf(&quot;################thread_task1 i: %i\\n&quot;, i);\n        i = 0;\n\n        pthread_mutex_unlock(&amp;mutex);\n        pthread_cond_signal(&amp;cond0);\n               \n    }\n}\n\nint main() {\n    pthread_t thread0;\n    pthread_t thread1;\n    \n    pthread_mutex_init(&amp;mutex, NULL);\n    pthread_cond_init(&amp;cond0, NULL);\n    pthread_cond_init(&amp;cond1, NULL);\n    \n    pthread_create(&amp;thread0, NULL, thread_task0, NULL);\n    pthread_create(&amp;thread1, NULL, thread_task1, NULL);\n    \n    pthread_join(thread0, NULL);\n    pthread_join(thread1, NULL);\n    \n    pthread_mutex_destroy(&amp;mutex);\n    pthread_cond_destroy(&amp;cond0);\n    pthread_cond_destroy(&amp;cond1);\n    return 0;\n}\n</code></pre><p><img src=\"//static.cnodejs.org/FnEZeakifY-C2w-SRaTnNDFtUqYD\" alt=\"7.png\"></p>\n<p>让我们简单分析一下代码吧，前3行引入了3个头文件，前2个已经介绍过了，第3个头文件中有sleep函数的定义，后面会用到。</p>\n<pre class=\"prettyprint language-c\"><code>#include &lt;stdio.h&gt;\n#include &lt;pthread.h&gt;\n#include &lt;unistd.h&gt;\n</code></pre><p>随后定义了变量i，互斥锁mutex和2个条件变量cond0、cond1，这里需要注意一下条件变量是需要和互斥锁一起使用的。</p>\n<pre class=\"prettyprint language-c\"><code>int i = 0;\n\npthread_mutex_t mutex;\npthread_cond_t cond0;\npthread_cond_t cond1;\n</code></pre><p>紧接着我们定义了2个函数，分别由2个线程执行，由于这两个函数文字解释比较麻烦，下面通过表格来表示两个线程的执行过程。这里需要注意的是，pthread_cond_wait会放弃当前线程获得的锁，并进入挂起状态。当其他线程通过pthread_cond_signal通知该线程时，该线程会被唤起，重新获得锁。</p>\n<pre class=\"prettyprint language-c\"><code>void * thread_task0(void * args) {\n    while(1) {\n        pthread_mutex_lock(&amp;mutex);\n        while (i != 0) {\n            pthread_cond_wait(&amp;cond0, &amp;mutex);\n        }\n        sleep(1);\n\n        printf(&quot;**************thread_task0 i: %i\\n&quot;, i);\n        i = 1;\n        pthread_mutex_unlock(&amp;mutex);\n        pthread_cond_signal(&amp;cond1);\n    }\n}\n\nvoid * thread_task1(void * args) {\n    while(1) {\n        pthread_mutex_lock(&amp;mutex);\n        while (i != 1) {\n            pthread_cond_wait(&amp;cond1, &amp;mutex);\n\n        }\n        sleep(1);\n        printf(&quot;################thread_task1 i: %i\\n&quot;, i);\n        i = 0;\n        \n        pthread_mutex_unlock(&amp;mutex);\n        pthread_cond_signal(&amp;cond0);\n    }\n}\n</code></pre><p><img src=\"//static.cnodejs.org/Fk0aHSE8mQaTVIQRgockvmVhjPhx\" alt=\"8.png\"></p>\n<p>main函数只是用来启动上面介绍的两个线程，所以这里就不解释了。</p>\n<h1>Libuv的线程</h1>\n<p>上面介绍了POSIX Threads，接下来让我们粗略的看下libuv的线程吧，libuv官网也给出了对应的API文档<a href=\"http://docs.libuv.org/en/v1.x/threading.html\">链接</a>，有兴趣的同学可以看下.\n通过翻看源码，我们可以在src/unix/thread.c和src/win/thread.c文件下看到libuv线程的实现，很简单，就是对各个平台原有线程API进行包装，使得API统一化，下面通过src/unix/thread.c稍稍看下它的实现吧。</p>\n<h2>线程创建API</h2>\n<pre class=\"prettyprint language-c\"><code>typedef pthread_t uv_thread_t;\n\nint uv_thread_create_ex(uv_thread_t* tid,\n                        const uv_thread_options_t* params,\n                        void (*entry)(void *arg),\n                        void *arg) {\n  int err;\n  pthread_attr_t* attr;\n  pthread_attr_t attr_storage;\n  size_t pagesize;\n  size_t stack_size;\n\n  &#x2F;* Used to squelch a -Wcast-function-type warning. *&#x2F;\n  union {\n    void (*in)(void*);\n    void* (*out)(void*);\n  } f;\n\n  stack_size =\n      params-&gt;flags &amp; UV_THREAD_HAS_STACK_SIZE ? params-&gt;stack_size : 0;\n\n  attr = NULL;\n  if (stack_size == 0) {\n    stack_size = thread_stack_size();\n  } else {\n    pagesize = (size_t)getpagesize();\n    &#x2F;* Round up to the nearest page boundary. *&#x2F;\n    stack_size = (stack_size + pagesize - 1) &amp;~ (pagesize - 1);\n#ifdef PTHREAD_STACK_MIN\n    if (stack_size &lt; PTHREAD_STACK_MIN)\n      stack_size = PTHREAD_STACK_MIN;\n#endif\n  }\n\n  if (stack_size &gt; 0) {\n    attr = &amp;attr_storage;\n\n    if (pthread_attr_init(attr))\n      abort();\n\n    if (pthread_attr_setstacksize(attr, stack_size))\n      abort();\n  }\n\n  f.in = entry;\n  err = pthread_create(tid, attr, f.out, arg);\n\n  if (attr != NULL)\n    pthread_attr_destroy(attr);\n\n  return UV__ERR(err);\n}\n</code></pre><p>可以看到创建线程的方法和我们在POSIX Threads中介绍的差不多，都是通过pthread_create来创建，只不过通过pthread_attr_t设置了一些线程属性罢了，比如线程堆栈的大小。</p>\n<h2>互斥量API</h2>\n<pre class=\"prettyprint language-c\"><code>typedef pthread_mutex_t uv_mutex_t;\n\nint uv_mutex_init(uv_mutex_t* mutex) {\n#if defined(NDEBUG) || !defined(PTHREAD_MUTEX_ERRORCHECK)\n  return UV__ERR(pthread_mutex_init(mutex, NULL));\n#else\n  pthread_mutexattr_t attr;\n  int err;\n\n  if (pthread_mutexattr_init(&amp;attr))\n    abort();\n\n  if (pthread_mutexattr_settype(&amp;attr, PTHREAD_MUTEX_ERRORCHECK))\n    abort();\n\n  err = pthread_mutex_init(mutex, &amp;attr);\n\n  if (pthread_mutexattr_destroy(&amp;attr))\n    abort();\n\n  return UV__ERR(err);\n#endif\n}\n\nvoid uv_mutex_lock(uv_mutex_t* mutex) {\n  if (pthread_mutex_lock(mutex))\n    abort();\n}\n\nvoid uv_mutex_unlock(uv_mutex_t* mutex) {\n  if (pthread_mutex_unlock(mutex))\n    abort();\n}\n</code></pre><p>互斥锁的API也和我们POSIX Threads里介绍的差不多。</p>\n<h1>总结</h1>\n<p>本文初步通过线程创建、互斥锁和条件变量介绍了POSIX Threads以及libuv本身的线程API，这些是libuv实现线程池的核心，结合上篇《Libuv学习——队列》，我们已经为下篇libuv线程池打好了基础，所以您有兴趣的话，可以关注我们微信公众号《牛技》，这样您将会在第一时间看到我们的文章。</p>\n<p><img src=\"//static.cnodejs.org/FlYENLtKY8oHA7-ew56dDTuVarvD\" alt=\"WechatIMG355.jpeg\"></p>\n</div>","title":"Libuv学习——线程基础","last_reply_at":"2019-11-10T16:57:32.769Z","good":false,"top":false,"reply_count":0,"visit_count":217,"create_at":"2019-11-10T16:57:32.769Z","author":{"loginname":"wanglei20116527","avatar_url":"https://avatars2.githubusercontent.com/u/5023412?v=4&s=120"}},{"id":"58c94ea659017af119c1d31b","author_id":"54fed7b5c1749396754897ba","tab":"share","content":"<div class=\"markdown-text\"><p>随着google translate api 的收费,有翻译需求的小伙伴都在与 google 斗智斗勇。</p>\n<p>网上流行一个翻译api，不需要获取google token，以get的方式调用。但大家都知道，get最大请求只有2048字符，远远不能满足小伙伴们的需求。那还有什么其他办法吗？难道真要去付费，楼主表示心在滴血！</p>\n<p>经过多次尝试，摸清了 google 翻译的规则，找出了token的计算方法。基于token能通过post方式调用翻译接口了，另外还基于爬虫抓取实现了网页翻译，解决了大多数的翻译需求。调用方法也超简单，不说了直接上代码。。。</p>\n<pre class=\"prettyprint language-javascript\"><code>\n  const translate = require(&#x27;translate-api&#x27;);\n\n  let transUrl = &#x27;https:&#x2F;&#x2F;nodejs.org&#x2F;en&#x2F;&#x27;;\n  translate.getPage(transUrl).then(function(htmlStr){\n    console.log(htmlStr.length)\n  });\n\n  let transText = &#x27;hello world!&#x27;;\n  translate.getText(transText,{to: &#x27;zh-CN&#x27;}).then(function(text){\n    console.log(text)\n  });\n\n\n</code></pre><p>示例项目： <a href=\"https://github.com/yixianle/google-translate\">https://github.com/yixianle/google-translate</a>\n在线演示： <a href=\"http://translate.hotcn.top/\">http://translate.hotcn.top/</a></p>\n</div>","title":"给大家分享一个免费的谷歌翻译api","last_reply_at":"2019-11-10T03:04:35.154Z","good":false,"top":false,"reply_count":14,"visit_count":45871,"create_at":"2017-03-15T14:24:38.715Z","author":{"loginname":"yixianle","avatar_url":"https://avatars0.githubusercontent.com/u/11406512?v=4&s=120"}},{"id":"5dc6d6bd865a9844a3020ecd","author_id":"58898ca65d4612c33919ea01","tab":"share","content":"<div class=\"markdown-text\"><p><strong>体验地址：</strong> <a href=\"https://phone.blackmatch.cn\">https://phone.blackmatch.cn</a></p>\n<p>喜欢简简单单的风格，麻雀虽小，五脏俱全（其实是不懂前端，哈哈）。纯属一时兴起，练手项目，来自后端仔的倔强～～服务器是薅羊毛来的，配置简陋，勿压哈。</p>\n<p><img src=\"//static.cnodejs.org/FjxFoVDwNfqtHZ7oUDEpqC44GLsC\" alt=\"1573312356216.jpg\"></p>\n<p><img src=\"//static.cnodejs.org/FjYwRuU7uaHNmJKKCh2TwwIcWlSd\" alt=\"1573312327654.jpg\"></p>\n<h2>技术栈</h2>\n<h3>前端：React + antd</h3>\n<h3>后端：egg + mysql + sequelize + 七牛云 + Nginx + let’s encrypt</h3>\n</div>","title":"写了个手机号码评论的东西","last_reply_at":"2019-11-10T02:45:51.502Z","good":false,"top":false,"reply_count":1,"visit_count":371,"create_at":"2019-11-09T15:09:49.039Z","author":{"loginname":"blackmatch","avatar_url":"https://avatars3.githubusercontent.com/u/12443954?v=4&s=120"}},{"id":"5dc62c8cece3813ad9ba7479","author_id":"5dc62b27865a9844a3020d6c","tab":"ask","content":"<div class=\"markdown-text\"><p>获取到文件的路径了 怎么才能判断该文件是否为隐藏文件啊</p>\n</div>","title":"nodejs根据文件路径如何判断是否为隐藏文件","last_reply_at":"2019-11-09T09:04:33.558Z","good":false,"top":false,"reply_count":4,"visit_count":354,"create_at":"2019-11-09T03:03:40.182Z","author":{"loginname":"chenyouf1996","avatar_url":"https://avatars1.githubusercontent.com/u/51696131?v=4&s=120"}},{"id":"5dc5004aece3813ad9ba713a","author_id":"558a0ac301d3ce0d73d69179","tab":"share","content":"<div class=\"markdown-text\"><p><strong>欢迎Star，欢迎Pr。不喜勿喷!</strong></p>\n<p>项目地址: <a href=\"https://github.com/thinkkoa/koatty\">https://github.com/thinkkoa/koatty</a></p>\n<hr>\n<h1>koatty</h1>\n<p>Koa2 + Typescript = koatty.</p>\n<p>Use Typescript’s decorator implement auto injection just like SpringBoot.</p>\n<p><a href=\"https://www.npmjs.com/package/koatty\"><img src=\"https://img.shields.io/npm/v/koatty.svg?style=flat-square\" alt=\"Version npm\"></a><a href=\"https://npmcharts.com/compare/koatty?minimal=true\"><img src=\"https://img.shields.io/npm/dm/koatty.svg?style=flat-square\" alt=\"npm Downloads\"></a></p>\n<h2>Installation</h2>\n<pre class=\"prettyprint language-shell\"><code>npm i -g koatty_cli\n</code></pre><h2>Quick Start</h2>\n<p>Check out the <a href=\"https://github.com/thinkkoa/koatty_demo/\">quick start example</a>.</p>\n<h2>Usage</h2>\n<h3>1.Create Project</h3>\n<pre class=\"prettyprint language-shell\"><code>koatty new projectName\n\ncd .&#x2F;projectName\n\nyarn install\n\nnpm start\n</code></pre><h3>2.Create a Controller</h3>\n<pre class=\"prettyprint language-shell\"><code>koatty controller test\n\n</code></pre><h3>3.Create a Service</h3>\n<pre class=\"prettyprint language-shell\"><code>koatty service test\n\n</code></pre><h3>3.Create a Middleware</h3>\n<pre class=\"prettyprint language-shell\"><code>koatty middleware test\n\n</code></pre><h3>4.Create a Model</h3>\n<p>支持 <a href=\"https://github.com/thinkkoa/thinkorm\">thinkorm</a>以及 <a href=\"https://github.com/typeorm/typeorm\">typeorm</a>。其他的ORM自行扩展即可</p>\n<pre class=\"prettyprint language-shell\"><code>&#x2F;&#x2F;thinkorm\nkoatty middleware test\n\n&#x2F;&#x2F;typeorm\nkoatty middleware -o typeorm test\n\n</code></pre><h3>5.Define TestController</h3>\n<pre class=\"prettyprint language-javascript\"><code>import { Controller, BaseController, Autowired, GetMaping, RequestBody, PathVariable, PostMaping, BaseApp, RequestMapping, RequestMethod } from &quot;koatty&quot;;\nimport { TestService } from &quot;..&#x2F;service&#x2F;TestService&quot;;\nimport { App } from &quot;..&#x2F;App&quot;;\n\n@Controller()\nexport class IndexController extends BaseController {\n    app: App;\n\n    @Autowired()\n    private testService: TestService;\n\n    init() {\n        &#x2F;&#x2F;...\n        this.app.cache = {};\n        console.log(&#x27;IndexController.init()&#x27;, this.app.cache);\n    }\n\n    @RequestMapping(&quot;&#x2F;&quot;, RequestMethod.ALL)\n    async default(@PathVariable(&quot;test&quot;) test: string) {\n        const info = await this.testService.sayHello();\n        return this.ok(test, info);\n    }\n\n    @PostMaping(&quot;&#x2F;test&quot;)\n    test(@RequestBody() body: any) {\n        &#x2F;&#x2F; return this.default(&#x27;aaa&#x27;);\n        return this.ok(&quot;test&quot;, body);\n    }\n}\n</code></pre><h2>How to debug</h2>\n<p>if you use vscode , edit the <code>.vscode/launch.json</code> , like this:</p>\n<pre class=\"prettyprint\"><code>{\n    &quot;version&quot;: &quot;0.2.0&quot;,\n    &quot;configurations&quot;: [\n        {\n            &quot;type&quot;: &quot;node&quot;,\n            &quot;request&quot;: &quot;launch&quot;,\n            &quot;name&quot;: &quot;TS Program&quot;,\n            &quot;args&quot;: [\n                &quot;${workspaceRoot}&#x2F;src&#x2F;App.ts&quot; \n            ],\n            &quot;runtimeArgs&quot;: [\n                &quot;--nolazy&quot;,\n                &quot;-r&quot;,\n                &quot;ts-node&#x2F;register&quot;\n            ],\n            &quot;sourceMaps&quot;: true,\n            &quot;cwd&quot;: &quot;${workspaceRoot}&quot;,\n            &quot;protocol&quot;: &quot;inspector&quot;,\n            &quot;internalConsoleOptions&quot;: &quot;neverOpen&quot;\n        }\n    ]\n}\n</code></pre><p>Select <code>TS Program</code> to debug run. Try to call <code>http://localhost:3000/</code> .</p>\n</div>","title":"给团队推广TypeScript，顺手造了个轮子.欢迎品尝O(∩_∩)O","last_reply_at":"2019-11-09T08:06:15.384Z","good":false,"top":false,"reply_count":4,"visit_count":842,"create_at":"2019-11-08T05:42:34.343Z","author":{"loginname":"richenlin","avatar_url":"https://avatars3.githubusercontent.com/u/6151439?v=4&s=120"}},{"id":"5dc627a2865a9844a3020d66","author_id":"5d92253c865a9844a3019496","tab":"share","content":"<div class=\"markdown-text\"><h2>介绍</h2>\n<p>图像可以用多种不同的类型数据表示，本文将它们归纳为 5 种类型：DOM，URL，<code>File</code>，<code>ImageData</code> 和 Buffer。</p>\n<h2>图像的数据类型</h2>\n<h3>DOM</h3>\n<h4><code>&lt;img&gt;</code></h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img\" title=\"`&lt;img&gt;`\"><code>&lt; img &gt;</code></a> 元素从 URL（Data URL，HTTP URL 或 Object URL）加载图像。</p>\n<h4><code>&lt;canvas&gt;</code></h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas\" title=\"`&lt;canvas&gt;`\"><code>&lt; canvas &gt;</code></a> 元素通过 canvas API <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage\" title=\"`drawImage`\"><code>drawImage</code></a> 来获取 <code>&lt;img&gt;</code> 元素上的图像数据。</p>\n<h3>URL</h3>\n<h4>Data URL</h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URIs\" title=\"Data URL\">Data URL</a> 带有 base64 编码的图像数据。可以从 Data URL 数据中解码出图像的二进制数据。Data URL 数据的大小比原始的二进制数据大一些。</p>\n<h4>HTTP URL</h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_URL\" title=\"HTTP URL\">HTTP URL</a> 代表存储在服务器上的图像。HTTP URL 用于从服务器获取图像数据。</p>\n<h4>Object URL</h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL\" title=\"Object URL\">Object URL</a> 用来代表存储在浏览器内存中的 <code>File</code> 或 <code>Blob</code> 对象。Object URL 可以由<code>createObjectURL</code> API 来创建，并由 <code>revokeObjectURL</code> API 释放。</p>\n<h3>文件</h3>\n<h4><code>Blob</code></h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/Blob\" title=\"`Blob`\"><code>Blob</code></a> 是带有二进制数据的类文件对象。它包含一个只读的 <code>size</code> 属性和一个只读的 <code>type</code> 属性。你可以通过 <code>slice</code>，<code>stream</code>，<code>text</code> 等方法来读取二进制数据。</p>\n<h4><code>File</code></h4>\n<p>一个 <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/File\" title=\"`File`\"><code>File</code></a> 对象是一个特殊的 <code>Blob</code> 对象。除了 <code>Blob</code> 的属性和方法外，<code>File</code> 对象还包含 <code>lastModified</code>，<code>name</code> 等属性。</p>\n<h3><code>ImageData</code></h3>\n<p>一个 <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/ImageData\" title=\"`ImageData`\"><code>ImageData</code></a> 对象是一个 JavaScript 对象，包含 <code>width</code>，<code>height</code> 和 <code>data</code> 属性，分别表示图像宽度，高度和像素数据。 <code>data</code> 属性是一个一维数组，包含 <code>R，G，B，A，R，G，B，A</code> 这样格式的数据。每个 <code>R，G，B，A</code> 代表一个像素。可以通过 <code>&lt;canvas&gt;</code> API <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createImageData\" title=\"`createImageData`\"><code>createImageData</code></a> 或 <code>ImageData</code> 构造函数来创建 <code>ImageData</code>。</p>\n<h3>Buffer</h3>\n<h4><code>ArrayBuffer</code></h4>\n<p><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer\" title=\"`ArrayBuffer`\"><code>ArrayBuffer</code></a> 是在浏览器中唯一一种访问二进制数据的方法。<code>ArrayBuffer</code> 代表图像的原始二进制数据缓冲区。我们无法读取和写入 <code>ArrayBuffer</code> ，只能将 <code>ArrayBuffer</code> 转换为 <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/DataView\" title=\"`DataView`\"><code>DataView</code></a> 或 <a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray\" title=\"TypedArray\">TypedArray</a> 来读取和写入二进制数据。</p>\n<h4><code>Buffer</code></h4>\n<p><a href=\"https://nodejs.org/api/buffer.html\" title=\"`Buffer`\"><code>Buffer</code></a> 是 Node.js 中特殊的一种 <code>Uint8Array</code>，Node.js 对其进行了一些优化。</p>\n<h2>在 <code>ArrayBuffer</code>，<code>DataView</code>，TypedArray 和 <code>Buffer</code> 之间转换</h2>\n<p><img src=\"https://vivaxyblog.github.io/assets/2019-11-06-comprehensive-image-processing-on-browsers/ArrayBuffer-TypedArray-Buffer-DataView.svg\" alt=\"如何在 ，，TypedArray 和  之间转换\"></p>\n<h2>在 DOM，URL，Blob(File)，<code>ImageData</code> 和 Buffer 之间转换</h2>\n<p><img src=\"https://vivaxyblog.github.io/assets/2019-11-06-comprehensive-image-processing-on-browsers/DOM-URL-File-Data.svg\" alt=\"如何在 DOM，URL，Blob(File)， 和 Buffer 之间转换\"></p>\n<h2>参考资料</h2>\n<ul>\n<li><a href=\"https://github.com/WangYuLue/image-conversion\" title=\"WangYuLue/image-conversion\">WangYuLue/image-conversion</a></li>\n</ul>\n</div>","title":"浏览器图像转换手册","last_reply_at":"2019-11-09T02:42:42.587Z","good":false,"top":false,"reply_count":0,"visit_count":266,"create_at":"2019-11-09T02:42:42.587Z","author":{"loginname":"vivaxy","avatar_url":"https://avatars0.githubusercontent.com/u/4216856?v=4&s=120"}},{"id":"5dc51f07865a9844a3020b47","author_id":"5abb5da70b13e3ad6954cf13","tab":"ask","content":"<div class=\"markdown-text\"><p>已知node虚拟机内存缓存区大小为1.5G左右，直接使用zlib解压会造成程序崩溃</p>\n<p>有没有哪个库是边读边解压的？</p>\n<p>或者，能不能将压缩包拆成若干个小的压缩包再分别解压后合并？</p>\n</div>","title":"nodejs 如何解压10G左右的压缩包？","last_reply_at":"2019-11-09T01:50:26.941Z","good":false,"top":false,"reply_count":6,"visit_count":610,"create_at":"2019-11-08T07:53:43.860Z","author":{"loginname":"Flywor","avatar_url":"https://avatars2.githubusercontent.com/u/17354215?v=4&s=120"}},{"id":"5d90765fece3813ad9b9f867","author_id":"523f8d7a101e5745215a9cde","tab":"ask","content":"<div class=\"markdown-text\"><p>我的代码如下：</p>\n<p><img src=\"//static.cnodejs.org/Fsoh3X03yEFaXZWeHQwCMqW2Kym5\" alt=\"image.png\"></p>\n</div>","title":"项目代码时需要有回调，但ali-oss是用 await处理异步。引起我下载文件时，还没有完就是读文件了。","last_reply_at":"2019-11-08T13:55:45.815Z","good":false,"top":false,"reply_count":7,"visit_count":981,"create_at":"2019-09-29T09:16:15.415Z","author":{"loginname":"mrlong","avatar_url":"//gravatar.com/avatar/b2b4503d15a3a7f2b55196e3596c0f1d?size=48"}},{"id":"5dc53012865a9844a3020bcf","author_id":"5dc0f2e5865a9844a301ff20","tab":"share","content":"<div class=\"markdown-text\"><p>本文将介绍几种部署Goku API Gateway的方式，最快一分钟可使用上为网关，详情请看全文。</p>\n<h3>什么是Goku API Gateway？</h3>\n<p>Goku API Gateway （中文名：悟空 API 网关）是一个基于 Golang 开发的运行在企业系统服务边界上的微服务网关。当您构建网站、App、IOT甚至是开放API交易时，Goku API Gateway 能够帮你将内部系统中重复的组件抽取出来并放置在Goku上运行，如进行用户授权、访问控制、防火墙、数据转换等；并且Goku 提供服务编排的功能，让企业可以快速从各类服务上获取需要的数据，对业务实现快速响应。</p>\n<p>Goku API Gateway 的社区版本（CE）拥有完善的使用指南和二次开发指南，代码使用纯 Go 语言编写，拥有良好的性能和扩展性，并且内置的插件系统能够让企业针对自身业务进行定制开发。使用 Goku API Gateway 能让业务开发团队更加专注地实现业务。</p>\n<p>一套完整的 Goku API Gateway 由一个 <strong>控制台</strong> 和 <strong>若干个 网关节点</strong> 组成。节点通过IP地址注册在控制台中，控制台内的配置项会对所有节点生效。控制台用于配置网关的运行信息，例如访问策略、转发的API信息等等；节点用于实际的转发。当控制台上发布了最新的配置，所有节点都会主动更新自己的运行配置；在控制台发布的配置支持版本管理，可以手动发布和回滚。</p>\n<p>Goku API Gateway支持管理多个网关节点的集群，实现让用户访问不同的集群从而访问不同的服务地址的目的。</p>\n<p>下面是以两个DC（Data Center）为例的部署架构简图：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2Jsb2cuZW9saW5rZXIuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE5LzEwLyVFNiVBRCU4QyVFNSU4RCU4RSVFOSU4MyVBOCVFNyVCRCVCMiVFNiU5NiVCOSVFNiVBMSU4OCVFRiVCQyU4OCVFNyU5NCU5RiVFNCVCQSVBNyVFNyU4RSVBRiVFNSVBMiU4MyVFRiVCQyU4OS5wbmc?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\nGoku的网关节点除了能够直接获取控制台的配置外，也支持采用直接读取配置文件的方式来使用。配置文件可以通过EOLINKER 官方提供的线上控制台来导出，线上控制台可让用户方便修改配置，修改完后导出配置文件，放到节点的服务器上即可。</p>\n<p><strong>线上控制台地址：<a href=\"http://goku-console.eolinker.com\">http://goku-console.eolinker.com</a></strong></p>\n<p>使用线上控制台的话无法区分不同集群，如需使用更强功能建议还是部署自己的私有云控制台。</p>\n<h3>本文给出几种Goku API Gateway的部署方式：</h3>\n<p>一、节点docker+线上控制台</p>\n<p>二、节点docker+线上控制台Docker</p>\n<p>三、使用安装包直接安装</p>\n<p><strong>部署安装时需要用到的链接：</strong></p>\n<ul>\n<li>节点Docker：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node</a></li>\n<li>控制台Docker：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console</a></li>\n<li>线上控制台：<a href=\"http://goku-console.eolinker.com\">http://goku-console.eolinker.com</a></li>\n<li>安装包地址：<a href=\"https://github.com/eolinker/goku-api-gateway\">https://github.com/eolinker/goku-api-gateway</a></li>\n</ul>\n<h2>一、节点Docker+线上控制台</h2>\n<p>Goku API Gateway提供节点Docker和控制台Docker，最快的方式就是仅部署节点Docker，然后通过线上控制台来配置网关信息，配置完成后导出文件并上传到节点服务器，最后启动节点Docker。</p>\n<h4>使用步骤</h4>\n<p>1、配置完毕后，在线上控制台的首页导出配置：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS9SV3hxOEdVYzI3YmExNjBhM2NkYjYyMDBkYzYxYWVjMzk2N2IyZDA4NTkyM2Y1Yg?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\n2、将配置上传到服务器，启动docker：</p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p {转发端口号}:6689 \\\n-v {日志挂载地址}:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work \\\n-v {配置文件地址}:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work&#x2F;goku-node.json \\\neolinker&#x2F;goku-api-gateway-ce-node\n</code></pre><p>完整配置示例如下：</p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p 6689:6689 \\\n-v &#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work \\\n-v &#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;versionConfig_b14a1af4-b961-4dfb-8bfe-d4d157dd651e.json:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work&#x2F;goku-node.json \\\neolinker&#x2F;goku-api-gateway-ce-node\n</code></pre><p><strong>用户通过网关访问API地址为：{{节点IP:端口}}/{{转发URL}} 。</strong></p>\n<h2>二、节点Docker+控制台Docker</h2>\n<h4>（一）部署控制台</h4>\n<p><strong>1、 创建goku-ce专属网络</strong></p>\n<pre class=\"prettyprint language-clike\"><code>docker network create --driver bridge --subnet=172.18.12.0&#x2F;24 --gateway=172.18.12.1 goku-ce\n</code></pre><p><strong>2、运行控制台程序</strong></p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p {浏览器访问端口号}:7000 \\\n-v {sqlite数据库挂载地址}:&#x2F;app&#x2F;goku-ce&#x2F;console&#x2F;work \\\n-e GOKU_ADMIN_PASSWORD={控制台admin密码} \\\n--network=goku-ce \\\n--ip 172.18.12.2 \\\n--name goku-ce-console \\\neolinker&#x2F;goku-api-gateway-ce-console\n</code></pre><p>完整示例启动如下：</p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p 7000:7000 \\\n-v &#x2F;app&#x2F;goku-ce&#x2F;work:&#x2F;app&#x2F;goku-ce&#x2F;console&#x2F;work \\\n-e GOKU_ADMIN_PASSWORD=123456 \\\n--network=goku-ce \\\n--ip 172.18.12.2 \\\n--name goku-ce-console \\\neolinker&#x2F;goku-api-gateway-ce-console\n</code></pre><p><strong>3、 登录控制台</strong></p>\n<p>打开浏览器，输入 域名/IP+浏览器访问端口号，进入控制台页面，输入用户名（admin）及密码（启动设置的admin密码）进行登录:\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS9WRzRFZlp5ZmQzNjI4NDc1NTc5Y2NkYWUzMzg4MDI0MGMzOGQyNmYzODEyNjgzZA?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\"></p>\n<h4>（二）部署网关节点</h4>\n<p><strong>1、进入控制台，新建节点</strong></p>\n<p>节点IP需要和启动节点docker容器时绑定的IP 一致：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS82VTRyNG5NOTU1YzM4ZjRmODdlOWFlMzdjMDYxYWU5Nzc4YTY4MmUxMjA2OTM0Yw?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\n点击查看 <a href=\"https://help.eolinker.com/#/tutorial/?groupID=c-367&amp;productID=19\">新建节点详细教程</a></p>\n<p><strong>2、在控制台生成并发布配置</strong></p>\n<p>发布配置后，节点会读取控制台的最新配置来运行。</p>\n<p>点击查看 发<a href=\"https://help.eolinker.com/#/tutorial/?groupID=c-115&amp;productID=14\">布配置的教程链接</a></p>\n<p><strong>3、启动节点docker容器</strong></p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p {转发端口号}:6689 \\\n--network goku-ce \\\n--ip {节点IP} \\\n-v {日志挂载地址}:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work \\\n-e GOKU_ADMIN_ADDRESS={控制台IP}:7005 \\\neolinker&#x2F;goku-api-gateway-ce-node\n</code></pre><p>完整示例如下：</p>\n<pre class=\"prettyprint language-clike\"><code>docker run -dt -p 6689:6689 \\\n--network goku-ce \\\n--ip 172.18.12.3 \\\n-v &#x2F;app&#x2F;goku-ce&#x2F;work:&#x2F;app&#x2F;goku-ce&#x2F;node&#x2F;work \\\n-e GOKU_ADMIN_ADDRESS=172.18.12.2:7005 \\\neolinker&#x2F;goku-api-gateway-ce-node\n</code></pre><p><strong>4、查看节点运行状态</strong></p>\n<p>进入节点管理页面，若节点的状态显示为运行中，则节点正常启动：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS8xd3NDOExHZjVmOTgxN2Y5MDU3MjQ1OGYwNDc2YjYwMjJlMzdkMGVlZTE4ZDJlYg?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\"></p>\n<h2>三、通过安装包直接部署</h2>\n<p>安装准备：到Github上下载最新的Release包。</p>\n<p>项目地址：<a href=\"https://github.com/eolinker/goku-api-gateway\">https://github.com/eolinker/goku-api-gateway</a></p>\n<h4>（一）安装控制台</h4>\n<p><strong>1、安装命令:</strong></p>\n<p>mkdir -p {install dir}\nmv console-{version}.tar.gz {tmp}/\ncd {tmp}/\ntar -xzf console-{version}.tar.gz\ncd console-{version} &amp;&amp; ./install.sh {install dir}\ncd {install dir}</p>\n<p><strong>2、首次安装进入{install dir}/console/config文件夹，编辑配置文件内容，配置语法参照yaml。</strong></p>\n<p>goku.conf 如下：</p>\n<p>admin_bind: 绑定节点获取配置的地址，形如IP:Port，填写内网地址或本机地址\nlisten_port: 管理后台监听端口，可以开放给外网访问\ndb_type: 数据库类型,v3.1.0只支持 sqlite\ndb_path: sqlite db的文件路径</p>\n<p><strong>3、进入{install dir}/console文件夹，运行run.sh文件以启动控制台。</strong></p>\n<p><strong>首次运行：</strong></p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start {config file} 管理员账号 管理员密码\n</code></pre><p><strong>示例：</strong></p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start config&#x2F;goku.conf admin 123456\n</code></pre><p><strong>非首次运行：</strong></p>\n<pre class=\"prettyprint language-clike\"><code>.&#x2F;run.sh start|restart\n</code></pre><p><strong>4、在浏览器输入服务器IP+程序监听端口号，进入控制台页面</strong></p>\n<p>注：程序监听端口号为goku.conf的配置项listen_port的值</p>\n<h4>（二）、节点安装</h4>\n<p><strong>1、安装命令：</strong></p>\n<pre class=\"prettyprint language-clike\"><code>  mkdir -p {install dir}\n  mv goku-node-{version}.tar.gz {tmp}&#x2F;\n  cd {tmp}&#x2F;\n  tar -xzf goku-node-{version}.tar.gz\n  cd goku-node-{version} &amp;&amp; .&#x2F;install.sh {install dir}\n  cd {install dir}\n</code></pre><p><strong>2、在控制台新建节点</strong></p>\n<p>（1）登录控制台，一级菜单选择 <strong>网关节点</strong>，创建集群：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS82VzU5NWlJZjA0ODExOWJmN2Y2OWFiYTFhN2IzZmJiMGYxYmU4MzU1NjVlODVlMA?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\n（2）进入相应的集群，为不同集群 <strong>添加节点</strong>：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS9ybFdmMWRJODA2NDI3Y2M2NGM0YjczYWYzNGRiYTM1YTc1OGE0ZDlmMjFlODZmYw?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\n（3）点击 <strong>新增节点</strong> 按钮，填写节点信息后点击 <strong>确定</strong>：</p>\n<ul>\n<li>新建节点时的节点IP字段填写内网IP或本地IP</li>\n<li>若配置文件（goku.conf）中的admin_bind字段值IP部分为127.0.0.1或localhost，此处节点IP必须填写127.0.0.1</li>\n</ul>\n<p><strong>3、启动节点程序：</strong></p>\n<pre class=\"prettyprint language-clike\"><code> {install dir}&#x2F;run.sh {start|stop|reload|restart|force-reload} [admin url] [port]\n</code></pre><p><strong>此处的admin url值与配置文件（goku.conf）中的admin_bind字段值一致。</strong></p>\n<p><strong>示例：</strong></p>\n<pre class=\"prettyprint language-clike\"><code> {install dir}&#x2F;run.sh start 127.0.0.1:7005 7702\n</code></pre><p><strong>4、检查节点是否正常运行：</strong></p>\n<p>进入控制台的网关节点页面，若节点的状态显示为运行中，则节点正常启动：\n<img src=\"https://imgconvert.csdnimg.cn/aHR0cDovL2RhdGEuZW9saW5rZXIuY29tL2NvdXJzZS8xd3NDOExHZjVmOTgxN2Y5MDU3MjQ1OGYwNDc2YjYwMjJlMzdkMGVlZTE4ZDJlYg?x-oss-process=image/format,png\" alt=\"在这里插入图片描述\">\n最后，你也可以通过编译Github上Master分支的代码，编译最新的内容来安装，编译教程：<a href=\"https://help.eolinker.com/#/tutorial/?groupID=c-372&amp;productID=19\">https://help.eolinker.com/#/tutorial/?groupID=c-372&amp;productID=19</a></p>\n<h2>相关链接</h2>\n<p><strong>节点Docker</strong>：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-node</a></p>\n<p><strong>控制台Docker</strong>：<a href=\"https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console\">https://hub.docker.com/r/eolinker/goku-api-gateway-ce-console</a></p>\n<p><strong>新上控制台</strong>：<a href=\"http://goku-console.eolinker.com\">http://goku-console.eolinker.com</a></p>\n<p><strong>项目地址</strong>：<a href=\"https://github.com/eolinker/goku-api-gateway\">https://github.com/eolinker/goku-api-gateway</a></p>\n<p><strong>项目官网</strong>：<a href=\"https://www.eolinker.com\">https://www.eolinker.com</a></p>\n</div>","title":"几种部署Goku API Gateway的方式，最快一分钟可使用上网关","last_reply_at":"2019-11-08T09:06:26.486Z","good":false,"top":false,"reply_count":0,"visit_count":276,"create_at":"2019-11-08T09:06:26.486Z","author":{"loginname":"Tungevaag","avatar_url":"https://avatars2.githubusercontent.com/u/57382593?v=4&s=120"}},{"id":"5dc4e78fece3813ad9ba70dd","author_id":"5a72ca56ce45d440451465c6","tab":"ask","content":"<div class=\"markdown-text\"><p>如题！！大佬们，egg框架如何配置让某个文件夹或文件改变不自动重启。我看了一下egg框架的使用egg-watch实现的，但是没看到文档里面可以配置不监听某个文件夹。</p>\n</div>","title":"egg框架如何配置让某个文件夹的文件改变不自动重启","last_reply_at":"2019-11-08T04:00:47.965Z","good":false,"top":false,"reply_count":1,"visit_count":378,"create_at":"2019-11-08T03:57:03.263Z","author":{"loginname":"heguangda","avatar_url":"https://avatars1.githubusercontent.com/u/29668335?v=4&s=120"}},{"id":"5dbd4db0ece3813ad9ba5e28","author_id":"5cfdc81b95fcc914aa2673c8","tab":"share","content":"<div class=\"markdown-text\"><p>造了好多轮子，上次发的这篇文章得到了关注<a href=\"https://cnodejs.org/topic/5d82db5b95464514f7ed7f93\">我写了个系统，用来一键创建测试环境</a></p>\n<p>陆续更新了一些功能和优化了体验后，用起来也是丝般顺滑了，于是又开始造新轮子了</p>\n<p><a href=\"https://ops-coffee.cn/s/a3eJjVTtuUjzwyk21nTBqQ\">Django实现WebSSH操作物理机或虚拟机</a></p>\n<p><img src=\"https://blz.nosdn.127.net/sre/images/20191101.webssh.gif\" alt></p>\n<p>这个轮子<strong>有源码</strong>，你可以自己部署使用</p>\n</div>","title":"痴迷于造轮子的我又来了，这次写了个web系统，接管xshell，crt，实现在web端操作服务器，给源码","last_reply_at":"2019-11-08T01:59:34.544Z","good":false,"top":false,"reply_count":19,"visit_count":2865,"create_at":"2019-11-02T09:34:40.581Z","author":{"loginname":"ops-coffee","avatar_url":"https://avatars1.githubusercontent.com/u/42868360?v=4&s=120"}},{"id":"5d784a29a7474a231a58ad80","author_id":"5becd64fbe1b120abac597f4","tab":"share","content":"<div class=\"markdown-text\"><p><strong>Egg.js</strong>是《阿里旗下产品》基于Node.js 和 Koa的一个Nodejs的企业级应用开发框架，它可以帮助开发团队及开发人员降低开发和维护成本。它类似于 Ruby 的 Ruby On Rails、Python 的 Django、Php 的 Laravel，是一款值得深入研究的框架。</p>\n<p><strong>大地老师Egg.js仿小米商城项目已更新130讲</strong>，仿小米商城项目不是一个简单的管理系统，完全的仿小米商城项目实战，RBAC权限管理、商品管理、用户管理、导航管理、文章管理、订单管理、支付宝支付、微信支付…</p>\n<p><strong>完整目录访问：</strong> <a href=\"https://www.itying.com/goods-941.html\">https://www.itying.com/goods-941.html</a></p>\n<p><strong>eggjs 10讲免费入门教程访问：</strong> <a href=\"http://bbs.itying.com/topic/5bcd6da2dc95510a3063e10d\">http://bbs.itying.com/topic/5bcd6da2dc95510a3063e10d</a></p>\n<p><strong>Egg.js基础教程包括:</strong>  Egg.js的基本使用、Egg.js中的路由 、Egg.js中的控制器、Egg.js中的服务、Egg.js中的Model、Egg.js中的Config 以及插件、Egg.js中的view模块引擎、Egg.js中的中间件 、Egg.js扩展 Extend、Egg.js CSRF 的防范、Egg Cookie、Egg Session、Egg.js 定时任务、Mongodb4.x、Mongoose、Egg.js+Mongoose、Egg.js中Mysql的使用、Sequelize ORM框架的使用等…</p>\n<p><strong>Egg.js仿小米商城项目包括:</strong>  Egg.js基础的应用 、egg-view多模板引擎、Egg中Md5的使用、Egg生成验证码、数据库ER图、用户RBAC权限管理（不同角色用户登录后台显示不同菜单，涉及角色、权限、用户的增删改查以及关联）、公共的Ajax改变状态、Ajax双击更改数量、Ajax双击排序、图片上传、生成缩略图、wysiwyg-editor的使用、商品管理中动态生成商品属性表单、批量上传图片插件、redis 的使用、Egg.js发送短信、Pc端微信支付、Pc端支付宝支付、Elasticsearch全文搜索引擎、Socket.io机器人 、Socket.io多人聊天室、Socket.io群聊、前后端分离 RESTful API Api接口、购买域名 服务器、域名备案、nginx负载均衡、SSL证书Https配置等等…</p>\n<p><img src=\"//static.cnodejs.org/Fp8erhbPkGZrsYFsd-gxn2z4Gssb\" alt=\"1.png\"></p>\n<p><img src=\"//static.cnodejs.org/FhCAuCKVwbQnTCJWtkGKv6ZR8toN\" alt=\"2.png\"></p>\n<p><img src=\"//static.cnodejs.org/FpnFP2WY9YBnPygNGT7y977hnQ5O\" alt=\"3.png\"></p>\n<p><img src=\"//static.cnodejs.org/Fu_lhZW2WdVtE05rGRqtVWT42G9Q\" alt=\"4.png\"></p>\n<p><img src=\"//static.cnodejs.org/Fq7M2FL4nsEbXvp_xOmW2RFZZn1m\" alt=\"5.png\"></p>\n<p><img src=\"//static.cnodejs.org/Fk7rfDAEAhgo5IUiOH7phlC7MWpv\" alt=\"6.png\"></p>\n<p><img src=\"//static.cnodejs.org/FosTPjBgGJCq_UT87elcbwgUic6a\" alt=\"7.png\"></p>\n<p><img src=\"//static.cnodejs.org/FuKpyqW5SX_dzDkP_tUujD-OvkwJ\" alt=\"8.png\"></p>\n<p><img src=\"//static.cnodejs.org/FsphTYau58anhXz2x_SgCxbexsMT\" alt=\"9.png\"></p>\n<p><img src=\"//static.cnodejs.org/FofjI9KOV0_ZQwg-VzfYps51KsEk\" alt=\"10.png\"></p>\n</div>","title":"Egg.js仿小米商城项目已更新130讲-RBAC权限管理、Mongoose、Sequelize、Elasticsearch、Socket.io、redis、Graphql你想要的都有讲","last_reply_at":"2019-11-08T01:53:19.849Z","good":false,"top":false,"reply_count":17,"visit_count":9419,"create_at":"2019-09-11T01:13:13.414Z","author":{"loginname":"magege666","avatar_url":"https://avatars1.githubusercontent.com/u/45024820?v=4&s=120"}},{"id":"5dbffffdece3813ad9ba63cf","author_id":"57ce675e80ad316647adcc0b","tab":"share","content":"<div class=\"markdown-text\"><p>地址：<a href=\"http://vueos.jspapa.com/\">VueOS</a>  欢迎来体验</p>\n<p>使用Vue开发的WebOS，目前实现了基础的功能：桌面，锁屏，登陆，系统基础设置，记事本，打开外部应用(内嵌)等</p>\n<p>目前还只是初级阶段，能用的功能比较少，但是基本的架构已经完成，后面就是开发应用的问题，当然还没后端，数据都是存在本地的，想法是后面增加后端以支持数据持久化，而且后端打算用nodejs，或者大家有什么建议？</p>\n</div>","title":"使用Vue开发了一个WebOS","last_reply_at":"2019-11-08T01:40:52.335Z","good":false,"top":false,"reply_count":8,"visit_count":1623,"create_at":"2019-11-04T10:39:57.323Z","author":{"loginname":"biggerV","avatar_url":"https://avatars3.githubusercontent.com/u/21165821?v=4&s=120"}}]}