<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>伞菌の博客</title>
        <link>https://umb.ink/</link>
        <description>闲鱼的生活记录</description>
        <lastBuildDate>Sat, 15 Aug 2026 10:03:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for VanBlog</generator>
        <language>	zh-cn</language>
        <image>
            <title>伞菌の博客</title>
            <url>https://umb.ink/static/img/ch.ico</url>
            <link>https://umb.ink/</link>
        </image>
        <copyright>All rights reserved 2026, 伞菌</copyright>
        <item>
            <title><![CDATA[企业内双周发版SOP分享]]></title>
            <link>https://umb.ink/post/47</link>
            <guid>https://umb.ink/post/47</guid>
            <pubDate>Wed, 06 May 2026 05:38:52 GMT</pubDate>
            <description><![CDATA[<h1>0. 前言</h1>
<p>为了业务线各岗位高效协作，划分职责边界，提升工作效率的同时降低工作强度，减少无效加班的情况。规范工作流程和发版节奏。（不仅适用于互联网，一切团队协作都可参考本SOP，把执行节点进行替换即可）</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>0. 前言</h1>
<p>为了业务线各岗位高效协作，划分职责边界，提升工作效率的同时降低工作强度，减少无效加班的情况。规范工作流程和发版节奏。（不仅适用于互联网，一切团队协作都可参考本SOP，把执行节点进行替换即可）</p>
<!-- more -->
<h1>1. 工作原则</h1>
<ul>
<li>一：需求冻结制度</li>
</ul>
<p>进入开发阶段的需求，非特殊情况，原则上不得变更，新需求一律放入需求池。任何情况下不得口头插入需求。紧急需求必须拉会评审。</p>
<ul>
<li>二：发版节奏固定</li>
</ul>
<p>每两周为一个迭代周期，发版日固定为每个迭代的最后一个周四。除P0级线上事故外，不允许临时发版。</p>
<ul>
<li>三：各司其职</li>
</ul>
<p>每项工作有且只有一个「负责人」(Owner)，其他人为「协作者」。负责人对交付质量和进度负全责，协作者配合完成。</p>
<ul>
<li>四：关键环节把控</li>
</ul>
<p>当工作出现问题，全部要找项目经理协调，原则上不出现策划/产品越过项目经理与研发直接沟通的情况。</p>
<ul>
<li>五：文档驱动</li>
</ul>
<p>任何情况下，如果要开会，必须撰写文档，不论是技术方案还是产品方案，使用AI辅佐的文档必须自己review先过一遍。</p>
<h1>2. 流程与职责边界：</h1>
<p><img src="/static/img/e15d83f1a4e0d6b9c36c6b2285328dec.whiteboard_exported_image.webp" alt="whiteboard_exported_image.png"></p>
<p><img src="/static/img/3d9aeb329a8d1fcf44032afa944a8515.image.webp" alt="image.png"></p>
<h1>3. 需求变更控制</h1>
<p>变更分级</p>
<ul>
<li>轻微变更（文案修改、颜色微调）：开发自行判断可否吸收，报备产品经理即可</li>
<li>中度变更（功能逻辑调整、交互修改）：必须提交变更申请，拉相关人员开小会，经产品负责人+技术负责人评审通过后方可执行</li>
<li>重大变更（新增功能、砍掉已开发功能）：必须开评审会，评估影响范围和延期风险，由栗子拍板<br>
紧急需求绿色通道</li>
</ul>
<p>针对紧急场景，设立专项绿色通道：</p>
<ol>
<li>提出紧急需求，明确交付时间和核心诉求</li>
<li>产品经理「紧急需求评审会」，明确紧急需求有哪些交付标准和内容（15分钟内完成）</li>
<li>技术负责人评估：当前迭代哪些内容可以交付、哪些必须砍掉、额外需要什么</li>
<li>形成「紧急版本交付清单」：明确列出本次包含/不包含的功能</li>
<li>全员对齐后按清单执行，不得在清单之外临时加内容</li>
</ol>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[2025-timeline]]></title>
            <link>https://umb.ink/post/46</link>
            <guid>https://umb.ink/post/46</guid>
            <pubDate>Fri, 20 Feb 2026 06:02:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>声明：本人全篇纯人力手写，无AI成分，或有观感不佳或语法错误，还请谅解。</p>
</blockquote>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <blockquote>
<p>声明：本人全篇纯人力手写，无AI成分，或有观感不佳或语法错误，还请谅解。</p>
</blockquote>
<!-- more -->
<h1>时间线</h1>
<h2>一月：加班、春节与重新思考</h2>
<p>一月初，因当时所在的业务部门有封闭项目上线，导致一月延续了24年年底的情况，疯狂的加班。有多离谱呢，大概就是隔三差五凌晨四点、五点下班，经常性晚上11点下班，周末没有自由时间，要注意oncall工单和准备各种文档材料来应对年底的绩效打分。</p>
<p>总之在过年前的一月那段日子里，很累，只觉得毫无价值的累，或许大厂的工作免不了如此，但当时我觉得人生不该如此。这也引发了我对这份工作的重新审视，损失健康和个人时间，换来看似体面、相对高薪资的大厂工作真的是我想要的吗？</p>
<blockquote>
<p>叠个甲，或许有读者会觉得：“诶呀你都大厂程序员了，肯定赚的多，又想赚得多，又想轻松，怎么可能的事情？你看看底层流水线工人xxxxx，比你累多了还赚的少”。 薪资高低更多是因为市场需求而非个人努力，我只是简单认为所有的工作都不应该牺牲个人健康和空闲时间来换取报酬。牛马不分高低贵贱，苦难就是苦难，苦难不值得任何歌颂与对比，一个月十万的无产者和一个月一千块钱的无产者，本质上是一样的，无非前者运气好点能混个富足的温饱罢了。</p>
</blockquote>
<p>一月末，春节，忙碌了一年终于回到了惬意的老家，按惯例和老家的伙伴们聚在了一起，打打台球上上网，搓搓麻将放放火。忙碌一年后，总算稍有喘息。</p>
<p><img src="/static/img/b9e338c6f6d101d27b9061d809701eb1.QQ_1768058781010.webp" alt="QQ_1768058781010.png"></p>
<p><img src="/static/img/78504fbd4cd93f016c21f07ae02081de.QQ_1768058883963.webp" alt="QQ_1768058883963.png"></p>
<p><img src="/static/img/00ac016e11e04e9036d0b9e08a23cf7d.image.webp" alt="image.png"></p>
<h2>二月：旅行、看机会与再出发</h2>
<h3>二月初</h3>
<p>大年初一到初七，带一辈子没出过几次省的妈妈，坐飞机，去了云南玩。老妈看到旅途中各种新奇事物与风景的开心，又何尝不是一种独特的风景？</p>
<p>（当然也有一点自豪感，从小山村农村普通家庭走出来的小孩，变得有能力带父母出远门旅行的自豪）另外就是这次旅行后顿觉，人生不仅仅只有生活的苟且和工作的奴役，也有数不尽的有趣等着我们经历。</p>
<p>关于云南我有一篇旅行攻略，感兴趣的朋友可以参考：https://umb.ink/post/38</p>
<h4>出发那天妈妈在飞机上，对着窗外景色，看了又看。</h4>
<p align="center"><img src="/static/img/7fea0153264721e7d1c686721f45265f.QQ_1768059244185.webp" width="40%"></p>
<h4>大理古城：</h4>
<p>（我感觉不如丽江古城，大理古城商业化气息太重了，很无聊）</p>
<p align="center"><img src="/static/img/189788574ed8c0090fabf9902f4eddee.image.webp" width="40%"></p>
<p>同行的本地朋友（也是大学死党）的“导管大炮”：</p>
<p align="center"><img src="/static/img/a8280aeae5641b1bce55740ca9ed0ad5.image.webp" width="40%"></p>
<h4>洱海（重量级）：</h4>
<p>如果九寨归来不看水，那洱海一定担得起 洱海归来不看湖的美誉。</p>
<p>去洱海，一定要去看日出！！！</p>
<p align="center"><img src="/static/img/426a8fffc8acaad12df9cf672fdac42e.QQ_1768060486258.webp" width="40%"></p> <p align="center"><img src="/static/img/710b7d86135352fd9052c1ec25e18754.QQ_1768060410157.webp" width="40%"></p> <p align="center"><img src="/static/img/b58180cc766b57375fdbf74ac99f72fa.image.webp" width="40%"></p> <p align="center"><img src="/static/img/3a25ae0f659aad94c49bd7a913683c8f.image.webp" width="40%"></p> <p align="center"><img src="/static/img/95e770bf86a23769fa79f3a7b9669405.image.webp" width="40%"></p>
<p>双廊的花与洱海的鸥：</p>
<p align="center"><img src="/static/img/dbd65feb813ea1f5c203ae70bf1c05b3.QQ_1768060683609.webp" width="40%"></p> <p align="center"><img src="/static/img/324fb7c1b963241f2eb5918389bf5a22.image.webp" width="60%"></p> <p align="center"><img src="/static/img/fa35882a7b4b972278ad9484363aa437.image.webp" width="60%">
</p>
<p>渔舟唱晚具象化：</p>
<p align="center"><img src="/static/img/1470bf3ff179e6d3ae69601fc3c10097.image.webp" width="40%"></p>
<p>在地热国参加了云南蹦迪：</p>
<p align="center"><img src="/static/img/5262cd51d241b70e93ac617f956829fa.QQ_1768061000785.webp" width="60%"></p>
<h4>丽江（古城与雪山）</h4>
<p>丽江真是古城里面无敌的存在</p>
<p>非常有故事感的旅拍路人：</p>
<p align="center"><img src="/static/img/496e3dd6f25c2de525129cb5153c47dc.image.webp" width="40%"></p>
<p>很有民族风格的井</p>
<p align="center"><img src="/static/img/2b5b172551908b56c2add0b0d2afb60d.QQ_1768061290332.webp" width="40%"></p>
<p>早起看的日照金山：</p>
<p align="center"><img src="/static/img/a9c6a144148e4fe728bec66cc65f8b9b.image.webp" width="40%"></p>
<p>旁边的日落西山：</p>
<p align="center"><img src="/static/img/af11516c35eaa9f38e6838da10e0bc77.image.webp" width="40%"></p>
<p>绝美的饮玉巷</p>
<p align="center"><img src="/static/img/0e52db946504ffe6971e45a4c4188bb3.image.webp" width="40%"></p>
<p>非常好看的无名小巷（古城到处都是，非常耐逛！）</p>
<p align="center"><img src="/static/img/7a08b6329119b88dd4e8ff2dabf9d3bb.QQ_1768061483628.webp" width="40%"></p>
<p>在祈福处的国际友人留下的美好祈愿，第一次切身感受到世界的互相交流。</p>
<p align="center"><img src="/static/img/14db9f32972a2b7234e0ed1f004e7480.image.webp" width="40%"></p>
<h4>玉龙雪山（重量级，无敌美！）</h4>
<p>雪山下蓝月谷</p>
<p align="center"><img src="/static/img/fb4becf87f796206be2b20dfa68a7216.image.webp" width="60%"></p> <p align="center"><img src="/static/img/b6285dd9811b17432f29d394a0c0b607.image.webp" width="60%"></p>
<p>牦牛坪的远眺：</p>
<p>现场肉眼可见的震撼，见到山的那一刻，旅途的疲惫全无，只剩下对山的震撼。</p>
<p align="center"><img src="/static/img/570e0bf2cdde50cfe0117f9c8ee3dc34.image.webp" width="40%"></p>
<p>苍鹰拨开云雾见青天</p>
<p align="center"><img src="/static/img/06b35f4518aceabb356e1a3eda10bfd7.image.webp" width="40%"></p>
<p>给朋友拍的游客照：</p>
<p align="center"><img src="/static/img/022c39fbf7805f75f22f556a702789c3.image.webp" width="40%"></p>
<p>很有旅人感的照片：</p>
<p align="center"><img src="/static/img/29a9c62482d147f7254437a11add1fa3.image.webp" width="40%"></p>
<p>下山后和朋友在古城酒吧里听着民谣研究起了代码：</p>
<p>此时完全不累，甚至非常激动。</p>
<blockquote>
<p>或许我们所讨厌的并非工作本身，而是充满了压榨的压抑环境</p>
</blockquote>
<p align="center"><img src="/static/img/7b4478334a77499c361550b700b03243.QQ_1768062207955.webp" width="40%"></p>
<p>最后一天，来自俺妹的cos：</p>
<p align="center"><img src="/static/img/973246f8a0d27194acc9d6178ac0a9c9.image.webp" width="40%"></p>
<h2>三月：决心</h2>
<p>年后复工，感觉自己真的要勇敢迈出改变的那一步。碰巧，一朋友在某游戏工作室，恰好缺一个后端，就去简单聊了聊，线下聊的很愉快，当天晚上就给了比较满意的薪酬和待遇。于是决定离开，跳槽。在此前也有不少朋友劝我，劝我再多考虑一下，毕竟在大厂和在游戏工作室的发展还是不一样的。</p>
<p>但人总是要经历的，我很喜欢的LOL职业选手Deft有一句名言：</p>
<blockquote>
<p><strong>其实人生在世，并不需要太多别人的建议</strong></p>
</blockquote>
<p>因此在年后复工后，我鼓起勇气，还是向领导提出了裸辞。</p>
<p>依然记得签名上标注了lastday的那段时间，合作的同事与业务方，面对我的突然离开，表现得都很惊讶。很多同事和业务方坦白跟我合作的都很愉快，未曾想我突然就要离职了。但天下没有不散的宴席，虽然在大厂一坤年的工作中倍感压力，但确实与很多同事合作非常愉快。只是人总是要向前看的，如果不能适应环境，那不妨鼓起勇气换个环境。</p>
<p>记得很深，有位在其它部门，但一起校招的同届朋友向我诉苦，我走了以后没人听他骂老板了，他那边一年换了两个领导，而且还是阿里来的，很喜欢搞江湖文化，导致他们这些组内“原住民”被边缘化的很惨，新领导安插新嫡系，裁撤老员工，把没人想做的脏活杂活都给他干，导致他的日子也很苦。</p>
<p>离职流程走的很快，从我提出到交接工作到还电脑，也就三周左右，最后一天走的匆匆，交换电脑后，走出园区，那天我没有骑车，难得在离职的下午，一点点晃悠着步行走回了家，重新看了看走了两年半的上班路。</p>
<p>直到当天晚上和朋友吃饭，依然有一种兴奋感，我人生的第一次跳槽，第一次离职，这样的经历真的太令人激动了。</p>
<p align="center"><img src="/static/img/ee67c358121db405ff318168f89ab2f5.IMG_6943.webp" width="40%"></p>
<h2>四月：新的开始</h2>
<p>4月，来到了新的公司，与新的同事共事，并且工作节奏逐渐步入正轨。</p>
<p>令我惊讶的是，新公司有非常多的“老前辈”，不乏有五十多岁的互联网第一代程序员。</p>
<p>新公司不奉行互联网的优胜劣汰与35岁红线，也不奉行自愿付出，自愿加班的互联网PUA文化，而且愿意以相对优渥的待遇来接纳这些大龄程序员，并且这些老前辈的技术也是相当的扎实，不乏有手搓魔改UE引擎的狠人大佬。</p>
<p>与一帮老员工形成对比的，是公司占了近一半的年轻人，而且都很强，清北毕业和哈理工的同事，也有港中文的博士毕业，团队实力相当雄厚。不过由于老板比较念旧情，还有一部分相对水平没有那么强大的同事，在合作时需要手把手去教他们一些东西，因此偶尔沟通会有点头疼，但除此外都让我感觉，新的工作环境相比于充满压力和PUA的大厂好了不少。</p>
<p>总听别人说互联网压抑，压榨，35岁年龄红线，但某种程度上，确实需要鼓起勇气去外面看看才能知其真假。</p>
<blockquote>
<p><strong>小马过河的故事在二十年后依然可以启发每个人。</strong></p>
</blockquote>
<h2>五月：朋友的婚礼</h2>
<p>朋友结婚的日子在五一假期如期到来，第一次参加朋友的婚礼，多少有点激动，虽然24年就已有收到邀请，但真到这一天多少还是有些不一样的感觉。</p>
<p>在婚礼前一天，我们帮朋友布置了婚礼场地，从早布置到晚。</p>
<p><img src="/static/img/8c6979986f34972d9aab114109818466.image.webp" alt="image.png"></p>
<p>婚礼当天进展还是很快的，很顺利的堵门，游戏，婚宴，然后结束，恍若一梦。</p>
<p>一年到头都难聚在一起的朋友也难得乘此机会好好聚了聚。自从工作后，大家都有各自的人生和路要走，能聚在一起说话的机会越来越难。或许下次再像如此，只能是下一个某某结婚。而当所有人步入下阶段后，或许这段友情也只是记忆的一瞬。</p>
<p>返程路上，有种不太真实的感觉，我还处在问爹妈买什么衣服好看的阶段，我朋友居然已经成家进入下一阶段了。我还在跟朋友约晚上开黑通宵的年纪，朋友居然都一个个订婚了。甚至偶尔刷抖音能刷到以前的初高中同学，他们的孩子已经上幼儿园了。</p>
<p>一种荒诞但又现实的矛盾感。但也或许每个人都要如此经历。</p>
<h2>六月：北京</h2>
<p>六月值得一说的是把老爹拐到了北京，二十年没出过山西的老爹（年轻时其实也爱玩），总算到北京玩了两天，也留下了两张标准游客照。（值得一说的是老爹年轻也曾走南闯北，但是成家后仿佛蹲了监狱，半辈子再没为了自己潇洒过）</p>
<p align="center"><img src="/static/img/6c7e9b8ea283609c3c85a0aed42d5444.IMG_2382.webp"width="40%"></p>
<p>曾无法理解为什么老一辈人总是想办法吃苦，总说为了下一代。但以现在的思维出发，依然觉得老一辈人不必给自己那么大压力，儿孙自有儿孙福，何必执着于为子孙兜底。</p>
<p>人生在世，有些东西不经历是永远不会懂的，与其总想着为下一代兜底，不如让下一代趁着年轻多去经历、多去犯错。</p>
<blockquote>
<p><strong>说教无益，折断的骨头才是最好的课本。</strong></p>
</blockquote>
<h2>七月：洛天依！演唱会！</h2>
<p>又一次！洛天依演唱会开到了北京场，叫上了我室友和我妹妹一起，非常nice，体验巨巨好！</p>
<p align="center"><img src="/static/img/460f0edb2413fbfd78a6e2f189bb71b7.9b8772dd4cdf6698f460416458fbb98a.webp"width="40%"></p>
<p align="center"><img src="/static/img/79a263c6c01391b1823bf9d60195e09e.d5ad39947f890d523e4f9697aa1c9581.webp"width="40%"></p>
<p>另外：偶遇抓到了山新：（演唱会的时候山新就在我侧后方坐着，特别特别幸运！）</p>
<p align="center"><img src="/static/img/52874a72cc1c691e988ebbe2b87eb271.7a97b100396b805002bbb37a955caf2f.webp"width="40%"></p>
<h2>八月：日本！</h2>
<p>此生第一次出国，从六月就开始计划，定酒店，买机票，办签证，但直到出发坐上飞机的那一刻，我猜真正意识到，我居然要出国了。恍如梦境，放在十年前还在读书的想都不敢想，在ANA航班上，我充满了激动和好奇。</p>
<div align="center">
<ul>
<li>当时柜台值机的机票：
<blockquote>
<p>不得不说ANA的机票多少有点简陋了，但激动已经难以克制</p>
</blockquote>
</li>
</ul>
  <img src="/static/img/7f0ce30bca47c38737351fc863690651.7A3D3ACA08AEFD8D477D774C9C8A0481.webp" width="50%" />
    <!-- 主图 -->
<div style="text-align:center; margin:60px auto;">
  <img src="/static/img/5f8f921c68ed6d6c29fa5c903fae7a7b.FEF5DCDB0E4228F7411A25E3F7E7462E.webp"
       style="max-width:75%; height:auto; border-radius:8px;" />
</div>
<h3>从起飞到落地的 Day 1</h3>
<p>Day1 落地当天就去了《你的名字》在东京的巡礼。</p>
<!-- 巡礼照片网格 -->
<div style="
  display:grid;
  grid-template-columns: repeat(2, 1fr);
  gap:40px;
  max-width:1000px;
  margin:60px auto;
">
  <div style="text-align:center;">
    <img src="/static/img/fe962bce6e2b42052c2380367d222315.2c056baab658e732d13b3fb0b8923e_livephoto.webp"
         style="width:100%; border-radius:6px;" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/50654c155f99b45aa0b408abf1d488c9.8824D3281F3B140C498149B1E095714F.webp"
         style="width:150%; border-radius:6px;" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/653539e24c938a5f43f80d2c899f1960.C6F19D7FEC2E7FCEE34EB74916A6B4B4.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/b25c087fc0dc7a8b56f1e3d4af21aa72.1BCA6A595BAE6079815B15E36327C9F9.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/15a549b507dc36dbd33400e1f21c069b.17165C7FEB474570A36E5A8A0321F839.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/9acea3b69c300e48b030f296684a20d2.A28CE124CC18F6E6048598943FEB51AB.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
</div>
<!-- 夜景与街道部分 -->
<div style="
  display:grid;
  grid-template-columns: repeat(2, 1fr);
  gap:40px;
  max-width:1000px;
  margin:60px auto;
">
  <div style="text-align:center;">
    <img src="/static/img/f55a65678fd87b7defeffd7cba872860.5FEBE9235D676B9EBE4B400564E3E7BD.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
  <div style="text-align:center;">
    <img src="/static/img/de6959d75884cbbb29cbfc73558507f5.D3FD6F29BAF28325FDF900C7B8383AA0.webp"
         style="width:100%; border-radius:6px; transform:rotate(90deg);" />
  </div>
</div>
<!-- 最后一张收尾图 -->
<div style="text-align:center; margin:60px auto;">
  <img src="/static/img/197622aafad292481e5d652afb6495af.10C8AC3120D2F8700C0FCDC259815C68.webp"
       style="max-width:75%; border-radius:8px;" />
</div>
<p>Day1就这样草草结束，巡礼了君名，去了歌舞伎町（不愧是日式风俗街）。细节就不放了，后续我准备上线一个旅行日记，到时候放在里面（挖坑ing）</p>
<h3>Day2-Day3：初音演唱会+秋叶原+东京城市天际线+美军军事基地</h3>
<p>（篇幅原因。简单放几张印象深刻的，后续细节我放到旅行相册里，因为实在是太多了，东京接头随手一拍就能出片）<br>
演唱会</p>
<p align="center"> <img src="/static/img/fa6f78d62e76795e65f25a660e05c11f.IMG_3640.webp" width="65%" /> </p>
秋叶原
<p align="center"> <img src="/static/img/c7a84defaab01e9892f08bfec32d8d26.IMG_3746.webp" width="65%" /> </p>
东京城市天际线
<p align="center"> <img src="/static/img/86862c21a4a57770e12d1ea08ab193b8.IMG_3681.webp" width="48%" /> <img src="/static/img/ed7d6fd6420c341d030fad30b4cd37b9.IMG_3703.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/8c1c19b60b037ae1ea3374bbaf3b6f1a.IMG_3740.webp" width="65%" /> </p>
街景
<p align="center"> <img src="/static/img/69ddea64531e25903805dc7386873896.IMG_3917.webp" width="48%" /> <img src="/static/img/f21e9d21c8fa61927ad7080da3e484f5.IMG_3796.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/4baeab7deb6f629b00a7094f671a7570.IMG_4170.webp" width="65%" />  <img src="/static/img/e191dd0245646cf3de01e71dbb9e060f.8917cb799ee01de12595e4d627dc4e_livephoto.webp"
         style="width:50%; border-radius:6px;" /></p>
台场（迪迦 & 数码宝贝取景地）
<p align="center"> <img src="/static/img/0ea66cb7c42a9c627871683fba706be1.IMG_3848.webp" width="48%" /> <img src="/static/img/07b9e76430d07bdcfef5ed804424be59.IMG_3841.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/88238eea1e5c6cabcbee9b8bd84d6cd6.IMG_3851.webp" width="48%" /> <img src="/static/img/f732fbff2fc656bd1a9c5964763742c0.IMG_3885.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/604232b29bcd24576cbf445f824060c7.IMG_3872.webp" width="48%" /> <img src="/static/img/4834adb764692e484391f5b21eb811f5.IMG_3901.webp" width="48%" /> </p>
横须贺美军基地
<p align="center"> <img src="/static/img/7524411d61668f0f50f8d4c2875c37ed.IMG_3965.webp" width="48%" /> <img src="/static/img/3f7f4132dc04dbe74df4af1a4a5ec314.IMG_4081.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/5b31408fda62c684eddbbbc850466f5a.IMG_3973.webp" width="48%" /> <img src="/static/img/63b373afc4ab50221a0b1c81d0a17d14.IMG_4041.webp" width="48%" /> </p> <p align="center"> <img src="/static/img/1040e47ba01f01b7c73ca4d1a00c76cc.IMG_4006.webp" width="65%" /> </p>   
<h3>花火大会！</h3>
<div style="max-width:900px; margin:10px auto;">
  <!-- 前两张并排 -->
  <div style="display:flex; justify-content:center; gap:10px; margin-bottom:20px;">
    <img src="/static/img/3765a37593cf2f0582860bdf52ed9a64.8dcc0cc3564177f6a291bdc0ba2faf6d.webp"
         style="width:42%; border-radius:12px; display:block;" />
  <img src="/static/img/b5daff9dee9dcba36254edbb011ac7a1.image.webp"
         style="width:42%; border-radius:12px; display:block;" />
      <img src="/static/img/b8d9e12de69c2ee97f3bdbc3c78c1204.a8c4626d7aab505ff6ccedbe68d76f46.webp"
         style="width:42%; border-radius:12px; display:block;" />
  </div>
</div>
<p>花火大会随拍</p>
<div style="max-width:900px; margin:10px auto;">
  <!-- 前两张并排 -->
  <div style="display:flex; justify-content:center; gap:10px; margin-bottom:20px;">
    <img src="/static/img/7b5a7132ba21a63196902d2c440d52ea.IMG_4209.webp"
         style="width:42%; border-radius:12px; display:block;" />
  <img src="/static/img/fbc1d3886913bb84915cb38e9950fdc1.IMG_4213.webp"
         style="width:42%; border-radius:12px; display:block;" />
      <img src="/static/img/3ba098064db6dcf32f4eacf9f7c5010c.IMG_4180.webp"
         style="width:42%; border-radius:12px; display:block;" />
  </div>
</div>
<p><img src="/static/img/21257fe7797730f3edf8b0dee8f503c4.image.webp" alt="image.png"></p>
<h3>day4–day5：诹访湖</h3>
<blockquote>
<p>《你的名字》三叶故乡取景地</p>
</blockquote>
<p align="center">
<img src="/static/img/02a1d85b5aa156d43be48eae48be38ae.IMG_4484.webp" width="75%">
</p>
<p align="center">
<img src="/static/img/abf5ba8b920b0937afd101324f7132aa.IMG_4598.webp" width="48%">
<img src="/static/img/300929ecc0c8378c02d414f42aa723bf.IMG_4433.webp" width="48%">
</p>
<p align="center">
<img src="/static/img/73ae300f44dbdcbc856d31f93c3dadff.IMG_4425.webp" width="48%">
<img src="/static/img/abd1322f3d51586b06166ceb2a0877ec.IMG_4617.webp" width="48%">
</p>
<hr>
<h3>其他</h3>
<h4>日本乡村景</h4>
<p align="center">
<img src="/static/img/2e75be56957e4f9967856a82deb1f5d9.IMG_4360.webp" width="75%">
</p>
<h4>涩谷十字路口</h4>
<p align="center">
<img src="/static/img/ae67fd118d1abef5c30704edaff56557.IMG_4755.webp" width="75%">
</p>
</div>
<h2>9-12月</h2>
<p>这几个月没有发生什么，只是简单的在工作，推进各个节奏。回味着上半年的种种。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/日常">日常</category>
        </item>
        <item>
            <title><![CDATA[2025-年度总结]]></title>
            <link>https://umb.ink/post/45</link>
            <guid>https://umb.ink/post/45</guid>
            <pubDate>Fri, 20 Feb 2026 06:12:27 GMT</pubDate>
            <description><![CDATA[<h1>引言</h1>
<p>一年又一年。恍惚之间，2025 又匆匆走到了尾声。转眼毕业也将近三年，光阴如隙，来不及细数。</p>
<p>原本想简单聊聊这一年的经历，可细想之下，似乎并没有什么惊天动地的大事。可当真正回头望去，才发现，这一年竟也经历了太多值得铭记的瞬间。</p>
<p>一时语塞，索性就从回顾开始。</p>
<h1>关键词</h1>
<p>回顾我的 2025，给它一个关键词的话，我会选择——“经历”。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>引言</h1>
<p>一年又一年。恍惚之间，2025 又匆匆走到了尾声。转眼毕业也将近三年，光阴如隙，来不及细数。</p>
<p>原本想简单聊聊这一年的经历，可细想之下，似乎并没有什么惊天动地的大事。可当真正回头望去，才发现，这一年竟也经历了太多值得铭记的瞬间。</p>
<p>一时语塞，索性就从回顾开始。</p>
<h1>关键词</h1>
<p>回顾我的 2025，给它一个关键词的话，我会选择——“经历”。</p>
<!-- more -->
<p>这一年，我经历了工作的变动，住所的迁移，生活环境的改变；也经历了朋友的来来往往，人际关系的靠近与疏远。</p>
<p>乍看之下，好像没有什么轰轰烈烈的大事件，但恰恰是在这些琐碎而具体的小事里，堆叠出了属于这一年的厚度。</p>
<p>它们或许平凡，却真实；或许细碎，却深刻。每一段经历，都留下了一些难忘的回忆，也沉淀出一些值得珍惜的经验。</p>
<p>慢慢地我开始意识到，人生或许并不在于完成多少宏大的目标，而在于去经历那些未曾经历的事情，在经历中体会，在体会中成长。</p>
<h1>时间线</h1>
<p>篇幅所限，具体的时间线整理在这里：（图比较多，加载图片有可能比较慢，请见谅）<br>
https://umb.ink/post/46</p>
<h1>为什么是“经历”？</h1>
<p>相比身边的同龄人，我算是比较早进入社会的一批。没有复读，也没有 gap 去考研或考公，毕业后便直接投入工作。再加上一些并不算深厚、却也真实存在的大厂经历，以及偶尔兼职做职业规划、提供咨询的过程，让我有机会接触到形形色色的人，也听到了许多不同的人生故事。</p>
<p>后来慢慢发现——</p>
<ul>
<li>很多迷茫，本质上是因为经历太少；</li>
<li>很多焦虑，本质上是因为未知太多。</li>
</ul>
<p>而“经历”对人的塑造，是极其深刻的。</p>
<blockquote>
<p>经历多了，遇事才会坦然。</p>
</blockquote>
<p>我曾接触过一些长辈，已近“知天命”的年纪，却在某些问题上依旧像孩童一般。固守旧有观念，排斥新鲜事物；面对未知时慌张无措，甚至本能地抗拒改变。</p>
<ul>
<li><strong>年龄，并不天然带来成熟；</strong></li>
<li><strong>时间，也不会自动转化为见识。</strong></li>
</ul>
<blockquote>
<p>真正让人沉稳的，从来不是年岁，而是经历。</p>
</blockquote>
<p>当你见过足够多的选择，听过足够多的故事，踩过足够多的坑，你自然会明白——很多事情不过如此，很多焦虑也终将过去。</p>
<p>于是，2025 对我而言，不是“成就”的一年，而是“经历”的一年。<br>
而经历，本身，就是一种成长。</p>
<blockquote>
<p>经历越多，越能看清自己，也才能真正成长。</p>
</blockquote>
<p>在做咨询的时候，我常会遇到两类极端的朋友：<br>
一种是“优越感爆棚”，一种是“妄自菲薄”。</p>
<p>最典型的，是前者。</p>
<p>有些自身条件并不占优的人，却常常说：</p>
<blockquote>
<p>“非大厂配不上我，不去。”</p>
<p>“年薪不到 30 万没必要工作。”</p>
<p>“我才不要做销售，不体面。”</p>
<p>“人要对自己好一点，贷款也要买车。”</p>
</blockquote>
<p>这些话听起来像是自信，甚至像清醒，但——真的如此吗？</p>
<p>不可否认，每个人都有自己的价值观。但从我接触过的许多人和故事来看，过度的自我优越，往往来源于<strong>缺少真实挫折的经历</strong>。</p>
<p>他们未必家庭富裕，却往往被父母尽力托举。父母替他们承担了生活的重量，他们却从未真正感受过生活的重量。<br>
于是，在缺乏参照与碰撞的情况下，容易高估自己的能力，同时低估现实的门槛。</p>
<p>更矛盾的是——<br>
一旦真正进入社会，面对赤裸裸的竞争与筛选，这种“理想自我”和“真实自我”的落差，会在瞬间崩塌。<br>
很多人会从自负直接跌入自卑，甚至难以承受压力，走向极端。</p>
<hr>
<p>而另一类人恰恰相反。他们常说：</p>
<blockquote>
<p>“我配不上。”</p>
<p>“我什么都不会，肯定找不到工作。”</p>
<p>“我还是听父母的，去流水线踏实一点吧。”</p>
</blockquote>
<p>这些人往往成长于压迫感更强的环境。父母能力有限却控制欲强，从小被否定、被比较、被要求“懂事”。<br>
长期的评价体系让他们形成强烈的“不配得感”——即使别人认可，他们也难以相信。</p>
<p>于是做事畏缩，机会面前先退一步。</p>
<p>鼓励可以短暂提振，却无法真正改变，因为问题不在认知，而在经历的缺失。</p>
<hr>
<p>仔细观察会发现，这两种看似相反的性格，本质却相同——</p>
<ul>
<li><strong>都没有经历过足够真实、足够匹配的反馈。</strong></li>
</ul>
<p>如果自负的人，从小被引导去直面困难，他可能会成为真正自信的人；</p>
<p>如果自卑的人，拥有几次可验证的成功体验，他的成长速度往往极快。</p>
<p>所以人的发展，很像“小马过河”。<br>
别人告诉你深浅没有意义，只有自己试过，河才有刻度。</p>
<p>经历越多，能力边界才会清晰：</p>
<p>面对超出能力的事敢于挑战，面对能力之内的事游刃有余。</p>
<p>因此，多经历——<br>
无论好事坏事，都是成长的组成部分。</p>
<hr>
<blockquote>
<p><strong>任何经历，都是一堂生动、珍贵的一课</strong></p>
</blockquote>
<p>仔细回想，前些年在大厂加班加的死去活来的经历，初到北京被街道办强拆出租屋的经历，找工作时被内卷疯狂PUSH的经历，恋爱时吵的不可开交的经历，高考时学习学到昏天黑地的经历....这些在当时的我看来都是生活带给我的一些“苦难”，但从现在的我看来，这些都是生活教会给我的珍贵一课。</p>
<ul>
<li>大厂的加班让我学会了如何提高自己的工作和协作效率，也更加明白了身体最重要。</li>
<li>虽然被强拆了出租屋，但也第一次学会了冷静面对突发意外，学会了如何与不讲理的老登回旋。</li>
<li>找工作的内卷让我明白了自己的上限在哪里，也明白了量变一定会引起质变。</li>
<li>恋爱的矛盾让我明白了如何与异性相处，如何处理好思维方式差异导致的矛盾。</li>
<li>高考时的苦难学习让我在日后遇到艰苦的环境时，会下意识想，“这算啥，高考早五晚十都经历过了”。</li>
<li>北漂的生活虽苦，但是让我认知和见识涨了非常多，也让我第一次意识到，人可以舍弃所有所谓的“责任”，去追寻自己所想的未来。（当然，也可以说提升了幸福感，毕竟对比之下，没有什么比小镇做题家从0开始北漂更苦的事情了）</li>
</ul>
<blockquote>
<p><strong>经历越多，路也会越走越宽。</strong></p>
</blockquote>
<p>偶尔朋友会打趣我：“你怎么总能听到这么多故事，而且很多还挺惨的？”</p>
<p>这其实是个因果倒置：<br>
幸福的人往往不需要倾诉；<br>
不幸的人，需要出口。</p>
<p>很多故事之所以沉重，是因为它们必须被说出来，否则当事人就要独自承担全部重量。</p>
<p>也许正因为我平时会帮人做规划，也喜欢聊一些“歪理”，不少人遇到事情会来找我聊聊。</p>
<p>于是虽然我很宅，却认识了很多人；<br>
我经历不多，却听过很多故事。<br>
而这些间接的经历，同样在塑造一个人。</p>
<h1>后记</h1>
<p>回头看，2025 并没有发生什么决定命运的大事。<br>
但我开始理解一件事：</p>
<p>成长不是因为某个瞬间改变了人生，</p>
<p>而是经历让你慢慢不再需要改变人生。</p>
<p>这一年不是“有所成”的一年，</p>
<p>而是第一次不再着急“有所成”的一年。</p>
<p>而经历，本身，就是答案。</p>
<h1>新年愿望</h1>
<p>身体健康</p>
<p>升职发财</p>
<p>去一趟欧洲旅行</p>
<p>去新疆旅行</p>
<p>学会一首完整的双手钢琴曲</p>
<p>看到更多、更美好的故事</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[如何培养工程师思维？]]></title>
            <link>https://umb.ink/post/44</link>
            <guid>https://umb.ink/post/44</guid>
            <pubDate>Wed, 10 Sep 2025 15:17:13 GMT</pubDate>
            <description><![CDATA[<h1>引言</h1>
<p>这篇文章主要写给还没毕业或刚刚毕业的同学，希望通过这篇文章，能够让大家从学生思维跳脱出来，向真正的工程师思维进行转变，更好地提升自己。</p>
<h1>学生思维和工程师思维有什么区别？为什么要转变？</h1>
<p>我们说的工程师思维不仅仅是局限于做工程师的人，而是拥有像工程师一样解决问题的能力，推进项目落地的思维方式。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>引言</h1>
<p>这篇文章主要写给还没毕业或刚刚毕业的同学，希望通过这篇文章，能够让大家从学生思维跳脱出来，向真正的工程师思维进行转变，更好地提升自己。</p>
<h1>学生思维和工程师思维有什么区别？为什么要转变？</h1>
<p>我们说的工程师思维不仅仅是局限于做工程师的人，而是拥有像工程师一样解决问题的能力，推进项目落地的思维方式。</p>
<!-- more -->
<p>在学生时代，我们习惯了“老师布置什么，我就做什么”的学习模式——作业按要求完成、考试按范围复习，这种“等待指令”的思维惯性让知识接收高效运转，但是不会主动思考去解决问题，过度关注局部/短期的事物，这就是我们常说的“学生思维”。而工程师思维与之不同，工程师思维强调自己探索，从全局角度整体思考，要具有能够独立解决复杂问题的能力。相比学生思维，工程师思维更加严谨，更加主动。</p>
<p>进入社会、跨入职场后，我们需要主动解决问题的能力，需要有从全局角度思考的综合性思维。因此学生思维是无法帮助我们继续成长的，我们要从“知识接收者”到“问题解决者”的角色转型。</p>
<blockquote>
<p>举个例子：<br>
现在学校里面要做一套校园门禁系统，并且校长把这个项目交给你来做，此时你会如何着手？</p>
<p>如果是普通学生，会想如何设计门禁硬件，可能会用esp32做门禁闸机板，可能会用java写后台门禁管理系统，但大部分情况下，当学生按这个思维设计下去之后会发现，做出来的东西完全不能用，也不好用，项目也推不动。这是因为学生思维惯性，<strong>只关注</strong>到了“门禁系统”这一个具体的<strong>局部系统</strong>，而不是整个项目。做项目，做工程不仅仅只有浮于表面的硬件设备或代码平台，还包括了上下游一系列的用户和利益相关方，包括了对项目未来可能会存在的一系列风险的预估。这些是学生思维所无法涵盖的。</p>
<p>而对于有一定成熟经验的工程师，“门禁系统”在设计时，会考虑很多前置问题，它的受众群体到底包括哪些人？如何让学校内的每个人都能接受新的门禁系统？如何与相关方高效沟通？在硬件选型上，如何保证闸机运行的稳定性？供电、网络稳定性如何保证？项目推进时需要涉及哪些校内部门，需要和哪些领导打交道？这些全部确认后，后续才是如何着手落实这个项目。</p>
</blockquote>
<p>因此，学会工程师思维，能够帮助我们跳脱出认知困境，站在全局角度思考问题，更好地推动问题解决或项目落地，也可以更好地辅助我们做完美的决策。</p>
<h1>如何转变？</h1>
<h2>1. 改变环境</h2>
<p>拥有工程师思维离不开真正的“工程师环境”，也就是要在实际的工程化环境，真正致知于行，才能更好的学习和培养工程师思维。在学校这个相对与社会隔绝的环境中，很难有机会接触到真正的落地项目。</p>
<p>有些同学可能参加各种创新创业大赛，做一些打比赛的项目，甚至是做一些小规模的校内创业项目，这些项目虽然一定程度上可以帮助我们初步积攒一些经验，了解到一定量的工程化思想。但是跟实际的公司、社会环境中的落地项目相比，依然相差甚远。</p>
<p>因此，在个人能力满足的情况下，我建议大家多多出去到社会企业中进行调研、实习，哪怕去到比较底层，非常草台班子的初创公司。特别是前往科技较为发达的一线、新一线城市去，实习，工作。真正切身实地感受与人沟通、规划资源、学习职场前辈的各种工作方法论，在这个过程中逐渐培养起工程师思维，一点点升级打怪。</p>
<p>大家需要明白一个事情，学校能教的有且只有书本和老师PPT上的内容，非常有限，学校不会教你文档怎么写，PPT如何做，如何与人沟通，如何快速解决问题等等，只有切身到真正的社会中，才能学会这些东西。正如古话所言纸上得来终觉浅，绝知此事要躬行。</p>
<h2>2. 学会自我寻找答案</h2>
<p>在程序员的职业发展中，有一个文章叫做“提问的智慧”，是所有程序员的必修课，而我认为这是所有人的必修课，每个人都应该学会“如何向别人提问”，学会自我解决问题，学会如何正确从他人那里获取知识。</p>
<p>自我解决问题与向他人寻求答案并不冲突，自我解决问题的过程中，合理向他人寻求帮助也是一种很好的方法，但是这不代表所有事情都要依赖于他人，向他人寻求答案的本质是将答案转变为自己的能力。如果一味地依赖他人，不自己探索，那么问题永远存在，永远不属于自己。</p>
<p>那么如何学会自我探索，自我寻找答案呢？</p>
<ol>
<li>用好搜索引擎/AI问答：搜索引擎和AI是一个非常强大的工具，有任何疑问，先从搜索引擎寻找答案，推荐谷歌、其次是bing，最不推荐百度（百度害人，很容易出现一个问题搜索下去一整页全是广告）</li>
<li>使用思维导图，梳理问题链路。在遇到问题时先梳理问题关系，对问题进行拆解，尝试多多使用思维导图和流程图，去寻找问题关键点。举例：我要找工作，那么我就要从头梳理我找工作这个问题，首先我想要什么工作？然后我的期望薪资是什么?我的能力有哪些？我预期的工作岗位要求有哪些？将这些问题作为一个个思维图进行导出，然后寻找他们之间的关联。</li>
</ol>
<blockquote>
<ol start="3">
<li>举例：我通过拆解“我要找工作”这个问题来绘制思维图。把一堆生涩的招聘要求和我个人的能力直观地呈现出来，寻找之间的关联，更好地让我能够看到我的短板和我的长处。</li>
</ol>
</blockquote>
<p><img src="/static/img/65deb074e511f90014d44dde4b4f95ff.image.webp" alt="image.png"></p>
<h2>3. 多模仿，多看多做</h2>
<p>不论是在校园内，还是在社会中，在我们身边一定有很优秀的人，他们思维敏捷，做事雷厉风行，那么他们就是我们学习的榜样。我们可以多多观察这些人在工作时，在沟通时的行为逻辑和方式，甚至可以自己悄悄进行记录或大胆与他们单聊请教，了解他们的思维方式和学习方式（当然，前提是方式要合理，要尊重别人）。而与优秀的人聊天，每每都会给我们不同的新启发。并且很多优秀的人也非常乐于分享自己的经验和经历，与优秀的人沟通一定是一件对自我提升很大的事情。</p>
<p>吸纳不同人的思维，学习不同人的做事方式，最终转变为自己的风格，长此以往，不知不觉中我们将会也变成别人口中“优秀的人”。</p>
<p>但需要注意的是，不是所有人思维逻辑都适合无脑学习，一定要结合自己的实际情况。东施效颦不可取。</p>
<h2>适度去拓展眼见</h2>
<p>认知，眼见的拓展也是褪去学生思维的一大核心，当自己的眼见拓宽，会看到很多不同的事物，而思想也会更加全面，更加容易找到问题切入点。</p>
<p>例如在大学生时期，很多人不会把“图书管理系统”当回事，觉得这种东西只是很简单的一个课程设计级别的CRUD系统，但倘若真正见识过企业级的图书管理系统，就完全不会这样想，企业级的图书管理系统要考虑稳定性，考虑易用性，考虑数据安全，服务稳定，可能作为学生不会觉得服务宕机几分钟或者丢失一些数据是什么大事，但在企业层面，几分钟的服务不可用可能就是数百万不可挽回的损失。而倘若没有真正见过几分钟亏损几百万的场面，大多数学生是无法想象到服务稳定性是多么重要的一件事。</p>
<p>因此，适度地去参加各种科技展会，技术沙龙，开拓自己的眼见（甚至可以适当地放弃一些校内课程来参加各类科技展会），我相信展会带来的眼界拓展一定比学校的落后课程的作用更大。</p>
<h1>结语</h1>
<p>最后我想说，从学生思维到工程师思维的转变，从来不是一蹴而就的瞬间蜕变，而是一场需要耐心与实践的长期修行。当我们放下&quot;等待指令&quot;的惯性，开始主动扛起解决问题的责任；当我们跳出课本里的标准答案，学会在现实的复杂约束中寻找最优解——这种思维方式的进化，不仅会让我们在职场中走得更稳，更会成为我们终身成长的底层能力。</p>
<p>记住，真正的工程师思维，不只是写代码、做项目的专业技巧，更是一种&quot;把不可能变成可能&quot;的信念。它教会我们在迷雾中找到方向，在阻力中开拓路径，在质疑中坚持价值。或许起步时会有笨拙与迷茫，但每一次主动思考、每一次解决问题、每一次突破认知边界，都会让我们离&quot;优秀工程师&quot;的目标更近一步。</p>
<p>愿你从此刻开始，带着这份思维的&quot;工具箱&quot;，勇敢地走向更广阔的世界。不必害怕犯错，不必焦虑速度，因为每一次实践都是蜕变的契机。当你真正用工程师的眼睛审视世界，用工程师的双手改变现实，你会发现：成长，永远比想象中更精彩。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[cursor-大模型锁国区问题解决]]></title>
            <link>https://umb.ink/post/42</link>
            <guid>https://umb.ink/post/42</guid>
            <pubDate>Tue, 22 Jul 2025 03:28:00 GMT</pubDate>
            <description><![CDATA[<ol>
<li>打开你的cursor，选择setting-&gt;network-&gt;选择http1.1</li>
</ol>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <ol>
<li>打开你的cursor，选择setting-&gt;network-&gt;选择http1.1</li>
</ol>
<!-- more -->
<p>setting在界面右上角<br>
<img src="/static/img/02c1ef2125ee0c19dc11e2cce9bc937f.image.webp" alt="image.png"><br>
2. 打开你的魔法工具的全局代理模式，然后cursor就正常了。</p>
<p>什么？你不知道什么是魔法工具？你可以退出程序员这个行列了。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[游戏服务开发-DS服务架构设计]]></title>
            <link>https://umb.ink/post/40</link>
            <guid>https://umb.ink/post/40</guid>
            <pubDate>Tue, 06 May 2025 08:41:51 GMT</pubDate>
            <description><![CDATA[<h1>背景</h1>
<p>最近接手了一个新游的架构设计，因为新游偏向社交类游戏，单个游戏体内会存在多人联机、多种不同类型游戏对局的交互，即要考虑多种不同玩法服务的兼容和多人联系情况下的稳定性。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>背景</h1>
<p>最近接手了一个新游的架构设计，因为新游偏向社交类游戏，单个游戏体内会存在多人联机、多种不同类型游戏对局的交互，即要考虑多种不同玩法服务的兼容和多人联系情况下的稳定性。</p>
<!-- more -->
<h1>问题</h1>
<p>传统的C/S架构中，可以一定程度上容忍server侧的变更或者某个实例突发宕机的情况（大不了重新请求一下就好了），但游戏架构要求多人联机场景下的稳定性和兼容性，无法容忍游戏联机过程中server中某个实例突发宕机或扩缩容的情况，因为当一个容器突发宕机，那就意味着那个容器上的游戏对局将会受到影响，严重影响用户体验。</p>
<p>我们先来看看使用C/S架构开发游戏的架构模式：</p>
<p><img src="/static/img/3487f1f632f7ce34fe14414921cb1986.image.webp" alt="image.png"></p>
<p>从这个架构中可以明显看出下面的这些问题：</p>
<ol>
<li><strong>数据同步链路长</strong>：在一场对局中，传统C/S架构下，每个客户端WS所连接的Server实例可能不一样，并且同一个对局内的数据流同步必须依赖中间件进行，数据流传输链路长，高负载请求下，可能出现延迟感知明显的问题。</li>
<li><strong>稳定性不足</strong>：假如某个实例宕机，此时对应的用户会突然断开连接并丢失部分操作请求，影响对局进程。</li>
<li><strong>延迟较高</strong>：客户端可能分布在世界各地，每个客户端到服务的延迟都不同，在多人游戏对实时性要求较高的情况下，不同的游戏延迟会导致用户感知到的对局数据不同，影响游戏体验</li>
<li><strong>开发/维护成本较高</strong>：即使在不同的游戏模式下，都需要单独在不同模式的游戏serevr中实现相同的对局、房间管理逻辑，而客户端需要根据不同的游戏模式去连接不同的对局服务器，额外增加成本。</li>
</ol>
<h1>DS</h1>
<p>没有什么是设计一个中间层解决不了的。面对多人联机社交游戏场景下的服务设计，我们引入DS服务层的概念来解决这个问题。</p>
<p>DS，全称叫做Dedicated Server（专用服务器），它可以用来给多人联机时提供稳定的房间信息管理和对局进度管理以及用户客户端的WS连接管理，并且一定程度上可以降低我们多游戏业务的开发成本。</p>
<p>那么我们来引入DS看下引入DS之后的架构：</p>
<p><img src="https://umb.ink/static/img/edf294b8d94d2e288f8cf375411164b6.image.webp" alt=""></p>
<h1>DS优势</h1>
<p>可以看到，引入DS之后，每个游戏客户端所连接的WS服务节点由DS统一调度管理，提高整个系统的可维护性。</p>
<ol>
<li>
<p>在同一个对局内，每个客户端连接的对局服务器在同一个实例上面，这样可以最大程度减少数据同步带来的性能损耗。</p>
</li>
<li>
<p>数据会在DS内部的cache实例上缓存，假如在对局运行中某个DS实例崩溃，那么此时DS网关可以快速将实例上的所有连接转到其他实例，并从cache中把相关数据load到新实例里面，在用户端的感知也仅仅只是短暂断线重连，而且作用于同一对局内的所有用户，可以保证游戏对局进程影响最小化。</p>
</li>
<li>
<p>得益于DS收束了所有的WS代理连接，那么我们就可以支持游戏客户端在一个游戏场景中，同时使用多个游戏WS服务，而客户都只感知到了一个WS数据流，多个WS客户端有DS实现WS映射。</p>
</li>
</ol>
<p>例如：在一个游戏场景中，存在房间管理的WS、用户上下线的WS、用户沟通消息RTC信令、游戏玩法的对局数据WS等多个WS连接点，有DS的接入，客户端只需要接入一个DS的ws即可，如下架构图：</p>
<p><img src="/static/img/c065741b12b724c2a7c52b9d2ee8c2d8.image.webp" alt="image.png"></p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[我们的年轻人失去心气了吗？]]></title>
            <link>https://umb.ink/post/39</link>
            <guid>https://umb.ink/post/39</guid>
            <pubDate>Thu, 10 Apr 2025 03:58:51 GMT</pubDate>
            <description><![CDATA[<h1>引言</h1>
<p>  近来多与身边的朋友或网友闲聊，发现大家在25岁前后的这个阶段，在聊起人生目标与计划时，很多都丧失了“心气”，尽显颓势，感受不到年轻人应有的激情。</p>
<p>  这倒并不能说错，每个人都有自己的人生追求与规划。只是这令我感到比较疑惑，在25岁这个年纪，原本应当积极向上，朝气蓬勃的年纪，却总给人一种已经年过花甲的释然、沉闷和随时等死的“丧气感”。</p>
<p>  网上近些年也有个段子很火，精力充沛的老年人和死气沉沉的年轻人，应该让老年人替年轻人上班，年轻人替老年人退休。</p>
<p>  这不禁让我思考，为什么我们年轻人都失去心气了？</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>引言</h1>
<p>  近来多与身边的朋友或网友闲聊，发现大家在25岁前后的这个阶段，在聊起人生目标与计划时，很多都丧失了“心气”，尽显颓势，感受不到年轻人应有的激情。</p>
<p>  这倒并不能说错，每个人都有自己的人生追求与规划。只是这令我感到比较疑惑，在25岁这个年纪，原本应当积极向上，朝气蓬勃的年纪，却总给人一种已经年过花甲的释然、沉闷和随时等死的“丧气感”。</p>
<p>  网上近些年也有个段子很火，精力充沛的老年人和死气沉沉的年轻人，应该让老年人替年轻人上班，年轻人替老年人退休。</p>
<p>  这不禁让我思考，为什么我们年轻人都失去心气了？</p>
<!-- more -->
<h1>先问是不是，再问为什么</h1>
<p>  我偶然会与身边身处各个人生阶段的同龄朋友沟通，在我的留意下，尚且还在上学的，心气最足，幻想着未来的一切可能。毕业后两年的，心气最弱，不乏已经有将“老婆孩子热炕头”作为人生终极理想、混吃等死作为人生规划的。毕业两三年依然在啃老，做着“考公、考研、考编”三部曲来逃避人生的也大有人在。</p>
<p>  毕业后在北京北漂了两年的我，在西二旗这个牛马聚集地，也见识到了诸多的无奈。大厂中拿着几十万甚至百万年薪的打工人，终此一生的目标是背上天价的贷款，在北京买套房，甘愿被公司压榨自己的健康与时间，生活中只剩下了工作与睡觉（有的连睡觉都不能满足）。</p>
<p>  当问及这些北漂打工人的人生目标与规划时，许多人含糊不清，好似除了上班已经再无其他说的出口的生活。为了在北京落户买房，然后呢？背上30年的房贷，每个月60%的工资全部拿去还房贷，剩下的勉强够温饱。当房贷还完，人已暮年，坐等西去。</p>
<p>  当然，每个人都有自己的的人生规划与活法，我们无权对他人的生活规划指指点点，只讨论大家为什么失去心气这个问题。至少我认为，我们的生活不应该只有无休止的工作，我们的理想不应该是为了在忙碌的大城市求得一隅安置，我们的目标不应该只是为了生存。</p>
<h1>环境问题</h1>
<p>   到这里可能大家会思考一个问题，是我不想去追求梦想吗？是我不想休息吗？是我要生存呀。那这就归到了一个客观因素上，国内目前的工作环境极其苛刻，有班上的人，面对的是无时无刻的劳动力压榨和职场PUA；没有班上的人，面对的是日趋增长的物价和逐渐贬值的存款。因此，大家不得不去内卷、去把自己的生命献给资本家，去用自己的梦想、自己的健康、自己的未来 换回在社会中能够短暂生存的资源。</p>
<p>当&quot;生存&quot;成为人生主旋律时，&quot;生活&quot;便成了橱窗里的奢侈品。我们不得不正视一个残酷的现实：在经济增速放缓与阶层固化加剧的双重挤压下，年轻人陷入了&quot;高学历-高负债-低幸福感&quot;的莫比乌斯环。大学毕业前，所有人都在告诉年轻人，说他们未来可期，是最幸福的一代，鸡娃教育也从小就告诉年轻人，大学念完就轻松了，就熬出头了，但是从来没有人告诉年轻人，真正的社会是什么样的。当温室的花朵怀揣从课本上了解到的片面知识进入残酷的社会，极大的落差是令很多人无法接受的。</p>
<p>许多985院校的毕业生的第一份工作很多也只能勉强覆盖北京单间租金，在北京能得温饱，这种结构性的经济压力，让&quot;诗与远方&quot;在生存算术题面前显得格外苍白。在这种生存的落差下，年轻人所追求的美好生活与心气，自然日趋减少，甚至开始对生活祛魅，对理想祛魅，逐渐向往当下的享乐与逃避躺平。</p>
<h1>价值观问题</h1>
<p>当婚恋市场用房产证丈量人生价值，就业市场只看每个人的剩余可压榨价值，那些关于艺术创作、社会创新或自由职业的人生规划，往往被贴上&quot;不务正业&quot;的标签，整个社会都弥漫着畸形的价值观，一个人生来要 内卷、上学、补习班、遵纪守法然后听从号召结婚、养家、背债、消费、养娃。</p>
<p>总之社会希望底层的普通人是什么样，那就要是什么样。当社会需要普通人进厂时，那我们就要脱下孔乙己的长衫进厂打工。当需要普通人为家庭付出时，那我们就要早早结婚，身负房贷，掏空积蓄支付彩礼、首付，身下孩子身上父母，我们的行为和价值观永远只能被普罗社会所裹挟，至于年轻人自己？谁在乎。再加上短视频吃人血馒头的营销号们无孔不入，不断放大这些畸形的价值观，强烈的不适感不断裹挟着我们这代人。</p>
<p>持续多变的畸形价值观与年轻人朝气蓬勃，充满理想的价值观格格不入，在剧烈的冲突下，面对生存的压力，家庭的压力，另一半的压力，不得不低头。但是这就好像没有人喜欢上班一样，强迫一个人接受他不愿意接受的事物和观念，自然会磨损心理。</p>
<h1>教育问题</h1>
<p>我们上一代人，特别是上一代的基层父母，大多认知相对短浅（毕竟也没资源去见世面），保守封建，鸡娃倾向严重，忽略孩子的生长环境和心理健康培养。这种教育模式培养出的年轻人，往往在人生前二十年的成长中只学会了两件事：服从权威与参与竞争。</p>
<p>从小学到高中的十二年间，学生被切割成无数个标准化零件，在&quot;提高一分干掉千人&quot;的残酷竞争中，逐渐将人生价值等同于分数和排名。当这些年轻人走出象牙塔后却发现，社会竞争的维度远比试卷上的选择题复杂得多。</p>
<p>更致命的是，基础教育与社会需求存在严重的结构性错位。高校专业设置与产业升级速度严重脱节，特别是很多高校不思进取，很多教辅资源还是几年前甚至十几年前的内容。</p>
<p>以我读的计算机专业为例，大学很多教材里面的内容还是上个世纪的内容，还在教非常落后的技术（能教spring boot已经是非常先进的内容了），而我们的时代需要的是各种新兴生产力。整天在大学中教授老掉牙的东西，却又希望学生早日找到一份好工作，这合理吗？</p>
<p>很希望我们的大学老师能够走出象牙塔，真正地去社会公司里面体验下新兴的技术是什么样的，我指的不是去跟旅游一样去人家公司参观，而是真正以一个技术人才的角度去入职，去体验公司里面的技术需求和各类流程，好好思考一下社会所需要的到底是什么样的人才。</p>
<p>学生作为信息闭塞的一方，带着自己大学四年积攒的令其自豪的知识进入社会，发现社会早就淘汰了老掉牙的知识，这个时候学生又会受到多大的打击？长此以往，自然心气大受打击甚至有些极端的年轻人在进入社会后选择自暴自弃，草草结束坎坷的一生。</p>
<h1>破局</h1>
<p>有的年轻人并非喜欢低迷的心气，更多只是对现实生活的无奈，但又寻求不到破局的方法。这里结合博主自己的一些经历谈谈我的看法。</p>
<ol>
<li><strong>阶段性享受，摈弃常态化高压工作</strong>：</li>
</ol>
<p>我并不推崇享乐主义，相反我认为享乐主义不可取。但常态化高压工作更不可取，适度让自己享受一下，去听许久期待的演唱会、去看从小想去看的世界、去做任何想做的事情，不要害怕花钱。</p>
<p>我们大部分都是底层人，几乎不可能靠加班和努力跨越阶级，不断地高压工作只会折损自己的精神状态和身体健康，那不如阶段性地放下一切去讨好自己，天是塌不下来的。月赚三千照样生活，月赚三万也照样生活，不追求结婚养娃，人生在世，只为活自己就好。什么？你说未来生病急需钱用怎么办？那就去死呗，年轻享受年老早死难道不是一件幸事吗？。</p>
<p>对自己任性一点，其实90%的人活着也对社会没什么特别大的价值，我们是社会的边角料，但却是自己的全部，所以对自己好一点，以实现自己的人生价值为第一优先，至于别的，爱咋咋地。</p>
<ol start="2">
<li><strong>端正自己的位置，屁股决定脑袋</strong>：</li>
</ol>
<p>屁股决定脑袋，如果你是富二代，那你就去做富二代该做的事情，去想富二代该想的事情。如果你是底层的普通人，那你就多关心自己的事情，关心明天吃什么，关心家里人健康如何，关心自己该做什么。而不是整天替老爷们担心社会经济怎么样，公司怎么样，你是打工人那就关心自己怎么样能做好工作，早日升职加薪。端正自己的位置，不考虑自己改变不了的事情。</p>
<p>或许，有的人会说，那些我无法改变的事情会影响我自己的工作和生活。那又如何呢？即使担心也无法改变不是吗？养成一个思维链：这个事情和我有没有关系？-&gt; 这个事情会不会影响我的正常工作生活-&gt; 这个事情我可以改变吗？ -&gt; 如果有一个答案是否定的，那这个事情你没必要思考。顺其自然爱咋咋地，天塌不下来，只要死不了都是小事，你都没有办法的事情，担心也没用。</p>
<p>另外，明确自己适合做什么也很重要，也就是说要对自己的能力和认知有一个清晰了解。比如我目前是一名程序员，技术还不错，做全栈工程师或者架构师，那我在这个阶段就不考虑自己能力以外的事情，不对自己能力以外的东西投入过多的精力。我赚程序员这份钱，我就不专门投入精力考虑怎么赚自媒体的钱，因为我认为我目前的能力与性格不适合去做，只会凭空消耗自己的精力。再比如我是一个大学生，我技术能力很强，但是我不爱说话，而且不喜欢理论知识，只喜欢实践。那我就绝对不考虑考研考公，而是去钻研技术，进入大厂。</p>
<p>但是，明确自己的适合做什么这是一个阶段性动态变化的变量。比如我现在在做架构师，但是随着业务扩张，我可能要开始带团队，逐渐有了带团队的经验，那么这个时候，对自己的认知也要进行更新，我就不仅仅适合做架构师，我这个适合也可以尝试去做管理了。一味地固步自封，不更新对自己的认知，只会踌躇不前。</p>
<ol start="3">
<li><strong>不考虑任何沉没成本，学会及时止损</strong>：</li>
</ol>
<p>学会接受损失，比如我谈了一段恋爱，这段恋爱持续了七八年，但是这个期间我在感情里面得不到任何快乐，只有内耗，但是又舍不得这么多年的感情，只能摸摸忍受。你以为分手是葬送了前几年的努力，其实不然，分手是把自己推向了一个新的起点而不是回到原点。只有接受这段感情的失败，及时止损，才有重新开始的机会。</p>
<p>很多事情也是这样的道理，再比如我在一家公司干的并不快乐，天天被脑残老板PUA、加班，但是碍于在公司呆了很多年，离职很亏，但是不离职只会不断地继续被压榨，直到某天被公司当作废物耗材一脚踢掉。及时止损，马上换赛道，就会有新的起点和机会。</p>
<p>因此在发现自己有沉没成本而影响自己做某个决策时，学会忽略所有的沉没成本，接受已有的损失，前往新的起点。</p>
<ol start="4">
<li><strong>不对任何人寄托任何期望</strong>：</li>
</ol>
<p>明确自己是自己的主角，你活着没人在意你，你死了也没什么人为你悲伤，每个人都有自己的生活和人生。别觉得任何人就应该对你怎么样，就应该怎么样，没有人欠你的，也没有人生来就对你好，也没有人百分百会按你的期望发展。不要将任何期望寄托在别人身上，平等地将所有人认为不可靠，把关注点回到自己，去关注自己的能力提升和认知提升才是核心。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[大理-丽江六天五夜旅游攻略]]></title>
            <link>https://umb.ink/post/38</link>
            <guid>https://umb.ink/post/38</guid>
            <pubDate>Tue, 18 Feb 2025 09:45:26 GMT</pubDate>
            <description><![CDATA[<h1>攻略说明</h1>
<p>本攻略侧重云南自然风光游玩，轻人文景观（古城等），因此仅供想要侧重游玩自然风光的进行参考</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>攻略说明</h1>
<p>本攻略侧重云南自然风光游玩，轻人文景观（古城等），因此仅供想要侧重游玩自然风光的进行参考</p>
<!-- more -->
<h1>涉及地点</h1>
<p>云南北线攻略：昆明-大理-丽江，以大理与丽江为主，昆明走马观花</p>
<p>因为六天五夜时间比较紧张，其实完整北线应该是 昆明-大理-丽江-香格里拉</p>
<p>核心地点：大理洱海、大理古城、丽江古城、玉龙雪山风景区、昆明滇池</p>
<p>预算：旺季人均2k（不算机票与自购特产）</p>
<h1>时间安排：</h1>
<ol>
<li>
<p>第一天 落地昆明，然后前往大理，先游玩大理古城（第一天下午+晚上），晚上可以在大理古城附近吃傣味手抓饭体验，逛到晚上10点可以打车前往酒店入住。</p>
</li>
<li>
<p>第二天 在龙龛码头看日出，然后骑电动车环游洱海西岸，裳苍山雪。</p>
</li>
</ol>
<ul>
<li>码头日出：</li>
</ul>
<p><img src="/static/img/ae3c4a6fe76af8ccb250c7392a024cdf.IMG_9592.webp" alt="IMG_9592.JPG"></p>
<ul>
<li>苍山雪：</li>
</ul>
<p><img src="/static/img/2464effac91f8204bfeb9d6f0cf5e628.KIM20250212-025419.webp" alt="KIM20250212-025419.png"></p>
<ul>
<li>洱海西岸：</li>
</ul>
<p><img src="/static/img/941f84f8eb193e22a4813c648d709f69.IMG_2EC6FEC603DB-1.webp" alt="IMG_2EC6FEC603DB-1.jpeg"></p>
<ol start="3">
<li>第三天 包车或自驾游洱海东岸，游双廊古镇，看鹿卧山，小普陀。下午在东岸看洱海日落，晚上前往丽江。</li>
</ol>
<ul>
<li>双廊古镇：</li>
</ul>
<p><img src="/static/img/05ce4c7c14910eed6ed3d21e33337e56.image.webp" alt="image.png"></p>
<ul>
<li>鹿卧山：</li>
</ul>
<p><img src="/static/img/0ec02e8cd450e43df6e30cdc7633d25d.image.webp" alt="image.png"></p>
<ol start="4">
<li>第四天 在丽江古城早起看古城的日照金山（需要碰运气），全天游古城，晚上可以去古城酒吧一条街体验丽江古城的民谣酒吧。</li>
</ol>
<p><img src="/static/img/55432eebae820cca7c0ce604fadee980.image.webp" alt="image.png"></p>
<p><img src="/static/img/d131262902b6522fb19486cc105ddc57.image.webp" alt="image.png"></p>
<ol start="5">
<li>第五天 包车或自驾前往玉龙雪山风景区，凌晨出发，早上可以到东巴谷看日照金山，然后进入山区上大索道（如果抢不到票去中索道牦牛坪也很不错），下山后坐景区大巴前往蓝月谷。</li>
</ol>
<p>玉龙雪山脚下：</p>
<p><img src="/static/img/13ff4a3c19689f060b9a7c2d15dd4479.image.webp" alt="image.png"></p>
<p>牦牛坪：</p>
<p><img src="/static/img/f4e2a0b3b5be95236772f4e7f6d4e1cc.image.webp" alt="image.png"></p>
<ol start="6">
<li>第六天 早起返回昆明，时间充裕可以去海洪湿地公园看滇池海鸥，然后去翠湖公园看郁金香，如果还有时间推荐斗南花卉市场。</li>
</ol>
<h1>路线安排：</h1>
<p>注意：建议不要携带行李箱，因为路途遥远且各个景点之间相距甚远，行李箱会非常不方便，最好做背包客，一个背包解决所有问题。</p>
<ul>
<li>
<p>第一天：</p>
<p>早上飞机落地昆明，走马上任前往大理。昆明-大理 动车约一个半小时，约下午三点到达大理。</p>
<p>大理站到大理古城可以坐大理旅游观光线路大巴，途中会经过兴盛大桥，到达兴盛大桥可以下车打卡，下午刚好是打卡的好时候。</p>
</li>
</ul>
<p><img src="/static/img/11afda3d06a3ffac389d9fc0aeb95dce.6d7808104393cc84210fddd90be7c1ae.webp" alt="6d7808104393cc84210fddd90be7c1ae.png"></p>
<p>下午和晚上在大理古城游玩，玩够了可以骑共享电动车前往酒店入住（注意千万不要租古城附近的电动车，否则你还需要回来还车）。</p>
<ul>
<li>第二天：</li>
</ul>
<p>早起去看龙龛码头的日出（注意可能人会很多，建议提早去占位置），龙龛古渡的观景台上有“上关花、下关风、苍山雪、洱海月”四个石碑，可以打卡。</p>
<p>日出回来可以吃个早餐，洱海附近很多卖烤饵块早餐的。然后可以租个车在洱海西岸游玩。</p>
<ul>
<li>第三天</li>
</ul>
<p>洱海东岸环线</p>
<p>旺季建议在东岸包车（注意，不要包本地人车行的车，非常贵而且坑），建议打一个滴滴，然后咨询师傅能不能包他的车，直接找网约车师傅包车会更便宜（没有中间商赚差价）而且很多都是本地人，比导游更了解哪里好玩。</p>
<p>淡季建议租车（不堵车的洱海东岸值得自驾）</p>
<ul>
<li>第四天</li>
</ul>
<p>从大理到丽江，可以在大理选择直达丽江古城的大巴车（70每人）经济实惠，也可以坐高铁直达。</p>
<p>到达丽江后可以打车到丽江古城（如果是大巴会直接送到古城门口）</p>
<ul>
<li>第五天</li>
</ul>
<p>玉龙雪山（记住提前一周抢票！！！）玉龙雪山分为大索道-上雪山、中索道-上牦牛坪、小索道-去云杉坪</p>
<p>去之前建议在古城购买一瓶氧气（十块钱）千万不要到了景区再买，景区都是几百一瓶。</p>
<p>尽量不要在古城购买一日游套餐！体验非常差。导游会忽悠你去买高价氧气，租用很烂的那种保温服。</p>
<p>在雪山如果准备去蓝月谷的话，不要坐观光车，直接乘坐景区通勤大巴到蓝月谷的下一站，可以直接徒步，体验更好。</p>
<ul>
<li>第六天</li>
</ul>
<p>从丽江坐高铁返回昆明返程，路上会经过洱海，可以留意下火车窗外的风光。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/日常">日常</category>
        </item>
        <item>
            <title><![CDATA[Android-常用ADB命令与工具类]]></title>
            <link>https://umb.ink/post/37</link>
            <guid>https://umb.ink/post/37</guid>
            <pubDate>Fri, 24 Jan 2025 10:36:31 GMT</pubDate>
            <description><![CDATA[<p>整理下在工作中常用到的各种ADB命令和一些工具方法</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>整理下在工作中常用到的各种ADB命令和一些工具方法</p>
<!-- more -->
<h1>ADB指令（常见）</h1>
<ol>
<li>
<p>隐藏/恢复系统的菜单栏和任务栏</p>
<p>隐藏：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>wm overscan  0,-70,0,-70
</code></pre>
<p>恢复：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>wm overscan  0,0,0,0
</code></pre>
</li>
<li>
<p>禁用/启用Launcher</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>pm disable-user com.android.launcher3 // 禁用
pm enable com.android.launcher3 // 启用
</code></pre>
</li>
<li>
<p>ADB 安装/卸载应用</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>adb install -r xxx.apk
adb uninstall com.xxxx.xxxxx(应用包名）
</code></pre>
</li>
<li>
<p>ADB 启动/停止应用</p>
<pre><code>am force-stop com.xxxxx; (应用包名）
am start -n com.xxxxxx/.MainActivity （应用包名+应用入口Activity）
</code></pre>
</li>
<li>
<p>截图</p>
<pre><code>screencap -p /sdcard/screenshot.png
</code></pre>
</li>
<li>
<p>导出某个时间段日志</p>
</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>adb devices adb logcat -v time -t &quot;2022-01-01 00:00:00&quot; -T &quot;2022-01-02 00:00:00&quot; &gt; log.txt adb pull log.txt /path/to/local/directory
</code></pre>
<ol start="7">
<li>adb 导出文件到PC</li>
</ol>
<pre><code>adb pull /sdcard/xxxx/ .
</code></pre>
<ol start="8">
<li>打开设备浏览器并访问对应网址</li>
</ol>
<pre><code>adb shell am start -a android.intent.action.VIEW -d https://baidu.com
</code></pre>
<ol start="9">
<li>启动/停用设备自身adb端口</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>setprop service.adb.tcp.port 5555;stop adbd;start adbd
</code></pre>
<ol start="10">
<li>设置应用为主屏幕应用</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>cmd package set-home-activity com.xxx/.MainActivity
</code></pre>
<ol start="12">
<li>应用授权启动</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>pm grant com.xxxx
</code></pre>
<ol start="13">
<li>设置音量</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>media volume --show --stream 2 --set 1
</code></pre>
<ol start="14">
<li>重启应用</li>
</ol>
<pre><code>eboot;
</code></pre>
<h1>工具类&amp;方法</h1>
<ol>
<li>静默安装</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">class</span> <span class="hljs-title class_">InstallAPK</span> {
    <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> {

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> timeOut = <span class="hljs-number">5</span> * <span class="hljs-number">1000L</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MD5_APPEND0 = <span class="hljs-string">&quot;0&quot;</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MD5_STRING = <span class="hljs-string">&quot;MD5&quot;</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> SHA1_NAME = <span class="hljs-string">&quot;SHA1&quot;</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">&quot;InstallAPK&quot;</span>
        <span class="hljs-comment">/**
         * install slient
         *
         * <span class="hljs-doctag">@param</span> context
         * <span class="hljs-doctag">@param</span> filePath
         * <span class="hljs-doctag">@return</span> 0 means normal, 1 means file not exist, 2 means other exception
         * error
         */</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">installSlient</span><span class="hljs-params">(filePath: <span class="hljs-type">String</span>, packName: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Int</span> {

            <span class="hljs-keyword">val</span> file = File(filePath)
            <span class="hljs-keyword">if</span> (!file.exists()) {
                <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
            }
            <span class="hljs-keyword">var</span> args: Array&lt;String&gt;
            <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt; Build.VERSION_CODES.M) {
                args = arrayOf(<span class="hljs-string">&quot;pm&quot;</span>, <span class="hljs-string">&quot;install&quot;</span>, <span class="hljs-string">&quot;-r&quot;</span>, <span class="hljs-string">&quot;-i&quot;</span>, packName, <span class="hljs-string">&quot;--user&quot;</span>, <span class="hljs-string">&quot;0&quot;</span>, filePath)
            } <span class="hljs-keyword">else</span> {

                args = arrayOf(<span class="hljs-string">&quot;pm&quot;</span>, <span class="hljs-string">&quot;install&quot;</span>, <span class="hljs-string">&quot;-r&quot;</span>, filePath)
            }
            <span class="hljs-keyword">val</span> processBuilder = ProcessBuilder(*args)

            <span class="hljs-keyword">var</span> process: Process? = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">var</span> successResult: BufferedReader? = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">var</span> errorResult: BufferedReader? = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">val</span> successMsg = StringBuilder()
            <span class="hljs-keyword">val</span> errorMsg = StringBuilder()
            <span class="hljs-keyword">var</span> result: <span class="hljs-built_in">Int</span>

                process = processBuilder.start()
                successResult = BufferedReader(InputStreamReader(process!!.inputStream))
                errorResult = BufferedReader(InputStreamReader(process.errorStream))
                <span class="hljs-keyword">var</span> s: String?

                <span class="hljs-keyword">do</span> {
                    s = successResult.readLine()
                    <span class="hljs-keyword">if</span> (s != <span class="hljs-literal">null</span>) {
                        successMsg.append(s)
                    } <span class="hljs-keyword">else</span>
                        <span class="hljs-keyword">break</span>
                } <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>)

                <span class="hljs-keyword">do</span> {
                    s = errorResult.readLine()
                    <span class="hljs-keyword">if</span> (s != <span class="hljs-literal">null</span>) {
                        errorMsg.append(s)
                    } <span class="hljs-keyword">else</span>
                        <span class="hljs-keyword">break</span>
                } <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>)

                    successResult?.close()
                    errorResult?.close()


                process?.destroy()

            <span class="hljs-keyword">if</span> (successMsg.toString().contains(<span class="hljs-string">&quot;Success&quot;</span>) ||
                successMsg.toString().contains(<span class="hljs-string">&quot;success&quot;</span>)
            ) {
                result = <span class="hljs-number">0</span>
            } <span class="hljs-keyword">else</span> {
                result = <span class="hljs-number">2</span>
            }

            <span class="hljs-keyword">return</span> result
        }
        <span class="hljs-comment">/**
         * 获取apk的包名
         */</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApkPackageName</span><span class="hljs-params">(
            context: <span class="hljs-type">Context</span>,
            apkPath: <span class="hljs-type">String</span>?,
        )</span></span>: String? {
            apkPath ?: <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
            <span class="hljs-keyword">val</span> pm = context.packageManager
            <span class="hljs-keyword">val</span> info = pm.getPackageArchiveInfo(apkPath, <span class="hljs-number">0</span>)
            <span class="hljs-keyword">return</span> info?.packageName
        }


        <span class="hljs-comment">/**
         * MD5加密
         * <span class="hljs-doctag">@param</span> byteStr 需要加密的内容
         * <span class="hljs-doctag">@return</span> 返回 byteStr的md5值
         */</span>
        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">encryptionMD5</span><span class="hljs-params">(byteStr: <span class="hljs-type">ByteArray</span>?)</span></span>: String {
            <span class="hljs-keyword">var</span> messageDigest: MessageDigest? = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">val</span> md5StrBuff = StringBuffer()
            <span class="hljs-keyword">try</span> {
                messageDigest = MessageDigest.getInstance(MD5_STRING)
                messageDigest.reset()
                messageDigest.update(byteStr)
                <span class="hljs-keyword">val</span> byteArray = messageDigest.digest()
                <span class="hljs-comment">//            return Base64.encodeToString(byteArray,Base64.NO_WRAP);</span>
                <span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> byteArray.indices) {
                    <span class="hljs-keyword">if</span> (Integer.toHexString(<span class="hljs-number">0xFF</span> and byteArray[i].toInt()).length == <span class="hljs-number">1</span>) {
                        md5StrBuff.append(MD5_APPEND0).append(
                            Integer.toHexString(
                                <span class="hljs-number">0xFF</span> and byteArray[i]
                                    .toInt()
                            )
                        )
                    } <span class="hljs-keyword">else</span> {
                        md5StrBuff.append(Integer.toHexString(<span class="hljs-number">0xFF</span> and byteArray[i].toInt()))
                    }
                }
            } <span class="hljs-keyword">catch</span> (e: NoSuchAlgorithmException) {
                e.printStackTrace()
            }
            <span class="hljs-keyword">return</span> md5StrBuff.toString()
        }

        <span class="hljs-comment">/**
         * 获取app签名md5值,与“keytool -list -keystore D:\Desktop\app_key”‘keytool -printcert     *file D:\Desktop\CERT.RSA’获取的md5值一样
         */</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignMd5Str</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, packageName: <span class="hljs-type">String</span>)</span></span>: String {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> packageInfo: PackageInfo = context.packageManager.getPackageInfo(
                    packageName, PackageManager.GET_SIGNATURES
                )
                <span class="hljs-keyword">val</span> signs: Array&lt;Signature&gt; = packageInfo.signatures
                <span class="hljs-keyword">val</span> sign: Signature = signs[<span class="hljs-number">0</span>]
                <span class="hljs-keyword">return</span> encryptionMD5(sign.toByteArray())
            } <span class="hljs-keyword">catch</span> (e: PackageManager.NameNotFoundException) {
                e.printStackTrace()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">hexDigest</span><span class="hljs-params">(bytes: <span class="hljs-type">ByteArray</span>?)</span></span>: String? {
            <span class="hljs-keyword">val</span> md5: MessageDigest = <span class="hljs-keyword">try</span> {
                MessageDigest.getInstance(MD5_STRING)
            } <span class="hljs-keyword">catch</span> (e: java.lang.Exception) {
                e.printStackTrace()
                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>
            }
            <span class="hljs-keyword">val</span> md5Bytes = md5.digest(bytes)
            <span class="hljs-keyword">val</span> hexValue = StringBuffer()
            <span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> md5Bytes.indices) {
                <span class="hljs-keyword">val</span> `<span class="hljs-keyword">val</span>` = md5Bytes[i].toInt() and <span class="hljs-number">0xff</span>
                <span class="hljs-keyword">if</span> (`<span class="hljs-keyword">val</span>` &lt; <span class="hljs-number">16</span>) hexValue.append(MD5_APPEND0)
                hexValue.append(Integer.toHexString(`<span class="hljs-keyword">val</span>`))
            }
            <span class="hljs-keyword">return</span> hexValue.toString()
        }

        <span class="hljs-comment">/**
         * 从APK中读取签名
         *
         * <span class="hljs-doctag">@param</span> file
         * <span class="hljs-doctag">@return</span>
         * <span class="hljs-doctag">@throws</span> IOException
         */</span>
        <span class="hljs-meta">@Throws(IOException::class)</span>
        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignaturesFromApk</span><span class="hljs-params">(strFile: <span class="hljs-type">String</span>)</span></span>: ByteArray? {
            <span class="hljs-keyword">val</span> file = File(strFile)
            <span class="hljs-keyword">val</span> jarFile = JarFile(file)
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> je: JarEntry = jarFile.getJarEntry(<span class="hljs-string">&quot;AndroidManifest.xml&quot;</span>)
                <span class="hljs-keyword">val</span> readBuffer = ByteArray(<span class="hljs-number">8192</span>)
                <span class="hljs-keyword">val</span> certs: Array&lt;Certificate&gt;? = loadCertificates(jarFile, je, readBuffer)
                <span class="hljs-keyword">if</span> (certs != <span class="hljs-literal">null</span>) {
                    <span class="hljs-keyword">for</span> (c <span class="hljs-keyword">in</span> certs) {
                        <span class="hljs-keyword">return</span> c.encoded
                    }
                }
            } <span class="hljs-keyword">catch</span> (ex: java.lang.Exception) {
                ex.printStackTrace()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }

        <span class="hljs-comment">/**
         * 加载签名
         *
         * <span class="hljs-doctag">@param</span> jarFile
         * <span class="hljs-doctag">@param</span> je
         * <span class="hljs-doctag">@param</span> readBuffer
         * <span class="hljs-doctag">@return</span>
         */</span>
        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">loadCertificates</span><span class="hljs-params">(
            jarFile: <span class="hljs-type">JarFile</span>,
            je: <span class="hljs-type">JarEntry</span>?,
            readBuffer: <span class="hljs-type">ByteArray</span>,
        )</span></span>: Array&lt;Certificate&gt;? {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> inputStream: InputStream = jarFile.getInputStream(je)
                <span class="hljs-keyword">while</span> (inputStream.read(readBuffer, <span class="hljs-number">0</span>, readBuffer.size) !== -<span class="hljs-number">1</span>) {
                }
                inputStream.close()
                <span class="hljs-keyword">return</span> je?.certificates
            } <span class="hljs-keyword">catch</span> (e: IOException) {
                e.printStackTrace()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }
        
        <span class="hljs-comment">/**
         * 校验已经安装的软件签名是否和插件市场下发的软件签名一致
         * <span class="hljs-doctag">@param</span> localPkgName 已经安装的包名
         * <span class="hljs-doctag">@param</span> path 插件市场下发的软件路径
         */</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkSign</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, localPkgName: <span class="hljs-type">String</span>, path: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> pathMD5 = hexDigest(getSignaturesFromApk(path))
                <span class="hljs-keyword">val</span> localPkgNameMD5 = getSignMd5Str(context, localPkgName)
                LogUtils.i(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> pathMD5: <span class="hljs-variable">$pathMD5</span>&quot;</span>)
                LogUtils.i(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> localPkgNameMD5: <span class="hljs-variable">$localPkgNameMD5</span>&quot;</span>)
                <span class="hljs-keyword">if</span> (TextUtils.isEmpty(localPkgNameMD5)) {
                    LogUtils.i(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> isEmpty&quot;</span>)
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
                }
                LogUtils.i(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> isSame: <span class="hljs-subst">${pathMD5 == localPkgNameMD5}</span>&quot;</span>)
                <span class="hljs-keyword">return</span> pathMD5 == localPkgNameMD5
            } <span class="hljs-keyword">catch</span> (e: java.lang.Exception) {
                e.printStackTrace()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">allowDowngrade</span><span class="hljs-params">(sessionParams: <span class="hljs-type">PackageInstaller</span>.<span class="hljs-type">SessionParams</span>)</span></span> {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-comment">//允许低版本安装的原因：在新版本出问题但是来不及修复需要进行版本回退</span>
                <span class="hljs-keyword">val</span> allowDowngrade: Method = PackageInstaller.SessionParams::<span class="hljs-keyword">class</span>.java.getMethod(
                    <span class="hljs-string">&quot;setAllowDowngrade&quot;</span>,
                    <span class="hljs-built_in">Boolean</span>::<span class="hljs-keyword">class</span>.javaPrimitiveType
                )
                allowDowngrade.isAccessible = <span class="hljs-literal">true</span>
                allowDowngrade.invoke(sessionParams, <span class="hljs-literal">true</span>)
            } <span class="hljs-keyword">catch</span> (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">installApk21</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, apkFilePath: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {

            <span class="hljs-keyword">val</span> apkFile = File(apkFilePath)
            <span class="hljs-keyword">if</span>(apkFile.exists())
            {
                LogHelper.saveRequestLog(<span class="hljs-string">&quot;<span class="hljs-variable">$apkFilePath</span>, 文件存在&quot;</span>)
            }<span class="hljs-keyword">else</span>{
                LogHelper.saveRequestLog(<span class="hljs-string">&quot;<span class="hljs-variable">$apkFilePath</span>, 文件不存在&quot;</span>)
            }
            <span class="hljs-keyword">val</span> packageInstaller = context.packageManager.packageInstaller
            <span class="hljs-keyword">val</span> pkgName = getApkPackageName(context, apkFilePath)
            LogHelper.saveRequestLog(<span class="hljs-string">&quot;InstallAPK apk path:<span class="hljs-variable">$apkFilePath</span>, package name:<span class="hljs-variable">$pkgName</span>&quot;</span>)


            pkgName?.let {
                <span class="hljs-comment">//如果签名不一致则先卸载</span>
                <span class="hljs-keyword">if</span> (!checkSign(context, pkgName, apkFilePath)) {
                    LogHelper.saveRequestLog(<span class="hljs-string">&quot;InstallAPK apk package uninstall&quot;</span>)
                    uninstallSignError21(context, pkgName, apkFilePath)
                }

                <span class="hljs-keyword">val</span> sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
                    .apply {
                    setAppPackageName(pkgName)
                    setSize(apkFile.length())
                }
                allowDowngrade(sessionParams)
                <span class="hljs-keyword">val</span> sessionId = createSession(packageInstaller, sessionParams)
                <span class="hljs-keyword">if</span> (sessionId != -<span class="hljs-number">1</span> &amp;&amp; copyApkFile(packageInstaller, sessionId, apkFilePath, it)) {
                    <span class="hljs-keyword">return</span> install(
                        context,
                        packageInstaller,
                        sessionId,
                        InstallReceiver::<span class="hljs-keyword">class</span>.java
                    )
                }
            }
            LogHelper.saveRequestLog(<span class="hljs-string">&quot;Install falied&quot;</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
        }

        <span class="hljs-comment">/**
         * 最后提交 session，并且设置回调
         **/</span>
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">install</span><span class="hljs-params">(
            context: <span class="hljs-type">Context</span>,
            packageInstaller: <span class="hljs-type">PackageInstaller</span>,
            sessionId: <span class="hljs-type">Int</span>,
            cls: <span class="hljs-type">Class</span>&lt;*&gt;
        )</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">var</span> session: PackageInstaller.Session? = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">try</span> {
                LogHelper.saveRequestLog(<span class="hljs-string">&quot;start install&quot;</span>)
                session = packageInstaller.openSession(sessionId).apply {
                    <span class="hljs-keyword">val</span> intent = Intent(context, cls)
                    <span class="hljs-keyword">val</span> pendingIntent =
                        <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
                            PendingIntent.getBroadcast(
                                context,
                                <span class="hljs-number">1</span>, intent,
                                PendingIntent.FLAG_MUTABLE
                            )
                        } <span class="hljs-keyword">else</span> {
                            PendingIntent.getBroadcast(
                                context,
                                <span class="hljs-number">1</span>, intent,
                                PendingIntent.FLAG_UPDATE_CURRENT
                            )
                        }
                    commit(pendingIntent.intentSender)
                }
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
            } <span class="hljs-keyword">catch</span> (ex: Exception) {
                LogHelper.saveRequestLog(<span class="hljs-string">&quot;Install falied exception:<span class="hljs-subst">${ex.message}</span>&quot;</span>)
            } <span class="hljs-keyword">finally</span> {
                session?.close()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
        }
        
        <span class="hljs-comment">/**
         * 根据 sessionParams 创建 Session
         **/</span>
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">createSession</span><span class="hljs-params">(
            packageInstaller: <span class="hljs-type">PackageInstaller</span>,
            sessionParams: <span class="hljs-type">PackageInstaller</span>.<span class="hljs-type">SessionParams</span>,
        )</span></span>: <span class="hljs-built_in">Int</span> {
            <span class="hljs-keyword">val</span> sessionId: <span class="hljs-built_in">Int</span> = <span class="hljs-keyword">try</span> {
                packageInstaller.createSession(sessionParams)
            } <span class="hljs-keyword">catch</span> (e: IOException) {
                e.printStackTrace()
                -<span class="hljs-number">1</span>
            }
            <span class="hljs-keyword">return</span> sessionId
        }

        <span class="hljs-comment">/**
         * 将 apk 文件输入 session
         **/</span>
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">copyApkFile</span><span class="hljs-params">(
            packageInstaller: <span class="hljs-type">PackageInstaller</span>,
            sessionId: <span class="hljs-type">Int</span>, apkFilePath: <span class="hljs-type">String</span>, pkgName: <span class="hljs-type">String</span>,
        )</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">var</span> success: <span class="hljs-built_in">Boolean</span>
            <span class="hljs-keyword">val</span> apkFile = File(apkFilePath)
            <span class="hljs-keyword">try</span> {
                packageInstaller.openSession(sessionId).use { session -&gt;
                    session.openWrite(pkgName, <span class="hljs-number">0</span>, apkFile.length()).use { <span class="hljs-keyword">out</span> -&gt;
                        FileInputStream(apkFile).use { input -&gt;
                            LogUtils.d(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> copyApkFile openWrite start writing <span class="hljs-subst">${apkFile.absolutePath}</span>&quot;</span>)
                            <span class="hljs-keyword">var</span> read: <span class="hljs-built_in">Int</span>
                            <span class="hljs-keyword">val</span> buffer = ByteArray(<span class="hljs-number">1024</span>)
                            <span class="hljs-keyword">while</span> (input.read(buffer).also { read = it } != -<span class="hljs-number">1</span>) {
                                <span class="hljs-keyword">out</span>.write(buffer, <span class="hljs-number">0</span>, read)
                            }
                            session.fsync(<span class="hljs-keyword">out</span>)
                            syncFile2Disk()
                            success = <span class="hljs-literal">true</span>
                        }
                    }
                }
            } <span class="hljs-keyword">catch</span> (e: IOException) {
                LogUtils.e(e.stackTraceToString())
                success = <span class="hljs-literal">false</span>
            }
            LogUtils.d(<span class="hljs-string">&quot;<span class="hljs-variable">$TAG</span> copyApkFile openWrite writ done! success = <span class="hljs-variable">$success</span>&quot;</span>)
            <span class="hljs-keyword">return</span> success
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">syncFile2Disk</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">try</span> {
                CmdUtil.execCmdSilent(<span class="hljs-string">&quot;sync&quot;</span>)
<span class="hljs-comment">//            Shell.SH.run(&quot;sync&quot;)</span>
            } <span class="hljs-keyword">catch</span> (e: Exception) {
                e.printStackTrace()
            }
        }
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">uninstallSignError21</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, pkgName: <span class="hljs-type">String</span>, apkFilePath: <span class="hljs-type">String</span>)</span></span> {
            <span class="hljs-keyword">try</span> {
                runBlocking(Dispatchers.IO) {
                    LogUtils.d(<span class="hljs-string">&quot;&gt;&gt;&gt; InstallAPK apk package name:<span class="hljs-variable">$pkgName</span> sign diff <span class="hljs-variable">$apkFilePath</span>&quot;</span>)
                    uninstall21(context, pkgName)
                    <span class="hljs-comment">//给卸载一些时间 5秒</span>
                    delay(timeOut)
                }
            } <span class="hljs-keyword">catch</span> (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        <span class="hljs-meta">@SuppressLint(<span class="hljs-string">&quot;MissingPermission&quot;</span>)</span>
        <span class="hljs-meta">@JvmStatic</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">uninstall21</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, pkgName: <span class="hljs-type">String</span>)</span></span> {
            LogUtils.d(<span class="hljs-string">&quot;&gt;&gt;&gt; InstallAPK uninstall21 pkgName = <span class="hljs-variable">$pkgName</span>&quot;</span>)
            <span class="hljs-keyword">val</span> packageInstaller = context.packageManager.packageInstaller
            <span class="hljs-keyword">val</span> intent = Intent(context, InstallReceiver::<span class="hljs-keyword">class</span>.java)
            <span class="hljs-keyword">val</span> pendingIntent =
                <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
                    PendingIntent.getBroadcast(
                        context, <span class="hljs-number">2</span>, intent,
                        PendingIntent.FLAG_MUTABLE
                    )
                } <span class="hljs-keyword">else</span> {
                    PendingIntent.getBroadcast(
                        context, <span class="hljs-number">2</span>, intent,
                        PendingIntent.FLAG_UPDATE_CURRENT
                    )
                }
            <span class="hljs-keyword">try</span> {
                packageInstaller.uninstall(pkgName, pendingIntent.intentSender)
            } <span class="hljs-keyword">catch</span> (e: IllegalArgumentException) {
                e.printStackTrace()
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">installSilence</span><span class="hljs-params">()</span></span> {
            
        }
        
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">installSilentWithReflection</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, filePath: <span class="hljs-type">String</span>?,packName: <span class="hljs-type">String</span>)</span></span> {

                <span class="hljs-keyword">val</span> packageManager = context.packageManager
                <span class="hljs-keyword">val</span> method: Method = packageManager.javaClass.getDeclaredMethod(
                    <span class="hljs-string">&quot;installPackage&quot;</span>,
                    *arrayOf&lt;Class&lt;*&gt;?&gt;(
                        Uri::<span class="hljs-keyword">class</span>.java,
                        IPackageInstallObserver::<span class="hljs-keyword">class</span>.java,
                        <span class="hljs-built_in">Int</span>::<span class="hljs-keyword">class</span>.javaPrimitiveType,
                        String::<span class="hljs-keyword">class</span>.java
                    )
                )
                method.setAccessible(<span class="hljs-literal">true</span>)
                <span class="hljs-keyword">val</span> apkFile = File(filePath)
                <span class="hljs-keyword">val</span> apkUri: Uri = Uri.fromFile(apkFile)
                method.invoke(packageManager, arrayOf(apkUri, <span class="hljs-keyword">object</span> : IPackageInstallObserver.Stub() {
                    <span class="hljs-meta">@Throws(RemoteException::class)</span>
                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">packageInstalled</span><span class="hljs-params">(pkgName: <span class="hljs-type">String</span>, resultCode: <span class="hljs-type">Int</span>)</span></span> {

                    }
                }, <span class="hljs-number">2</span>, packName))
        }
    }
}
</code></pre>
<ol start="3">
<li>获取设备各种基本信息（部分需要root）</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">object</span> DeviceInfoUtil {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> maximumPassengerPref = <span class="hljs-string">&quot;maximum&quot;</span>
    <span class="hljs-meta">@SuppressLint(<span class="hljs-string">&quot;HardwareIds&quot;</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSN</span><span class="hljs-params">()</span></span>: String {
        <span class="hljs-comment">//return &quot;e0d8c45adef0&quot;</span>
        <span class="hljs-keyword">return</span> Build.SERIAL
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isNetworkAvailable</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">val</span> connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) <span class="hljs-keyword">as</span> ConnectivityManager
        <span class="hljs-keyword">if</span> (connectivityManager != <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">val</span> activeNetworkInfo: NetworkInfo? = connectivityManager.activeNetworkInfo
            <span class="hljs-keyword">return</span> activeNetworkInfo != <span class="hljs-literal">null</span> &amp;&amp; activeNetworkInfo.isConnected()
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSN</span><span class="hljs-params">(context:<span class="hljs-type">Context</span>)</span></span>:String{
        <span class="hljs-keyword">var</span> androidId: String =
            Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID)
        <span class="hljs-keyword">if</span>(androidId.isNullOrBlank())
        {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O) {
                    androidId = Build.getSerial()
                } <span class="hljs-keyword">else</span> {
                    androidId = Build.SERIAL
                }
            }<span class="hljs-keyword">catch</span> (e:Exception){

            }
        }
        <span class="hljs-keyword">if</span>(androidId.isNullOrBlank())
        {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> telephonyManager =
                    context.getSystemService(TELEPHONY_SERVICE) <span class="hljs-keyword">as</span> TelephonyManager
                androidId = telephonyManager.imei
            }<span class="hljs-keyword">catch</span> (e:Exception){

            }
        }
        <span class="hljs-keyword">return</span> androidId
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getMacAddress</span><span class="hljs-params">()</span></span>: String {

        <span class="hljs-keyword">var</span> macAddress=<span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">try</span> {

                <span class="hljs-keyword">val</span> all: List&lt;NetworkInterface&gt; =
                    Collections.list(NetworkInterface.getNetworkInterfaces())
                <span class="hljs-keyword">for</span> (nif <span class="hljs-keyword">in</span> all) {
                    <span class="hljs-keyword">if</span> (!nif.name.equals(<span class="hljs-string">&quot;eth0&quot;</span>, ignoreCase = <span class="hljs-literal">true</span>)) <span class="hljs-keyword">continue</span>
                    <span class="hljs-keyword">val</span> macBytes = nif.getHardwareAddress() ?: <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>
                    <span class="hljs-keyword">val</span> res1 = StringBuilder()
                    <span class="hljs-keyword">for</span> (b <span class="hljs-keyword">in</span> macBytes) {
                        res1.append(String.format(<span class="hljs-string">&quot;%02X:&quot;</span>, b))
                    }
                    <span class="hljs-keyword">if</span> (res1.length &gt; <span class="hljs-number">0</span>) {
                        res1.deleteCharAt(res1.length - <span class="hljs-number">1</span>)
                    }
                    macAddress = res1.toString()
                }
        } <span class="hljs-keyword">catch</span> (ex: Exception) {
            <span class="hljs-comment">// Handle exception</span>
        }


        <span class="hljs-keyword">return</span> macAddress <span class="hljs-comment">// Default MAC address if not found</span>
    }


    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">saveRequestLog</span><span class="hljs-params">(<span class="hljs-keyword">data</span>:<span class="hljs-type">String</span>)</span></span>
    {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">var</span> formatter: DateFormat = SimpleDateFormat(<span class="hljs-string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>)
            <span class="hljs-keyword">var</span> formatter2: DateFormat = SimpleDateFormat(<span class="hljs-string">&quot;yyyy-MM-dd&quot;</span>)
            <span class="hljs-keyword">val</span> time: String = formatter.format(Date())
            <span class="hljs-keyword">val</span> fileName = <span class="hljs-string">&quot;request-<span class="hljs-subst">${formatter2.format(Date())}</span>.log&quot;</span>
            <span class="hljs-keyword">if</span> (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                <span class="hljs-keyword">val</span> sdCardPath = Environment.getExternalStorageDirectory();
                <span class="hljs-keyword">val</span> path = sdCardPath.getAbsolutePath() + <span class="hljs-string">&quot;/TVAgent/logs&quot;</span>
                <span class="hljs-keyword">val</span> dir = File(path)
                <span class="hljs-keyword">if</span> (!dir.exists()) {
                    dir.mkdirs()
                }
                <span class="hljs-keyword">var</span> content =
                    <span class="hljs-string">&quot;Time:[<span class="hljs-subst">${time}</span>]---<span class="hljs-subst">${data}</span>\n&quot;</span>
                <span class="hljs-keyword">val</span> file = File(<span class="hljs-string">&quot;<span class="hljs-subst">${path}</span>/<span class="hljs-subst">${fileName}</span>&quot;</span>)
                <span class="hljs-keyword">val</span> raf = RandomAccessFile(file, <span class="hljs-string">&quot;rw&quot;</span>)
                <span class="hljs-keyword">val</span> originalContent = ByteArray(raf.length().toInt())
                raf.readFully(originalContent)
                raf.seek(<span class="hljs-number">0</span>)
                raf.write(content.toByteArray())
                raf.write(originalContent)
                raf.close()
            }
        }<span class="hljs-keyword">catch</span> (ex: IOException)
        {

        }
    }


    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getStorageInfo</span><span class="hljs-params">()</span></span>:String {
        <span class="hljs-keyword">val</span> statFs = StatFs(Environment.getRootDirectory().getPath())
        <span class="hljs-keyword">val</span> blockSize = statFs.blockSizeLong
        <span class="hljs-keyword">val</span> totalBlocks = statFs.blockCountLong
        <span class="hljs-keyword">val</span> availableBlocks = statFs.availableBlocksLong
        <span class="hljs-keyword">val</span> totalSpace = totalBlocks * blockSize
        <span class="hljs-keyword">val</span> availableSpace = availableBlocks * blockSize
        <span class="hljs-keyword">return</span> (availableSpace.toDouble()/totalSpace.toDouble()).toString()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readCPUStat</span><span class="hljs-params">()</span></span>: LongArray {
        <span class="hljs-keyword">try</span> {
            BufferedReader(FileReader(<span class="hljs-string">&quot;/proc/stat&quot;</span>)).use { br -&gt;
                <span class="hljs-keyword">val</span> line = br.readLine()
                <span class="hljs-keyword">if</span> (line != <span class="hljs-literal">null</span> &amp;&amp; line.startsWith(<span class="hljs-string">&quot;cpu&quot;</span>)) {
                    <span class="hljs-keyword">val</span> tokens =
                        line.split(<span class="hljs-string">&quot;\\s+&quot;</span>.toRegex()).dropLastWhile { it.isEmpty() }
                            .toTypedArray()
                    <span class="hljs-keyword">val</span> user = tokens[<span class="hljs-number">1</span>].toLong()
                    <span class="hljs-keyword">val</span> nice = tokens[<span class="hljs-number">2</span>].toLong()
                    <span class="hljs-keyword">val</span> system = tokens[<span class="hljs-number">3</span>].toLong()
                    <span class="hljs-keyword">val</span> idle = tokens[<span class="hljs-number">4</span>].toLong()
                    <span class="hljs-keyword">val</span> iowait = tokens[<span class="hljs-number">5</span>].toLong()
                    <span class="hljs-keyword">val</span> irq = tokens[<span class="hljs-number">6</span>].toLong()
                    <span class="hljs-keyword">val</span> softirq = tokens[<span class="hljs-number">7</span>].toLong()

                    saveRequestLog(<span class="hljs-string">&quot;读取cpu信息:user:<span class="hljs-subst">${user}</span> nice:<span class="hljs-subst">${nice}</span> system:<span class="hljs-subst">${system}</span> idle:<span class="hljs-subst">${idle}</span> iowait:<span class="hljs-subst">${iowait}</span> irq:<span class="hljs-subst">${irq}</span> softirq:<span class="hljs-subst">${softirq}</span>&quot;</span>)
                    <span class="hljs-comment">// 包含所有CPU时间的数组</span>
                    <span class="hljs-keyword">return</span> longArrayOf(user, nice, system, idle, iowait, irq, softirq)
                }
            }
        } <span class="hljs-keyword">catch</span> (e: IOException) {
            saveRequestLog(<span class="hljs-string">&quot;读取cpu信息异常:<span class="hljs-subst">${e.message}</span>&quot;</span>)
        }
        <span class="hljs-keyword">return</span> LongArray(<span class="hljs-number">0</span>)
    }


    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSystemMemoryInfo</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span>:String {
        <span class="hljs-keyword">val</span> activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) <span class="hljs-keyword">as</span> ActivityManager
        <span class="hljs-keyword">val</span> memoryInfo = ActivityManager.MemoryInfo()
        activityManager.getMemoryInfo(memoryInfo)
        <span class="hljs-keyword">var</span> totalMemory=<span class="hljs-number">0L</span>
        <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.JELLY_BEAN) {
            totalMemory = memoryInfo.totalMem
        }
        <span class="hljs-keyword">val</span> availableMemory = memoryInfo.availMem
        <span class="hljs-keyword">val</span> lowMemory = memoryInfo.lowMemory <span class="hljs-comment">// 设备当前是否处于低内存状态</span>
        <span class="hljs-keyword">return</span> (availableMemory.toDouble()/totalMemory.toDouble()).toString()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getIP</span><span class="hljs-params">()</span></span>: String {
        <span class="hljs-keyword">val</span> interfaces = NetworkInterface.getNetworkInterfaces()
        <span class="hljs-keyword">while</span> (interfaces.hasMoreElements()) {
            <span class="hljs-keyword">val</span> networkInterface = interfaces.nextElement()
            <span class="hljs-keyword">val</span> addresses = networkInterface.inetAddresses
            <span class="hljs-keyword">while</span> (addresses.hasMoreElements()) {
                <span class="hljs-keyword">val</span> address = addresses.nextElement()
                <span class="hljs-keyword">if</span> (!address.isLoopbackAddress &amp;&amp; address.hostAddress.indexOf(<span class="hljs-string">&#x27;:&#x27;</span>) &lt; <span class="hljs-number">0</span>) {
                    <span class="hljs-keyword">return</span> address.hostAddress
                }
            }
        }
        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getVersionType</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span>: <span class="hljs-built_in">Int</span> {
        <span class="hljs-keyword">val</span> sharedPreferences = context.getSharedPreferences(<span class="hljs-string">&quot;version&quot;</span>, Context.MODE_PRIVATE)
        <span class="hljs-keyword">return</span> sharedPreferences.getInt(<span class="hljs-string">&quot;version_type&quot;</span>, <span class="hljs-number">0</span>)
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setVersionType</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, versionType: <span class="hljs-type">Int</span>)</span></span> {
        <span class="hljs-keyword">val</span> sharedPreferences = context.getSharedPreferences(<span class="hljs-string">&quot;version&quot;</span>, Context.MODE_PRIVATE)
        <span class="hljs-keyword">val</span> editor = sharedPreferences.edit()
        editor.putInt(<span class="hljs-string">&quot;version_type&quot;</span>, versionType)
        editor.apply()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getVersionName</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span>: String? {
        <span class="hljs-keyword">val</span> packageManager = context.packageManager
        <span class="hljs-keyword">val</span> packageInfo: PackageInfo
        <span class="hljs-keyword">var</span> versionName: String? = <span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">try</span> {
            packageInfo = packageManager.getPackageInfo(context.packageName, <span class="hljs-number">0</span>)
            versionName = packageInfo.versionName
        } <span class="hljs-keyword">catch</span> (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
        <span class="hljs-keyword">return</span> versionName
    }

}
</code></pre>
<ol start="4">
<li>应用执行ADB Shell指令</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CmdUtil</span> {
    <span class="hljs-comment">// 执行adb shell并获取返回字符串</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">execCmd</span><span class="hljs-params">(String cmd)</span> {
        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;
        <span class="hljs-type">String</span> <span class="hljs-variable">line</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;
        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">Process</span> <span class="hljs-variable">proc</span> <span class="hljs-operator">=</span> Runtime.getRuntime().exec(cmd);
            <span class="hljs-type">InputStreamReader</span> <span class="hljs-variable">is</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InputStreamReader</span>(proc.getInputStream());
            <span class="hljs-type">BufferedReader</span> <span class="hljs-variable">br</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BufferedReader</span>(is);
            <span class="hljs-keyword">while</span> ((line = br.readLine()) != <span class="hljs-literal">null</span>) {
                result += line;
            }
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        }
        <span class="hljs-keyword">return</span> result;
    }


    <span class="hljs-comment">// cmd=&quot;chmod 777 /dev/ttyACM&quot;</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">execRootCmdSilent</span><span class="hljs-params">(String cmd)</span> {
        <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> -<span class="hljs-number">1</span>;
        <span class="hljs-type">DataOutputStream</span> <span class="hljs-variable">dos</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">Process</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> Runtime.getRuntime().exec(<span class="hljs-string">&quot;su&quot;</span>);
            dos = <span class="hljs-keyword">new</span> <span class="hljs-title class_">DataOutputStream</span>(p.getOutputStream());
            dos.writeBytes(cmd + <span class="hljs-string">&quot;\n&quot;</span>);
            dos.flush();
            dos.writeBytes(<span class="hljs-string">&quot;exit\n&quot;</span>);
            dos.flush();
            p.waitFor();
            result = p.exitValue();
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-keyword">if</span> (dos != <span class="hljs-literal">null</span>) {
                <span class="hljs-keyword">try</span> {
                    dos.close();
                } <span class="hljs-keyword">catch</span> (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        <span class="hljs-keyword">return</span> result;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">execCmdSilent</span><span class="hljs-params">(String cmd)</span> {
        <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> -<span class="hljs-number">1</span>;
        <span class="hljs-type">DataOutputStream</span> <span class="hljs-variable">dos</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">Process</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> Runtime.getRuntime().exec(cmd);
            dos = <span class="hljs-keyword">new</span> <span class="hljs-title class_">DataOutputStream</span>(p.getOutputStream());
            dos.writeBytes(<span class="hljs-string">&quot;exit\n&quot;</span>);
            dos.flush();
            p.waitFor();
            result = p.exitValue();
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-keyword">if</span> (dos != <span class="hljs-literal">null</span>) {
                <span class="hljs-keyword">try</span> {
                    dos.close();
                } <span class="hljs-keyword">catch</span> (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        <span class="hljs-keyword">return</span> result;
    }



    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">rebootSystem</span><span class="hljs-params">(Context context)</span> {
        LogUtils.v(<span class="hljs-string">&quot;Reboot&quot;</span>, <span class="hljs-string">&quot;Reboot--&gt;broadcast_reboot&quot;</span>);
        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">Intent</span> <span class="hljs-variable">intent</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Intent</span>(Intent.ACTION_REBOOT);
            intent.putExtra(<span class="hljs-string">&quot;nowait&quot;</span>, <span class="hljs-number">1</span>);
            intent.putExtra(<span class="hljs-string">&quot;interval&quot;</span>, <span class="hljs-number">1</span>);
            intent.putExtra(<span class="hljs-string">&quot;window&quot;</span>, <span class="hljs-number">0</span>);
            context.sendBroadcast(intent);
        }<span class="hljs-keyword">catch</span> (Exception e) {
            LogUtils.e(e);
        }

    }

    <span class="hljs-meta">@SuppressLint(&quot;HardwareIds&quot;)</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getDeviceSN</span><span class="hljs-params">(Context context)</span> {
        <span class="hljs-type">String</span> <span class="hljs-variable">serialNumber</span> <span class="hljs-operator">=</span> System.getProperty(<span class="hljs-string">&quot;persist.sys.boardsn.value&quot;</span>, <span class="hljs-literal">null</span>);
        <span class="hljs-keyword">if</span>(serialNumber == <span class="hljs-literal">null</span>) {
            serialNumber= Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
        }
        <span class="hljs-keyword">return</span> serialNumber;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">callCmd</span><span class="hljs-params">(String cmd, String filter)</span> {
        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;
        <span class="hljs-type">String</span> <span class="hljs-variable">line</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;
        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">Process</span> <span class="hljs-variable">proc</span> <span class="hljs-operator">=</span> Runtime.getRuntime().exec(cmd);
            <span class="hljs-type">InputStreamReader</span> <span class="hljs-variable">is</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InputStreamReader</span>(proc.getInputStream());
            <span class="hljs-type">BufferedReader</span> <span class="hljs-variable">br</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BufferedReader</span>(is);

            <span class="hljs-keyword">while</span> ((line = br.readLine()) != <span class="hljs-literal">null</span>
                    &amp;&amp; line.contains(filter) == <span class="hljs-literal">false</span>) {
                result += line;
            }

            result = line;
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        }
        <span class="hljs-keyword">return</span> result;
    }

}

</code></pre>
<ol start="5">
<li>网络信息相关获取</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">NetworkInfoUtil</span>(<span class="hljs-keyword">val</span> context: ReactContext) {
    <span class="hljs-keyword">var</span> ipv4: String = <span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">get</span>() {
            <span class="hljs-keyword">return</span> field.apply {
                refreshIp()
            }
        }
    <span class="hljs-keyword">var</span> ipv6: String = <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">&quot;network&quot;</span>
    <span class="hljs-keyword">var</span> bssid = <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> request: NetworkRequest = NetworkRequest
        .Builder()
        .addTransportType(
            NetworkCapabilities.TRANSPORT_WIFI
        ).build()
    <span class="hljs-keyword">var</span> mac: String = <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) <span class="hljs-keyword">as</span> ConnectivityManager



    <span class="hljs-comment">/**
     * 用于Android12 以下获取IP
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">refreshIp</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> en: Enumeration&lt;NetworkInterface&gt; = NetworkInterface.getNetworkInterfaces()
            <span class="hljs-keyword">while</span> (en.hasMoreElements()) {
                <span class="hljs-keyword">val</span> intf: NetworkInterface = en.nextElement()
                <span class="hljs-keyword">val</span> enumIpAddr: Enumeration&lt;InetAddress&gt; = intf.inetAddresses
                <span class="hljs-keyword">while</span> (enumIpAddr.hasMoreElements()) {
                    <span class="hljs-keyword">val</span> inetAddress: InetAddress = enumIpAddr.nextElement()
                    <span class="hljs-keyword">if</span> (inetAddress.isLoopbackAddress) {
                        <span class="hljs-keyword">continue</span>
                    }
                    LogUtils.d(<span class="hljs-string">&quot;WifiPreference&quot;</span>, inetAddress.hostAddress)
                    <span class="hljs-keyword">if</span> (inetAddress <span class="hljs-keyword">is</span> Inet6Address) {
                        LogUtils.d(<span class="hljs-string">&quot;ipv6&quot;</span>, inetAddress.hostAddress)
                        ipv6 = inetAddress.hostAddress <span class="hljs-keyword">as</span> String
                        <span class="hljs-keyword">continue</span>
                    }
                    <span class="hljs-keyword">if</span> (inetAddress <span class="hljs-keyword">is</span> Inet4Address) {
                        LogUtils.d(<span class="hljs-string">&quot;ipv4&quot;</span>, inetAddress.hostAddress)
                        ipv4 = inetAddress.hostAddress <span class="hljs-keyword">as</span> String
                        <span class="hljs-keyword">continue</span>
                    }
                }
            }
        } <span class="hljs-keyword">catch</span> (ex: SocketException) {
            LogUtils.e(<span class="hljs-string">&quot;WifiPreference IpAddress&quot;</span>, ex.toString())
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getMacAddress</span><span class="hljs-params">()</span></span>: String {
        <span class="hljs-keyword">val</span> wm = context.applicationContext.getSystemService(Context.WIFI_SERVICE) <span class="hljs-keyword">as</span> WifiManager?
        <span class="hljs-keyword">if</span> (wm != <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">val</span> winfo = wm.connectionInfo
            <span class="hljs-keyword">if</span> (winfo != <span class="hljs-literal">null</span>) {
                <span class="hljs-keyword">val</span> s = winfo.bssid?: <span class="hljs-string">&quot;&quot;</span>
                LogUtils.d(<span class="hljs-string">&quot;mac&quot;</span>, s)
                <span class="hljs-keyword">return</span> s
            }
        }
        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>
    }
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isLocationEnabled</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">var</span> locationMode = <span class="hljs-number">0</span>
        <span class="hljs-keyword">val</span> locationProviders: String
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) {
            locationMode = <span class="hljs-keyword">try</span> {
                Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE)
            } <span class="hljs-keyword">catch</span> (e: Settings.SettingNotFoundException) {
                e.printStackTrace()
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
            }
            locationMode != Settings.Secure.LOCATION_MODE_OFF
        } <span class="hljs-keyword">else</span> {
            locationProviders = Settings.Secure.getString(
                context.contentResolver,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED
            )
            !TextUtils.isEmpty(locationProviders)
        }
    }
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSSID</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, callback: <span class="hljs-type">Callback</span>)</span></span> {
        <span class="hljs-keyword">val</span> wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) <span class="hljs-keyword">as</span> WifiManager?
        <span class="hljs-keyword">if</span> (!wifiManager!!.isWifiEnabled) {
           callback.invoke(<span class="hljs-string">&quot;&quot;</span>)
           <span class="hljs-keyword">return</span>
        }
        <span class="hljs-keyword">if</span> (wifiManager != <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
                <span class="hljs-keyword">val</span> mRequest: NetworkRequest = NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                    .build()
                <span class="hljs-keyword">val</span> mConnectivityManager: ConnectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) <span class="hljs-keyword">as</span> ConnectivityManager
                <span class="hljs-keyword">val</span> mNetworkCallback = <span class="hljs-keyword">object</span> : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCapabilitiesChanged</span><span class="hljs-params">(network: <span class="hljs-type">Network</span>, networkCapabilities: <span class="hljs-type">NetworkCapabilities</span>)</span></span> {
                        <span class="hljs-keyword">super</span>.onCapabilitiesChanged(network, networkCapabilities)
                        <span class="hljs-keyword">val</span> wifiInfo = networkCapabilities.transportInfo <span class="hljs-keyword">as</span> WifiInfo?
                        <span class="hljs-keyword">var</span> ssid = <span class="hljs-string">&quot;&quot;</span>
                        wifiInfo?.let {
                            ssid = wifiInfo.ssid.replace(<span class="hljs-string">&quot;\&quot;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;&lt;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;&gt;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)
                            LogUtils.d(<span class="hljs-string">&quot;ssid&quot;</span>, ssid)
                            mConnectivityManager.unregisterNetworkCallback(<span class="hljs-keyword">this</span>)
                        }
                        <span class="hljs-keyword">try</span> {
                            callback.invoke(ssid)
                        }<span class="hljs-keyword">catch</span> (e: Exception){
                        }
                    }
                }
                mConnectivityManager.registerNetworkCallback(mRequest, mNetworkCallback)
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">val</span> connectionInfo = wifiManager.connectionInfo
                <span class="hljs-keyword">if</span> (connectionInfo?.networkId != -<span class="hljs-number">1</span>) {
                    <span class="hljs-keyword">var</span> ssid = connectionInfo?.ssid?.replace(<span class="hljs-string">&quot;\&quot;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)
                    callback.invoke(ssid)
                }
            }
        }
<span class="hljs-comment">//        return ssid</span>
    }

    <span class="hljs-comment">// 获取系统LocationMode的值</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getLocationMode</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Int</span> {
        <span class="hljs-comment">// 返回值是0 的时候, 就是 LOCATION_MODE_OFF 这个宏</span>
        <span class="hljs-comment">//  返回值为1的时候 就是 LOCATION_MODE_SENSORS_ONLY 就是上面的仅限设备GPS</span>
        <span class="hljs-comment">//  返回值是2, 就是 LOCATION_MODE_BATTERY_SAVING 低耗电量</span>
        <span class="hljs-comment">//  返回值为3, 就是 LOCATION_MODE_HIGH_ACCURACY 仅限设备</span>
        <span class="hljs-keyword">var</span> locationMode = <span class="hljs-number">0</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) {
            locationMode = <span class="hljs-keyword">try</span> {
                Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE)
            } <span class="hljs-keyword">catch</span> (e: Settings.SettingNotFoundException) {
                LogUtils.e(<span class="hljs-string">&quot;getLocationMode&quot;</span>, e.message)
                e.printStackTrace()
                <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
            }
            locationMode
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-number">0</span>
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getScanWifiEnable</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">val</span> locationManager = context.getSystemService(Context.LOCATION_SERVICE) <span class="hljs-keyword">as</span> LocationManager?
        <span class="hljs-keyword">return</span> locationManager!!.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSSID</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, callback: (<span class="hljs-type">ssid</span>: <span class="hljs-type">String</span>?)-&gt; <span class="hljs-type">Unit</span>)</span></span> {
        <span class="hljs-keyword">val</span> wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) <span class="hljs-keyword">as</span> WifiManager?
        <span class="hljs-keyword">if</span> (!wifiManager!!.isWifiEnabled) {
            callback.invoke(<span class="hljs-string">&quot;&quot;</span>)
            <span class="hljs-keyword">return</span>
        }
        <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
            <span class="hljs-keyword">val</span> mRequest: NetworkRequest = NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .build()
            <span class="hljs-keyword">val</span> mConnectivityManager: ConnectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) <span class="hljs-keyword">as</span> ConnectivityManager
            <span class="hljs-keyword">val</span> mNetworkCallback = <span class="hljs-keyword">object</span> : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCapabilitiesChanged</span><span class="hljs-params">(network: <span class="hljs-type">Network</span>, networkCapabilities: <span class="hljs-type">NetworkCapabilities</span>)</span></span> {
                    <span class="hljs-keyword">super</span>.onCapabilitiesChanged(network, networkCapabilities)
                    <span class="hljs-keyword">val</span> wifiInfo = networkCapabilities.transportInfo <span class="hljs-keyword">as</span> WifiInfo?
                    <span class="hljs-keyword">var</span> ssid = <span class="hljs-string">&quot;&quot;</span>
                    wifiInfo?.let {
                        ssid = wifiInfo.ssid.replace(<span class="hljs-string">&quot;\&quot;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;&lt;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;&gt;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)
                        LogUtils.d(<span class="hljs-string">&quot;ssid&quot;</span>, ssid)
                        mConnectivityManager.unregisterNetworkCallback(<span class="hljs-keyword">this</span>)
                    }
                    <span class="hljs-keyword">try</span> {
                        callback.invoke(ssid)
                    }<span class="hljs-keyword">catch</span> (e: Exception){
                    }
                }
            }
            mConnectivityManager.registerNetworkCallback(mRequest, mNetworkCallback)
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">val</span> connectionInfo = wifiManager.connectionInfo
            <span class="hljs-keyword">if</span> (connectionInfo?.networkId != -<span class="hljs-number">1</span>) {
                <span class="hljs-keyword">var</span> ssid = connectionInfo?.ssid?.replace(<span class="hljs-string">&quot;\&quot;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)
                connectionInfo.bssid?.let {
                    bssid = it
                }
                callback.invoke(ssid)
            }
        }
<span class="hljs-comment">//        return ssid</span>
    }
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">initNetwork</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">when</span> (Build.VERSION.SDK_INT) {
            Build.VERSION_CODES.S -&gt; {
                <span class="hljs-keyword">val</span> obj = <span class="hljs-keyword">object</span> : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onLinkPropertiesChanged</span><span class="hljs-params">(
                        network: <span class="hljs-type">Network</span>,
                        linkProperties: <span class="hljs-type">LinkProperties</span>
                    )</span></span> {
                        <span class="hljs-keyword">super</span>.onLinkPropertiesChanged(network, linkProperties)
                        <span class="hljs-keyword">for</span> (addr <span class="hljs-keyword">in</span> linkProperties.linkAddresses) {
                            LogUtils.d(<span class="hljs-string">&quot;ip&quot;</span>, addr.address.hostAddress)
                            <span class="hljs-keyword">if</span> (addr.address <span class="hljs-keyword">is</span> Inet6Address) {
                                LogUtils.d(<span class="hljs-string">&quot;ipv6&quot;</span>, addr.address.hostAddress)
                                ipv6 = addr.address.hostAddress <span class="hljs-keyword">as</span> String
                                <span class="hljs-keyword">continue</span>
                            }
                            <span class="hljs-keyword">if</span> (addr.address <span class="hljs-keyword">is</span> Inet4Address) {
                                LogUtils.d(<span class="hljs-string">&quot;ipv4&quot;</span>, addr.address.hostAddress)
                                ipv4 = addr.address.hostAddress <span class="hljs-keyword">as</span> String
                                <span class="hljs-keyword">continue</span>
                            }
                        }
                    }
                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCapabilitiesChanged</span><span class="hljs-params">(
                        network: <span class="hljs-type">Network</span>,
                        networkCapabilities: <span class="hljs-type">NetworkCapabilities</span>
                    )</span></span> {
                        <span class="hljs-keyword">super</span>.onCapabilitiesChanged(network, networkCapabilities)
                        <span class="hljs-keyword">try</span> {
                            <span class="hljs-keyword">val</span> wifiInfo = networkCapabilities.transportInfo <span class="hljs-keyword">as</span> WifiInfo
                            <span class="hljs-keyword">if</span> (wifiInfo.bssid != <span class="hljs-string">&quot;02:00:00:00:00:00&quot;</span>){
                                mac = wifiInfo.bssid
                            }
                            <span class="hljs-keyword">if</span> (wifiInfo.ssid != <span class="hljs-string">&quot;&lt;unknown ssid&gt;&quot;</span>) {
<span class="hljs-comment">//                                ssid = wifiInfo.ssid</span>
                            }
                        } <span class="hljs-keyword">catch</span> (e: Exception) {
                            LogUtils.e(e)
                        }
                    }

                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onLost</span><span class="hljs-params">(network: <span class="hljs-type">Network</span>)</span></span> {
<span class="hljs-comment">//                        ssid = &quot;&quot;</span>
                        mac = <span class="hljs-string">&quot;&quot;</span>
                        <span class="hljs-keyword">super</span>.onLost(network)
                    }

                    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onUnavailable</span><span class="hljs-params">()</span></span> {
<span class="hljs-comment">//                        ssid = &quot;&quot;</span>
                        mac = <span class="hljs-string">&quot;&quot;</span>
                        <span class="hljs-keyword">super</span>.onUnavailable()
                    }
                }
                connMgr.registerNetworkCallback(request, obj)
            }
            <span class="hljs-keyword">else</span> -&gt; {
                <span class="hljs-keyword">val</span> wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) <span class="hljs-keyword">as</span> WifiManager
                <span class="hljs-keyword">val</span> net = wifiManager.connectionInfo
<span class="hljs-comment">//                ssid = net.ssid?: &quot;&quot;</span>
                mac = net.bssid?: <span class="hljs-string">&quot;&quot;</span>
                refreshIp()
            }
        }
    }

    <span class="hljs-keyword">init</span> {
        initNetwork()
    }
}
</code></pre>
<ol start="6">
<li>签名、加密与keystore相关</li>
</ol>
<ul>
<li>获取签名：</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>
<span class="hljs-keyword">import</span> android.content.Context
<span class="hljs-keyword">import</span> android.content.pm.PackageInfo
<span class="hljs-keyword">import</span> android.content.pm.PackageManager
<span class="hljs-keyword">import</span> android.content.pm.Signature
<span class="hljs-keyword">import</span> android.util.Log
<span class="hljs-keyword">import</span> java.security.MessageDigest
<span class="hljs-keyword">import</span> java.util.Locale

<span class="hljs-comment">/**
 * 获取签名工具类
 */</span>
<span class="hljs-keyword">object</span> AppSigning {
    <span class="hljs-keyword">val</span> MD5 = <span class="hljs-string">&quot;MD5&quot;</span>
    <span class="hljs-keyword">val</span> SHA1 = <span class="hljs-string">&quot;SHA1&quot;</span>
    <span class="hljs-keyword">val</span> SHA256 = <span class="hljs-string">&quot;SHA256&quot;</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> mSignMap = HashMap&lt;String, ArrayList&lt;String&gt;?&gt;()

    <span class="hljs-comment">/**
     * 返回一个签名的对应类型的字符串
     *
     * <span class="hljs-doctag">@param</span> context
     * <span class="hljs-doctag">@param</span> type
     * <span class="hljs-doctag">@return</span> 因为一个安装包可以被多个签名文件签名，所以返回一个签名信息的list
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignInfo</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>?, type: <span class="hljs-type">String</span>?)</span></span>: ArrayList&lt;String&gt;? {
        <span class="hljs-keyword">if</span> (context == <span class="hljs-literal">null</span> || type == <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }
        <span class="hljs-keyword">val</span> packageName = context.packageName ?: <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        <span class="hljs-keyword">if</span> (mSignMap[type] != <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">return</span> mSignMap[type]
        }
        <span class="hljs-keyword">val</span> mList = ArrayList&lt;String&gt;()
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> signs = getSignatures(context, packageName)
            <span class="hljs-keyword">for</span> (sig: Signature <span class="hljs-keyword">in</span> signs!!) {
                <span class="hljs-keyword">var</span> tmp = <span class="hljs-string">&quot;error!&quot;</span>
                <span class="hljs-keyword">if</span> (MD5 == type) {
                    tmp = getSignatureByteString(sig, MD5)
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (SHA1 == type) {
                    tmp = getSignatureByteString(sig, SHA1)
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (SHA256 == type) {
                    tmp = getSignatureByteString(sig, SHA256)
                }
                mList.add(tmp)
            }
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            Log.e(<span class="hljs-string">&quot;e&quot;</span>, e.message!!)
        }
        mSignMap[type] = mList
        <span class="hljs-keyword">return</span> mList
    }

    <span class="hljs-comment">/**
     * 获取签名sha1值
     *
     * <span class="hljs-doctag">@param</span> context
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSha1</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>?)</span></span>: String {
        <span class="hljs-keyword">var</span> res = <span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">val</span> mlist = getSignInfo(context, SHA1)
        <span class="hljs-keyword">if</span> (mlist != <span class="hljs-literal">null</span> &amp;&amp; mlist.size != <span class="hljs-number">0</span>) {
            res = mlist[<span class="hljs-number">0</span>]
        }
        <span class="hljs-keyword">return</span> res
    }

    <span class="hljs-comment">/**
     * 获取签名MD5值
     *
     * <span class="hljs-doctag">@param</span> context
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getMD5</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>?)</span></span>: String {
        <span class="hljs-keyword">var</span> res = <span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">val</span> mlist = getSignInfo(context, MD5)
        <span class="hljs-keyword">if</span> (mlist != <span class="hljs-literal">null</span> &amp;&amp; mlist.size != <span class="hljs-number">0</span>) {
            res = mlist[<span class="hljs-number">0</span>]
        }
        <span class="hljs-keyword">return</span> res
    }

    <span class="hljs-comment">/**
     * 获取签名SHA256值
     *
     * <span class="hljs-doctag">@param</span> context
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSHA256</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>?)</span></span>: String {
        <span class="hljs-keyword">var</span> res = <span class="hljs-string">&quot;&quot;</span>
        <span class="hljs-keyword">val</span> mlist = getSignInfo(context, SHA256)
        <span class="hljs-keyword">if</span> (mlist != <span class="hljs-literal">null</span> &amp;&amp; mlist.size != <span class="hljs-number">0</span>) {
            res = mlist[<span class="hljs-number">0</span>]
        }
        <span class="hljs-keyword">return</span> res
    }

    <span class="hljs-comment">/**
     * 返回对应包的签名信息
     *
     * <span class="hljs-doctag">@param</span> context
     * <span class="hljs-doctag">@param</span> packageName
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignatures</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, packageName: <span class="hljs-type">String</span>)</span></span>: Array&lt;Signature&gt;? {
        <span class="hljs-keyword">var</span> packageInfo: PackageInfo? = <span class="hljs-literal">null</span>
        <span class="hljs-keyword">try</span> {
            packageInfo =
                context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
            <span class="hljs-keyword">return</span> packageInfo.signatures
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            Log.e(<span class="hljs-string">&quot;e&quot;</span>, e.toString())
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
    }

    <span class="hljs-comment">/**
     * 获取相应的类型的字符串（把签名的byte[]信息转换成16进制）
     *
     * <span class="hljs-doctag">@param</span> sig
     * <span class="hljs-doctag">@param</span> type
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignatureString</span><span class="hljs-params">(sig: <span class="hljs-type">Signature</span>, type: <span class="hljs-type">String</span>)</span></span>: String {
        <span class="hljs-keyword">val</span> hexBytes = sig.toByteArray()
        <span class="hljs-keyword">var</span> fingerprint = <span class="hljs-string">&quot;error!&quot;</span>
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> digest = MessageDigest.getInstance(type)
            <span class="hljs-keyword">if</span> (digest != <span class="hljs-literal">null</span>) {
                <span class="hljs-keyword">val</span> digestBytes = digest.digest(hexBytes)
                <span class="hljs-keyword">val</span> sb = StringBuilder()
                <span class="hljs-keyword">for</span> (digestByte: <span class="hljs-built_in">Byte</span> <span class="hljs-keyword">in</span> digestBytes) {
                    sb.append(
                        Integer.toHexString(digestByte.toInt() and <span class="hljs-number">0xFF</span> or <span class="hljs-number">0x100</span>)
                            .substring(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>)
                    )
                }
                fingerprint = sb.toString()
            }
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            Log.e(<span class="hljs-string">&quot;e&quot;</span>, e.toString())
        }
        <span class="hljs-keyword">return</span> fingerprint
    }

    <span class="hljs-comment">/**
     * 获取相应的类型的字符串（把签名的byte[]信息转换成 95:F4:D4:FG 这样的字符串形式）
     *
     * <span class="hljs-doctag">@param</span> sig
     * <span class="hljs-doctag">@param</span> type
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSignatureByteString</span><span class="hljs-params">(sig: <span class="hljs-type">Signature</span>, type: <span class="hljs-type">String</span>)</span></span>: String {
        <span class="hljs-keyword">val</span> hexBytes = sig.toByteArray()
        <span class="hljs-keyword">var</span> fingerprint = <span class="hljs-string">&quot;error!&quot;</span>
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> digest = MessageDigest.getInstance(type)
            <span class="hljs-keyword">if</span> (digest != <span class="hljs-literal">null</span>) {
                <span class="hljs-keyword">val</span> digestBytes = digest.digest(hexBytes)
                <span class="hljs-keyword">val</span> sb = StringBuilder()
                <span class="hljs-keyword">for</span> (digestByte: <span class="hljs-built_in">Byte</span> <span class="hljs-keyword">in</span> digestBytes) {
                    sb.append(
                        Integer.toHexString(digestByte.toInt() and <span class="hljs-number">0xFF</span> or <span class="hljs-number">0x100</span>)
                            .substring(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>).uppercase(Locale.getDefault())
                    )
                    sb.append(<span class="hljs-string">&quot;:&quot;</span>)
                }
                fingerprint = sb.substring(<span class="hljs-number">0</span>, sb.length - <span class="hljs-number">1</span>).toString()
            }
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            Log.e(<span class="hljs-string">&quot;e&quot;</span>, e.toString())
        }
        <span class="hljs-keyword">return</span> fingerprint
    }
}

</code></pre>
<ul>
<li>加解密：</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> java.io.IOException
<span class="hljs-keyword">import</span> java.nio.charset.Charset
<span class="hljs-keyword">import</span> java.security.*
<span class="hljs-keyword">import</span> java.security.cert.CertificateException
<span class="hljs-keyword">import</span> javax.crypto.*
<span class="hljs-keyword">import</span> javax.crypto.spec.GCMParameterSpec
<span class="hljs-comment">/**
 * 解密
 */</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Decrypt</span> {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> TRANSFORMATION = <span class="hljs-string">&quot;AES/GCM/NoPadding&quot;</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> ANDROID_KEY_STORE = <span class="hljs-string">&quot;AndroidKeyStore&quot;</span>

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> keyStore: KeyStore? = <span class="hljs-literal">null</span>

    <span class="hljs-keyword">init</span> {
        initKeyStore()
    }

    <span class="hljs-meta">@Throws(
        KeyStoreException::class,
        CertificateException::class,
        NoSuchAlgorithmException::class,
        IOException::class
    )</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">initKeyStore</span><span class="hljs-params">()</span></span> {
        keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply {
            load(<span class="hljs-literal">null</span>)
        }
    }

    <span class="hljs-meta">@Throws(
        UnrecoverableEntryException::class,
        NoSuchAlgorithmException::class,
        KeyStoreException::class,
        NoSuchProviderException::class,
        NoSuchPaddingException::class,
        InvalidKeyException::class,
        IOException::class,
        BadPaddingException::class,
        IllegalBlockSizeException::class,
        InvalidAlgorithmParameterException::class
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">decryptData</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>, encryptedData: <span class="hljs-type">ByteArray</span>?, encryptionIv: <span class="hljs-type">ByteArray</span>?)</span></span>: String {
        <span class="hljs-keyword">val</span> cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
        <span class="hljs-keyword">val</span> spec = GCMParameterSpec(<span class="hljs-number">128</span>, encryptionIv)
        cipher.<span class="hljs-keyword">init</span>(Cipher.DECRYPT_MODE, getSecretKey(alias), spec)
        <span class="hljs-keyword">return</span> String(cipher.doFinal(encryptedData), Charset.forName(<span class="hljs-string">&quot;UTF-8&quot;</span>))
    }

    <span class="hljs-meta">@Throws(
        NoSuchAlgorithmException::class,
        UnrecoverableEntryException::class,
        KeyStoreException::class
    )</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSecretKey</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>)</span></span>: SecretKey? {
        <span class="hljs-keyword">return</span> (keyStore!!.getEntry(alias, <span class="hljs-literal">null</span>) <span class="hljs-keyword">as</span> KeyStore.SecretKeyEntry).secretKey
    }
}
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> android.security.keystore.KeyGenParameterSpec
<span class="hljs-keyword">import</span> android.security.keystore.KeyProperties
<span class="hljs-keyword">import</span> androidx.<span class="hljs-keyword">annotation</span>.NonNull
<span class="hljs-keyword">import</span> java.io.IOException
<span class="hljs-keyword">import</span> java.security.*
<span class="hljs-keyword">import</span> javax.crypto.*

<span class="hljs-keyword">data</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">EncryptIndex</span>(
    <span class="hljs-keyword">val</span> encryption: ByteArray,
    <span class="hljs-keyword">val</span> vi: ByteArray
)

<span class="hljs-comment">/**
 * 加密
 */</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Encrypt</span> {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> TRANSFORMATION = <span class="hljs-string">&quot;AES/GCM/NoPadding&quot;</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> ANDROID_KEY_STORE = <span class="hljs-string">&quot;AndroidKeyStore&quot;</span>

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> iv: ByteArray

    <span class="hljs-meta">@Throws(
        UnrecoverableEntryException::class,
        NoSuchAlgorithmException::class,
        KeyStoreException::class,
        NoSuchProviderException::class,
        NoSuchPaddingException::class,
        InvalidKeyException::class,
        IOException::class,
        InvalidAlgorithmParameterException::class,
        SignatureException::class,
        BadPaddingException::class,
        IllegalBlockSizeException::class
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">encryptText</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>, textToEncrypt: <span class="hljs-type">String</span>)</span></span>: EncryptIndex {
        <span class="hljs-keyword">val</span> cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.<span class="hljs-keyword">init</span>(Cipher.ENCRYPT_MODE, getSecretKey(alias))
        <span class="hljs-keyword">return</span> EncryptIndex(cipher.doFinal(textToEncrypt.toByteArray(charset(<span class="hljs-string">&quot;UTF-8&quot;</span>))), cipher.iv)
    }

    <span class="hljs-meta">@NonNull</span>
    <span class="hljs-meta">@Throws(
        NoSuchAlgorithmException::class,
        NoSuchProviderException::class,
        InvalidAlgorithmParameterException::class
    )</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSecretKey</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>)</span></span>: SecretKey? {
        <span class="hljs-keyword">val</span> keyGenerator: KeyGenerator = KeyGenerator
            .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
        keyGenerator.<span class="hljs-keyword">init</span>(
            KeyGenParameterSpec.Builder(
                alias,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
            )
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .build()
        )
        <span class="hljs-keyword">return</span> keyGenerator.generateKey()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getIv</span><span class="hljs-params">()</span></span>: ByteArray {
        <span class="hljs-keyword">return</span> iv
    }
}
</code></pre>
<ul>
<li>keystore数据存储</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">KeyStoreUtil</span>(<span class="hljs-keyword">val</span> context: ReactContext) {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> encrypt = Encrypt()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> decrypt = Decrypt()
    <span class="hljs-keyword">val</span> kDfpSharedPreference = SharePreferencesBuilder().build(context, <span class="hljs-string">&quot;security&quot;</span>)
    <span class="hljs-keyword">val</span> cerfPfxSharedPreferences = SharePreferencesBuilder().build(context, <span class="hljs-string">&quot;pfx&quot;</span>)
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> pfxKeyStore: KeyStore? = <span class="hljs-literal">null</span>


    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getPfxKeyStore</span><span class="hljs-params">(pfx: <span class="hljs-type">ByteArrayInputStream</span>, pwd: <span class="hljs-type">String</span>)</span></span>: KeyStore? {
        <span class="hljs-keyword">if</span> (pfxKeyStore != <span class="hljs-literal">null</span>)
            <span class="hljs-keyword">return</span> pfxKeyStore
        pfxKeyStore = KeyStore.getInstance(<span class="hljs-string">&quot;PKCS12&quot;</span>)
        pfxKeyStore!!.load(pfx, pwd.toCharArray())
        <span class="hljs-keyword">return</span> pfxKeyStore
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">decodeData</span><span class="hljs-params">(user: <span class="hljs-type">String</span>, <span class="hljs-keyword">data</span>: <span class="hljs-type">String</span>, iv: <span class="hljs-type">String</span>)</span></span>: String {
        <span class="hljs-keyword">val</span> decodeData: ByteArray = Base64.decode(<span class="hljs-keyword">data</span>, Base64.DEFAULT)
        <span class="hljs-keyword">val</span> decodeIv: ByteArray = Base64.decode(iv, Base64.DEFAULT)
        <span class="hljs-keyword">return</span> decryptData(user, decodeData, decodeIv)
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAssertIndex</span><span class="hljs-params">(user: <span class="hljs-type">String</span>)</span></span>: NativeAsset? {
        <span class="hljs-keyword">val</span> <span class="hljs-keyword">data</span> = cerfPfxSharedPreferences.getString(user)
        <span class="hljs-keyword">val</span> iv = cerfPfxSharedPreferences.getString(user + <span class="hljs-string">&quot;iv&quot;</span>)
        <span class="hljs-keyword">val</span> index = decodeData(user, <span class="hljs-keyword">data</span>, iv)
        <span class="hljs-keyword">return</span> Gson().fromJson(index, NativeAsset::<span class="hljs-keyword">class</span>.java)
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getPfxCertWithHttp</span><span class="hljs-params">(user: <span class="hljs-type">String</span>)</span></span>: OkHttpClient {
        <span class="hljs-keyword">val</span> assetModel = getAssertIndex(user)
        <span class="hljs-keyword">val</span> pfxByte = Base64.decode(assetModel?.pfx, Base64.DEFAULT)
        <span class="hljs-keyword">val</span> pfx = pfxByte.inputStream()
        <span class="hljs-keyword">val</span> pwd = assetModel?.password
        <span class="hljs-keyword">val</span> sslParams = TrustManagerUtil.getSslSocketFactory(pfx, pwd, context, user)
        <span class="hljs-keyword">return</span> OkHttpClient.Builder()
            .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
            .hostnameVerifier{ _, _ -&gt; <span class="hljs-literal">true</span>}
            .build()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getPfxSn</span><span class="hljs-params">(user: <span class="hljs-type">String</span>)</span></span>: String? {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> <span class="hljs-keyword">data</span> = cerfPfxSharedPreferences.getString(user)
            <span class="hljs-keyword">val</span> iv = cerfPfxSharedPreferences.getString(user + <span class="hljs-string">&quot;iv&quot;</span>)
            <span class="hljs-keyword">val</span> index = decodeData(user, <span class="hljs-keyword">data</span>, iv)
            <span class="hljs-keyword">val</span> assetModel = Gson().fromJson(index, NativeAsset::<span class="hljs-keyword">class</span>.java)
            <span class="hljs-keyword">val</span> pfx = Base64.decode(assetModel.pfx, Base64.DEFAULT).inputStream()
            <span class="hljs-keyword">val</span> sn = getPfxSn(pfx, assetModel.password).apply {
                LogUtils.d(<span class="hljs-string">&quot;pfx sn:&quot;</span>, <span class="hljs-keyword">this</span>)
            }
            sn
        }<span class="hljs-keyword">catch</span> (e: Exception) {
            e.printStackTrace()
            e.message
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getCert</span><span class="hljs-params">(user: <span class="hljs-type">String</span>)</span></span>: X509Certificate? {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> <span class="hljs-keyword">data</span> = cerfPfxSharedPreferences.getString(user)
            <span class="hljs-keyword">val</span> iv = cerfPfxSharedPreferences.getString(user + <span class="hljs-string">&quot;iv&quot;</span>)
            <span class="hljs-keyword">val</span> index = decodeData(user, <span class="hljs-keyword">data</span>, iv)
            <span class="hljs-keyword">val</span> assetModel = Gson().fromJson(index, NativeAsset::<span class="hljs-keyword">class</span>.java)
            <span class="hljs-keyword">val</span> pfx = Base64.decode(assetModel.pfx, Base64.DEFAULT).inputStream()
            getX509Certificate(pfx, assetModel.password)!!.cert
        }<span class="hljs-keyword">catch</span> (e: Exception) {
            e.printStackTrace()
            e.message
            <span class="hljs-literal">null</span>
        }
    }

    <span class="hljs-keyword">data</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CertData</span>(
        <span class="hljs-keyword">val</span> cert: X509Certificate,
        <span class="hljs-keyword">val</span> privateKey: PrivateKey,
        <span class="hljs-keyword">var</span> caList: ArrayList&lt;X509Certificate&gt; = ArrayList()
    )
    <span class="hljs-comment">// pfx证书</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getX509Certificate</span><span class="hljs-params">(pfx: <span class="hljs-type">ByteArrayInputStream</span>, pwd: <span class="hljs-type">String</span>)</span></span>: CertData? {
        <span class="hljs-keyword">val</span> pfxKeyStore = getPfxKeyStore(pfx, pwd)
        <span class="hljs-keyword">val</span> aliases: Enumeration&lt;String&gt; = pfxKeyStore!!.aliases()
        <span class="hljs-keyword">var</span> x509Certificate: X509Certificate? = <span class="hljs-literal">null</span>
        <span class="hljs-keyword">var</span> privateKey: PrivateKey? = <span class="hljs-literal">null</span>
        aliases.iterator().forEach {
            <span class="hljs-keyword">if</span> (pfxKeyStore.isKeyEntry(it)) {
                <span class="hljs-keyword">try</span> {
                   privateKey = pfxKeyStore.getKey(it, pwd.toCharArray()) <span class="hljs-keyword">as</span> PrivateKey
                   x509Certificate = pfxKeyStore.getCertificate(it) <span class="hljs-keyword">as</span> X509Certificate
                }<span class="hljs-keyword">catch</span> (e: Exception){
                    LogUtils.e(<span class="hljs-string">&quot;load x509 fail&quot;</span>, e)
                }
            }
        }
        <span class="hljs-keyword">return</span> x509Certificate?.let { privateKey?.let { it1 -&gt; CertData(it, it1) } }
    }

    <span class="hljs-comment">// 获取证书序列号</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getPfxSn</span><span class="hljs-params">(pfx: <span class="hljs-type">ByteArrayInputStream</span>, pwd: <span class="hljs-type">String</span>)</span></span>: String? {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> cert = getX509Certificate(pfx, pwd)!!.cert
            <span class="hljs-keyword">val</span> serialNumber = cert.serialNumber
            serialNumber.toString()
        }<span class="hljs-keyword">catch</span> (e: Exception) {
            e.printStackTrace()
            <span class="hljs-literal">null</span>
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">deleteCert</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>)</span></span> {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">val</span> aliases = pfxKeyStore!!.aliases();
            aliases.iterator().forEach {
                <span class="hljs-keyword">if</span> (pfxKeyStore!!.isKeyEntry(it)) {
                    pfxKeyStore?.deleteEntry(alias)
                }
            }
        }<span class="hljs-keyword">catch</span> (e: Exception) {
            e.printStackTrace()
        }
    }

    <span class="hljs-comment">// 加密</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">encryptData</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>, <span class="hljs-keyword">data</span>: <span class="hljs-type">String</span>)</span></span>: EncryptIndex? {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> {
            encrypt.encryptText(alias, <span class="hljs-keyword">data</span>)
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            e.printStackTrace()
            LogUtils.e(e.message)
            <span class="hljs-literal">null</span>
        }
    }

    <span class="hljs-comment">// 解密</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">decryptData</span><span class="hljs-params">(alias: <span class="hljs-type">String</span>, <span class="hljs-keyword">data</span>: <span class="hljs-type">ByteArray</span>, iv: <span class="hljs-type">ByteArray</span>)</span></span>: String {
        <span class="hljs-keyword">return</span> decrypt.decryptData(alias, <span class="hljs-keyword">data</span>, iv)
    }

}
</code></pre>
<ul>
<li>http自定义信任管理</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> android.<span class="hljs-keyword">annotation</span>.SuppressLint
<span class="hljs-keyword">import</span> com.facebook.react.bridge.ReactContext
<span class="hljs-keyword">import</span> java.io.IOException
<span class="hljs-keyword">import</span> java.io.InputStream
<span class="hljs-keyword">import</span> java.security.KeyStore
<span class="hljs-keyword">import</span> java.security.KeyStoreException
<span class="hljs-keyword">import</span> java.security.NoSuchAlgorithmException
<span class="hljs-keyword">import</span> java.security.cert.CertificateException
<span class="hljs-keyword">import</span> java.security.cert.X509Certificate
<span class="hljs-keyword">import</span> javax.net.ssl.*

<span class="hljs-keyword">class</span> <span class="hljs-title class_">TrustManagerUtil</span> {
    <span class="hljs-keyword">class</span> <span class="hljs-title class_">SSLParams</span> {
        <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> sSLSocketFactory: SSLSocketFactory
        <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> trustManager: X509TrustManager
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UnSafeTrustManager</span> : <span class="hljs-type">X509TrustManager</span> {
        <span class="hljs-meta">@SuppressLint(<span class="hljs-string">&quot;TrustAllX509TrustManager&quot;</span>)</span>
        <span class="hljs-meta">@Throws(CertificateException::class)</span>
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkClientTrusted</span><span class="hljs-params">(chain: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">X509Certificate</span>&gt;, authType: <span class="hljs-type">String</span>)</span></span> {
        }

        <span class="hljs-meta">@SuppressLint(<span class="hljs-string">&quot;TrustAllX509TrustManager&quot;</span>)</span>
        <span class="hljs-meta">@Throws(CertificateException::class)</span>
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkServerTrusted</span><span class="hljs-params">(chain: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">X509Certificate</span>&gt;, authType: <span class="hljs-type">String</span>)</span></span> {
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAcceptedIssuers</span><span class="hljs-params">()</span></span>: Array&lt;X509Certificate&gt; {
            <span class="hljs-keyword">return</span> arrayOf()
        }
    }


    <span class="hljs-keyword">private</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyTrustManager</span> <span class="hljs-meta">@Throws(NoSuchAlgorithmException::class, KeyStoreException::class)</span>
    <span class="hljs-keyword">constructor</span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> localTrustManager: X509TrustManager) : X509TrustManager {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> defaultTrustManager: X509TrustManager?

        <span class="hljs-keyword">init</span> {
            <span class="hljs-keyword">val</span> var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
            var4.<span class="hljs-keyword">init</span>(<span class="hljs-literal">null</span> <span class="hljs-keyword">as</span> KeyStore?)
            defaultTrustManager = chooseTrustManager(var4.trustManagers)
        }


        <span class="hljs-meta">@SuppressLint(<span class="hljs-string">&quot;TrustAllX509TrustManager&quot;</span>)</span>
        <span class="hljs-meta">@Throws(CertificateException::class)</span>
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkClientTrusted</span><span class="hljs-params">(chain: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">X509Certificate</span>&gt;, authType: <span class="hljs-type">String</span>)</span></span> {

        }

        <span class="hljs-meta">@Throws(CertificateException::class)</span>
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkServerTrusted</span><span class="hljs-params">(chain: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">X509Certificate</span>&gt;, authType: <span class="hljs-type">String</span>)</span></span> {
            <span class="hljs-keyword">try</span> {
                defaultTrustManager!!.checkServerTrusted(chain, authType)
            } <span class="hljs-keyword">catch</span> (ce: CertificateException) {
                localTrustManager.checkServerTrusted(chain, authType)
            }
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAcceptedIssuers</span><span class="hljs-params">()</span></span>: Array&lt;X509Certificate&gt; {
            <span class="hljs-keyword">return</span> arrayOf()
        }
    }

    <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> {

        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSslSocketFactory</span><span class="hljs-params">(certificates: <span class="hljs-type">InputStream</span>, password: <span class="hljs-type">String</span>?, context: <span class="hljs-type">ReactContext</span>, alias: <span class="hljs-type">String</span>)</span></span>: SSLParams {
            <span class="hljs-keyword">val</span> sslParams = SSLParams()
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> trustManagers = prepareTrustManager(certificates, context, alias)
                <span class="hljs-keyword">val</span> keyManagers = prepareKeyManager(certificates, password)
                <span class="hljs-keyword">val</span> sslContext = SSLContext.getInstance(<span class="hljs-string">&quot;TLS&quot;</span>)
                <span class="hljs-keyword">var</span> trustManager: X509TrustManager = UnSafeTrustManager()

                trustManagers?.let { arrayOfTrustManagers -&gt;
                    <span class="hljs-keyword">val</span> x509TrustManager = chooseTrustManager(arrayOfTrustManagers)
                    x509TrustManager?.let { trustManager = MyTrustManager(it) }
                }

                sslContext.<span class="hljs-keyword">init</span>(keyManagers, arrayOf&lt;TrustManager&gt;(trustManager), <span class="hljs-literal">null</span>)
                sslParams.sSLSocketFactory = sslContext.socketFactory
                sslParams.trustManager = trustManager
                <span class="hljs-keyword">return</span> sslParams
            } <span class="hljs-keyword">catch</span> (e: Exception) {
                <span class="hljs-keyword">throw</span> AssertionError(e)
            }
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">prepareTrustManager</span><span class="hljs-params">(certificate: <span class="hljs-type">InputStream</span>, context: <span class="hljs-type">ReactContext</span>, alias: <span class="hljs-type">String</span>)</span></span>: Array&lt;TrustManager&gt;? {
            <span class="hljs-keyword">if</span> (certificate == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">val</span> keyStore = KeyStore.getInstance(<span class="hljs-string">&quot;PKCS12&quot;</span>)
                keyStore.load(<span class="hljs-literal">null</span>)
                <span class="hljs-keyword">val</span> cert = KeyStoreUtil(context).getCert(alias)
                keyStore.setCertificateEntry(alias, cert)
                <span class="hljs-keyword">try</span> {
                    certificate.close()
                } <span class="hljs-keyword">catch</span> (e: IOException) { }

                <span class="hljs-keyword">val</span> trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
                trustManagerFactory.<span class="hljs-keyword">init</span>(keyStore)

                <span class="hljs-keyword">return</span> trustManagerFactory.trustManagers
            } <span class="hljs-keyword">catch</span> (e: Exception) {
                e.printStackTrace()
            }

            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">prepareKeyManager</span><span class="hljs-params">(bksFile: <span class="hljs-type">InputStream</span>?, password: <span class="hljs-type">String</span>?)</span></span>: Array&lt;KeyManager&gt;? {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">if</span> (bksFile == <span class="hljs-literal">null</span> || password == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>

                <span class="hljs-keyword">val</span> clientKeyStore = KeyStore.getInstance(<span class="hljs-string">&quot;PKCS12&quot;</span>)
                clientKeyStore.load(bksFile, password.toCharArray())
                <span class="hljs-keyword">val</span> keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
                keyManagerFactory.<span class="hljs-keyword">init</span>(clientKeyStore, password.toCharArray())
                <span class="hljs-keyword">return</span> keyManagerFactory.keyManagers

            } <span class="hljs-keyword">catch</span> (e: java.lang.Exception) {
                e.printStackTrace()
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">chooseTrustManager</span><span class="hljs-params">(trustManagers: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">TrustManager</span>&gt;)</span></span>: X509TrustManager? {
            <span class="hljs-keyword">for</span> (trustManager <span class="hljs-keyword">in</span> trustManagers) {
                <span class="hljs-keyword">if</span> (trustManager <span class="hljs-keyword">is</span> X509TrustManager) {
                    <span class="hljs-keyword">return</span> trustManager
                }
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }
    }
}
</code></pre>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[Go-快速使用Grpc]]></title>
            <link>https://umb.ink/post/36</link>
            <guid>https://umb.ink/post/36</guid>
            <pubDate>Fri, 24 Jan 2025 10:43:08 GMT</pubDate>
            <description><![CDATA[<p>整理一下之前工作中用到的GRPC相关内容</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>整理一下之前工作中用到的GRPC相关内容</p>
<!-- more -->
<h1>grpc定义</h1>
<p>grpc是一种接口描述协议，我们可以通过定义proto文件来自动实现相关代码逻辑，举个例子：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>syntax = <span class="hljs-string">&quot;proto3&quot;</span>;

// 标记所属包名
package grpc_test;
// 标记生成的go代码目录：即生成代码后会把代码文件丢到当前目录下的grpc_test文件夹中（如果是java-grpc，则是java_package)
option go_package = <span class="hljs-string">&quot;./grpc_test&quot;</span>;

// 可以把这个messsage理解为结构体定义，这个message映射为代码类或实体结构体，这里的=1和=2是在标记这个字段，从1开始，第几个字段标记为几
message GrpcHelloWorldReq {
    string message = 1;
    // 可以定义string list
    repeated string messageList = 2;
}

message GrpcHelloWorldResp {
    string message = 1;
    // 可以定义结构体list
    repeated GrpcHelloWorldReq req = 2;
}

service MeetingDeviceService {
    rpc HelloWorld(GrpcHelloWorldReq) returns (GrpcHelloWorldResp){}
    // 定义一个rpc接口，输入GrpcHelloWorldReq结构体，返回GrpcHelloWorldResp
}

</code></pre>
<h1>代码生成</h1>
<p>在proto文件目录下运行下面的指令</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>protoc --go_out=. *.proto
protoc --go-grpc_out=. *.proto    
</code></pre>
<p>成功运行后你就会在目录/grpc_test/下看到.pb.go的文件，这个里面包含了根据定义的proto文件生成的代码</p>
<h1>接口实现</h1>
<p>服务端：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;context&quot;</span>
	<span class="hljs-string">&quot;fmt&quot;</span>
	<span class="hljs-string">&quot;net&quot;</span>

	<span class="hljs-string">&quot;kafka-test/main/grpc_test&quot;</span>

	<span class="hljs-string">&quot;google.golang.org/grpc&quot;</span>
)

<span class="hljs-keyword">type</span> server <span class="hljs-keyword">struct</span> {
	grpc_test.MeetingDeviceServiceServer
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *server)</span></span> HelloWorld(ctx context.Context, req *grpc_test.GrpcHelloWorldReq) (*grpc_test.GrpcHelloWorldResp, <span class="hljs-type">error</span>) {
	fmt.Println(<span class="hljs-string">&quot;request:&quot;</span>, req.Message)
	<span class="hljs-keyword">return</span> &amp;grpc_test.GrpcHelloWorldResp{Message: <span class="hljs-string">&quot;hello &quot;</span> + req.Message}, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
	listen, err := net.Listen(<span class="hljs-string">&quot;tcp&quot;</span>, <span class="hljs-string">&quot;:8001&quot;</span>)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		fmt.Printf(<span class="hljs-string">&quot;failed to listen: %v&quot;</span>, err)
		<span class="hljs-keyword">return</span>
	}
	s := grpc.NewServer()
	<span class="hljs-comment">//reflection.Register(s)</span>

	<span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		s.Stop()
		listen.Close()
	}()

	fmt.Println(<span class="hljs-string">&quot;Serving 8001...&quot;</span>)
	err = s.Serve(listen)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		fmt.Printf(<span class="hljs-string">&quot;failed to serve: %v&quot;</span>, err)
		<span class="hljs-keyword">return</span>
	}
}
</code></pre>
<p>客户端：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;context&quot;</span>
	<span class="hljs-string">&quot;fmt&quot;</span>
	<span class="hljs-string">&quot;sync&quot;</span>

	<span class="hljs-string">&quot;kafka-test/main/grpc_test&quot;</span>

	<span class="hljs-string">&quot;google.golang.org/grpc&quot;</span>
	<span class="hljs-string">&quot;google.golang.org/grpc/credentials/insecure&quot;</span>
)

<span class="hljs-keyword">var</span> (
	meetingRoomRpcClient grpc_test.MeetingDeviceServiceClient
	once                 = sync.Once{}
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
	<span class="hljs-keyword">var</span> serviceHost = <span class="hljs-string">&quot;127.0.0.1:8001&quot;</span>

	conn, err := grpc.Dial(serviceHost, grpc.WithTransportCredentials(insecure.NewCredentials()))
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		fmt.Println(err)
	}
	<span class="hljs-keyword">defer</span> conn.Close()
	<span class="hljs-comment">// 初始化client（这里client作为调用方，所以只需要初始化一次）</span>
	once.Do(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		meetingRoomRpcClient = grpc_test.NewMeetingDeviceServiceClient(conn)
		<span class="hljs-comment">//meetingRoomRpcClient = grpc_test.NewMeetingDeviceServiceClient(&quot;meeting-device-service&quot;)</span>
	})

}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RpcHellWorld</span><span class="hljs-params">()</span></span> {
	worldResp, err := meetingRoomRpcClient.HelloWorld(context.Background(), &amp;grpc_test.GrpcHelloWorldReq{
		Message:     <span class="hljs-string">&quot;hello world&quot;</span>,
		MessageList: []<span class="hljs-type">string</span>{<span class="hljs-string">&quot;hello world&quot;</span>, <span class="hljs-string">&quot;hello world&quot;</span>},
	})
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		<span class="hljs-keyword">return</span>
	}
	fmt.Println(worldResp.Message)
}
</code></pre>
<p>启动服务后，rpc服务会运行在本地的8001端口，因此作为下游客户端的rpc服务只需要监听这个8001端口即可实现rpc调用。当然，下游的rpc客户端服务也可以定义自己的proto rpc文件，然后被其他服务调用自己的rpc方法。</p>
<p>在rpc服务调用过程中，谁提供方法接口，谁是server，谁调用方法接口，谁是client，只是一个相对概念。</p>
<h1>进阶——服务发现ETCD</h1>
<p>当RPC服务多了以后，我们需要配置每个RPC服务的IP和端口，非常麻烦，因此这就需要用到注册中心来简化RPC服务管理。用注册中心的话，我们只需要记住每个服务的服务名即可，就不用再挨个记IP了。</p>
<p>放一个官方文档：https://etcd.io/docs/v3.5/quickstart/<br>
安装请自行参考官方文档</p>
<p>ETCD注册服务端：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>
<span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;context&quot;</span>
	<span class="hljs-string">&quot;flag&quot;</span>
	<span class="hljs-string">&quot;fmt&quot;</span>
	<span class="hljs-string">&quot;net&quot;</span>
	<span class="hljs-string">&quot;time&quot;</span>

	grpc_test2 <span class="hljs-string">&quot;kafka-test/rpc/grpc_test&quot;</span>

	<span class="hljs-comment">// etcd</span>
	eclient <span class="hljs-string">&quot;go.etcd.io/etcd/client/v3&quot;</span>
	<span class="hljs-string">&quot;go.etcd.io/etcd/client/v3/naming/endpoints&quot;</span>
	<span class="hljs-string">&quot;google.golang.org/grpc&quot;</span>
)

<span class="hljs-keyword">const</span> (
	<span class="hljs-comment">// grpc 服务名</span>
	GrpcTestServiceName = <span class="hljs-string">&quot;umb/grpc-test&quot;</span>
	<span class="hljs-comment">// etcd 端口</span>
	ETCD_ADDR = <span class="hljs-string">&quot;http://localhost:2379&quot;</span>
)

<span class="hljs-keyword">type</span> server <span class="hljs-keyword">struct</span> {
	grpc_test2.UnimplementedGrpcHelloWorldServiceServer
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *server)</span></span> HelloWorld(ctx context.Context, req *grpc_test2.GrpcHelloWorldReq) (*grpc_test2.GrpcHelloWorldResp, <span class="hljs-type">error</span>) {
	fmt.Println(<span class="hljs-string">&quot;request:&quot;</span>, req.Message)
	<span class="hljs-keyword">return</span> &amp;grpc_test2.GrpcHelloWorldResp{Message: <span class="hljs-string">&quot;hello &quot;</span> + req.Message}, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registerEndPointToEtcd</span><span class="hljs-params">(ctx context.Context, addr <span class="hljs-type">string</span>)</span></span> {
	<span class="hljs-comment">// 创建 etcd 客户端</span>
	etcdClient, _ := eclient.NewFromURL(ETCD_ADDR)
	etcdManager, _ := endpoints.NewManager(etcdClient, GrpcTestServiceName)

	<span class="hljs-comment">// 创建一个租约，每隔 10s 需要向 etcd 汇报一次心跳，证明当前节点仍然存活</span>
	<span class="hljs-keyword">var</span> ttl <span class="hljs-type">int64</span> = <span class="hljs-number">10</span>
	lease, _ := etcdClient.Grant(ctx, ttl)

	<span class="hljs-comment">// 添加注册节点到 etcd 中，并且携带上租约 id</span>
	_ = etcdManager.AddEndpoint(ctx, fmt.Sprintf(<span class="hljs-string">&quot;%s/%s&quot;</span>, GrpcTestServiceName, addr), endpoints.Endpoint{Addr: addr}, eclient.WithLease(lease.ID))

	<span class="hljs-comment">// 每隔 5 s进行一次延续租约的动作</span>
	<span class="hljs-keyword">for</span> {
		<span class="hljs-keyword">select</span> {
		<span class="hljs-keyword">case</span> &lt;-time.After(<span class="hljs-number">5</span> * time.Second):
			<span class="hljs-comment">// 续约操作</span>
			resp, _ := etcdClient.KeepAliveOnce(ctx, lease.ID)
			fmt.Printf(<span class="hljs-string">&quot;keep alive resp: %+v&quot;</span>, resp)
		<span class="hljs-keyword">case</span> &lt;-ctx.Done():
			<span class="hljs-keyword">return</span>
		}
	}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InitRpcServer</span><span class="hljs-params">()</span></span> {
	<span class="hljs-keyword">var</span> port <span class="hljs-type">int</span>
	flag.IntVar(&amp;port, <span class="hljs-string">&quot;port&quot;</span>, <span class="hljs-number">8001</span>, <span class="hljs-string">&quot;port&quot;</span>)
	flag.Parse()
	addr := fmt.Sprintf(<span class="hljs-string">&quot;http://localhost:%d&quot;</span>, port)
	listen, err := net.Listen(<span class="hljs-string">&quot;tcp&quot;</span>, addr)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		fmt.Printf(<span class="hljs-string">&quot;failed to listen: %v&quot;</span>, err)
		<span class="hljs-keyword">return</span>
	}
	s := grpc.NewServer()
	<span class="hljs-comment">//reflection.Register(s)</span>
	grpc_test2.RegisterGrpcHelloWorldServiceServer(s, &amp;server{})
	<span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		s.Stop()
		listen.Close()
	}()
	ctx, cancel := context.WithCancel(context.Background())
	<span class="hljs-keyword">defer</span> cancel()
	<span class="hljs-comment">// 注册 grpc 服务节点到 etcd 中</span>
	<span class="hljs-keyword">go</span> registerEndPointToEtcd(ctx, addr)
	fmt.Println(<span class="hljs-string">&quot;Serving 8001...&quot;</span>)
	err = s.Serve(listen)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		fmt.Printf(<span class="hljs-string">&quot;failed to serve: %v&quot;</span>, err)
		<span class="hljs-keyword">return</span>
	}
}
</code></pre>
<p>ETCD注册客户端：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;context&quot;</span>
	<span class="hljs-string">&quot;fmt&quot;</span>
	<span class="hljs-string">&quot;sync&quot;</span>

	grpc_test2 <span class="hljs-string">&quot;kafka-test/rpc/grpc_test&quot;</span>

	<span class="hljs-comment">// etcd</span>
	eclient <span class="hljs-string">&quot;go.etcd.io/etcd/client/v3&quot;</span>
	eresolver <span class="hljs-string">&quot;go.etcd.io/etcd/client/v3/naming/resolver&quot;</span>
	<span class="hljs-string">&quot;google.golang.org/grpc&quot;</span>
	<span class="hljs-string">&quot;google.golang.org/grpc/credentials/insecure&quot;</span>
)

<span class="hljs-keyword">var</span> (
	meetingRoomRpcClient grpc_test2.GrpcHelloWorldServiceClient
	once                 = sync.Once{}
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
	<span class="hljs-comment">// 创建 etcd 客户端</span>
	etcdClient, _ := eclient.NewFromURL(ETCD_ADDR)

	<span class="hljs-comment">// 创建 etcd 实现的 grpc 服务注册发现模块 resolver</span>
	etcdResolverBuilder, _ := eresolver.NewBuilder(etcdClient)

	<span class="hljs-comment">// 拼接服务名称，需要固定义 etcd:/// 作为前缀</span>
	etcdTarget := fmt.Sprintf(<span class="hljs-string">&quot;etcd:///%s&quot;</span>, GrpcTestServiceName)

	<span class="hljs-comment">// 创建 grpc 连接代理</span>
	conn, _ := grpc.Dial(
		<span class="hljs-comment">// 服务名称</span>
		etcdTarget,
		<span class="hljs-comment">// 注入 etcd resolver</span>
		grpc.WithResolvers(etcdResolverBuilder),
		<span class="hljs-comment">// 声明使用的负载均衡策略为 roundrobin     grpc.WithDefaultServiceConfig(fmt.Sprintf(`{&quot;LoadBalancingPolicy&quot;: &quot;%s&quot;}`, roundrobin.Name)),</span>
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	<span class="hljs-keyword">defer</span> conn.Close()
	<span class="hljs-keyword">defer</span> conn.Close()
	<span class="hljs-comment">// 初始化client（这里client作为调用方，所以只需要初始化一次）</span>
	once.Do(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		meetingRoomRpcClient = grpc_test2.NewGrpcHelloWorldServiceClient(conn)
		<span class="hljs-comment">//meetingRoomRpcClient = grpc_test.NewMeetingDeviceServiceClient(&quot;meeting-device-service&quot;)</span>
	})

}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RpcHellWorld</span><span class="hljs-params">()</span></span> {
	worldResp, err := meetingRoomRpcClient.HelloWorld(context.Background(), &amp;grpc_test2.GrpcHelloWorldReq{
		Message:     <span class="hljs-string">&quot;hello world&quot;</span>,
		MessageList: []<span class="hljs-type">string</span>{<span class="hljs-string">&quot;hello world&quot;</span>, <span class="hljs-string">&quot;hello world&quot;</span>},
	})
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		<span class="hljs-keyword">return</span>
	}
	fmt.Println(worldResp.Message)
}
</code></pre>
<p>这样基于ETCD改造之后，server和客户端只需要知道对方的名称就能用了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[Go 接入sarama认证的Kafka]]></title>
            <link>https://umb.ink/post/34</link>
            <guid>https://umb.ink/post/34</guid>
            <pubDate>Fri, 24 Jan 2025 10:38:18 GMT</pubDate>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <!-- more -->
<h1>前置准备</h1>
<p>安装kafka-sarama库</p>
<p>直接附带我的go mod：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>require (
	github.com/IBM/sarama v1<span class="hljs-number">.45</span><span class="hljs-number">.0</span>
	github.com/xdg/scram v1<span class="hljs-number">.0</span><span class="hljs-number">.5</span>
	<span class="hljs-keyword">go</span>.uber.org/zap v1<span class="hljs-number">.27</span><span class="hljs-number">.0</span>
)

require (
	github.com/davecgh/<span class="hljs-keyword">go</span>-spew v1<span class="hljs-number">.1</span><span class="hljs-number">.1</span> <span class="hljs-comment">// indirect</span>
	github.com/eapache/<span class="hljs-keyword">go</span>-resiliency v1<span class="hljs-number">.7</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	github.com/eapache/<span class="hljs-keyword">go</span>-xerial-snappy v0<span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-number">-20230731223053</span>-c322873962e3 <span class="hljs-comment">// indirect</span>
	github.com/eapache/queue v1<span class="hljs-number">.1</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	github.com/golang/snappy v0<span class="hljs-number">.0</span><span class="hljs-number">.4</span> <span class="hljs-comment">// indirect</span>
	github.com/hashicorp/errwrap v1<span class="hljs-number">.1</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	github.com/hashicorp/<span class="hljs-keyword">go</span>-multierror v1<span class="hljs-number">.1</span><span class="hljs-number">.1</span> <span class="hljs-comment">// indirect</span>
	github.com/hashicorp/<span class="hljs-keyword">go</span>-uuid v1<span class="hljs-number">.0</span><span class="hljs-number">.3</span> <span class="hljs-comment">// indirect</span>
	github.com/jcmturner/aescts/v2 v2<span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	github.com/jcmturner/dnsutils/v2 v2<span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	github.com/jcmturner/gofork v1<span class="hljs-number">.7</span><span class="hljs-number">.6</span> <span class="hljs-comment">// indirect</span>
	github.com/jcmturner/gokrb5/v8 v8<span class="hljs-number">.4</span><span class="hljs-number">.4</span> <span class="hljs-comment">// indirect</span>
	github.com/jcmturner/rpc/v2 v2<span class="hljs-number">.0</span><span class="hljs-number">.3</span> <span class="hljs-comment">// indirect</span>
	github.com/klauspost/compress v1<span class="hljs-number">.17</span><span class="hljs-number">.11</span> <span class="hljs-comment">// indirect</span>
	github.com/pierrec/lz4/v4 v4<span class="hljs-number">.1</span><span class="hljs-number">.22</span> <span class="hljs-comment">// indirect</span>
	github.com/rcrowley/<span class="hljs-keyword">go</span>-metrics v0<span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-number">-20201227073835</span>-cf1acfcdf475 <span class="hljs-comment">// indirect</span>
	github.com/xdg/stringprep v1<span class="hljs-number">.0</span><span class="hljs-number">.3</span> <span class="hljs-comment">// indirect</span>
	<span class="hljs-keyword">go</span>.uber.org/multierr v1<span class="hljs-number">.10</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	golang.org/x/crypto v0<span class="hljs-number">.32</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	golang.org/x/net v0<span class="hljs-number">.34</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
	golang.org/x/text v0<span class="hljs-number">.21</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span>
)

</code></pre>
<h1>XDGSCRAM 解密</h1>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>
<span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;crypto/sha256&quot;</span>
	<span class="hljs-string">&quot;crypto/sha512&quot;</span>

	<span class="hljs-string">&quot;github.com/xdg/scram&quot;</span>
)

<span class="hljs-keyword">var</span> (
	<span class="hljs-comment">// SHA256 ...</span>
	SHA256 scram.HashGeneratorFcn = sha256.New
	<span class="hljs-comment">// SHA512 ...</span>
	SHA512 scram.HashGeneratorFcn = sha512.New
)

<span class="hljs-comment">// XDGSCRAMClient ...</span>
<span class="hljs-keyword">type</span> XDGSCRAMClient <span class="hljs-keyword">struct</span> {
	*scram.Client
	*scram.ClientConversation
	scram.HashGeneratorFcn
}

<span class="hljs-comment">// Begin ...</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(x *XDGSCRAMClient)</span></span> Begin(userName, password, authzID <span class="hljs-type">string</span>) (err <span class="hljs-type">error</span>) {
	x.Client, err = x.HashGeneratorFcn.NewClient(userName, password, authzID)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		<span class="hljs-keyword">return</span> err
	}
	x.ClientConversation = x.Client.NewConversation()
	<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// Step ...</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(x *XDGSCRAMClient)</span></span> Step(challenge <span class="hljs-type">string</span>) (response <span class="hljs-type">string</span>, err <span class="hljs-type">error</span>) {
	response, err = x.ClientConversation.Step(challenge)
	<span class="hljs-keyword">return</span>
}

<span class="hljs-comment">// Done ...</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(x *XDGSCRAMClient)</span></span> Done() <span class="hljs-type">bool</span> {
	<span class="hljs-keyword">return</span> x.ClientConversation.Done()
}

</code></pre>
<h1>消费者接入：</h1>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
	<span class="hljs-string">&quot;context&quot;</span>
	<span class="hljs-string">&quot;log&quot;</span>
	<span class="hljs-string">&quot;os&quot;</span>
	<span class="hljs-string">&quot;os/signal&quot;</span>
	<span class="hljs-string">&quot;sync&quot;</span>
	<span class="hljs-string">&quot;syscall&quot;</span>

	<span class="hljs-string">&quot;github.com/IBM/sarama&quot;</span>
)

<span class="hljs-keyword">type</span> Kafka <span class="hljs-keyword">struct</span> {
	brokers           []<span class="hljs-type">string</span>
	topics            []<span class="hljs-type">string</span>
	startOffset       <span class="hljs-type">int64</span>
	version           <span class="hljs-type">string</span>
	ready             <span class="hljs-keyword">chan</span> <span class="hljs-type">bool</span>
	group             <span class="hljs-type">string</span>
	channelBufferSize <span class="hljs-type">int</span>
	assignor          <span class="hljs-type">string</span>
}

<span class="hljs-keyword">var</span> assignor = <span class="hljs-string">&quot;range&quot;</span>
<span class="hljs-keyword">var</span> topics = []<span class="hljs-type">string</span>{<span class="hljs-string">&quot;your_topic&quot;</span>} <span class="hljs-comment">// topic</span>
<span class="hljs-keyword">var</span> group = <span class="hljs-string">&quot;consumer_group&quot;</span> <span class="hljs-comment">// 订阅消费组</span>
<span class="hljs-keyword">var</span> user = <span class="hljs-string">&quot;user&quot;</span> <span class="hljs-comment">// sarma用户</span>
<span class="hljs-keyword">var</span> password = <span class="hljs-string">&quot;pwd&quot;</span>  <span class="hljs-comment">// sarama 密码</span>
<span class="hljs-keyword">var</span> brokers = []<span class="hljs-type">string</span>{<span class="hljs-string">&quot;127.0.0.1:6279&quot;</span>} <span class="hljs-comment">// 你的kafka host</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewKafka</span><span class="hljs-params">()</span></span> *Kafka {
	<span class="hljs-keyword">return</span> &amp;Kafka{
		brokers:           brokers,
		topics:            topics,
		group:             group,
		channelBufferSize: <span class="hljs-number">1000</span>,
		ready:             <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-type">bool</span>),
		assignor:          assignor,
	}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(k *Kafka)</span></span> Connect() <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
	log.Println(<span class="hljs-string">&quot;kafka init...&quot;</span>)
	config := sarama.NewConfig()
	config.Version = sarama.V2_4_1_0 <span class="hljs-comment">// 版本号必须注意要同步，否则会报错</span>
	<span class="hljs-comment">// 分区分配策略</span>
	<span class="hljs-keyword">switch</span> assignor {
	<span class="hljs-keyword">case</span> <span class="hljs-string">&quot;sticky&quot;</span>:
		config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategySticky
	<span class="hljs-keyword">case</span> <span class="hljs-string">&quot;roundrobin&quot;</span>:
		config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin
	<span class="hljs-keyword">case</span> <span class="hljs-string">&quot;range&quot;</span>:
		config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange
	<span class="hljs-keyword">default</span>:
		log.Panicf(<span class="hljs-string">&quot;Unrecognized consumer group partition assignor: %s&quot;</span>, assignor)
	}
	config.Consumer.Offsets.Initial = sarama.OffsetNewest
	config.ChannelBufferSize = k.channelBufferSize <span class="hljs-comment">// channel长度</span>
	config.Consumer.Offsets.Initial = sarama.OffsetOldest
	config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin
	config.Net.SASL.Enable = <span class="hljs-literal">true</span>
	config.Net.SASL.User = user
	config.Net.SASL.Password = password
	config.Net.SASL.Handshake = <span class="hljs-literal">true</span>
	config.Net.SASL.SCRAMClientGeneratorFunc = <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> sarama.SCRAMClient {
		<span class="hljs-keyword">return</span> &amp;XDGSCRAMClient{HashGeneratorFcn: SHA256}
	}
	config.Net.SASL.Mechanism = sarama.SASLTypeSCRAMSHA256
	<span class="hljs-comment">// 创建client</span>
	log.Println(<span class="hljs-string">&quot;brokers: &quot;</span>, brokers)
	newClient, err := sarama.NewClient(brokers, config)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		log.Fatalf(<span class="hljs-string">&quot;Error creating client: %v&quot;</span>, err)
	}
	<span class="hljs-comment">// 获取所有的topic</span>
	topics, err := newClient.Topics()
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		log.Fatal(err)
	}
	log.Println(<span class="hljs-string">&quot;topics: &quot;</span>, topics)

	<span class="hljs-comment">// 根据client创建consumerGroup</span>
	client, err := sarama.NewConsumerGroupFromClient(k.group, newClient)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		log.Fatalf(<span class="hljs-string">&quot;Error creating consumer group client: %v&quot;</span>, err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	wg := &amp;sync.WaitGroup{}
	wg.Add(<span class="hljs-number">1</span>)
	<span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		<span class="hljs-keyword">defer</span> wg.Done()
		<span class="hljs-keyword">for</span> {
			<span class="hljs-keyword">if</span> err := client.Consume(ctx, k.topics, k); err != <span class="hljs-literal">nil</span> {
				<span class="hljs-comment">// 当setup失败的时候，error会返回到这里</span>
				log.Println(<span class="hljs-string">&quot;Error from consumer: %v&quot;</span>, err)
				<span class="hljs-keyword">return</span>
			}
			<span class="hljs-comment">// check if context was cancelled, signaling that the consumer should stop</span>
			<span class="hljs-keyword">if</span> ctx.Err() != <span class="hljs-literal">nil</span> {
				log.Println(ctx.Err())
				<span class="hljs-keyword">return</span>
			}
			k.ready = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-type">bool</span>)
		}
	}()
	&lt;-k.ready
	log.Println(<span class="hljs-string">&quot;Sarama consumer up and running!...&quot;</span>)
	<span class="hljs-comment">// 保证在系统退出时，通道里面的消息被消费</span>
	<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
		log.Println(<span class="hljs-string">&quot;kafka close&quot;</span>)
		cancel()
		wg.Wait()
		<span class="hljs-keyword">if</span> err = client.Close(); err != <span class="hljs-literal">nil</span> {
			log.Println(<span class="hljs-string">&quot;Error closing client: %v&quot;</span>, err)
		}
	}
}

<span class="hljs-comment">// Setup is run at the beginning of a new session, before ConsumeClaim</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(k *Kafka)</span></span> Setup(session sarama.ConsumerGroupSession) <span class="hljs-type">error</span> {
	log.Println(<span class="hljs-string">&quot;setup&quot;</span>)
	session.ResetOffset(<span class="hljs-string">&quot;lvmi&quot;</span>, <span class="hljs-number">0</span>, <span class="hljs-number">13</span>, <span class="hljs-string">&quot;&quot;</span>)
	log.Println(session.Claims())
	<span class="hljs-comment">// Mark the consumer as ready</span>
	<span class="hljs-built_in">close</span>(k.ready)
	<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(k *Kafka)</span></span> Cleanup(sarama.ConsumerGroupSession) <span class="hljs-type">error</span> {
	log.Println(<span class="hljs-string">&quot;cleanup&quot;</span>)
	<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// ConsumeClaim must start a consumer loop of ConsumerGroupClaim&#x27;s Messages().</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(k *Kafka)</span></span> ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) <span class="hljs-type">error</span> {

	<span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span></span>
	<span class="hljs-comment">// Do not move the code below to a goroutine.</span>
	<span class="hljs-comment">// The `ConsumeClaim` itself is called within a goroutine, see:</span>
	<span class="hljs-comment">// https://github.com/Shopify/sarama/blob/master/consumer_group.go#L27-L29</span>
	<span class="hljs-comment">// 具体消费消息</span>
	<span class="hljs-keyword">for</span> message := <span class="hljs-keyword">range</span> claim.Messages() {
		log.Printf(<span class="hljs-string">&quot;[topic:%s] [partiton:%d] [offset:%d] [value:%s] [time:%v]&quot;</span>,
			message.Topic, message.Partition, message.Offset, <span class="hljs-type">string</span>(message.Value), message.Timestamp)
		<span class="hljs-comment">// 更新位移</span>
		session.MarkMessage(message, <span class="hljs-string">&quot;&quot;</span>)
	}
	<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
	k := NewKafka()
	c := k.Connect()

	sigterm := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> os.Signal, <span class="hljs-number">1</span>)
	signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
	<span class="hljs-keyword">select</span> {
	<span class="hljs-keyword">case</span> &lt;-sigterm:
		log.Println(<span class="hljs-string">&quot;terminating: via signal&quot;</span>)
	}
	c()
}
</code></pre>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[2024-时间线]]></title>
            <link>https://umb.ink/post/33</link>
            <guid>https://umb.ink/post/33</guid>
            <pubDate>Sun, 22 Dec 2024 15:40:19 GMT</pubDate>
            <description><![CDATA[<h1>2024-时间线</h1>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>2024-时间线</h1>
<!-- more -->
<ul>
<li>
<h3>1月：新年</h3>
</li>
</ul>
<p>一月初人在上海，恰好赶上跨年，比较幸运在八角场线下参加了B站的跨年夜活动</p>
<p>当时拍的照片：</p>
<p><img src="/static/img/127f8276a6418f13d004c41374f7b6aa.image.webp" alt="image.png"></p>
<p>在上海和朋友夹了娃娃，非常幸运，夹了一大坨</p>
<p><img src="/static/img/4d2a9d6d810f6fa224c09a243c1d0e85.image.webp" alt="image.png"></p>
<p>月末高中的兄弟来北京玩，一起去了颐和园（高糊画质）</p>
<p><img src="/static/img/54f66d03995012893d1b2637054b5d41.5853d864655344cefb37386245b2e528.webp" alt="5853d864655344cefb37386245b2e528.png"></p>
<ul>
<li>
<h3>2月：过年</h3>
</li>
</ul>
<p>难得回家休息，拼了从上海带回来的自由高达：</p>
<p><img src="/static/img/b75c3f7740aa6f0fc7b93e7ba049cda4.image.webp" alt="image.png"></p>
<p><img src="/static/img/613c79ec8f7e1112a75e69b818e2828a.4ABC18269B32CB206D5CB3B5F60679D5.webp" alt="4ABC18269B32CB206D5CB3B5F60679D5.png"></p>
<p>过年最重要的事情当然是几个街溜子在午夜放烟花</p>
<p><img src="/static/img/1165169aa51c1978517bd1b7905c46aa.image.webp" alt="image.png"></p>
<p>年后陪家人去了古县城（这里真的安利太原古县城）</p>
<p><img src="/static/img/874a59f71bbd1cd53c106ae978124b08.image.webp" alt="image.png"></p>
<p><img src="/static/img/4709fe8b23376ec9c9d2c98fa5fd2262.image.webp" alt="image.png"></p>
<p>年后临近返工，获知了一位朋友将要订婚的消息，都非常开心，也是突然发现，原来大家要到谈婚论嫁的年纪了，原来大家已经不再是小孩了。</p>
<p>年后返工，遇上倒春寒，下雪的西二旗</p>
<p><img src="/static/img/e2400c687ea14ed11f3547d17f1f6100.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>3月：家庭新成员</h3>
</li>
</ul>
<p>3月，家庭新加入了新成员——GC，一直两周大的银渐层小猫（非常可爱！！！）</p>
<p>刚到家就往马桶钻的GC：那个时候还很瘦</p>
<p><img src="/static/img/b7251992944fd1bd4003528bdc8ac7f7.image.webp" alt="image.png"></p>
<p>现在的GC：</p>
<p><img src="/static/img/f168af465a459f31aaf08f11eb7502b7.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>4月：樱花</h3>
</li>
</ul>
<p>4月小区里的樱花宛如动漫场景，美不胜收，每天出门就是一个享受</p>
<p><img src="/static/img/73adce70a46186a17c432ca766a31758.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>5月 晚霞</h3>
</li>
</ul>
<p>平平无奇的五月，被某天的晚霞打破了原本的乏味</p>
<p><img src="/static/img/bba36a6f4ce5177b706603d0270b8c03.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>6月：搬家</h3>
</li>
</ul>
<p>被赶出家门，被迫搬家。因为上个房东不愿意出租了，被迫搬离了住了大半年的家，自如赔了点钱，但是还不够补贴差价的=.=。<br>
附一张六月的GC<br>
<img src="/static/img/564ed7e350526e5132a226378c417c15.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>7月：演唱会！</h3>
</li>
</ul>
<p>7月初，临近入职一周年，获得了公司发的奖章！好诶！</p>
<p><img src="/static/img/1a3e7a13a2d95c869fa65e0c759df181.image.webp" alt="image.png"></p>
<p>7月中，回家呆了几天。突然感觉人都是很脆弱的，可能不觉中某个人就不在了，顿感生命脆弱，也是在这个时候突然发现，我到了要承担家庭重大事项的年纪。</p>
<p>七月末，未来有你——初音未来演唱会！！！</p>
<p>带着曾经实验室的棒子——来自某位热情学弟赞助</p>
<p><img src="/static/img/65e8ed03fbc22e74fd0a5ff8bc6c0175.image.webp" alt="image.png"></p>
<p><img src="/static/img/355551f32826bf5ded74c64478cef3d9.image.webp" alt="image.png"></p>
<p>非常棒的演唱会企划展：</p>
<p><img src="/static/img/8a6648b515089e485c30d35151eb6a53.image.webp" alt="image.png"></p>
<p><img src="/static/img/7b3bf425216af2379dd0ec35b947c003.image.webp" alt="image.png"><br>
非常好的法披（狠狠爆了金币）</p>
<p><img src="/static/img/f9aa2d72b89dd0f239f85bd9224805c3.image.webp" alt="image.png"></p>
<p>因为快手买了冠名，所以这次请了蔡萝莉在现场站台cos：</p>
<p><img src="/static/img/dc96552cba8e6569324f035f88d767ab.image.webp" alt="image.png"></p>
<p>小插曲：</p>
<p>我完成了我的桌搭升级！</p>
<p><img src="/static/img/2bfba4f1a8c7bf413ea5269c227a43bb.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>8月： 旅游！开发者大会</h3>
</li>
</ul>
<ul>
<li>8月初，抓着月初的簪头，和好兄弟说走就走的周末极限苏州行</li>
</ul>
<p>拙政园的街道：</p>
<p><img src="/static/img/912e520d4152bfc1d88208ea148a02d0.image.webp" alt="image.png"></p>
<p>七里山塘：</p>
<p><img src="/static/img/08eb0002297dd7ae15f070eedbf7dbcb.image.webp" alt="image.png"></p>
<p>西园寺：</p>
<p><img src="/static/img/0c10261dd19e733de7c8af2a5a4fc7e6.image.webp" alt="image.png"><br>
附一张帅气的我好哥们（单身，在线招亲）博主本人长相很一般，且严重社恐，就不献丑了。</p>
<p><img src="/static/img/bc4bbd08ae710901b0221c698424696b.image.webp" alt="image.png"></p>
<p><img src="/static/img/ec905cc98bedd621b2527834b6aa957d.image.webp" alt="image.png"></p>
<p>还有苏州大裤衩</p>
<p><img src="/static/img/1529cc62f4a1822e8f355dcd23452c03.image.webp" alt="image.png"></p>
<p>金鸡湖偶遇的小情侣，这张是我抓拍的，非常有氛围感</p>
<p><img src="/static/img/e91e51462b38efcc287c4c0c63dbde8d.066DC899A1387E285C185D1E07D8C463.webp" alt="066DC899A1387E285C185D1E07D8C463.png"></p>
<p>凌晨极限跑到了杭州，进行杭州半日游：<br>
西湖旁边的凌晨客栈一角：<br>
<img src="https://umb.ink/static/img/f218e3688d890b0091c4e3861fed1ceb.image.webp" alt=""></p>
<p>看到了雷锋塔<br>
<img src="/static/img/5dc565c021abdc2531c0a89a918d0871.image.webp" alt="image.png"></p>
<p>某个周末，在北京王府井看到了高达</p>
<p><img src="/static/img/62871527361cca33fd85b8b77a891527.image.webp" alt="image.png"></p>
<ul>
<li>8月中：谷歌开发者大会！</li>
</ul>
<p><img src="/static/img/a29689fc7f9942654188f049194210a6.image.webp" alt="image.png"></p>
<p>世界各地有趣的开发团队：</p>
<p><img src="/static/img/a7c8e3552c01a139a9014d83cac79041.image.webp" alt="image.png"></p>
<ul>
<li>出发！四川黄龙与九寨沟</li>
</ul>
<p>公司团队团建，我们来到了黄龙与九寨景区<br>
黄龙：<br>
<img src="/static/img/f1ff32d9c3083eaae4bbb23aa4c8a1ea.image.webp" alt="image.png"></p>
<p>九寨沟：九寨归来不看水，是真的美</p>
<p><img src="/static/img/1869f4c5c4f2a159489eac7d80e202f2.image.webp" alt="image.png"></p>
<p><img src="/static/img/39842b46b376c764addad683eb58420b.image.webp" alt="image.png"></p>
<p><img src="/static/img/09cf3faca627286662e5755c2efd9dbd.image.webp" alt="image.png"></p>
<p>山也很美：</p>
<p><img src="/static/img/e303d85341bbbeb0b667819d8978b508.image.webp" alt="image.png"></p>
<p>藏民的白塔印象也很深</p>
<p><img src="/static/img/b94614f5ad602587a67adefe995ef566.image.webp" alt="image.png"></p>
<p>最后一天去了成都天府红，死宅天堂：</p>
<p><img src="/static/img/ad529679bf37ac2e9dbeb0b54dbe8474.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>9月：游戏</h3>
</li>
</ul>
<p>黑神话8.20上线便引爆了国内游戏圈，趁热九月我也进行了高强度开干。不得不说黑神话悟空真是一件值得被称为艺术品的游戏。<br>
可惜没有那么强的肝，最后也只是通关了三周目，没有做到全成就。<br>
<img src="/static/img/8284bf527f2aac7cdf7c119f50a19fb3.image.webp" alt="image.png"></p>
<ul>
<li>
<h3>10月：国庆，故地重游，演唱会！！！！</h3>
</li>
</ul>
<p>趁着放假，连夜回到了太原，非常好看的太原迎泽大桥，满满的红色浪漫</p>
<p><img src="/static/img/05efa890ebc46a8feae25e16e7ab0ee3.image.webp" alt="image.png"></p>
<p>回到了学校，仅仅一年变得如此气派，震惊了我，差点以为走错了学校。果然是人类定则：毕业后母校马上变样</p>
<p><img src="/static/img/59c211726a903245b3e8e133e1ec898f.image.webp" alt="image.png"></p>
<ul>
<li>10月4日，期盼了10年的洛天依演唱会！！！</li>
</ul>
<p>我从刚上初中那会就喜欢听洛天依的歌，从前尘入梦入坑，被这个纯虚拟的少女歌声吸引，逐渐了解了V家、调教、编程、建模等很多东西，洛天依出现在我的生活中，我也多少受影响才走上了计算机的道路。</p>
<p>第一次在现场，听到上山岗的时候，听哭了。<br>
<img src="/static/img/458a60d7b1c0fc6a9836d47a576d538d.image.webp" alt="image.png"><br>
<img src="/static/img/6adba0ca9cc1152b220e22fc58768c45.image.webp" alt="image.png"><br>
附近的企划展，也特别好逛（狠狠给禾念爆金币）：<br>
<img src="/static/img/b8e8da98a59da5acdc657b0298502e56.image.webp" alt="image.png"><br>
<img src="/static/img/7c7c46f5a6f620adad626624598fe6ad.image.webp" alt="image.png"></p>
<p>到家后的战利品：</p>
<p><img src="/static/img/884828151d1feca0ae0d074a70ac08ac.image.webp" alt="image.png"></p>
<ul>
<li>10月20 —— 给学弟学妹分享</li>
</ul>
<p>实验室之前的学弟学妹在10月中突然邀请我回去给大家分享一些关于大学和就业规划的事情，借着实验室新一批纳新的机会，也比较有幸给大家分享了一些我自己的经历。</p>
<p><img src="/static/img/fb139466360b086b024813b815f9f25e.39b93f4b881a0095ca93818d0046e508.webp" alt="39b93f4b881a0095ca93818d0046e508.jpeg"></p>
<ul>
<li>
<h3>11月 总决赛以及加班加班还是加班</h3>
</li>
</ul>
<p>11.2是LOL 全球总决赛的日子，公司社团组织了线下观影活动，去现场看了比赛，虽然结果BLG惜败，但依然值得铭记。</p>
<p>依然记得穿着RNG队服去伦敦现场看BLG的哥们<br>
<img src="/static/img/63c0d1b91073f913497245e49b94a27d.image.webp" alt="image.png"></p>
<ul>
<li>临近年底，团队内追赶项目交付，开始了封闭开发的生活，11月经常连明昼夜地干活，不觉一月就过去了。</li>
<li>
<h3>12月 加班加班</h3>
</li>
</ul>
<p>更临近交付日期了，还是加班。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/日常">日常</category>
        </item>
        <item>
            <title><![CDATA[2024年度总结]]></title>
            <link>https://umb.ink/post/umb</link>
            <guid>https://umb.ink/post/umb</guid>
            <pubDate>Fri, 20 Feb 2026 05:55:45 GMT</pubDate>
            <description><![CDATA[<h1>我的2024年度总结</h1>
<p>    2024好像过的很快，却又好像过的很慢，懵懵懂懂间，一年又结束了。</p>
<p>    回顾2024年，我的全年关键词是：祛魅。若相比2023年的“变化”，2024年更多的，我见识了形形色色的人与事，感悟到很多东西非我所想，也非我所能，坦然接受，踏步向前，才是人生的基调。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>我的2024年度总结</h1>
<p>    2024好像过的很快，却又好像过的很慢，懵懵懂懂间，一年又结束了。</p>
<p>    回顾2024年，我的全年关键词是：祛魅。若相比2023年的“变化”，2024年更多的，我见识了形形色色的人与事，感悟到很多东西非我所想，也非我所能，坦然接受，踏步向前，才是人生的基调。</p>
<!-- more -->
<h2>为什么是祛魅？</h2>
<p>     祛魅，应该算是今年来网络上比较火热的词，对世界祛魅，对职场祛魅。对人生祛魅。意为慢慢消除对一切事物的滤镜，消解美好期待和幻想光环，不去神化、美化一些人或事物，将关注点回到自身。</p>
<p>     回顾我的这一年，经历了很多事情，也见到了很多形形色色的人，发现很多人或事，并没有我想象的的那么美好，也并没有我想象的那么糟糕。</p>
<p>     工作上祛魅，曾经我觉得大厂工作是一件非常自豪的事情，向往大厂的技术氛围，向往大厂的顶级工作环境与先进业务，幻想大厂唯业务技术说话的工作环境。但经历了一年的大厂工作，发现其实大家也都是草台班子，同样会出现的边缘业务，同样会出现的低级故障，同样会出现的人情世故，同样会出现的工作失误，同样会出现的形势误判，同样会出现的吹牛浮夸，浑水摸鱼，终归大家都没有什么不同罢了，终是<strong>优秀的人在大厂施展拳脚，而不是大厂促成了优秀的人</strong>。</p>
<blockquote>
<p>以自我为中心不断进步的人，才是真正的优秀。</p>
</blockquote>
<p>     生活上祛魅，过去一年，曾经那个跟着大人上宴席蹭吃蹭喝的我，也到了要作为一个独立的人，出席这些事情的年纪。身边的老人逐渐离去，新人逐渐成婚，逐渐像当年的父母那样，帮衬朋友、上礼的人。小时候跟着大人，只盼着宴席快快上菜，但当轮到自己出席的时候，才深感面对逝者的悲伤，面对新人婚姻的庆贺，面对人生进程加速的无奈。宴席这个在曾经稚嫩的我看来一个可以跟着大人大吃大喝的好事，却变得令我感情复杂。时常会想，如果当轮到我作为宴席的主角，我又能否克制感情，主持好大局？</p>
<p>     感情上祛魅，从小受父母影响，对一份感情，特别是恋爱的感情，非常看重，幻想着，只得一人心，白首不相离，希望有像父母那样纯粹、一眼便是终生的浪漫感情，对爱情这种事情无限幻想着美好。但是经历了身边其他朋友分分合合，见到了各种各样分崩离析的感情，我顿感无力。直到有天无意间看到了付航的脱口秀，“<strong>你是你自己剧本的主角</strong>”，豁然开朗，再美好的感情，再美好的人，也不能成为某个人生命的主角，我永远作为一个自由的个体，作为自己的主角，任何人的离开，只是结束了这一段剧本，还会有下一段新的剧本等待我的演绎。人生就是一趟永无尽头的旅行，我的列车上会搭载形形色色的人，并与我有一段或深或浅的故事，但终归他们都有到站下车的一天，而我的列车永无止境。所以无论是亲人的离去、挚爱的离去、朋友的离去，无需怀疑自己，也无需遗憾，行好事，问前程，有更加精彩的下一段旅程在等着自己。</p>
<blockquote>
<p>“离开的人不要挽留，到来的人不要轻薄”。</p>
</blockquote>
<p>     人际交往上祛魅。以前认为高学历、高地位的人总是各方面都远优秀于自己，而没读过什么书，也没有家庭背景的人好像样样不如自己。经常在和高学历背景的朋友交往中有些自卑，但和老家提早进入社会的朋友相比又觉得自己混的还行，有些自负。但随着愈发了解到真实的他们，发现，大家也就都那样。高学历高背景的同样会有抽象的人，离谱的性格，摆烂挂科的废人；提早步入社会，地位较低的劳苦大众也有工作出色，年纪轻轻便在自己的行业发光发热，月入过万，小有所成，甚至有些朋友的现状超过了绝大多数同龄人。终归大家也只是芸芸众生的普通人，只是学历不同、家庭背景不同罢了。在某些特定方面，我自己做的远比高学历高地位的优秀，又在某些地方远不及没读过几年书的人看的透彻，做的更好。人皆平等，没有人是神话，也没有人是完全烂透的人渣。</p>
<blockquote>
<p>“大家也就都那样”</p>
</blockquote>
<p>     自我祛魅，作为标准的小镇做题家出身，不给别人添麻烦，要多考虑别人，多吃苦才能有出息。类似的语言总在耳边提起，仿佛生来是为了这个世界、为了社会、为了国家、为了家庭、为了他人而活。好像所有的老中普通人一辈子都这样，为了父母而学习，为了公司而工作，为了孩子割舍自己所爱。这样的自我我并不认可，所以今年我随着自己任性了，去了沪苏杭、去了很多次漫展、去了洛天依的演唱会、去了初音未来的演唱会、在有限的时间里做了我想做的事情，我认为这些是真正有意义的事情。</p>
<blockquote>
<p>“想做就做，死了就做不成了”</p>
</blockquote>
<p>     也在这些事情之后，对很多事情没有了太多的执着，对很多人或事没有了那么深的滤镜，随心而为，很多时候只有自己才是自己的主角，其他人或事，只是自己人生剧本中的一小段插曲。</p>
<p>     因此，我选择了祛魅，作为我这一年的总结。</p>
<h2>生活的总结</h2>
<p><a href="https://umb.ink/post/33">2024-时间线</a></p>
<h2>工作的总结</h2>
<p>今年工作是比较繁杂的，整体压力也相对较大，但总归看着工作上线，也是有所成就感。</p>
<p>因为事情太杂太琐碎，索性直接总结今年在工作中学到的东西与成果好了。</p>
<h4>结果</h4>
<ol>
<li>去年做的班车项目，今年加入了基于GPS的自动切换路线迭代了一版本，并且！总乘车人次破一百万啦，看数据的时候感觉非常满足。</li>
<li>今年新做了一个网络可视化的项目，第一次接触到了海量的数据计算，首次学习并使用了Clickhouse进行数据聚合和处理，第一次写程序处理每天千万的数据量，感觉很棒。</li>
<li>相比去年，感觉今年的工作主导性更少，支持性更多，大部分时候都是配合大项目推进，负责其中的一些模块开发，但看着项目如期能够上线，依然有充足感。</li>
</ol>
<h4>感受</h4>
<p>累，今年真实感受到了大厂对牛马的压榨与内卷的环境，工作中要处处留心，随时留痕，精力一半在工作上另一半在操心和各种任何事情的处理上，身心俱疲。</p>
<h2>来年的愿望</h2>
<ol>
<li>来年希望能去一次日本，去一次洱海。</li>
<li>希望能对工作更得心应手。</li>
<li>希望生活更规律一些，能有精力去运动。</li>
<li>遇到一些更美好的人与事情。</li>
<li>健康的活着。</li>
</ol>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[使用Let's Encrypt部署免费证书！远离国内云服务提供商]]></title>
            <link>https://umb.ink/post/31</link>
            <guid>https://umb.ink/post/31</guid>
            <pubDate>Fri, 22 Nov 2024 19:13:16 GMT</pubDate>
            <description><![CDATA[<h1>起因</h1>
<p>之前一直在用腾讯云提供的免费SSL证书，但是从今年年中以来，腾讯云停止了免费签发一年期的SSL证书，而是必须用它给的三个月有效期的证书，非常的恶心人。</p>
<p>因此，转而考虑使用Let's Encrypt签发的免费SSL证书，彻底干掉愚蠢的国内供应商提供的证书。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>起因</h1>
<p>之前一直在用腾讯云提供的免费SSL证书，但是从今年年中以来，腾讯云停止了免费签发一年期的SSL证书，而是必须用它给的三个月有效期的证书，非常的恶心人。</p>
<p>因此，转而考虑使用Let's Encrypt签发的免费SSL证书，彻底干掉愚蠢的国内供应商提供的证书。</p>
<!-- more -->
<h1>配置</h1>
<p>在开始之前，请确保你服务器的80端口可以正常访问，并安装了nginx（当然也可以不用nginx，用caddy或者其他的请另寻教程）</p>
<p>另外在你的域名服务提供商那里配置DNS A记录解析到要配置证书的服务器上面</p>
<p>在配置时关闭CDN访问！否则验证会失败！</p>
<h1>使用certbot</h1>
<h2>在你的linux服务器上安装certbot</h2>
<pre><code>apt install certbot
</code></pre>
<h2>配置证书</h2>
<p>-d后面跟你的域名即可</p>
<pre><code> certbot --nginx -d umb.ink
</code></pre>
<p>如果遇到Saving debug log to /var/log/letsencrypt/letsencrypt.logThe requested nginx plugin does not appear to be installed的报错，如下图：</p>
<p><img src="/static/img/53f311c68da7b735fce189b511ad31ec.image.webp" alt="image.png"></p>
<p>则安装python3-certbot-nginx即可，<code>sudo apt install python3-certbot-nginx</code></p>
<p>完成后继续运行最上面的配置证书指令，按照提示配置你的邮箱、并选择同意</p>
<p><img src="/static/img/868dca6da88fb1c97f9fc02fef91aed9.image.webp" alt="image.png"></p>
<p>如果遇到Challenge failed for domain 的报错， 检查你的服务器80端口是否可以正常访问！包括但不限于防火墙、端口占用等。<br>
<img src="/static/img/25b5e8ca2dcd60d729c43fcd5ae11706.image.webp" alt="image.png"></p>
<p>成功配置后：</p>
<p><img src="/static/img/a8862d242beaf74f78e2425ea11f9648.image.webp" alt="image.png"></p>
<p>此时从浏览器外网访问你的域名，就可以看到证书了</p>
<p>配置很简单，并且完全免费，配置只需要几行指令，很省事，是非常好的文明</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[如何做好自己的大学规划乃至人生规划？（含我的经历）]]></title>
            <link>https://umb.ink/post/30</link>
            <guid>https://umb.ink/post/30</guid>
            <pubDate>Mon, 21 Oct 2024 14:30:03 GMT</pubDate>
            <description><![CDATA[<h1>前言</h1>
<p>这篇文章主要面向初入大学或是正处在人生分界线不知去往何处的同学们，在这里结合了我自己的过去几年的大学经历和工作两年来的一些所见所识，希望用我浅薄的一些感悟，能够给你们一些启发或帮助。</p>
<blockquote>
<p>读前说明：这篇文章大部分为我自己的主观感悟，我的经验、经历并不适合所有人，仅在此为读者提供一些思考方向。</p>
</blockquote>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>前言</h1>
<p>这篇文章主要面向初入大学或是正处在人生分界线不知去往何处的同学们，在这里结合了我自己的过去几年的大学经历和工作两年来的一些所见所识，希望用我浅薄的一些感悟，能够给你们一些启发或帮助。</p>
<blockquote>
<p>读前说明：这篇文章大部分为我自己的主观感悟，我的经验、经历并不适合所有人，仅在此为读者提供一些思考方向。</p>
</blockquote>
<!-- more -->
<h1>先说我自己——普通</h1>
<p>  写这个篇章主要是想打消部分同学的自卑情绪，希望我的经历能够引起部分人的共鸣，能够让大家带着和我聊天的心情来读这篇文章。如果对我经历没什么兴趣，可以跳过，这里和后面的内容其实没什么关系。</p>
<h2>我的经历</h2>
<h3>大学前</h3>
<p>   我不论中考还是高考都只是比较普通的发挥，没有优秀到像火箭班的精英那样，也没有差到去职业中学，在初高中算得上是班里那种不是特别出彩但也不令老师头疼的“普通学生”。家庭也是普通农村移居县城的普通家庭，饿不死但也不算得富裕的典型中国普通农村职工家庭。</p>
<h3>大学</h3>
<ul>
<li>
<p><strong>高考后</strong>，我迎来了自己的第一次抉择，我当时认为，如果此生不能接触我所感兴趣的东西，那我这辈子都将毫无意义。我后来填报志愿，几乎都选了计算机作为第一志愿，也就这样入了我从小心念的计算机门道。</p>
</li>
<li>
<p><strong>大一来学校前</strong>，我偶然了解到我们院系有一个组织叫做“学生实验室”，这个组织主要凝聚了所有对各种计算机技术感兴趣，并且希望在计算机技术上深造的一批同学，类似于社团，但是相比于社团它会有更大的平台资源和学习机会，也会有考核制度。对于在此前从未接触过电脑编程的我，认为实验室都是大佬，我努力加入未来也可以成为像他们一样改变世界的程序员（盲目崇拜感）。</p>
</li>
<li>
<p><strong>在进入学校前</strong>，当时在迎新群里天天水群，听了学长的建议，在入学前找网课自学c语言，并且为加入实验室做准备，在这个期间，还结识了一圈小伙伴一起学习，而这帮小伙伴也成了后来我在实验室的死党们。</p>
</li>
<li>
<p><strong>进入学校后</strong>，我参加了实验室的招新面试，我当时意向动漫创新实验室和安卓实验室（现在我们叫AI+移动互联实验室），这俩应该算得上是当时我们院系综合实力最好的两个实验室了，一个方向是ACG游戏领域，另一个是移动互联开发，最后抉择的时候还是选择加入了安卓，主要因为我和这个实验室学长们接触相对多一些，觉得这个实验室里学长们都好牛逼。</p>
</li>
</ul>
<blockquote>
<p>入学不久我就第一次进了医院，因为吃了兰村刁民有问题的麻辣拌，搞得上吐下泻了好久，直接医院，而因为刚入学还没有医保，我硬怼了200的“天价“医药费，上兰帝国理工208块钱一碗的麻辣拌，就此诞生，这个梗在我身边一直传到了我大学毕业。</p>
</blockquote>
<ul>
<li><strong>在加入实验室后</strong>，我对自己的大学生活其实没有什么规划，只知道我要先学习技术，至于是考研还是就业，当时我也不知道自己要干啥，对大学生活充满新奇和迷茫，什么都想试试，也想着走一步看一步。</li>
</ul>
<p>实验室一隅：</p>
<p><img src="/static/img/7f00c5791bf69d15491938806b7e5fd9.5609A8B21F8028D7745634590ADBC3F3.webp" alt="5609A8B21F8028D7745634590ADBC3F3.png"></p>
<ul>
<li><strong>第一次获奖</strong>，是在第一个学期末，我们院系有一个专门面向大一的程序设计评奖，让大家用c语言做一个游戏出来，比谁的完成度好，可玩性高。然后我当时沉迷碧蓝航线，我就拿c语言做了一个山寨版碧蓝，甚至还做了一点很简单的gal剧情（至于实现的办法全是if else的shi 山罢了），或许是因为比较新奇的游戏构想和那个特殊的Boss战提示“FBI Warning”，让评委们给我评了一个一等奖，有种第一次被充分肯定的感觉，特激动，还准备了好久的发言稿，然后站在了全院面前进行了自己的获奖分享，想来那真的是我大一的全部高光时刻。</li>
</ul>
<blockquote>
<p>转头刚结束颁奖典礼，我就进了医院，连烧三天，住院七天，或许是因为太过兴奋了罢。</p>
<p>我出院半个月后，新冠爆发了。</p>
</blockquote>
<ul>
<li><strong>第一次带队做项目</strong>，我们学校有一个创新创业竞赛，规模其实很小，算的上是挑战杯的校赛，当时实验室里面惯例是学长学姐把他们已经拿过国奖的项目给我们这些小屁孩去练手，写写文档跑跑材料什么的。但我当时觉得我可以，我结合家里人是煤矿工人，想了一个方向是做井下安全监测设备的，自己找资料，自己找队员，自己写文档，自己想创新点什么的，全程几乎没有依靠学长学姐的帮助，虽然都是纸上谈兵，但是这个我自己带队，自己主笔的项目一直冲到了校赛决赛。这让我感觉很自豪，虽然整个项目都是我的凭空想象，做不出任何实际的产品，在现在看来甚至可笑，但已经可以很好的交代当时的我自己了。</li>
</ul>
<p>当时的小盆栽：<br>
<img src="/static/img/380a84433321373f762e3e29f53c1c7e.A019094F0E31F12923EE97A87471FF79.webp" alt="A019094F0E31F12923EE97A87471FF79.png"></p>
<ul>
<li><strong>第一次国赛</strong>，后来在实验室呆了一年多，技术逐渐也上来了，当时的我什么前端、后端、安卓、AI、爬虫、嵌入式开发板都摸着玩了一遍，虽然不算精通但是都算是能撸一个玩意儿出来的水平了。这个时候，刚好到了要准备次年全国计算机设计大赛的时候了。
<ul>
<li>当时因为我喜欢撸QQ机器人，我自己做了一个<a href="https://github.com/Dev-Umb/FAQ-Bot-QQ">群内问答机器人和非法消息监测机器人</a>，主要给迎新群做一些新生问答和各种诈骗消息监测的,并且好评还不错，甚至好多其他院的招新办闻讯来找我希望把bot借给他们用的。</li>
<li>最后在全国计算机设计大赛选题上，我就想基于我已经做过的Bot，去把互联网不实消息的检测做成一个大的开放平台，去给其他人用，关于这个项目可以看我<a href="https://umb.ink/post/10#%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90">当时的文章</a>以及<a href="https://github.com/Fake-Information-Monitoring">代码仓库</a>，做的很烂，但私以为对当时的我来说已经能够交代自己。</li>
<li>前后这个项目大概做了六个月左右，这个期间学了docker部署、学了SVM模型、词缀树算法什么的，最后项目也是很幸运地闯进了决赛，我们项目小组只有三个人，一个UI一个前端一个后端+算法，我后端算法一把梭，也因此学到了不少东西。依然记得最后交稿前我们通宵写PPT和文档，赶工项目，一晚上没有睡觉，第二天睡醒接着准备，赶在截止前完成了交稿。</li>
<li>最后嘛，国赛没有拿到我期望的结果，答辩的时候感受到了评委的压力，被问懵了，最后只拿到了国三的成绩，当时感觉有点失落，感觉自己付出了很多努力，甚至觉得自己做的东西还不错，但最后结果却不太如人意，但我觉得那段时间我是充实的，不论是在这个过程学习，还是和大家一起熬夜，都没有觉得有多么累。</li>
</ul>
</li>
</ul>
<p>后来毕业的时候留存下来自己的一堆证书：</p>
<p><img src="/static/img/a86a2d0186c0b52ac4382e6a84507d72.image.webp" alt="image.png"></p>
<ul>
<li><strong>第一次做实验室负责人</strong>，或许是我在实验室的活跃，也或许是我和两个死党的“三人帮组合”，让实验室导师们和学长学姐们将实验室22年度负责人的接力棒交给了我。并且刚好正值实验室成立十周年这个很特别的年度，接过接力棒后，“三人帮”做了实验室十周年庆，到处跑资源，给实验室争取来了自实验室这个组织成立以来的最大一笔经费，给实验室置办了巨量的资源，包括了算力服务器、机柜、3D光固化打印机、全套VR采集设备和无人机等一大堆在当时的我们看来是很奢侈的资源，实验室也在不到半年的时间里换了天地，而且实验室各种培训也如火如荼，总觉得在我们那一年，实验室格外地热闹，令人兴奋。</li>
</ul>
<p>当时拿出来秀的4090：<br>
<img src="/static/img/efb1626d3aae555798b2c7386795f5d7.236C5E6AB0D57ABCADB8320B196107EE.webp" alt="236C5E6AB0D57ABCADB8320B196107EE.png"></p>
<p>三人帮留影：<br>
<img src="https://umb.ink/static/img/c3d744d78eab31dc849a5f073192abef.IMG_8251%2820241017-023635%29.webp" alt=""></p>
<p>  <strong>第一次做赛区负责人</strong>，当实验室负责人和老师们难免接触较多，刚好那年学生科的一位负责计算机设计大赛的老师找上了我希望我帮忙负责山西省的计算机设计大赛的筹备和组织工作，虽然听起来比较高级，但其实更多是苦力活，人员组织、赛事通知和答疑、赛事参会、评委组织、报名组织、赛事评审工作筹划，都得自己做，前后忙碌了半年的样子，累麻了只能说那段时间，但是回过头看那段时间真的对我锻炼很多，各种和人沟通表达的锻炼，工作筹划的锻炼，对我而言都是一次非常难忘的经历。（而且还能吹牛逼，我负责过省级大规模的A类竞赛组织，参赛有四五千人）</p>
<p>实验室的一些奖牌</p>
<p><img src="/static/img/396a33718c180585388bf238cac83933.7EFF31AA99B809ED835F0C226DEA46FF.webp" alt="7EFF31AA99B809ED835F0C226DEA46FF.png"></p>
<h3>找工作</h3>
<p>  <strong>第一次真的迷茫</strong>，临近毕业的时候，我意识到我大学一直忽略了一个很重要的点，那就是究竟是考研还是就业的选择，当时身边的人都已经全部无脑准备考研了，虽然询问他们为什么要考研，99%的回答是“大家都考”。我当时陷入了迷茫，不知道自己毕业后该往何处去，担心工作找不到考研又错失机会，又担心考不上研究生更难找工作，我有种既要又要的冲动，啥都想抓，又想考研又想找工作，甚至已经买了书选了学校。后来谈了恋爱，我意识到，我不想考研了，因为我懒，而且我想赚钱（这个时候没有那么多的理想抱负了已经）而且我大学学了很多技术，就是没有钻研科研相关的东西，可以说我的整个大学除了学习和科研以外其他技能点都点了个差不多。</p>
<p>  于是我试着开始投简历，恰逢因为疫情导致经济下行，各个大厂都在暴力裁员，23年秋招哀鸿遍野，简历全部石沉大海，那会我每天的节奏就是睡醒投简历，然后背八股，刷题，然后面试，然后被刷，再投简历，如此仿佛了不知道多少次。那段时间只能用绝望形容，我开始怀疑我四年的努力，怀疑我的能力，怀疑未来的方向，甚至有好多次，已经进入offer评估或者HR面了，最后还是被告知没有HC或者没通过offer审核被挂。最终机缘巧合，来到了我现在工作的组里当实习生，最后实习转正正式入职。</p>
<p>整个招聘历程，我花了一年的时间，前后投递了近一千份简历，面试一百多场，最后手中寥寥几个中小厂offer（没有凡尔赛，当时手里最好的是在那年点击即送的吉利汽车和比亚迪的offer, 笛子高攀不起那是后话）</p>
<h3>工作后</h3>
<p>  工作后，接触了大量的人和事，第一次发觉山外青山，人外有人，环境对人的影响太大了，在职场中，远有能力超群，智商超群，情商超群的人，也发现职场、社会对能力的要求远不是学校所教的那点皮毛，深感大学学校教的都是什么垃圾，落伍而无用。但好在大学的经历让我能够相对胜任我的工作岗位，虽不出色但也不是特别糟糕。</p>
<h1>规划</h1>
<p>  与教育时期被迫规划，服从性成长不同，进入大学后，我们是一个独立的人，我们要自己决定自己的未来，要自己独立成长，而做好规范是成长的第一步。</p>
<h2>认知</h2>
<p>规划的第一步，是改变自己的认知，不论是信息获取方面还是思维方面，社会是复杂的，保持“小镇做题家”的思维逻辑是无法走长远的。</p>
<h3>1. 信息获取</h3>
<p>  在大学前，绝大部分同学的信息获取来自家庭、老师和课本，但是大学后，不会有人主动告诉你该做什么，要做什么，学校能教授的东西非常有限，甚至可以说是无用，因此不要在大学指望学校能教给你什么有用的东西。我们现在的大学教育和社会所需是完全脱节的，你在学校学到的东西在实际公司的业务场景中可能已经落后十年了。也不要指望大学的老师教你找工作，他们自己都没找过工作，怎么可能交给你实际的东西,因此必须要学会主动搜集有用信息。</p>
<p>搜集有用信息的方式：</p>
<ul>
<li>i. 学会使用搜索引擎。</li>
</ul>
<p>  凡事多去搜索，不要指望有人主动告诉你，任何问题的答案都是自己摸索出来的。另外不要认为搜索引擎很简单，熟练使用搜索引擎可以事半功倍（<strong>不要使用百度！</strong> 有能力用Google就用Google，没有就用bing），可以学习这篇内容：https://zhuanlan.zhihu.com/p/70415957 99%的问题都可以通过搜索引擎解决。</p>
<ul>
<li>ii. 多去了解行业动向和信息。</li>
</ul>
<p>  可以向本专业或认识的一些已经在行业内有一定建树的前辈进行提问，向本专业站在顶端的导师们提问也完全可以，相信提问得当，大部分前辈都乐于回答，但在此之前，请先学会<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md">如何提问</a>,坏的提问会很招人烦。</p>
<ul>
<li>iii. 多去摸索，多去试错，多去了解。</li>
</ul>
<p>  大学是与社会脱节的，那么如果你希望获取到最有用的信息，社会就是最好的学堂，有技术能力的就去实习，没有的可以利用业务时间去参加一些义务劳动或兼职，社会上能够学到很多在学校学不到的东西。</p>
<p>  <strong>但是！注意度量</strong>，很多行业或职业信息，了解一次就足够，例如奶茶店打工，你可以暑期兼职两个月，边打工边摸索奶茶店运营模式和细节，学会之后就没有必要再去兼职了，这种低价值劳动只会浪费你的时间。而对于自己希望从事的工作，例如学计算机的同学，多去中大厂或科研院所实习，大多数情况下都是多多益善的，每次不同公司的实习，都能给你带来不同的体验和收获。</p>
<ul>
<li>iv. 跳脱出信息茧房。</li>
</ul>
<p>  不要认为你所认为的就是正确的，不要被信息茧房束缚，要明确未来皆有可能，万事万物没有真确的道理。</p>
<p>  举个例子，有的同学很容易在学校和老师的引导下认为，自己学什么专业就只能去从事什么方面的工作，这是完全错误的。往大了看，很多从政风生水起的干部，专业都理工科，并非汉语言学或政治主修。往小了看，大厂很多年入百万程序员，也并非计算机科班出身，拿我身边来说，我有位同事本硕是法医专业，现在和我们一起在做后端开发。<strong>一切都是事在人为</strong>。</p>
<p>  学计算机的照样可能去做母猪的产后护理，学文学的照样可能从事工科研发，学数学的也可能去做HR，大学院系只是一个专业，仅此而已，不是你人生的全部，做你想做的事情，没有什么不可能。</p>
<h3>2. 独立思考</h3>
<p>  老生长谈的论点了，很多人的误区是，独立思考就是不听任何人的话，不从众，大家干什么我就反着来。这不叫独立思考，这是愚蠢。</p>
<p>  我认为真正的独立思考，是在收集巨量的客观信息进行整理之后，结合自己的主观思想，作出自身认为最能说服自己的观点。最典型的例子就是，很多人上大学前被家长和老师灌输唯读书论，仿佛不读书天要塌了，于是盲目考研，考公，考教资等等，总之不管什么东西，先考再说，考了就有用，但是，真的有用吗？所考的这个东西，对自己真的有价值吗？或者说对自己带来的收益和付出成正比吗？</p>
<p>  最有意思的例子就是我见过学计算机的学弟学妹抢着去考计算机二级证书的，仔细想想，学计算机的考P的计算机二级，有这种时间和精力多去刷两道算法题都比在那背计算机二级题库强。</p>
<p>  再举个例子，某些大学老师与校外考研机构勾结，渲染考研焦虑，打造所谓考研基地大学，把大学变成高中，让很多同学听从深感焦虑，一窝蜂涌向考研，且不说在学历贬值的当下，考研的ROI（投入与回报比）已经很低了，更何况但凡稍微了解一下就能知道，很多研究生都很扯淡，没有一点含金量，无非都是一个借口逃避现实罢了。</p>
<p>  在社会节奏异常快速的今天，学历的含金量在逐渐下降。而且很多人本就不适合考研，却将老师家长奉为真理，看不清自己的定位与未来。一问“为什么考研”，回答：“老师说不考研找不到工作”“我看大家都考，我也得考”“家里让我考，那我就考”。私以为，当你说出这些观点的时候，就算是侥幸上研，也是fw一个，未来进入社会，也大概率还是举步维艰。有考研自欺欺人的这时间和精力，还不如好好锻炼锻炼自己的综合能力。</p>
<p>  或许有人会反驳我，很多大厂很多岗位就是卡学历啊，不考研怎么进大厂？诚然，大厂卡校招学历，而且行情非常艰难是事实，但这和独立思考并无直接关系，我本意也并非不支持考研，我所反对的，是盲目不加以思考的考研，当然也包括考公，就是那种能说出“我看大家都考，我也得考“这种话的人。</p>
<p>  在我身边不乏有目标清晰的人，考研目的明确，“为了科研梦想”、“为了用高学历进入体制内发展”、“为了用高学历赚更多钱”、“本科专业不喜欢，所以考研为了从事自己想学的专业”，甚至哪怕是为了给自己争一口气，打脸某位前女友/前男友，这种也远比“我看大家都考，我也得考“要好一些，有清晰的目标，本质上就是有自己的一些独立思考在里面。</p>
<p>  当然，以上关于考研考公问题的论证也适用于其他所有的考证或就业，如果有人说出“我看别人都找工作，我也找工作”这种话，那也是一个无可救药的人。</p>
<p>  因此，没事多去思考，多去搜集信息，多和身边的人进行头脑风暴，锻炼自己独立思考的能力，不做无脑思考，人云亦云的人，也不要做完全反叛，反对一切可以反对的人。</p>
<h3>3. 自驱学习</h3>
<p>  这是一个比较鸡汤的观点，学习是一个比较反人类的东西，大多数人天生懒惰（比如我），但是学习又是不可轻视的一个东西，今天不学习，明天变垃圾。但是很多人又会在这个上面有一些误区，认为学习都很令人痛苦，甚至用学习无用论来逃避现实，亦或认为学习就是把书本教的学会，把老师上课讲的听懂，就叫学习。这些我认为都是对学习的误区。</p>
<p>  首先，学习未必是痛苦的，若你能从学习中得到正反馈，那学习就是有趣的。拿我举例子，在大学期间，我切实讨厌学校教的东西，认为读书很痛苦，但是我认为学技术非常有趣，我可以用爬虫整理自己喜欢的涩图（好孩子不要学），我可以用AI调教出专属女友或是让AI暴击校园群内的“诈骗学姐”，我可以使用Web技术建立自己的个人博客，我可以用QQ机器人写一个整活Bot来玩，这些东西的学习都让我感觉非常有趣且满足。并且如果你有了解国内一些比较牛逼的技术大牛，会发现他们大多数都是将技术作为自己的本源兴趣开展学习的（这也是为什么国人程序员好多二次元的间接原因）。</p>
<p>  也就是说，从学习中寻找自己的兴趣点，让学习给自己带来正向反馈。如果你在某些学习中实在找不到自己的兴趣点，只能感受到痛苦，并且这个痛苦会导致你几乎学不进去，三天打鱼两天晒网，那就换，换一个能给自己带来正向反馈的。最愚蠢的就是抱着一个自己压根不想学的东西硬啃，但是又不能持之以恒，三天大鱼两天晒网。（这里点名某些借着考研、考公混日子的人，既然学不进去，又坚持不动，那又何必骗自己呢）</p>
<p>  第二，学习不是别人告诉你，然后听懂就行了。纸上得来终觉浅，绝知此事要躬行，归根到底，学习只听懂别人所讲是不行的，别人教给你的，那是别人学会的，不是你学会的，听懂了和学会了是两个东西，小镇做题家认为听懂了会考试了就算学会，举个例子，在计算机专业，学校很多只会教开发数据库使用Mysql，但是很少告诉你，为什么惯例很多使用Mysql，为什么不用Redis，为什么不用PgSQL，难道是其他数据库不好用吗？而这些思考的答案，是在你听懂教授的知识后，自己去思考，去找资源，去从根源上理解，然后用学到的东西去实践，然后验证自己的答案，才能知道的，而把这个过程完成之后，才能真正称得上，学会了数据库。</p>
<p>  第三，学习不是仅限于学习知识。我自己将学习分为两种，一种是死知识，一是一，二是二，比如书本的知识，各种技术知识，往往都是固定的一套逻辑内容。还有一种是不固定的知识，或者叫通用知识，有点类似经验主义，比如如何表达，如何撰写好的文章，如何管理自己的时间和精力，如何与人相处等等，这些是需要大量实践才能总结学习出来的，并且需要多加留意、思考生活学习中的各种细节，而我们中国教育体系里最为缺乏的，就是对于学生这种通用知识的教育，很多人学习很好，技术很强，但是很多常识性的东西一塌糊涂，不懂怎么管理自己的时间，不懂怎么推动一个项目落地，不懂怎么与人沟通（我就是这种人，但我技术还很差）。</p>
<p>  这里重点说通用知识如何学习，对大学生来说，多去参加组织性的东西，比如实验室、学生会什么的，多去外出到真正的公司职场里实习，哪怕是很小的小作坊（但要有独立思考能力，别无脑过去被骗了），注意尽可能不要去接单，当乙方这种对结果导向非常强烈的工作，我们的目的是学习，不是做出成果，要让自己有一个即使什么都做不好也有回旋的余地。而且面向社会接单，一个没步入社会的黄毛小子怎么和人家老油条对线？到时候死都不知道怎么死的。</p>
<p>   对于初入大学什么都不懂的小萌新，建议可以试试辩论，辩论是一个非常锻炼通用技巧的东西，很适合内向的人破局。另外就是没事可以和自己的朋友去大谈政治，大谈新闻，哪怕是暴论，极端言论，目的不在于讨论是非，在于不经意间提高自己的逻辑能力和表达能力。（但注意不要在社交平台上对线，尽量私下面对面表达，谈不拢就真人快打.jpg）</p>
<h3>4. 兴趣力</h3>
<p>   兴趣是塑造一个人的基本，一切学习的开端始于兴趣，努力可以提高一个人的下限，但是兴趣可以提高一个人的上限。兴趣可以带给我们学习的正反馈，也可以是生活的调味剂，在大学期间多去拓展自己的兴趣爱好，丰富自己精神世界的同时，也说不定会在某个时刻带来意想不到的机会。这块就不细说了，毕竟我只是一个二次元死宅，没什么特别高尚的兴趣（。成为社畜以后也没什么机会拓展兴趣了，只是给不谙世事的学弟学妹一点我的建议。</p>
<h2>社会现状</h2>
<p>   简单说下我的见闻吧，当前的社会是快节奏社会，各行各业都在卷，对于像我一样出身平凡，长相平凡，能力的平凡的人来说，认清自己定位，了解社会现实是比较重要的。我们要接受社会不如意这个事实，甚至得接受现在的社会与百年前吃人的社会无本质区别的残酷现状，但是生活需要继续，同志仍需努力。</p>
<p>   钱多事少离家近的工作生活对于大多数人来说是一个相对理想化的东西，无资源无背景的人只能靠运气得到一份这样的工作，但是连刮刮乐都只能中20的大多数人，何来的好运呢。最典型的就是互联网的996工作，其他行业更甚，如果你觉得岁月真的静好，那真的只是大多数人在替你负重前行。多的是人拿着月薪三千的待遇干着堪比牛马的苦力，科比不一定真的看过凌晨四点的洛杉矶，但是北京马驹桥真的有凌晨四点的农民工，北京海淀真的有凌晨四点下班的程序员（如果觉得我说的有偏差，当我见识短浅即可，毕竟我也仅仅步入社会不到两年的时间）。</p>
<p>   因此社会现状并不如我们所理想的情况下，我们能做的就是接受并且努力向上，莫要听自暴自弃之流人的哭诉，摆烂躺平不能解决任何问题，我们改变不了世界，就去改变自己，钱当道的世界，唯有提升自己，积累原始财富，既包括自己能力的财富，也包括金钱的财富，多去拼，敢去拼，趁着年轻多试错，万一有一线机会呢，虽然社会残酷，但普通人尚有上升空间和机会，努力不一定会实现阶层跃迁，但能赚点钱让自己有去看世界的底气。摆烂躺平，随波逐流，终会埋没在时代的洪流里面。</p>
<h2>选择</h2>
<p>   选择大于努力，如我前面所说，我们每个人只是时代的一粒尘，随便一股风就可能吹跑，随便一滴雨就可能把我们打在地上，但是如果乘上时代的风，那就是乌鸦变凤凰，雷军说过站在风口上猪都能飞起来，也是这个道理，选择了对的方法，仅需很少的努力便可以起飞，选错了方向，寒窗苦载二十年，终是默默无闻。</p>
<p>   如何做好选择？结合我前面所说，多方面的信息来源，强大的独立思考能力，强大的学习能力与执行力，这些是做出一个好选择的前提。（如果家里有矿的，可以忽略），没有好的信息来源，吃shi都赶不上热乎的，就好像国庆前的那波股市大涨，很多人看到别人赚钱了才入场，直接成为韭菜狠狠被收割，而聪明的人懂得提前洞察信息，已经赚的盆满钵满套现离场。没有强大的独立思考能力，就会被别人误导，我大学期间见过许多人，整个大学都被家里安排，原本可以选择自己感兴趣的计算机，但是家里人认为土木工程更牛逼！然后嘛，，，，稍微懂得人应该都知道下场了，任何人都有思考的局限性，人只能想象自己见识过的东西，你所听到的东西，一定是别人想让你听到的，教你赚钱的人一定不是想你赚钱，而是想赚你的钱，所以更显自主独立思考的重要。没有强大的学习和执行能力，那就是看着别人赚钱眼红后悔自己为什么当初没有去做罢了，无需多说。</p>
<h1>结语</h1>
<p>综上所言，希望读完我的文章能够对读者有所帮助，或许其中有些观点尚显稚嫩或是有所偏差，但若能引发你一定程度的思考，也算不枉所写、特别是处于象牙塔里被保护的很好的大学生们，希望能够早日意识到自我，做好自己的大学乃至人生规划。</p>
<h1>PS</h1>
<p>如果你读完觉得有些地方有偏差或者有想更深一点了解的，想与我讨论下，欢迎留言或发Email联系我，我乐意友好地交流一下。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[LineArt-线稿转换API]]></title>
            <link>https://umb.ink/post/26</link>
            <guid>https://umb.ink/post/26</guid>
            <pubDate>Thu, 07 Mar 2024 17:39:20 GMT</pubDate>
            <description><![CDATA[<p>将之前写的线稿算法部署了一下<br>
<a href="https://umb.ink/c/line-art">点我快速体验</a></p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>将之前写的线稿算法部署了一下<br>
<a href="https://umb.ink/c/line-art">点我快速体验</a></p>
<!-- more -->
<h1>基本效果：</h1>
<p><img src="https://i.loli.net/2021/04/12/wpBqePcGD8OmKxr.jpg" alt=""></p>
<p><strong>提取后：</strong><br>
<img src="https://i.loli.net/2021/04/12/EZjAFvkiQNt5pCT.png" alt=""></p>
</div>
</body>
<h1>请求体</h1>
<p>请求URL：https://service.umb.ink/art/img</p>
<p>Method :   POST</p>
<p>RequestForm: 将img作为data放在FormData中进行请求</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-punctuation">{</span>
	<span class="hljs-attr">&quot;file&quot;</span><span class="hljs-punctuation">:</span> fileData
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Response：处理完成后的线稿图像为二进制流PNG图片</p>
<h1>示例</h1>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">function</span> <span class="hljs-title function_">postData</span>(<span class="hljs-params"></span>) {
        <span class="hljs-keyword">var</span> xmlhttp;
        xmlhttp=<span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>();
        <span class="hljs-keyword">var</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>();
        formData.<span class="hljs-title function_">append</span>(<span class="hljs-string">&quot;file&quot;</span>, data);
        xmlhttp.<span class="hljs-title function_">open</span>(<span class="hljs-string">&quot;POST&quot;</span>,basePath+<span class="hljs-string">&quot;/img&quot;</span>,<span class="hljs-literal">true</span>); <span class="hljs-comment">// 请求图片</span>
        xmlhttp.<span class="hljs-property">responseType</span> = <span class="hljs-string">&quot;blob&quot;</span>;
        xmlhttp.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>);
            <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> == <span class="hljs-number">200</span>) {
                <span class="hljs-keyword">var</span> blob = <span class="hljs-variable language_">this</span>.<span class="hljs-property">response</span>;
                <span class="hljs-keyword">var</span> img = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&quot;result-image&quot;</span>); <span class="hljs-comment">// 渲染到img标签</span>
                img.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) {
                    <span class="hljs-variable language_">window</span>.<span class="hljs-property">URL</span>.<span class="hljs-title function_">revokeObjectURL</span>(img.<span class="hljs-property">src</span>);
                };
                img.<span class="hljs-property">src</span> = <span class="hljs-variable language_">window</span>.<span class="hljs-property">URL</span>.<span class="hljs-title function_">createObjectURL</span>(blob);
            }
        }
        xmlhttp.<span class="hljs-title function_">send</span>(formData);

    }
    <span class="hljs-comment">//赋值变量</span>
    <span class="hljs-keyword">var</span> basePath = <span class="hljs-title function_">getContextPath</span>();
    <span class="hljs-comment">// 获取项目路径</span>
    <span class="hljs-keyword">function</span> <span class="hljs-title function_">getContextPath</span>(<span class="hljs-params"></span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;http://101.34.24.60:8008&quot;</span>
    }
</code></pre>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[2023工作总结]]></title>
            <link>https://umb.ink/post/24</link>
            <guid>https://umb.ink/post/24</guid>
            <pubDate>Tue, 31 Dec 2024 10:31:42 GMT</pubDate>
            <description><![CDATA[<h1>我的2023年度工作总结</h1>
<p>因为最近事情比较多，比较忙，迟来的工作总结，回顾23年，对于我是充满忙碌、变化与挑战的一年，这一年曲折坎坷，终是轻舟已过万重山。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>我的2023年度工作总结</h1>
<p>因为最近事情比较多，比较忙，迟来的工作总结，回顾23年，对于我是充满忙碌、变化与挑战的一年，这一年曲折坎坷，终是轻舟已过万重山。</p>
<!-- more -->
<p>2024.3.10更新：拿到了23年度的某手洛子峰奖（全都仰仗大佬们）、有幸拿到了所属线的踏实进取奖，瞬间感觉自己一年的付出得到了认可。</p>
<h1>2023 Q3、Q4所参与的项目：</h1>
<h2>1. 内部网络工具APP：</h2>
<ul>
<li><strong>角色</strong>：Android研发支持</li>
<li><strong>工作内容</strong>：编写Android底层网络信息与信号强度的检测工具类，封装底层接口</li>
<li><strong>产出</strong>：协助了内部工具网络检测功能性技术方案的验证</li>
<li><strong>自我评价</strong>：后半年Android原生侧需求较少，因此并没有很多的工作内容，但参与的网络检测功能性技术方案的验证较为顺利。</li>
</ul>
<h2>2. 班车系统：</h2>
<ul>
<li><strong>角色</strong>：技术主R</li>
<li><strong>工作内容</strong>：完成了新版本班车设备系统的研发工作，完成班车APP与管理后台的研发，攻克实际业务场景下的痛点</li>
<li><strong>产出</strong>：解决了班车系统在极端弱网环境下无法使用、现场人力维护成本高的痛点，完成了上线和交付工作，业务运行相对稳定。</li>
<li><strong>自我评价</strong>：在此前没有接触过车机设备的情况下，接手这个项目还是比较有挑战性的，在过程中也踩了非常多的坑，也学到了很多，并且第一次意识到了产品和沟通管理对一个项目的重要性。对个人来说，经过这个项目，意识到自己还有很多需要改进的地方，是一次很好的成长。（我自己一个人做客户端+后端+主R，感觉还是蛮nb的）</li>
</ul>
<h2>3. 会议室预约屏：</h2>
<ul>
<li><strong>角色</strong>：技术主R</li>
<li><strong>工作内容</strong>：
<ol>
<li>开发会议室预约屏信息展示优化、自动巡检、设备自动开关机等功能</li>
<li>开发会议室预约屏管理平台</li>
</ol>
</li>
<li><strong>产出</strong>：
<ol>
<li>完成累计一千多会议室的预约屏全量上线工作</li>
<li>上线会议室预约屏自动开关机、自动巡检策略，降低设备故障率到10%内</li>
<li>参与开发并上线了会议室预约屏管理平台，提高会议预约屏维护效率</li>
</ol>
</li>
<li><strong>自我评价</strong>：预约屏的相关需求迭代开发和上线都相对较为顺利，也较为快速，个人对自己在预约屏项目上的表现较为满意。</li>
</ul>
<h2>4. 智能化会议室：</h2>
<ul>
<li><strong>角色</strong>：研发支持</li>
<li><strong>工作内容</strong>：
<ol>
<li>实现会议室人体存在、开关数据的投票算法和数据入库。</li>
<li>完成会议室智能化电视集控的开发</li>
</ol>
</li>
<li><strong>产出</strong>：协助完成了会议室智能化数据看板的搭建工作，完成了电视集控的灰度上线</li>
<li><strong>自我评价</strong>：
<ol>
<li>在数据看板中，对于大型数据的处理此前没有相关经验，外加同时期精力相对有限，导致初期项目进展并不顺利，数据入仓代码较为混乱，集成开发较为困难，并且上线测试后还出现了部分数据不准的问题，数据库内写入超千万量级的数据维护起来也很费劲，然后替换了旧有的数据格式，替代为相对简洁的投票机制，希望在24年度无极看板能够完美上线运行。</li>
<li>在会议室集控项目中，因此前有对接绿米SDK的经验，因此相对开发和灰度上线都相对较为顺利，同时还完成了海信电视APP集控的方案测试验证，因此相对较为满意。</li>
</ol>
</li>
</ul>
<h2>5. 内部资源管理平台（CMDB）：</h2>
<ul>
<li><strong>角色</strong>：后端研发支持</li>
<li><strong>工作内容</strong>：
<ol>
<li>基于Casbin实现平台权限管理和审计模块的开发。</li>
<li>实现网络管理平台的CMDB资源Excel模板文件的上传与下载</li>
</ol>
</li>
<li><strong>产出</strong>：
<ul>
<li>完成了权限管理模块的研发与上线，并对服务进行了解耦和优化</li>
<li>完成了CMDB资源Excel模板文件的上传与下载功能。</li>
</ul>
</li>
<li><strong>自我评价</strong>：
<ol>
<li>在权限管理中，自己之前没有接触过RABC模型和Casbin开发，通过这次的权限管理需求，首次了解并且学习了RABC模型和Casbin的使用，并且独立解决了多容器场景下Casbin数据无法正常同步的问题，对自己有较大的成长。</li>
<li>在会议室集控项目中，因此前有对接终端传感器的经验，因此相对开发和灰度上线都相对较为顺利，同时还完成了电视APP集控的方案测试验证，因此相对较为满意。</li>
</ol>
</li>
</ul>
<h1>对自己的评价</h1>
<blockquote>
<p><strong>关键字</strong>：成长、变化</p>
</blockquote>
<p>自入职后的23后半年学到了非常多的东西，不仅仅包括技术上的，也包括很多通用性的能力，包括文档撰写、项目管理、沟通等。在工作的半年，自己的提升非常大，感觉自己这半年是充实且充满成长性的半年。</p>
<p>但是半年中也发现了自己在职场中存在的一些缺点和不足，包括沟通能力不强，不能很好的主动和人进行有效沟通；信息传递意识不强，很多信息不能及时同步到上级与项目相关方；不能很好地管理自己的精力与排期；偶尔会管理不好自己的压力情况等，希望下年度能够有所进步。</p>
<p>总的来说23年的工作个人打分：7分/10分，欠缺3分为沟通一分、压力管理一分、技术目标未达到个人预期一分。</p>
<h1>工作中迷惑的地方</h1>
<p><strong>排期问题</strong>：在业务中经常会遇到无法预估排期的问题，先前并没有相关经验，单靠调研并不能准确预估排期时间，排期多了和少了都会有问题，反而会造成压力，导致项目效果不理想。</p>
<h1>24年度的规划</h1>
<ol>
<li><strong>技术规划：</strong></li>
</ol>
<ul>
<li>在维持正常业务开发的基础上，侧重于后端技术的扩展与提高，学习分布式、微服务、RPC等后端技术栈，弥补后端技术短板的情况</li>
<li>在安卓侧想办法整理出较为完善的个人技术架构，包括BaseLauncher、Iot应用快速搭建脚手架等，提高开发效率</li>
</ul>
<ol start="2">
<li><strong>通用能力规划</strong>：</li>
</ol>
<ul>
<li>适当处理好自身心态，寻求压力与工作平衡点，锻炼与人沟通技术。</li>
</ul>
<h1>24年度目标：</h1>
<ul>
<li>技术上熟练后端各类技术栈，研究各类后端架构，并编写一些技术性验证demo发布到博客或Github。</li>
<li>通用能力上锻炼自身沟通能力。</li>
</ul>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[面临大学毕业——如何准备下一阶段？]]></title>
            <link>https://umb.ink/post/23</link>
            <guid>https://umb.ink/post/23</guid>
            <pubDate>Sun, 21 Jan 2024 11:56:15 GMT</pubDate>
            <description><![CDATA[<p>又到一年毕业季，新的韭菜要进入社会被噶了，这里写篇博客给一些自己短浅的建议供计算机相关专业的学弟学妹们参考。</p>
<blockquote>
<p>免责声明：因本人经验短浅，本文章仅供计算机相关专业参考，对其他专业可能不具有参考价值，并且本文章仅为本人建议，仅供抛砖引玉，引发读者自身思考，读者的个人决定与本人无关。</p>
</blockquote>
<h1>毕业——考研？就业？考公？</h1>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>又到一年毕业季，新的韭菜要进入社会被噶了，这里写篇博客给一些自己短浅的建议供计算机相关专业的学弟学妹们参考。</p>
<blockquote>
<p>免责声明：因本人经验短浅，本文章仅供计算机相关专业参考，对其他专业可能不具有参考价值，并且本文章仅为本人建议，仅供抛砖引玉，引发读者自身思考，读者的个人决定与本人无关。</p>
</blockquote>
<h1>毕业——考研？就业？考公？</h1>
<!-- more -->
<h2>选择</h2>
<p>毕业后对于普通的大多数来说，大家无非就三种去向，考研、就业和考公，这里我结合我自己的一些经验分享一下我的看法。</p>
<p>这三种选择建议最好<strong>结合自身情况与调研实际情况</strong>去决定，多去咨询相关学长学姐去了解就业/考研/考公的实际情况。</p>
<blockquote>
<p>考研/考公 本人没了解过，这里不作过多篇幅，但是如果你不知道自己要干啥，而是随大流去考研/考公，并且自己的自制力没有那么好，那么建议滚去就业，不要浪费自己的时间，并且各个厂很少有招聘往届生的机会，也就是最好不要指望考研没上继续二战，最后再找工作，机会只有一次。</p>
</blockquote>
<h3>就业</h3>
<p>对于学计算机的同学来说，若本科就业，首选央企、互联网中大厂，次选国企小厂，不建议去外包（包括华为od）。</p>
<ul>
<li><strong>现实情况</strong>：现在计算机就业市场存在饱和，各个中大厂逐渐减少校招名额和比例，各家都在降本增效，拿到中大厂offer的难度与日俱增。同时很多下岗大厂员工投身进入所谓的“程序员速成班”，越来越多不明觉厉的人进入互联网企业做起了外包，导致大厂的用人倾向逐渐倾向外包化，在可预见的未来互联网行业只会越来越卷。</li>
<li><strong>自身条件</strong>：进入中大厂的水平要求较高，如果你大学三年都没有很好地积累非常扎实的技术栈和较多的项目开发经验，那么卷中大厂绝对不是一个好的选择。
<ul>
<li>如果你熟悉Java、Python、Go、Js等任意技术栈（有熟练理解其底层原理），并且至少刷完了力扣100题，那么好好准备可以冲大厂了。</li>
</ul>
</li>
<li><strong>中大厂/央企/国企/小厂 区别：</strong>
<ul>
<li>本质上不论去哪种性质的企业，作为程序员，干的活基本都是差不多技术水平的（算法岗除外），面试造火箭，工作拧螺丝就是这个道理。</li>
<li>中大厂的优势：平台大、相对机会多，流程规范，优势项目经验多，大厂经验会非常宝贵。</li>
<li>中大厂劣势：压力大、裁员可能高、没有个人时间</li>
<li>中大厂适合什么人：技术水平高、喜欢打拼、想赚钱</li>
<li>央企/大型国企优势：平台大、环境相对轻松、稳定，福利高</li>
<li>央企/大型国企劣势：门槛高</li>
<li>央企/大型国企适合什么人：学习成绩优异、党员、想进体制但是不想考公，家里有背景。</li>
<li>小厂/边缘国企的优势：环境轻松、门槛低、部分有后发潜力</li>
<li>小厂/边缘国企劣势：待遇低、技术质量低、对个人提升很难没有促进</li>
<li>小厂/边缘国企适合什么人：技术力差，学习中等，喜欢轻松的生活工作</li>
</ul>
</li>
</ul>
<h3>思想</h3>
<p>我们大学的教育与社会是极其割裂的，在脱离大学进入社会前，一定要转变自己的思想。自信、善于表达自己、适度地装逼表现自己，要比闷头苦干的人更适合社会节奏。</p>
<p>同时学会与人沟通，换位思考，与人沟通的能力在职场中远大于个人技术能力，在实际职场中，沟通占7分，技术占3分，但请注意对于校招生来说，技术依然是首选项，沟通能力仅是能助我们走的更远，不代表技术就不重要。</p>
<p>学会大胆表现自己，不要担心在面试官或领导面前表达出来会班门弄斧，如果你在大学期间加入了实验室，并且取得了一些A类赛事的国奖，熟读了一门或几门技术栈，有自己独立开发项目的能力，那么你已经超越了至少50%的同龄人（包括985/211），如果你有这样的能力，你是很牛逼的。</p>
<p>推荐一本书：《跃迁》https://book.douban.com/subject/27078435/</p>
<h3>就业准备？</h3>
<ol>
<li>
<p>渠道</p>
<p>Boss直聘（相对最靠谱）、智联招聘（国企、传统企业偏多）、牛客（校招、实习）、实习僧（实习）</p>
</li>
<li>
<p>简历</p>
<p>简历是非常重要的一个点，简历的好坏直接决定了面试官对你的第一印象，对于计算机专业的来说，简历应该包含下面的几个要素：</p>
</li>
</ol>
<ul>
<li><strong>个人基本信息</strong>，（学历、专业、绩点、邮箱、电话、微信、个人照片），个人信息尽量直观、精简，<strong>不要写学生班干部经历什么的，技术岗位没人管你是不是班干部</strong></li>
<li><strong>掌握技术栈</strong>：视你投递的岗位和个人技术积累而定，比如你投递java后端，那么技术栈就尽量写SpringBoot、Mysql等，如果投递的是Web前端，就写写熟悉Js、Vue、React等技术栈。</li>
<li><strong>项目经历</strong>：这里最为重要，项目经历尽量写的一眼能看明白<strong>你在项目里做了什么、有什么成就</strong>，可以参考STAR原则去写，即项目是什么？你的角色是什么？你做了什么？过程是什么？结果是什么？这样写出来的最为直观。<br>
<img src="https://picx.zhimg.com/80/v2-ba5e57c18bcf05240750085008251576_1440w.webp?source=1def8aca" alt=""></li>
<li><strong>获奖</strong>：  可以写写自己获得的省级以上奖项，<strong>校级以下的没必要写</strong>，没有任何含金量</li>
<li><strong>其他经历</strong>：包括社团主席、赛事组织负责人等可以写写，如果是班干部之类的任职，没有必要写。</li>
</ul>
<ol start="4">
<li>
<p>心态</p>
<p>放平心态，找工作70%靠的是运气，30%靠的是实力，很多时候你可能比其他人技术更好，能力更强，但是面试官选择了另一个人，挂了你，不是你不行，而是刚好另一个人运气好，更贴合面试官的喜好和岗位匹配度。本人之前校招的时候三个月投递了380+公司，面试了一百多场，最后也才拿到了四五个offer，很多时候放宽心去做就好了。</p>
</li>
</ol>
<blockquote>
<p>但行好事，莫问前程。</p>
</blockquote>
<blockquote>
<p>多在几颗树上吊死，总能成一个。</p>
</blockquote>
<ol start="5">
<li>
<p>方向选择：<br>
前端？后端？客户端？</p>
<p>就现在个人感觉来说，后端和前端的岗位还是蛮多的，AI算法岗对本科几乎没有任何机会，因此推荐继续卷前端后端，客户端发展有局限性，即使校招进去后也需要转前后端，否则容易被淘汰。测试岗位不推荐，但是可以考虑，在大厂中测试岗位相对其他来说门槛会低一点，但是对个人提升也比较有限。</p>
</li>
<li>
<p>技术</p>
<p>背八股已经是基本要求了，更多注重技术与业务结合，思考一个算法或代码特性可以在什么场景中使用会更有加分。</p>
</li>
</ol>
<ul>
<li>力扣算法</li>
<li>拿得出手的项目（学习STAR原则对项目进行整理）</li>
<li>熟读技术八股文</li>
</ul>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[快速部署一份自己的Stable Diffusion]]></title>
            <link>https://umb.ink/post/22</link>
            <guid>https://umb.ink/post/22</guid>
            <pubDate>Mon, 08 Jan 2024 17:11:00 GMT</pubDate>
            <description><![CDATA[<p>最近闲来无事，折腾一下很久之前已经火完了的Stable Diffusion。这篇博客主要说明如何快速在有英伟达显卡的电脑上部署一份Stable Diffusion以及如何引入自己希望的模型。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>最近闲来无事，折腾一下很久之前已经火完了的Stable Diffusion。这篇博客主要说明如何快速在有英伟达显卡的电脑上部署一份Stable Diffusion以及如何引入自己希望的模型。</p>
<!-- more -->
<p>本人部署用环境：</p>
<ul>
<li>CPU：AMD 5600X</li>
<li>GPU：RTX3060</li>
<li>RAM：32GB</li>
</ul>
<h1>1.确定相关网址</h1>
<ul>
<li>SD-WebUI Github : SD经典UI仓库 https://github.com/AUTOMATIC1111/stable-diffusion-webui</li>
<li>HuggingFace: 用于下载各类所需模型,模型类型非常多 https://huggingface.co/</li>
<li>civitai: 用于下载各类所需模型，更加直观一点，https://civitai.com/models/119012/bluepencil-xl</li>
</ul>
<h1>2. Clone仓库</h1>
<p>打开你的Terminal，Clone SD WebUi仓库，在此之前请保证你的电脑有Git环境、cuda环境和python环境，关于如何安装Git请自行百度/知乎</p>
<pre><code>git clone git@github.com:AUTOMATIC1111/stable-diffusion-webui.git
</code></pre>
<h1>3.下载models</h1>
<p>在HuggingFace或c站都可以任意选择一个model进行下载，我这里使用的是https://civitai.com/models/119012/bluepencil-xl 这个模型<br>
<img src="https://s2.loli.net/2024/01/09/pM7yLniWgXel5b9.png" alt="QQ截图20240108162956.png"><br>
下载完成后，将下载好的model文件放在.\stable-diffusion-webui\models目录下即可</p>
<h1>4 启动SD</h1>
<p>点击运行.\stable-diffusion-webui下的webui-user.bat文件，会弹出一个终端执行框，等待其自动配置完成即可</p>
<p>如果出现错误，请检查你的cuda环境和Python环境是否正常<br>
<img src="https://s2.loli.net/2024/01/09/NhAgnoE6SlO8f4K.png" alt="QQ截图20240109010854.png"></p>
<h1>5 成品</h1>
<p><img src="https://s2.loli.net/2024/01/09/NqYBh4aR5ofjLMX.png" alt="QQ截图20240109010758.png"></p>
<p>参考prompt：</p>
<p>anime, cute girl, wizard hat, kimono, thigh-highs, happy, dynamic pose, &quot;HAPPY NEW YEAR!&quot;, 2024, pop font, landscape, sunrise, winter, red tone color, bloom effect, ambient occlusion, Bokeh, depth of field</p>
<p>speed：1360477511</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[Casbin多容器实例场景下数据不一致的问题解决]]></title>
            <link>https://umb.ink/post/21</link>
            <guid>https://umb.ink/post/21</guid>
            <pubDate>Thu, 12 Oct 2023 08:15:09 GMT</pubDate>
            <description><![CDATA[<h1>问题描述</h1>
<p>在Golang服务端使用Casbin作为权限管理的场景下，会增加多个Casbin实例来保证请求高并发和稳定性，但是与此同时会出现多个实例接口返回的数据不一致的问题，这种问题会严重影响业务使用。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>问题描述</h1>
<p>在Golang服务端使用Casbin作为权限管理的场景下，会增加多个Casbin实例来保证请求高并发和稳定性，但是与此同时会出现多个实例接口返回的数据不一致的问题，这种问题会严重影响业务使用。</p>
<!-- more -->
<h1>问题原因</h1>
<p>在Casbin的设计中，为了保证高并发下的可观性能，Casbin内置一个内存缓存，每当执行读数据的动作时，会先获取内存缓存中的数据，而不是数据库的数据。</p>
<p>因此在多实例场景下，当一个实例的Casbin数据发生变更时，其它实例的Casbin缓存并没有及时同步刷新，这就导致了当请求被分发到对应的实例上时，获取到的依然还是旧的错误数据。</p>
<h1>问题解决</h1>
<p>Casbin官方提供了一种可行的解决方法——Watcher机制，可以阅读对应文档<a href="https://casbin.org/zh/docs/watchers/">CasbinWathcer</a></p>
<p>文档中说明了casbin支持使用Redis、Kafka、zookeeper等中间件实现多实例之间的数据同步</p>
<p>我采用了成本较低的Redis作为Watcher，下面是我的代码参考：</p>
<ul>
<li>Watcher回调函数：</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment">// 这是一个包装方法，方便传入Enforcer实例来实现回调中对casbin的操作</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ConsumeCallback</span><span class="hljs-params">(e *casbin.Enforcer)</span></span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(msg <span class="hljs-type">string</span>)</span></span> {
	<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(msg <span class="hljs-type">string</span>)</span></span> {
		zap.L().Info(<span class="hljs-string">&quot;enforcer update msg:&quot;</span> + msg)
                <span class="hljs-comment">// 在接收到任意的权限变更后，刷新Enforce实例，达到刷新缓存的目的</span>
		e.LoadPolicy()
	}
}
</code></pre>
<p>需要注意的是我们这里给到的回调函数必须是func(msg string)的形式，因为Casbin Watcher规定了回调到的方法只会给到一个Msg信息，剩下的需要我们自己处理。</p>
<p>具体的MSG信息官方文档并没有给出，不过我在实际测试过程中有总结到一个大的结构体对象可以供有需要的进行参考</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment">// 回调中给到的msg的结构大概就是下面struct中定义的样子</span>
<span class="hljs-keyword">type</span> MSG <span class="hljs-keyword">struct</span> {
	Method      <span class="hljs-type">string</span>
	ID          <span class="hljs-type">string</span>
	Sec         <span class="hljs-type">string</span>
	Ptype       <span class="hljs-type">string</span>
	Params      <span class="hljs-keyword">interface</span>{}
	NewRules    <span class="hljs-keyword">interface</span>{}
	OldRules    <span class="hljs-keyword">interface</span>{}
	NewRule     <span class="hljs-keyword">interface</span>{}
	OldRule     <span class="hljs-keyword">interface</span>{}
	FieldValues <span class="hljs-keyword">interface</span>{}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *MSG)</span></span> MarshalBinary() ([]<span class="hljs-type">byte</span>, <span class="hljs-type">error</span>) {
	<span class="hljs-keyword">return</span> json.Marshal(m)
}

<span class="hljs-comment">// UnmarshalBinary decodes the struct into a User</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *MSG)</span></span> UnmarshalBinary(data []<span class="hljs-type">byte</span>) <span class="hljs-type">error</span> {
	<span class="hljs-keyword">if</span> err := json.Unmarshal(data, m); err != <span class="hljs-literal">nil</span> {
		<span class="hljs-keyword">return</span> err
	}
	<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<ul>
<li>Casbin注册回调函数</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *CasbinOptions)</span></span> Init(db *gorm.DB) (*casbin.Enforcer, <span class="hljs-type">error</span>) {
	w, _ := rediswatcher.NewWatcher(viper.GetString(<span class="hljs-string">&quot;redis.addr&quot;</span>), rediswatcher.WatcherOptions{
		Options: redis.Options{
			Network:  <span class="hljs-string">&quot;tcp&quot;</span>,
			Password: <span class="hljs-string">&quot;redis.password&quot;</span>,
			DB:       <span class="hljs-string">&quot;redis.iotDB&quot;</span>,
			PoolSize: <span class="hljs-string">&quot;redis.poolSize&quot;</span>,
		},
		Channel: <span class="hljs-string">&quot;/casbin&quot;</span>,
		<span class="hljs-comment">// 忽略容器自身发起的订阅消息推送，防止出现死循环</span>
		IgnoreSelf: <span class="hljs-literal">true</span>,
	})

	adapter, err := gormadapter.NewAdapterByDB(db)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		zap.L().Error(<span class="hljs-string">&quot;casbin adapter init error:&quot;</span> + err.Error())
		<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
	}
	e, err := casbin.NewEnforcer(c.Path, adapter)
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		zap.L().Error(<span class="hljs-string">&quot;casbin enforcer init error:&quot;</span> + err.Error())
		<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
	}
        <span class="hljs-comment">// 设置RedisWatcher</span>
	_ = e.SetWatcher(w)
        <span class="hljs-comment">// 设置回调函数</span>
	_ = w.SetUpdateCallback(ConsumeCallback(e))
	err = e.LoadPolicy()
	<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
		zap.L().Error(<span class="hljs-string">&quot;casbin policy init error:&quot;</span> + err.Error())
		<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
	}
	_ = e.SavePolicy()
	<span class="hljs-keyword">return</span> e, <span class="hljs-literal">nil</span>

}
</code></pre>
<p>完成后重新运行验证效果即可</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[如何写好一份规范的代码？]]></title>
            <link>https://umb.ink/post/20</link>
            <guid>https://umb.ink/post/20</guid>
            <pubDate>Wed, 11 Oct 2023 07:25:29 GMT</pubDate>
            <description><![CDATA[<h1>一、引言</h1>
<h2>代码整洁的重要性</h2>
<p>代码整洁是软件开发中至关重要的一个方面。当我们编写整洁的代码时，可以提高代码的可读性、可维护性和可扩展性。整洁的代码易于理解，减少了他人阅读和维护代码时的困惑和错误。同时，整洁的代码也有助于减少bug的产生，提高软件的质量。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>一、引言</h1>
<h2>代码整洁的重要性</h2>
<p>代码整洁是软件开发中至关重要的一个方面。当我们编写整洁的代码时，可以提高代码的可读性、可维护性和可扩展性。整洁的代码易于理解，减少了他人阅读和维护代码时的困惑和错误。同时，整洁的代码也有助于减少bug的产生，提高软件的质量。</p>
<!-- more -->
<h2>代码整洁的好处</h2>
<ol>
<li>
<p>可读性：整洁的代码易于阅读和理解，减少了他人阅读代码时的困惑和错误。清晰的代码结构和命名规范使得代码逻辑更加明确，提高了团队协作效率。（简单来说就是要让一起协同工作的人能读懂你的代码，而不是一坨答辩）</p>
</li>
<li>
<p>可维护性：整洁的代码易于维护。清晰的代码结构和模块化设计使得修改和扩展代码更加容易。通过遵循一致的编码风格和注释规范，他人能够更快速地理解代码的意图和实现细节。（</p>
</li>
<li>
<p>可测试性：整洁的代码易于进行单元测试和集成测试。清晰的函数和类设计使得测试代码更加简洁和可靠。通过良好的错误处理和异常处理机制，能够更好地捕获和处理错误，提高代码的健壮性。</p>
</li>
<li>
<p>可扩展性：整洁的代码易于扩展和重构。良好的代码结构和设计原则使得添加新功能或修改旧功能时能够保持代码的灵活性和可扩展性。通过避免代码的重复和冗余，减少了后续维护的工作量。</p>
</li>
</ol>
<h1>二、代码命名</h1>
<p>在编写代码时，良好的命名是十分重要的，它能够提高代码的可读性和可理解性。以下是关于命名的几个要点：</p>
<ol>
<li>
<p>使用有意义的变量名：变量名应该能够准确地描述变量的用途和含义。例如，如果我们需要表示一个人的年龄，可以使用age作为变量名，而不是使用无意义的字母或数字。</p>
</li>
<li>
<p>避免使用含糊不清的缩写：尽量避免使用难以理解的缩写，因为缩写可能会使代码的可读性降低。例如，如果我们需要表示一个订单的创建时间，可以使用creationTime作为变量名，而不是使用缩写形式如ct。</p>
</li>
<li>
<p>统一的命名风格：在整个代码库中保持统一的命名风格是很重要的。例如，可以选择使用驼峰命名法或下划线命名法，并在项目中保持一致。例如，如果我们使用驼峰命名法，可以将一个变量命名为firstName，而不是first_name或FIRSTNAME。（一般对于Java、Golang而言我们更喜欢使用驼峰命名，而对于c/c++、python或脚本语言更喜欢使用下划线命名）</p>
</li>
<li>
<p>避免误导性的命名：命名应该准确地反映变量或函数的用途，避免使用与其实际功能不符的名称。例如，如果我们有一个函数用于计算圆的面积，可以将其命名为calculateCircleArea，而不是calculateRectangleArea。</p>
</li>
<li>
<p>避免使用中文拼音：很多初学者，特别是一些没有接触编程或从某些培训班体系出来的程序员会非常喜欢使用中文拼音来命名变量，这是非常不好的习惯，比如下面的代码：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-type">String</span> <span class="hljs-variable">diYiXueQi</span>  <span class="hljs-operator">=</span> <span class="hljs-string">&quot;第一学期&quot;</span>
</code></pre>
<p>如上面的变量，很容易引起误解，读者在阅读代码时，很难第一反应了解到这个diYiXueQi到底代表什么，而使用英文可以很顺畅地表达我们的想法。</p>
</li>
</ol>
<h1>三、函数</h1>
<p>在编写代码时，函数是组织和封装逻辑的基本单元。函数设计的好不好直接决定了项目的鲁棒性和耦合度。<br>
下面我通过python来举例什么是好的函数：</p>
<ol>
<li>函数的简洁性和清晰性：函数应该保持简洁和清晰，只关注单一的任务或功能。避免函数过于复杂和冗长，可以通过拆分函数、提取子函数等方式来简化函数的逻辑。</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：复杂且冗长的函数</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 复杂的数据处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 复杂的数据处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 复杂的数据处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 复杂的数据处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：简洁清晰的函数</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    preprocess_data(data)
    transform_data(data)
    analyze_data(data)
    save_data(data)
</code></pre>
<ol start="2">
<li>函数的单一责任原则：每个函数应该只负责一个明确的任务或功能，并且只做这一个任务。这样可以提高函数的可重用性和可测试性，同时也使得代码更易于理解和维护。</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：一个函数负责多个任务</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    preprocess_data(data)
    transform_data(data)
    analyze_data(data)
    save_data(data)
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：每个函数只负责一个任务</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">preprocess_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据预处理逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">transform_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据转换逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">analyze_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据分析逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">save_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据保存逻辑...</span>
</code></pre>
<ol start="3">
<li>函数的命名和参数选择：函数的命名应该能够准确地描述函数的功能和作用。选择有意义的函数名和参数名，可以让代码更易于理解和使用。</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：函数名和参数名不具备描述性</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">calc</span>(<span class="hljs-params">a, b</span>):
    <span class="hljs-comment"># 计算逻辑...</span>

<span class="hljs-comment"># 不好的示例：使用缩写和无意义的参数名</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">d</span>):
    <span class="hljs-comment"># 数据处理逻辑...</span>
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：具有描述性的函数名和参数名</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">calculate_sum</span>(<span class="hljs-params">num1, num2</span>):
    <span class="hljs-comment"># 计算两个数的和...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据处理逻辑...</span>
</code></pre>
<ol start="4">
<li>避免过长的函数：函数应该尽量避免过长，超过一屏幕的长度。长函数难以理解和维护，可以通过拆分函数或提取子函数来减少函数的长度。</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：过长的函数</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 长长的处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 长长的处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 长长的处理逻辑...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ...</span>
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：拆分和提取子函数</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">process_data</span>(<span class="hljs-params">data</span>):
    preprocess_data(data)
    transform_data(data)
    analyze_data(data)
    save_data(data)

<span class="hljs-keyword">def</span> <span class="hljs-title function_">preprocess_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据预处理逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">transform_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据转换逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">analyze_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据分析逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">save_data</span>(<span class="hljs-params">data</span>):
    <span class="hljs-comment"># 数据保存逻辑...</span>
</code></pre>
<ol start="5">
<li>函数的注释和文档：为函数提供清晰的注释和文档，可以帮助其他开发人员理解函数的用途、输入和输出。注释应该简洁明了，解释函数的功能和关键步骤，而文档可以提供更详细的说明和示例。</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">def</span> <span class="hljs-title function_">calculate_sum</span>(<span class="hljs-params">num1, num2</span>):
    <span class="hljs-string">&quot;&quot;&quot;
    计算两个数的和

    Args:
        num1 (int): 第一个数
        num2 (int): 第二个数

    Returns:
        int: 两个数的和
    &quot;&quot;&quot;</span>
    <span class="hljs-keyword">return</span> num1 + num2
</code></pre>
<h1>四、注释</h1>
<p>注释是用来解释代码意图、提供额外信息并帮助理解代码的文字说明，注释的存在不仅仅是帮助别人理解你的代码，也是帮助未来的自己在维护时能够想起代码是干什么的（对于某些自己写的远古项目，如果你没有注释，那么那份代码可能只有上帝知道是干什么的）：</p>
<ol>
<li>
<p>注释的作用和使用场景：注释的主要作用是解释代码的意图、提供上下文信息以及帮助其他开发人员理解代码。注释的使用场景包括：</p>
<ul>
<li>解释代码的目的和功能。</li>
<li>提供算法或逻辑的解释。</li>
<li>强调特殊的注意事项或约束条件。</li>
<li>提供对代码的修改或优化的说明。</li>
<li>文档函数、类或模块的用途和使用方法。</li>
</ul>
<p>注释应该在需要解释的地方使用，并且应该与代码保持同步，以确保注释的准确性和有效性。</p>
</li>
<li>
<p>注释应该解释代码的意图而非实现细节：注释应该关注代码的意图和目的，而不是详细描述代码的实现细节。代码本身应该尽可能自解释，而注释则应该解释代码为什么要这样做，以及代码的目的和预期结果。</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：注释过多的实现细节</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">calculate_sum</span>(<span class="hljs-params">num1, num2</span>):
    <span class="hljs-comment"># 将num1和num2相加</span>
    result = num1 + num2
    <span class="hljs-keyword">return</span> result
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：注释解释代码的意图</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">calculate_sum</span>(<span class="hljs-params">num1, num2</span>):
    <span class="hljs-comment"># 计算两个数的和</span>
    result = num1 + num2
    <span class="hljs-keyword">return</span> result
</code></pre>
</li>
<li>
<p>避免无用或误导性的注释：注释应该是有用的，不应该存在无意义或误导性的注释。避免在代码中添加显而易见的注释，注释应该提供额外的信息，帮助理解代码。</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：无用的注释</span>
result = num1 + num2  <span class="hljs-comment"># 计算结果</span>
</code></pre>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 好的示例：提供额外信息的注释</span>
<span class="hljs-comment"># 将num1和num2相加得到结果</span>
result = num1 + num2
</code></pre>
</li>
<li>
<p>注释的更新和维护：注释是与代码同步的重要部分，应该随着代码的修改和演进进行更新和维护。当代码发生变化时，相应的注释也需要相应地更新，以保持注释与代码的一致性。</p>
<p>注释应该被视为代码的一部分，同样受到代码审查和维护的过程。确保注释的准确性和完整性，以便其他开发人员能够依赖和理解注释提供的信息。</p>
</li>
</ol>
<h1>五、格式化</h1>
<p>代码的格式化对于代码的可读性和可维护性非常重要，如果你的代码没有合理的格式化，那么非常容易让你的代码看起来像一大坨💩。<br>
通过合理地使用缩进、空格、折行和空行，我们可以使代码更易读、易于理解和维护。保持一致的格式化风格，并遵守通用的格式化准则，有助于提高代码的可读性和一致性：</p>
<ol>
<li>
<p>代码的缩进和对齐：使用一致的缩进风格来展示代码的结构和层次关系。常见的缩进方式是使用四个空格或者一个制表符（Tab）。对于每个代码块，都应该有相同的缩进级别，以便于阅读和理解代码的逻辑。</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：缩进不一致</span>
<span class="hljs-keyword">if</span> condition:
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Condition is true!&quot;</span>)
   <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Indented inside if block&quot;</span>)

<span class="hljs-comment"># 好的示例：一致的缩进</span>
<span class="hljs-keyword">if</span> condition:
    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Condition is true!&quot;</span>)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Indented inside if block&quot;</span>)
</code></pre>
</li>
<li>
<p>空格的使用：在代码中适当地使用空格可以提高代码的可读性。以下是一些常见的空格使用场景：</p>
<ul>
<li>在运算符两侧和逗号后面添加空格，以增加可读性。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：缺少空格</span>
result=a+b

<span class="hljs-comment"># 好的示例：添加空格</span>
result = a + b
</code></pre>
<ul>
<li>在函数的参数列表和元素列表中，逗号后面添加一个空格。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：缺少空格</span>
my_function(arg1,arg2,arg3)

<span class="hljs-comment"># 好的示例：添加空格</span>
my_function(arg1, arg2, arg3)
</code></pre>
<ul>
<li>在注释中，运算符和关键字后面添加一个空格。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：缺少空格</span>
<span class="hljs-comment">#计算两个数的和</span>
result = num1+num2

<span class="hljs-comment"># 好的示例：添加空格</span>
<span class="hljs-comment"># 计算两个数的和</span>
result = num1 + num2
</code></pre>
</li>
<li>
<p>行的长度和折行规则：为了保持代码的可读性，应该遵守适当的行长度限制。通常，建议将每行代码限制在80个字符以内。如果一行代码超过了限制，可以使用括号、反斜杠或换行符进行折行。</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 不好的示例：过长的行</span>
long_line = <span class="hljs-string">&quot;This is a very long line of code that exceeds the recommended line length and makes it hard to read and understand.&quot;</span>

<span class="hljs-comment"># 好的示例：使用括号进行折行</span>
long_line = (<span class="hljs-string">&quot;This is a very long line of code that exceeds the recommended line length &quot;</span>
             <span class="hljs-string">&quot;and makes it hard to read and understand.&quot;</span>)

<span class="hljs-comment"># 好的示例：使用反斜杠进行折行</span>
long_line = <span class="hljs-string">&quot;This is a very long line of code that exceeds the recommended line length &quot;</span> \
            <span class="hljs-string">&quot;and makes it hard to read and understand.&quot;</span>

<span class="hljs-comment"># 好的示例：使用换行符进行折行</span>
long_line = <span class="hljs-string">&quot;This is a very long line of code that exceeds the recommended line length\n&quot;</span> \
            <span class="hljs-string">&quot;and makes it hard to read and understand.&quot;</span>
</code></pre>
</li>
<li>
<p>空行的使用：适当地使用空行可以增加代码的可读性，将代码分成逻辑块和功能块。以下是一些常见的空行使用场景：</p>
</li>
</ol>
<ul>
<li>在函数和类之间使用空行，以及在函数内部的逻辑块之间使用空行，以增加代码的可读性。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">def</span> <span class="hljs-title function_">function1</span>():
    <span class="hljs-comment"># 代码逻辑...</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">function2</span>():
    <span class="hljs-comment"># 代码逻辑...</span>

<span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span>:
    <span class="hljs-comment"># 类定义...</span>

function1()

function2()
</code></pre>
<ul>
<li>在函数和类的定义之前使用空行，以增加代码的可读性。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> module1

<span class="hljs-keyword">import</span> module2


<span class="hljs-keyword">def</span> <span class="hljs-title function_">my_function</span>():
    <span class="hljs-comment"># 代码逻辑...</span>


<span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span>:
    <span class="hljs-comment"># 类定义...</span>
</code></pre>
<ul>
<li>在代码的不同部分之间使用空行，以增加代码的可读性和分组。</li>
</ul>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-keyword">import</span> module1

<span class="hljs-keyword">import</span> module2


<span class="hljs-keyword">def</span> <span class="hljs-title function_">function1</span>():
    <span class="hljs-comment"># 代码逻辑...</span>


<span class="hljs-keyword">def</span> <span class="hljs-title function_">function2</span>():
    <span class="hljs-comment"># 代码逻辑...</span>
</code></pre>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[快速搭建部署自己的ChatGPT]]></title>
            <link>https://umb.ink/post/18</link>
            <guid>https://umb.ink/post/18</guid>
            <pubDate>Thu, 30 Mar 2023 14:22:51 GMT</pubDate>
            <description><![CDATA[<p>ChatGPT好使是有目共睹的，<s>特别对于我这种懒狗</s>，但是相对的ChatGPT受限于某些神秘力量，导致在使用它的时候比较麻烦，而OpenAI官方为了解决这些问题给出了可以快速集成到个人应用中的APPKEY，因此出现了一些可以一站式解决的傻瓜式ChatGPT部署方案。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>ChatGPT好使是有目共睹的，<s>特别对于我这种懒狗</s>，但是相对的ChatGPT受限于某些神秘力量，导致在使用它的时候比较麻烦，而OpenAI官方为了解决这些问题给出了可以快速集成到个人应用中的APPKEY，因此出现了一些可以一站式解决的傻瓜式ChatGPT部署方案。</p>
<!-- more -->
<p>我这里采用<a href="https://github.com/mckaywrigley/chatbot-ui">ChatBot-WebUI</a>这个开源库来快速部署</p>
<h2>前期准备</h2>
<p>你需要一个APP_KEY,可以从<a href="https://platform.openai.com/account/api-keys">这里获取</a>，当前前提是你拥有一个可以正常使用的ChatGPT账号。<br>
如果你不懂得什么叫<s>接码平台</s>，也不懂得什么叫<s>互联网虚拟混砖逾越技术</s>，可以找你认识的，有chatGPT账号的朋友Py一个Key</p>
<h2>开始</h2>
<ol>
<li>Fork chatbot-ui仓库到自己仓库</li>
<li>访问https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmckaywrigley%2Fchatbot-ui  使用Vercel快速部署chatbot-ui（其实官方都给弄好了，剩下的自己搞就完了）按它的一路点点点就行</li>
<li>完成以后是这样的：</li>
</ol>
<p><img src="https://umb.ink/static/img/74890d1bce066a510858b8d100a13c63.image.png" alt="image.png"><br>
6. 这时可以尝试访问它给到你的DOMAINS里的链接，同样的如果你有自己的域名，可以添加DNS记录来快速访问它（不要忘记去CF上给自己的域名加记录）：</p>
<p><img src="https://umb.ink/static/img/425f285f9ac5bb8ebd3672817ba2f64d.image.png" alt="image.png"><br>
7. 部署完成以后访问，你会看到如下页面：</p>
<p><img src="https://umb.ink/static/img/928f69dc7721358cc71fd277c4aa0053.image.png" alt="image.png"><br>
8. 将你的APPKEY粘贴到左下角的OpenAI API 密钥中：</p>
<p><img src="https://umb.ink/static/img/088825c261db0c56ac1754cd470e5473.image.png" alt="image.png"></p>
<ol start="9">
<li>开始你的ChatGPT对话就可以了</li>
<li>PS如果希望持久化APPKEY，让别人一打开就可以用的话，你需要向仓库里添加一份.env.local配置文件，参考官方的README</li>
</ol>
<p><img src="https://umb.ink/static/img/d29b3aa6b51e57f2cf8fea7d4295d099.image.png" alt="image.png"><br>
添加之后重新让Vercel构建一下，再部署，就可以啦</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[Spring-Jar包逆向更改内容]]></title>
            <link>https://umb.ink/post/15</link>
            <guid>https://umb.ink/post/15</guid>
            <pubDate>Fri, 04 Nov 2022 13:04:40 GMT</pubDate>
            <description><![CDATA[<p>最近维护几个老旧的服务（shi山），并且源代码已经莫得了，只有生产环境的几个jar包，但是要对里面的一些配置进行一些更改，因此尝试做了一下简单的jar解包</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>最近维护几个老旧的服务（shi山），并且源代码已经莫得了，只有生产环境的几个jar包，但是要对里面的一些配置进行一些更改，因此尝试做了一下简单的jar解包</p>
<!-- more -->
<h1>环境</h1>
<p>MacOS &amp;&amp; Linux均可</p>
<h1>解包</h1>
<p>使用指令</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>tar -zxvf &lt;jar-name&gt;.jar -C &lt;Dir&gt;
</code></pre>
<p>参数说明：<br>
jar-name: 需要解包的目标jar<br>
Dir：解包到对应的文件夹下，如果想要解包到当前目录下，则不需要加-C，像这样</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>tar -zxvf &lt;jar-name&gt;.jar
</code></pre>
<h1>逆向.class文件</h1>
<p>解包后的目录大概是这样的：</p>
<p><img src="https://umb.ink/static/img/680d4066f6d6bdaf22d3aa87f9df8911.%E6%88%AA%E5%B1%8F2022-11-04%2020.05.36.png" alt="截屏2022-11-04 20.05.36.png"><br>
.class文件都在BOOT-INF下</p>
<p><img src="https://umb.ink/static/img/3a6b5f9c59212e28703e0958800cc83b.%E6%88%AA%E5%B1%8F2022-11-04%2020.06.44.png" alt="截屏2022-11-04 20.06.44.png"><br>
lib文件夹是程序依赖的lib库<br>
classes文件夹就是包括了所有的.class文件<br>
.class文件是Java的二进制支持文件，所以我们需要进行逆向还原出Java源文件<br>
办法很简单，直接使用IDEA打开该目录，IDEA内置.class文件自动逆向还原，只可惜权限是read only<br>
像这样：<br>
<img src="https://umb.ink/static/img/94784c17a724fcfe723abaee20842ea4.image.png" alt="image.png"></p>
<p><img src="https://umb.ink/static/img/6d3ee6785f4a25247c214ec53f6a7f99.image.png" alt="image.png"><br>
这样的话就可以查看到逆向后的.class文件源码了（甚至是kt代码）</p>
<h1>替换</h1>
<p>如果需要修改的文件或者配置，直接cv逆向后的文件代码（虽然它是read only，但是可以复制），复制到任意一个Java文件中，然后用java -c 进行编译打包为.class文件，然后把原来BOOT-INF下的对应.class文件直接进行替换即可</p>
<h1>重新打包</h1>
<p>替换完成后需要重新把这些文件打包回jar包，使用下面的指令（在解包目录下）：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code> jar -cvf0m &lt;jar-name&gt;.jar   ./META-INF/MANIFEST.MF  .  
</code></pre>
<p>指定打包后的jar-name，然后运行指令，就会在当前目录下生成重新打包后的jar文件<br>
到此我们的jar逆向解包、更新文件、重新打包就完成了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[使用Nginx反向代理多个服务]]></title>
            <link>https://umb.ink/post/14</link>
            <guid>https://umb.ink/post/14</guid>
            <pubDate>Thu, 07 Mar 2024 05:16:54 GMT</pubDate>
            <description><![CDATA[<h1>起因</h1>
<ul>
<li>由于服务器到期的原因，最近对Blog做了一下迁移，原本计划由腾讯云迁移到阿里云，但是发现那样的话还必须把备案也迁移过去，实在是太麻烦了，于是考虑把博客迁移到我个人维护的课表小程序的云服务器上，但是与此同时问题也出现了。。。。</li>
</ul>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>起因</h1>
<ul>
<li>由于服务器到期的原因，最近对Blog做了一下迁移，原本计划由腾讯云迁移到阿里云，但是发现那样的话还必须把备案也迁移过去，实在是太麻烦了，于是考虑把博客迁移到我个人维护的课表小程序的云服务器上，但是与此同时问题也出现了。。。。</li>
</ul>
<!-- more -->
<h1>问题</h1>
<ul>
<li>服务器上面有很多服务，比如课表小程序、机器人还有自己写的一些小service，所以当时采用了Nginx做端口代理分发，监听了Https的443和Http 80这两个端口，但是我的博客启动也需要监听443和80，这就导致了端口冲突，且我课表小程序的域名与我博客的域名完全不同，需要根据域名去做端口反向代理</li>
<li>另外由于服务比较多，而且比较庞杂（我也想过迁移服务，但是没钱买服务器啊Orz），因此需要尽可能避免修改其他服务的配置，在保持现有配置的情况下加入博客的反向代理配置</li>
<li>由于Nginx原有的证书与博客域名不匹配，因此需要单独为博客配置一下证书</li>
</ul>
<h1>解决方案</h1>
<p>Nginx有一种叫做vHost的配置，可以将多个sever分在多个conf里单独配置，使用vhost可以解决我们的sever过多配置庞杂的问题</p>
<h2>1. 创建一个conf.d的目录，在nginx.conf中添加如下配置：</h2>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-attribute">include</span> /etc/nginx/conf.d/<span class="hljs-regexp">*.conf</span>;
</code></pre>
<h2>2. 修改Blog端口以及配置vhost</h2>
<p>新建一个Blog.conf在conf.d目录下,配置blog的vhost文件</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-section">server</span> {
  <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
  <span class="hljs-comment"># 改为你的网址</span>
  <span class="hljs-attribute">server_name</span> com.xxx;
  <span class="hljs-comment"># 重定向为 https</span>
  <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://xxx.xxx;

}
<span class="hljs-section">server</span> {
  <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;
  <span class="hljs-comment"># 改为你的网址</span>
  <span class="hljs-attribute">server_name</span> umb.ink;
  <span class="hljs-comment"># HTTPS的证书的公私钥</span>
  <span class="hljs-attribute">ssl_certificate</span> xxxx/xxx/xxx;
  <span class="hljs-attribute">ssl_certificate_key</span> xxx/xxx/xxx;

  <span class="hljs-section">location</span> / {
    <span class="hljs-comment"># 改为容器的 PORT,也就是目标sever的端口</span>
    <span class="hljs-attribute">proxy_pass</span> http://localhost:79;
    <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
    <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;
  }

}
</code></pre>
<p>其他的sever也可以像这样在conf.d目录下新建xxx.conf然后进行配置</p>
<h2>3. nginx reload</h2>
<pre><code>nginx -s reload
</code></pre>
<p>之后就可以尝试访问目标sever，访问成功的话就说明配置成功了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[build react-native android]]></title>
            <link>https://umb.ink/post/1</link>
            <guid>https://umb.ink/post/1</guid>
            <pubDate>Tue, 18 Oct 2022 11:52:13 GMT</pubDate>
            <description><![CDATA[<p>最近试着build了一个react native的android项目，血压嗯高，踩了一堆一堆的坑，<s>得出结论，node_moudles就是一坨💩</s></p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>最近试着build了一个react native的android项目，血压嗯高，踩了一堆一堆的坑，<s>得出结论，node_moudles就是一坨💩</s></p>
<!-- more -->
<h1>环境配置</h1>
<p>react native是依赖于node构建的跨平台移动端框架，<s>本质上感觉就是一个web</s>，所以它也有node的通病，屎一样的node_moudles。。。。我参考了这几篇教程配置的环境，直接贴在下面我就不单独写了（毕竟人家的肯定比我专业一点），按教程来一点点安装即可<br>
<a href="https://www.reactnative.cn/">官方中文文档</a><br>
<a href="https://zhuanlan.zhihu.com/p/213878203">知乎大神教程</a></p>
<h1>踩坑</h1>
<h2>1. node_moudles 库依赖版本冲突,以及各种乱七八糟的问题</h2>
<p>node_moudles内部某些库的依赖冲突的时候会报错，很难解决，，，最好的解决办法是换<a href="https://www.yarnpkg.cn/">yarn</a>去自动处理这些问题<br>
更新：之后又遇到了各种个样的问题，最简单粗暴且有效的办法：</p>
<pre><code>rm -rf node_moudles/
yarn install
</code></pre>
<p>重装node_moudles解决90%的问题（</p>
<h2>2. macos bash_profile重启terminal不生效的问题</h2>
<p>因为build react native 是在terminal环境下的，所以需要配置bash_profile环境变量文件，但是每次启动terminal都需要source ./bash_profile，关闭terminal以后就会失效，所以就很难受<br>
解决办法：<br>
在bash_profile目录下，编写zshrc文件,内容如下</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>if [ -f ~/.bash_profile ]; then
   source ~/.bash_profile
fi
~      
</code></pre>
<p>这些脚本的意义在于，每次启动terminal的时候会自动装载路径下的bash_profile文件，就不需要手动配置环境变量了<br>
保存脚本后运行：</p>
<pre><code>source ~./.zshrc
</code></pre>
<h2>3. npm timeout</h2>
<p>老生常谈的问题了，，，，换镜像并改用<a href="https://www.yarnpkg.cn/">yarn</a>就好</p>
<h2>4. gradle与java版本不适配</h2>
<p>不得不说，gradle的版本兼容问题一直是历史大坑，当出现这样的报错时，就大概是Java版本和gradle不兼容的问题了：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>BUG! exception in phase &#x27;semantic analysis&#x27; in source unit &#x27;_BuildScript_&#x27; Unsupported class file major version
</code></pre>
<p>这个时候就要看看gradle版本对应哪个Java版本，我因为本地电脑系统里的Java版本是18、13、8，而项目gradle要11.。。。所以找了大半天切换一下Java版本为11就解决了这个问题，然后yarn react-native run-android就可以正常build了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[Build WireGuard- Android]]></title>
            <link>https://umb.ink/post/2</link>
            <guid>https://umb.ink/post/2</guid>
            <pubDate>Mon, 10 Oct 2022 02:21:43 GMT</pubDate>
            <description><![CDATA[<h1>起源</h1>
<p>最近尝试手动build <a href="https://github.com/WireGuard/wireguard-android">wireguard- android</a>,踩了不少坑，大概总结一下</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>起源</h1>
<p>最近尝试手动build <a href="https://github.com/WireGuard/wireguard-android">wireguard- android</a>,踩了不少坑，大概总结一下</p>
<!-- more -->
<h1>#0 环境问题</h1>
<p>wireguard- android采用c++、go、java和kotlin进行开发，所以需要makefile和gomod的支持，这就要求我们系统中有这些环境（<s>但是仓库的readme完全没说，屑中屑）</s>，因此首先如果要成功build wireguard，就需要先装好环境</p>
<h2>1. NDK支持</h2>
<p>NDK是Android中帮助我们与c/c++动态链接库进行交互的工具包，默认Android是不会单独为项目导入的，所以需要手动去引入NDK,NDK内置了makefile编译所需要的东西，不然的话无法正常编译makefile</p>
<h2>2. sha256sum</h2>
<p>wireguard在build时会有一个校验文件的过程，也就是distributionSha256Sum，它会调用sha256sum这条指令进行校验<br>
这是一个Linux下用来校验文件是否合法的指令，但是macOS环境下是没有这个指令的，所以需要手动安装它的工具包</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>brew install coreuntils
</code></pre>
<h2>3. go mod proxy</h2>
<p>wireguard还需要对go进行编译，因此也需要go的环境支持（<s>可是它readme还是没有写</s>）<br>
在mac下可以直接使用brew安装go环境</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>brew install go
</code></pre>
<p>但是由于国内网络环境问题，在build中可能会出现dial tcp xxx.xxx.xxx.xxx timeout或者connect refused的报错，比如我遇到:</p>
<pre><code>dial tcp 142.251.42.241:443: connect: connection refused
</code></pre>
<p>这种报错是由于go env缺乏国内代理导致的，更换proxy代理即可，在terminal中运行下面的指令即可</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>go env -w GOPROXY=https://goproxy.cn
</code></pre>
<h2>4. 特殊：flock(1)-[MACOS]</h2>
<p>这个东西其他系统是不需要的，但是macos需要单独安装这个环境，官方readme有提示macOS users may need flock(1).<br>
同样的，直接用brew装就行了</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>brew install flock
</code></pre>
<p>注意：<strong>brew国内镜像资源站是没有flock（1）的资源的，如果你配了中科大、阿里的brew源，就可能报错fatil，所以最好在安装前更换回brew的默认源</strong></p>
<h1>#1 Android Studio问题</h1>
<p>好容易配了这么多环境，应该可以开始build了，然后Android Studio报错了：</p>
<p><img src="https://umb.ink/static/img/126bd59a4c270e2060bc2969a8f37dd6.76F68C44-0183-4031-B109-767749FCF474.png" alt="76F68C44-0183-4031-B109-767749FCF474.png"><br>
这个是直接读报错信息即可，因为AndroidStudio要求避免使用distributionSha256Sum，这样可能会导致AndroidStudio的crash（真屑啊），最好的也是最直接的解决办法就是。。。在gradle-wrapper.properties文件中把这一行属性注释掉。。。</p>
<h1>#最后</h1>
<p>配好了一堆环境，解决完Androidstuio的问题，这时候再点击build，就可以build success了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[图片转线稿算法]]></title>
            <link>https://umb.ink/post/3</link>
            <guid>https://umb.ink/post/3</guid>
            <pubDate>Sat, 08 Oct 2022 15:05:12 GMT</pubDate>
            <description><![CDATA[<p>一种提取图片边缘的算法</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>一种提取图片边缘的算法</p>
<!-- more -->
<h1>起源</h1>
<p>突发奇想利用边缘提取做了一个图片转线稿的小程序</p>
<p>先来看一下效果：</p>
<img src="https://i.loli.net/2021/10/11/e9G2JMzf1ilEcKB.png" style="zoom: 50%;" />
<h1>环境</h1>
<p>Python 3.8 、PIL、numpy</p>
<p>直接使用pip install 安装即可</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>pip install pillow
pip install numpy
</code></pre>
<h1>思路</h1>
<p>众所周知，我们所看到的图片是一副二维图片，由一堆RGB色值各不相同的像素点构成。</p>
<p>那么我们可以将各个像素点的<strong>RGB色值转变为图片的第三维度</strong>，这样图片就由二维平面图变为了三维立体图。</p>
<p>此时我们将这张图片<strong>看作一个实实在在的三维物体</strong>，三维物体表面某点高度由对应二维平面的像素点的RGB色值决定，RGB色值有大有小，自然三维物体<strong>表面就会有高低起伏</strong>。</p>
<p>在某个高度，某个方向上<strong>添加一个点光源照射这个物体</strong>，在光的照射下，物体凹凸不平的表面自然而然就会出现阴影（RGB色值变化越大的地方表面越陡，阴影越深），再<strong>将这些阴影投影回二维平面</strong>，这样就得到了图片的边缘。</p>
<h1>实现</h1>
<ol>
<li>
<h4>将原图转为灰度图</h4>
<p>为了简化矩阵，提高运算速度，我们对图像进行灰度图转换</p>
</li>
<li>
<h4>使用高斯滤波进行降噪</h4>
<p>这个算法对噪点很敏感（你想想平地上突然隆起一个笔直的擎天柱是不是显得特别扎眼）去除噪点影响，方法为使用高斯滤波，使图像模糊，然后使用领域降噪对图像进行降噪处理</p>
</li>
<li>
<h4>计算图像梯度</h4>
<p>numpy自带矩阵梯度计算(np.gradient)，该梯度用来作为图像升维参考，梯度值越大，说明图像颜色在该点变化率越大，对应的虚拟深度应该也越大。</p>
</li>
<li>
<h4>赋予图像虚拟深度</h4>
<p>因为图像中的RGB值有大有小，直接作为三维高度并不合适，需要经过一些权衡计算来将其转变为三维高度，我们这里结合图像梯度和颜色向量来计算，并预设一个放大深度值，用来放大深度效果。</p>
</li>
<li>
<h4>光源设置</h4>
<p>这个需要进行调参了，光源的位置是一个很玄学的问题，位置不同，最后的效果也完全不一样。</p>
<p>个人觉得光源的最佳位置是与平面俯视角（即z轴与平面的夹角）呈 α =（pi / 2.2 ）°，平面方向角（即x与y的夹角呈β =（pi / 4 ）°，这个角度下提取到的线稿最为清晰。</p>
</li>
<li>
<h4>三维梯度转二维灰度</h4>
<p>最后我们需要的是二维矩阵而不是三维矩阵，因此分别求平面各点在xy平面、xz平面、yz平面的投影</p>
<p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msub><mi>D</mi><mrow><mi>x</mi></mrow></msub><mo separator="true">,</mo><msub><mi>D</mi><mrow><mi>y</mi></mrow></msub><mo separator="true">,</mo><msub><mi>D</mi><mrow><mi>z</mi></mrow></msub></mrow><annotation encoding="application/x-tex">D_{x},D_{y},D_{z}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="strut" style="height:0.68333em;"></span><span class="strut bottom" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="base displaystyle textstyle uncramped"><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit">x</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mpunct">,</span><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit" style="margin-right:0.03588em;">y</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mpunct">,</span><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit" style="margin-right:0.04398em;">z</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span></span></span></span></span></p>
<p>将三个方向的投影的值相加的和与原色相乘，也就是（灰度化）</p>
<p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mo>(</mo><msub><mi>D</mi><mrow><mi>x</mi></mrow></msub><mo>+</mo><msub><mi>D</mi><mrow><mi>y</mi></mrow></msub><mo>+</mo><msub><mi>D</mi><mrow><mi>z</mi></mrow></msub><mo>)</mo><mo>∗</mo><mn>2</mn><mn>5</mn><mn>5</mn></mrow><annotation encoding="application/x-tex">(D_{x}+D_{y}+D_{z})*255
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="strut" style="height:0.75em;"></span><span class="strut bottom" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="base displaystyle textstyle uncramped"><span class="mopen">(</span><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit">x</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mbin">+</span><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit" style="margin-right:0.03588em;">y</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mbin">+</span><span class="mord"><span class="mord mathit" style="margin-right:0.02778em;">D</span><span class="vlist"><span style="top:0.15em;margin-right:0.05em;margin-left:-0.02778em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle cramped"><span class="mord scriptstyle cramped"><span class="mord mathit" style="margin-right:0.04398em;">z</span></span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mclose">)</span><span class="mbin">∗</span><span class="mord mathrm">2</span><span class="mord mathrm">5</span><span class="mord mathrm">5</span></span></span></span></span></p>
</li>
<li>
<h4>极值处理</h4>
<p>将处理后的图像进行极值处理，RGB值靠近黑色（0）的归为黑色，靠近白色（255）的归为白色，突出边缘。</p>
</li>
<li>
<h4>锐化处理</h4>
<p>为了让我们最后的线稿更加清晰，可以对处理后的图片进行一定程度的锐化操作</p>
<h1>代码</h1>
</li>
</ol>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code><span class="hljs-comment"># 领域降噪</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">calculate_noise_count</span>(<span class="hljs-params">img_obj, w, h, width, height</span>):
    count = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> _w_ <span class="hljs-keyword">in</span> [w - <span class="hljs-number">1</span>, w, w + <span class="hljs-number">1</span>]:
        <span class="hljs-keyword">for</span> _h_ <span class="hljs-keyword">in</span> [h - <span class="hljs-number">1</span>, h, h + <span class="hljs-number">1</span>]:
            <span class="hljs-keyword">if</span> _w_ &gt; width - <span class="hljs-number">1</span>:
                <span class="hljs-keyword">continue</span>
            <span class="hljs-keyword">if</span> _h_ &gt; height - <span class="hljs-number">1</span>:
                <span class="hljs-keyword">continue</span>
            <span class="hljs-keyword">if</span> _w_ == w <span class="hljs-keyword">and</span> _h_ == h:
                <span class="hljs-keyword">continue</span>
            <span class="hljs-keyword">if</span> img_obj[_w_, _h_] &lt; <span class="hljs-number">230</span>:  <span class="hljs-comment"># 这里因为是灰度图像，设置小于230为非白色</span>
                count += <span class="hljs-number">1</span>
    <span class="hljs-keyword">return</span> count


<span class="hljs-comment"># 高斯滤波+锐化+领域降噪的线稿提取方案</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">img_ege_get</span>(<span class="hljs-params">byte: <span class="hljs-built_in">bytes</span></span>):
    time = datetime.now()
    byte_stream = io.BytesIO(byte)
    im1 = Image.<span class="hljs-built_in">open</span>(byte_stream).convert(<span class="hljs-string">&#x27;L&#x27;</span>) <span class="hljs-comment"># 灰度图</span>
    im = im1.<span class="hljs-built_in">filter</span>(ImageFilter.GaussianBlur(radius=<span class="hljs-number">0.75</span>)) <span class="hljs-comment"># 高斯模糊 75%</span>

    a = np.asarray(im).astype(<span class="hljs-string">&#x27;float&#x27;</span>)

    depth = <span class="hljs-number">10.</span> <span class="hljs-comment"># 设定虚拟深度</span>
    grad = np.gradient(a)
    grad_x, grad_y = grad
    grad_x = grad_x * depth / <span class="hljs-number">100.</span>
    grad_y = grad_y * depth / <span class="hljs-number">100.</span>
    <span class="hljs-comment"># 梯度向量计算</span>
    A = np.sqrt(grad_x ** <span class="hljs-number">2</span> + grad_y ** <span class="hljs-number">2</span> + <span class="hljs-number">1.</span>)
    uni_x = grad_x / A
    uni_y = grad_y / A
    uni_z = <span class="hljs-number">1.</span> / A
    vec_el = np.pi / <span class="hljs-number">2.2</span>
    vec_az = np.pi / <span class="hljs-number">4.</span>
    dx = np.cos(vec_el) * np.cos(vec_az)
    dy = np.cos(vec_el) * np.sin(vec_az)
    dz = np.sin(vec_el)

    b = <span class="hljs-number">255</span> * (dx * uni_x + dy * uni_y + dz * uni_z)
    b = b.clip(<span class="hljs-number">0</span>, <span class="hljs-number">255</span>)  <span class="hljs-comment"># 二值化处理，要么为0，（黑色边缘）要么为255（白色背景）</span>
    im2 = Image.fromarray(b.astype(<span class="hljs-string">&#x27;uint8&#x27;</span>))
    im2 = im2.<span class="hljs-built_in">filter</span>(ImageFilter.SHARPEN)
    weight, height = im2.size
    <span class="hljs-comment"># 降噪</span>
    pim = im2.load()
    <span class="hljs-built_in">map</span>(row_noise, [(pim, height, weight, w,) <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(weight)])
    imgByteArray = io.BytesIO()
    im2.save(imgByteArray, <span class="hljs-built_in">format</span>=<span class="hljs-string">&#x27;png&#x27;</span>)
    imgByteArray = imgByteArray.getvalue()
    <span class="hljs-built_in">print</span>(datetime.now() - time)
    <span class="hljs-keyword">return</span> imgByteArray

<span class="hljs-comment"># 领域降噪</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">row_noise</span>(<span class="hljs-params">pim, height, weight, w</span>):
    <span class="hljs-keyword">for</span> h <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(height):
        <span class="hljs-keyword">if</span> calculate_noise_count(pim, w, h, weight, height) &lt; <span class="hljs-number">4</span>:
            pim[w, h] = <span class="hljs-number">255</span>
</code></pre>
<h1><a href="https://github.com/farewell12345/FAQ-Bot-QQ/tree/master/src/main/Python">源码地址</a></h1>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招] 一嗨租车 客户端开发岗 一面+二面+HR面]]></title>
            <link>https://umb.ink/post/4</link>
            <guid>https://umb.ink/post/4</guid>
            <pubDate>Tue, 20 Sep 2022 07:42:54 GMT</pubDate>
            <description><![CDATA[<p>一嗨租车面试比较突然，也没有约面，直接电话打来开始面试，有点猝不及防，这点上体验不太好，但是另一方面来说效率真的还蛮高，两轮技术面，一轮HR面，仅仅用时两天就确认通过面试聊offer了</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>一嗨租车面试比较突然，也没有约面，直接电话打来开始面试，有点猝不及防，这点上体验不太好，但是另一方面来说效率真的还蛮高，两轮技术面，一轮HR面，仅仅用时两天就确认通过面试聊offer了</p>
<!-- more -->
<h1>一面</h1>
<ol>
<li>自我介绍</li>
<li>Activity启动模式</li>
<li>Java 锁</li>
<li>高并发集合（HashMap、List）</li>
<li>RxJava有没有了解</li>
<li>Android事件分发流程</li>
<li>handler组成</li>
<li>线程池</li>
<li>反问</li>
</ol>
<p>一面总共也就十来分钟，特别快，问完就撤，面试官来也匆匆去也匆匆的感觉。。。</p>
<h1>二面</h1>
<p>一面过了几个小时二面的电话就来了，，，和一面差不多，也是十几分钟就结束了</p>
<ol>
<li>自我介绍</li>
<li>Service的理解</li>
<li>之前实习过程中所学到的东西</li>
<li>有过了解Flutter吗？</li>
<li>Set和List的区别</li>
<li>线程池有没有了解</li>
<li>反问</li>
</ol>
<h1>HR面</h1>
<ol>
<li>未来规划</li>
<li>对岗位是否感兴趣？</li>
<li>在实验室主要负责什么？</li>
<li>如何开展培训？</li>
<li>解答疑问，聊入职相关</li>
</ol>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招]比心客户端开发岗（需实习转正）一面+二面+HR]]></title>
            <link>https://umb.ink/post/5</link>
            <guid>https://umb.ink/post/5</guid>
            <pubDate>Sat, 15 Oct 2022 12:51:55 GMT</pubDate>
            <description><![CDATA[<p>今天（9.14）比心HR突然打电话约面，介于明天后天都有面试和笔试，就约在了今天晚上面试<br>
更新，一面通过，下周二面</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>今天（9.14）比心HR突然打电话约面，介于明天后天都有面试和笔试，就约在了今天晚上面试<br>
更新，一面通过，下周二面</p>
<!-- more -->
<h1>一面</h1>
<ol>
<li>自我介绍</li>
<li>Android启动模式</li>
<li>Android生命周期</li>
<li>项目中如何实现网络请求</li>
<li>说一下Handler</li>
<li>说一下Java的锁</li>
<li>voilatile关键字修饰下，i++是否会产生线程冲突？</li>
<li>说一下在加类锁的情况下，外部访问static方法和类的对象实例访问会不会产生线程冲突？</li>
<li>有没有自定义View？自定义View有哪些方式？</li>
<li>说一下项目里遇到的难点？</li>
<li>线程池有了解吗？</li>
<li>如何在android里实现多个网络请求同步工作？</li>
<li>反问</li>
</ol>
<h1>二面</h1>
<p>时隔太久了，有很多记不太清楚了</p>
<ol>
<li>接触过哪些Android性能优化的方法？</li>
<li>平时如何学习？</li>
<li>说说项目</li>
</ol>
<h1>后续</h1>
<p>发了offer，但是因为要实习转正，并且在上海，所以只能遗憾地拒绝了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招]360 Android开发岗 一面]]></title>
            <link>https://umb.ink/post/6</link>
            <guid>https://umb.ink/post/6</guid>
            <pubDate>Sat, 15 Oct 2022 12:52:11 GMT</pubDate>
            <description><![CDATA[<p>9月6号完成了360一面，目前还在流程中，后续会更新</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>9月6号完成了360一面，目前还在流程中，后续会更新</p>
<!-- more -->
<h1>一面</h1>
<ol>
<li>自我介绍</li>
<li>Activity生命周期</li>
<li>View事件分发流程</li>
<li>线程和进程的区别</li>
<li>handler的组成</li>
<li>handler里一个handler可以有几个looper？</li>
<li>handler是如何实现线程切换的？</li>
<li>Android常见的内存泄漏</li>
<li>Android里的进程通信有哪些实现方式？</li>
<li>Android虚拟机和进程的区别？</li>
<li>如何实现自定义view？</li>
<li>Java锁的实现</li>
<li>Kotlin协程</li>
<li>问项目</li>
<li>算法题，线程交替打印</li>
<li>反问</li>
</ol>
<p>面试使用的是牛客，期间频繁掉线，导致面试的时候面试官一直在频繁重连，所以整体体验一般，但是面试官很有趣，留着长发，看着很和蔼，希望能有二面</p>
<h1>后续</h1>
<p>挂了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招]百度客户端广告SDK研发岗-一面+二面+三面+加面（已重新流转）]]></title>
            <link>https://umb.ink/post/7</link>
            <guid>https://umb.ink/post/7</guid>
            <pubDate>Fri, 09 Sep 2022 13:53:41 GMT</pubDate>
            <description><![CDATA[<p>七月初投递百度提前批，七月底完成了一面+二面+三面+加面</p>
<p>之前忙于实习，一直没更，凭借记忆尽量回想一些问题更新一下。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>七月初投递百度提前批，七月底完成了一面+二面+三面+加面</p>
<p>之前忙于实习，一直没更，凭借记忆尽量回想一些问题更新一下。</p>
<!-- more -->
<h1>一面</h1>
<ol>
<li>自我介绍</li>
<li>Android Service的启动方式？<br>
答：onStart、onBind</li>
<li>说一下MVC、MVP和MVVM的区别以及你对他们的理解？<br>
答：巴拉巴拉，，，</li>
<li>了解SharedPreferences吗？知道commit和apply的区别吗？<br>
答：同步和异步，一个是直接写硬盘一个先内存后异步写硬盘，一个有返回没返回</li>
<li>说一下Kotlin的高阶函数？<br>
答：基于函数式编程思想，本质上类似Java的lambda表达式，map，filter啦之类的，，，</li>
<li>说一下Kotlin的协程？它与线程有什么区别？<br>
答：协程原理，更适合轻量级的异步任务，程序层面的异步调度，，，，</li>
<li>了解Activity的启动模式吗？有哪几种？<br>
答：四种，巴拉巴拉，，，，</li>
<li>了解Handler吗？它是干什么的？它有哪些部分组成？</li>
<li>一个Handler里面的Looper最多有几个？</li>
<li>Android如果发生了内存泄漏，有哪些可能？<br>
答：Handler内部类，静态变量滥用，匿名内部类太多巴拉巴拉，，，</li>
<li>Android如果发生了ANR，有哪些可能？</li>
<li>介绍一下你的项目，其中有哪些难点？如何解决的？</li>
<li>反问</li>
</ol>
<h1>二面</h1>
<ol>
<li>自我介绍</li>
<li>了解Android广播机制吗？本地广播是什么？</li>
<li>如何优化一个ListView？<br>
答：contentView复用，使用ViewHolder对contentView进行处理</li>
<li>saveInstanceState有了解吗？<br>
答： 用在当Activity重新启动中用来保存状态并在重新OnCreate的时候恢复，存一个Bundle</li>
<li>Bundle是干什么的？<br>
答：可以保存Key-Value数据，一般主要用来在Activity中进行通信</li>
<li>Android权限你知道有哪些？如何申请？<br>
答：读写权限、定位权限、网络权限，，，巴拉巴拉</li>
<li>问项目</li>
<li>OKHttp了解吗？<br>
答：只知道它使用了责任链模式</li>
<li>责任链模式的优缺点？<br>
答：便于维护和拓展巴拉巴拉，，，</li>
<li>反问</li>
</ol>
<h1>三面</h1>
<p>三面是部门Leader，问的更加偏向于业务思维</p>
<ol>
<li>自我介绍</li>
<li>问项目</li>
<li>你认为什么样的广告是好的广告？（提示：可以从展示形式、持续时间来切入）<br>
答：巴拉巴拉</li>
<li>了解Fragment吗？它的生命周期能不能说一下？</li>
<li>你在学习和研发过程中有没有遇到过很棘手的问题，如何解决的？</li>
<li>了解过SDK开发吗？之前在那家公司实习主要做什么工作？</li>
<li>有使用过SDK吗？你认为SDK和库的区别是什么？</li>
<li>之前对百度有了解吗？有接触过相关的业务或者使用过相关的产品吗？</li>
<li>反问</li>
</ol>
<p>三面比较快，可能因为面试官有事情，前后只有半个小时不到，然后面试官跟我约了加面，让我简单找资料学一下SDK的设计原则相关的知识，他加面的时候会再问我</p>
<h1>加面</h1>
<p>加面比较轻松，基本就是面试官与我闲聊</p>
<ol>
<li>你来说说现在你所了解到的关于SDK的相关知识，如何设计研发一款SDK？</li>
<li>你是如何学习到这些知识的？</li>
<li>你对日后的规划是怎样的？</li>
<li>考虑来实习吗？</li>
<li>面试官对我的评价（很不错，希望把这个offer给我，但是要看人事那边的情况）</li>
<li>反问</li>
</ol>
<h1>后续</h1>
<p>隔了一个月后，百度提前批结束了，尝试联系面试官，面试官说我面试早就通过了，没有进入下一流程是因为一直需要等待提前批结束后进入排序，面试官说对我的打分并不低，但是由于学校的问题，今年人事卡双一流高校，卡的很严，会帮我准备相关的材料争取一下，但是建议我不要抱太大希望。</p>
<h3>9.9 简历被流转，重新进入初筛阶段，百度寄摆</h3>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招]大疆-客户端开发岗-一面（已挂）]]></title>
            <link>https://umb.ink/post/8</link>
            <guid>https://umb.ink/post/8</guid>
            <pubDate>Sat, 15 Oct 2022 12:54:15 GMT</pubDate>
            <description><![CDATA[<p>上上周大疆一面，整体面试体验还是不错的，无奈被挂</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>上上周大疆一面，整体面试体验还是不错的，无奈被挂</p>
<!-- more -->
<h1>一面</h1>
<ol>
<li>自我介绍</li>
<li>Java多线程和锁</li>
<li>介绍一下项目</li>
<li>项目里的难点，如何解决？</li>
<li>Kotlin的Let、Apply、Also关键字</li>
<li>Kotlin的扩展函数和高阶函数</li>
<li>Kotlin的单例是线程安全的吗？</li>
<li>如何设计一个线程安全的单例？</li>
<li>说一下你知道的设计模式</li>
<li>责任链模式的优缺点</li>
<li>说说如何理解MVC、MVP、MVVM？</li>
<li>Kotlin的ViewModel是如何实现数据监听的？</li>
<li>线程池有没有了解？</li>
<li>Activity四种启动模式</li>
<li>Activity的通信方式</li>
<li>Bundle原理有没有了解</li>
<li>本地广播和全局广播是什么？</li>
<li>Handler的组成和原理</li>
<li>Handler如何实现线程通信？</li>
<li>你认为Kotlin和Compose相比于传统Java有什么优势？</li>
<li>反问</li>
</ol>
<h1>结果</h1>
<p>一面面试官说面试表现比较好，但是最后需要看打分排序决定是否通过，然后一面大概一周后收到了感谢信，BE</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[[秋招]滴滴一面+二面 面经]]></title>
            <link>https://umb.ink/post/9</link>
            <guid>https://umb.ink/post/9</guid>
            <pubDate>Sat, 15 Oct 2022 12:53:42 GMT</pubDate>
            <description><![CDATA[<p>上周通过Boss直聘，以部门直推的方式约到了滴滴客户端地图定位SDK开发岗位面试<br>
一二面比较突然和急促，并且从晚上7点连续完成了一面和二面的流程，长达两个小时，问的比较多也比较累，这里简单总结一下</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>上周通过Boss直聘，以部门直推的方式约到了滴滴客户端地图定位SDK开发岗位面试<br>
一二面比较突然和急促，并且从晚上7点连续完成了一面和二面的流程，长达两个小时，问的比较多也比较累，这里简单总结一下</p>
<!-- more -->
<h1>一面</h1>
<blockquote>
<ol>
<li>自我介绍</li>
<li>面试官介绍了一下部门的业务和技术栈</li>
<li>问：进程和线程的区别？进程的生命周期有没有了解<br>
答：<em>运行层面不同、周期不同巴拉巴拉，，，，</em></li>
<li>问：面向对象里封装、继承和多态是如何理解的？在Java和C++里分别如何实现？<br>
答：<em>巴拉巴拉，，，，</em></li>
<li>问：C++虚函数有没有了解？<br>
答：<em>没有</em></li>
<li>问：操作系统里TCP三次握手的理解，为什么不能是两次握手或者四次握手？<br>
答：<em>TCP原理巴拉巴拉，，，</em></li>
<li>问：操作系统里如果两个进程需要实现通信，有哪几种方式？<br>
答：<em>（IPC管道、内存共享、文件共享）</em></li>
<li>问：程序从代码到运行结束输出结果的过程中，发生了什么？<br>
答：<em>（以Java为例，构建、编译字节码、转换机器码、运行）</em></li>
<li>问：C++编译的过程相比Java有什么区别？<br>
答：<em>（少了编译字节码的环节，速度更快巴拉巴拉，，，）</em></li>
<li>问：对多线程有没有了解，介绍一下Java里多线程的实现方式和几种加锁的关键字<br>
答：<em>（Thread、Runnable、线程池、类锁、对象锁、方法锁、同步代码块、Lock）</em></li>
<li>问：有四个线程ABCD，如何使用锁实现ABCD的执行顺序？假如让ABC同时执行，D最后执行，又如何实现？</li>
<li>问：Java的泛型和C++泛型的区别<br>
答：<em>（真泛型和假泛型）</em></li>
<li>问：对操作系统虚拟内存有了解吗？</li>
<li>问：说一下对于已经排序好的一个集合如何对其高效查找？<br>
答：（<em>二分</em>）</li>
<li>问：说一下对于斐波那契数列问题的两种解决算法？它们有什么特点，如何选择？<br>
答：<em>（递归，动态规划，巴拉巴拉，，，，）</em></li>
<li>问：说一下对于编码原则的理解<br>
答：<em>巴拉巴拉，，</em></li>
<li>问：思维题：五升的水杯和七升的水杯，如何操作可以得到四升的水？<br>
答：<em>只需要这样，再那样（挺经典的思维题，很简单），巴拉巴拉，，</em></li>
<li>问：你简历上所写的其中一个项目，在开发过程中遇到的难题以及如何解决的？</li>
<li>反馈：技术挺厉害的，答得都不错，稍等一会去叫二面面试官过来面Android相关</li>
</ol>
</blockquote>
<h1>二面（都比较简单，就不写答案了）</h1>
<ol>
<li>自我介绍</li>
<li>问：Activity生命周期？</li>
<li>问：Activity之间的通信如何实现？</li>
<li>问：Android Service如何理解？</li>
<li>问：handler原理？</li>
<li>问：handler的looper是否可以切换线程？</li>
<li>问：线程池有没有了解？</li>
<li>问：广播机制有没有了解？</li>
<li>问：View的事件分发机制</li>
<li>问：操作系统里进程通信方式？</li>
<li>问：Android内存泄漏</li>
<li>问：Android虚拟机和进程的关系</li>
<li>问：强引用、弱引用、软引用和虚引用</li>
<li>问：C++有多少了解？</li>
<li>问：有没有接触过定位相关的技术？</li>
<li>问：有没有使用过Android内置的一些定位API？</li>
<li>问：Android系统架构有哪些层？每层承载了哪些功能？</li>
<li>问：假如让你去学习定位领域的知识与技术，在现在一无所知的情况下，大概需要多久可以熟悉业务并上手？</li>
<li>反问</li>
</ol>
<h1>结果</h1>
<p>感觉整体问的比较底层和基础，更偏向于问基础知识和C++方面的知识，整体体验不错，虽然门槛较高（好像是得硕士才行，但是不知道为什么我就约到面试了），面试官是面了这么多里面相对比较和蔼的，也很幽默风趣，整体体验很棒，希望能够拿到offer</p>
<h1>后续</h1>
<p>HR面一直拖一直拖，每次都因为一些急事鸽了，，，搞得已经没什么信心继续面滴滴了，已经快一个月了，依然没有搞完HR面，，，随缘了</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[一个简易开放平台（RPC）的设计思路]]></title>
            <link>https://umb.ink/post/10</link>
            <guid>https://umb.ink/post/10</guid>
            <pubDate>Thu, 08 Sep 2022 14:45:20 GMT</pubDate>
            <description><![CDATA[<h1>起因</h1>
<p>​		去年7月打计算机程序设计大赛的时候设计了一套互联网虚假信息监测开放平台，本来想着打完比赛就写篇博客，结果比赛越打越多，<s>绝对不是我咕咕咕</s>。最近投实习，暂时闲下来了，分享一下当时的一些设计思路与经验。</p>
<!--more-->
<h1>基本构成要素</h1>
<p>一个开放平台以提供RPC（Remote Procedure Call）远程过程调用服务为主，应该包含以下要素：</p>
<ol>
<li>鉴权设计（OAuth1.0、OAuth2.0）</li>
<li>RPC服务</li>
</ol>
<h1>平台定位</h1>
<p>​		开放平台的定位大部分都是ToB的，因此需要精准定位客户需求，这里以我设计的互联网虚假信息监测开放平台为例。</p>
<h2>目标群体</h2>
<p>​		要了解客户需求，首先要明确客户到底是哪些人。</p>
<p>​		显而易见，需要互联网虚假信息监测服务的场景集中于各类社交平台等舆论密集的地方，因此，这种社交平台的开发者和管理者就是我们的目标群体。</p>
<h2>痛点分析</h2>
<p>​		确立了目标群体，那么就要分析这个群体最需要什么，即这个行业中存在的痛点在哪，从痛点着手切入，才能开发出好用的产品。</p>
<p>在虚假信息监测开放平台中，目标群体的痛点就在于人力审核互联网舆论的高成本、低效率，众所周知，互联网舆论是非常庞大的，要审核起来非常费劲，但是又不能完全放弃管控，<s>你永远不知道用户会搞出什么幺蛾子</s>。</p>
<p>​		因此，本平台就立足于互联网人力审核成本高、效率低这一痛点上进行构建，平台的核心是监测互联网虚假信息，平台的目标，就是让社交平台的管理人员能够提高舆论审核的工作效率，降低人力成本，将他们从无尽地审核地狱里解放出来。</p>
<h3>结论</h3>
<p>​		至此，我们就完成了第一步，开放平台的目标定位，接下来要做的，就是切入我们上面所分析出的行业痛点，进行平台的概要设计</p>
<h1>概要设计</h1>
<p>​		在这一阶段我们需要完成整体项目的结构梳理，包括架构、功能点。</p>
<h2>需求分析</h2>
<p>​		上面我们确立了平台需要致力于解决的痛点，那么接下来我们就需要针对痛点来推断需求，<strong>功能为需求而服务</strong>，因此基于关键痛点进行需求分析是非常重要的一个环节。</p>
<p>​		针对互联网舆论人力审核成本高、效率低这一痛点，我们可以拆分出多条需求。</p>
<blockquote>
<ol>
<li>
<p>监测多种虚假信息文本，主要包括谣言、敏感词、营销号三种</p>
</li>
<li>
<p>需要提供自定义化服务，以解决不同平台有不同敏感词监测规范的情况。（<s>比如在B站，你所热爱的就是你的生活</s>，在B站是违禁词，但在其它地方可不一定是）</p>
</li>
<li>
<p>需要为接入的平台提供数据可视化服务，以方便用户查看各类虚假信息的趋势，更好地制定舆论管控规则</p>
</li>
<li>
<p>要有容易上手的接口设计和详细的说明文档，防止用户看完一头雾水，依然不会用</p>
</li>
<li>
<p>稳定可靠的服务支持，毫无疑问一个社交平台的信息量是非常恐怖的，因此我们的平台必须具有极高的稳定性，要能够抗住非常恐怖的流量才可以</p>
</li>
</ol>
</blockquote>
<h2>功能设计</h2>
<p>​		有了明确的需求，就可以针对需求来设计功能了，我们从上面的五条需求展开来设计功能</p>
<h4>1. 虚假信息文本监测</h4>
<p>​		针对于虚假信息文本监测的需求，我们以提供RPC接口的方式为用户提供云端虚假信息监测服务，身份校验采用OAuth1.0原则（因为简单，现在应该使用2.0），通过签发令牌的方式来实现。</p>
<p>​		鉴于需要至少实现三种不同的舆论识别，也即谣言、敏感词、营销号，所以我们需要提供三套不同的RPC服务，分别就是：谣言监测、敏感词识别、营销文本分类识别。</p>
<h4>2. 自定义舆论监测规则</h4>
<p>​		针对部分平台需要自定义监测规则的需求来说，同样可以使用RPC接口的方式为用户提供云端服务，不同与上一点，这个功能下我们需要让用户上传自定义的文件规则。然后后台基于该数据文件，制定适合用户的识别模型。</p>
<h4>3. 数据可视化服务</h4>
<p>​		舆论的实时情况统计，统计用户的平台在某个时间段内的虚假信息数量，并展示给用户观看，因此需要后端实现在监测到违法信息时，对其进行一个分类统计的功能，包括在某个时间段内用户所接入平台的谣言舆论数量、带有敏感词监测的舆论数量。</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>起因</h1>
<p>​		去年7月打计算机程序设计大赛的时候设计了一套互联网虚假信息监测开放平台，本来想着打完比赛就写篇博客，结果比赛越打越多，<s>绝对不是我咕咕咕</s>。最近投实习，暂时闲下来了，分享一下当时的一些设计思路与经验。</p>
<!--more-->
<h1>基本构成要素</h1>
<p>一个开放平台以提供RPC（Remote Procedure Call）远程过程调用服务为主，应该包含以下要素：</p>
<ol>
<li>鉴权设计（OAuth1.0、OAuth2.0）</li>
<li>RPC服务</li>
</ol>
<h1>平台定位</h1>
<p>​		开放平台的定位大部分都是ToB的，因此需要精准定位客户需求，这里以我设计的互联网虚假信息监测开放平台为例。</p>
<h2>目标群体</h2>
<p>​		要了解客户需求，首先要明确客户到底是哪些人。</p>
<p>​		显而易见，需要互联网虚假信息监测服务的场景集中于各类社交平台等舆论密集的地方，因此，这种社交平台的开发者和管理者就是我们的目标群体。</p>
<h2>痛点分析</h2>
<p>​		确立了目标群体，那么就要分析这个群体最需要什么，即这个行业中存在的痛点在哪，从痛点着手切入，才能开发出好用的产品。</p>
<p>在虚假信息监测开放平台中，目标群体的痛点就在于人力审核互联网舆论的高成本、低效率，众所周知，互联网舆论是非常庞大的，要审核起来非常费劲，但是又不能完全放弃管控，<s>你永远不知道用户会搞出什么幺蛾子</s>。</p>
<p>​		因此，本平台就立足于互联网人力审核成本高、效率低这一痛点上进行构建，平台的核心是监测互联网虚假信息，平台的目标，就是让社交平台的管理人员能够提高舆论审核的工作效率，降低人力成本，将他们从无尽地审核地狱里解放出来。</p>
<h3>结论</h3>
<p>​		至此，我们就完成了第一步，开放平台的目标定位，接下来要做的，就是切入我们上面所分析出的行业痛点，进行平台的概要设计</p>
<h1>概要设计</h1>
<p>​		在这一阶段我们需要完成整体项目的结构梳理，包括架构、功能点。</p>
<h2>需求分析</h2>
<p>​		上面我们确立了平台需要致力于解决的痛点，那么接下来我们就需要针对痛点来推断需求，<strong>功能为需求而服务</strong>，因此基于关键痛点进行需求分析是非常重要的一个环节。</p>
<p>​		针对互联网舆论人力审核成本高、效率低这一痛点，我们可以拆分出多条需求。</p>
<blockquote>
<ol>
<li>
<p>监测多种虚假信息文本，主要包括谣言、敏感词、营销号三种</p>
</li>
<li>
<p>需要提供自定义化服务，以解决不同平台有不同敏感词监测规范的情况。（<s>比如在B站，你所热爱的就是你的生活</s>，在B站是违禁词，但在其它地方可不一定是）</p>
</li>
<li>
<p>需要为接入的平台提供数据可视化服务，以方便用户查看各类虚假信息的趋势，更好地制定舆论管控规则</p>
</li>
<li>
<p>要有容易上手的接口设计和详细的说明文档，防止用户看完一头雾水，依然不会用</p>
</li>
<li>
<p>稳定可靠的服务支持，毫无疑问一个社交平台的信息量是非常恐怖的，因此我们的平台必须具有极高的稳定性，要能够抗住非常恐怖的流量才可以</p>
</li>
</ol>
</blockquote>
<h2>功能设计</h2>
<p>​		有了明确的需求，就可以针对需求来设计功能了，我们从上面的五条需求展开来设计功能</p>
<h4>1. 虚假信息文本监测</h4>
<p>​		针对于虚假信息文本监测的需求，我们以提供RPC接口的方式为用户提供云端虚假信息监测服务，身份校验采用OAuth1.0原则（因为简单，现在应该使用2.0），通过签发令牌的方式来实现。</p>
<p>​		鉴于需要至少实现三种不同的舆论识别，也即谣言、敏感词、营销号，所以我们需要提供三套不同的RPC服务，分别就是：谣言监测、敏感词识别、营销文本分类识别。</p>
<h4>2. 自定义舆论监测规则</h4>
<p>​		针对部分平台需要自定义监测规则的需求来说，同样可以使用RPC接口的方式为用户提供云端服务，不同与上一点，这个功能下我们需要让用户上传自定义的文件规则。然后后台基于该数据文件，制定适合用户的识别模型。</p>
<h4>3. 数据可视化服务</h4>
<p>​		舆论的实时情况统计，统计用户的平台在某个时间段内的虚假信息数量，并展示给用户观看，因此需要后端实现在监测到违法信息时，对其进行一个分类统计的功能，包括在某个时间段内用户所接入平台的谣言舆论数量、带有敏感词监测的舆论数量。</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/感悟">感悟</category>
        </item>
        <item>
            <title><![CDATA[gitalk 403、Error_Validation_Failed以及Network Error问题解决]]></title>
            <link>https://umb.ink/post/11</link>
            <guid>https://umb.ink/post/11</guid>
            <pubDate>Thu, 08 Sep 2022 14:44:50 GMT</pubDate>
            <description><![CDATA[<h1>问题起源</h1>
<p>在配置gitalk评论后，会出现403报错、Netword Error和Error_Validation_Failed等问题而导致gitalk无法正常使用,这些问题多半都是因为<strong>缺乏gitalk代理</strong>导致的</p>
<!--more-->
<p><img src="https://i.loli.net/2021/10/21/9fRcw8ajYz4WMTU.png" alt=""></p>
<h1>查找解决办法</h1>
<p>先前往gitalk的github仓库看有没有人提issues，一番搜寻后发现确实有不少人有这个问题，而且已经给出了可能的原因</p>
<p><img src="https://i.loli.net/2021/10/21/NUlaYjnCmKt2J3S.png" alt=""></p>
<p>前往博客查看接口响应情况</p>
<p><img src="https://i.loli.net/2021/10/21/YiNMa5j2KdnHqAB.png" alt=""></p>
<p>确实如issues所说，国内无法正常访问github的access_token，按照issues提供的解决办法，来挂个代理试试</p>
<p>通过和<a href="https://rainchan.win/">某位朋友</a>线下py,拿到了他自己搭建的一个代理地址</p>
<blockquote>
<p>或者可以去别的网站打开F12寻找他们的access_token代理地址，比如<a href="https://gitalk.github.io/">gitalk的官方网站</a></p>
<p><img src="https://i.loli.net/2021/10/21/HRJPQ8Ntix2EdFV.png" alt=""></p>
</blockquote>
<p>然后去hexo主题源目录下找到gitalk的源文件（各个主题的文件路径可能不同，但是总可以找到的）</p>
<p><img src="https://i.loli.net/2021/10/21/YFdWOxbtJple472.png" alt=""></p>
<p>修改gitalk对象构造器里的<strong>proxy字段</strong>（如果没有可以自己加一个）</p>
<p>最后编译，部署，问题便解决了</p>
<p>如果部署上去依然报错，那么多半是代理挂掉了，可以试试更换一个代理</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>问题起源</h1>
<p>在配置gitalk评论后，会出现403报错、Netword Error和Error_Validation_Failed等问题而导致gitalk无法正常使用,这些问题多半都是因为<strong>缺乏gitalk代理</strong>导致的</p>
<!--more-->
<p><img src="https://i.loli.net/2021/10/21/9fRcw8ajYz4WMTU.png" alt=""></p>
<h1>查找解决办法</h1>
<p>先前往gitalk的github仓库看有没有人提issues，一番搜寻后发现确实有不少人有这个问题，而且已经给出了可能的原因</p>
<p><img src="https://i.loli.net/2021/10/21/NUlaYjnCmKt2J3S.png" alt=""></p>
<p>前往博客查看接口响应情况</p>
<p><img src="https://i.loli.net/2021/10/21/YiNMa5j2KdnHqAB.png" alt=""></p>
<p>确实如issues所说，国内无法正常访问github的access_token，按照issues提供的解决办法，来挂个代理试试</p>
<p>通过和<a href="https://rainchan.win/">某位朋友</a>线下py,拿到了他自己搭建的一个代理地址</p>
<blockquote>
<p>或者可以去别的网站打开F12寻找他们的access_token代理地址，比如<a href="https://gitalk.github.io/">gitalk的官方网站</a></p>
<p><img src="https://i.loli.net/2021/10/21/HRJPQ8Ntix2EdFV.png" alt=""></p>
</blockquote>
<p>然后去hexo主题源目录下找到gitalk的源文件（各个主题的文件路径可能不同，但是总可以找到的）</p>
<p><img src="https://i.loli.net/2021/10/21/YFdWOxbtJple472.png" alt=""></p>
<p>修改gitalk对象构造器里的<strong>proxy字段</strong>（如果没有可以自己加一个）</p>
<p>最后编译，部署，问题便解决了</p>
<p>如果部署上去依然报错，那么多半是代理挂掉了，可以试试更换一个代理</p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[基于gitalk搭建博客评论]]></title>
            <link>https://umb.ink/post/12</link>
            <guid>https://umb.ink/post/12</guid>
            <pubDate>Sat, 08 Oct 2022 14:58:15 GMT</pubDate>
            <description><![CDATA[<h1>起因</h1>
<p>搭建好了个人博客，为了能够与沙雕网友在博客里愉快吹逼，需要开一个评论</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <h1>起因</h1>
<p>搭建好了个人博客，为了能够与沙雕网友在博客里愉快吹逼，需要开一个评论</p>
<!-- more -->
<h1>步骤</h1>
<p>这里选用使用人数最多的<a href="https://github.com/gitalk/gitalk">gitalk</a></p>
<h2>1. npm安装</h2>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>npm i --save gitalk
</code></pre>
<h2>2. 创建Github Application</h2>
<p>创建一个GithubApplication，可以<a href="https://github.com/settings/applications/new">点这里创建</a></p>
<p><img src="https://i.loli.net/2021/10/21/E13woviRhL8cdSH.png" alt=""></p>
<blockquote>
<p><strong>字段说明</strong>：</p>
<p>​	Application name：应用名称,叫啥都行，可以叫BlogTalk方便区分</p>
<p>​	Homepage URL：你的博客的链接，如果没有配域名的话就是https://&lt;githubname&gt;.github.io</p>
<p>​	Application description: 应用描述，随便写</p>
<p>​	Authorization callback URL:  验证回调地址，同HomePageURL</p>
</blockquote>
<h2>3. 查看clientID和clientSecret</h2>
<p>到Github首页，点击个人头像-&gt;settings-&gt;Developer settings-&gt;OAuth Apps-&gt;刚才创建的应用</p>
<p><img src="https://i.loli.net/2021/10/21/bBEZzhP5TvHpaxo.png" alt=""></p>
<p><img src="https://i.loli.net/2021/10/21/StJjKEZe4DcmU7Q.png" alt=""></p>
<p>如图就是ClientID，点击Generate a new client secret可查看Client secrets</p>
<p><img src="https://i.loli.net/2021/10/21/32ZzMywVvlUcXQ9.png" alt=""></p>
<h2>4. 创建一个github仓库用来存储评论</h2>
<p>随便新建一个仓库，注意设为Public即可</p>
<h2>5. 配置</h2>
<p><img src="https://i.loli.net/2021/10/21/EjRYfImGpcwPdug.png" alt=""></p>
<p>找到博客主体的配置文件中关于gitalk的配置（如果博客没有可以参考<a href="https://github.com/gitalk/gitalk">官方文档</a>，自己魔改一番）（如果不想改，建议换主题）</p>
<p>由此，gitalk便配置完成了，编译部署之后即可查看效果</p>
<h1>番外：配置代理</h1>
<p>在某些情况下可能会出现gitalk无法正常工作，报403、Netword Error等情况，这些情况只需要配置一个代理即可，解决方法可以参考<a href="https://farewell12345.github.io/2021/10/19/gitalk-403%E4%BB%A5%E5%8F%8AError-Validation-Failed%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/">另一篇文章</a></p>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
        <item>
            <title><![CDATA[基于Hexo搭建免费个人博客]]></title>
            <link>https://umb.ink/post/13</link>
            <guid>https://umb.ink/post/13</guid>
            <pubDate>Sat, 08 Oct 2022 15:00:51 GMT</pubDate>
            <description><![CDATA[<p>使用Hexo+Github Page快速搭建个人博客</p>
]]></description>
            <content:encoded><![CDATA[<div class="markdown-body rss">
      <link rel="stylesheet" href="https://umb.ink/markdown.css">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/default.min.css">
      <p>使用Hexo+Github Page快速搭建个人博客</p>
<!-- more -->
<p><strong>注意：本博客仅仅针对纯小白，快速搭建一个能用的博客</strong></p>
<h1>环境搭建</h1>
<ol>
<li>
<p>安装<a href="http://nodejs.cn/learn/how-to-install-nodejs">NodeJS</a>(建议使用 Node.js 12.0 及以上版本)</p>
</li>
<li>
<p>安装<a href="https://www.liaoxuefeng.com/wiki/896043488029600/896067074338496">Git</a>（参考廖雪峰git教程）</p>
</li>
<li>
<p>注册一个Github账号</p>
</li>
<li>
<p>登录Github</p>
</li>
<li>
<p>在GitHub上新建一个仓库</p>
<p><img src="https://i.loli.net/2021/04/12/altbZHiUjze6r73.png" alt=""></p>
<blockquote>
<p>如图，点击<strong>New repository</strong>，在<strong>Repository name</strong> 的位置填写域名，格式是 <strong>username.github.io</strong>.</p>
<p>比如你的github名字是farewell12345，那么这里就写farewell12345.github.io</p>
</blockquote>
<img src="https://i.loli.net/2021/04/12/TB6rNASgf49kPIy.png" style="zoom:67%;" />
<blockquote>
<p>填完以后可以啥都不用选，拉到最下面<strong>点击Create repository</strong></p>
<p>创建成功之后，进入仓库，点击右上角的 Settings</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/FJ9Dl2BVkEdSKMu.png" alt=""></p>
<blockquote>
<p>找到 GitHub Pages 选项，选择一个 GitHub 官方提供的主题</p>
</blockquote>
<p><img src="https://cdn.sspai.com/20190506142607.jpg" alt="img"></p>
<blockquote>
<p>主题随便选一个，选择完毕后在跳转界面点击Commit changes，就完成了Github Page的配置</p>
<p>这时可以试着访问一下 <strong>https://username.github.io</strong>   （就是刚才在<strong>Repository name</strong> 的位置填写的域名），如果正常打开了页面，就说明配置成功了。</p>
</blockquote>
</li>
<li>
<blockquote>
<p><strong>选择一个空文件夹</strong>，打开Bash窗口（装好Git以后右键菜单中自带），使用npm安装Hexo</p>
</blockquote>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>npm install hexo-cli -g
</code></pre>
<blockquote>
<p>安装完成后依次执行下列命令</p>
</blockquote>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>hexo init Blog
<span class="hljs-built_in">cd</span> Blog
npm install
hexo g
hexo s
</code></pre>
<blockquote>
<p>hexo s输入完毕后，不要关闭Bash窗口,就保持这样的状态，然后进入浏览器，输入http://localhost:4000，去尝试访问这个地址，如果访问到了页面，说明本地Hexo 环境已经搭建好了</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/khg9cIexFs4X6a7.png" alt=""></p>
</li>
</ol>
<h1>开始搭建属于自己的博客</h1>
<blockquote>
<p>到<a href="https://hexo.io/themes/index.html">Hexo主题网站（点击蓝字即可）</a>找一个你最顺眼的主题，点进去，找到它的使用手册，因为不同的主题其说明文档也不一样，所以在配置主题的过程中就需要自己去踩坑了。这里以我目前在用的<a href="https://github.com/theme-nexmoe/hexo-theme-nexmoe">Nexmoe主题</a>为例：</p>
<p>从Hexo主题网站找到它以后，点击进入它的Github仓库，点击Code，然后download zip（如果配置了Git SSH Key可以选择直接clone），下载主题。</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/UoNKeZAJabO1E8Y.png" alt=""></p>
<blockquote>
<p>打开你电脑上之前用Hexo生成的Blog文件夹,进入themes文件夹中，把刚才下载好的主题zip<strong>解压并放入这个themes文件夹下</strong></p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/9SWjfnUIdHOK1Xb.png" alt=""></p>
<p>​			<img src="https://i.loli.net/2021/04/12/NaRAJjgOYZemrPT.png" alt=""></p>
<blockquote>
<p>打开blog目录下的config.yml配置文件，修改theme为主题文件夹的名字，比如：</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/alC1nNsSvrdEwxp.png" alt=""></p>
<blockquote>
<p>在config.yml中修改博客名和描述等信息</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/YUftQ2PCTZ53NBw.png" alt=""></p>
<p><img src="https://i.loli.net/2021/04/12/CN4upV1bPYU8Dqy.png" alt=""></p>
<blockquote>
<p>author:作者</p>
<p>title：你博客标题（名字）</p>
<p>description：博客描述</p>
<p>url：设置你博客的域名（就刚才在Github上设置的那个）</p>
<p>config基础设置这样就可以了，其他如果想了解的话可以去<a href="https://hexo.io/zh-cn/docs/">Hexo官方文档</a></p>
</blockquote>
<blockquote>
<p><strong>返回主题仓库查看Readme</strong></p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/JT42NABOcyjuQlH.png" alt=""></p>
<blockquote>
<p>查看主题的教程，对主题进行自定义配置</p>
<p>打开主题文件夹</p>
</blockquote>
<p><img src="https://i.loli.net/2021/04/12/EYSJrR94QegWm8A.png" alt=""></p>
<p>打开这个目录下的config.yml，对照主题官方文档进行自定义配置</p>
<p>（因为每个人选择的主题不一样，所以这里就不多赘述了，一般主题的官方文档是写得很详细的）</p>
<h3>运行博客</h3>
<p>在Blog根目录下运行Bash窗口，输入</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>hexo g
hexo s
</code></pre>
<p>访问http://localhost:4000就可以看到自己配置出的博客了（如果步骤没有错误的话）</p>
<p>输入</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>hexo s --debug
</code></pre>
<p>可以进入debug模式，可以看到博客的运行日志</p>
<h3>部署博客</h3>
<p>进入Blog目录下的public文件夹，打开Bash窗口，依次输入以下命令：</p>
<pre class="hljs" style="background: #f3f3f3; padding: 8px;><code>git init
git remote add origin  &lt;你的博客在github上的链接&gt;
git add .
git commit -m <span class="hljs-string">&quot;提交的描述&quot;</span>
git push origin master
</code></pre>
<p>这里需要获取<strong>你的博客在github上的链接</strong>，打开博客对应的github仓库，点击code，</p>
<p><img src="https://i.loli.net/2021/04/12/Pdqr9t3ZfGAo6TC.png" alt=""></p>
<p>这个就是仓库的链接</p>
<p>push成功以后，等一会去查看github仓库是否有了我们push上去的东西，如果有，那就可以去试着访问https://username.github.io（就是刚才在<strong>Repository name</strong> 的位置填写的域名）了，如果出现了界面，那就说明博客成功了</p>
<p><img src="https://i.loli.net/2021/10/11/b5Yqy8RLKEJpz6U.png" alt=""></p>
<h4>如果配置的过程中遇到未知错误，请记住搜索引擎是个好东西（</h4>
</div>]]></content:encoded>
            <author>i@umb.ink (伞菌)</author>
            <category domain="https://umb.ink//category/技术">技术</category>
        </item>
    </channel>
</rss>