奇怪摸鱼站/2022-02-22T00:00:00+08:00Game/Web Developer灵活就业生活记录2022-02-22T00:00:00+08:002022-02-22T00:00:00+08:00魏远tag:None,2022-02-22:/posts/ling-huo-jiu-ye-sheng-huo-ji-lu.html<p>2022年2月21日傍晚,我正式成为一名灵活就业人员(指失业)。</p>
<p>直接原因是2月18日无预警开启的无限期每日无偿加班2小时活动。</p>
<p>持续记录,直到开始下一段旅程。</p>
<h3>DAY0 21日</h3>
<p>晚上,再次重写了周末整理的简历。</p>
<p>搞到很晚。</p>
<h3>DAY1 22日</h3>
<p>第一天,睡了一天,爽</p>
<p>摸了。</p>
<h3>DAY2 23日</h3>
<p>第二天,睡了半天,爽。</p>
<p>下午去做了核酸,又推进了一下SealDice,我打算赶紧把当前迭代结束,并开始下一步的学习计划。</p>
<p>完成了赋值语句。</p>
<h3>DAY3 24日</h3>
<p>第三天,睡了半天减2小时。</p>
<p>下午出门坐了一会,继续推进项目,完成了“跑团记录”这个功能。</p>
<p>肯德基疯狂星期四,但是感觉没有什么好吃的,而且人真多。</p>
<p>咖啡卡还挺实惠,味道还行。</p>
<h3>DAY4 25日</h3>
<p>Comming soon</p>Blender 添加相对于骨骼的位置约束2022-02-09T00:00:00+08:002022-02-09T00:00:00+08:00魏远tag:None,2022-02-09:/posts/blender-tian-jia-xiang-dui-yu-gu-ge-de-wei-zhi-yue-shu.html<p><img alt="img" src="static/20220209-相对于骨骼的位置约束.assets/8e8578e9-50c0-4d59-8c3c-b95dd7d273ee.jpg"></p>
<p><img alt="img" src="static/20220209-相对于骨骼的位置约束.assets/433f4415-7831-42c2-9a45-cba7cdb62e94.png"></p>我的第一张Blender作品2022-01-23T00:00:00+08:002022-01-23T00:00:00+08:00魏远tag:None,2022-01-23:/posts/wo-de-di-yi-zhang-blenderzuo-pin.html<p>设定大概就是废土中的一座移动工厂吧。</p>
<p>背景是近未来废土,小型核聚变核心得到广泛应用,材料学也得到了发展。</p>
<p>所以末世的人们捡辣鸡拼一拼,就造出了这种巨大的移动载具(长度大概在300米)。</p>
<p>感谢创意齿轮的Leo老师,我的组长,以及诸位同学。</p>
<p>比较业余,做的不好,但也算是尽力而为了。</p>
<p><img alt="image-20220222022439071" src="static/20220121-我的第一张Blender作品.assets/image-20220222022439071.png"></p>
<p><img alt="image-20220222022516506" src="static/20220121-我的第一张Blender作品.assets/image-20220222022516506.png"></p>
<p><img alt="image-20220222022521743" src="static/20220121-我的第一张Blender作品.assets/image-20220222022521743.png"></p>
<p><img alt="image-20220222022543892" src="static/20220121-我的第一张Blender作品.assets/image-20220222022543892.png"></p>Blender 抖音散射效果2021-12-09T00:00:00+08:002021-12-09T00:00:00+08:00魏远tag:None,2021-12-09:/posts/blender-dou-yin-san-she-xiao-guo.html<p><img alt="img" src="static/20211209-抖音散射效果.assets/f98badbc-61f8-4989-9943-f664c019d70e.jpg"></p>blender 体素化为固定大小方块2021-11-28T00:00:00+08:002021-11-28T00:00:00+08:00魏远tag:None,2021-11-28:/posts/blender-ti-su-hua-wei-gu-ding-da-xiao-fang-kuai.html<p>remesh的blocks模式可以进行效果比较好的体素化,但是不能指定大小。配合geometry nodes做程序化生成会导致这种现象:</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/7ab04eeb-0b60-4703-b88d-e54b75864678.jpg"></p>
<p>scale缩放,然后ctrl+a应用缩放,房子就散架了:</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/d8001566-697e-4fc6-a4c1-96efa7747604.jpg"></p>
<p>同样的,直接拉长房子也会导致散架</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/faec21f2-b973-4d44-90df-a34f779193bd.jpg"></p>
<p>这是因为remesh出的块大小不固定,有时是近似于1x1x1,有时等比更大,有时可能是1x1x2。</p>
<p>方案1:</p>
<p>用remesh修改器,blocks模式</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/39168b4e-7178-4b89-918d-123642102242.png"></p>
<p>建一个固定大小的box 和要体素化的图形join起来,不能乱改最外层box的大小。</p>
<p>https://blender.stackexchange.com/questions/41665/is-it-possible-to-make-the-remesh-modifier-block-size-constant</p>
<p>方案2:</p>
<p>不用修改器。</p>
<p>直接在gn中随机生成足够多的点,然后通过snap选点</p>
<p>https://lesterbanks.com/2021/03/how-to-voxelize-objects-with-geometry-nodes/</p>
<p>方案3:</p>
<p>直接使用voxel模式的remesh</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/4be3c091-3db3-4432-affb-4fd94cbf65c9.jpg"></p>
<p>但是边角会被弄成圆滑过渡</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/0a240a99-49fa-460f-a84c-d9587c134330.jpg"></p>
<p>以至于这样</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/b5708090-6940-4fca-9565-6c67ace7f74d.jpg"></p>
<p>最佳方案:</p>
<p>用remesh修改器,blocks模式。</p>
<p>此时当前的单位体素大小和当前mesh的bounding box size以及scale相关,因此实际上是可以计算出来的。</p>
<p>反过来想,我们已知bounding …</p><p>remesh的blocks模式可以进行效果比较好的体素化,但是不能指定大小。配合geometry nodes做程序化生成会导致这种现象:</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/7ab04eeb-0b60-4703-b88d-e54b75864678.jpg"></p>
<p>scale缩放,然后ctrl+a应用缩放,房子就散架了:</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/d8001566-697e-4fc6-a4c1-96efa7747604.jpg"></p>
<p>同样的,直接拉长房子也会导致散架</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/faec21f2-b973-4d44-90df-a34f779193bd.jpg"></p>
<p>这是因为remesh出的块大小不固定,有时是近似于1x1x1,有时等比更大,有时可能是1x1x2。</p>
<p>方案1:</p>
<p>用remesh修改器,blocks模式</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/39168b4e-7178-4b89-918d-123642102242.png"></p>
<p>建一个固定大小的box 和要体素化的图形join起来,不能乱改最外层box的大小。</p>
<p>https://blender.stackexchange.com/questions/41665/is-it-possible-to-make-the-remesh-modifier-block-size-constant</p>
<p>方案2:</p>
<p>不用修改器。</p>
<p>直接在gn中随机生成足够多的点,然后通过snap选点</p>
<p>https://lesterbanks.com/2021/03/how-to-voxelize-objects-with-geometry-nodes/</p>
<p>方案3:</p>
<p>直接使用voxel模式的remesh</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/4be3c091-3db3-4432-affb-4fd94cbf65c9.jpg"></p>
<p>但是边角会被弄成圆滑过渡</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/0a240a99-49fa-460f-a84c-d9587c134330.jpg"></p>
<p>以至于这样</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/b5708090-6940-4fca-9565-6c67ace7f74d.jpg"></p>
<p>最佳方案:</p>
<p>用remesh修改器,blocks模式。</p>
<p>此时当前的单位体素大小和当前mesh的bounding box size以及scale相关,因此实际上是可以计算出来的。</p>
<p>反过来想,我们已知bounding box size和单位体素大小,那么可以求出一个scale赋给当前mesh,从而达到目的。</p>
<p>已经有大佬做好了成品:</p>
<p>https://blender.stackexchange.com/questions/128191/how-can-i-use-a-block-size-of-1m-for-different-sized-models-using-the-remesh-mod</p>
<p><a href="wiz://open_attachment?guid=d1b4e0e7-4148-4b54-8595-3cadb5d8bd9f"><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/1355849265.png"></a></p>
<p>但是这个例子有一个问题就是必须 每个对象修改一次driver。如果想一个driver应用于所有,需要打开一个选项,同时driver表达式改为:</p>
<p>max(self.id_data.dimensions[0], self.id_data.dimensions[1], self.id_data.dimensions[2])/(2 ** self.octree_depth)</p>
<p>选项是:</p>
<p><img alt="img" src="static/20211128-blender体素化为固定大小方块.assets/878e4ebf-ffb8-4f71-b9ec-908ef25862b3.png"></p>
<p>此外,可以等待这个提案实装:</p>
<p>https://developer.blender.org/T89052</p>Blender 制作碎裂效果并导入unity2021-11-26T00:00:00+08:002021-11-26T00:00:00+08:00魏远tag:None,2021-11-26:/posts/blender-zhi-zuo-sui-lie-xiao-guo-bing-dao-ru-unity.html<p>首先,摆一个地面和要碎裂的物体</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/dd53c38c-98a4-4c97-bf54-0adc990482b8.jpg"></p>
<p>注意被碎裂的物体不能有面的穿插,例如互穿的两个cube,ctrl+l并在一块是不行的,可以用boolean的union来做处理。</p>
<p>首先我们来做碎块。</p>
<p>在blender的插件列表中搜索Cell Fracture并启用这个插件。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/2f80456e-873b-46ff-b464-1cd969a23dd9.png"></p>
<p>选中立方体,按f3,输入cell,调出菜单开碎</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/dadc2ca8-8688-4fd5-85e1-2d0294d69ffd.jpg"></p>
<p>最简单的设置只要改两个选项,Source Limit 是碎出的块数,Noise是随机扰动</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/08fe43ad-2bf0-45ae-9da8-0efd9f4ca402.jpg"></p>
<p>点击OK,一个碎裂物体就生成了,并和原本的叠在一块</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/983400de-05de-4911-acdb-1387a2101264.jpg"></p>
<p>删掉原物体,Shift+A添加一个空对象</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/647e4dac-744f-4c5d-a670-2f1aa8ad671c.jpg"></p>
<p>空对象在场景中显示为两个十字叠起来的坐标轴</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/7c91ddd3-842a-46c1-91b5-c4b2ea71170c.jpg"></p>
<p>将碎块parent到空对象上。</p>
<p>然后我们将所有碎块加上物理模拟。</p>
<p>选中所有碎块(不要包含空对象),点选物理选项卡,再点击Collision和Rigid Body</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/3a02d756-2f99-4ffd-a65d-0d5887c6372c.jpg"></p>
<p>展开Collision,调整一点摩擦力</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/98c726b1-878d-498b-aaf5-812aebbf412e.jpg"></p>
<p>然后做这个操作复制刚体到所有碎块上。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/801b1495-1cfb-4d3f-8268-a35fefa283dc.jpg"></p>
<p>这样碎块的物理就设置好了。</p>
<p>现在物理可以运行了,但是会直直的掉下去。所以我们还需要做一个地面。</p>
<p>选中plane,依然是Collision和Rigid Body两件套。随后展开Rigid Body的设置,Type改为Passive。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/91ddc780-7932-4d69-a579-4dea2cfbae11.jpg"></p>
<p>现在按空格键播放动画,就可以看到掉落碎裂的过程了。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/16056507-3289-4da7-9f7f-c0f317430094.jpg"></p>
<p>这样一来,效果已经实现 …</p><p>首先,摆一个地面和要碎裂的物体</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/dd53c38c-98a4-4c97-bf54-0adc990482b8.jpg"></p>
<p>注意被碎裂的物体不能有面的穿插,例如互穿的两个cube,ctrl+l并在一块是不行的,可以用boolean的union来做处理。</p>
<p>首先我们来做碎块。</p>
<p>在blender的插件列表中搜索Cell Fracture并启用这个插件。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/2f80456e-873b-46ff-b464-1cd969a23dd9.png"></p>
<p>选中立方体,按f3,输入cell,调出菜单开碎</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/dadc2ca8-8688-4fd5-85e1-2d0294d69ffd.jpg"></p>
<p>最简单的设置只要改两个选项,Source Limit 是碎出的块数,Noise是随机扰动</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/08fe43ad-2bf0-45ae-9da8-0efd9f4ca402.jpg"></p>
<p>点击OK,一个碎裂物体就生成了,并和原本的叠在一块</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/983400de-05de-4911-acdb-1387a2101264.jpg"></p>
<p>删掉原物体,Shift+A添加一个空对象</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/647e4dac-744f-4c5d-a670-2f1aa8ad671c.jpg"></p>
<p>空对象在场景中显示为两个十字叠起来的坐标轴</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/7c91ddd3-842a-46c1-91b5-c4b2ea71170c.jpg"></p>
<p>将碎块parent到空对象上。</p>
<p>然后我们将所有碎块加上物理模拟。</p>
<p>选中所有碎块(不要包含空对象),点选物理选项卡,再点击Collision和Rigid Body</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/3a02d756-2f99-4ffd-a65d-0d5887c6372c.jpg"></p>
<p>展开Collision,调整一点摩擦力</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/98c726b1-878d-498b-aaf5-812aebbf412e.jpg"></p>
<p>然后做这个操作复制刚体到所有碎块上。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/801b1495-1cfb-4d3f-8268-a35fefa283dc.jpg"></p>
<p>这样碎块的物理就设置好了。</p>
<p>现在物理可以运行了,但是会直直的掉下去。所以我们还需要做一个地面。</p>
<p>选中plane,依然是Collision和Rigid Body两件套。随后展开Rigid Body的设置,Type改为Passive。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/91ddc780-7932-4d69-a579-4dea2cfbae11.jpg"></p>
<p>现在按空格键播放动画,就可以看到掉落碎裂的过程了。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/16056507-3289-4da7-9f7f-c0f317430094.jpg"></p>
<p>这样一来,效果已经实现,流程也即将结束。</p>
<p>接下来我们把物理模拟烘焙成顶点动画:</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/024ef3ef-8b7b-4fd2-bb21-c2d0c51126ff.jpg"></p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/92dcd3c8-52cf-4526-8e04-e5811e2188b7.png"></p>
<p>完成之后时间轴上出现很多的点。这时即使我们删掉物理模拟,碎块还是会按照轨迹播放。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/87e7c914-f88c-40e7-925b-899dabd9e5ea.png"></p>
<p>最后我们需要导出动画到游戏引擎。</p>
<p>首先修改动画时间轴上的End来获得一个合理的帧数</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/418cdfa2-dbd1-48ca-ba07-2f442a2884d9.png"></p>
<p>接下来选中空节点和所有碎块,File -> Export -> DAE</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/ed5a661c-a9b5-4481-a640-398b8a560d29.jpg"></p>
<p>不用fbx是因为fbx不支持这种动画,所以用fbx导出到unity会发生很可怕的事情。外网有个哥们做了解释:</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/3205a2ca-158d-4c49-861b-272ed525fdb2.png"></p>
<p>另外abc格式也可以,但是unity要装个package(见参考)。</p>
<p>导出的时候选一下Selection Only,不然整个场景都被导出了。</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/caf5f5a7-7732-460c-ab35-f436b4be129e.png"></p>
<p>进unity看看:</p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/ed9aa39e-8a3b-4263-8547-22d493d90114.jpg"></p>
<p><img alt="img" src="static/20211126-blender制作碎裂效果并导入unity.assets/1c3653e4-5876-42bd-baf0-c242a2e907da.jpg"></p>
<p>完事。</p>
<p>参考:</p>
<p>https://blenderartists.org/t/cell-fracture-physics-fbx-export/1227694</p>
<p>https://docs.unity3d.com/Packages/com.unity.formats.alembic@1.0/manual/index.html</p>
<p>https://youtu.be/ihY4KkSXfhI</p>Web可编辑的贝塞尔曲线 easing 函数2021-11-26T00:00:00+08:002021-11-26T00:00:00+08:00魏远tag:None,2021-11-26:/posts/webke-bian-ji-de-bei-sai-er-qu-xian-easing-han-shu.html<p>网站:</p>
<p>https://cubic-bezier.com/#.34,1.56,.85,1.31</p>
<p><img alt="img" src="static/20211126-前端库-可编辑的贝塞尔曲线easing函数.assets/35f596fb-fbab-4a66-b0a9-1a2d705b0ae0.png"></p>
<p>或 https://codepen.io/osublake/pen/OyPGEo</p>
<p><img alt="img" src="static/20211126-前端库-可编辑的贝塞尔曲线easing函数.assets/3edf3afd-ed6b-4b90-aa37-4fd131d00b72.png"></p>
<p>再使用这个脚本生成Easing函数</p>
<p>https://gist.github.com/OSUblake/4d9f0caf980f4ee492ef</p>
<p><a href="wiz://open_attachment?guid=d50249ff-b552-4a29-bdad-8cc0340e9ad0"><img alt="img" src="static/20211126-前端库-可编辑的贝塞尔曲线easing函数.assets/88319640.png"></a></p>
<p>用法:</p>
<p>TweenLite.to(path, 2, { ease: CubicBezier.config(1, 2, 3, 4) });</p>Blender collection instance 的 origin 修改2021-11-21T00:00:00+08:002021-11-21T00:00:00+08:00魏远tag:None,2021-11-21:/posts/blender-collection-instance-de-origin-xiu-gai.html<p>默认在整个场景的原点,如果想要修改到collection主物体点,可以先设置cursor到物体位置,然后如此操作:</p>
<p><img alt="img" src="static/20211121-blender-collection-instance的origin修改.assets/06156670-05aa-4d8f-9747-cd4111bd3bcc.jpg"></p>
<p><img alt="img" src="static/20211121-blender-collection-instance的origin修改.assets/09d6712b-5886-4681-a4f9-cd44818f5b7b.png"></p>已知固定初速和落点的火炮抛物线计算2021-11-17T00:00:00+08:002021-11-17T00:00:00+08:00魏远tag:None,2021-11-17:/posts/yi-zhi-gu-ding-chu-su-he-luo-dian-de-huo-pao-pao-wu-xian-ji-suan.html<h3>应用于以下场景</h3>
<p>一门火炮在高度h2,目标在高度0</p>
<p>火炮开火后,弹药初速度固定为v0,求此弹道公式。</p>
<h4>答案</h4>
<p>已知 l (发射距离),h2(起点高度差),v0标量速度</p>
<p><img alt="img" src="static/20211117-已知固定初速和落点的火炮抛物线计算.assets/3c88d3c7-c0fd-411c-a0e5-6b99331425ae.png"></p>
<p><a href="https://www.wolframalpha.com/input/?i=solve++l%3Dv0*cos%28a%29*+%28+v0*sin%28a%29+%2Fg+%2B+sqrt%28v0+%5E+2+*+sin%5E2%28a%29+%2F+g%5E2+%2B+2*h2+%2Fg%29+%29%2C+for+a">wolframalpha</a></p>
<p>这个公式的解析解为</p>
<p><img alt="img" src="static/20211117-已知固定初速和落点的火炮抛物线计算.assets/4cdd7b68-7bd5-48be-ade5-e27bfda33b41.png"></p>
<p>转换为代码:</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">gravity</span> <span class="o">=</span> <span class="mf">9.8</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">posCalc</span><span class="p">(</span><span class="nx">endX</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">x</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">h</span> <span class="o">=</span> <span class="mf">0</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">v0</span> <span class="o">=</span> <span class="mf">13</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">l</span> <span class="o">=</span> <span class="nx">endX</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">g</span> <span class="o">=</span> <span class="nx">gravity</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">h2</span> <span class="o">=</span> <span class="nx">h</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">angle</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">acos</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="o">-</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="mf">0</span> <span class="o">-</span> <span class="nx">l …</span></code></pre></div><h3>应用于以下场景</h3>
<p>一门火炮在高度h2,目标在高度0</p>
<p>火炮开火后,弹药初速度固定为v0,求此弹道公式。</p>
<h4>答案</h4>
<p>已知 l (发射距离),h2(起点高度差),v0标量速度</p>
<p><img alt="img" src="static/20211117-已知固定初速和落点的火炮抛物线计算.assets/3c88d3c7-c0fd-411c-a0e5-6b99331425ae.png"></p>
<p><a href="https://www.wolframalpha.com/input/?i=solve++l%3Dv0*cos%28a%29*+%28+v0*sin%28a%29+%2Fg+%2B+sqrt%28v0+%5E+2+*+sin%5E2%28a%29+%2F+g%5E2+%2B+2*h2+%2Fg%29+%29%2C+for+a">wolframalpha</a></p>
<p>这个公式的解析解为</p>
<p><img alt="img" src="static/20211117-已知固定初速和落点的火炮抛物线计算.assets/4cdd7b68-7bd5-48be-ade5-e27bfda33b41.png"></p>
<p>转换为代码:</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">gravity</span> <span class="o">=</span> <span class="mf">9.8</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">posCalc</span><span class="p">(</span><span class="nx">endX</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">x</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">h</span> <span class="o">=</span> <span class="mf">0</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">v0</span> <span class="o">=</span> <span class="mf">13</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">l</span> <span class="o">=</span> <span class="nx">endX</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">g</span> <span class="o">=</span> <span class="nx">gravity</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">h2</span> <span class="o">=</span> <span class="nx">h</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">angle</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">acos</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="o">-</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="mf">0</span> <span class="o">-</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">4</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span> <span class="o">*</span> <span class="p">(</span><span class="nx">g</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">-</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">g</span> <span class="o">*</span> <span class="nx">h2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">-</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span><span class="p">))</span> <span class="o">/</span> <span class="p">(</span><span class="nx">h2</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span> <span class="o">+</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">g</span> <span class="o">*</span> <span class="nx">h2</span> <span class="o">*</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">2</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="nx">h2</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span> <span class="o">+</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="nx">h2</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span> <span class="o">+</span> <span class="nx">l</span> <span class="o">**</span> <span class="mf">2</span> <span class="o">*</span> <span class="nx">v0</span> <span class="o">**</span> <span class="mf">4</span><span class="p">))</span> <span class="o">/</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="mf">2</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">angle</span><span class="p">))</span> <span class="p">{</span>
<span class="err"></span> <span class="k">return</span> <span class="kc">NaN</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">tan</span><span class="p">(</span><span class="nx">angle</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="nx">gravity</span> <span class="o">*</span> <span class="p">(</span><span class="nx">x</span> <span class="o">**</span> <span class="mf">2</span><span class="p">))</span> <span class="o">/</span> <span class="p">(</span><span class="mf">2</span> <span class="o">*</span> <span class="p">((</span><span class="nx">v0</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">angle</span><span class="p">))</span> <span class="o">**</span> <span class="mf">2</span><span class="p">));</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">([</span><span class="mf">0</span><span class="p">,</span><span class="mf">1</span><span class="p">,</span><span class="mf">2</span><span class="p">,</span><span class="mf">3</span><span class="p">,</span><span class="mf">4</span><span class="p">,</span><span class="mf">5</span><span class="p">].</span><span class="nx">map</span><span class="p">((</span><span class="nx">x</span><span class="p">)</span> <span class="o">=></span> <span class="nx">posCalc</span><span class="p">(</span><span class="mf">5</span><span class="p">,</span> <span class="nx">x</span><span class="p">,</span> <span class="mf">2</span><span class="p">)))</span>
</code></pre></div>
<p>playgroud:</p>
<p>https://www.typescriptlang.org/play/?#code/DYUwLgBA5gTghgNwJZgJ4QLwQJwDoAcA3AFDEBmArgHYDGYSA9lRAA4MDOAwnMDQBQgqAEwAaALghUKAWwBGIGABoIgoQC0JUuQuUAPTTPlKIAC0wQADAEoIAb2IBIGk3aQEF8wEYAzCScvIISRXOFoQEXMAWTgwE1w4WXYBYRErQggAegyIQCorQBd4x2cqVwggkLC1KJi4hKTVNTTM7MBWvwLCgNNUNjBBCnYQKtjcdgBHGDA+MrBQmhBKgCp5iAAmCABqUuDpsIjFlbTSByyIdnM+dwg95Zsl9iQqZcAO4EAYf8BZ5UAhG0f24shQqFAPFhokM4HcqHw+CYugwelJ+pdoPBkGgbNlzh4rlZUSs-I5jnBzGCAHp8AC0nisfFG4z4qygpnWEHcROuGXcNg2q0AA8CSCChIRMiwAQgwHn50BFYuEvJAoGkgkgam+JQQnkGcWpEwuV0ZqyWsEQKHQSxMB2IEAgRRKkCB1WGYwmZiu2VVlyWqw2wI1DshbogABZGYGlqpdu6EaqWVY0a7MdivfaadcSBarZAwKtbUNNXwAKzxu051Vmi2gX5Uf4DLM1ZxJFQpTIQdEIjM2A5HDJlvkV0Dq+JgviIw1oBGhxvNpbF7HLPxZLt-XvV+K1wcJnNMtU60kMzGNi4ly0uBigXDABhQQcAciFN8vygXIHvPZAFmUYDfyfNTUPxWPIFP558O+9aiOOWIkI4SBkE2AByhgKLgwQwXAMF8A+WJ2I4DgwOAFAwMwyEwX4AC+jg4WAeHMLoCIJtsaHPjY258AayLGk21FxuOepNhOEAJiu6E3O6GEbCYJCkaQxBWn+AEXgA2q+niKMsijeIo-qKLmAC6uDSHALAQroNgYAAfKwHDcLwebKK+EC6DZWJWKQQA</p>
<h4>验算</h4>
<p>import matplotlib.pyplot as plt</p>
<p>#x_data = [0,1,2,3,4,5,6,7]</p>
<p>#y_data = [2, 2.089787159527464, 2, 1.7306385214176083, 1.281702723780289, 0.6531926070880416, -0.15489182865913387, -1.142550583461237]</p>
<p>x_data = [0,1,2,3,4,5]</p>
<p>y_data = [0, 0.11852191096741502, 0.1777828664511225, 0.1777828664511225, 0.11852191096741499, 0]</p>
<p>plt.plot(x_data,y_data)</p>
<p>plt.show()</p>
<h4>参考</h4>
<p>斜抛运动</p>
<p>https://baike.baidu.com/item/斜抛运动/9905547?fr=aladdin</p>
<p>斜抛运动的运用:烟花燃放和铅球投掷模型:</p>
<p>https://zhuanlan.zhihu.com/p/164584567</p>Android 渠道包几种技巧2021-11-02T00:00:00+08:002021-11-02T00:00:00+08:00魏远tag:None,2021-11-02:/posts/android-qu-dao-bao-ji-chong-ji-qiao.html<h2>1. 架构</h2>
<p>建议的架构是,游戏本体作为一个library module,每一个渠道作为一个app module,这样较为简单。</p>
<p><img alt="img" src="static/20211102-android 渠道包几种技巧.assets/234dc66d-92e4-4a50-ba36-21ca2f4b9e75.png"></p>
<h2>2. 编译缓慢</h2>
<p>给gradle换国内源</p>
<div class="highlight"><pre><span></span><code> repositories {
maven{ url'https://maven.aliyun.com/repository/google' }
maven{ url'https://maven.aliyun.com/repository/public' }
}
</code></pre></div>
<h2>3. 命令行传入编译参数</h2>
<p>gradlew :app:buildRelease -Papp_name=我的游戏</p>
<p>项目的总build.gradle中写</p>
<div class="highlight"><pre><span></span><code>ext {
app_id = project.hasProperty("app_id") ? project.property('app_id') : 'com.example.laya_template'
app_name = project …</code></pre></div><h2>1. 架构</h2>
<p>建议的架构是,游戏本体作为一个library module,每一个渠道作为一个app module,这样较为简单。</p>
<p><img alt="img" src="static/20211102-android 渠道包几种技巧.assets/234dc66d-92e4-4a50-ba36-21ca2f4b9e75.png"></p>
<h2>2. 编译缓慢</h2>
<p>给gradle换国内源</p>
<div class="highlight"><pre><span></span><code> repositories {
maven{ url'https://maven.aliyun.com/repository/google' }
maven{ url'https://maven.aliyun.com/repository/public' }
}
</code></pre></div>
<h2>3. 命令行传入编译参数</h2>
<p>gradlew :app:buildRelease -Papp_name=我的游戏</p>
<p>项目的总build.gradle中写</p>
<div class="highlight"><pre><span></span><code>ext {
app_id = project.hasProperty("app_id") ? project.property('app_id') : 'com.example.laya_template'
app_name = project.hasProperty("app_name") ? project.property('app_name') : 'testgame'
version_code = Integer.parseInt(project.hasProperty("version_code") ? project.property('version_code') : "1")
version_name = project.hasProperty("version_name") ? project.property('version_name') : "0.0.1"
channel_app_id = project.hasProperty("channel_app_id") ? project.property('channel_app_id') : ""
channel_app_id2 = project.hasProperty("channel_app_id2") ? project.property('channel_app_id2') : ""
channel_splash_id = project.hasProperty("channel_splash_id") ? project.property('channel_splash_id') : ""
}
</code></pre></div>
<p>app项目的gradle中可以应用这些配置</p>
<p><img alt="img" src="static/20211102-android 渠道包几种技巧.assets/4ccbf370-1b78-439a-8f66-2ed677fea9f9.png"></p>
<div class="highlight"><pre><span></span><code> <span class="nt">defaultConfig</span> <span class="p">{</span>
<span class="err">minSdk</span> <span class="err">19</span>
<span class="err">targetSdk</span> <span class="err">30</span>
<span class="err">applicationId</span> <span class="err">rootProject.ext.app_id</span>
<span class="err">versionCode</span> <span class="err">rootProject.ext.version_code</span>
<span class="err">versionName</span> <span class="err">rootProject.ext.version_name</span>
<span class="err">manifestPlaceholders</span> <span class="err">=</span> <span class="cp">[</span>
<span class="nx">app_name</span><span class="p">:</span> <span class="nx">rootProject.ext.app_name</span><span class="p">,</span>
<span class="nx">channel_app_id</span><span class="p">:</span> <span class="nx">rootProject.ext.channel_app_id</span><span class="p">,</span>
<span class="nx">channel_app_id2</span><span class="p">:</span> <span class="nx">rootProject.ext.channel_app_id2</span><span class="p">,</span>
<span class="nx">channel_splash_id</span><span class="p">:</span> <span class="nx">rootProject.ext.channel_splash_id</span><span class="p">,</span>
<span class="cp">]</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>同时传入到AndroidManifest.xml中:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><meta</span><span class="err">-data</span> <span class="na">android:name=</span><span class="s">"channel_app_id"</span> <span class="na">android:value=</span><span class="s">"</span><span class="cp">${</span><span class="n">channel_app_id</span><span class="cp">}</span><span class="s">"</span> <span class="nt">/></span>
<span class="nt"><meta</span><span class="err">-data</span> <span class="na">android:name=</span><span class="s">"channel_app_id2"</span> <span class="na">android:value=</span><span class="s">"</span><span class="cp">${</span><span class="n">channel_app_id2</span><span class="cp">}</span><span class="s">"</span> <span class="nt">/></span>
<span class="nt"><meta</span><span class="err">-data</span> <span class="na">android:name=</span><span class="s">"channel_splash_id"</span> <span class="na">android:value=</span><span class="s">"</span><span class="cp">${</span><span class="n">channel_splash_id</span><span class="cp">}</span><span class="s">"</span> <span class="nt">/></span>
</code></pre></div>
<p>传入后可以在application或者activity中获取:</p>
<div class="highlight"><pre><span></span><code><span class="nv">public</span> <span class="nv">String</span> <span class="nv">getMetaData</span> <span class="ss">(</span><span class="nv">String</span> <span class="nv">key</span><span class="ss">)</span> {
<span class="nv">try</span> {
<span class="nv">ApplicationInfo</span> <span class="nv">appInfo</span> <span class="o">=</span> <span class="nv">getPackageManager</span><span class="ss">()</span>
.<span class="nv">getApplicationInfo</span><span class="ss">(</span><span class="nv">getPackageName</span><span class="ss">()</span>,
<span class="nv">PackageManager</span>.<span class="nv">GET_META_DATA</span><span class="ss">)</span><span class="c1">;</span>
<span class="k">return</span> <span class="nv">appInfo</span>.<span class="nv">metaData</span>.<span class="nv">getString</span><span class="ss">(</span><span class="nv">key</span><span class="ss">)</span><span class="c1">;</span>
} <span class="nv">catch</span> <span class="ss">(</span><span class="nv">PackageManager</span>.<span class="nv">NameNotFoundException</span> <span class="nv">e</span><span class="ss">)</span> {
}
<span class="k">return</span> <span class="nv">null</span><span class="c1">;</span>
}
</code></pre></div>
<p>可能这里会有一个问题:是不是我所有渠道app模块都需要写一遍AndroidManifest.xml?</p>
<p>不用。在gamecore的AndroidManifest.xml里写,编译后所有app都可以读取到对应的值。</p>
<p>但是注意一点,虽然AndroidManifest.xml最终会被合并,但是manifestPlaceholders不会共享,例如如果我想在app_vivo中写一条:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><meta</span><span class="err">-data</span> <span class="na">android:name=</span><span class="s">"test222"</span> <span class="na">android:value=</span><span class="s">"</span><span class="cp">${</span><span class="n">app_name</span><span class="cp">}</span><span class="s">"</span> <span class="nt">/></span>
</code></pre></div>
<p>如果在app_vivo的build.gradle中没有定义app_name的值,就会发生报错。</p>Blender box cutter 记忆2021-11-02T00:00:00+08:002021-11-02T00:00:00+08:00魏远tag:None,2021-11-02:/posts/blender-box-cutter-ji-yi.html<p>D 菜单</p>
<p>shift + d 另一个菜单</p>
<p>t 实体化</p>
<p><img alt="img" src="static/20211102-blender-boxcutter记忆.assets/79c447cd-0ad4-44f2-808f-f799f1787c20.jpg"></p>
<p>shift + t 锥化</p>
<p><img alt="img" src="static/20211102-blender-boxcutter记忆.assets/b5d6cd77-3c25-4392-8b55-9edd26bb19b1.jpg"></p>
<p>shift+v</p>
<p><img alt="img" src="static/20211102-blender-boxcutter记忆.assets/3f72d6b8-f096-4edd-ad1f-a1c82e2effeb.jpg"></p>Laya粒子与Unity的一点不同2021-10-26T00:00:00+08:002021-10-26T00:00:00+08:00魏远tag:None,2021-10-26:/posts/layali-zi-yu-unityde-yi-dian-bu-tong.html<h2>1. 当多个粒子呈现父子关系,父节点的play并不会影响子节点,这和unity不同。</h2>
<p>laya</p>
<p><a href="https://github.com/layabox/LayaAir/blob/master/src/layaAir/laya/d3/core/particleShuriKen/ShurikenParticleSystem.ts#L2006-L2038">LayaAir/ShurikenParticleSystem.ts at master · layabox/LayaAir (github.com)</a></p>
<p>unity:</p>
<p><a href="https://docs.unity3d.com/ScriptReference/ParticleSystem.Play.html">Unity - Scripting API: ParticleSystem.Play (unity3d.com)</a></p>
<h2>2. 而laya粒子节点从场景树上remove之后再addChild又会重新播放,是因为:</h2>
<p><a href="https://github.com/layabox/LayaAir/blob/23ec5dc957ac9de14d2bde9eca6673dde7e0d4cc/src/layaAir/laya/d3/core/particleShuriKen/ShuriKenParticle3D.ts#L445-L463">LayaAir/ShuriKenParticle3D.ts at 23ec5dc957ac9de14d2bde9eca6673dde7e0d4cc · layabox/LayaAir (github.com)</a></p>
<p>/**</p>
<p>* @inheritDoc</p>
<p>* @override</p>
<p>* @internal</p>
<p>*/</p>
<p>_activeHierarchy(activeChangeComponents: any[]): void {</p>
<p> super._activeHierarchy(activeChangeComponents);</p>
<p> (this.particleSystem.playOnAwake) && (this.particleSystem.play …</p><h2>1. 当多个粒子呈现父子关系,父节点的play并不会影响子节点,这和unity不同。</h2>
<p>laya</p>
<p><a href="https://github.com/layabox/LayaAir/blob/master/src/layaAir/laya/d3/core/particleShuriKen/ShurikenParticleSystem.ts#L2006-L2038">LayaAir/ShurikenParticleSystem.ts at master · layabox/LayaAir (github.com)</a></p>
<p>unity:</p>
<p><a href="https://docs.unity3d.com/ScriptReference/ParticleSystem.Play.html">Unity - Scripting API: ParticleSystem.Play (unity3d.com)</a></p>
<h2>2. 而laya粒子节点从场景树上remove之后再addChild又会重新播放,是因为:</h2>
<p><a href="https://github.com/layabox/LayaAir/blob/23ec5dc957ac9de14d2bde9eca6673dde7e0d4cc/src/layaAir/laya/d3/core/particleShuriKen/ShuriKenParticle3D.ts#L445-L463">LayaAir/ShuriKenParticle3D.ts at 23ec5dc957ac9de14d2bde9eca6673dde7e0d4cc · layabox/LayaAir (github.com)</a></p>
<p>/**</p>
<p>* @inheritDoc</p>
<p>* @override</p>
<p>* @internal</p>
<p>*/</p>
<p>_activeHierarchy(activeChangeComponents: any[]): void {</p>
<p> super._activeHierarchy(activeChangeComponents);</p>
<p> (this.particleSystem.playOnAwake) && (this.particleSystem.play());</p>
<p>}</p>
<p>/**</p>
<p>* @inheritDoc</p>
<p>* @override</p>
<p>* @internal</p>
<p>*/</p>
<p>_inActiveHierarchy(activeChangeComponents: any[]): void {</p>
<p> super._inActiveHierarchy(activeChangeComponents);</p>
<p> (this.particleSystem.isAlive) && (this.particleSystem.simulate(0, true));</p>
<p>}</p>
<p>这一段代码。</p>
<h2>3. 如果想给粒子特效添加一个父节点,方便unity内调试,可以勾掉Shape,并将Emission选为0就可以了。</h2>
<p><img alt="img" src="static/20211026-Laya粒子与Unity的一点不同.assets/55b18035-ab9a-4aea-b7c7-7dcd9beb4b72.png"></p>Blender2021-10-09T00:00:00+08:002021-10-09T00:00:00+08:00魏远tag:None,2021-10-09:/posts/blender.html<p>修改名字,同名的融球是一组。</p>Blender LowPoly资源2021-10-01T00:00:00+08:002021-10-01T00:00:00+08:00魏远tag:None,2021-10-01:/posts/blender-lowpolyzi-yuan.html<p>调色盘</p>
<p>https://lospec.com/palette-list</p>
<p>用Unity制作isometric(等距视角)风格游戏的插件与资源精选</p>
<p>https://cowlevel.net/article/2111554</p>
<p>https://itch.io/game-assets/tag-3d/tag-car/tag-low-poly</p>
<p>polygon runway系列</p>
<p>https://www.bilibili.com/video/BV1t4411L7sm</p>blender渲染当前3d viewport2021-09-14T00:00:00+08:002021-09-14T00:00:00+08:00魏远tag:None,2021-09-14:/posts/blenderxuan-ran-dang-qian-3d-viewport.html<p>view->viewport render image</p>blender按数字键会隐藏collection如何解决2021-09-11T00:00:00+08:002021-09-11T00:00:00+08:00魏远tag:None,2021-09-11:/posts/blenderan-shu-zi-jian-hui-yin-cang-collectionru-he-jie-jue.html<p><img alt="img" src="static/20210911-Blender-数字键隐藏collection解决.assets/0.22544673538962418.png"></p>
<p>向下滚动</p>
<p><img alt="img" src="static/20210911-Blender-数字键隐藏collection解决.assets/80da488f-bf63-4137-8bd4-ecbc4391cb04.png"></p>Blender用布尔倒模2021-08-29T00:00:00+08:002021-08-29T00:00:00+08:00魏远tag:None,2021-08-29:/posts/blenderyong-bu-er-dao-mo.html<p>先做一个结构,并将外框拉大</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/51d1ab4c-7a78-4a7a-9457-eaddf92990fb.jpg"></p>
<p>新建一个cube缩小到小于外框,这样竖着插进来</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/0643aa99-69da-466c-aaa9-d427f098a688.jpg"></p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/eb7f70ae-fb51-4e11-b9f9-2e15ff36477a.jpg"></p>
<p>进行一个布尔</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/0cdb5b29-1197-426d-a0d1-6e075119fe0d.jpg"></p>
<p>应用修改器</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/11144b51-5d03-4a3c-94bf-1cdbdb9b0555.jpg"></p>
<p>这样一来,比如我新建一个cube,就可以用这块挖出一个复刻的结构</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/2101fc6c-45df-452e-b5cd-66296c5d0add.jpg"></p>
<p>我还可以这样</p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/0f8d8401-b01d-40a3-8a5b-e29dccd48958.jpg"></p>
<p><img alt="img" src="static/20210829-Blender-用布尔倒模.assets/9bac8d36-5ddb-443c-976b-e498012970f2.jpg"></p>Blender多色荧光棒2021-08-27T00:00:00+08:002021-08-27T00:00:00+08:00魏远tag:None,2021-08-27:/posts/blenderduo-se-ying-guang-bang.html<p><img alt="image-20220222013409975" src="static/20210827-Blender-多色荧光棒.assets/image-20220222013409975.png"></p>Blender一种隔位环选的办法2021-08-23T00:00:00+08:002021-08-23T00:00:00+08:00魏远tag:None,2021-08-23:/posts/blenderyi-chong-ge-wei-huan-xuan-de-ban-fa.html<p>目标</p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/a392575f-7fa6-488e-af31-30080130f29d.jpg"></p>
<p>直接用select desert会这样</p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/147cc167-e53f-4894-80e2-6e056b03d21c.jpg"></p>
<p>可以先选上面的部分</p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/3da6c4bd-5438-46c3-900f-90fe2b6b08d3.jpg"></p>
<p>然后shift+g -> normal</p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/ac8231b6-f972-4e9a-933b-13fa30220aac.jpg"></p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/b7bb9071-a175-464b-a53d-3d834d68f616.jpg"></p>
<p>存为顶点组</p>
<p><img alt="img" src="static/20210823-Blender-一种隔位环选的办法.assets/7a082364-c90b-4ae1-bf8c-33f33248a646.png"></p>
<p>最后发现……其实可以用面去选择。</p>CGJ 2021 (Gamjam) 赛后回顾2021-08-08T23:30:00+08:002021-08-08T23:30:00+08:00魏远tag:None,2021-08-08:/posts/cgj-2021-gamjam-sai-hou-hui-gu.html<p>不知不觉,CGJ已经是一个月前的事情了。</p>
<p>那是一个月前的周五,下午三点半。</p>
<p>把手上改了最后一版的游戏包匆匆交给发行之后,急急奔向了天府软件园。去参加CGJ 2021。</p>
<h3>赛前</h3>
<p>这是一个48小时,围绕官方当场给出的主题做游戏的活动。</p>
<p>这是我第一次打线下的GameJam。打完之后我觉得线下比线上更有趣,大概是多了一种叫做氛围的东西。</p>
<p>场地是这里一家公司提供的,现场有大概多支队伍,每个小队有一个小单间,房间有点挤但也不错了。</p>
<p>赛程是前台签到 -> 规则讲解 + 放出题目 -> 游戏制作 -> 作品展示。</p>
<p>[前台]</p>
<p>公司还为参赛者准备了一些礼物,签到之后发一个。</p>
<p>礼物是一些国产动漫的毛绒玩具,罗小黑啊 尸兄啊,还有我就叫不上名字了。</p>
<p>我进场的时候,正看到一个哥们手里被发了一个Q版的某动漫女主角,</p>
<p>然后那哥们犹豫了一阵,说道:“不要二次元”。</p>
<p>一时间空气中充满了快活的气息。</p>
<p>[礼物、标牌]</p>
<p>时间回到比赛前一周。</p>
<p>我们队伍是赛前组的,一半人在微信群,一半人在QQ群。</p>
<p>总共是5个人,一个3D美术大佬是队长,算我在内2个开发,一个会写一些代码的策划,还有一个好像没搞清状况的哥们,说自己是策划。</p>
<p>赛前我和3D美术大佬见了一面,那个没搞清状况的策划老兄说自己能来 …</p><p>不知不觉,CGJ已经是一个月前的事情了。</p>
<p>那是一个月前的周五,下午三点半。</p>
<p>把手上改了最后一版的游戏包匆匆交给发行之后,急急奔向了天府软件园。去参加CGJ 2021。</p>
<h3>赛前</h3>
<p>这是一个48小时,围绕官方当场给出的主题做游戏的活动。</p>
<p>这是我第一次打线下的GameJam。打完之后我觉得线下比线上更有趣,大概是多了一种叫做氛围的东西。</p>
<p>场地是这里一家公司提供的,现场有大概多支队伍,每个小队有一个小单间,房间有点挤但也不错了。</p>
<p>赛程是前台签到 -> 规则讲解 + 放出题目 -> 游戏制作 -> 作品展示。</p>
<p>[前台]</p>
<p>公司还为参赛者准备了一些礼物,签到之后发一个。</p>
<p>礼物是一些国产动漫的毛绒玩具,罗小黑啊 尸兄啊,还有我就叫不上名字了。</p>
<p>我进场的时候,正看到一个哥们手里被发了一个Q版的某动漫女主角,</p>
<p>然后那哥们犹豫了一阵,说道:“不要二次元”。</p>
<p>一时间空气中充满了快活的气息。</p>
<p>[礼物、标牌]</p>
<p>时间回到比赛前一周。</p>
<p>我们队伍是赛前组的,一半人在微信群,一半人在QQ群。</p>
<p>总共是5个人,一个3D美术大佬是队长,算我在内2个开发,一个会写一些代码的策划,还有一个好像没搞清状况的哥们,说自己是策划。</p>
<p>赛前我和3D美术大佬见了一面,那个没搞清状况的策划老兄说自己能来,约了6点。结果我们俩6点到,然后等了一个半小时才开席,最后9点多离开的时候也未见他一面。</p>
<p>好在比赛时队伍其他人都还很给力。</p>
<h3>赛前的赛前</h3>
<p>来说说一些准备工作:</p>
<h4>技术方面</h4>
<p>赛前一周我在完善框架:</p>
<p>https://github.com/fy0/unity-tskit</p>
<p>一个unity + fgui + puerts的解决方案。5月时候其前身被用来打过ld48。</p>
<p>算是做了很多准备。后来发现其实有点想多了。</p>
<p>按照我本来预想,是实现一个mini rpg作为备选模板,但是没来得及做。</p>
<p>另一个考量就是,由于mobx带来的数据同步能力,实现模拟经营或者是rpg这种需要展示大量数据,又经常变化的情景,会有极大的效率优势。</p>
<p>但是实际题目出来之后,没有去做这两种类型。这也有多方面原因,一会再分析。</p>
<p>就单说技术选型上,gamejam其实还是求快的,其实我觉得可能单c#会好一些,可以比较暴力的解决一些问题。</p>
<p>这次也很幸运,我是web全栈转游戏开发,另一个开发在公司也用puerts,所以技术栈基本匹配,不然这里也要多花很多时间。</p>
<h4>协作方面</h4>
<p>初始计划是<strong>git(代码) + svn(资源)两个repo</strong></p>
<p>最后由于整个团队都用过git或者是别的分布式版本控制工具,所以就采用了这个方案:</p>
<p><strong>git lfs 单 repo</strong></p>
<p>也就是资源文件使用lfs,非资源文件就普通git管理。</p>
<p>这样代码仓库的托管服务也比较好找,我们也是提前建好了帐号、repo并加了权限。</p>
<h3>比赛当天</h3>
<p>回到比赛现场。周五那天签到完成之后,就是现场讲解。</p>
<p>讲了什么已经忘了,反正最后放了这么一张图:</p>
<p>[图]</p>
<p>比赛就正式开始了。</p>
<h4>解题思路</h4>
<p>一开始想的是脱轨到回到正轨,引申为让事物不断回归秩序。</p>
<p>所以想了一个大概是一个球会不断向内塌缩,玩家需要在内部使用某种方法进行固定和修复。</p>
<p>后来在如何表现上犯了难。</p>
<p>所以最后做的游戏是这样:</p>
<p>场景是一间屋子,展现了大概20年中若干个时间片段:游戏中的主角小时候在这里生活,年少时离家上学,结婚,多年后旧地重游。</p>
<p>主角的人生多有波折,并不美满,但是他有一个改变过去的机会。</p>
<p>玩家在游戏中不断经历4个画面的轮回,每一幅画能够做一次不显眼的改变,拿走或放回一件物品。每次改动发生后,未来的画面都会产生微小的变化。</p>
<p>最终变数足够多的时候,导向一个美满的人生故事。</p>
<p>不过最后受限于时间和技术水平,做的不咋样。。</p>
<p>https://womengxin.coding.net/p/ggj21/d/game-bin/git</p>
<h4>赛后想出来的解题思路</h4>
<p>看了各路大佬的作品,脑洞大开啊。</p>
<p>想到一个创意:画面上是月台、武士和远远开过来的列车。</p>
<p>玩家操控武士,在关键时刻使用居合斩,就可以切断列车!</p>
<p>玩法类似音游打点。</p>
<h4>总结</h4>
<p>还有很大的提升空间!!!</p>unity 物体/预设截图2021-07-29T00:00:00+08:002021-07-29T00:00:00+08:00魏远tag:None,2021-07-29:/posts/unity-wu-ti-yu-she-jie-tu.html<p>日常工作中,经常会遇到需要截图一大堆预设给美术,用于商店、物品图标等场合。</p>
<p>可以使用这个脚本去解决截图问题:</p>
<p>https://gist.github.com/mickdekkers/5c3c62539c057010d4497f9865060e20</p>
<p>https://gist.github.com/fy0/6f5c4ca0e478a96958d94ec9c9b1b4a5 (自己fork一份防止删除)</p>
<p>使用这个脚本需要下载他的 snapshot-camera-demo.zip,然后将Scripts下的</p>
<p>SnapshotCamera.cs、SnapshotCameraTest.cs、SnapshotCameraTestEditor.cs添加进你的项目</p>
<p>举例来说,比如我们要截图的场景有三个物体:</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/aa242739-b68b-4cd4-a139-a3611cbf7d98.png"></p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/417c0bcd-8f81-4eea-b876-64d862e0342d.png"></p>
<p>将SnapshotCameraTest作为Component添加给场景中随便一个对象</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/c4973e9c-281b-4bb6-b4bc-945d7d52d28d.png"></p>
<p>运行场景,拖动场景中game object到 Object to Snapshot 就可以预览截图。</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/d405a15d-47c8-4bed-addb-b49279c1b4d2.jpg"></p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/60a1fa0c-783c-429f-8be5-1961fe884460.jpg"></p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/97d8d227-0fae-416b-82b3-952fbdc7b9a3.jpg"></p>
<p>这时候敲空格键可以保存文件截图,注意是一次一个</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/c6679dcc-6b3c-44ed-9d0d-9044488b03bd.jpg"></p>
<p>截图同时会打log</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/70ef079b-833c-4e30-9eba-9d0beee3e134.png"></p>
<p>图默认是512x512,正交视图,透明底色,符合大部分要求,可以在脚本中定制。</p>
<p><img alt="img" src="static/20210729-unity物体预设截图.assets/d7b793d3-dc63-4bb7-aef4-2dc949482c47.jpg"></p>
<p>参考SnapshotCameraTest代码稍微写一个小脚本,就可以实现批量截图了。</p>Cascadeur 骨骼绑定简单入门笔记2021-07-24T20:05:00+08:002021-07-24T20:05:00+08:00魏远tag:None,2021-07-24:/posts/cascadeur-gu-ge-bang-ding-jian-dan-ru-men-bi-ji.html<p>Cascadeur 是一个人物动画的工具。</p>
<p>导入一个模型之后,首先要进行骨骼的绑定。</p>
<p>有两种绑定方式,一种是Rigging tool,一种是Quick rigging tool。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/deb2a191-6a4c-4f56-8d6c-b280e1bd810a.png"></p>
<p>先说结论:必须使用 Quick rigging tool,因为不用的话不能使用Auto-Posing,用这软件意义就小了很多。</p>
<p>但是Quick rigging tool生成的绑定经常有问题(例如膝盖反了),所以建议先走一遍Rigging tool的绑定流程,这样你就能知道哪里出了问题,从而手动修复。</p>
<p>绑定流程的话参考这个视频:</p>
<p>https://www.youtube.com/watch?v=w7zO7yObD6c</p>
<p>因为是旧版本所以略有不同。主要一个是在于镜像(Mirror)方式变了。当前版本的镜像方式是这样的:</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/e3a7d881-8e3b-4619-8530-d79e4295ec74.jpg"></p>
<p>随意框选一下绑好的骨骼,随后左侧填写好参数,点击“Create mirror object”即可。</p>
<p>然后就是这个样子。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/95e24d19-0725-4fb2-b33b-6ab4fdcbec95.jpg"></p>
<p>另外视频最后一部分是关于权重和物体质量的,这个了解下操作方式和代表意义即可,不用真的做一遍,因为后面 …</p><p>Cascadeur 是一个人物动画的工具。</p>
<p>导入一个模型之后,首先要进行骨骼的绑定。</p>
<p>有两种绑定方式,一种是Rigging tool,一种是Quick rigging tool。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/deb2a191-6a4c-4f56-8d6c-b280e1bd810a.png"></p>
<p>先说结论:必须使用 Quick rigging tool,因为不用的话不能使用Auto-Posing,用这软件意义就小了很多。</p>
<p>但是Quick rigging tool生成的绑定经常有问题(例如膝盖反了),所以建议先走一遍Rigging tool的绑定流程,这样你就能知道哪里出了问题,从而手动修复。</p>
<p>绑定流程的话参考这个视频:</p>
<p>https://www.youtube.com/watch?v=w7zO7yObD6c</p>
<p>因为是旧版本所以略有不同。主要一个是在于镜像(Mirror)方式变了。当前版本的镜像方式是这样的:</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/e3a7d881-8e3b-4619-8530-d79e4295ec74.jpg"></p>
<p>随意框选一下绑好的骨骼,随后左侧填写好参数,点击“Create mirror object”即可。</p>
<p>然后就是这个样子。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/95e24d19-0725-4fb2-b33b-6ab4fdcbec95.jpg"></p>
<p>另外视频最后一部分是关于权重和物体质量的,这个了解下操作方式和代表意义即可,不用真的做一遍,因为后面 Quick rigging tool 会帮你弄。</p>
<p>---</p>
<p>另外说一下用 Quick rigging tool 的流程和一些技巧。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/c4a02691-db6a-4650-b748-6365554b4cb7.jpg"></p>
<p>首先建议把tool放在右边,然后把模型摆在左边,这样你在tool的骨骼树里每点击一个东西,左边都会给你高亮,方便你对应。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/0cf94fd9-c0a4-4cc4-9385-f8bac568c7dd.jpg"></p>
<p>弄好之后点下面那个“Create prototype rig”生成绑定原型。</p>
<p>这个是什么意思呢?就是说绑定分两个阶段,一个是原型,这时候你在Point controller mode里看到的rig是蓝色的,你改变他的位置,旋转他,不会有联动的效果。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/4c24e727-b579-4d7b-b7ee-c21860ef5595.jpg"></p>
<p>等你点了Create rig之后,这时候会生成真正的rig,这时候就是黑色了。</p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/a2258b22-18c4-499a-9899-b9b604bfce02.png"></p>
<p><img alt="img" src="/static/20210724-Cascadeur骨骼绑定简单入门笔记.assets/bd299d7f-8ac3-49ba-9eb8-ee4fda1741fa.jpg"></p>
<p>这时候基本就不能反悔,角色也会动起来了。</p>
<p>所以建议是create rig之后,先试着动一动关节看看是不是那个效果,如果不行的话立即ctrl+z撤销至create rig之前,再作调整,如此反复。</p>
<p>所以也建议分多个阶段存盘,例如原型rig阶段,刚绑定好之后,开始k动作之类。</p>
<p>简易FAQ:</p>
<p>\1. 为啥一定要用 Quick rigging tool?</p>
<p>为了使用AutoPosing tool,这非常重要。</p>
<p>\2. 删除骨骼</p>
<p>框选你要删除的骨骼,到Rigging tool中点击“delete rig element”</p>
<p>原型骨骼一般都能删除,但是create rig之后有些时候删不掉,不要问为啥,我也不知道。</p>
<p>\3. create rig 报错 但是搞不懂 也搜索不到</p>
<p>非常简单,如果报错信息中有提到哪个骨骼,那把对应的骨骼部分删了再试,如果删了之后create rig成功了,重做这部分即可。</p>
<p>必要时候重做整个rig。</p>
<p>\4. Quick rigging tool 要求身体部位名字唯一,但是有的部分没有怎么办?</p>
<p>这个跟建模的工作流有关。</p>
<p>比如我上面绑定的模型就是指头部分没有区分命名,左右手的几个指头一样名字,导致后面rig创建不了。</p>
<p>我不可能给他改名字,因为好多个模型所有的骨骼名字是一样的,改一个就要改所有,非常麻烦。</p>
<p>因此首先如果能找到建模师,将他捶一顿要求他改名就行了。</p>
<p>如我退求其次的话,就把手指的自动绑定删掉,先把rig生成了,然后用普通的Rigging tool把手指弄出来就行了。</p>
<p>这样也能用auto pose</p>Unity 人物动画转换 - humanoid / generic 互转2021-07-22T11:18:00+08:002021-07-22T11:18:00+08:00魏远tag:None,2021-07-22:/posts/unity-ren-wu-dong-hua-zhuan-huan-humanoid-generic-hu-zhuan.html<h3>前言</h3>
<p>先说说什么是humanoid,还有为什么要做转换。</p>
<p>首先,现在常用的人物模型,虽然人物的骨骼和命名不尽相同,但都提供了一组通用的数据来表明人形身体上某些固定的部位分别对应什么骨骼。unity可以读取这组数据来完成自动的rig绑定。</p>
<p>因此拥有humanoid数据的人物模型,即使所有骨骼的名字都不同,人物动作也可以共用。</p>
<p>其次,为什么做转换?</p>
<p>第一个是一些使用unity为工具链的引擎,例如laya只支持generic动画,所以要转换。</p>
<p>第二个是一些软件修改模型会导致humanoid数据丢失变成了generic类型,但是你又想套用一些humanoid动画上去,所以就要拿未丢失humanoid数据的模型给那些动画做一个转换。</p>
<h3>具体操作</h3>
<p>需要插件 Animation Converter</p>
<p>需要两个相同骨骼的FBX模型(可以是同一个复制两份)。</p>
<p>第一个FBX模型的Animation设置为Humanoid(需要从3D软件导出时带Humanoid信息)。</p>
<p>第二个设置为Generic。</p>
<p><img alt="img" src="/static/20210722-Unity人物动画转换:humanoid、generic 互转.assets/1d9061f2-4a9d-430f-bbaf-6825d610ceb7.png"></p>
<p>打开转换器,先将这两个模型拖到最底下的Input和output,然后修改Output中的选项,并将需要转换的动作拖进Input。</p>
<p><img alt="img" src="/static/20210722-Unity人物动画转换:humanoid、generic 互转.assets/1925c5fb-42b3-46af-9591-d9d54d92fda5.png"></p>
<p>最后点击Convert即可,转换后的动画会输出在Output Directory目录。</p>Blender三角化在制造棱角时的一个应用技巧2021-06-18T19:15:00+08:002021-06-18T19:15:00+08:00魏远tag:None,2021-06-18:/posts/blendersan-jiao-hua-zai-zhi-zao-leng-jiao-shi-de-yi-ge-ying-yong-ji-qiao.html<p>比如我们有一块砖,然后在砖头上环切一刀,做个缺角来增强真实感。</p>
<p><img alt="image-20210801180743674" src="/static/20210618-Blender-三角化在制造棱角时的一个应用技巧.assets/image-20210801180743674.png"></p>
<p>例如这里</p>
<p><img alt="img" src="/static/20210618-Blender-三角化在制造棱角时的一个应用技巧.assets/798e1912-b245-4890-bcca-9b2ce6f8d6ab.jpg"></p>
<p>我们通过bevel压下这个点</p>
<p><img alt="img" src="/static/20210618-Blender-三角化在制造棱角时的一个应用技巧.assets/a4cb9d01-205d-4537-b4c1-65c7848c885b.jpg"></p>
<p>然后ctrl+T</p>
<p><img alt="img" src="/static/20210618-Blender-三角化在制造棱角时的一个应用技巧.assets/7b6c7cce-b476-43dd-a4ed-d76560a0339a.jpg"></p>
<p>这样一个砖缝就做好了。</p>六月。天气渐渐热了2021-06-07T00:20:00+08:002021-06-07T00:20:00+08:00魏远tag:None,2021-06-07:/posts/liu-yue-tian-qi-jian-jian-re-liao.html<p>时间来到了六月,夏日。</p>
<p>对我来说,正是踢了被子冷,盖好被子热的时节。</p>
<p>这就导致我梦中无意识的掀开盖上被子,所以精神就很差。</p>
<p>如果不是着凉,就是上火,这也算是我转季节的保留节目罢。</p>
<p>最近一周在接发行给的SDK,说实话写的不是很好(比如我们这种项目结构就完全没有考虑到)。</p>
<p>但是能用。</p>
<p>我渐渐觉得“能用”可能才是一种常态,我之前理解的不对。</p>
<p>我发觉我可能被开源社区那些明星项目给惯坏了,那些无一不是简单易用、文档详实、考虑周到、用户众多、代码优雅、架构完善。</p>
<p>这就导致我对这种“能用”的代码很陌生,不会用。</p>
<p>但是我觉得这种东西可能能够培养一种信心,因为你要接好这个SDK真的是需要坚持的。</p>
<p>当你接不好的时候你就会感到怀疑,</p>
<p>怀疑你的代码有问题,怀疑步骤有问题,怀疑框架有问题,怀疑SDK有问题。</p>
<p>但是你也不知道究竟是TMD哪有问题。</p>
<p>所以就感到怀疑人生。</p>
<p>所以你就必须相信。</p>
<p>相信自己一定能弄好,相信有demo可以我也可以,相信那个代码质量比较一般的SDK。</p>
<p>谨向人类文明的拓世者们献上崇高的敬意,因为这确实不容易。</p>
<p>等真的弄好了,好像确实挺简单的。</p>
<p>然后我看着自己码的游戏,被发行的SDK贴了一堆五颜六色的小广告,也很难形容是一种什么样的感受。毕竟工作完成了吧 …</p><p>时间来到了六月,夏日。</p>
<p>对我来说,正是踢了被子冷,盖好被子热的时节。</p>
<p>这就导致我梦中无意识的掀开盖上被子,所以精神就很差。</p>
<p>如果不是着凉,就是上火,这也算是我转季节的保留节目罢。</p>
<p>最近一周在接发行给的SDK,说实话写的不是很好(比如我们这种项目结构就完全没有考虑到)。</p>
<p>但是能用。</p>
<p>我渐渐觉得“能用”可能才是一种常态,我之前理解的不对。</p>
<p>我发觉我可能被开源社区那些明星项目给惯坏了,那些无一不是简单易用、文档详实、考虑周到、用户众多、代码优雅、架构完善。</p>
<p>这就导致我对这种“能用”的代码很陌生,不会用。</p>
<p>但是我觉得这种东西可能能够培养一种信心,因为你要接好这个SDK真的是需要坚持的。</p>
<p>当你接不好的时候你就会感到怀疑,</p>
<p>怀疑你的代码有问题,怀疑步骤有问题,怀疑框架有问题,怀疑SDK有问题。</p>
<p>但是你也不知道究竟是TMD哪有问题。</p>
<p>所以就感到怀疑人生。</p>
<p>所以你就必须相信。</p>
<p>相信自己一定能弄好,相信有demo可以我也可以,相信那个代码质量比较一般的SDK。</p>
<p>谨向人类文明的拓世者们献上崇高的敬意,因为这确实不容易。</p>
<p>等真的弄好了,好像确实挺简单的。</p>
<p>然后我看着自己码的游戏,被发行的SDK贴了一堆五颜六色的小广告,也很难形容是一种什么样的感受。毕竟工作完成了吧。</p>
<p>只能说,毕竟工作完成了。</p>
<p>对与错的准绳不是恒定的,我也难以评价这种行为。</p>
<p>这值得吗?</p>Wellcome Back2021-06-06T23:50:00+08:002021-06-06T23:50:00+08:00魏远tag:None,2021-06-06:/posts/wellcome-back.html<p>曾几何时,一个博客项目算是web入门标配。</p>
<p>现在已经不流行这个啦。</p>
<p>我也从“我要自己写一个” 变成了 “呜呜有大佬做过吗我想用” 这样子。</p>
<p>也确实,没有必要。</p>
<p>大佬们也很厉害。</p>
<p>以前开过博客,但是都不怎么认真写。</p>
<p>但我终于还是想要写点什么,所以从一堆吃灰的域名中捡了一个转到外网。</p>
<p>相比专栏,博客算是一种不那么严肃的形式吧,我觉得挺好的。</p>
<p>是时候重新出发了。</p>从2015到2018,WEB与时光与我2018-01-10T04:01:00+08:002018-01-10T04:01:00+08:00魏远tag:None,2018-01-10:/posts/cong-2015dao-2018webyu-shi-guang-yu-wo.html<h3>故事</h3>
<p>新的一年如约而来又不期而至,在感叹又年长了一些的同时,也是时候再来一次一年一度的告别与再见的仪式了。</p>
<p>我是一个有些执着于“记录”的人,从开始接触编程到现在有许多年了,过去的代码却还大多能找到,但是很散乱。于是从2015年起,我有了一个新的习惯:每年按照年份创建一个文件夹,在这一年中无论是郑重开坑,还是要做点繁重些的杂务,都在这个年份文件夹里创建一个目录来存放源码。当然太简单的代码用过就不必储存,基本上都随着IEP的关闭一起消失在茫茫内存之中了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqpniMjUOxT9Jaz1v41jyAqJYh_J-std"></p>
<p>但是这样仍有些乱糟糟的,后来为了直观看出这些项目谁先谁后,于是在文件夹前面加了序号,成了“XX.项目名”这样的格式。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmfEbmeN7V2iugddI5t-1jm0fKAW-std"></p>
<p>每一年开年的时候,就是和上一年的大部分代码说再见的时候了。创建好新一年的文件夹之后,我会挑选一些上一年的项目。如果项目还在写的话,我就把那目录复制一份到新一年的文件夹里,旧的就永远定格在那一天,这就是我的告别与再会。我觉得时光是一个有些神秘的词汇,而这些代码文本在一定程度上描绘了属于过去某段时光的我,因此也好像也具有了些别样的意义。可能大部分人都希望回到从前,但现实是只能在时光易逝的无奈中向前走着。而在翻阅这些目录的同时,也仿佛在审视自己的过去。</p>
<h3>2015年</h3>
<p>我们先从有记录的最早一年开始。2015年在web领域算是一个转折点,后知后觉地,我们可以说ES6的正式发布算是一个标志性事件,从此以后前后端分离成为主流的开发方式。但是事实上整个生态链的成熟更加功不可没,今天流行的众多框架和工具都在2014年左右立项,并在2015年基本成熟,包括Webpack …</p><h3>故事</h3>
<p>新的一年如约而来又不期而至,在感叹又年长了一些的同时,也是时候再来一次一年一度的告别与再见的仪式了。</p>
<p>我是一个有些执着于“记录”的人,从开始接触编程到现在有许多年了,过去的代码却还大多能找到,但是很散乱。于是从2015年起,我有了一个新的习惯:每年按照年份创建一个文件夹,在这一年中无论是郑重开坑,还是要做点繁重些的杂务,都在这个年份文件夹里创建一个目录来存放源码。当然太简单的代码用过就不必储存,基本上都随着IEP的关闭一起消失在茫茫内存之中了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqpniMjUOxT9Jaz1v41jyAqJYh_J-std"></p>
<p>但是这样仍有些乱糟糟的,后来为了直观看出这些项目谁先谁后,于是在文件夹前面加了序号,成了“XX.项目名”这样的格式。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmfEbmeN7V2iugddI5t-1jm0fKAW-std"></p>
<p>每一年开年的时候,就是和上一年的大部分代码说再见的时候了。创建好新一年的文件夹之后,我会挑选一些上一年的项目。如果项目还在写的话,我就把那目录复制一份到新一年的文件夹里,旧的就永远定格在那一天,这就是我的告别与再会。我觉得时光是一个有些神秘的词汇,而这些代码文本在一定程度上描绘了属于过去某段时光的我,因此也好像也具有了些别样的意义。可能大部分人都希望回到从前,但现实是只能在时光易逝的无奈中向前走着。而在翻阅这些目录的同时,也仿佛在审视自己的过去。</p>
<h3>2015年</h3>
<p>我们先从有记录的最早一年开始。2015年在web领域算是一个转折点,后知后觉地,我们可以说ES6的正式发布算是一个标志性事件,从此以后前后端分离成为主流的开发方式。但是事实上整个生态链的成熟更加功不可没,今天流行的众多框架和工具都在2014年左右立项,并在2015年基本成熟,包括Webpack、Vue.js、AngularJS 2,而React和TypeScript则要更早到2013年。</p>
<p>值得一提的是Babel.js也是这一年改为这一名字的(以前叫6to5),我认为这是JS今日局面的最大功臣。在这之前,由于老JS实在是不堪用,各种转译器群雄并立,纷纷割据一方,整个前端的局面是狼烟四起,山河破碎,并在一定程度上波及了使用nodejs的后端。</p>
<p>然而在这一年之后,在Babel.js这面旗帜之下,全新面貌的JavaScript可以算得上是一扫寰宇,无人可敌,于是几乎所有转译语言都滚蛋啦。毕竟有着一个正统的名号,又很是Pythonic,那些转译器就算再来十个也不够打的。</p>
<p>不过在这之前呢?我们还是回到新旧交替的2015年吧。先来看看这一年的我。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FuayaZFhuWVz__0c89zyLfKPHzP_-std"></p>
<p>2015年的我前后开了45个小坑,多与web有关,其中大概有一半是各种练习和试做。真正认真下些功夫的恐怕也就一掌之数,其中一些项目在未来的几年还会多次见到。有些项目在历年的变化也反映了相当有趣的结果……</p>
<p>我学会的第一个python web框架是web.py,在上古的时代,PHP很稳定的占据了沉默的多数,而ROR则是潮流的代表,一时名声大噪。而python这边,则是Django、Tornado、Flask、Bottle、Web.py几家纷争,当然了,今天我们仍然能看到它们,而且还有一些新的小伙伴。</p>
<p>不过在那些古老的时代,基本要么使用Django全家桶,要么使用一套经典的组合方案:Web框架、ORM、模板引擎。Web框架在除了Django的几个里面选,ORM基本上就是SQLAlchemy,模板引擎则是以mako和jinja2为代表。总之大家都是MVC架构,不过我更喜欢Django的MVT的叫法(Model View Template),尽管我不太喜欢Django。</p>
<p>因此在2015年的前半部分,我仍然是延续了这样经典的思路,不过这个时候我对这一套已经比较熟练了,也基本上形成了自己的习惯,那就是 SQLAlchemy + Tornado + Mako 三件套,为此还搞了一个cli,也就是在图中出现的第26个项目 fpage 了。https://github.com/fy0/fpage</p>
<p>我们找个典型来看一看:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FpTxUxw8s08BCumq3Pui9-H749kN-std"></p>
<p>model</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FuqTl37AyJVgWqCRKWSmCsAFt28J-std"></p>
<p>view</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FhUMUWhHqSNin7cycFhu1SC3fIP2-std"></p>
<p>static</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FjI8P5vGMvln-8LoK5fr7zpQupSK-std"></p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fn3RlM9gtCScDUTyr3sK_DIu4ohw-std"></p>
<p>template</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FtA2DUbuxpfaWWhSiJKnKQTwtnS1-std"></p>
<p>这就是标准的MVT模式了。可以看到,这一年的我还在使用Python2,不过也同时考虑了对Python3的支持。而前端方面这时我还在使用 Coffeescript …… 唉,往事不堪回首啊。这一时期,前端在某些程度上从属于后端,我这里的html文件中实际上有很多模板语法,但他们是由后端往里传值,并进行渲染的。当然了,模板中少量的逻辑代码也遵从 Python 语法。</p>
<p>而在下半年,我接触到了Vue.js,同时也摸了摸TypeScript。TS于我有如擦肩而过,终于不再相见,而Vue.js却相伴至今。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fl5RoAVHwkTHjpAPxTQIWlO1MY5O-std"></p>
<p>这就是2015年了。</p>
<h3>2016年</h3>
<p>可能跟我在这年潜心学习,颇有一点两耳不闻窗外事的意思有关,这一年好像没发生什么大事,Web届世界和平。我在这一年自然而然的上了 Vue.js 全家桶的船,从此不做……不对,从此又一次跟上了大部队,体验了一些新时代的新魔法。ES2015 stage-2 深得我心,从此再也离不了 async await 语法。</p>
<p>我这边过渡到了前后端分离的开发方式,想必看起来风平浪静的业界应该也是如此吧。感觉这一年大家对于MVVM框架的讨论变多了,而我使用的 Vue 和 webpack 也纷纷进行了大版本的更新,它们都在这一年得到了很大成长。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fhv7VGqLQi9l6_01H8QehvSpUHNB-std"></p>
<p>这一年的目录一共是32个,我也在Web之外接触了一些新的领域。第一项的tinyre也是一个我的老项目,这是一个3千行左右c99实现的正则引擎,不过实际上之前一直都处于很多bug的状态,2015年末到2016年差不多重写了一遍,补了很多test,基本可用了。不过作为学习项目,其实还是非常僵硬的,执行效率相当的差……</p>
<p>还是回到Web上来,这一年我们抓的典型是22号选手,叫做SingleNote的,也就是我的博客程序的初始名字了。但后来一想,这名字不甚吉利,于是改成了StoryNote。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FpfcY6n5T2XE5t_PZLRvHPv5gJsF-std"></p>
<p>这是一个前后端分离的项目,其实很多时候会选择建立前端后端两个repo,对于公司项目的话无可厚非,但个人项目来说我觉得太过麻烦了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqLipxP7YX1rnB6gswqkIvPiTjAQ-std"></p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fm9n1WIDn036MfNlKUvW9_3l0qU5-std"></p>
<p>可以看到,后端基本上还是那个样子(这也是从fpage生成出的),主力是 tornado + peewee(另一个ORM框架),但是实际上这时候后端已经只提供API接口了,templates目录虽然存在,但并没有实际用到。另外可以注意到,这个时候只有py3的临时文件,我已经完全迁移到py3,而且不再考虑向下兼容。
前端则是vue-cli生成出来的,形状也比较典型。不过实际上,这个项目在这一年并没有写完……而是鸽置到了下一年。</p>
<h3>2017年</h3>
<p>终于来到了刚刚过去的2017年。</p>
<p>这一年Web领域比较大的事情算是有一件,那就是React的协议之争。我等吃瓜群众隔岸观火,场面有来又回也不算十分劲爆。当然最后以Facebook的屈服告终,删了那个 PATENTS了事。但是虽然React的专利授权虽然被删了,但是Facebook很多其他项目里还有这个,其中就有我很看好的YogaLayout,这让我有些心有戚戚焉。</p>
<p>而对我来说的变化,首先是由于觉得不切实际,把序号从三位数改成两位数。另外随着对新的编程套路的熟悉,又有了一些新的大胆的想法。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FoC0wwIhsvAwRxuSgiXyR2GED8fk-std"></p>
<p>话说大家应该也能看出来,由于每年前几个项目都是从上一年搬过来的,一般代表了一种美好的祝愿。然而现实是冰冷残酷的,老项目往往遭逢暂时鸽置的命运,人在江湖,身不由己啊。</p>
<p>这一年大概在年中的时候产生了一些新的思路,大致就是感觉现在的后端写着没什么意思,就是一些增删改查。这样一来我还不如后端只控制一下用户权限,把查表的操作让渡给前端。这样数据表直接对应api,建表后直接就能开始写前端,爱查什么查什么。</p>
<p>啊,还画了一个草图。不过既然掏不出干货,那贴图就只是象征性的,字也很丑大家不必细看。
<img alt="" src="//fy0-me-bucket.slgame.org/FvLxrZWOWrfcSBpwM8kOWH3quXjX-std"></p>
<p>总之这一想法下的产品就是一个基于aiohttp的框架slim,还有一个用于验证框架可行性的实践项目Icarus:</p>
<p>https://github.com/fy0/slim</p>
<p>https://github.com/fy0/Icarus</p>
<p>目前他们都在开发阶段,而且没文档没测试,所以基本没什么参考价值。我现在也顾不上写文档,当然等写好了之后,我会郑重一些介绍这两个项目的。</p>
<p>话说Icarus这个项目可能看了前文的人会觉得眼熟……没错,这就是2015年已经出现的那个项目,不过后来被用新的技术方案完全重写了(还没写完,不过老方案其实也是半成品)。我们还是用这个项目再举个例子:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FsCU4KMd6Tkxwt_dSyVrWQPtL-Lj-std"></p>
<p>可以看到是一个典型的 vue-cli 模板项目了。有人可能问那后端呢?后端在backend目录里,这个样子:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fl9EiNtoQPy6N4IXBejA0ZIKe5GU-std"></p>
<p>真是越混越惨了。15年的时候前端模板从属于后端,16年平分秋色,17年完全反过来了,唉。</p>
<h3>2018年</h3>
<p>刚开年,就一图流吧。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmfePsKYvHzCgVgcD8q9JKqa8Y-c-std"></p>
<p>希望这几个项目不要继承前辈的光荣传统,当然我也会努力更新的!</p>2017总结:送君千里,终须一别2018-01-01T00:01:00+08:002018-01-01T00:01:00+08:00魏远tag:None,2018-01-01:/posts/2017zong-jie-song-jun-qian-li-zhong-xu-yi-bie.html<h2>开篇</h2>
<p>年少的时候,常常见到《写给十年后的自己》这样的作文题目,不知不觉,离那时却真的已有十年了。十年前的我带着对未来的迷茫和少年的无畏开始了高中生活,而十年后的我却早已忘记当年的梦想是什么。命运的无状借由时间之手刻在每一个人的身上,我们对新年的所想也渐渐从对未来的期待到对失去的恐惧。不过,应该只是一些平凡的想法吧,我向来所求不多。</p>
<p>在过去的一年里,果然年计划又不负众望的没有完成。不过好像比去年稍微好了一些?这也未必啊,去年可是搞了python lite解释器这样听起来吊吊的项目(虽然也暂时鸽置了)。</p>
<p>下面是年终总结:</p>
<h3>游戏</h3>
<p>游戏方面,首先是果断的交出了 Dota2 会员年卡,还是豪华版的,而且还又肝又氪的打出了小精灵至宝。不过老实说,对于TI7的结局我还是比较失望的。</p>
<p>另外通关了《晶体管》,总体来说是个不错的游戏。其他通关游戏还包括《迈阿密热线》两作和育碧的《看门狗》,以及一些在steam没有挂名的小游戏。</p>
<p>没有通关的游戏有《8 bit armies》(只打穿了一线任务)、《observer》(实在太晕了)。</p>
<p>此外还参与评测了《汐 Shio》、《飞虎队 …</p><h2>开篇</h2>
<p>年少的时候,常常见到《写给十年后的自己》这样的作文题目,不知不觉,离那时却真的已有十年了。十年前的我带着对未来的迷茫和少年的无畏开始了高中生活,而十年后的我却早已忘记当年的梦想是什么。命运的无状借由时间之手刻在每一个人的身上,我们对新年的所想也渐渐从对未来的期待到对失去的恐惧。不过,应该只是一些平凡的想法吧,我向来所求不多。</p>
<p>在过去的一年里,果然年计划又不负众望的没有完成。不过好像比去年稍微好了一些?这也未必啊,去年可是搞了python lite解释器这样听起来吊吊的项目(虽然也暂时鸽置了)。</p>
<p>下面是年终总结:</p>
<h3>游戏</h3>
<p>游戏方面,首先是果断的交出了 Dota2 会员年卡,还是豪华版的,而且还又肝又氪的打出了小精灵至宝。不过老实说,对于TI7的结局我还是比较失望的。</p>
<p>另外通关了《晶体管》,总体来说是个不错的游戏。其他通关游戏还包括《迈阿密热线》两作和育碧的《看门狗》,以及一些在steam没有挂名的小游戏。</p>
<p>没有通关的游戏有《8 bit armies》(只打穿了一线任务)、《observer》(实在太晕了)。</p>
<p>此外还参与评测了《汐 Shio》、《飞虎队:空战中国 FLYING TIGERS: SHADOWS OVER CHINA》、《电竞俱乐部 ESports Club》、《Your Smile Beyond Twilight:黄昏下的月台上》、《走向光明 To The Light》、《波西亚时光 My Time at Portia》、《启蒙 Enlightenment》、《Vilmonic》等一些游戏,其中一部分游戏经历并不那么的让人愉快……玩完了还要写一篇长文去吐槽。</p>
<p>这一年倒是没有很多的剁手操作,毕竟现有的也玩不完。其实我觉得很多时间被浪费了,还不如用来玩游戏。</p>
<h3>技术</h3>
<p>我想技术水平方面没有太大的提升,只是接触了一些新的东西,但不如上一年那样触摸新的领域给人更多的震撼。</p>
<p>这一年我本来是想基本上同web后端作别的。年中的时候我设计了一个 sql 数据库转 web api 的框架,计划写好以后只需要建建表,配置一下权限就搞定了。本来计划用半年时间写完框架 + 文档 + 测试,再基于框架写一个项目来实战检验。结果文档没有,测试没有,附属项目也没写完,好气啊。</p>
<p>还有就是稍微摸了摸esp8266 stm32,买了一个国产的 Orange Pi(发现很热)、还有玩了一下树莓派,基本上就是arm全家桶的样子吧。想我上一个类似设备还是2012或是13年买的cubieboard1,现在出这板子的方糖科技好像已经有点凉了……</p>
<h2>结尾</h2>
<p>希望新年能够好一些吧!年内一定要完成各种计划!再见了,2017!</p>记一次艰难的老版本VS完全卸载(VS2013)2017-12-31T02:12:00+08:002017-12-31T02:12:00+08:00魏远tag:None,2017-12-31:/posts/ji-yi-ci-jian-nan-de-lao-ban-ben-vswan-quan-xie-zai-vs2013.html<h3>山雨欲来</h3>
<p>尽管被很多人戏称为“宇宙第一IDE”,但是Visual Studio在我印象中却一直不是最佳选择,这个老朋友我觉得只能说“还不坏”,不过正在变得越来越好。其实很多人可能不知道,VS的进化也是一个循序渐进的过程,从VS2012版本奠定了新的界面形态之后,后面不知道是不是冥冥之中自有天意,解决了好几个我吐槽过的问题:比如说没有在同名头文件源文件切换的快捷键(就像是vim的A插件,现在有了,Ctrl+K, Ctrl+O连按),比如丢人的C/C++新标准支持程度(VS2013以降有很大改观),比如太慢的启动速度和太臃肿的安装包(VS2017大为改善),还有比如不像是IDEA一样有重构功能和快速修复(VS2017最近也有了重构和Alt+Enter)等等。</p>
<p>我对VS的使用基本上是每隔两个大版本更新一次,几年来我的主要使用经历是:VS2008、VS2012、VS2017。那大家肯定会注意到了,说我胡说,因为标题上明明写的是VS2013卸载啊!这其实就有个说法了,其实原因是我在那段时间换了电脑。我原本VS2012 express用了好几年,感觉自己萌萌哒,结果换了电脑之后重新装VS,发现微软偷偷把VS2012的页面藏起来了,也不知道是为什么。装上2013之后我明白了,这明明就是VS2012 update5嘛!估计是怕自己改版本号冒充新版本的事情被人发现 …</p><h3>山雨欲来</h3>
<p>尽管被很多人戏称为“宇宙第一IDE”,但是Visual Studio在我印象中却一直不是最佳选择,这个老朋友我觉得只能说“还不坏”,不过正在变得越来越好。其实很多人可能不知道,VS的进化也是一个循序渐进的过程,从VS2012版本奠定了新的界面形态之后,后面不知道是不是冥冥之中自有天意,解决了好几个我吐槽过的问题:比如说没有在同名头文件源文件切换的快捷键(就像是vim的A插件,现在有了,Ctrl+K, Ctrl+O连按),比如丢人的C/C++新标准支持程度(VS2013以降有很大改观),比如太慢的启动速度和太臃肿的安装包(VS2017大为改善),还有比如不像是IDEA一样有重构功能和快速修复(VS2017最近也有了重构和Alt+Enter)等等。</p>
<p>我对VS的使用基本上是每隔两个大版本更新一次,几年来我的主要使用经历是:VS2008、VS2012、VS2017。那大家肯定会注意到了,说我胡说,因为标题上明明写的是VS2013卸载啊!这其实就有个说法了,其实原因是我在那段时间换了电脑。我原本VS2012 express用了好几年,感觉自己萌萌哒,结果换了电脑之后重新装VS,发现微软偷偷把VS2012的页面藏起来了,也不知道是为什么。装上2013之后我明白了,这明明就是VS2012 update5嘛!估计是怕自己改版本号冒充新版本的事情被人发现,于是就把2012版本神隐了。</p>
<p>而这个VS2013也在刚刚安装好的第二天就从预装的Win8搬家到了Win10(通过升级通道升级,注意此处为伏笔),而且也能正常使用。不过我很快看到VS2017的新特性介绍,看到那个启动速度对比就一阵激动,于是迅速安装了新的VS2017,自此就没有用过旧版本了。如此相安无事了很久,直到有天我看着开始菜单感觉怪怪的:
<img alt="" src="//fy0-me-bucket.slgame.org/Fs763YuGDYbVm9ykUWa5Wt9Khd9U-std"></p>
<p>话说这VS2013占地这么久了,长期没有使用,是不是也该动一动?</p>
<h3>牛刀小试</h3>
<p>嗯,于是说走就走,按照经验,这东西在控制面板里就能卸载。
<img alt="" src="//fy0-me-bucket.slgame.org/FpHILdZE2IiWz5LJeeFe1SlvkR5i-std">
然而事情并没有这么简单,当发现控制面板中空无一物的时候,我的内心也是茫然的。此事看来大有蹊跷!果然,后面的经历也是一波三折。</p>
<h3>再接再厉</h3>
<p>于是我四处搜索解决方案,发现好像还有很多人问相同的问题。我看到老司机说:“你去找个安装程序,就可以卸载了”。</p>
<p>然后我下了一个VS2015安装包,好几个G,结果打开之后告诉我不行,版本不兼容。看来必须要跟之前版本完全一样的,然后又一番折腾:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FnRHCrxiDa5jddFIN9DsFJI4GC-A-std"></p>
<p>一试之下,才知并非如此,看来我是遇上了假死机,也不知道之前的兄弟们如何解决了问题。总不能是重装系统大法吧?</p>
<h3>陷入僵局</h3>
<p>这时候通过了解,我大致了解到之所以没有卸载的程序的原因是部分注册表信息被搞丢了,嫌疑最大的操作就是系统升级。我不由得暗骂一声晦气,这从Win8升到Win10,Win10也滚了红石和红石创意者更新两个大版本,谁知道这中间发生了什么?不过这也于事无补了,只能继续想办法。</p>
<p>第三个找到的解决方案是通过wmic找到相关的包,然后逐一按照卸载。过程就不讲了,因为我最终也没用这个办法。为什么呢?与VS2013相关的大概有几十个包,我在这试着卸载了一个包之后,觉得不靠谱:这路子太野,而且悄无声息的,谁知道是真的卸载了,还是只把注册信息删了?如果只是后者,那等我找到真的解决方案的时候,线索不也因此毁掉了吗?</p>
<h3>柳暗花明</h3>
<p>然而我没想到的是,路子最野的其实还是微软自己。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FkYUW_ps9JCzjBM7mjIbZqLFzJ6o-std"></p>
<p>https://www.zhihu.com/question/25104894/answer/179157353</p>
<p>微软为了解决这个问题,前后开发了四代工具!这下终于可以让专业的来了:</p>
<p>https://github.com/Microsoft/VisualStudioUninstaller</p>
<blockquote>
<p>Visual Studio Uninstallation sometimes can be unreliable and often leave out a lot of unwanted artifacts. Visual Studio Uninstaller is designed to thoroughly and reliably remove these unwanted artifacts. </p>
</blockquote>
<p>看看这描述,完美符合需求。</p>
<h3>图穷匕见</h3>
<p>二话不说把这程序搞下来,然后就开始了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmkgSVfcJSaCmf1jzPVvAmOMxXmE-std"></p>
<p>嗯?这么快就180/202的进度了?这不是再有20个包就卸载完了么。定睛一看,发现这个计数是倒着的。然后就是漫长的等待。</p>
<p>开始时间大概是晚上9点45分</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FihNLI92plbnwz-lfyjQAUA3hCGF-std"></p>
<p>终于,在11点19分的时候,最后一个包也被卸载掉了。历时94分钟,还是安装在SSD盘的情况下。好吧,毕竟VS,我们都习惯了的。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fq6UNaa6JgSNc6-P3_ehIiKXLVLF-std"></p>
<p>还是微软工程师仗义,解我磁盘空间倒悬之急啊!卸载程序都不安生,这次的经历可以说是非常的智障了。</p>一个软件工程师的焊接入门笔记2017-12-30T23:12:00+08:002017-12-30T23:12:00+08:00魏远tag:None,2017-12-30:/posts/yi-ge-ruan-jian-gong-cheng-shi-de-han-jie-ru-men-bi-ji.html<h3>前言</h3>
<p>从小到大,我几乎从未碰过电烙铁。对那些复杂的电子元件和电路板,我向来是敬而远之的。虽然作为一个软件工程师,我清楚地知道自己写的程序就是借助电流在它们之上跑来跑去从而对现实世界造成影响,不过绝大多数情况下,我都是在屏幕之内做文章。然而凡事总有例外。那天,我买了一片GSM开发板打算用来插副卡收辣鸡短信,收到货发现是一片PCB板,带两个单独的 10 Pin 排针,这意味着我要么选择退货,要么就得自己动手焊接了。</p>
<p>当我犹豫了半天,最后还是决定把它搞好的时候,这样的故事就拉开了序幕。虽然也只是一点微小的工作,我完成了曾经觉得自己不太可能的做的事情。有些让人意外的是,这并不像是我曾以为的那样困难。我对PCB焊接是完全的零基础,大学时代作为计算机专业也不知为何没有上过相关课程,反而是很多其他专业的同学有过这样的经历。不管怎样,这算是一项还算比较实用的技能吧,因此我将自己的入门经验分享一下,主要就是上手、简单的焊排针和抠元件,如果有问题请大家指正。</p>
<h3>准备工作</h3>
<p>首先要购买电烙铁、焊锡、电烙铁架、面包板(尽量大的那种),这是基本配置。电烙铁我买的是20多块钱30w的,100g一卷焊锡丝用了10块钱,感觉这能用好久了。再来一个六块五的烙铁架,用于挂烙铁和焊锡丝。面包板则是用于固定电路板的,通常电路板上排针都会设置在边缘而且对称,这样你将排针先插在面包板上 …</p><h3>前言</h3>
<p>从小到大,我几乎从未碰过电烙铁。对那些复杂的电子元件和电路板,我向来是敬而远之的。虽然作为一个软件工程师,我清楚地知道自己写的程序就是借助电流在它们之上跑来跑去从而对现实世界造成影响,不过绝大多数情况下,我都是在屏幕之内做文章。然而凡事总有例外。那天,我买了一片GSM开发板打算用来插副卡收辣鸡短信,收到货发现是一片PCB板,带两个单独的 10 Pin 排针,这意味着我要么选择退货,要么就得自己动手焊接了。</p>
<p>当我犹豫了半天,最后还是决定把它搞好的时候,这样的故事就拉开了序幕。虽然也只是一点微小的工作,我完成了曾经觉得自己不太可能的做的事情。有些让人意外的是,这并不像是我曾以为的那样困难。我对PCB焊接是完全的零基础,大学时代作为计算机专业也不知为何没有上过相关课程,反而是很多其他专业的同学有过这样的经历。不管怎样,这算是一项还算比较实用的技能吧,因此我将自己的入门经验分享一下,主要就是上手、简单的焊排针和抠元件,如果有问题请大家指正。</p>
<h3>准备工作</h3>
<p>首先要购买电烙铁、焊锡、电烙铁架、面包板(尽量大的那种),这是基本配置。电烙铁我买的是20多块钱30w的,100g一卷焊锡丝用了10块钱,感觉这能用好久了。再来一个六块五的烙铁架,用于挂烙铁和焊锡丝。面包板则是用于固定电路板的,通常电路板上排针都会设置在边缘而且对称,这样你将排针先插在面包板上,再把电路板轻轻放在上面,排针可以轻易穿过孔洞而且不会发生焊歪的情况。除此之外,助焊剂买了一小瓶,不买普通那种盒装的松香是因为印象中那玩意味道很重。另外就是万用表,一来这东西搞硬件本身就算是必备,二来测试两个相邻焊点是否连在一块。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FhSygteWh6EI6-Hbq22_649LtCMi-std"></p>
<p>这里稍微说下,电烙铁除了普通的这种,还有焊台卖,价格相比电烙铁就高多了。焊台一般是热风枪+可调温电烙铁的组合。其实对我们这种简单的需求来说,既不需要调温也不需要热风枪,更可能很长时间也焊不了两次,所以焊台就没有必要了。而且价格也是参差不齐,感觉里面水深,如果有需求的话还是不要图便宜。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FjhNlqo8veuv1EFQnlxHmBoSf0sa-std"></p>
<p>还有要买的就是练习用的洞洞板和排针了。这里说一下,可以看到排针有各种规格,首先是间距,这个我们用的大部分都是2.54mm没得说,树莓派啊各种派啊arduino啊都是这种,另一个就是几针,有什么1*2-10P/14P/16P/20P/40P好多种,看的人是一脸懵逼。我一开始买的是短的排针,结果收到货之后发现边缘有一些痕迹,一想估计也是拿长针剪的,而且也不好收纳,第二次就干脆买了一堆40P的长针了。</p>
<h3>到货,开工</h3>
<p>等了几天之后,买的东西到货了。由于都是理工专业,有几个同事以前玩过这个,我就请他们从旁指点几下,把家伙摆在桌面上就开搞了。为了避免对桌面造成影响,我找了个快递盒拆了铺在桌子上。除了我们在准备阶段提到的几样东西,还要拿一个小刀来,没有的话剪刀也行。</p>
<p>首先,把电烙铁接起来架在架子上等加热。不知道是不是普遍现象,新电烙铁第一次加热的时候,会有烟从烙铁头冒出来缓缓上升,同时烙铁头的颜色也会改变。听说烟是出厂时烙铁头上的松香被加热冒出来的,变颜色应该就是氧化了。</p>
<p>如无意外,你的烙铁头应该是一个圆锥形。烙铁头被加热后,裹一点锡上去,不用太多,尽量均匀。具体方式就是手抓着烙铁不动,然后另一手捏着焊锡丝去往烙铁头那个圆锥靠近尖端的部分上碰,一边碰一边旋转。如果温度足够,焊锡丝会在瞬间熔化并附在烙铁头上,如果没有如此的话,那就是两种情况:</p>
<ol>
<li>温度不够,再等一会</li>
<li>烙铁头氧化严重</li>
</ol>
<p>如果发生了情况2,烙铁头会变成古铜色、黑色等一看就不妙的颜色。这种情况在后面会很常见,解决办法是用小刀刮,没小刀用剪刀也行,不必断电。刮完了会看到银白色,这时候需要赶紧把焊锡丝贴上去,不然马上又会看到氧化过程。</p>
<p>熔化焊锡丝一样会有烟气冒出,不用惊慌,这也是因为现在的焊锡丝多数都内置松香,这烟还是松香的。第一次倒腾一个两三百度的东西难免有些紧张,这里还是提醒大家注意安全。</p>
<h3>焊排针</h3>
<p>现在差不多能正式操作了。不过在要用的电路板模块上胡搞之前,最好还是拿洞洞板练习一下。建议拿一小段排针剪成两三个一组这样焊。</p>
<h4>动手细节</h4>
<p>焊排针其实最重要的是手感,技巧方面只有一两个点。可能每个人方式有所不同,我说说我用过的两种。当然在此之前,我们先固定排针和PCB板子,当然这个就八仙过海各显神通了,我这里举个例子:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Ftd4rJBsUZB1bTkMVTk1YQJFPy89-std"></p>
<p>这是用面包板来固定的,当然这个时候焊接已经是完成状态了。</p>
<p>第一种是一个孔一个孔焊接,我采取的办法是在固定完成之后,把焊锡丝靠一点到排针上,烙铁头从焊锡丝那个方向靠近排针,这个过程会将焊锡丝熔掉。当然如果没有熔掉,先用小刀刮一刮。</p>
<p>再然后将烙铁头轻轻贴到排针和板子上——一般来说熔掉焊锡丝之后,一部分锡会粘在排针上,另一部分在烙铁上,这时候用烙铁头接触排针与板子上圈圈接触的部分,如果情况比较好,大约不到一秒的时间就能看到焊锡包裹在了排针上,多至两三秒也不是什么大问题。</p>
<p>通常我们可以观察到一个很有趣的现象,那是熔化的焊锡在板子的底圈上扩散成一整圈的样子。如果是铜制的底圈那么就更加明显,黄铜色的圈会在烙铁尖带着焊锡靠近的时候在瞬间被刷成银白色,有时还会有轻微的“嗤”的一声。而即使不是铜制的,也足以明显地观察到银色的圈荡漾了一下然后暗下去,剩余的焊锡会自动堆成半个球面,这时候烙铁尖靠着中间的针直直向上提,就能得到一个不错的焊点了。这也是我最喜欢的部分。</p>
<p>第二种办法的后半段与第一种相同,前半段则是将一段焊锡贴在排针侧面,然后用烙铁大致的点过去,这个点过去的过程基本上只是为了将焊锡丝黏在排针上面。但是注意,不要点在排针上,而是点在两个排针之间。因为灼热的烙铁头基本会在瞬间切断焊锡丝,切断之后,稍微向排针那边靠一下,焊锡丝就会有些黏在上面了。</p>
<p>另一个目的是用料平均,这个不说了大家都能领会,只要切断的位置合适基本就行。到这时候可能板面上很丑,可能切割出现了失误,可能有一小节焊锡丝掉了,这都是正常操作,没有关系。后面补救一下就可以了。</p>
<p>粘好了之后能空出拿焊锡丝的手,此时就能够一口气焊过去了。针对上面的失误情况,建议先把比较合适的先焊掉,后面再拽过焊锡丝重新处理剩下的。</p>
<p>那么怎么样的焊点是好焊点呢?首先自然能是够起到作用,另外不要短路,其次主要是焊锡的量要合适,最好能构成一个微微的凸包,而排针正插在凸包的最顶端,颜色明亮些就更好了。另外是不要对PCB板造成太大影响,焊点周围尽量干净一些。当然我是比较菜的,往往处理的不怎么漂亮,从图上也能看出来。</p>
<h4>错误处理</h4>
<p>基本上我们买到的元件都不会太过于难为人,也就是说即使是我这样的菜鸡也能比较轻松的处理,而且容错率是相当高的。我遇到的几个问题大概解法如下:</p>
<ol>
<li>熔不开焊锡丝或焊点</li>
</ol>
<p>首先确定电烙铁没问题,加热时间也足够,然后用小刀把烙铁头上的东西刮了,基本就很容易了。</p>
<ol>
<li>焊锡弄少了</li>
</ol>
<p>烙铁头半贴着排针底部,把焊锡丝向两者中间靠,如无意外是秒熔并顺着排针汇聚在底部。如果没有熔化,祭出小刀。</p>
<ol>
<li>焊锡弄多了</li>
</ol>
<p>仍然是用小刀把烙铁头弄干净,然后凑上去熔化那些焊锡,手里转一转尽量多裹一点在烙铁头上。之后拿到旁边刮掉,重复。</p>
<ol>
<li>仍然是焊锡弄多,相邻两针连一块了</li>
</ol>
<p>如果弄得实在太多,先用3里面的方式弄走一部分。随后用烙铁尖在两针连线的中间从切线方向划过,这样基本上可以分开。然后分而治之即可。</p>
<h3>抠元件</h3>
<p>偶尔也会有抠元件的必要,但是一些时候这需要热风枪、吸锡器等等工具的辅助。</p>
<p>不过情急之时,手里只有电烙铁的时候,也是能抠下一些简单元件的。</p>
<p>电烙铁抠元件的主要问题是受热不均匀。元件一般会被多处(>=2)引脚固定,由于焊锡很快就会凝固,你熔掉其中一边,再去熔另一边的时候,原来的位置很快就凝固了。这本来是焊锡的优势,此时却成了我们的阻碍。</p>
<p>通过这点我们也很容易认识到,如果引脚很多,干脆就直接放弃了。如果比较少,还是可以尝试一下的。</p>
<p>最初的时候我试图几个点来回熔,结果几乎没什么实际效果,只搞下来几个两脚的元件。</p>
<p>后来我几番尝试,领悟了一个“推”字诀。具体操作是这样的:</p>
<p>当你熔掉一个脚之后,找你顺手的位置用烙铁头轻轻推一下或者撬一下。然后熔下一个角。如此一轮之后,元件就很容易能被取下来了。当然还是注意,单个脚不要停靠太久。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqlOwU5TWyVl5U7Uh6isx5ugPWAk-std"></p>
<p>这里照例给一张。我当时急于拆下上面的AMS1117稳压模块用于ESP8266转接板,上面依稀可见的战火痕迹就是我来回试验的过程,可以很明显看到经过右侧一片狼藉的试炼之后,左边用对了技巧很轻松就弄下来了。当然这是另外一个故事了,感兴趣的话请翻阅前篇《ESP8266系列芯片入手指南》。</p>
<p>后来我想想原因,可能是这样的:尽管我们下意识觉得引脚是个相对较硬的东西(对比于焊锡),但是实际上并不是,金属本身也是有弹性的。之前我们的操作没什么效果的原因是熔了之后焊锡近乎没有移动位置就重新凝固了,如果我们使它的位置稍微移动一点,我们就稍微多一点机会。几番折腾下来之后,就比较容易弄下来了。至于是不是如此,我也就不清楚了……</p>
<h3>结尾</h3>
<p>这篇文章又是拖了好久好久,年末了也是千头万绪。文章前中后部分撰文时间隔了也挺久,但愿没有什么纰漏。希望给大家一些帮助吧。</p>esp8266 萌新三连:刷机(MicroPython)、亮灯、联网2017-11-12T20:11:00+08:002017-11-12T20:11:00+08:00魏远tag:None,2017-11-12:/posts/esp8266-meng-xin-san-lian-shua-ji-micropython-liang-deng-lian-wang.html<h3>前言</h3>
<p>书接上回。在前一篇《入手指南》之中我们已经比较详细的列举了 esp8266 的一些信息,所以这里就……还是要简单介绍一下这枚芯片。这是一枚主频在80/160mhz双模式并集成WIFI的32位物联网芯片,比1元硬币大一点,仅售10元,使用这枚芯片的简单的开发板价格也不超过20块钱,堪称物美价廉。</p>
<p>我们的目标是在这片小小的板子上运行一个缩水的 python 解释器 —— MicroPython,并让它连入我们的局域网WIFI。</p>
<p>坦白的说,这篇文章介绍的都是一些入门问题,不存在什么高深的操作,也是对我自己探索开荒过程的回顾,若有同路者,希望能以本文免去大家一小段奔波之苦。</p>
<p>P.S. 本文在 Windows 环境下完成</p>
<h3>刷机</h3>
<p>前文的选购中我们介绍了两种开发板,其实如无意外大家买到的接近20块钱的开发板应该都是这种样式:</p>
<p><img alt="" src="http://fy0-me-bucket.slgame.org/FmKeWvdzmCsW4KQrH32iDhW3XSDZ-std"></p>
<p>也就是 Nodemcu 制式的开发板。这个开发板集成了 usb-ttl 芯片,因此你弄个 microusb 线就可以完成所有操作了。</p>
<p>那么首先下载固件:http://micropython.org/download</p>
<p>选择 esp8266-20171101-v1 …</p><h3>前言</h3>
<p>书接上回。在前一篇《入手指南》之中我们已经比较详细的列举了 esp8266 的一些信息,所以这里就……还是要简单介绍一下这枚芯片。这是一枚主频在80/160mhz双模式并集成WIFI的32位物联网芯片,比1元硬币大一点,仅售10元,使用这枚芯片的简单的开发板价格也不超过20块钱,堪称物美价廉。</p>
<p>我们的目标是在这片小小的板子上运行一个缩水的 python 解释器 —— MicroPython,并让它连入我们的局域网WIFI。</p>
<p>坦白的说,这篇文章介绍的都是一些入门问题,不存在什么高深的操作,也是对我自己探索开荒过程的回顾,若有同路者,希望能以本文免去大家一小段奔波之苦。</p>
<p>P.S. 本文在 Windows 环境下完成</p>
<h3>刷机</h3>
<p>前文的选购中我们介绍了两种开发板,其实如无意外大家买到的接近20块钱的开发板应该都是这种样式:</p>
<p><img alt="" src="http://fy0-me-bucket.slgame.org/FmKeWvdzmCsW4KQrH32iDhW3XSDZ-std"></p>
<p>也就是 Nodemcu 制式的开发板。这个开发板集成了 usb-ttl 芯片,因此你弄个 microusb 线就可以完成所有操作了。</p>
<p>那么首先下载固件:http://micropython.org/download</p>
<p>选择 esp8266-20171101-v1.9.3.bin (elf, map) (latest) 就可以了,刚刚更新的。</p>
<p>刷机工具:https://github.com/nodemcu/nodemcu-flasher</p>
<p>老实说好多年不见 delphi 写的工具了,这门语言还是很棒的,可惜时下比较封闭,越发的没落了。</p>
<p>都准备好之后,打开设备管理器,找到“端口(COM和LPT)”,如无意外有一项和其他长的不一样,这就是我们要用的端口了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fo0tJCE1KYyKuGtkc6enJ4iPKU-t-std"></p>
<p>通过串口访问板子,是刷机后进行配置时必不可少的一步,不妨先做了。</p>
<p>以我这里的 XShell 为例,新建一个连接,协议选择 Serial</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fs6WTPhEeJgOX1PO-GGXtq3GVFWi-std"></p>
<p>然后选择一下端口和波特率即可</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fm-2k3IoKRtUOKqjVpMXRpANmoJN-std"></p>
<p>连上之后按reset,显示的内容可能与我这里有些不同,但一般来说能看到一些可读信息,这就可以了。比如我这里的 ready。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FkWeefabhhTYuNuZa7ljVTaDEtaj-std"></p>
<p>其他软件,例如 Putty 也是同理,设置界面大同小异。</p>
<p>Linux 用户自行选择,也只给出一个 picocom 的例子,或者用 Putty 也没什么问题。</p>
<div class="highlight"><pre><span></span><code>picocom /dev/ttyUSB0 -b <span class="m">115200</span>
</code></pre></div>
<p>测试完之后记得<strong>断开连接</strong>!不然刷机程序无法工作。</p>
<p>打开刷机工具,切换到 Config 下,点第一项右边的齿轮选择固件</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqeRQqDhuvScVQLQPYK3Y9NotEAX-std"></p>
<p>波特率(Baudrate)也改一下</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FkkQ7XnSTloKCkkYo648vILop2FM-std"></p>
<p>切到第一个选项卡,点 Flash 即可</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FngBluTPMP4iPEJdPJ0Y-bkv4fsa-std"></p>
<p>刷机中。。。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FlLf8Btx2sWXTBcHor3a7xYCYKI1-std"></p>
<p>刷机完成</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FneVZlV5PCX5mfiYKAwhpi7xBZe9-std"></p>
<p>这个开发板在刷机的时候不需要你做什么,如果点下 Flash 进度条还没开始走,那你试着按按板子上的 flash 按钮,一般来说会自动检测,还是很顺的。</p>
<p>而上文中提到的另一种自己焊接的开发板就没有这么方便了,还记得那张转接板的原理图吗?</p>
<p><img alt="就是这张" src="//fy0-me-bucket.slgame.org/FmP_SZqYgDJF2fEyXtTLWgFHm8ZJ-std"></p>
<p>按照图中的说法,我们要把 GPIO0、GPIO15 和 GND 接在一块,再加上 USB-TTL 线本身的 GND 线,所以这里是四根线。绿线+黄线那个就是 USB-TTL 线的接地。四个在面包板上插一排,就这样接在一块了。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FqzKl6ZbT9lKMs40eJyhFFw3xjY5-std"></p>
<p>这里最好再把 reset 那个孔插一根公对公的线。这时候你在刷机程序上点了 Flash,如果不开始读条的话,你就用 reset 空出的那个头去碰一下 GND 的金属部分,以便顺利继续刷机程序。记住这个过程千万别乱动板子,很容易停止刷机进程,但貌似也刷不坏就是了。</p>
<p>其他系统用户建议使用 esptool 进行刷机:https://github.com/espressif/esptool</p>
<p>这样刷机就说完了。</p>
<h3>亮灯</h3>
<p>关掉刷机程序,连线也恢复正常。这时再次用软件连上串口,按 reset 后大致会出现这样的画面:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FhCi8Vw7dxxpyn0u9an3FFM3Nn35-std"></p>
<p>一般情况下会直接从 MircoPython vX.Y.Z ... 开始(呃,我截图的时候还是1.9.2,现在是1.9.3了)</p>
<p>进去之后直接是一个 MicroPython 的 REPL,支持 TAB 补全,按 Ctrl + E 能进入粘贴模式,感觉还挺6的,但很遗憾不支持中文。这里请大家无视我丢人的 ls 操作。</p>
<p>目前为止都是一些软件操作,下面我们来做一个能够直接影响现实的操作:亮灯。</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">machine</span> <span class="kn">import</span> <span class="n">Pin</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">Pin</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">Pin</span><span class="o">.</span><span class="n">OUT</span><span class="p">)</span>
<span class="n">p2</span><span class="o">.</span><span class="n">value</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div>
<p>esp8266上自带一个蓝色的 LED 灯,如无意外这时候已经被点亮了。想要熄灭也很简单,调用 <code>p2.value(1)</code> 即可。</p>
<p>据说之所以能亮是因为GPIO2和是和灯绑定在一块的,具体为啥是GPIO2不是别的这个我也不是很清楚,毕竟我也只是个硬件菜鸡,只是比萌新们多飞了一会。</p>
<p>总之这样一来我们终于也是完成了一件与硬件直接相关的操作了,可喜可贺。</p>
<h3>联网</h3>
<p>诸位应该记得 esp8266 的定位是一个 wifi 物联网芯片,可以使用 wifi 连接是其最大的亮点。那么如何接入网络呢?</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">network</span>
<span class="n">wlan</span> <span class="o">=</span> <span class="n">network</span><span class="o">.</span><span class="n">WLAN</span><span class="p">(</span><span class="n">network</span><span class="o">.</span><span class="n">STA_IF</span><span class="p">)</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">active</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">scan</span><span class="p">()</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">isconnected</span><span class="p">()</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">'WIFI 热点'</span><span class="p">,</span> <span class="s1">'密码'</span><span class="p">)</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">config</span><span class="p">(</span><span class="s1">'mac'</span><span class="p">)</span>
<span class="n">wlan</span><span class="o">.</span><span class="n">ifconfig</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">wlan</span><span class="p">)</span>
</code></pre></div>
<p>这样我们就获得了一个联网设备。特别注意一点,由于无法输入中文,中文热点需要自行转码成 utf-8 编码的 bytes 加进去才能上网。这样设置了之后,每次启动设备都会自动连入指定热点,即使热点消失也会等待自动重连。</p>
<p>现在我们有一件有趣的事情可以做了,那就是使用 WebREPL。在终端输入:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">webrepl_setup</span>
</code></pre></div>
<p>然后会有几句这样那样的提示,问你要不要开机启动,建议选“是”,不然每次用都要执行下面这两句:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">webrepl</span>
<span class="n">webrepl</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</code></pre></div>
<p>调整好后访问 http://micropython.org/webrepl/ ,修改IP为自己设备的然后 Connect,这时候会跟你要密码,默认密码为 <strong>micropythoN</strong>,注意大小写。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FrAPpSb633jW8cqzxY97gkhwh0iQ-std"></p>
<p>这样就可以在网页里对板子做操作了,这个页面还可以上传文件。现在我们就可以基本脱离 usb-ttl 线来使用设备了。</p>
<p>不过这个页面总是写默认IP为192.168.4.1(默认情况下板子会自己开一个热点给你连接,你连上之后IP就是这个),每次都要改很麻烦,这里提供一个猴子脚本去自动改地址:</p>
<div class="highlight"><pre><span></span><code><span class="c1">// ==UserScript==</span>
<span class="c1">// @name Micropython Websocket REPL</span>
<span class="c1">// @namespace Violentmonkey Scripts</span>
<span class="c1">// @include http://micropython.org/webrepl/</span>
<span class="c1">// @grant none</span>
<span class="c1">// ==/UserScript==</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'url'</span><span class="p">).</span><span class="nx">value</span> <span class="o">=</span> <span class="s1">'ws://192.168.0.103:8266/'</span>
</code></pre></div>
<p>除了连接 wifi 之外,esp8266还可以开热点,两者是可以同时进行的。</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">network</span>
<span class="n">ap</span> <span class="o">=</span> <span class="n">network</span><span class="o">.</span><span class="n">WLAN</span><span class="p">(</span><span class="n">network</span><span class="o">.</span><span class="n">AP_IF</span><span class="p">)</span>
<span class="n">ap</span><span class="o">.</span><span class="n">active</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
<span class="n">ap</span><span class="o">.</span><span class="n">config</span><span class="p">(</span><span class="n">essid</span><span class="o">=</span><span class="s1">'test'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ap</span><span class="p">)</span>
</code></pre></div>
<p>全参数的话是</p>
<div class="highlight"><pre><span></span><code><span class="n">ap</span><span class="o">.</span><span class="n">config</span><span class="p">(</span><span class="n">essid</span><span class="o">=</span><span class="s1">'test'</span><span class="p">,</span> <span class="n">channel</span><span class="o">=</span><span class="mi">11</span><span class="p">,</span> <span class="n">authmode</span><span class="o">=</span><span class="n">network</span><span class="o">.</span><span class="n">AUTH_WPA_WPA2_PSK</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'123123123'</span><span class="p">)</span>
</code></pre></div>
<p>特别提醒一下,wifi密码有最小长度限制,如果写短了的话的话会报一个这样的错误:</p>
<div class="highlight"><pre><span></span><code>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: can't set AP config
</code></pre></div>
<p>让人很是摸不着头脑。</p>
<h3>EX:安装软件包和文件管理</h3>
<p>MicroPython 提供了一个简单的包管理工具,称为upip,令人惊奇的是upip也是从pypi安装软件包的。不过我感觉因为由于网络、内存、储存空间等等限制,目前实用性还是比较差。</p>
<p>由于我们现在有且只有 REPL,熟悉的 shell 命令方式调用 pip 是无法使用的。这里需要使用调用的方式安装软件包:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">upip</span>
<span class="n">upip</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="s1">'xxx'</span><span class="p">)</span>
</code></pre></div>
<p>软件包会安装在 /lib 目录,而REPL所在的目录是 / 。默认情况下当然是没有 /lib 这个目录了,比较奇怪的是这种情况下我这里会直接报一个 <code>memory allocation failed</code> 的错误。当然这种时候使用 <code>os.mkdir('lib')</code> 就能解决问题。</p>
<p>此外 MicroPython 很大一个问题还是文件管理不方便,REPL 还是不如真正的 shell 来的顺手。为了解决这一问题人民群众也是想了很多办法,我看到一个大家比较认可的方案是这样的:</p>
<p>https://github.com/wendlers/mpfshell</p>
<p>用起来也比较简单,但注意这货不知怎的在 Windows 下不会直接在 Scripts 下生成 exe 文件,这样就不能通过 cmd 直接启动它。所以我就先在 mingw 里试了一下:</p>
<div class="highlight"><pre><span></span><code>pip3 install mpfshell
mpfshell
mpfs> open ws:192.168.0.103,123123
</code></pre></div>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FstaxIxVx1QI3gunc9L4DeVtWuR5-std"></p>
<p>注意这里,<code>open ws:192.168.0.103</code> 后面不要带端口号,后面的 123123 是访问密码。</p>
<p>这是通过 websocket (和WebREPL一致)来访问设备,当然也可以通过串口访问:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FnAI3joBeu5DG0i6jM4dX3Wdf1cP-std"></p>
<p>另外 cmd 其实也可以进行访问,不过要手动 import 调用一下:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FvQJgfVh5C1gubAukyxHcrqow9tT-std"></p>
<p>不过由于使用了 sh 方式的控制字符来设置终端的特殊格式(也就是文字颜色什么的),也很明显没有做 Windows 兼容。而我们知道这些控制字符实际上是特定的不可见字符+参数,所以在 cmd 下会显示乱码,如下图:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmoE8NnzlevNJsrNeWzuCKmT8SlJ-std"></p>
<p>所以还是推荐大家在 mingw 或类似环境下使用。不过 mpfshell 下的 repl 仍然会有乱码问题,这就没办法了。微软的 WSL 我没有试过,不知能否提供更好些的兼容。此外这个 shell 的具体功能这里就不赘述了,参见项目页面即可。</p>
<p>在知道这玩意之前,我自己也写了一个辣鸡脚本来做一些简单操作。与 mpfshell 不同,我这个脚本是直接在板子上运行的:</p>
<p>https://gist.github.com/fy0/982488ddbb5eafcda565923621895cb6</p>
<p>不完全地支持 ls cat mkdir rm quit(q) 五个命令,当然我这个实现也是很菜的,也只是用来应应急。使用的话把文件用各种手段(比如WebREPL)传上去就算完事了,然后 <code>from mpt import sh</code>,再 <code>sh()</code> 即可。</p>
<p>为了方便可以把这句话加到 boot.py 的最后一行,这样每次开机就自动运行了,不需要再每次 import 后才能使用,就如同这样:</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FviDw1rbljQuvXDteNg_dfzkp6Ee-std"></p>
<p>虽然加在末尾不那么 PEP8,不过更为现实的考量是如果我们在修改这个脚本的过程中出现问题,那么这一行的异常会中断其他项目的加载。因此选择在预设项目都加载完成后再加载自定义脚本。</p>
<h3>后记</h3>
<p>文档与参考:</p>
<p>固件下载:http://micropython.org/download</p>
<p>刷机工具(Win):https://github.com/nodemcu/nodemcu-flasher</p>
<p>刷机工具(通用):https://github.com/espressif/esptool</p>
<p>MicroPython esp8266官方文档:http://docs.micropython.org/en/latest/esp8266/</p>
<p>MicroPython 官方论坛:http://forum.micropython.org</p>
<p>其实这篇文章又是拖了很久,从一些截图的时间和版本就可以看出来。其实我计划中10月期间能够分享一下使用 OrangePi 和初次使用电烙铁的经验,但不幸的是今天已经是11月12日,果然还是太摸了啊……</p>
<p>以上就是我想说的,祝大家玩的愉快。</p>python3 csv/xls/json/pickle 等序列化反序列化代码速查2017-11-12T20:11:00+08:002017-11-12T20:11:00+08:00魏远tag:None,2017-11-12:/posts/python3-csvxlsjsonpickle-deng-xu-lie-hua-fan-xu-lie-hua-dai-ma-su-cha.html<h3>全部</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># pandas 秒天秒地</span>
<span class="c1"># 条件有限的时候再考虑别的</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_clipboard</span><span class="p">()</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_html</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_json</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_msgpack</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_pickle</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_clipboard</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_csv</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_csv</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_excel</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_html</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_html</span><span class="p">(</span><span class="n">fn …</span></code></pre></div><h3>全部</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># pandas 秒天秒地</span>
<span class="c1"># 条件有限的时候再考虑别的</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_clipboard</span><span class="p">()</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_html</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_json</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_msgpack</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_pickle</span><span class="p">(</span><span class="n">data_or_path</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_clipboard</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_csv</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_csv</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_excel</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_html</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_html</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_json</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_json</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_msgpack</span><span class="p">()</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_msgpack</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="n">df</span><span class="o">.</span><span class="n">to_pickle</span><span class="p">(</span><span class="n">fn</span><span class="p">)</span>
<span class="c1"># 转 dict 时 orient</span>
<span class="c1"># orient : str {‘dict’, ‘list’, ‘series’, ‘split’, ‘records’, ‘index’}</span>
<span class="c1"># Determines the type of the values of the dictionary.</span>
<span class="c1"># dict (default) : dict like {column -> {index -> value}}</span>
<span class="c1"># list : dict like {column -> [values]}</span>
<span class="c1"># series : dict like {column -> Series(values)}</span>
<span class="c1"># split : dict like {index -> [index], columns -> [columns], data -> [values]}</span>
<span class="c1"># records : list like [{column -> value}, ... , {column -> value}]</span>
<span class="c1"># index : dict like {index -> {column -> value}}</span>
<span class="c1"># 转 json 时 orient</span>
<span class="c1"># orient : string</span>
<span class="c1"># The format of the JSON string</span>
<span class="c1"># split : dict like {index -> [index], columns -> [columns], data -> [values]}</span>
<span class="c1"># records : list like [{column -> value}, ... , {column -> value}]</span>
<span class="c1"># index : dict like {index -> {column -> value}}</span>
<span class="c1"># columns : dict like {column -> {index -> value}}</span>
<span class="c1"># values : just the values array</span>
<span class="c1"># table : dict like {‘schema’: {schema}, ‘data’: {data}} describing the data, and the data component is like orient='records'.</span>
<span class="c1"># 其余不一一列出,详见</span>
<span class="c1"># http://pandas.pydata.org/pandas-docs/stable/api.html#id12</span>
</code></pre></div>
<h3>csv</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># 读取</span>
<span class="kn">import</span> <span class="nn">csv</span>
<span class="n">reader</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">reader</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'test.csv'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf-8'</span><span class="p">))</span>
<span class="n">reader</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">reader</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'test.csv'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'cp936'</span><span class="p">))</span> <span class="c1"># for excel</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="kn">import</span> <span class="nn">csv</span>
<span class="n">info</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'ret.csv'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">,</span> <span class="n">newline</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf-8'</span><span class="p">))</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'ret.csv'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">,</span> <span class="n">newline</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf_8_sig'</span><span class="p">))</span> <span class="c1"># BOM utf-8, 这样excel不乱码</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerows</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</code></pre></div>
<h3>xls</h3>
<p>尽量用 pandas,不然处理时间啥的还要费力</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 单列读取</span>
<span class="kn">import</span> <span class="nn">xlrd</span>
<span class="n">xls</span> <span class="o">=</span> <span class="n">xlrd</span><span class="o">.</span><span class="n">open_workbook</span><span class="p">(</span><span class="s1">'test.xls'</span><span class="p">)</span>
<span class="n">sheet</span> <span class="o">=</span> <span class="n">xls</span><span class="o">.</span><span class="n">sheet_by_name</span><span class="p">(</span><span class="s1">'Sheet1'</span><span class="p">)</span>
<span class="n">rowA</span> <span class="o">=</span> <span class="n">sheet</span><span class="o">.</span><span class="n">row_values</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># 第一行</span>
<span class="n">colA</span> <span class="o">=</span> <span class="n">sheet</span><span class="o">.</span><span class="n">col_values</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># 第一列</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 全部读取</span>
<span class="kn">import</span> <span class="nn">xlrd</span>
<span class="n">xls</span> <span class="o">=</span> <span class="n">xlrd</span><span class="o">.</span><span class="n">open_workbook</span><span class="p">(</span><span class="s1">'test.xls'</span><span class="p">)</span>
<span class="n">sheet</span> <span class="o">=</span> <span class="n">xls</span><span class="o">.</span><span class="n">sheet_by_name</span><span class="p">(</span><span class="s1">'Sheet1'</span><span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">sheet</span><span class="o">.</span><span class="n">row_values</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">sheet</span><span class="o">.</span><span class="n">nrows</span><span class="p">)]</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="c1"># 摸了,用 pandas 或 csv, utf-8 with bom 输出吧</span>
</code></pre></div>
<h3>json</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># 读取</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="n">info</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'data.json'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="nb">open</span><span class="p">(</span><span class="s1">'data.json'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">info</span><span class="p">))</span>
</code></pre></div>
<h3>pickle</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># 读取</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="n">info</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'data.pkl'</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="nb">open</span><span class="p">(</span><span class="s1">'data.pkl'</span><span class="p">,</span> <span class="s1">'wb'</span><span class="p">)</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">pickle</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">info</span><span class="p">))</span>
</code></pre></div>
<h3>纯文本</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># 读取</span>
<span class="n">txt</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf-8'</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf-8'</span><span class="p">)</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">txt</span><span class="p">)</span>
</code></pre></div>
<h3>二进制</h3>
<div class="highlight"><pre><span></span><code><span class="c1"># 读取</span>
<span class="n">data</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># 写入</span>
<span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="s1">'wb'</span><span class="p">)</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div>
<p>P.S. 其中很多一直在用但一直没统一整理,这样就方便很多了。内容实际大部分是月初填充的,但觉得太水不好意思单发。正好今天发个文,这篇算是附带的。</p>python 3.5+ 协程自我挂起及其应用2017-10-26T01:10:00+08:002017-10-26T01:10:00+08:00魏远tag:None,2017-10-26:/posts/python-35-xie-cheng-zi-wo-gua-qi-ji-qi-ying-yong.html<h3>前言</h3>
<p>有段时间没有写文章了。说来惭愧,没写文章的一个主要原因是这个博客还不支持传图。</p>
<p>近来又颇懒惰,于是就长久的没有更新了……</p>
<p>P.S. 此文章实际写于《esp8266 系列芯片入手指南》之前</p>
<h3>背景</h3>
<p>网络编程中我们经常面对双方建立连接,两边同时可以收/发消息的情况。</p>
<p>例如说我们连接到某服务器,最简单的状态下,我们向服务器发送一条指令,然后直接等待服务器返回并读取出数据。
也就是这种情况:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">serial</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">Serial</span><span class="p">(</span><span class="s1">'COM10'</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s1">'AT'</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">s</span><span class="o">.</span><span class="n">read_all</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'OK'</span>
</code></pre></div>
<p>为了保证返回结果能够正确,这个过程有两个问题:</p>
<ol>
<li>永远必须等待一条指令执行完成返回后,才能执行下一条指令</li>
<li>服务器基本没有机会主动向我们发送数据</li>
</ol>
<p>实际上这是一种极大的浪费,更为流行的做法是一边发送一边接收 …</p><h3>前言</h3>
<p>有段时间没有写文章了。说来惭愧,没写文章的一个主要原因是这个博客还不支持传图。</p>
<p>近来又颇懒惰,于是就长久的没有更新了……</p>
<p>P.S. 此文章实际写于《esp8266 系列芯片入手指南》之前</p>
<h3>背景</h3>
<p>网络编程中我们经常面对双方建立连接,两边同时可以收/发消息的情况。</p>
<p>例如说我们连接到某服务器,最简单的状态下,我们向服务器发送一条指令,然后直接等待服务器返回并读取出数据。
也就是这种情况:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">serial</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">Serial</span><span class="p">(</span><span class="s1">'COM10'</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s1">'AT'</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">s</span><span class="o">.</span><span class="n">read_all</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'OK'</span>
</code></pre></div>
<p>为了保证返回结果能够正确,这个过程有两个问题:</p>
<ol>
<li>永远必须等待一条指令执行完成返回后,才能执行下一条指令</li>
<li>服务器基本没有机会主动向我们发送数据</li>
</ol>
<p>实际上这是一种极大的浪费,更为流行的做法是一边发送一边接收,有的时候协议或框架自己实现了信息配对,直接在发送时传个 callback 函数就可以了。</p>
<p>而有些时候,则是全局共用同一个 callback。</p>
<p>一个典型场景是这样的:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">on_message</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">head</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">msg</span>
<span class="k">if</span> <span class="n">head</span> <span class="o">==</span> <span class="s1">'on_init'</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">elif</span> <span class="n">head</span> <span class="o">==</span> <span class="s1">'on_task_new'</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">on_task_new_done</span><span class="p">(</span><span class="n">task_id</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">[</span><span class="s1">'data'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">task_new</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">([</span><span class="s1">'task_new'</span><span class="p">]))</span>
</code></pre></div>
<p>我相信大家应该对这一幕很熟悉,或多或少写过类似的代码。</p>
<p>我是在写 UART 串口程序的时候遇到这一需求的,此外 websocket 也类似,不过这一技巧对客户端的作用远大于服务端,而且基本上不会有 python websocket client的场景。</p>
<p>这例代码中的主要问题是将逻辑上写在一个函数中的比较方便的过程,强行分割成了两个部分。</p>
<p>或许有人会觉得,一应一答两相分开,清清楚楚没毛病呀?</p>
<p>其实不然,复杂度上升以后,这种模式会成为开发人员的噩梦。</p>
<h3>期望</h3>
<p>我认为对开发人员来说理想的模式是这样的:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">task_new</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">([</span><span class="s1">'task_new'</span><span class="p">]))</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">many_caozuo</span><span class="p">()</span> <span class="c1"># 挂起自己,等待被唤醒的代码</span>
<span class="bp">self</span><span class="o">.</span><span class="n">on_task_done</span><span class="p">(</span><span class="n">task_id</span> <span class="o">=</span> <span class="n">ret</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span> <span class="n">data</span><span class="o">=</span><span class="n">ret</span><span class="p">[</span><span class="s1">'data'</span><span class="p">])</span>
</code></pre></div>
<p>我们知道有协程这种东西的存在,希望函数在执行到某一时间挂起自己,然后暴露一个什么对象出来。等到另一边接收到消息之后,再利用这个暴露出来的对象,将挂起的函数唤醒。</p>
<p>这一想法是符合逻辑的,借助 python 3.5 版本之后的特性,<code>asyncio.Future</code> 对象能够让我们实现挂起与等待唤醒,而 async/await 能够让整个过程变得优雅起来。</p>
<p>我们来举个例子,这样一个场景:
<img alt="" src="//fy0-me-bucket.slgame.org/FhyZ1FEhZOUHpJ7Gw4jEWsBV0PcJ-std">
程序被切成了至少四段。我们预计改造之后会产生这样的效果:
<img alt="" src="//fy0-me-bucket.slgame.org/FrwZ0_2iDfSqPLlbLuk2cKZjPUgr-std"></p>
<p>下面我们将讨论如何做这件事情。</p>
<h3>实现</h3>
<p>核心问题:<strong>异步函数(协程)挂起自身,由其他函数唤醒</strong></p>
<p>直接上代码:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="n">future</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
<span class="k">global</span> <span class="n">future</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'func begin'</span><span class="p">)</span>
<span class="n">future</span> <span class="o">=</span> <span class="n">loop</span><span class="o">.</span><span class="n">create_future</span><span class="p">()</span>
<span class="n">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="n">future</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'func end with </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="n">ret</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">func2</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'func2 begin'</span><span class="p">)</span>
<span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="s1">'greetings from func2'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'func2 end'</span><span class="p">)</span>
<span class="n">asyncio</span><span class="o">.</span><span class="n">ensure_future</span><span class="p">(</span><span class="n">func</span><span class="p">())</span>
<span class="n">asyncio</span><span class="o">.</span><span class="n">ensure_future</span><span class="p">(</span><span class="n">func2</span><span class="p">())</span>
<span class="n">loop</span><span class="o">.</span><span class="n">run_forever</span><span class="p">()</span>
<span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>这段代码的意思是并行执行 func 和 func2,其中 func 直接挂起自己,而 func2 在等待两秒后将 func 唤醒然后结束。</p>
<p>这里 Future 对象起了关键作用,这里利用了Future的两个性质:</p>
<ol>
<li>可以用 await 关键字等待</li>
<li>当 Future 实例被 set_result() 之后,该 Future 的状态会被标记为完成并且结束对实例的等待</li>
</ol>
<p>而取出 set_result 的值,有两种方式:</p>
<ol>
<li>在 future 完成之后,通过 ret = future.result() 取出</li>
<li>通过 ret = await future 等待完成并取出</li>
</ol>
<p>我们这里使用了 await 等待的方式,而在更古老的时候,需要通过注册 callback 来完成收尾工作。具体见 Future.add_done_callback(),这里不就展开讨论了。</p>
<h3>应用</h3>
<p>有了工具之后,我们对协议接口稍微做一些改动,以使得演示代码简单明了。</p>
<p>原始格式:发 [command, data] 收 ['on_' + command, data]</p>
<p>修改格式:发 [random_id, command, data] 收 [random_id, 'on_' + command, data]</p>
<p>前面各增加一个 id 来做标识,简单生成如下:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">os</span>
<span class="n">get_random_id</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="s1">'</span><span class="si">%x</span><span class="s1">'</span> <span class="o">%</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">urandom</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span> <span class="s1">'little'</span><span class="p">)</span>
</code></pre></div>
<p>改造后的 Connection 如下:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">def</span> <span class="nf">on_message</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">rid</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">msg</span>
<span class="k">if</span> <span class="n">rid</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span><span class="p">:</span>
<span class="n">fut</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span><span class="p">[</span><span class="n">rid</span><span class="p">]</span>
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span><span class="p">[</span><span class="n">rid</span><span class="p">]</span>
<span class="n">fut</span><span class="o">.</span><span class="n">set_result</span><span class="p">([</span><span class="n">head</span><span class="p">,</span> <span class="n">data</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">_send_command</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">list</span><span class="p">):</span>
<span class="n">rid</span> <span class="o">=</span> <span class="n">get_random_id</span><span class="p">()</span>
<span class="n">future</span> <span class="o">=</span> <span class="n">loop</span><span class="o">.</span><span class="n">create_future</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span><span class="p">[</span><span class="n">rid</span><span class="p">]</span> <span class="o">=</span> <span class="n">future</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">([</span><span class="n">rid</span><span class="p">]</span> <span class="o">+</span> <span class="n">data</span><span class="p">))</span>
<span class="k">return</span> <span class="n">future</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">task_new</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_new'</span><span class="p">])</span>
<span class="k">return</span> <span class="n">ret</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="c1"># data, 即 task 的详细信息</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">task_get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
<span class="n">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_get'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'id'</span><span class="p">:</span> <span class="nb">id</span><span class="p">}])</span>
<span class="k">return</span> <span class="n">ret</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div>
<p>这样一来,不仅逻辑能够写在一块,用户也可以直接 <code>await conn.task_new()</code> 来一步到位获取结果了。</p>
<p>这种模式在一个行为对应多条指令的情况下十分优雅,能够完爆应答分离模式(当然这主要归功于async/await语法,不然一串回调也好看不到哪儿去),举例:</p>
<div class="highlight"><pre><span></span><code> <span class="k">async</span> <span class="k">def</span> <span class="nf">task_new_and_setup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">timeout</span><span class="p">):</span>
<span class="n">task</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_new'</span><span class="p">])[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_set_name'</span><span class="p">,</span> <span class="n">task</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span> <span class="n">name</span><span class="p">])</span>
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_set_timeout'</span><span class="p">,</span> <span class="n">task</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span> <span class="n">timeout</span><span class="p">])</span>
<span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">send_command</span><span class="p">([</span><span class="s1">'task_start'</span><span class="p">,</span> <span class="n">task</span><span class="p">[</span><span class="s1">'id'</span><span class="p">])</span>
<span class="k">return</span> <span class="n">task</span>
</code></pre></div>
<p>现在还有一个问题,那就是如果一直拿不到返回结果,<code>_send_command</code> 会无限制的等待下去,这可不行:</p>
<div class="highlight"><pre><span></span><code> <span class="k">async</span> <span class="k">def</span> <span class="nf">_send_command</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">list</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
<span class="n">rid</span> <span class="o">=</span> <span class="n">get_random_id</span><span class="p">()</span>
<span class="n">future</span> <span class="o">=</span> <span class="n">loop</span><span class="o">.</span><span class="n">create_future</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span><span class="p">[</span><span class="n">rid</span><span class="p">]</span> <span class="o">=</span> <span class="n">future</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">([</span><span class="n">rid</span><span class="p">]</span> <span class="o">+</span> <span class="n">data</span><span class="p">))</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">future</span><span class="p">,</span> <span class="n">timeout</span><span class="p">)</span>
</code></pre></div>
<p>这样的话超时情况下会抛出 asyncio.TimeoutError 异常。</p>
<p>除此之外,如果没有信息回调机制的话,也可以使用轮询拉取信息来同样使用这个技巧</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_wait_table</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_recv</span><span class="p">()</span>
<span class="n">loop</span><span class="o">.</span><span class="n">call_later</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">func</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_recv</span><span class="p">()</span>
<span class="n">loop</span><span class="o">.</span><span class="n">call_later</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">func</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_recv</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">msgs</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">check_messages</span><span class="p">()</span>
<span class="k">if</span> <span class="n">msgs</span><span class="p">:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">msgs</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">on_message</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
</code></pre></div>
<h3>结束</h3>
<p>说实话这个技巧更有用的地点可能是前端,按照相同的思路很容易就能写一个JS版本。</p>
<p>在浏览器控制台中做测试:</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="nx">a</span> <span class="o">=</span> <span class="nx">resolve</span><span class="p">;</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">(()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="mf">1111</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>
<p>随后运行:</p>
<div class="highlight"><pre><span></span><code><span class="nx">a</span><span class="p">()</span>
</code></pre></div>
<p>得到 1111 的输出。那么在实际应用中 await 该 promise 即可达成目的。</p>
<p>此场景有奇效</p>
<div class="highlight"><pre><span></span><code> 'task_create'
=> 'task_create_ret'
'task_set_param1', id
=> 'task_set_param1_ret'
'task_set_param2', id
=> 'task_set_param2_ret'
'task_start', id
=> 'task_start_ret'
</code></pre></div>
<p>可改写为</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">command</span><span class="p">(</span><span class="s1">'task_create'</span><span class="p">)</span>
<span class="nx">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">command</span><span class="p">(</span><span class="s1">'task_set_param1'</span><span class="p">,</span> <span class="nx">ret</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="mf">111</span><span class="p">)</span>
<span class="nx">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">command</span><span class="p">(</span><span class="s1">'task_set_param2'</span><span class="p">,</span> <span class="nx">ret</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="mf">222</span><span class="p">)</span>
<span class="nx">ret</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">command</span><span class="p">(</span><span class="s1">'task_start'</span><span class="p">,</span> <span class="nx">ret</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span>
</code></pre></div>
<p>update 1: 加入了 JS 相关内容</p>
<p>update 2: 加了两张说明图</p>esp8266 系列芯片入手指南!2017-10-23T23:10:00+08:002017-10-23T23:10:00+08:00魏远tag:None,2017-10-23:/posts/esp8266-xi-lie-xin-pian-ru-shou-zhi-nan.html<h3>前言</h3>
<p>esp8266 这款芯片我是久闻其名。10块上下的价格,32位架构,支持wifi,迷你便携这些特性都很有吸引力。但硬件毕竟深坑不可轻入,直到不久前才终于按捺不住,买来玩了一下。</p>
<p>这块芯片可以在 80/160mhz 中切换,这在单片机中已经不低了。足够的性能加上32位架构,使得可玩性大大提升。</p>
<p>社区的大佬们也充分释放了他们的热情,目前在这块板子上除了C语言之外可以运行 Lua (nodemcu)、JavaScript (Espruino)、Python (Micropython),可以说是非常的多种多样了。</p>
<p>而我的短期目标是先在上面跑起来 Micropython,然后借此学习一下硬件世界的规则,长远一点的大概也会做个“智能某某”吧。</p>
<p>作为 esp8266 系列(计划中,咕)的开篇,这篇文章主要是讲述一些在购买和挑选过程中遇到的问题,希望对大家有所帮助。若有问题也不吝赐教。</p>
<h3>芯片与开发板</h3>
<p>我是没什么硬件方面经验的,因此直接选择了现成的开发板,淘宝大概17、18块钱的样子,引出大量引脚,自带复位按键,集成USB-TTL芯片。用microusb线直接连上电脑就可以直接通过串口来交互啦 …</p><h3>前言</h3>
<p>esp8266 这款芯片我是久闻其名。10块上下的价格,32位架构,支持wifi,迷你便携这些特性都很有吸引力。但硬件毕竟深坑不可轻入,直到不久前才终于按捺不住,买来玩了一下。</p>
<p>这块芯片可以在 80/160mhz 中切换,这在单片机中已经不低了。足够的性能加上32位架构,使得可玩性大大提升。</p>
<p>社区的大佬们也充分释放了他们的热情,目前在这块板子上除了C语言之外可以运行 Lua (nodemcu)、JavaScript (Espruino)、Python (Micropython),可以说是非常的多种多样了。</p>
<p>而我的短期目标是先在上面跑起来 Micropython,然后借此学习一下硬件世界的规则,长远一点的大概也会做个“智能某某”吧。</p>
<p>作为 esp8266 系列(计划中,咕)的开篇,这篇文章主要是讲述一些在购买和挑选过程中遇到的问题,希望对大家有所帮助。若有问题也不吝赐教。</p>
<h3>芯片与开发板</h3>
<p>我是没什么硬件方面经验的,因此直接选择了现成的开发板,淘宝大概17、18块钱的样子,引出大量引脚,自带复位按键,集成USB-TTL芯片。用microusb线直接连上电脑就可以直接通过串口来交互啦,非常方便。</p>
<p><img alt="包装" src="//fy0-me-bucket.slgame.org/Fu9y0FZfzqQFkfOYF8LdJ5Cgjb4d-std"></p>
<p>经过几年的时间,esp8266 已经发展为一个芯片系列,最初 8pin 512kb flash 的 esp-01 已经不怎么够用。我买的这一片实际上是 esp8266-12E,拥有更多的IO接口和 4MB 的 flash。</p>
<p>具体详情可见: http://www.esp8266.com/wiki/doku.php?id=esp8266-module-family</p>
<p>其中还是漏了一些型号,比如 ESP-12N,不过据我观察 12N 和 12E 接口是一样的,刷同样的固件也并未出问题。其实 ESP-12 的这几个芯片 wiki 也没说清楚区别在哪里,大概细微之处只有翻手册才能详解了。</p>
<p><img alt="正面" src="//fy0-me-bucket.slgame.org/FmKeWvdzmCsW4KQrH32iDhW3XSDZ-std"></p>
<p><img alt="背面" src="//fy0-me-bucket.slgame.org/Fsc6sWJh-2lljZyMC8-19mFYadbj-std"></p>
<p>这只是白肚皮的,我还买到一块同款黑肚皮的,背面引脚也标着字,更方便一些。不过串口芯片有别,前者CP2102,后者CH340,据说前者好点,不过我也说不清具体多好多坏,够用就好。</p>
<h3>自行焊接</h3>
<p>第二个方案就硬核一些啦。也存在着几个门槛,比如工具(电烙铁、焊锡、万用表)还有手艺要求(焊接)。</p>
<p>但是相比于方案一,这个方案实际上更为有趣,价格也更为便宜。一枚 ESP-12E 芯片 10 块钱,转接板不超过 5 毛钱,<strong>AMS1117-3.3V 线性稳压芯片</strong>一毛钱一个(之所以加粗是为了提醒大家千万不要忘了买这个不起眼的东西,不然是用不了的,我这个封装规格是SOT-223,有些商家标的是SOT-89我不知道是否是模具不同,最好都买一个),总成本也就是10.5元左右,比开发板省了40%的钱,性价比爆表。</p>
<p>这个方案的优势:
1. 性价比高,节省40%
2. 体积小,节省一半空间
3. ~~逼格高~~</p>
<p>方案劣势:
1. 需要动手
2. 板子上缺少复位按钮
3. 刷机时需要自备USB-TTL线(购买3元的CH340G USB转串口+杜邦线自己DIY即可,或者买成品)
4. 刷机时要手动接一下GPIO0 GPIO15和GND
5. 芯片上纵向的6个针脚没有引出,可用的PIN比开发板来的少</p>
<p>先放个原理图</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FmP_SZqYgDJF2fEyXtTLWgFHm8ZJ-std"></p>
<p>转接板关键字就是:esp8266 转接板</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fv8Y2omC2_UvXr_GEB4K1NbepYXA-std"></p>
<p>依旧是我们熟悉的防静电袋,一个板加两个排针构成了其中的内容。</p>
<p>其实这块小板焊接好的很多商家也有卖,但要作价15大洋,不如方案一远矣。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FoKOpIOo0tySbmRaV-H_l7n57Yu0-std"></p>
<p>芯片本体。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FvojFV04KOCUrb66_486q9-9hof7-std"></p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FptcxOWm-RJYy5wjm61ZTN65mivX-std"></p>
<p>正面与背面。</p>
<p>这里需要焊接的部分,第一个是芯片本体,第二个是排针,第三个是稳压芯片,建议按照先芯片后排针最后稳压芯片的顺序来。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fr_UT9Klk1Q6-Rf2nUqUIG9nj1PU-std"></p>
<p>正面焊接完成之后就是这个样子。</p>
<p>具体怎么焊接我这个焊接新手这里也不献丑了,我这里芯片焊点还处理的很丑陋呢。后面专门写一篇焊接入门的感想再详细分说,其实我觉得还是挺简单的,只是一套工具算是个小的门槛。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FryeOdbX6nKXsj3sDhy6v4kN-Xc3-std"></p>
<p>背面,这里留空的位置就是稳压芯片了,再次提醒大家别忘记购买<strong>AMS1117-3.3V SOT-223/SOT-89 线性稳压芯片</strong>,如果不确定是 SOT-223 还是 SOT-89 封装就两种都买,~~否则就会像我这样变得不知所措最后从其他地方抠了个芯片才成功使用起来~~。</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/Fh1P2MPp9JZKqL2IbjNokP6UvJxg-std"></p>
<p>这是焊接好之后。</p>
<p>这样焊接方案也告一段落,说实话我对这个转接板是不太满意的,因为其实可以做的更小,大家看原理图就明白了,无非一阵操作弄几个电阻,然后用小间距转大间距的杜邦线做连接就可以了。</p>
<p>这家店的设计就深得我心,可惜引脚太少,而且这玩意敢卖28你敢信?</p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FlIqnwWBect1y1dfVUjYdFSlIghx-std"></p>
<p><img alt="" src="//fy0-me-bucket.slgame.org/FjXjjPRM03yBFlj1x_noM37cdu-Q-std"></p>
<p>当然也不是没有猛男裸板飞线的,具体我就只贴个连接了:</p>
<p>http://www.51hei.com/bbs/dpj-47846-1.html</p>
<p>画面太美,我不敢看</p>
<h3>结尾</h3>
<p>写到这里我已是弱弩之末,但作为收尾,还是一定要说点什么。硬件上面临的挑战和软件上不尽相同,对我来说也有着些许难得的新鲜感。由于精力有限,或许我不会在这里停留太久,不过我会尽量把这个过程分享出来。能快速搞到这么多东西,也要感谢淘宝和华强北吧。</p>
<p>说到这里我对华强北也有一些感慨,我所能想象的和想象不到的关于硬件的一切都能透过网线和4块钱的邮费在这个市场里找到。有些理解为什么那里能诞生了那么多的硬件创业团队。不知道在硬件开发人员心目中那里是否是他们的圣地,但对我而言那是我不理解的另一个世界,在我的想象中他们过着日出而作日落而息白天打包傍晚发货的生活,无数包裹就这样发向整个中国,或许是整个世界。</p>
<p>对了,提醒一下大家,华强北的商家基本都是中午到下午3、4点前完成打包,因此买东西要趁早,不然只能等到第二天晚上发货。每个商家基本都会有满多少免邮费(100出头),但是页面上不会标出来,只有你问客服的时候他才会告诉你,最后下单的时候联系客服改价。</p>
<p>希望本文对大家有所帮助!</p>
<p>P.S. 终于给博客更新了贴图功能,争取下版本可以游客评论。狗才摸鱼.jpg</p>关于《模式 - 有效事件: 可取代数十种设计模式》2017-09-20T22:09:00+08:002017-09-20T22:09:00+08:00魏远tag:None,2017-09-20:/posts/guan-yu-mo-shi-you-xiao-shi-jian-ke-qu-dai-shu-shi-chong-she-ji-mo-shi.html<p>原文地址:
https://msdn.microsoft.com/zh-cn/magazine/mt795187.aspx?f=255&MSPPError=-2147217396</p>
<p>标题起的很吓人,但我认为也许只是一种从编程语言到配置文件的回归。</p>
<blockquote>
<p>解决方案总是蕴含在问题之中。自相矛盾的是,OOP 旨在解决的一些问题(全局函数和数据)反而成为 OOP 无意间引发的问题的解决方案。如果你看看有效事件这种设计模式,首先会发现的是,在某种程度上,它是回归基本的,同时使用全局函数取代方法和类。然而,由于无需在有效事件的使用者和实现之间知道并共享签名或类型,因此与使用 OOP 相比,环境更像是黑盒。这样一来,可以轻松地进行交换,例如,将 SaveToDatabase 与 InvokeWebService 或 SaveToFile 进行交换。没有接口、类型、POD 结构和类,只有一个共用签名。只有优质的普通旧数据 …</p></blockquote><p>原文地址:
https://msdn.microsoft.com/zh-cn/magazine/mt795187.aspx?f=255&MSPPError=-2147217396</p>
<p>标题起的很吓人,但我认为也许只是一种从编程语言到配置文件的回归。</p>
<blockquote>
<p>解决方案总是蕴含在问题之中。自相矛盾的是,OOP 旨在解决的一些问题(全局函数和数据)反而成为 OOP 无意间引发的问题的解决方案。如果你看看有效事件这种设计模式,首先会发现的是,在某种程度上,它是回归基本的,同时使用全局函数取代方法和类。然而,由于无需在有效事件的使用者和实现之间知道并共享签名或类型,因此与使用 OOP 相比,环境更像是黑盒。这样一来,可以轻松地进行交换,例如,将 SaveToDatabase 与 InvokeWebService 或 SaveToFile 进行交换。没有接口、类型、POD 结构和类,只有一个共用签名。只有优质的普通旧数据。数据只会传入传出!</p>
</blockquote>
<p>似乎是配置文件和数据序列化反序列化框架。</p>
<blockquote>
<p>我意识到,经验丰富的构架师一定会认为这种多形性构造幼稚可笑且简单粗糙。然而,这种简单性正是它奏效的原因所在。借助有效事件,可以从数据库、配置文件或通过用户在窗体文本框中提供名称来提取方法或函数的名称。可以将此看作是不需要显式类的多形性变体。这是不含类型的多形性。这是在运行时期间动态确定的多形性。通过剔除所有关于多形性的传统观念,并重构多形性的实质内容,最终会生成实际有效的多形性(封装和多形性),而不含类、类型、接口或设计模式。链接有效事件就像原子结合成分子一样简单。这就是敏捷软件!</p>
</blockquote>
<p>我天天用动态语言,所以说对于多形性……</p>
<hr>
<p>有没有觉得这个思路有些熟悉?其实我觉得和 FAAS 之类套路很是相似啊。</p>
<p>如果我的判断没错,作者貌似是做了一个类似微服务的抽象,实现了调用、服务自动注册、数据序列化、回调等等一系列功能。</p>
<p>不过作者给出的那个项目例子貌似还是挺厉害的,有时间可以看一下。</p>一个自动转换音频为 opus 格式的小工具2017-09-19T00:09:00+08:002017-09-19T00:09:00+08:00魏远tag:None,2017-09-19:/posts/yi-ge-zi-dong-zhuan-huan-yin-pin-wei-opus-ge-shi-de-xiao-gong-ju.html<p>这两天发现了除 ogg vorbis 之外的另一个开源免费无专利的音频格式,即 opus。</p>
<p>根据观察这格式也是 ogg 封装(ogg是容器格式,vorbis才是编码,常说的ogg格式实际上指的是vorbis编码格式),各方的支持度其实相当不错。</p>
<p>据大佬说比较优秀,特别是码率较低的时候,ogg会出噪音,而 opus 不会,而且编码速度极快。那么在游戏开发和语音通话等方面应该有上佳表现。</p>
<p>于是顺手写了一个自动转换器,编码器和脚本放在同一目录执行脚本,然后向 input 目录丢 ".wav", ".aiff", ".flac", ".ogg" 这几种格式的文件,会自动转换成同名的 opus 编码文件输出在 output 目录。</p>
<p>https://github.com/fy0/autoconv</p>博客系统更新 1.0.32017-09-16T02:09:00+08:002017-09-16T02:09:00+08:00魏远tag:None,2017-09-16:/posts/bo-ke-xi-tong-geng-xin-103.html<p>我记得听过这样一句话,是说如果上线的产品没有能够简陋到丢人的程度,那一定是速度太慢。</p>
<p>作为~~一代摸鱼王、~~精益求精信条的追随者与践行者,这种论调我自然是不屑的。</p>
<p>所以后果就是坑迟迟填不完。</p>
<p>因此前段时间忽然仿佛意识到了什么的我赶紧把这个半成品博客程序翻了出来,匆匆完善了一下就直接上线了。</p>
<p>上线之后几天进行了大量的 BUG 修正、功能调整与优化。果然开门丢人和关门丢人还是有很大不同吧!</p>
<p>而就在一个多小时前,本博客使用的 https://github.com/fy0/storynote 项目发布了 1.0.3 版本。</p>
<p>其中最重要的功能就是<strong>文章引用</strong>,具体效果就是发表的带引用链的文章,直接点击后到引用处,而点击评论则到内部。也就是你们在外面看到的绿色标题的文章了。</p>
<p>这也是我初版时就策划好的功能,因此不用改 topic 表。这样一来很多文章我也懒得写两份,也能较快的给空荡荡的博客填充一些内容。</p>
<p>我觉得 StoryNote 大概会在一两个版本后达到稳定状态,如果有人想要用遇到什么问题的话(我觉得应该没有),鉴于评论还不好用,那么可以到github给我提issue或者找到我邮箱给我发邮件。</p>
<p>本文就是这样了,下次再见!</p>开始准备填一个社区系统的老坑2017-09-10T23:09:00+08:002017-09-10T23:09:00+08:00魏远tag:None,2017-09-10:/posts/kai-shi-zhun-bei-tian-yi-ge-she-qu-xi-tong-de-lao-keng.html<p>几年前的时候挺想做个小社区,聚集一些志同道合的人搞事情,现在想想是有些年轻了。现在虽然时过境迁,但留下的坑还在,正好我的新后端框架需要一个项目来证明自己,所以我决定把这坑填了。</p>
<p>项目在这里:https://github.com/fy0/Icarus</p>
<p>链接里头内容是空的。几天前我建立了一个新的空项目把老代码顶掉了,因为偏差太严重~~(所以并不是删库跑路)~~。</p>
<p>老代码是一个旧时代那种MVC的 web 项目,主要基于 tornado、mako、peewee、coffeescript、sockjs等库,而且不仅要兼容 Python 2/3,还计划只用 sql 数据库并同时支持 MySQL/PostgreSQL/SQLite……</p>
<p>而新项目将是一个符合现在主流节奏的前后端分离的项目。数据库使用 PostgreSQL 和 Redis,后端使用基于 aiohttp 的新辣鸡框架并仅限 Python 3.5+,前端则是 vue 全家桶 …</p><p>几年前的时候挺想做个小社区,聚集一些志同道合的人搞事情,现在想想是有些年轻了。现在虽然时过境迁,但留下的坑还在,正好我的新后端框架需要一个项目来证明自己,所以我决定把这坑填了。</p>
<p>项目在这里:https://github.com/fy0/Icarus</p>
<p>链接里头内容是空的。几天前我建立了一个新的空项目把老代码顶掉了,因为偏差太严重~~(所以并不是删库跑路)~~。</p>
<p>老代码是一个旧时代那种MVC的 web 项目,主要基于 tornado、mako、peewee、coffeescript、sockjs等库,而且不仅要兼容 Python 2/3,还计划只用 sql 数据库并同时支持 MySQL/PostgreSQL/SQLite……</p>
<p>而新项目将是一个符合现在主流节奏的前后端分离的项目。数据库使用 PostgreSQL 和 Redis,后端使用基于 aiohttp 的新辣鸡框架并仅限 Python 3.5+,前端则是 vue 全家桶。</p>
<p>名字还是 Icarus 不变,争取 2018 之年前搞完,敬请期待!</p>
<p>附录:</p>
<div class="highlight"><pre><span></span><code>$ git log <span class="p">|</span> grep <span class="s2">"commit"</span> <span class="p">|</span> wc -l -
<span class="m">148</span> -
</code></pre></div>
<div class="highlight"><pre><span></span><code>commit 09ba5a0f172a26a7ccb838f2f9264e6663fc1c1c
Author: fy
Date: Mon Sep 21 01:27:52 2015 +0800
初始提交
</code></pre></div>
<p>P.S. 我发现每年搞事基本都是九月份,也是不知道为什么?</p>修复 marked 配合 prism 进行语法高亮时,渲染代码块的格式错误2017-09-10T02:09:00+08:002017-09-10T02:09:00+08:00魏远tag:None,2017-09-10:/posts/xiu-fu-marked-pei-he-prism-jin-xing-yu-fa-gao-liang-shi-xuan-ran-dai-ma-kuai-de-ge-shi-cuo-wu.html<p>当前<a href="https://github.com/fy0/storynote">这个博客</a>就是使用了这个渲染代码的方案,这也是我的亲身经历。</p>
<p>我相信与我遇到相同问题的人还有不少,下面来说说遇到的问题是什么,如何解决。</p>
<h3>缘起</h3>
<p>我一直觉得有一个地方不协调,那就是markdown引用代码的时候,我这里渲染出来代码没有背景颜色。之前我以为是默认样式本来如此,结果上篇文章加了很多代码发现与正文区分不开,很难看。于是就去看了 prism.js 官网之后发现默认样式是有背景色的,而我的没有,这很明显就出了偏差。</p>
<p>既然出了偏差,那就是要负责的。</p>
<p>那我们先来观察一下 http://prismjs.com 里面代码是怎么引用的:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">pre</span> <span class="na">data-src</span><span class="o">=</span><span class="s">"prism.js"</span> <span class="na">class</span><span class="o">=</span><span class="s">" language-javascript"</span><span class="p">></span>
<span class="p"><</span><span class="nt">code</span> <span class="na">class</span><span class="o">=</span><span class="s">" language-javascript"</span><span class="p">></span>
...
<span class="p"></</span><span class="nt">code</span><span class="p">></span>
<span class="p"></</span><span class="nt">pre</span><span class="p">></span>
</code></pre></div>
<p>看上去不错,那我们渲染出来的 html 呢?</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">pre</span><span class="p">></span>
<span class="p"><</span><span class="nt">code</span> <span class="na">class</span><span class="o">=</span><span class="s">"lang-javascript"</span><span class="p">></span>
...
<span class="p"></</span><span class="nt">code</span><span class="p">></span>
<span class="p"></</span><span class="nt">pre</span><span class="p">></span>
</code></pre></div>
<p>呃,其实我也不知道为什么 …</p><p>当前<a href="https://github.com/fy0/storynote">这个博客</a>就是使用了这个渲染代码的方案,这也是我的亲身经历。</p>
<p>我相信与我遇到相同问题的人还有不少,下面来说说遇到的问题是什么,如何解决。</p>
<h3>缘起</h3>
<p>我一直觉得有一个地方不协调,那就是markdown引用代码的时候,我这里渲染出来代码没有背景颜色。之前我以为是默认样式本来如此,结果上篇文章加了很多代码发现与正文区分不开,很难看。于是就去看了 prism.js 官网之后发现默认样式是有背景色的,而我的没有,这很明显就出了偏差。</p>
<p>既然出了偏差,那就是要负责的。</p>
<p>那我们先来观察一下 http://prismjs.com 里面代码是怎么引用的:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">pre</span> <span class="na">data-src</span><span class="o">=</span><span class="s">"prism.js"</span> <span class="na">class</span><span class="o">=</span><span class="s">" language-javascript"</span><span class="p">></span>
<span class="p"><</span><span class="nt">code</span> <span class="na">class</span><span class="o">=</span><span class="s">" language-javascript"</span><span class="p">></span>
...
<span class="p"></</span><span class="nt">code</span><span class="p">></span>
<span class="p"></</span><span class="nt">pre</span><span class="p">></span>
</code></pre></div>
<p>看上去不错,那我们渲染出来的 html 呢?</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">pre</span><span class="p">></span>
<span class="p"><</span><span class="nt">code</span> <span class="na">class</span><span class="o">=</span><span class="s">"lang-javascript"</span><span class="p">></span>
...
<span class="p"></</span><span class="nt">code</span><span class="p">></span>
<span class="p"></</span><span class="nt">pre</span><span class="p">></span>
</code></pre></div>
<p>呃,其实我也不知道为什么 <code>lang-</code> 的前缀在我这里也是可以高亮的,大概是有什么神秘力量吧。</p>
<p>可以很明显看出差别了,对吧?经过实测发现,如果 <code><pre></code> 没有class,那么就没有背景(本文遇到的情况);如果 <code><code></code> class 不对,那么代码相对于背景的 padding 不正常(这是本文随后又遇到的情况)。</p>
<p>再随便找几个 prism 的主题看一下:
https://github.com/PrismJS/prism-themes/blob/master/themes/prism-darcula.css</p>
<div class="highlight"><pre><span></span><code><span class="nt">code</span><span class="o">[</span><span class="nt">class</span><span class="o">*=</span><span class="s2">"language-"</span><span class="o">],</span>
<span class="nt">pre</span><span class="o">[</span><span class="nt">class</span><span class="o">*=</span><span class="s2">"language-"</span><span class="o">]</span> <span class="p">{</span>
<span class="err">...</span>
<span class="p">}</span>
</code></pre></div>
<p>基本所有的 css 定义都是冲着 <code><code></code> 和 <code><pre></code> 去的。代码和实测的结论完全符合,下面着手解决问题。</p>
<h3>调查</h3>
<p>我们先来复习一下 marked 到 prism 的关联是如何完成的:</p>
<div class="highlight"><pre><span></span><code><span class="nx">marked</span><span class="p">.</span><span class="nx">setOptions</span><span class="p">({</span>
<span class="nx">renderer</span><span class="o">:</span> <span class="k">new</span> <span class="nx">marked</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">(),</span>
<span class="nx">sanitize</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nx">highlight</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">stdlang</span> <span class="o">=</span> <span class="nx">lang</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">[</span><span class="nx">stdlang</span><span class="p">])</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">[</span><span class="nx">stdlang</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div>
<p>这里看到是在 options 里面插入了一个函数来做这件事情,这函数直接返回渲染好的 html</p>
<p>那问题来了,是 prism 搞错了吗?并不是,我看了代码之后发现是 marked 弄错了。摘抄一段 marked 源码:</p>
<div class="highlight"><pre><span></span><code><span class="nx">Renderer</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">code</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">,</span> <span class="nx">escaped</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">out</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">out</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="nx">out</span> <span class="o">!==</span> <span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">escaped</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">code</span> <span class="o">=</span> <span class="nx">out</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="s1">'<pre><code>'</span>
<span class="o">+</span> <span class="p">(</span><span class="nx">escaped</span> <span class="o">?</span> <span class="nx">code</span> <span class="o">:</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="kc">true</span><span class="p">))</span>
<span class="o">+</span> <span class="s1">'\n</code></pre>'</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="s1">'<pre><code class="'</span>
<span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">langPrefix</span>
<span class="o">+</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">lang</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
<span class="o">+</span> <span class="s1">'">'</span>
<span class="o">+</span> <span class="p">(</span><span class="nx">escaped</span> <span class="o">?</span> <span class="nx">code</span> <span class="o">:</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="kc">true</span><span class="p">))</span>
<span class="o">+</span> <span class="s1">'\n</code></pre>\n'</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div>
<p>这段代码有一系列问题……我们先只说与类型相关的部分。</p>
<p>我们看到这里进行了 <code>var out = this.options.highlight(code, lang);</code> 这样一个调用。</p>
<p>如果没有返回值就 <code>return '<pre><code>' + ...</code> 如果有的话就拼装一下</p>
<div class="highlight"><pre><span></span><code> <span class="k">return</span> <span class="s1">'<pre><code class="'</span>
<span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">langPrefix</span>
<span class="o">+</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">lang</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
<span class="o">+</span> <span class="s1">'">'</span>
</code></pre></div>
<p>实际上拼好就是 <code><pre><code class="lang-xxx"></code>。<code>langPrefix</code>,一个没有出现在文档中的值,实际上影响了拼装出的 class 的前缀,默认值是 'lang-'</p>
<h3>解决</h3>
<p>已经弄清楚了问题,下面就进行处理吧。翻阅 marked 文档得知可以手动重载 renderer 的渲染函数,这里也不卖关子,直接贴出最终的解决方案:</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">renderer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">marked</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">();</span>
<span class="nx">renderer</span><span class="p">.</span><span class="nx">code</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">,</span> <span class="nx">escaped</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">out</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">);</span>
<span class="c1">//if (out != null && out !== code) { [*3]</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">out</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">escaped</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">code</span> <span class="o">=</span> <span class="nx">out</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="sb">`<pre class="</span><span class="si">${</span><span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">langPrefix</span><span class="si">}</span><span class="sb">PLACEHOLDER"><code>`</span>
<span class="c1">// + (escaped ? code : escape(code, true)) [1]</span>
<span class="o">+</span> <span class="nx">code</span>
<span class="o">+</span> <span class="s1">'\n</code></pre>'</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">langText</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">langPrefix</span> <span class="o">+</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">lang</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">return</span> <span class="sb">`<pre class="</span><span class="si">${</span><span class="nx">langText</span><span class="si">}</span><span class="sb">"><code class="</span><span class="si">${</span><span class="nx">langText</span><span class="si">}</span><span class="sb">">`</span> <span class="c1">// [2]</span>
<span class="o">+</span> <span class="p">(</span><span class="nx">escaped</span> <span class="o">?</span> <span class="nx">code</span> <span class="o">:</span> <span class="nx">escape</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="kc">true</span><span class="p">))</span>
<span class="o">+</span> <span class="s1">'\n</code></pre>\n'</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// 后面 setOptions 的代码</span>
<span class="nx">marked</span><span class="p">.</span><span class="nx">setOptions</span><span class="p">({</span>
<span class="nx">renderer</span><span class="o">:</span> <span class="nx">renderer</span><span class="p">,</span>
<span class="nx">sanitize</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nx">langPrefix</span><span class="o">:</span> <span class="s1">'language-'</span><span class="p">,</span>
<span class="nx">highlight</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">lang</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">stdlang</span> <span class="o">=</span> <span class="nx">lang</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">[</span><span class="nx">stdlang</span><span class="p">])</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">[</span><span class="nx">stdlang</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div>
<p>关于改动:</p>
<p>[1] highlight 没有返回时,也就是没有代码高亮,这里我出于个人习惯还是给了 <code><pre></code> 一个 class,让背景能够渲染出来,因为我经常用 ``` ``` 来显示一些执行结果,很显然这是带不上高亮的,但没有背景又很难看。而里面的 escape 注释掉是因为外面已经有 <code><pre></code> 元素了,如果再 escape 会显示出 %20 等等玩意,对读者来说就是乱码。</p>
<p>[2] 改写了原版写法,给 <code><pre></code> 加上了 class</p>
<h3>还有问题</h3>
<p>改完了之后发现仍有代码渲染不正常,是这个样子的:</p>
<div class="highlight"><pre><span></span><code>goaccess%20access.log%20-o%20report.html%20--real-time-html
</code></pre></div>
<p>但我们想要的是这个样子:</p>
<div class="highlight"><pre><span></span><code>goaccess access.log -o report.html --real-time-html
</code></pre></div>
<p>经过排查,问题出在这里,也就是 [3] 修改的部分。</p>
<div class="highlight"><pre><span></span><code><span class="nx">renderer</span><span class="p">.</span><span class="nx">code</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">,</span> <span class="nx">escaped</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">out</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">lang</span><span class="p">);</span>
<span class="c1">//if (out != null && out !== code) { [*3]</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">out</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">escaped</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">code</span> <span class="o">=</span> <span class="nx">out</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>[3] 对部分简单代码来说 out == code 是完全可能的,就比如上面那个例子用 bash 语法高亮,渲染前后内容完全一致。那么如果说 escaped 不被置为 true,那在最后明明已经格式化好的代码又要被 escape 一次,于是就出现了乱码。</p>
<h3>完结</h3>
<p>写这篇文章花了一个小时多一点,实际上比调试花了更多时间。在版本库中对应这部分:</p>
<p>https://github.com/fy0/storynote/blob/master/page/src/main.js#L109-L179</p>goaccess 配置记录2017-09-09T22:09:00+08:002017-09-09T22:09:00+08:00魏远tag:None,2017-09-09:/posts/goaccess-pei-zhi-ji-lu.html<p>本来是计划弄一个站点统计的,初步意向是百度,结果发现被 adblock 默认规则屏蔽了。</p>
<p>拿关键字一搜发现不仅是百度,不少人专门将各种统计都配置成全家被屏蔽的状态,这可就不得了了。于是看看自建的web统计方案,基本上就是 piwik 一家独大。然而群众又表示这货很占资源,我这小服务器就还是算了吧。</p>
<p>后面又试图转战 VeryNginx,好嘛,这项目我也盯了很久了。谁知此项目毫无长进,访问统计废到不能再废,也是无法解决问题。</p>
<p>因此最后方案是 goaccess ,实际上这不是统计而是 nginx 日志分析。对单页应用来说肯定各种不准,但也无所谓了。我就随便看看大致情况,图个乐就行。</p>
<p>https://goaccess.io</p>
<p>ubuntu系统的默认源里有这个软件,但是版本是 0.9.x,我看到最新稳定版本是 1.2,那就按官方文档添加源吧。</p>
<div class="highlight"><pre><span></span><code><span class="nb">echo</span> <span class="s2">"deb http://deb.goaccess.io/ </span><span class="k">$(</span>lsb_release -cs …</code></pre></div><p>本来是计划弄一个站点统计的,初步意向是百度,结果发现被 adblock 默认规则屏蔽了。</p>
<p>拿关键字一搜发现不仅是百度,不少人专门将各种统计都配置成全家被屏蔽的状态,这可就不得了了。于是看看自建的web统计方案,基本上就是 piwik 一家独大。然而群众又表示这货很占资源,我这小服务器就还是算了吧。</p>
<p>后面又试图转战 VeryNginx,好嘛,这项目我也盯了很久了。谁知此项目毫无长进,访问统计废到不能再废,也是无法解决问题。</p>
<p>因此最后方案是 goaccess ,实际上这不是统计而是 nginx 日志分析。对单页应用来说肯定各种不准,但也无所谓了。我就随便看看大致情况,图个乐就行。</p>
<p>https://goaccess.io</p>
<p>ubuntu系统的默认源里有这个软件,但是版本是 0.9.x,我看到最新稳定版本是 1.2,那就按官方文档添加源吧。</p>
<div class="highlight"><pre><span></span><code><span class="nb">echo</span> <span class="s2">"deb http://deb.goaccess.io/ </span><span class="k">$(</span>lsb_release -cs<span class="k">)</span><span class="s2"> main"</span> <span class="p">|</span> sudo tee -a /etc/apt/sources.list.d/goaccess.list
wget -O - https://deb.goaccess.io/gnugpg.key <span class="p">|</span> sudo apt-key add -
sudo apt-get update
sudo apt-get install goaccess
</code></pre></div>
<p>装好以后是个命令行工具,但我想要开web服务好像不是那么简单。官方配置文档看得人头大,看着也没什么 step by step 的内容,所以写个文章记下来。</p>
<p>下面正式开始:</p>
<h3>1. 统一日志格式</h3>
<p>我有一些非标准形式的nginx日志,这是从上古流传下来的一份个人配置导致的,我决定把他们全清了。反正我也从来不看。</p>
<p>具体过程就是改配置,删去格式定义和使用该格式的地方。停掉 nginx 删除所有旧日志等等。</p>
<h3>2. 简单配置 goaccess.conf</h3>
<p>先试一下官方的例子。</p>
<div class="highlight"><pre><span></span><code><span class="nb">cd</span> /var/log/nginx
goaccess access.log -o report.html --real-time-html
</code></pre></div>
<p>提示需要对 /etc/goaccess.conf 进行配置</p>
<div class="highlight"><pre><span></span><code>GoAccess - version 1.2 - Mar 7 2017 06:40:58
Config file: /etc/goaccess.conf
Fatal error has occurred
Error occured at: src/parser.c - parse_log - 2705
No time format was found on your conf file.
</code></pre></div>
<p>打开这个文件以后,我们看到几个标着 (required) 的项目</p>
<p>其中 <code>Time Format Options (required)</code> 和 <code>Date Format Options (required)</code> 我们都选第一项,因为提示是 <code>Apache/NGINX's log formats below</code>。</p>
<p><code>Log Format Options (required)</code> 则让人有点懵逼,因为分成了<code>NCSA Combined Log Format</code>,<code>Common Log Format (CLF)</code>,<code>W3C</code> 等等好多种</p>
<p>进行比对之后确认 nginx 是 <code>NCSA Combined Log Format</code>,因此把 <code>#log-format COMBINED</code> 这行的注释取消掉。</p>
<p>所以目前实际有用的就三行:</p>
<div class="highlight"><pre><span></span><code>time-format %H:%M:%S
date-format %d/%b/%Y
log-format COMBINED
</code></pre></div>
<p>其他保持默认即可。</p>
<h3>3. 实时 web 分析页面</h3>
<p>再来看这条命令:</p>
<div class="highlight"><pre><span></span><code>goaccess access.log -o report.html --real-time-html
</code></pre></div>
<p>根据文档 https://goaccess.io/man SERVER OPTIONS 一节的描述,goaccess还可以加 <code>--addr</code> <code>--port</code> 等参数。</p>
<p>这导致我陷入了一个严重的误区:<strong>我误以为 goaccess 能够直接提供一个对外的完整 web 服务</strong>,然后怎么调都调不对。</p>
<p>实际上,<strong>goaccess 只能生成一个 html 报告文件,需要你使用 http server 将此文件挂到网络上</strong>,也就是 <code>-o report.html</code> 里面的这个 report.html。</p>
<p>而 --real-time-html 的作用是启动一个 websocket 服务,默认会启动在 0.0.0.0 并挂在 7890 端口上。</p>
<p>在这之后,<strong>访问 report.html,他会试图去连接这个ws服务,从而完成数据的更新</strong>。</p>
<p>那么现在理清了思路,开始写配置文件:</p>
<div class="highlight"><pre><span></span><code>sudo vi /etc/nginx/conf.d/tongji.conf
</code></pre></div>
<p>别忘了顺便把 websocket 也映射了。</p>
<div class="highlight"><pre><span></span><code><span class="k">server</span> <span class="p">{</span>
<span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
<span class="kn">server_name</span> <span class="s">tongji.fy0.me</span><span class="p">;</span>
<span class="kn">location</span> <span class="s">/ws/</span> <span class="p">{</span>
<span class="kn">proxy_pass</span> <span class="s">http://localhost:7890</span><span class="p">;</span>
<span class="kn">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
<span class="kn">proxy_set_header</span> <span class="s">Upgrade</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
<span class="kn">proxy_set_header</span> <span class="s">Connection</span> <span class="s">"upgrade"</span><span class="p">;</span>
<span class="p">}</span>
<span class="kn">root</span> <span class="s">/var/web/tongji</span><span class="p">;</span>
<span class="kn">index</span> <span class="s">index.html</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>然后执行以下命令:</p>
<div class="highlight"><pre><span></span><code>sudo goaccess ./access.log -o /var/web/tongji/index.html --real-time-html --ws-url<span class="o">=</span>ws://tongji.fy0.me:80/ws/ --addr<span class="o">=</span><span class="m">127</span>.0.0.1
</code></pre></div>
<p>这样就可以访问了</p>
<p>细节处还有很多问题,接下来一一修正。</p>
<h3>4. 完善细节</h3>
<h4>时区问题</h4>
<p>如果你右上角显示的时间不对,那么注意你的系统时区。</p>
<div class="highlight"><pre><span></span><code>timedatectl <span class="c1"># 查看</span>
sudo timedatectl set-timezone Asia/Shanghai <span class="c1"># 设置时区为东八区</span>
</code></pre></div>
<h4>多个日志文件</h4>
<div class="highlight"><pre><span></span><code>sudo goaccess ./*access.log* -o /var/web/tongji/index.html --real-time-html --ws-url<span class="o">=</span>ws://tongji.fy0.me:80/ws/ --addr<span class="o">=</span><span class="m">127</span>.0.0.1
</code></pre></div>
<p>如果单目录下多文件直接使用路径通配,多目录的话在 <code>./*access.log*</code> 后面再加一条路径就行。</p>
<p>不过这东西毕竟不是站点统计,也没有针对域名的过滤器。所以建议不同服务配置不同的 access_log 然后分别开一个服务。</p>
<h4>REFERRERS URLS 面板</h4>
<p>内容比较有趣,通过编辑 /etc/goaccess.conf,将</p>
<div class="highlight"><pre><span></span><code>ignore-panel REFERRERS
</code></pre></div>
<p>这一行注释掉来开启。</p>
<h4>多个服务配置</h4>
<p>在上面使用的命令后面加上 <code>--port=xxxx --daemonize</code> 这么一段参数,端口号一字排开。<code>-o</code> 和 <code>-ws-url</code> 也进行修改分别对应生成不同的 html 文件还有访问不同的 ws 地址。</p>
<p>总之就是端口号、ws-url、文件名三项要改,再加上对应的nginx配置。如果 ws 连接状态码得到301,那不用想就是请求转发失败,特别注意 <code>/ws</code> 与 <code>/ws/</code> 的区别。</p>
<h3>4. 感想 & 结束</h3>
<p>什么破玩意,以后不搞了。</p>新的旅程,又是一年夏末时2017-09-06T22:09:00+08:002017-09-06T22:09:00+08:00魏远tag:None,2017-09-06:/posts/xin-de-lu-cheng-you-shi-yi-nian-xia-mo-shi.html<h2>不属于我的开学季</h2>
<p>不知不觉,已经来到了2017年的夏末。</p>
<p>从大一学会写留言板开始,也过去这么多年了。现在想来,当年翻开 web.py 教程的那一天绝对是值得纪念的一天。我也决计想不到,没过几年 web.py 项目的作者自杀了然后这项目也死了。</p>
<p>就像我想不到的其他很多事情一样。</p>
<p>而我在通过 web.py 入门的几个月后就转投了 tornado 框架,就这么一直用用用,用到现在。</p>
<h2>缘起</h2>
<p>说说这个博客程序吧。</p>
<p>也是从那时起,每隔一两年我都有些写一个博客程序的想法然后很快作罢。有一件事情是显而易见的:博客程序对 web 新手来说是一项极佳的实践。为什么这么说呢?作为经典入门项目留言板的豪华升级版,博客程序包含了文章、用户、标签、评论、管理界面等多种元素,同时涉及到初步的安全问题处理,技术上不会太复杂,知识点上覆盖的很全面,而且又有成就感还可以拿出去装逼,何乐而不为呢?简直堪称新手进阶项目的不二之选。</p>
<p>然而遗憾的是,作为 web 开发的老咸鱼,新手时代已经是我遥远的记忆了 …</p><h2>不属于我的开学季</h2>
<p>不知不觉,已经来到了2017年的夏末。</p>
<p>从大一学会写留言板开始,也过去这么多年了。现在想来,当年翻开 web.py 教程的那一天绝对是值得纪念的一天。我也决计想不到,没过几年 web.py 项目的作者自杀了然后这项目也死了。</p>
<p>就像我想不到的其他很多事情一样。</p>
<p>而我在通过 web.py 入门的几个月后就转投了 tornado 框架,就这么一直用用用,用到现在。</p>
<h2>缘起</h2>
<p>说说这个博客程序吧。</p>
<p>也是从那时起,每隔一两年我都有些写一个博客程序的想法然后很快作罢。有一件事情是显而易见的:博客程序对 web 新手来说是一项极佳的实践。为什么这么说呢?作为经典入门项目留言板的豪华升级版,博客程序包含了文章、用户、标签、评论、管理界面等多种元素,同时涉及到初步的安全问题处理,技术上不会太复杂,知识点上覆盖的很全面,而且又有成就感还可以拿出去装逼,何乐而不为呢?简直堪称新手进阶项目的不二之选。</p>
<p>然而遗憾的是,作为 web 开发的老咸鱼,新手时代已经是我遥远的记忆了。写这样一个程序对我来说,称得上的是内心毫无波动,处理那些细节甚至是一种折磨。古早的年代写过几个半吊子,也用过一段时间的静态博客(pelican),不过登陆到服务器上写 markdown 再编译后才能看效果的流程着实让人蛋疼。所以那个也就荒废了。</p>
<p>实际上对我这种老咸鱼来说,写博客程序的过程想想就觉得令人索然无味。然而终究还是勉强把 <a href="https://github.com/fy0/storynote">StoryNote</a> 写了出来。</p>
<p>所以为什么会这样呢?在2015 2016这两年间,早些年前后端分离的呼声终于变成了主流的声音,前端框架的战局也基本上明朗。大体而言 Vue.js React 和 AngularJS 三家踩着同类产品的累累骸骨登上王座,各自割据一方。我在试用了 Vue.js 全家桶之后安利给隔壁dalao,而 dalao 有天跟我说,为了融会贯通 Vue 全家桶的奥义,决定用 nodejs 方案 + Vue 写个个人博客出来。</p>
<p>我惊呆了,身为咸鱼的我赶紧把丢在地上的梦想捡起来擦了擦,似乎又有了一些斗志……</p>
<p>然而这也是去年的事情了。几次失去梦想和翻身挣扎之后,时间都过去了一年,现在也终于是断断续续把这玩意做的差不多了。唉,咸鱼之怒,不过五分钟热度。所以说写博客这种事情还是要趁早做好呀。~~打死我也不会再写新的了!~~</p>
<h2>未来</h2>
<p>以后我打算在这里发布一些技术笔记,脑洞想法等等。</p>
<p>还计划把过去几年的坑渐渐填掉一些,有些还算有意思的项目在里面,届时也会不断发一些进度和感想。</p>
<p>欢迎留言和关注,也欢迎访问<a href="https://github.com/fy0">我的 github</a>。</p>
<p>就是这样,回见!</p>