注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

Demo体验

Demo体验

场景Demo,开箱即用
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

看了我项目中购物车、订单、支付一整套设计,同事也开始悄悄模仿了...

在我的mall电商实战项目中,有着从商品加入购物车到订单支付成功的一整套功能,这套功能的设计与实现对于有购物需求的网站来说,应该是一套通用设计了。今天给大家介绍下这套功能设计,涵盖购物车、生成确认单、生成订单、取消订单以及支付成功回调等内容,希望对大家有所帮助...
继续阅读 »

在我的mall电商实战项目中,有着从商品加入购物车到订单支付成功的一整套功能,这套功能的设计与实现对于有购物需求的网站来说,应该是一套通用设计了。今天给大家介绍下这套功能设计,涵盖购物车、生成确认单、生成订单、取消订单以及支付成功回调等内容,希望对大家有所帮助!



mall项目简介


这里还是简单介绍下mall项目吧,mall项目是一套基于 SpringBoot + Vue + uni-app 的电商系统(Github标星60K),采用Docker容器化部署。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能,功能很强大!



后台管理系统演示



前台商城项目演示



功能设计



这里介绍下从商品加入购物车到订单支付成功的整个流程,涵盖流程的示意图和效果图。



流程示意图


以下是从商品加入购物车到订单支付成功的流程图。



流程效果图


以下是从商品加入购物车到订单支付成功的效果图,可以对照上面的流程示意图查看。



数据库设计


为了支持以上购物流程,整个订单模块的数据库设计如下。



设计要点



接下来介绍下整个购物流程中的一些设计要点,涵盖加入购物车、生成确认单、生成订单、取消订单以及支付成功回调等内容。



加入购物车


功能逻辑


用户将商品加入购物车后,可以在购物车中查看到商品。购物车的主要功能就是存储用户选择的商品信息及计算购物车中商品的优惠。



购物车优惠计算流程



相关注意点



  • 购物车中商品优惠金额不会在购物车中体现,要在生成确认单时才会体现;

  • 由于商品优惠都是以商品为单位来设计的,并不是以sku为单位设计的,所以必须以商品为单位来计算商品优惠;

  • 代码实现逻辑可以参考mall项目中OmsPromotionServiceImpl类的calcCartPromotion方法。


生成确认单


功能逻辑


用户在购物车页面点击去结算后进入生成确认单页面。确认单主要用于用户确认下单的商品信息、优惠信息、价格信息,以及选择收货地址、选择优惠券和使用积分。



生成确认单流程



相关注意点



  • 总金额的计算:购物车中所有商品的总价;

  • 活动优惠的计算:购物车中所有商品的优惠金额累加;

  • 应付金额的计算:应付金额=总金额-活动优惠;

  • 代码实现逻辑可以参考mall项目中OmsPortalOrderServiceImpl类的generateConfirmOrder方法。


生成订单


功能逻辑


用户在生成确认单页面点击提交订单后生成订单,可以从订单详情页查看。生成订单操作主要对购物车中信息进行处理,综合下单用户的信息来生成订单。



下单流程



相关注意点




  • 库存的锁定:库存从获取购物车优惠信息时就已经从pms_sku_stock表中查询出来了,lock_stock字段表示锁定库存的数量,会员看到的商品数量为真实库存减去锁定库存;




  • 优惠券分解金额的处理:对全场通用、指定分类、指定商品的优惠券分别进行分解金额的计算:



    • 全场通用:购物车中所有下单商品进行均摊;

    • 指定分类:购物车中对应分类的商品进行均摊;

    • 指定商品:购物车中包含的指定商品进行均摊。




  • 订单中每个商品的实际支付金额计算:原价-促销优惠-优惠券抵扣-积分抵扣,促销优惠就是购物车计算优惠流程中计算出来的优惠金额;




  • 订单号的生成:使用Redis来生成,生成规则:8位日期+2位平台号码+2位支付方式+6位以上自增id;




  • 优惠券使用完成后需要修改优惠券的使用状态;




  • 代码实现逻辑可以参考mall项目中OmsPortalOrderServiceImpl类的generateOrder方法。




取消订单


功能逻辑


订单生成之后还需开启一个延时任务来取消超时的订单,用户也可以在订单未支付的情况下直接取消订单。



订单取消流程



相关注意点



  • 代码实现逻辑可以参考mall项目中OmsPortalOrderServiceImpl类的cancelOrder方法。


支付成功回调


功能逻辑


前台用户订单支付完成后,第三方支付平台需要回调支付成功接口。



支付成功回调流程



相关注意点



  • 代码实现逻辑可以参考mall项目中OmsPortalOrderServiceImpl类的paySuccess方法。


总结


今天给大家介绍了mall项目中整套购物流程的功能设计,其实对于很多网站来说都需要这么一套功能,说它是通用功能也不为过。从本文中大家可以看到,mall项目的整套购物流程设计的还是比较严谨的,考虑到了方方面面,如果你对mall项目整套购物流程实现感兴趣的话可以学习下mall项目的代码。


项目源码地址


github.com/macrozheng/…


作者:MacroZheng
来源:juejin.cn/post/7290931758787313725
收起阅读 »

工作中时间都去哪了——《重要的两小时》选读 P1

———— # 本篇是:程序员成长-杂谈分享05 最近看了一本讲关于如何科学休息的书,里面有些观点写的很精辟,忍不住想要摘抄下来一直品读,并与各位分享~ 当我们端起第一杯咖啡时,就会用掌上设备检查电子邮箱,看一眼又有谁给我们加上了一条待办事项。邮件一封封被打开...
继续阅读 »

———— # 本篇是:程序员成长-杂谈分享05


最近看了一本讲关于如何科学休息的书,里面有些观点写的很精辟,忍不住想要摘抄下来一直品读,并与各位分享~



当我们端起第一杯咖啡时,就会用掌上设备检查电子邮箱,看一眼又有谁给我们加上了一条待办事项。邮件一封封被打开,压力也在一点点地累积,每一封邮件里都包含着我们明知自己不可能迅速完成的要求。于是我们只好把这些邮件标记为“未读”,留待“晚点再说”。而在脑海中,我们会把这些邮件扔到昨晚(昨晚离开办公室时明明已经很晚了) 没做完的那堆工作里。还有更多的邮件等着你回,更多的电话等着你打,更多的表格等着你填。而所有这一切都需要你立即集中精力去做。


实际上,在我们能着手进行真正重要的工作之前一-真正重要的工作也实在太多了-还需要打起精神解决许多事。我们经常工作一整天:先是在办公室里,回家后还得照顾家人、打扫房间、缴各种费用,有时甚至要一直忙到上床睡觉。简单来说,就是要做的事情太多,时间永远不够用.



卧槽。。这说的不就是我吗??感觉事情一件接着一件,每一件都不是马上就能完成的,要找人推进、要填表、要拟一段话等等;回到家后就是吃饭、洗碗、遛狗、洗澡、睡觉、家务等等,没有什么空余时间哇!



你想成为哪一个富兰克林?那个给自己的爱好和社交休闲留出时间、不断产生新的兴趣的富兰克林,还是那个比同行竞争者更勤勉、高效、受人尊重又有钱的生意人的家伙?在如今这个时代,似乎没有足够的时间能让你做到两者兼备,所以我们只能选择要么享受生活,要么成功。告诉你们一个好消息: 这个选择根本不存在。只有当我们错误地以为高效完全依赖于挤出足够多的时间工作时,我们才会面对这种选择的压力。



作者以富兰克林为例子,说了他是如何闲暇的度过时间的:作家、发明家、印刷家、哲学家、政治家、邮政局长、外交家,都是空闲时间给他的引导~


上面这段话我震惊了,我们原以为不能兼顾空闲时间和忙碌的工作,其实是不对的,空闲的时间可以与高效工作并存,这个高效的工作关键点不在于:挤压时间或者延长时间!或者说这个本来是跟时间无关的!时间是什么?思考我们对时间的感知。



身体动作还有可能影响你的情绪,并影响你对其他人的想法和意愿的理解。研究表明,如果你在评价某人时做了个敌对性的手势一-比如竖中指,你就很可能会把对方看成敌人,因为身体的动作事先已经暗示了这种敌对的倾向。或者,回想一下你是怎么学习的--这当然得依靠记忆力,但这并不是像往电脑上装软件或下载文件那样,把记忆植入大脑之中;相反,你得逐步培养记忆力,要花时间来改变神经元结构,让它们更容易相互激活。这也许能够解释为什么在考试前一晚突击复习效果远不如在数天之内一步步地进行复习一-如果你想记得久一点的话。



我们的行为会影响思想,这个毋庸置疑;同样,我们的思维习惯、行为方式会影响对时间的判断!!



心理学和神经学的科研成果已经告诉我们,我们该在何时、以何种方法创造出大脑的高效工作时间。在本书中,我将会详细分享五种看似简单的策略,对那些大忙人来说,它们能极为有效地帮助他们实现每日的“高效两小时”


1.意识到你的抉择点。只要你开始一项任务,基本上就会处于自动工作的状态,这样就很难改变你的工作方向。因此,要利用好不同任务交接时的那一刻--在这种时候,你能够选择下一步该做什么,然后把精力放到接下来最重要的任务上。


2.管理你的心理能量。需要高度自控力或专注度的任务可能会迅速消耗你的能量,而那些令你情绪化的任务则会让你不在状态。所以要学会按照这些任务的不同要求和恢复时间来安排任务。


3.不要与分心做无意义的缠斗。要学会引导自己的注意力。人的注意力系统天生就是会四处走神、不断更新目标,而不是永远专注的。不让自己分心,就像是阻断海浪一样毫无用处。了解你大脑的工作方式,才能帮助你在分心后迅速而有效地回到手头的工作上。


4.利用你的身心联系。注意你的运动和饮食方式,让它们保证你能完成某个短期内的目标。你可以在闲暇时再随心所欲地运动、吃东西。


5.让你的工作环境为你所用。去了解什么样的环境因素能够帮助你达到最佳状态,并学会如何随之调节环境。只要你知道了什么会让你分心,什么能预先让你的大脑进入创造和冒险状态,你就能调整自己的工作环境,从而实现高效能的工作。



基于科学,本书的核心观点分为这 5 点!


目前只读到了第一点,就大有感触,接着后面会说~~



大部分情况下,我们都处于自动模式下一-我们的所思、所感、所为都是按照无意识的常规程序进行的。所谓无意识,指的是思维或大脑并非有意识去做事。当然,我并不是指我们的所有行为都是不假思索的,我的意思是,我们对许多行为都习以为常并且非常熟练,所以几乎不需要有意识地监控它们。 虽然我在前言中强调过我们的大脑并不是电脑,也不能永远按照预期持之以恒地工作,然而在某种意义上,我们的大脑与电脑又非常相似:我们所做的几乎所有事情--从用牙线剔牙,到花一整天时间回复邮件--都是按照神经的常规程序来做的。这种神经程序和电脑程序其实并无太大差别,它引导着我们的思想、感受和行为。在某种程度上,我们是在自动地完成这些常规程序,而不会有意识地去考虑或反思这么做到底有没有道理。我们一旦开始了一项神经性常规活动,就会像电脑程序一样一直运行下去,直到完成任务,或是被打断。如果你开始用牙线剔牙,很可能直到你剔完牙,都不会意识到自己究竟完成了多少个繁复的步骤才把牙剔干净。如果你在到达办公室几分钟后就开始查阅电子邮件,很可能在你不知不觉的时候,就已经开始条件反射似的打开、阅读、回复一封邮件,然后是下一封,再下一封......也许直到你的同事打断你,拉着你一起去吃午餐时,你才会停下来。但是,当你一大早去办公室前,很可能是打算完成其他工作的,但一旦开始回复邮件,你的神经性常规程序就开始运行,而你却没办法停下来。除非有什么事情打断你,才能让你从这种状态下脱身。



作者举了一个极为生动的例子:道格在各种工作任务中挣扎,做很多工作都是无意识的!


这也就解释了:为什么我们会感觉忙了一天,又啥也没做?但实际上,事情一件接着一件也没停下来过!



我们每天所做的大部分事情一般不经大脑,只是由我们的习惯指引着,几乎不需要什么有意识的思考。这并不是件坏事。就像杜西格所解释的,我们的习惯很有存在的必要,它们能够节省大脑的能量。我们要让自己的大脑从这些行为中解放出来,才能解决不断产生的新问题。再举个例子,当我们克服了不会跳交谊舞的难题之后,就能按习惯动作进行,接下来才有精力和舞伴聊天。但你要是想在第一次学探戈时跟人聊天,那绝对会是一场灾难--我们需要有意识地关注自己的舞步。想象一下,如果我们必须集中注意力完成每一个行为--比如我们的每一步该落在何处一-那我们还能做什么呀! 实际上,每一天都是由一系列习惯性的神经常规程序组成的,我们一般称这些程序为“任务”: 早上起床、穿好衣服去上班、搭乘各种交通工具到工作地点、打开电脑、回复电子邮件、吃午餐、参加员工大会、跑步、做晚饭、洗漱然后上床睡觉。问题就在于,我们经常会从一个任务跳到另一个任务,却不仔细想想下一步最好该做什么。我们只是条件反射似的做出反应,或者是跟着直觉走,不管它们到底对不对,这就导致了无数时间和能量就这么白白地浪费了。



太棒了,说的就是我,周一到周五,工作的我就像机器人一样!很多事不用思考,全凭直觉!



我的经验告诉我,大家都习惯于在这种时刻--或者说抉择点一匆忙略过,好去做某些让你觉得自己“很高效”的事。匆忙地掠过一个抉择点一-也就是不同任务的间隙--也许能给你省出五分来钟的时间,但是,完成不该完成的任务可能会浪费掉一个钟头。不过,这五分钟的确会让你惴惴不安,因为在这种时刻,我们能够非常清楚地意识到每秒的流逝,而在那浪费掉的一小时中,因为我们基本上处于自动模式所以不会觉得难受。可惜的是,很多人都把时间浪费在了那些并不重要,或者在那个时间段根本不能好好完成的工作上了。 另一个困难就是,既然我们会如此频繁地进入自动模式,那么每天其实并没有太多机会能让我们有意识地决定下一步该完成什么工作。所以,意识到这些抉择点并抓住它们就显得极为重要。在接下来的几页 里,我会告诉你该如何做,但首先,让我们来了解神经常规程序是如何工作的,为什么人们会如此轻易地误用抉择点,这对我们大有裨益。



这一段内容比标题有含金量的多的多,我的工作中正是会不自觉的进入自动模式,在不断的工作任务中穿插,当下决定做下一件事的时候,很随意;往往这种随意决定会带来更多工作负担,比如事情的联系、对任务的专注等等,从一件事跳到另一件事,总需要额外的成本。



在学术界有一种理论影响范围很广,它认为人类在很多方面都是“认知上的懒惰者”。在其他条件完全相同的情况下,我们倾向于选择在思维层面阻力最小的那条路。正因为那些无意识的、已经很熟练的神经性常规程序相对而言更容易完成,而那些需要慎重考虑的、有意识的抉择则需要更多思维上的努力,所以,作为认知上的懒惰者,只要有可能,我们就会更倾向于依赖自动的神经性常规行为,而不是有意识的抉择。 在一步步完成神经性常规程序时,人人都会进入一种忘我的状态在韦氏词典里,关于“忘我”这个词有这样一条定义:“一种状态,在此状态下你不会意识到周围发生的事,因为你正在想着其他事。”如果你正在准备一份PPT,就很可能意识不到有两个同事正站在你的座位附近;如果你正在认真阅读一份报告,也许就意识不到自己饿了,或者已经到了午餐时间。当神经性常规程序在运行的时候,你既不会有太多的自我意识,也不太容易意识到在这个程序之外发生的一切。 但当常规程序结束的时候 (例如,当你剔完牙或者读完报告时)或者被什么人或事打断之后 (例如,当你正在准备PPT时,一位同事来向你求助关于某个项目的问题) ,自我意识就会浮现出来。从沉溺于神经性常规程序到停止这一程序,这之间的转变很可能会让你感到不适。



太对了,我们不关注“做决策”,而通过自动模式去做任务,是因为:我们在认知上的懒惰,需要在很多矛盾的事情下做判断,这是一种挑战,如果不愿意做,不花时间多考虑抉择,那么只会:南辕北辙,再次进入盲目的自动工作中;



例如,对着电脑屏幕看一封电子邮件和转过头听你妻子讲她的聚会计划 (我还从来没有这个福气) ,这两个动作就是相互矛盾的。这是两个相矛盾的常规活动,其中一个让你像僵尸一样沉浸在与收件人的虚拟对话中,而另一个则要求你回应并参与一场跟你配偶进行的现实对话。 这两种行为--盯着电脑和转过去面对我们的配偶一-是相互矛盾的,所以才会需要这种有意识地评估并做出决定的能力来帮助我们解决这种冲突。当察觉到某种冲突需要我们注意时,我们大脑中的某个特殊区域--前扣带皮质区--就会变得活跃起来。有些学者认为这一区域相当于某种警告系统,能够唤醒我们进行有意识的思维活动。有意识的反思似乎只是一种权宜之计,只有当我们更加无意识的活动导致了相互矛盾的行为,需要我们做出决定时,这种反思才会活跃起来。由此可知,抉择点通常是作为冲突一-无意识的自动行为之间相冲突、行为和目标之间相冲突一-的结果而出现的。在这样的时刻,我们往往发觉自已在被拉扯向不同的方向。



工作和生活中,经常遇到这样的矛盾:两个任务有冲突,自己就会被拉扯到不同的方向!!



正因为抉择点通常出现在矛盾的时刻,所以它们往往会令人不快。在前面的例子里,边构思边写邮件,与转过头面对你的配偶,听听她想要讲些什么,这两个任务如果分开来,你大概都很乐意去做:但一旦你必须从两者之间选择其一,我敢打赌,一两次之后你就会觉得烦躁,并发现抉择点真是令人不快。 在自我意识更加警醒的这些时刻,我们也开始注意到其他的各种事件,比如那些我们本来打算做却忘记做的事情,又比如时间的流逝。努力地控制正在做的事可能会让你觉得辛苦**。一项研究表明,你越是需要 注意自己的思绪、情感和行为,你就越觉得时间的流逝很缓慢。然而,这种不够“高产”的时间流逝,并不意味着你就浪费了很多时间。这只意味着我们恰巧更能意识到时间的存在而已。我认识的大多数人,当有很多事情要做的时候,如果他们意识到时间在流逝而自己却毫无进展,他们都会感到焦虑或充满愧疚。正因为这些抉择点会令人不舒服,我们往往才选择尽快跳过它们。



这就是核心:为什么我们专注于一项工作,一到两个小时,觉得很快就过去了!而在多个任务切换,多种任务矛盾、各种问题冲突的情况下,时间又慢,人又有情绪上的急躁,就会很累了!!



而这,往往正是让事情搞砸的地方。



真相了!!


面对任务矛盾的时候,疏于做决策,轻易进入自动模式,会被拉扯、会让对时间的感知变慢、会有情绪、会感觉很累!




P1 是前 20 页部分,今天暂且先读到这儿~~


作者:掘金安东尼
来源:juejin.cn/post/7291245033519398949
收起阅读 »

2023年震撼!Java地位摇摇欲坠?Java在TIOBE排行榜滑坡至历史最低!

一、Java掉到历史最低 从2023年6月开始Java掉到历史最低排到第4位 2023年10月tiobe编程语言排行榜,Java仍然还是排到了第4位,C# 和 Java 之间的差距从未如此之小。 top 10 编程语言1988年~2023年历史排名 引用...
继续阅读 »

一、Java掉到历史最低


从2023年6月开始Java掉到历史最低排到第4位



2023年10月tiobe编程语言排行榜,Java仍然还是排到了第4位,C# 和 Java 之间的差距从未如此之小。



top 10 编程语言1988年~2023年历史排名



引用tiobe官网上TIOBE Software 首席执行官的话:


10 月头条:C# 越来越接近 Java


C# 和 Java 之间的差距从未如此之小。目前,差距仅为 1.2%,如果保持这种趋势,C# 将在大约 2 个月的时间内超越 Java。在所有编程语言中,Java 的跌幅最大,为 -3.92%,C# 的涨幅最大,为 +3.29%(每年)。这两种语言一直在相似的领域中使用,因此二十多年来一直是竞争对手。Java 受欢迎程度下降的主要原因是 Oracle 在 Java 8 之后决定引入付费许可模式。微软在 C# 上采取了相反的做法。过去,C#只能作为商业工具Visual Studio的一部分。如今,C# 是免费且开源的,受到许多开发人员的欢迎。Java 的衰落还有其他原因。首先,Java 语言的定义在过去几年中没有发生太大变化,而其完全兼容的直接竞争对手 Kotlin 更易于使用且免费。——TIOBE Software 首席执行官 Paul Jansen


二、编程语言排行榜


编程语言排行榜是一种用来衡量编程语言的流行度或受欢迎程度的指标,它通常会根据一些数据或标准来对编程语言进行排序和评价。不同的编程语言排行榜可能会有不同的数据来源、计算方法和评估标准,因此它们的结果也可能会有所差异。


目前,最知名和权威的编程语言排行榜之一是 TIOBE 编程社区指数,它由成立于 2000 年 10 月位于荷兰埃因霍温的 TIOBE Software BV 公司创建和维护。TIOBE 编程社区指数通过对网络搜索引擎中涉及编程语言的查询结果数量进行计算,来衡量各种编程语言的受欢迎程度。TIOBE 编程社区指数每个月都会更新一次,并且每年还会评选出一门年度编程语言,表示该门语言在当年的排名中上升幅度最大。除了 TIOBE 编程社区指数之外,还有一些其他的编程语言排行榜,以下是列举的一些编程语言排行榜。


1、TIOBE编程语言排行榜


TIOBE是一家荷兰的编程软件质量评估公司,每月发布一份编程语言排行榜。它使用搜索引擎查询结果、开发者社区活跃度和其他指标来评估编程语言的受欢迎程度。



2023年10月TIOBE编程语言排行榜



2、Stack Overflow开发者调查


Stack Overflow每年进行一次开发者调查,其中包括有关最受欢迎编程语言的信息。Stack Overflow 开发者调查是最权威的编程语言排行榜之一,该调查可以反映全球开发者对编程语言的喜好和使用情况。在选择编程语言时,可以参考该调查的结果,但也需要根据自己的实际需求和开发环境进行综合考虑。



连续三年最受欢迎编程语言排名,可以明显的看出Java的占比在逐年的降低



3、GitHub编程语言趋势榜


GitHub提供了一个编程语言趋势页面,显示了开发者在GitHub上使用的编程语言趋势。虽然这不是正式的排行榜,但反映了实际的开发趋势。


GitHub在趋势榜比较前的基本者是Python或Go的项目



GitHub官网已经去掉了top的排名榜只保留了趋势榜,由一些GitHub的爱好者和贡献者创建和维护的www.github-zh.com的GitHub中文社区网站,是非官方github网站,它旨在为中文用户提供GitHub的相关资讯、教程、交流和协作平台,还可以查到Github项目排行榜。



三、展望Java


可以看到各种编程语言排行榜的数据,虽然会存在片面的情况,但也大体能表现出Java的地位在下降,遥想当年Java是排行榜霸榜老大哥。



虽然Java明显下降,或许正如TIOBE首席执行官说的“Java 受欢迎程度下降的主要原因是 Oracle 在 Java 8 之后决定引入付费许可模式。微软在 C# 上采取了相反的做法。”在这个开放的世界里真正的开源而不是利用开源来测试付费项目才能真正的让大家推崇。


Java的许可模式变化导致用户流失。自从Java 8之后,甲骨文公司决定对Java的商业使用收取费用,这使得一些企业和开发者转向其他免费或开源的语言,如C#、Python等 。


Java的竞争对手不断发展和创新,提供了更多的选择和优势。例如,C#在.NET平台上不断完善和扩展,支持跨平台、混合开发、WebAssembly等技术 ;Python在数据科学、人工智能、Web开发等领域有着广泛的应用和生态 ;Kotlin作为Android官方推荐的语言,兼容Java,并提供了更多的语法糖和功能 。




Java虽然在编程语言排行榜上有所下降,但并不意味着Java就没有前途和价值。Java仍然是一门成熟、稳定、高效、跨平台的语言,拥有庞大的用户群和丰富的生态系统。Oracle作为Mysql、Java等重量级项目的拥有者,也在不断地改进和创新Java,让Java能够适应时代的变化和需求。包括Java 17的免费、Kafka/Spring Boot新版本最低的Java版本为17、Java 21引入协程等,都是Oracle在努力让Java保持竞争力和活力的例证 。



未来在不断的变化,说不定马斯克的美女机器人就真的造出来了。。。


当然,我们也不能忽视其他编程语言的发展和优势,我们应该保持开放和学习的心态,了解不同语言的特点和适用场景,选择最合适的语言来解决问题。编程语言只是工具,重要的是我们能够用它们创造出有价值的产品和服务。


作者:玄明Hanko
来源:juejin.cn/post/7290849115721285667
收起阅读 »

可别小看了一边写代码嘴里一边叨咕的同事,人家可能用的是小黄鸭调试法

什么,鸭子还能调试代码?什么神奇的鸭子啊。 当然不是了,是鸭子帮你调试,那好像也有点儿厉害。 初听感觉是傻子,再听感觉是玄学。 什么是小黄鸭调试法 当然不是鸭子调试代码了,也不是鸭子帮你调试,其实还是靠你自己的。 小黄鸭调试法(Rubber Duck Deb...
继续阅读 »

什么,鸭子还能调试代码?什么神奇的鸭子啊。


当然不是了,是鸭子帮你调试,那好像也有点儿厉害。


初听感觉是傻子,再听感觉是玄学。



什么是小黄鸭调试法


当然不是鸭子调试代码了,也不是鸭子帮你调试,其实还是靠你自己的。


小黄鸭调试法(Rubber Duck Debugging)是一种常用于解决编程问题的技巧,不是代码技术层面的技巧。


大致的调试过程是这样的:



  1. 首先你写好了代码,或者有些逻辑一直写不出来,然后很有自信或者不自信;

  2. 然后你找到一只鸭子,玩具鸭子,或者任意一个电脑旁边的物件;

  3. 最后,把你的代码的逻辑尽量详细的讲个上一步找到的对象,比如一只玩具鸭子;

  4. 通过讲解的过程,你很有可能发现代码上的漏洞,有时候还能发现隐藏的漏洞;



你还可以拉过旁边的人,对着他讲,前提是保证别人不会打你。


这个过程更像是一种review的过程,而且是那种非常具体的review,只不过是自己 review 自己的代码或逻辑。


它的核心是通过将问题或逻辑用语言描述出来,在这个过程中找到解决问题的线索。


虽然这个方法听起来可能有点奇怪,但它在实际中确实能够帮助很多人解决问题。解释问题的过程可能会强迫你慢下来,更仔细地思考,从而找到之前忽略的问题点。


另外,在进行这一些列操作的过程中,尽量保证周围没有人,不然别人可能觉得你是个傻子。


当然了,这个操作你可以在心里默默进行,也是一样的效果。


各位平时工作中有没有遇见过有人使用小黄鸭调试法呢?我看到这个概念的时候想了一下,好像还真碰到过。之前有同事在那儿写代码,一边写嘴里一边叨咕,也不知道在说啥,还开玩笑说这是不是你们这个星座的特质(某个星座)。


现在想想,人家当时用的是不是小黄鸭调试法呀,只恨当初孤陋寡闻,没有问清楚啊。


内在原理


小黄鸭调试法的内在原理其实是涉及到认知心理学中的一些概念的,并不真的是玄学和沙雕行为。


认知外部化


这是小黄鸭调试法的核心。当你将问题从内心中的思考状态转移到外部表达时,你会更加仔细地思考问题。解释问题需要你将问题的细节和步骤以清晰的语言描述出来,这个过程可以帮助你整理思路,更好地理解问题。


问题表达


描述问题的过程可以迫使你更具体地考虑问题。将问题分解为不同的部分,逐步地解释代码的执行流程,有助于你更好地理解代码中可能的缺陷或错误。


观察问题:


当你通过语言表达问题时,可能会注意到之前忽略的细节。这可能是因为你在描述问题时需要更仔细地审查代码和逻辑,从而让你注意到潜在的问题点。


听觉和口头处理


讲述问题的过程涉及到将问题从书面表达转化为口头表达。听觉和口头处理可以帮助你以不同的方式来理解问题,可能会在你的大脑中触发新的洞察力。


认知切换:


与代码一起工作时,你可能会一直陷入相同的思维模式中,难以看到问题。通过将问题从代码中抽离出来,并通过描述来关注它,你会进行认知切换,从而能够以不同的角度审视问题。


总结起来其实很简单,如果一个知识点你理解了,你一定能给别人讲出来,或者写出来,而且别人能够理解。如果你在讲的时候发现有模棱两可的地方,那说明你还没有百分百理解。


就像我们平时写技术文章一样,有时候碰到一些细节写半天也写不清楚,那就是还没有完全理解。


作者:古时的风筝
来源:juejin.cn/post/7290932061174022159
收起阅读 »

🤔公司实习生居然还在手动切换node版本?

web
前段时间看了实习生的新项目,发现他启动不了项目,因为node版本太低,我让他去用nvm来管理node的版本,然后看到他切换版本的时候是这样的,先用nvm下载安装目标的node版本,然后在把安装好的node版本替换掉原先的node路径下的node_modules...
继续阅读 »

前段时间看了实习生的新项目,发现他启动不了项目,因为node版本太低,我让他去用nvm来管理node的版本,然后看到他切换版本的时候是这样的,先用nvm下载安装目标的node版本,然后在把安装好的node版本替换掉原先的node路径下的node_modules,而不是用命令行进行版本切换,才发现原来他使用nvm来切换node版本虽然显示切换成功,但全局的node版本一直是不变的,所以才用文件覆盖的方式强制进行解决,对于这个问题进行解决并且梳理



可以直接跳到第四步查看解决方案


1️⃣ 安装nvm


where nvm

找不到nvm路径的朋友可以用这个命令来找到nvm的安装路径,默认的安装路径都是差不多的


image.png


2️⃣ 查看目前node版本


可以看到目前的版本是node V16.14.2


image.png


3️⃣ nvm安装目标node版本



nvm的主要作用就是用来切换node的版本,为什么要切换node的版本,就是因为你的当前node版本和你要启动的项目不兼容,有两种可能,要么是你的项目太旧,你的node版本相对来说比较高,需要用到向下兼容;另外一种可能就是你项目用到的node比较新,你需要进行升级



先安装需要安装的目标版本,用isntall来安装你需要的对应node版本


image.png


回到你的nvm安装路径,就可以看到你已经安装的各种版本的node文件夹
image.png


当然也可以用命令行


nvm list

image.png


4️⃣ nvm切换到目标node版本


切换到目标node版本使用nvm use


nvm use

查看目前nvm安装了哪些版本 然后use来进行切换


image.png


到切换的时候发现了问题,这里无论怎么切换,node的版本依然不会变


image.png


可以看到我用的use来切换到15的版本,但是再次查看nvm的node历史版本,可以看到还是位于16.14.2的node版本,明明就是这么顺利的问题,出了一个让人摸不到头脑的事情


5️⃣寻找问题


既然nvm切换版本已经成功,那么为什么node版本不会变,有没有可能根本改的不是同一个node,或者是存在两个node,直到我打开环境变量一看,为啥会存在两个node的路径,可能的原因就是之前的node版本没有删除,node -v一直输出的是安装前的node


image.png


原来已经安装了一个node的,全局的node指向的node路径和nvm切换node的路径是不一样的


nvm切换的node是基于他文件夹中的nodejs


image.png


image.png



点进去看你会发现他也是有一个node.exe的程序的,所以问题是已经找到的了,目前系统上出现了两个node,并且nvm切换的node版本并不是全局的node,因为环境变量已经指向了旧的node,他的版本不会改变,那么nvm去怎么切换都是没有用的



6️⃣解决方案


image.png


看了网上的一些解决方案都是要在nvm中新建两个文件夹的方式来解决,但是其实直接把nodejs删除也是一个很直接的办法,先通过where node找到当前的node的安装目录,直接进行删除


image.png


最后是通过把另外一个目录的node进行删除,重新看一下node的安装路径,也就是重新执行一下 where node


image.png


可以看到在nvm配置正确的情况下是能直接指向nvm下的node的


最后重新切换一下node的版本,也就是上文的操作


image.png


PS


我指的手动切换是nvm下载node版本之后手动去替换node_modules,原来大家觉得用nvm use也是手动替换(确实是我的问题),经过评论区广大jym指正,可以尝试一下volta这个工具来进行切换版本,真正做到不用手动替换,后续我会亲自去体验一下并且发文,感谢评论区小伙伴


🙏 感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请关注我的更新,给个喜欢和分享。您的支持是我写作的最大动力!


作者:一只大加号
来源:juejin.cn/post/7291096702021304354
收起阅读 »

OpenHarmonyMeetup2023深圳站圆满举办

10月15日,OpenHarmonyMeetup2023城市巡回深圳站活动,在深圳市科学馆隆重举办。活动由OpenHarmony项目群工作委员会主办,深圳市科技传播促进会承办,深圳市科学馆、触觉智能、广鸿会协办,电子发烧友、深开鸿、鸿湖万联、开鸿智谷提供支持,...
继续阅读 »

10月15日,OpenHarmonyMeetup2023城市巡回深圳站活动,在深圳市科学馆隆重举办。活动由OpenHarmony项目群工作委员会主办,深圳市科技传播促进会承办,深圳市科学馆、触觉智能、广鸿会协办,电子发烧友、深开鸿、鸿湖万联、开鸿智谷提供支持,吸引了深圳本地开发者与企业的广泛参与。本次深圳站Meetup重点围绕“OpenHarmony正当时-生态及技术应用”为主题做精彩深度分享。

OpenHarmonyMeetup深圳站签到现场

在深圳市科学技术协会的指导下,本次活动得到OpenHarmony项目群工作委员会批准,由深圳市科技传播促进会秘书长郝立芳作为出品人,携五位开源领域专家分别从六个维度进行了议题分享,并举办了以”开源论道“为主题的深度话题探讨。主要聚焦OpenHarmony技术及生态发展、OpenHarmony开源硬件探索、开源科普与开源人才培养、OpenHarmony产品商业应用场景、OpenHarmony商显领域发展、标准系统OpenHarmony硬件方案落地场景等维度,让更多开发者了解OpenHarmony项目,影响推动更多拥有热情和创造力的技术开发者加入到社区。

OpenHarmonyMeetup2023深圳站活动合影

深圳市科学技术协会党组成员、副主席石兴中出席并致辞,指出未来技术开源的发展以及当下推动OpenHarmony生态建设的重要性,从多方面鼓励和肯定了活动意义,同时也表示深圳市科学技术协会在学术交流和技术沙龙方面有很多支持政策,希望OpenHarmonyMeetup深圳站这样的活动能成为自主创新大讲堂等深圳市科协品牌活动的重要内容之一,成为深圳创新之城的技术交流活动亮丽名牌。深圳科学馆副馆长宋欠芽、科普部部长柳进材、深圳媒体研究会副秘书长胡志方共同出席本次活动。

深圳市科学技术协会党组成员、副主席石兴中发表致辞

第一个议题由来自广鸿会创始人、OpenHarmonyLoongArchSIG组长连志安带来《OpenHarmony生态及技术展望》主题演讲,重点介绍了万物互联时代下的技术变革、OpenHarmony技术架构和未来发展方向。连志安表示,软件是推动技术变革的重要推动力,在万物互联的时代背景下,以面向全场景、分布式的操作系统是构筑信息技术革命的基石。OpenHarmony作为新一代操作系统,以其三大技术特性,支撑万物互联产业的发展。

广鸿会创始人、OpenHarmonyLoongArchSIG组长连志安做主题分享

第二个议题由来自深圳市科技传播促进会秘书长郝立芳带来《OpenHarmony开源硬件的探索》主题演讲,介绍了开源大师兄项目、基于OpenHarmony的火星基地课程研发与设计,以及开源科普与开源人才培养整体体系建设。郝立芳表示,中小学科技教育启迪创新是主流,将开源原则和技术融入开源人才培养,培育技术人才从娃娃抓起,呼吁更多的开发者关注到青少年开发板的应用,为提升新一代开发者参与开源的能力而努力。

深圳市科技传播促进会秘书长郝立芳做主题分享

第三个议题由来自深开鸿生态合作总监王旭带来《OpenHarmony践行科技数字未来》主题演讲,介绍了基于OpenHarmony打造的KaihongOS在实际商业应用场景的落地,以KaihongOS构建数字化底座,赋能智慧城市、智慧港口等商用领域落地,展示OpenHarmony技术特性能力。

深开鸿生态合作总监王旭做主题分享

第四个议题由湖南开鸿智谷数字产业发展有限公司副总裁李传钊和开鸿智谷应用开发专家艾彬共同带来《OpenHarmony UI建设初探》主题演讲,他们从OpenHarmony能够在UI/UX层面做到什么样的高度、开发一套定制化UI又会面临哪些困难、跨设备的UI又有哪些关注点等问题出发,分享了OpenHarmony系统的UI建设关键技术特点使用和成果。

湖南开鸿智谷数字产业发展有限公司副总裁李传钊做主题分享

湖南开鸿智谷数字产业发展有限公司应用开发专家艾彬做主题分享

第五个议题由来自鸿湖万联开发总监何波带来《OpenHarmony在商显领域的创新思路分享》主题演讲,介绍基于OpenHarmony的商显解决方案及商显发展成果、趋势与展望。何波表示,期待OpenHarmony未来越来越好,生态越来越繁荣,同时为商显领域带来更多的发展,促进各个厂商通力合作,共同打造全新的基于物联网世界的商显新天地。

鸿湖万联开发总监何波做主题分享

第六个议题由来自深圳触觉智能科技有限公司副总经理朱经波带来《OpenHarmony硬件生态解决方案》主题演讲,介绍了标准系统OpenHarmony硬件方案落地场景,分享了基于OpenHarmony的当前硬件生态。朱经波表示,新的OS在传统硬件平台上将会焕发不一样的活力,触觉智能从微小处着手,打造高性价比的创客开发板和适合行业级的硬件系统解决方案,包含教育、智慧矿山、三防平板、手持终端、金融政务、工业控制器等,让更多创客加入到OpenHarmony的生态开发中来,让更多的软件开发公司充分聚焦行业及业务应用软件而不必考虑稳定可靠硬件设计的烦恼。

深圳触觉智能科技有限公司副总经理朱经波做主题分享

在本次活动最后,还举行了以“开源论道”为主题的圆桌论坛,围绕开源技术创新、从业方向、人才培养等话题,深入探讨了开源技术的发展与生态共建创新模式。

OpenHarmonyMeetup2023深圳站圆桌会议互动环节

OpenHarmony是由开放原子开源基金会(OpenAtomFoundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建下一代智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。OpenHarmony开源三年来,社区快速成长。截至2023年9月底,已有50家共建单位、累计超过5900名贡献者,累计产出超一亿行代码;累计已有156个厂家的413款产品通过兼容性测评,覆盖金融、超高清、教育、商显、工业、警务、城市、交通、医疗等领域。OpenHarmony社区已成为“下一代智能终端操作系统根社区”,携手共筑万物互联的底座,使能千行百业的数字化转型。

OpenHarmony社区,下一代智能终端操作系统根社区,扎根于和开发者面对面近距离交流,吸纳更多具备热情和创造力的技术开发者加入到社区,我们期待更多具备组织能力、领导能力的技术开发者和我们一起为OpenHarmony共同发声。如果你对开源充满激情,请带着你的活动方案联系我们,我们将为你提供更多活动能量,让你更加贴近OpenHarmony。

收起阅读 »

推荐一款“自学编程”的宝藏网站!详解版~(在线编程练习,项目实战,免费Gpt等)

🌟云端源想学习平台,一站式编程服务网站🌟云端源想官网传送门⭐📚精品课程:由项目实战为导向的视频课程,知识点讲解配套编程练习,让初学者有方向有目标。🎯📈课程阶段:每门课程都分多个阶段进行,由浅入深,很适合零基础和有基础的金友们自由选择阶段进行练习学习。🌈🎯章节实...
继续阅读 »

🌟云端源想学习平台,一站式编程服务网站🌟


云端源想官网传送门


📚精品课程:由项目实战为导向的视频课程,知识点讲解配套编程练习,让初学者有方向有目标。🎯


📈课程阶段:每门课程都分多个阶段进行,由浅入深,很适合零基础和有基础的金友们自由选择阶段进行练习学习。🌈


🎯章节实战:每一章课程都配有完整的项目实战,帮助初学者巩固所学的理论知识,在实战中运用知识点,不断积累项目经验。🔥


💼项目实战:企业级项目实战和小型项目实战结合,帮助初学者积累实战经验,为就业打下坚实的基础,成为实战型人才。🏆


729cc0d1da3852fe1d8a0eb81a6b357b.png


🧩配套练习:根据课程小节设置配套选择练习或编程练习,帮助学习者边学边练,让学习事半功倍。💪


💻在线编程:支持多种编程语言,创建Web前端、Java后端、PHP等多种项目,开发项目、编程练习、数据存储、虚拟机等都可在线使用,并可免费扩展内存,为你创造更加简洁的编程环境。🖥


🤝协作编程:可邀请站内的好友、大佬快速进入你的项目,协助完成当前项目。与他人一起讨论交流,快速解决问题。👥


📂项目导入导出:可导入自己在在线编辑过的项目再次编辑,完成项目后也可以一键导出项目,降低试错成本。🔗


🤖AI协助编程:AI智能代码协助完成编程项目,随时提问,一键复制代码即可使用。💡

ddcdaae2cab0bac546d4d195018866f0.png


🔧插件工具:在使用在线编程时,可在插件工具广场使用常用的插件,安装后即可使用,帮助你提高编程效率,带来更多便捷。🛠


📞一对一咨询:编程过程中,遇到问题随时提问,网站1V1服务(在职程序员接线,不是客服),实时解决你在项目中遇到的问题。📬


🛠工具广场:提供一些好用的在线智能工具,让你能够快速找到各种实用工具和应用。覆盖了多个领域,包括智能AI问答等。🔍


910fa68dda93dd2594530d0e5af268c7.pngd419dacb74e778686c40897862075ac8.png

收起阅读 »

推荐一款“自学编程”的宝藏网站!详解版~(在线编程练习,项目实战,免费Gpt等)

🌟云端源想学习平台,一站式编程服务网站🌟 云端源想官网传送门 ⭐ 📚精品课程:由项目实战为导向的视频课程,知识点讲解配套编程练习,让初学者有方向有目标。🎯 📈课程阶段:每门课程都分多个阶段进行,由浅入深,很适合零基础和有基础的金友们自由选择阶段进行练习学...
继续阅读 »

🌟云端源想学习平台,一站式编程服务网站🌟


云端源想官网传送门


📚精品课程:由项目实战为导向的视频课程,知识点讲解配套编程练习,让初学者有方向有目标。🎯


📈课程阶段:每门课程都分多个阶段进行,由浅入深,很适合零基础和有基础的金友们自由选择阶段进行练习学习。🌈


🎯章节实战:每一章课程都配有完整的项目实战,帮助初学者巩固所学的理论知识,在实战中运用知识点,不断积累项目经验。🔥


💼项目实战:企业级项目实战和小型项目实战结合,帮助初学者积累实战经验,为就业打下坚实的基础,成为实战型人才。🏆




🧩配套练习:根据课程小节设置配套选择练习或编程练习,帮助学习者边学边练,让学习事半功倍。💪


💻在线编程:支持多种编程语言,创建Web前端、Java后端、PHP等多种项目,开发项目、编程练习、数据存储、虚拟机等都可在线使用,并可免费扩展内存,为你创造更加简洁的编程环境。🖥


🤝协作编程:可邀请站内的好友、大佬快速进入你的项目,协助完成当前项目。与他人一起讨论交流,快速解决问题。👥


📂项目导入导出:可导入自己在在线编辑过的项目再次编辑,完成项目后也可以一键导出项目,降低试错成本。🔗


🤖AI协助编程:AI智能代码协助完成编程项目,随时提问,一键复制代码即可使用。💡



🔧插件工具:在使用在线编程时,可在插件工具广场使用常用的插件,安装后即可使用,帮助你提高编程效率,带来更多便捷。🛠


📞一对一咨询:编程过程中,遇到问题随时提问,网站1V1服务(在职程序员接线,不是客服),实时解决你在项目中遇到的问题。📬


🛠工具广场:提供一些好用的在线智能工具,让你能够快速找到各种实用工具和应用。覆盖了多个领域,包括智能AI问答等。🔍



收起阅读 »

在Flutter上封装一套类似电报的图片组件

前言 最近项目需要封装一个图片加载组件,boss让我们实现类似电报的那种效果,直接上图: 就像把大象装入冰箱一样,图片加载拢共有三种状态:loading、success、fail。 首先是loading,电报的实现效果是底部展示blur image, 上面盖...
继续阅读 »

前言


最近项目需要封装一个图片加载组件,boss让我们实现类似电报的那种效果,直接上图:


581697505195_.pic.jpg


就像把大象装入冰箱一样,图片加载拢共有三种状态:loading、success、fail。


首先是loading,电报的实现效果是底部展示blur image, 上面盖了个progress indicator。blur image有三方库可以实现:flutter_thumbhash | Flutter Package (pub.dev),但是这个库有个bug: 它使用到了MemoryImage, 并且MemoryImage的bytes参数每次都是重新生成的,因而无法使用缓存。所以上面的progress刷新时底部的blur image都会不停闪烁。


//MemoryImage
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is MemoryImage
&& other.bytes == bytes
&& other.scale == scale;
}

@override
int get hashCode => Object.hash(bytes.hashCode, scale);

笔者覆写了equals和hashcode方法,通过listEquals方法来比较bytes,考虑到thumb_hash一般数据量都比较小估计不会有性能问题。
也有人给了个一次性比较8个byte的算法【StackOverflow摘抄】😄


/// Compares two [Uint8List]s by comparing 8 bytes at a time.
bool memEquals(Uint8List bytes1, Uint8List bytes2) {
if (identical(bytes1, bytes2)) {
return true;
}

if (bytes1.lengthInBytes != bytes2.lengthInBytes) {
return false;
}

// Treat the original byte lists as lists of 8-byte words.
var numWords = bytes1.lengthInBytes ~/ 8;
var words1 = bytes1.buffer.asUint64List(0, numWords);
var words2 = bytes2.buffer.asUint64List(0, numWords);

for (var i = 0; i < words1.length; i += 1) {
if (words1[i] != words2[i]) {
return false;
}
}

// Compare any remaining bytes.
for (var i = words1.lengthInBytes; i < bytes1.lengthInBytes; i += 1) {
if (bytes1[i] != bytes2[i]) {
return false;
}
}

return true;
}

图片加载和取消重试


电报在loading的时候可以手动取消下载,这个在Flutter官方Image组件和cached_network_iamge组件都是不支持的,因为在设计者看来既然图片加载失败了,那重试也肯定还是失败(By design)。
extended_image库对cancel和retry做了支持,这里要给作者点赞👍🏻


取消加载


加载图片是通过官方http库来实现的, 核心逻辑是:


final HttpClientRequest request = await httpClient.getUrl(resolved);
headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
if (timeLimit != null) {
response.timeout(
timeLimit!,
);
}
return response;

返回的response是个Stream对象,通过它来获取图片数据


final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: chunkEvents != null
? (int cumulative, int? total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
}
: null,
);

图片加载进度就是通过ImageChunkEvent来获取的,cumulative代表当前已加载的长度,total是总长度,所有图片加载库都是通过它来显示进度的。所以,如何取消呢?这里就需要用到Flutter异步的一个API了:


Future.any(<Future<T>>[Future cancelTokenFuture, Future<Uint8List> imageLoadingFuture])

在加载的时候除了加载图片数据的Future,我们再额外生成一个Future,当需要取消加载的时候只需要后者抛出Error那加载就会直接终止,extended_image就是这么做的:


class CancellationTokenSource {
CancellationTokenSource._();

static Future<T> register<T>(
CancellationToken? cancelToken, Future<T> future) {
if (cancelToken != null && !cancelToken.isCanceled) {
final Completer<T> completer = Completer<T>();
cancelToken._addCompleter(completer);

///CancellationToken负责管理cancel completer
return Future.any(<Future<T>>[completer.future, future])
.then<T>((T result) async {
cancelToken._removeCompleter(completer);
return result;
}).catchError((Object error) {
cancelToken._removeCompleter(completer);
throw error;
});
} else {
return future;
}
}
}

这种取消机制有个问题:虽然上层会捕获抛出的异常终止加载,但是网络请求还是会继续下去直到加载完图片所有数据,我于是翻看了Flutter的API,发现上面提到的解析HttpResponse的方法consolidateHttpClientResponseBytes有个注释:


/// The `onBytesReceived` callback, if specified, will be invoked for every
/// chunk of bytes that is received while consolidating the response bytes.
/// If the callback throws an error, processing of the response will halt, and
/// the returned future will complete with the error that was thrown by the
/// callback. For more information on how to interpret the parameters to the
/// callback, see the documentation on [BytesReceivedCallback].

即onBytesReceived方法如果抛出异常那么就会终止数据传输,所以可以根据chunkEvents是否alive来判断是否需要继续传输,如果不需要就直接抛出异常,从而终止http请求。


重试


图片加载有两种重试:第一种是自动重试,笔者遇到了一个connection closed before full header was received错误,而且是高概率出现,目前没有好的解决办法,加上自动重试机制后好了很多。


第二种就是手动重试,自动重试达到阈值后还是失败,手动触发加载。我这里主要讲第二种,在电报里的展示效果是这样:


591697507850_.pic.jpg


这里卡了我好久,主要是我对Flutter的ImageCache了解不深入导致的,首先看几个问题:


1. 页面有一张图片加载失败,退出页面重新进来图片会自动重新加载吗?


答案是不一定,Flutter图片缓存存储的是ImageStreamController对象,这个对象里有一个FlutterErrorDetails? _currentError;属性,当加载图片失败后_currentError会被赋值,所以退出后重进页面虽然会导致页面重新加载,但是获取到的缓存对象有Error,那就会直接进入fail状态。
缓存的清理是个很复杂的问题, ImageStreamCompleter的清理逻辑主要靠两个属性:_listeners_keepAliveHandles


List<ImageStreamListener> _listeners = [];

@mustCallSuper
void _maybeDispose() {
if (!_hadAtLeastOneListener || _disposed || _listeners.isNotEmpty || _keepAliveHandles != 0) {
return;
}

_currentImage?.dispose();
_currentImage = null;
_disposed = true;
}

_listerners的add和remove时机和Image组件有关


/// image.dart
/// 加载图片
void _resolveImage() {
......
final ImageStream newStream =
provider.resolve(createLocalImageConfiguration(
context,
size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null,
));
_updateSourceStream(newStream);
}

void _updateSourceStream(ImageStream newStream) {
......
/// 向ImageStreamCompleter注册Listener
_imageStream!.addListener(_getListener());
}

既然有了_listeners那为什么还需要_keepAliveHandles属性呢,原因就是在image组件所在页面不在前台时会移除注册的listerner,如果没有_keepAliveHandles属性那缓存可能会被错误清理:


@override
void didChangeDependencies() {
_updateInvertColors();
_resolveImage();

if (TickerMode.of(context)) {
///页面在前台的时候获取最新的ImageStreamCompleter对象
_listenToStream();
} else {
///页面不在前台移除Listener
_stopListeningToStream(keepStreamAlive: true);
}
super.didChangeDependencies();
}

回到最开始的问题:如果加载失败的图片组件在其他页面不存在,那image组件dispose的时候就会清理掉缓存,第二次进入该页面的时候就会重新加载。反之,如果其他页面也在使用该缓存,那二次进入的时候就会直接fail。


一个很好玩的现象是,假如两个页面在加载同一张图片,那么其中一个页面图片加载失败另外一个页面也会同步失败。


2. 判定加载的是同一张图片


这里的相同很重要,因为它决定了ImageCache的存储,比如笔者自定义一个NetworkImage:


class _NetworkImage extends ImageProvider<_NetworkImage> {

_NetworkImage(this.url);

final String url;

@override
ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode);

@override
Future<ExtendedNetworkImageProvider> obtainKey();
}

obtainKey一般都会返回SynchronousFuture<_NetworkImage>(this),它代表的是ImageCache使用的键,ImageCache判断当前是否存在缓存的时候会拿Key和缓存的所有键进行比对,这个时候equals和hashcode就开始起作用了:


@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is _NetworkImage
&& other.url == url;
}

@override
int get hashCode => Object.hash(url);

因为我们需要支持取消加载,所以最初我考虑加上cancel token到相同逻辑的判定,但是这会导致同一张图片被不停重复加载,缓存完全失效。


为了解决上面的问题,我对ImageCache起了歪脑筋:能不能在没有加载成功的时候允许并行下载,但是只要有一张图片成功,那后续都可以复用缓存?
如果要实现这个效果,那就必须缓存下所有下载成功的ImageProvider或者对应的CancelToken。下载成功的监听好办,在MultiFrameImageStreamCompleter加个监听就完事。难的是缓存消除的时机判断,ImageCache的缓存机制很复杂(_pendingImages,_cacheImage,_liveImages),并且没有缓存移除的回调。


最终,我放弃了这个方案,不把cancel token放入ImageProvider的比较逻辑中。


3. 实现图片重新加载


首先,我给封装的图片组件加了个reloadFlag参数,当需要重新加载的时候+1即可:


@override
void didUpdateWidget(OldWidget old) {
if(old.reloadFlag != widget.reloadFlag) {
_resolveImage();
}
}

但是,这个时候不会起作用,因为之前失败的缓存没被清理,ImageProvider的evict方法可实现清理操作。


4. 多图状态管理


我在适配折叠屏的时候发现了一个场景:多页面下载相同图片时有时无法联动,首先看cancel:



  • A页面加载图片时使用CancelToken A,新建缓存

  • B页面使用CancelToken B, 复用缓存


B的CancelToken完全没用到,所以是cancel不了的。为了解决这个问题,我创建了一个CancelTokenManager,按需生成CancelToken,并在加载成功或失败时清理掉。


然后是重试,多图无法同时触发重试,虽然可以复用同一个ImageStreamCompleter对象,但ImageStream对象却是Image组件单独生成的,所以只能借助状态管理框架或者事件总线来实现同步刷新。


作者:芭比Q达人
来源:juejin.cn/post/7290732297427107895
收起阅读 »

你是先就业还是先择业?

就业的”就“不是让你将就   是不是大家常常听到家里人唠叨的一句话:“有工作就行了啊,别那么挑剔,你都这么大了,还指望着家里养着你啊” 。还是老师说:“我们要建立优先就业再择业的就业观,不能一直不去就业呀”什么的叭叭叭。   其实某方面来说他们并没有说错,我们...
继续阅读 »

就业的”就“不是让你将就


  是不是大家常常听到家里人唠叨的一句话:“有工作就行了啊,别那么挑剔,你都这么大了,还指望着家里养着你啊” 。还是老师说:“我们要建立优先就业再择业的就业观,不能一直不去就业呀”什么的叭叭叭。


  其实某方面来说他们并没有说错,我们已经成年了,需要独立自主。在漂亮国,到了18岁好像都要分家了吧。不过我们在中国,中国的国情肯定和漂亮国不一样。除此之外中国家庭从小的哭穷式教育,估计让许多孩子都想自己经济独立吧。这个现象导致了大家认为有工作就行了,我管他什么工作呢。


  但是从自身职业发展来讲,这是对自己极其不负责的表现,往往许多人的第一份工作就决定了人生轨迹,不论是以后决定发展的城市,还是以后工作的方向,其实已经早已埋下种子。你说你可以换工作啊,可以跳槽啊,现实往往会打醒你,你以为你没了应届生身份,凭着你那不到一年的工作经验,人家企业看中你什么。所以我们要就业要为自己,同时也要为自己的未来负责,我们要慎之又慎。所以我们要就业不过的选择自己合适的就业不能盲目就业,家长的思想大部分过时了,停留在了上了大学就有好工作的时代。我们只能参照而不能按部就班,对于老师,大部分是为了提高学校就业率完成指标而已,不必要太大理会,当然和你关系好的老师除外,但是相反如果和你关系好他一定会不会让你草草就业的。


u=1343747016,2016950934&fm=253&fmt=auto&app=138&f=JPEG.webp


择业的”择“也别太择



钱多事少离家近,位高权重责任轻。睡觉睡到自然醒,数钱数到手抽筋。



  说完就业再谈谈择业,相信上面这句话大家都听过,这简直就是梦中情职,所以择业在我看来无非四种:离家近的、工资高的、自己感兴趣的、清闲的。这四种涵盖了大部分职业了吧。所以我们怎么择业,选择一个适合自己的职位对于未来发展是事半功倍的。


  大家选择职业的时候不知道是从哪方面来选择的,首先离家近,相信很多女生都是考虑这个优先吧,感觉男生就是喜欢仗剑走天涯那种🤣。然后考虑清闲的,想想你二十几岁的年龄你还要工作四五十年可能,选个清闲一点的职业不过分吧,最好就是考一个公务员事业编了,实在不行就去央企国企了,当然这种工作大家都想去,虽然工资不高但是福利好啊。再者就是兴趣了,把自己的兴趣培养成自己的职业也是可以的,大学就是很好的时间,选那种课比较少的专业,这里不得不再次吐槽大学课程的无用性。然后自己选一个自己喜欢的职业,比如摄影、博主什么的。不过当喜欢的事变成职业很多人也就不喜欢了,比如电竞职业选手他们天天十几个小时训练打游戏,他们下班还会想打游戏嘛🤣。就是坚持很重要。再再者,有的人说自己天生无感对什么都没兴趣,那么恭喜你和我一样🤣,就是什么的不是很感兴趣,也不讨厌,那么我建议搞钱,选个高薪的职业搞到足够的钱就退休了,当初就是看程序员薪资高入行了,对钱总感兴趣了吧。总而言之择业择业选择自己合适的再就业。


  鱼和熊掌不可兼得。选择离家近的就得忍受小镇的慢节奏,没有快速的地铁,没有好玩的游乐场,有的只是街坊邻居的互相寒暄,没有夜晚的灯红酒绿,只有晚上八九点就安静的大街。选择清闲的公务员,那么就要懂的人情世故,还有每个月几千块钱的工资。选择自己感兴趣的,那么就得忍受孤独,经得起自我怀疑要有坚定的勇气。高工资的不用多说了吧,996,007时间就是金钱,加班是常态,通宵也是偶尔。所以没有哪份职业好坏,选择自己合适的,加油奋斗吧!


作者:过了三分之二年河东
来源:juejin.cn/post/7216729979622883389
收起阅读 »

北京十年,来深圳了

离开北京是计划 2013年去的北京,至今整十年,来去匆匆。 几年前就计划好了,赶在孩子上幼儿园之前离开北京,选一个城市定居。 给孩子一个稳定的环境,在这儿上学成长,建立稳定的、属于他自己的朋友圈。人一生中最珍贵的友谊都是在年少无知、天真烂漫的时候建立的。 我们...
继续阅读 »


离开北京是计划


2013年去的北京,至今整十年,来去匆匆。


几年前就计划好了,赶在孩子上幼儿园之前离开北京,选一个城市定居。


给孩子一个稳定的环境,在这儿上学成长,建立稳定的、属于他自己的朋友圈。人一生中最珍贵的友谊都是在年少无知、天真烂漫的时候建立的。


我们希望孩子从他有正式的社交关系开始-幼儿园阶段,尽早适应一个省市的教育理念和节奏,不要等到中小学、甚至高中阶段突然的打断孩子的节奏,插班到一个陌生的班级。他同时要面临环境和学业的压力,不是每个孩子都能很快调整过来的。


我自己小学阶段换了好几次学校,成绩的波动很明显,不希望孩子再面临同样的风险。


另一方面,基于我们年龄的考虑,也要尽快离开,岁数太大了,换城市跳槽不一定能找到合适的岗位。


19年,基于对移动端市场的悲观,我开始考虑换一个技术方向。2020年公司内转岗,开始从事图形相关技术开发,计划2023年离开北京,是考虑要留给自己3年的时间从零开始积累一个领域的技术。


来深圳市是意外


这几年一直在关注其他城市的"落户政策"、"互联网市场"、"房价"、"政府公共服务"。有几个城市,按优先级:杭州、广州、武汉、深圳。这些都是容易落户的城市,我们想尽快解决户口的困扰。


看几组数据:




2023年5月份数据


可以看到,杭州的房价排在第6位,但是收入和工作机会排进前4,所以首选杭州,性价比之王。


广州的房价和工作收入都排第5,中策。


武汉的工作机会排进前10,但是房价在10名开外,而且老家在那边,占尽地利,下策。


深圳的房价高的吓人,和这个城市提供的医疗、教育太不匹配,下下策。


最后选择深圳是形势所逼,今年行情史上最差,外面的机会很少。我和老婆都有机会内部转岗到深圳,所以很快就决定了。


初识深圳


来之前做了基本的调研,深圳本科45岁以内 + 1个月社保可以落户。我公司在南山,老婆的在福田,落户只能先落到对应的区。


我提前来深圳,一个星期租好了房子,确定了幼儿园。


老婆步行15分钟到公司,孩子步行500米到幼儿园,我步行 + 地铁1小时到公司。


福田和南山的教育资源相对充足,有些中小学名校今年都招不满,租房也能上,比龙华、宝安、龙岗等区要好很多。


听朋友说,在龙华一个很差的公立小学1000个小孩报名,只有500个学位。


有不少房东愿意把学位给租户使用,办理起来也不麻烦,到社区录入租房信息即可。和北京一样,采取学区划分政策,按积分排名录取,非常好的学校也要摇号碰运气。


租房


中介小哥陪我看了三四天房子,把这一片小区都看了个遍。考虑近地铁、近幼儿园、有电梯、装修良好等因素。


我本来想砍200房租,中介说先砍400,不行再加。结果我说少400,房东直接说好。我原地愣住了,之前排练的戏份都用不上了,或许今年行情不好,租房市场也很冷淡吧。


小区后面是小山,比较安静。


小区附近-0


小区附近-1


小区附近-2


小区附近-3


外出溜达,路过一所小学


深圳的很多小区里都有泳池
小区-泳池


夜晚的深圳,高楼林立,给人一种压迫感,和天空格格不入。明亮的霓虹灯,和北京一样忙碌。


晚上8点的深圳


晚上10点的深圳


对教育的看法



幸运的人一生都被童年治愈,不幸的人一生都在治愈童年--阿德勒



身边的朋友,有不少对孩子上什么学校有点焦虑,因为教育和高考的压力,有好友极力劝阻我来深圳。我认为在能力的范围内尽力就好,坦然面对一切。


焦虑是对自己无能为力的事情心存妄念。 如果一个人能坦然面对结果,重视当下,不虚度每一分每一秒,人生就不应该有遗憾。人生是来看风景的,结局都是一把灰,躺在盒子里,所以不要太纠结一定要结果怎么样。


学校是培养能力的地方,学历决定一个人的下限,性格和价值观决定上限,你终究成要为你想成为的人,不应该在自我介绍时除了学历能拿出手,一无是处。


不少人不能接受孩子比自己差。可是并没有什么科学依据能证明下一代的基因一定优于上一代吧,或许他们只是不能接受孩子比他们差,他们没有面子,老无所依。我天资一般,我也非常能接受孩子天资平庸,这是上天的旨意。


有些父母根本没有做好家庭教育,试图通过卷学校、一次性的努力把培养的责任寄托于学校。挣钱是成就自己,陪伴是成就孩子,成功的父母居中取舍。


陪伴是最好的家庭教育,如果因为工作而疏忽了孩子,我认为这个家庭是失败的,失败的家庭教育会导致家庭后半生矛盾重重,断送了全家人的幸福。


一个人缺少父爱,就缺少勇敢和力量,缺少母爱就缺少细腻与温和,孩子的性格很容易不健全。除非他自己很有天赋,能自己走出童年的阴影。


因为他长大面对的社会关系是复杂的,他需要在性格层面能融入不同的群体。性格不健全的孩子更容易走向偏激、自私、虚伪、或者懦弱,很多心理学家都是自我治疗的过程中,成为心理学大师。


一个人的一生中,学历不好带来的困扰是非常局部的,但是性格带来的问题将困扰其一生,包括工作、交朋结友、娶妻生子,并且还会传染给下一代。


榜样是最好的教育方法,没有人会喜欢听别人讲大道理,言传不如身教。有些人自己过的很可怜,拼命去鸡娃,那不是培养孩子,那是转移压力,过度投资,有赌棍的嫌疑。你自己过的很苦逼,你如何能说服孩子人生的意义是幸福?鸡娃的尽头是下一代鸡娃。


你只有自己充满能量,积极面对人生,你的孩子才会乐观向上;你只有自己持续的阅读、成长,你的孩子才会心悦诚服的学习;你只有自己做到追求卓越,你的孩子才会把优秀当成习惯。


不要给孩子传递一种信号,人生是苦的,要示范幸福的能力,培养孩子积极地入世观。


作者:sumsmile
来源:juejin.cn/post/7248199693934985272
收起阅读 »

2023年,28岁技术人关于职业和生活的思考

2023.9.29中秋节早晨,4个月的宝宝在旁边陪伴着我,记录了28岁一路以来的变化与成长。 关于我 5年前端,现定级为中级前端工程师,有一个很可爱的儿子,小名甜筒🍦,同时还有一个富有智慧的妻子,没错!我用了智慧一词,因为她在能力、为人处事、情绪管控以及子女...
继续阅读 »

2023.9.29中秋节早晨,4个月的宝宝在旁边陪伴着我,记录了28岁一路以来的变化与成长。



关于我


5年前端,现定级为中级前端工程师,有一个很可爱的儿子,小名甜筒🍦,同时还有一个富有智慧的妻子,没错!我用了智慧一词,因为她在能力、为人处事、情绪管控以及子女教育方面优于一般人,这是我的荣幸~


职业发展


关于中级这个头衔,我并不是很满意,在我的规划中,五年作为一个成长的阶梯,仅仅在前端这个领域,应该是要成长为高级开发,毕竟人生能有几个黄金五年呢?


晋升


在我前三年的工作经历中,的确走了一些坎坷,在第二年的时候就已经担任了某私企前端leader的角色,在技术并不是特别拔尖的情况下去做管理工作,属实有点困难,毕竟要兼顾的东西多了,难免有些手忙脚乱,加上当时作为一个技术人的性格胜任这份工作,在沟通上还是吃了蛮多亏,说白了就是需要情商,即做事让人感动,说话让人舒服。关于前半句,我是感动了自己,而后半句呢,虽然没有拉仇恨,但还是差强人意!


感悟


当然了,犯错伴随着成长,在我看来这个过程中最大的收获大于自己的情绪管理思维方式提升。


作为技术人内敛的性格在遇到问题时容易产生自我怀疑,内耗随之产生,而《非暴露沟通》给了我很多启发,不管是在工作或者家庭关系处理中增加了不少润滑油,以至于后面认识我的朋友给我贴了标签:情绪稳定逆商高


其实,更大的成长在于第四年,2022~2023年经历了父亲病痛、职业生涯选择和组建家庭。


2022年初,我背上了房贷,那时候的薪资不高,只有16k左右,而房贷是一个非常吉利的数字:6666,与此同时父亲生病几个月期间让我意识到自己抗风险能力太低,加上自己除了在技术领域关注甚多,对于其他方面一窍不通,谈时事新闻,我不关注;谈房子车子,我不懂;谈商业逻辑,那真是咸菜炖豆腐,不必多言!


于是对自己的职业生涯重新定位,除了技术管理这条路,我还能够做什么?对于家人,我又有多少时间陪伴?最终,我选择了离职,在一年空窗中,我尝试了其他非技术领域的项目,跟客户谈合作,跟人打交道,到处出差谈客户,而正是在这段经历中,我懂得了几点:


人性不可挑战:利益能够驱使人变的险恶,有些人为了金钱,不断打破自己的原则,触碰别人的底线,最后两败俱伤!


人的一生都在为自己的认知买单:我在房价最高的时候选择了入手,是因为听信于别人说现在不买以后房价更高了啊,谁谁谁买了不久之后涨了xxx倍啊,在没有多选择对比几个楼盘之后就入手,利率5.85%,同时还被忽悠可以返佣金,大概有三四万,当时还眼前一亮,但只是口头约定,并没有白纸黑字立据,随后就比较随意签了一大堆合同,其中包括首付贷利率10%,同时销售承诺免3年物业费,猴子吞大象,亏他张得开嘴,唯独就这次办事没有录音,最后签合同时一口咬定没有这回事,吃了哑巴亏;还有补充协议,写着房产证要在交房后720天之后才能拿到,这是最致命的,真是谢天谢地谢广坤


与此同时,做技术也是一样,某个阶段因为自身技术能力、认知缺陷在技术选型或方案选择上存在误差,在一段时间之后产生了历史债务,以往业务在小程序框架选型上就栽了跟头,在没有做过多的调研之后选择了一个组内成员比较熟悉的框架,在后面的业务发展中逐渐显得鸡肋,如不支持多端,框架出现断崖式更新,社区不活跃等等问题,所以在技术选型方面,我们需要考虑包括但不限于以下因素:业务发展方向、行业解决方案对比、解决了什么问题、组内成员的熟悉度、社区活跃度等等。


重回技术


看了不少行业,见了不少人,对于一些思维框架也在实际中得到验证,掌握一些思维模型在解决工作生活问题更加快捷,比如MECE法则结构化思维透过现象看本质逻辑链思维等等,站在更高的视角看问题,我重新选择技术道路,在互联网大裁员环境下,基础薪资涨幅也超过40%。着力于以下几点:



  • 对于接触新的技术,一上来撸文档或看视频,是一种低效并且不持久的学习方式,零碎的知识点就像是每一个神经元,相互没有连接成网络最终会形成一盘散沙,大脑并不擅长处理这种结构。而要多方面去渗透理解,如发展历史,着重解决哪些问题,相比其他类似技术,优势在哪?从这些角度入手,容易形成自己的知识框架。例如,在node生态中,express解决了什么问题?之后为什么又诞生孪生兄弟koa,两者有什么区别?后起之秀nest凭借什么能够脱颖而出,成为目前最流行企业级框架之一?按照这个逻辑链,梳理形成属于自己的知识体系。

  • 源动力,一切行动背后都应该有充分的理由。我的努力工作目的就是在大城市有属于自己的家,想让家人过得更好,让下一代起点高一些,同时摆脱原生家庭因素的影响。在我看来,即便做最基础的工作,都应该思考为什么而做。


生活


生活的琐碎会消磨人的意识,磨平人的棱角,正是因为这样,乐观看待未来的心态决定了一个人成长的速度。


就在前一晚还跟妻子争吵了一番,源于我的原生家庭并没有给予她应该有的尊重和对待,受到的不公平对待,感觉到委屈,这是我作为中间人失责的一方面。


过去的一年中,我们遭受了别人的诋毁、否定和质疑,但最后我们还是走向了婚姻的殿堂,还有了一个很乖、很可爱的宝宝,这是我们最大的欣慰。时间从来不语,但它会回答所有问题!


生活的逐渐变好源于家庭每一个成员的共同努力,很不庆幸在我原生家庭中存在一些负能量的人,她们的内耗能够将家庭其他成员的能量都消耗殆尽,当然啦,我无法改变这些人,但可以通过运动、鸡汤和励志电影不断让自己保持高能量状态,同时减少或不接触这类人,愿我们都能够成为高能量场的人。


总结与展望


失败是成功之母,总结是成功之父。复盘是我成长最大的途径,极客时间中也有关于该话题的专栏,推荐一看。同时,在我看来经历、改变是最宝贵的财富,是我心智成熟的一方面,源于《谁动了我的奶酪?》


在不久后的将来,当宝宝看到这篇文章的时候,他可以清楚地知道爸爸的过去以及此时所感所悟,所言所想,为他以后的道路再点亮一颗星星⭐️。


作者:寻找奶酪的mouse
来源:juejin.cn/post/7283749823429247013
收起阅读 »

程序员入行感触二三事

引言 好久没有发感触了,之前一直在做讲师授课,接触了好多入门的程序员,有很多感触,但是在做讲师的时候有时候不方便说,在做开发又开始忙,所以就沉淀下来了,忽然今天收到了之前一个学习的小伙伴的消息,心里有些触动,本人也不是一个特别喜欢发朋友圈的人,但是总感觉想说点...
继续阅读 »

引言


好久没有发感触了,之前一直在做讲师授课,接触了好多入门的程序员,有很多感触,但是在做讲师的时候有时候不方便说,在做开发又开始忙,所以就沉淀下来了,忽然今天收到了之前一个学习的小伙伴的消息,心里有些触动,本人也不是一个特别喜欢发朋友圈的人,但是总感觉想说点啥(矫情了,哈哈),所以写写做一个回顾吧。


编程行业从开始到现在被贴上了很多标签: 幸苦,掉头发,工资高,不愁工作等等,这些有好有坏,但是总结起来大多数人对编程行业的认知是:


1、需要一定的学历,尤其对数学和英语要求很高。


2、工作比较累,加班是便饭。


3、收入很可观,10k轻轻松松。


4、岗位比较多,是一个搞高级技术(嘿嘿嘿,之前一个家长和我聊的)的行业。


当然还有很多,但是就是上面这些认知让好多毕业迷茫、家境一般、工作遇到问题的人,把编程行业作为了一个全新开始的选择。于是,就有了市场,有了市场很快就有了资本,有了资本很快就有了营造焦虑氛围的营销策略,然后就有各种各样在掩盖在光鲜下的问题,又得真的很无奈,那么今天就聊聊吧。


问题


1、社会是你做程序员的第一绊脚石


啥意思,啥叫做社会,这里的社会不是一个群居的结构,而是人情世故(嘿嘿嘿),好多小伙伴是转行过来的,老话说的好,人往高处走,水往低处流,大部分转行的小伙伴不是来自于大家认知当中更好的行业(比如:公务员,医生,律师..嘿嘿嘿,扯远了),甚至编程本行业的也很少(程序员自学的能力还是很不错的),所以大家在学习之前就已经在社会上摸爬滚打了很久,久历人情,好处是好沟通,不好的地方就是真的把人情世故看的比技术更重要了,这一点可能拉低这些小伙伴70%的学习效果,你要明白,程序员这个行业确实也有人情世故,但前提是大家可以在一个水平上,这个水平可以是技术,也可以是职级,但是如果开头就这么琢磨的话,没有一个扎实的编程基础,真的很难立足在这个行业。没有必要的谦让,习惯性的差不多损耗了太多的学习效果了,既然选择编程,首先请把技术学好,哪怕是基础(当然那个行业也会有浑水摸鱼的,但是对于转行的小伙伴来说,概率太低了)


2、学历重要,学力也很重要


编程行业是一个需要终生学习的行业,不论是前端,后端,测试,运维还是其他岗位,如果在做技术就一定需要学习,好多人会说学历不够所以干不了编程,但是在我个人的眼里,学历确实重要,但是并没有完全限制你进入编程行业,因为:


(1)任何行业都是有完整的岗位结构的,需要的高精尖人才是需要的,但是普通的岗位也少不了,编程行业也是如此,有些岗位的学历要求不是很高。


(2)在编程行业除了那些竞争激烈的大厂,自考学历是有一定的市场和认可程度的


但是,在学历背后的学力就不是这样一个概念了,这里想表述的是学习能力,包括:


(1)专注能力,好多小伙伴如果之前有一定的社会经历或者在大学过的比较懒散,在没有聊到学历之前,先决条件就是能静下心来学习,但是很多小伙伴专注力根本不达标,听课走神,练习坐不住...(其实个人感觉任何一个行业,能静下心来做,并且活下来的都不会很差)


(2)学习习惯,这里不贬低学历低的小伙伴,但是不能否认的是,参加高考后获得一个高学历的小伙伴能力不谈,但是99%都有一个很好的学习习惯。比如不会在学习的时候把手机放到旁边,科学的记笔记,有效的复习和预习等等,所以在担心学历之前,请先培养好自己的学习习惯(个人建议,如果真的没有一个好的学习习惯,那么学习的时候就不要在眼前出现多余的东西分散注意力,比如: 课桌上除了听课的电脑,不要有其他的,之前见过的容易分散注意力的:手机,水杯,指尖陀螺,魔方....)


3、不要在没有选择能力的时候做出选择


这里想聊的是一些学习恐慌的小伙伴的惯性,好多小伙伴在选择了一种学习方式(买书,看视频,加入培训班)之后,还会进行类比学习,比如:买了Python的一本基础书,然后再大数据或者小伙伴的推荐下又买了另外一本,或者参加了培训班,又去看其他的教学视频,这些对小白同学的学习伤害会很大,因为,本身对技术没有全面的理解,不同的书,不同的教程传递的教学方法是不一样的,混着来有点像老家喝酒掺着喝,白酒不醉,啤酒不醉,白加啤那么就不一定了(很大概率会醉),所以小白同学最总要的不是再学习的过程当中进行对比,而是可以最快最稳的完成基础感念的学习,在自己脑子当中有了基础概念再做选择。


当然了,还有很多,一次也聊不完,之后有时间再聊吧,今天就先写这么多,欢迎大家讨论交流。


作者:老边
来源:juejin.cn/post/7174259081484763173
收起阅读 »

大厂996三个月,我曾迷失了生活的意义,努力找回中

作为一个没有啥家底的小镇做题家,在去年选Offer阶段时,工作强度是我最不看重的一个点,当时觉得自己年轻、身体好、精神足,996算起来一天不过12个小时,去掉吃饭时间,一天也就9到10个小时,完全没有任何问题,对当时热衷于外企、国企、考公的同学非常的不理解,于...
继续阅读 »

作为一个没有啥家底的小镇做题家,在去年选Offer阶段时,工作强度是我最不看重的一个点,当时觉得自己年轻、身体好、精神足,996算起来一天不过12个小时,去掉吃饭时间,一天也就9到10个小时,完全没有任何问题,对当时热衷于外企、国企、考公的同学非常的不理解,于是毫不犹豫的签了一个外界风评不太佳但是包裹给的相对多的Offer,然后便有了现在的心酸感悟。


入职前的忐忑


在一波三折终于拿到学位证后,怀着忐忑的心入职了。忐忑的原因主要是入职之前我并不知道我要入职什么部门,很怕我一个只会Java的后端被分配去写C++,毕竟Java是最好的语言,打死不学C++(手动狗头)。 也担心被分配到一个没有啥业务复杂度、数据复杂度的业务部门,每天CRUD迎接着产品的一个又一个需求,最后活成了CRUD Boy没有什么技术沉淀。又担心去和钱相关的部门,害怕自己代码出Bug,导致公司产生资损。


就这样忐忑着听完了入职当日上午的培训,中午便被我的mentor接走了,很不幸,我被我的mentor告知,我恰好被分在了和钱最直接相关的部门,我的心陡然沉重起来。


这里给出我最诚挚的建议,选Offer最好选一个实习过的部分,次之就是签了三方后去实习一段时间,如果发现部门的味儿不对或者和自己八字不合,此时还有机会跑路,以很小的代价换一家公司,不然毕业后入职就宛如开盲盒,万一遇到了很不适应的部门,交了社保、几个月的工作经验,到了市场上可谓是爹不疼娘不爱,比着校招时候的境遇差太多了。


熟悉项目的第一个月


得益于和钱相关,我部门的需求都不是很紧急,领导的态度是宁愿需求不上,也不能上出问题。所以每一个需求都没有说产品火急火燎的推动着要上线,都是稳扎稳打的在做,给予了开发比较充足的自测时间。但是呢,另外一方面,由于部门的业务领域比较底层,所以接手的项目往往都已经有了两三年的历史,相信写过代码的同学都知道,写代码的场景中最痛苦的那就是读懂别人的代码然后在别人的基础上进行开发,尤其是读懂几年前已经几经几手的项目代码。


第一个月刚进入公司,只是在熟悉项目代码,没有啥需求上的压力,相对来说还是比较轻松。遇到不熟悉的直接问靠谱的mentor,mentor也给热心的解答还是很幸运的。每天吃完公司订的盒饭。下楼转悠一圈就觉得美滋滋。


这个时候其实觉得,996不过如此嘛,好像也没有啥压力,真不搞不明白有啥可怕的。


进入开发状态的第二个月


在熟悉的差不多后,我就开始慢慢接手业务需求了,坦白的说,由于我接手的项目比较成熟,新接入业务需求往往不需要做什么开发工作,只需要做一些配置项,需求就完成了。 然而呢,作为一个几年的老项目,当然是处处埋的有彩蛋,你永远不知道哪里就会给你来一个惊喜。于是呢,我的工作开始变成,寻找代码中的彩蛋,搞明白各个配置项的含义,以及他们究竟是怎么组合的,然后和上下游联合对数据,发现数据对不上,就需要再埋进项目中一丝一缕的分析。


这个时候已经有些许的压力了,如果因为自己成为整个需求的卡点,那太过意不去了。于是开始每天勤勤恳恳,吃盒饭也没有那么香了,饭后散步的脚步也不再那么的愉悦,这时候开始感受到了肩上的压力。


本来我是坚决第一个离开工位下班,决心整治职场的人,但是往往在debug的路上,不经历的就把下班时间延长了一点又一点。而又由于在北京、上海这种大城市,住在公司旁边往往是一种奢望,导致我每天有较长的通勤时间。工作日一天下来,差不多就是晚上回去睡觉,早上醒来没有多久就出门赶地铁。


日复一日,就有一种流水线上螺丝钉的麻木感,周末往往一觉睡醒就结束了,感觉日子很重复,没有一些自己生活过的痕迹。


努力调整状态的第三个月


积极主动,是《高效能人士的七个习惯》中的第一个习惯,也是我印象最深的一个习惯。既然困难无法克服,那么咱们就要主动解决。
alt


工作中,努力开拓自己的视野,搭理好手中的一亩三分地的同时,仰头看看上游,低头往往下游,对他们的业务也多一些学习,理清楚自己工作的业务价值,同时呢,在当好一名螺丝钉之外,也尝试着找出整个流水线的可优化点和风险点,尝试着给出自己的解决方案,同时积极梳理已有的项目代码的技术难点,是如何通过配置化来应对复杂的业务场景,是如何通过自动重试保证数据一致性。


生活中,周末即使比较累了,但是努力也不再宅在家中,一刷手机一整天,而是尝试着做一些比较有挑战或者更有回忆的事情。比如沿着黄浦江骑行。
alt


比如自己下厨做几个菜


alt


比如邀请三五好友玩个桌游


alt


比如通过图书馆借一些杂书来消遣


alt


对后来人想说的话


部门与部门之间的差异,很有可能比公司之间的都要大,选择Offer尽可能的选一个实习过的、或者比较熟悉的部门,能有效避免开盲盒踩雷的风险。没有绝对完美的公司,即使好评如潮的外企、券商类公司,我仍然有一些不幸运的同学,遇到了很卷的部门,平时要自愿加班或者在公司“学习”。


即使遇到了困境,也需要保持积极良好的心态,退一万步想,即使工作丢了,但是咱们的身心健康不能丢。为了这几斗米,伤了身体,是非常得不偿失的。


在选Offer的时候尽量一步到位,以终为始,如果目标瞄定了二线城市,其实我个人不太建议为了某些原因比如对大厂技术的热衷、对一线城市繁华的向往而选择当北漂沪漂,漂泊在外的日子都比较苦,而且吃这种苦往往是没有啥意义的。



我是日暮与星辰之间,一名努力学习成长的后端练习生,创作不易,求点赞、求关注、求收藏,如果你有什么想法或者求职路上、工作路上遇到了什么问题,欢迎在评论区里和我一起交流讨论。



作者:日暮与星辰之间
来源:juejin.cn/post/7159960105759277070
收起阅读 »

谈谈成长

背景 距离毕业到已经三年多了, 距离实习到现在已经三年半了, 在主管的建议下, 8月17号在公司的研发部门做了 一场关于成长的分享, 从工作方式到技术能力提升对自己三年的成长进行了一个复盘, 幸运的是分享得到的反 馈非常好, 老板看了将录屏在整个研发部进行公布...
继续阅读 »

背景


距离毕业到已经三年多了, 距离实习到现在已经三年半了, 在主管的建议下, 8月17号在公司的研发部门做了
一场关于成长的分享, 从工作方式到技术能力提升对自己三年的成长进行了一个复盘, 幸运的是分享得到的反
馈非常好, 老板看了将录屏在整个研发部进行公布并且推荐大家观看, 这是部门内分享活动以来第一次公示与推荐录
屏的情况, 我自己也感觉非常感慨, 毕业以来, 一直朝着“一年入门, 三年高工、五年资深、七年架构”的目
标前进, 有时候回过头来会发现这一路还是挺有意思的, 借着这样一个平台, 以文字的形式描述出来, 期望
能够给前行路上的各位有一定的帮助


下面的描述中, 为了不暴露个人信息, 对公司名称的描述统一替换为 XX, 涉及到人名的我都会进行打码


01.png


一、为什么做这个分享(why)


1.1、原因一


主管说从实习加入XX三年半以来, 看到我成长了很多, 推荐我可以做一下关于个人成长这一块的分享


1.2、原因二


我个人仔细的想了一下, 三年半前加入XX, 当时公司才400多人, 没有一个正式的java团队(公司主c#), 三年半的时间, 公司发展到了750多人, 我经历了java团队从0到1的完整过程(目前已经有3个java团队, 加起来有接近30个java开发), 并且在这三年半的时间, 公司的后端业务项目开始从.net往java迁移, 有大量的重构项目和新的大型项目, 在此期间, 经历了可能有10个以上从0到1的大型项目的落地, 并且这些项目中我个人承担了绝大部分的核心功能开发, 在这些项目的锻炼下, 不管是技术还是工作方式方面, 都有学习到非常多的知识, 借着这样的机会复盘一下, 继续完善自己, 我主观认为这样的经历可能会有一定的参考价值, 期望能够通过这样的一个分享, 能够给一些可能遇到瓶颈或者跟我遇到一样问题的同学一些启发


二、工作方式上的成长及建议


2.1、需求方案的决策


Pre: 遇到问题 / 需求我应该怎么做, 还没有独当一面的能力


Now: 遇到问题 / 需求我思考可以怎么做, 并从自己的角度出发提出方案, 标识每个方案的优缺点, 询问应该怎么做(即使提出的方案都不是最优解, 但是有自己的思考, 并且在遇到更合适的方案时能有一个对比学习)


截图是两个例子, 在我实现功能的时候, 有不同的方案, 在把握不准哪种方案比较合适的情况下, 向主管进行询问, 对可行的方案的优缺点进行分析, 在主管多年的经验下确定最终的实施方案


02.png


03.png


2.2、需求功能的实现和上线


Pre: 需求处理完, 简单的自测或者压根就不自测, 测试流程走完以后发上线就不管了(在工作过程中其实有遇到许多同事也是这样), 在刚来实习以及刚毕业那段时间, 一个功能编码完以后测试反馈了许多的bug问题, 改了一个旧的出现一个新的, 之前有听过一个段子, 测试听到开发说的最多的是什么(我没改代码 / 网络问题 / 你再试试), 这个段子就在我身上出现了, 当时改一个功能, 连续几次都没改好(因为改完后没有充分验证就提测了), 见下图(2021年的聊天记录)


Now: 需求处理完, 充分的自测, 提测以后多次跟进测试同学的测试情况, 上线后跟进线上是否正常使用, 找产品进行验收流程


Suggest: owner意识、需求从哪里开始就从哪里结束, 回调产生事件的人, 形成闭环


owner意识是主管在小组中提出来的一个工作方式, 是说一个开发, 在多人协作的场景下, 不能只关注自己那一块的功能, 需要对整个流程有充分的了解, 以一个owner的角色参与到项目开发中, 跟进其他端的对接, 把控整个项目的进度, 要做到这个其实是很难的, 需要花费更多的精力, 但是一旦做到了, 对整个项目就有一种非常清晰的感觉, 能够更好的完成多人协作项目的落地, 需求从哪里开始就从哪里结束, 每次一个需求处理完以后, 我会严格按照 自测 -> 提测 -> 联调 -> 督促产品验收 -> 灰度环境发布及验收 -> 正式环境发布及验收 -> 同步所有有关开发自己的功能的发布情况


在这样一个闭环的链路中, 功能的落地和稳定有了非常明显的提高


04.png


2.3、句句有回应, 事事有着落


Pre: 忙的时候忘记回消息, 事情多的时候忘记一些临时分配的事情, 遇到的坑重复跳


Now: todoList、QQ置顶, 多检查(防止多次返工), 防止重复的问题重复出现(错别字)


todoList:
我是通过一个txt文件来进行一个记录的(当然有更合适的, 只是用习惯了), 会记录手上的需求有哪些, 每一个的进度是怎么样的, 上线流程是怎么样的, 我给自己定的上线流程中分为以下几步, 并且在上线的过程中严格的按照下面的流程进行操作, 这样极大的提高了上线的稳定性(特别是一些大版本的发布, 有这样的严格流程下, 稳定性有了非常明显的提高)


1、是否有旧数据需要处理


2、是否有sql脚本需要执行


3、是否依赖于其他人的功能


4、是否有单独服务器灰度的需求, 有些业务需要单独一台服务器来进行灰度, 然后通过nginx将部分流量转发过来验收


5、开始灰度, 灰度后产品验收, 全部负载发布完毕后回调需求开始的人, 形成闭环


QQ置顶: 我们公司是采用QQ作为沟通工具的(可能是因为很早之前就是用这个, 即使现在有企业微信, 但是沟通这一块大家都不太倾向于切到企业微信), 当在外面或者遇到其他比较紧急事情时有人找或者发消息, 可能看完就忘记回了, 于是我养成一个习惯, 有人发消息没时间回的, 第一时间置顶, 等有空后再来跟进, 跟进完后取消置顶


防止重复的问题重复出现: 我们需要写周报, 刚开始的时候有出现错别字、格式不正确、周报字数过多的情况(主管规定周报不能超过250个字), 后面为了避免这种情况, 我每次写完都会阅读两遍内容, 防止之前产生的问题重复发生, 每个人都有粗心的时候, 也不能保证不出现错误, 但是我们可以通过一些方式方法来尽量的避免错误的重复发生


2.4、学习别人优秀的地方


人无完人, 比如我有时候说话会比较直接(好吧, 我是直男....), 那时主管就让我在沟通这一块多跟斜对面的同事学习一下, 后面我就观察他的沟通方式, 然后自己从模仿开始, 慢慢的也学会了怎么进行有效的沟通、委婉的拒绝等, 学习别人优秀的地方也是成长中比较重要的一环


2.5、批量接口的重要性


Pre: 对需求的时候遇到一个问题就问一次, 效率低, 刚来实习的时候, 不熟悉业务, 当时接触到一块比较复杂的涉及到购物车优惠计算的业务, 当时跟对方讨论的时候, 遇到一个不懂的就直接去问, 多次打断他的工作, 效率非常低


Now: 对整个需求进行梳理, 整理所有的疑问一次性请教, 效率提高了很多, 也不会容易打断别人的工作(反到现在我经常被别人打断=,=看到了曾经的我....)


2.6、做的事太简单, 没有挑战性


今年带了一个实习生, 刚开始给他分配的工作都是比较简单的, 主要是修bug、小需求, 但是即使是这样简单的功能, 他做的也是磕磕碰碰, 比如代码规范不达标、空指针异常判断不全面、业务涉及到的点想的不全面等等许多瑕疵, 多次需要我来进行一个兜底, 后面他跟HR反馈说太简单没有挑战性, 于是我就分配了一个稍微大的一点需求给他, 结果做的惨不忍睹, 后面他实习结束时这个需求才做到一半, 我转给其他同事帮忙接手....结果那个同事看到代码后对我吐槽了许久....


当时收到反馈后, 我及时的找他进行沟通, 我的观点是他做的每一个简单的需求, 需求的完整稳定上线都是为了给主管建立一个做事靠谱的印象, 如果做事不靠谱, 经常需要他人来进行兜底, 那么谁也不敢把复杂重要的任务交给你做, 公司很多业务都涉及到商家的钱, 一旦这种重要的业务出问题, 那么会给公司造成巨大损失, 其实仔细想想, 自己实习那会也差不多, 想要做很厉害的项目, 用很流行的技术, 但是如果自己给人的感觉是不靠谱的, 那么主管自然就不敢把这些项目交给我了, 明白了这一点后, 当时我毕业一年给主管的目标是一个需求功能下来, 不管大小, 测试反馈的bug不能超过3个, 一个月产生的线上事故不能超过1次, 我努力做到了这一点, 线上事故几乎没发生过, 随之而来的是小组中绝大部分核心的业务、基础组件的开发都由我来处理, 以及一波大的涨薪


2.7、内卷


维基百科: 原是一个社会学概念,指一种文化模式发展到一定水平后,无法突破自身,只能在内部继续发展、复杂化的过程。大约从2018年开始,“内卷”一词在中国大陆变得广为人知,并引申表示付出大量努力却得不到等价的回报,必须在竞争中超过他人的社会文化,包含了恶性竞争、逐底竞争等更为负面的含义。


我们公司965几乎不加班, 所以我们6点会有比较充足的时间, 我一般去楼下吃完饭以后就会回到公司学习, 三年半的时间阅读了大量的源码书籍、学习了许多知识, 个人认为, 深入学习技术, 提高自己其实不属于内卷, 无意义的加班, 内耗等才是内卷, 其他小组也有人说我比较卷, 但是我们小组(包括主管)都是知道我晚上下班后是在公司学习的, 也很少会加班, 借着分享的机会我也跟大家澄清了这样的情况, 并且我们小组的氛围并没有因为我下班后的学习而导致整个小组都晚下班的情况(公司7点左右就基本空了.....)


三、技术能力上的成长及建议


3.1、如何学习


一、我学习一门未接触过的技术时, 会先看视频学习, 建立基本认识, 并且能够从讲师身上学到一些经验, 即先学会简单的使用
二、在了解了基本使用, 并且用起来的情况下, 我会查找跟该技术有关的权威书籍, 对权威书籍的学习是为了建立完整的知识体系


这个学习方式是我从大学以来就保持的, 而我认为这也是对我来说是最合适的学习方式


3.2、打地基-基础知识的重要性


基础知识对一个程序员来说是非常重要的, 个人认为基础越扎实的同学往往在学习技术的时候会吸收的更快, 并且也能够走的更远


一、数据结构, 我学习数据结构的时候, 会手写每个数据结构, 即使是最难的红黑树, 我也手写出来了(当时在大学的时候花了一个下午就为了写一个新增节点和删除节点的方法), 对于数据结构的学习, 我推荐: 恋上数据结构 这套视频, 讲的非常好, 大家如果有兴趣的话可以各显神通的去找找=,=


二、计算机网络, 计算机网络我是通过看视频加书籍的方式来学习的, 视频推荐: 韩立刚, B站就能搜到, 讲的通俗易懂, 我推荐了几个朋友看, 都反馈非常棒, 书籍推荐 计算机网络 第六版(考研408专用)


三、操作系统, 对操作系统的学习, 能够让我们在了解JVM、以及一些底层知识的时候(比如CAS、volatile、synchronize等原理)能够更加的顺利, 他们都是依赖于操作系统相关的知识来的, 视频我推荐: 哈工大的计算机操作系统, B站能搜到, 书籍推荐计算机操作系统(考研408专用)


四、汇编语言, 如果有看过深入理解Java虚拟机这本书, 那么里面就有出现跟汇编相关的话术, 如果对汇编有所了解, 能够亲身的体验到寄存器操作、中断的原理等, 这些在学习操作系统等知识的时候必然会遇到的话术, 视频我推荐推荐: 小甲鱼, B站就能搜到, 书籍我推荐(汇编语言(第3版) 王爽)


五、设计模式, 刚开始写代码的时候, 会一个方法写很多逻辑, 就像流水账一样, 一直写下去, 没有考虑复用等情况, 通过学习设计模式, 我们可以写出更加优雅的代码, 模板方法、单例、工厂等模式的使用能够使得我们的代码阅读性更高、扩展性更强, 学会了设计模式的情况下, 再去看自己之前写的代码就会发现还能写的更好! 并且有了这个知识的基础上, 我们去看一些框架源码的时候会更加顺利, 框架源码用到设计模式的时候命名都是通俗易懂的, 看到名字就知道用了什么模式, 就像程序员之前互相沟通一样, 这个我没有看视频, 我看的是 HeadFirst设计模式 这本书籍, 通过一些生动形象的例子, 把设计模式讲活了...


3.3、创造核心竞争力-不停留在只会用的地步


java开发往往离不开spring的生态系统, 框架开发出来就是给人更加方便开发功能用的, 如果仅仅会用, 那么在遇到一些问题的时候会无从下手, 三年半的时候, 我阅读了spring、springmvc、mybatis、springboot、springcloud等框架的源码, 通过书籍加视频的方式深入的了解了这些框架的原理, 看这些框架源码的时候, 不纠结于一些边线知识, 只管主线流程, 了解主线流程后, 我发现后续遇到问题时, 我能非常自信的跟进源码去排查问题, 在第四章节中我会整理每一个框架我都是通过哪些书籍来深入学习的


3.4、学以致用-尝试输出(github / 博客 / 分享)


学习一个知识, 如果仅仅看了一遍书 / 看了一遍视频, 那么可能过几天就会忘记了, 一般我是通过看视频 -> 记笔记 -> 看书 -> 对书中的知识点进行整理笔记, 笔记采用类似于给他人讲解的方式来记录 -> 将笔记记录在github 或者 以博客的形式分享出来, 在这样的链路下, 我每一步都能更加深刻的学习到知识点, 有时候看书看懂了不代表真懂了, 真正用笔记来描述的时候会发现是磕磕碰碰的, 与此同时, 将这些磕磕碰碰的知识去再次学习, 那么对整个知识点就会有更加全新的认识, 大家也可以看到, 我的掘金的博客是从2020年就开始写了, 都是我个人的口头描述转为文字描述


3.5、有枪不用和无枪可用


在掌握了工作中需要的知识点的情况下, 我们需要去学习流行的技术, 防止自己落伍, 技术的迭代更新是非常快的, 学习这些技术, 往往会给自己带来意想不到的结果


一年前公司我深入的去研究了eureka、zuul等springcloud组件的原理, 后面幸运的是, 公司有一个私有化部署的项目, 主管的计划是用微服务来搭建, 这个项目需要考虑到客户的资源,
有些客户可能预算比较高, 我们就可以提供一套完整的微服务来运行, 有些客户预算比较低, 那么可能最多就跑3-4个java项目, 于是主管的要求是我们的微服务功能, 需要能够满足上述的情况,
能够非常方便的将一个或者多个服务合并成一个服务, 并且自由搭配


正是因为我有对这些组件的深入了解, 我从源码层次提供了一套实现方案, 并且是最简单的实现方案, 主要的原理就是控制bean的加载(打包的时候一起打包, 但是不加载到内存)以及内部
rpc调用时的扩展(利用回环地址来尽可能的忽略http请求的花销), 如果我没有对这一块有所掌握, 那么我可能就失去了这样一个非常好的锻炼机会了


四、从成长的曲线来看侧重点


05.png


作者:zhongshenglong
来源:juejin.cn/post/7277489569958936588
收起阅读 »

茶百道全链路可观测实战

作者:山猎 茶百道是四川成都的本土茶饮连锁品牌,创立于 2008 年 。经过 15 年的发展,茶百道已成为餐饮标杆品牌,全国门店超 7000 家,遍布全国 31 个省市,实现中国大陆所有省份及各线级城市的全覆盖。2021 年 3 月 31 日,在成渝餐·饮峰会...
继续阅读 »

作者:山猎


茶百道是四川成都的本土茶饮连锁品牌,创立于 2008 年 。经过 15 年的发展,茶百道已成为餐饮标杆品牌,全国门店超 7000 家,遍布全国 31 个省市,实现中国大陆所有省份及各线级城市的全覆盖。2021 年 3 月 31 日,在成渝餐·饮峰会中,茶百道斩获“2021 成渝餐·饮标杆品牌奖”。2021 年 8 月,入选艾媒金榜(iiMedia Ranking)最新发布《2021 年上半年中国新式茶饮品牌排行 Top15》。2023 年 6 月 9 日,新茶饮品牌“茶百道”获得新一轮融资,由兰馨亚洲领投,多家知名投资机构跟投,估值飙升至 180 亿元。


今年 4 月,茶百道在成都总部举行了品牌升级发布会,宣布门店数突破 7000 家。根据中国连锁经营协会的数据,截至 2020 年、2021 年以及 2022 年 12 月 31 日,茶百道门店数量分别为 2,240 间、5,070 间以及 6,532 间,疫情并没有拖慢其扩张步伐。


随着业务规模的急速扩展,茶百道全面加速推进数字化转型战略。 但由于茶百道部分早期业务系统由外部 SaaS 服务商提供,无法满足线上业务高速增长所带来的大规模、高并发、弹性扩展、敏捷性、可观测等要求。为了满足线上线下门店客户需求与业务增长需要,针对店务、POS、用户交易、平台对接、门店管理、餐饮制作等核心链路服务,茶百道选择全面自研与阿里云云原生能力相结合,推动容器化、微服务化、可观测能力全面升级。


云原生化的业务价值


茶饮行业面临着市场竞争的压力和内部运营效率的提升需求。为了应对这些挑战,阿里云与茶百道一起完成云原生上云的转型,开启数字化的新征程。


采用容器和微服务技术实现了应用的轻量化和高可移植性。让企业可以更灵活地部署、扩展应用,快速响应市场需求,使得企业能够实现应用的高可用性和弹性扩展能力,无论面对突发的高峰访问量还是系统故障,都能保持业务的稳定运行。


引入了持续交付和持续集成的开发方式,帮助企业实现了快速迭代和部署。通过自动化的流程,企业能够更快地推出新功能和产品,与市场保持同步,抢占先机。


云原生的上云转型不仅带来了更高的安全性、可用性和可伸缩性,也提升了企业的创新能力和竞争力。


云原生带来的可观测挑战


茶百道作为业务高速发展的新兴餐饮品牌,每天都有海量的在线订单,这背后是与互联网技术的紧密结合,借助极高的数字化建设支撑茶百道庞大的销售量。因此,对于业务系统的连续性与可用性有着非常严苛的要求,以确保交易链路核心服务的稳定运行。特别是在每日高峰订餐时段、营销活动、突发热点事件期间,为了让用户有顺畅的使用体验,整个微服务系统的每个环节都需要保证在高并发大流量下的服务质量。


完善的全链路可观测平台以及 APM  ( Application Performance Management )工具,是保障业务连续性与可用性的前提。在可观测技术体系建设上,茶百道技术团队经历过比较多探索。全面实现容器化之前,茶百道在部分微服务系统上接入了开源 APM 工具,并进行超过一年时间的验证,但最终没有能够推广到整个微服务架构中,主要有这几个方面的原因:




  • 指标数据准确度与采样率之间的平衡难以取舍


    适当的采样策略是解决链路追踪工具成本与性能的重要手段,如果 APM 工具固定使用 100% 链路全采集,会带来大量重复链路信息被保存。在茶百道的庞大微服务系统规模下,100% 链路采集会造成可观测平台存储成本超出预期,而且在业务高峰期还会对微服务应用本身的性能带来一定影响。但开源工具在设定采样策略的情况下,又会影响指标数据准确度,使错误率、P99 响应时间等重要可观测指标失去观测与告警价值。




  • 缺少高阶告警能力


    开源工具在告警方面实现比较简单,用户需要自行分别搭建告警处理及告警分派平台,才能实现告警信息发送到 IM 群等基本功能。由于茶百道微服务化后的服务模块众多、依赖复杂。经常因为某个组件的异常或不可用导致整条链路产生大量冗余告警,形成告警风暴。造成的结果就是运维团队疲于应付五花八门且数量庞大的告警信息,非常容易遗漏真正用于故障排查的重要消息。




  • 故障排查手段单一


    开源 APM 工具主要基于 Trace 链路信息帮助用户实现故障定位,对于简单的微服务系统性能问题,用户能够快速找到性能瓶颈点或故障源。但实际生产环境中的很多疑难杂症,根本没有办法通过简单的链路分析去解决,比如 N+1 问题,内存 OOM,CPU 占用率过高,线程池打满等。这样就对技术团队提出了极高要求,团队需要深入了解底层技术细节,并具备丰富 SRE 经验的工程师,才能快速准确的定位故障根源。




接入阿里云应用实时监控服务 ARMS


在茶百道系统架构全面云原生化的过程中,茶百道技术团队与阿里云的工程师深入探讨了全链路可观测更好的落地方式。


ARMS 应用监控作为阿里云云原生可观测产品家族的重要成员,提供线程剖析、智能洞察、CPU & 内存诊断、告警集成等开源 APM 产品不具备的能力。在阿里云的建议下,茶百道技术团队尝试着将一个业务模块接入 ARMS 应用监控。


由于 ARMS 提供了容器服务 ACK 环境下的应用自动接入,只需要对每个应用的 YAML 文件增加 2 行代码就自动注入探针,完成整个接入流程。经过一段时间试用,ARMS 应用监控提供的实战价值被茶百道的工程师不断挖掘出来。茶百道同时使用了阿里云性能测试产品 PTS,来实现日常态和大促态的容量规划。因为ARMS和 PTS 的引入,茶百道日常运维与稳定性保障体系也发生了众多升级。


围绕 ARMS 告警平台构建应急响应体系


由于之前基于开源产品搭建告警平台时,经常遇到告警风暴的问题,茶百道对于告警规则的配置是非常谨慎的,尽可能将告警目标收敛到最严重的业务故障上,这样虽然可以避免告警风暴对 SRE 团队的频繁骚扰,但也会让很多有价值的信息被忽略,比如接口响应时间的突增等。


其实对于告警风暴问题,业界是有一整套标准解法的,其中涉及到去重、压缩、降噪、静默等关键技术,只是这些技术与可观测产品集成上存在一定复杂度,很多开源产品并没有在这个领域提供完善方案。


这些告警领域的关键技术,在 ARMS 告警平台上都有完整功能。以事件压缩举例,ARMS 提供基于标签压缩和基于时间压缩两种压缩方式。满足条件的多条事件会被自动压缩成为一条告警进行通知(如下图所示)。


图片
图: 基于标签压缩


图片
图:基于时间压缩


配合 ARMS 告警平台所提供的多种技术手段,可以非常有效的解决告警风暴的问题,因此茶百道技术团队开始重视告警的使用,逐步丰富更多的告警规则,覆盖应用接口、主机指标、JVM 参数、数据库访问等不同层面。


通过企业微信群进行对接,使告警通知实现 ISTM 流程的互动,当值班人员收到告警通知后,可以直接通过 IM 工具进行告警关闭、事件升级等能力,快速实现告警处理。(如下图所示)


图片
图:监控告警事件的智能化收敛与通告


灵活开放的告警事件处置策略满足了不同时效、场景的需求。茶百道在此基础上参考阿里巴巴安全生产最佳实践,开始构建企业级应急响应体系。将业务视角的应急场景作为事件应急处置的核心模型,通过不同告警级别,识别与流转对应的故障处理过程。这些都是茶百道在全面云原生化后摸索出的经验,并显著提升生产环境服务质量。


引入采样策略


从链路信息中提取指标数据,是所有 APM 工具的必备功能。不同于开源产品简单粗暴的指标提取方式,ARMS 应用监控使用端侧预聚合能力,捕捉每一次真实请求,先聚合,后采样,再上报,提供精准的指标监控。确保在采样策略开启的情况下,指标数据依然与真实情况保持一致。


图片
图:ARMS 端侧预聚合能力


为了降低 APM 工具带来的应用性能损耗,茶百道对大部分应用采取 10% 采样率,对于 TPS 非常高的应用则采取自适应采样策略,进一步降低高峰期应用性能损耗。通过实测,在业务高峰期,ARMS 应用监控造成的应用性能损耗比开源产品低 30% 以上且指标数据准确性可信赖, 比如接口级别的平均响应时间、错误数等指标都可以满足生产级业务需求。


图片
图:接口级别指标数据


异步链路自动埋点*


在 Java 领域存在异步线程池技术,以及众多开源异步框架,比如 RxJava、Reactor Netty、Vert.x 等。相较于同步链路,异步链路的自动埋点与上下文透传的技术难度更大。开源产品对主流异步框架的覆盖度不全,在特定场景下存在埋点失败问题,一旦出现这样的问题,APM 工具最重要的链路分析能力就难以发挥作用。


在这种情况下,需要开发者自行通过 SDK 手工埋点,以保证异步链路的上下文透传。这就会造成巨大的工作量且难以在团队内部大面积、快速推广。


ARMS 对主流的异步框架都实现了支持,无需任何业务代码上的侵入就能够异步链路上下文透传,即使对一些异步框架的特定版本没有及时支持,只要用户侧提出需求,ARMS 团队就能在新版本的探针中补齐。使用 ARMS 应用监控之后,茶百道技术团队直接将此前异步框架手工埋点代码进行了清理,大幅度减少维护工作量。


图片


图:异步调用的链路上下文


更高阶应用诊断技术的运用


在埋点覆盖度足够高的情况下,传统 APM 工具和链路跟踪工具能够帮助用户快速确定链路的哪一个环节(也就是Span)存在性能瓶颈,但需要更进一步排查问题根源时,就无法提供更有效的帮助了。


举一个例子,当系统 CPU 占用率显著提升时,是否因某个业务方法疯狂的消耗 CPU 资源所导致?这个问题对于大多数的 APM 产品而言,都是难以办法解决的。因为单从链路视图无法知晓每个环节的资源消耗情况。茶百道的工程师在使用开源工具时,曾多次遇到类似问题,当时只能凭借经验去猜测,再去测试环境反复对比来彻底解决,虽然也试过一些 Profiling 工具,但使用门槛比较高,效果不是很好。


ARMS 应用监控提供了 CPU & 内存诊断能力,可以有效发现 Java 程序中因为 CPU、内存和 I/O 导致的瓶颈问题,并按照方法名称、类名称、行号进行细分统计,最终协助开发者优化程序、降低延迟、增加吞吐、节约成本。CPU & 内存诊断可以在需要排查特定问题时临时开启,并通过火焰图帮助用户直接找到问题根源。在一次生产环境某应用 CPU 飙升场景中,茶百道的工程师通过 CPU & 内存诊断一步定位到问题是由一个特定业务算法所导致。


图片
图:通过火焰图分析 CPU 时间


此外,对于线上的业务问题,还可以通过 ARMS 提供的 Arthas 诊断能力在线排查。Arthas 作为诊断 Java 领域线上问题诊断利器,利用字节码增强技术,可以在不重启 JVM 进程的情况下,查看程序运行情况。


虽然 Arthas 使用有一定门槛,需要投入比较多精力进行学习,但茶百道的工程师非常喜欢使用这个工具。针对“到底符合哪种特殊的数据导致某业务异常”此类问题,没有比 Arthas 更方便的排查工具了。


图片


阶段性成果


经过 2 个月时间的调研与对比,茶百道决定全面从开源可观测平台转向 ARMS,从开源压测平台转向 PTS,并在团队内部进行推广。**随着使用的不断深入,ARMS 所提供的智能洞察、线程池分析等高阶可观测能力也逐步被茶百道的技术团队应用于日常运维中,线上问题排查效率相比之前也有了数倍提升。


在可观测产品本身的使用成本上,虽然表面上 ARMS 相比开源产品有所提高,但这是建立在开源方案数据单写,以及存在单点故障的情况下。其实茶百道的技术团队也非常清楚,之前的开源方案是存在高可用性隐患的,某个组件的故障会导致整个可观测方案不可用。只是大家对于开源方案提供的可观测能力并没有重度使用,所以才没有足够重视。所以综合来看,ARMS 整体成本并不会高于开源方案。


利用 ARMS 能力,茶百道实现了可观测指标采样率百分百覆盖,链路全采集,监控数据准确率大幅提供,能够快速实现业务故障的自动发现,有效的配合敏态业务发展。


故障发生后,监控系统需要第一时间通知相关人员,做初步定位,ARMS 告警告警能力实现了 ChatOps 能力,基于 IM 工具,快速触达相关人员,并且提供初步定位能力,是故障的响应能力大幅提升。


故障的快速恢复,对于控制业务影响至关重要,ARMS 利用全链路 Trace 能力,快速定位具体应用、接口、方法、慢sql等,是故障快速恢复的关键助手。茶百道技术团队负责人表示: “在与开源方案成本持平的前提下,ARMS 丰富且全面的全栈观测与告警能力,使茶百道快速建立运维观测与响应能力,故障恢复效率提升 50% 以上,故障恢复耗时****缩短 50%,真正做到用可观测为业务迅猛发展保驾护航。”


故障的预防收敛,在稳定性体系建设中是投入产出比极高的,PTS 利用全国流量施压的能力,和秒级监控能力,验证站点容量并定位性能瓶颈。茶百道在业务上线前,充分对单应用和全链路做压测,累计压测 800 余次,在上线前做到了性能问题的收敛,避免演进为线上故障。


下阶段目标


在可观测领域,Prometheus + Grafana 是指标数据存储、计算、查询、展示的事实标准,ARMS 产品家族提供托管加强的 Prometheus 和 Grafana 服务。ARMS 应用监控生成的指标数据也会自动保存到托管版 Prometheus 中,并预置数张 Grafana 大盘。茶百道的工程师们正在基于 Prometheus 和 Grafana,将应用层指标、关键业务指标、云服务指标进行结合,开发多维度可观测大盘。


在不久的将来,茶百道就会建立覆盖业务层、用户体验层、应用服务层、基础设置层、云服务层的统一可观测技术体系,为千万级用户同时在线的大规模微服务系统实现稳定性保障。


作者:阿里云云原生
来源:juejin.cn/post/7289767547329970231
收起阅读 »

几条有助于提高开发者学习效率的小建议

时间就像海绵中的水,挤一挤总还是有的! 思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜 前言 作为程序员不知道你是否有过这样的感受,每天光是应对产品经理天马行空般的需求就已经筋疲力尽了,每天下班后只想静静地躺着,但面对越来...
继续阅读 »

时间就像海绵中的水,挤一挤总还是有的!



思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜





前言


作为程序员不知道你是否有过这样的感受,每天光是应对产品经理天马行空般的需求就已经筋疲力尽了,每天下班后只想静静地躺着,但面对越来越严峻的大环境以及越来越卷的后浪,你内心仿佛又有个声音在不断提醒你要坚持学习。但当你准备学习时又发现自己似乎也没什么头绪,索性就随便点开一篇博客,或是随便找本书随便看看。等过段时间再回头一看,看似当下学了很多,但过几天真正用到时却发现前几天看的东西好像又忘得差不多了,周而复始,不断在奋进摆烂间周旋。


这些问题其实笔者也曾经历过,为了克服这些问题,我也曾阅读过很多有关学习的博客及书籍,同时也做了很多尝试,最终也算找到一条适合自身的学习方案。所以笔者今天想谈一谈笔者是如何来学习新技术的,希望对你的学习、工作有所启发。


树立正确战略方向,避免南辕北辙



做一件事的关键在于树立正确的目标



众所周知,程序员总是需要面对各种层出不穷的新框架,而当接触一款全新框架时,你通常会如何做呢?当接触一款全新的框架时,通常会先花一点时间来考虑如下两个问题:



  1. 通过学习我期待能达到一个水平

  2. 如何衡量我对于这个框架的掌握程度?


接下来,不妨听听我为什么会在学习伊始先考虑这两个问题。早些的时候,当我听闻项目中要用到新框架时,总是会闷头去网上搜寻与其相关的博客、书籍。然后,拼命利用各种空余时间来读这些内容,给别人的感觉就是我很,其实我内心之道,只不过是我菜罢了!


但就是这样似乎也没比别人厉害到哪去,通过阅读可能我确实会比别人多掌握一些奇技淫巧,但实际工作场景中基本不会用到,等过段时间某个场景确实需要了,我也忘得差不多了。最后,也只能借助搜索引擎来解决。费时费力,最终却收效甚微。


后来,我就开始反思自己的行为是否正确。经过反思,我逐渐意识到工作中像我这样接到任务就闷头干的傻小子不在少数。事实上,闷头干这个行为只是你完成目标的一种手段,而不是你的战略
此处的战略你可以理解为是你的目标,也即你的方向南辕北辙的故事想必大家都曾听过,如果一个人方向选取的错了,再怎么努力也是徒劳。


笔者所思考的那两个问题恰恰就是在考虑学习的目标。即通过学习我们对于这个知识点应该掌握到什么程度,而这个程度又该通过什么指标来量化。 当有了量化的指标后,便能准确衡量我们对于一个新技术的掌握情况。


事实上,当你明确自己学习的目标后,你对于所要做的事也才能有着更加清晰的认识,进而你所采取的行动也才能更加精准,这样你也不至于类似出现南辕北辙


就像学习一款新框架最初你的目标就应该是了解框架的基本使用方式,在这个目标指引下你要做的就是找寻与其相关的实用性文档,同时为了实现这一目标你也就不至于翻看解析其源码的专业性文档!


拆分目标,寻找可行的最小单元



对目标进行拆分,分清任务轻重缓急



当明确我们的目标后,我们下一步要做就是对目标进行拆分。这一过程中,我们不断地把大目标拆分为一个个切实可行的最小单元。接下来,便以笔者当初制定学习Spring源码的过程为例,来看看笔者是如何一步步地来将一个大目标拆分具体可行的可执行单元


某段时间内,笔者曾制定下一个深耕Spring源码的目标,期待通过学习能实现从会用熟悉背后原理跃迁,而衡量这一目标是否达到的标准就是能否在不翻越任何博客的情况下自己总结出Spring的相关知识点。基于这一目标,笔者对目标其进行了拆分。


首先,对目标模块进行拆分。要知道,在没有人手把手带领学习的情况下,从零开始学习Spring源码是非常困难的一件事。所以,笔者最开始最小行动单元是从Spring最基础的实用方式开始入手,也即从分析Spring最开始使用的ClassPathXmlApplicationContext开始分析。这一点从笔者之前写的从简单的配置文件开始,重新审视Spring的上下文环境就能看出端倪。这也是笔者这些年逐渐形成的习惯,即在分析时总是会从最基础的入手,然后不断深入。


当对目标进行拆分后,下一步就是确定每个模块所需要掌握的知识。还是以读Spring源码的来分析,笔者将其拆分为容器、资源加载、扩展点、DI、AOP等模块,对于其中的容器来说具体就是要分析Spring中的容器结构,也即对BeanFactory家族的分析,因为这部分就是Spring容器的核心内容。当然你还可以进行细分,直至将一个抽象的事物不断细化,直到拆分为以一个切实可行的步骤在停止。


当目标经历过前面的拆分,下一步就是行动了!对于怎么行动这就取决于每个人的自驱力了,这个并没有一个统一的标准,笔者在这里想再谈一谈是如何挤出时间来学习的。


合理规划时间,日拱一卒



时间就像海绵里的水, 只要愿意挤,总还是有的。



当明确了学习的目标后,下一步就是行动。


在如今这个快节奏的时间似乎成了最宝贵的资源,每天应付完工作,回到家稍微休息一下,吃个饭,洗漱完,似乎就又该上床睡觉了。而且即使有空闲时间,打游戏肯定比学习更快乐,难道要牺牲为数不多的娱乐时间来学习吗?答案肯定是否定,不妨来看看笔者每天是如何来挤时间学习的。


笔者通常七点起床,洗漱完,吃个早餐差不多七点半,然后出门去等公交。从家到公交这段过程中我通常会打开一个技术类相关的视频,然后边听边走。到车站差不多需要十五分钟左右,在站台等车差不多又是十几分钟。这就差不多二十多分钟的时间了,如果视频开倍速的话,这段时间正好可以看完一段三十多分钟左右的视频。


坐公交去公司的话差不多又二十多分钟,在公交车上的这段时间,我通常会打开备忘录对刚才视频的内容进行总结和提炼,差不多八点二十就可到公司,然后开启一天的工作。笔者的公司一般六点多就可以下班了,下班的路上将继续重复早上的行为,这样每天差不多通过通勤我可以挤出一个多小时的学习时间。


到家后通常我会再花十几分钟时间对一天学习的内容进行一个回顾和总结,之后做饭、洗碗,然后打游戏娱乐,大概十点多便洗漱上床多睡觉,这就是我平凡的一天。周末的时候通常会再花一个多小时,将本周学习的内容整理成相关的文档,以备后续查看和回顾。


不难发现,我一天其实都在挤时间来学习。经过笔者长时间的时间,笔者发现这种通过挤时间的方式可以充分可利用通勤的闲暇时间,也并没有因为学习而放弃我所有的休闲娱乐,更没有在累成狗的时候强行“打鸡血”逼着自己去学,相反我很适应现在这样的生活方式,因为一切都在自己的可控范围内。


当然这只是我个人的经历,可能不具有一般性,因为每个公司的作息和个人通勤时间肯定有着很大的差异。这里笔者分享自己的经历更多的是想说: 在当下这个快节奏的时代内,当从学校离开的那一刻起,我们就再也不会有像学生时代那样大段时间来学习了,所以只能从日常中来挤出时间学习。


总结


最后,在分享笔者一条笔者多年以来笔者一直坚持贯彻的一条学习法则,即学习一项新技术时,首先,以视频为入口,然后,以业界公认的名书继续深入理解;最后,以社交圈的同行或网上社区为输出交流。


作者:毅航
来源:juejin.cn/post/7290813210277036067
收起阅读 »

后遗症:年轻人现状

前言 作为一个人间观察员,同时我也是年轻人里面的一份子,我可以感觉到在生活的方方面面都能感觉到一些病态的现状。 1、首先跟我们日常相关的,结婚、买房买车,很多人的想法这是一笔很大的花销,另外工作在这几年经济比较差的环境下也比较难找,然后涨幅也没有那么高了;我...
继续阅读 »

前言




作为一个人间观察员,同时我也是年轻人里面的一份子,我可以感觉到在生活的方方面面都能感觉到一些病态的现状。


1、首先跟我们日常相关的,结婚、买房买车,很多人的想法这是一笔很大的花销,另外工作在这几年经济比较差的环境下也比较难找,然后涨幅也没有那么高了;我发现基本大家的话题都离不开这些哈哈,可能是所谓的人生大事。


2、在讨论上面话题的时候,或者在朋友圈看到别人发的东西,我能感觉身边很多人蛮焦虑,有些甚至抑郁


3、不止是心情上出现问题,蛮多年轻人身体健康上也出现问题,像最近的脆皮大学生的梗。当然这里更多是打工人的身体健康问题,毕竟每天坐着看电脑,运动少,然后吃着各种外卖,再加上工作压力啥的。


总结:可以感受到这个社会充斥着金钱至上的味道,即使你在温饱的情况也会觉得很难受,为啥没有暴富。金钱就像那个萝卜悬在我们面前鞭笞我们前进,彷佛偷下懒就是不对的,像李某人:你要想下自身的原因?有没有努力赚钱?


很多人在这种社会环境下,或者氛围下,身心疲惫,然后你再上网刷一下,更焦虑了,哈哈。当我尝试分析各种各种原因,包括客观上、主观上的问题,发现没法解决现状问题。


什么原因导致?




这就引出以前两个名人的分歧,就是周树人、胡适,周树人是偏向人性的唤醒,就是他认为人太过冷漠了,一群人围观“坏人”的砍头,对于跟自己不相关的事情漠不关心;而胡适认为是环境导致人们出现这种情况,所以两人出现分歧。


目前我认为环境问题占大头,在很长一段的历史里面,压制人的天性,控制你的说话,人人自危,在这种环境下生存的人就会出现冷漠的心态,也就是说是对应的产物。我们现在看到的还是表面的问题,至于本质的问题是什么呢?这就得谈到《共产党宣言》。


我是在读《毛泽东笔记精讲》的时候,描述了早期对乌托邦的向往,在失败之后通过反复看了《共产党宣言》很多遍,然后系统学习共产主义,调整了方向。我那时就好奇究竟里面讲了什么,怎么从乌托邦主义转辩证唯物主义的,其实里面就很显露的暴露出资本主义带来的现状。


《共产党宣言》




image.png


image.png


里面首先提到一点,历史是一部阶级斗争的博弈,在封建时代,资本主义就是打压对象,后来随着先进的生产力出现,资本主义开始扩张,通过对外取代落后的生产力来达到资本递增的需求,所以资本主义占据了上风。


但是随着生产力的发展,机器的普及,人们的工作开始变得细分化,劳动量越大(按我理解是对重复劳动会更多,相当于人也变成一台机器,不断重复,以前搞一个东西时间是长了些,但是一天工作量就那么些,需要各个环节的切换,当你只剩下某个步骤的时候,就是一台重复的机器)。


资本也会让整个社会趋向盲目性,因为它本身就有复利的目的,比如说100w一年存银行拿到多少钱,第二年把所有钱再作为本金进行一个复利的操作。当生产力无法突破的时候,或者说市场过于饱和的时候,开始出现了危机,它不仅影响人们的日常生活,还有相处方式,当人与人的关系只剩下交易、利益的时候,再也没有其他关系,这就是当今的社会风气,就是大家都向钱看,而它恰恰描述在《共产党宣言》里面。



当一座大厦缺乏精神支柱、价值体系的时候,它建的越高倒得越快



待完善


是的,这个宣言极大鼓舞人们追随共产主义,也给出一定的理论指导,在我看来有些地方还需要完善,首先目前的生产力没有达到这么高的水平,记得刘强东在谈到ai的时候,如果机器人可以代替普通人干活,那么我们真正离共产主义的那个时代不远了;另外按需分配的规则也需要完善,是按照以前一个人多少粮票、多少住房来衡量呢?我们的思维是被现状局限了,是不是以后的物质跟现在的水一样,相对来说是非常充裕的,我们真正按照需求来要这个量,但是这里面也有人性的问题,当然如果物质已经丰富到这种情况,也没有人在意多少。


另外在那个时代,每个人都能满足基本的需求,工作都让机器去做,那人们在做什么?这就有点科幻了,是不是未来我们可以去探索外太空呢?这确实是一个有意思的目标哈哈


未来的方向


1、生产力的提升是一个很重要的基础


2、当然这个时候去制定完善的按需分配机制也不合理,需要达到上面前提条件


共产主义是解决当前资本主义带来一系列的问题的方法,但是当前的条件还达不到。


学习的东西


唯心主义、辩证唯物主义


当我在看《毛泽东笔记精讲》的时候,里面提到了早期有段时间推崇乌托邦思想,后来体系接触社会主义之后,追随辩证唯物主义。那么我就很好奇,唯心主义、辩证唯物主义 区别很大吗?


这里可以举个例子,比如说我要做一个大项目,按照我以往的经验,我会去参考业界的过往经验,然后去规划当前符合公司情况的架构。问题来了,当社会主义在初创的时候,哪有借鉴的例子,所以它出现了唯心主义,通过自己对社会主义的理解,进行蓝图,思想概念的构造,然后进行实施,这就是早期德国的社会主义乌托邦。


它的问题是会朝着预想的方向去进展,重心是你自己yy出来的,辩证唯物主义是需要借助历史,来反推未来的发展,它需要依靠现实的demo来总结规律,这像不像我们易经,这是唯物主义。


它忽略了什么,人的主观能动性,人的意志往往可以克服种种困难,所以是辩证唯物主义。



坚持实践是检验真理的唯一标准



物理上客观条件是主要的决定因素,而心理上、精神上是一大变量,是影响因素;我们可以看到在长征途中,做好了心理上的宣导,才能战胜困难,这是在《毛泽东自述》谈到的心理建设的重要性。


关于现状的想法




1、解决温饱问题,基本的物质经济


2、哪些事情对你有意义


当你满足温饱问题之后,我认为应该更多思考什么是你真正要创造的东西,还是一百年后,几百年后,人们对这个少年的印象就是这是个打工人,资本的复利的炮灰,不这不是我想要的,所以我一直在抽时间去思考历史上有哪些精神、哪些处事方式值得我们学习的,然后提出自己的方法论。


当你在获得收获然后分享那一刻是幸福,是骄傲的,历史就是一本厚厚的书籍,有些记载了每个朝代的精英集团,有些承载了那个时代的思想,它是现代人前行的路灯。


3、建设自己的精神世界


知识体系重建,价值体系重建,精神世界重建,这是重点方向。


作者:大鸡腿同学
来源:juejin.cn/post/7289397650385731641
收起阅读 »

面试官:如何判断两个数组的内容是否相等

web
题目 给定两个数组,判断两数组内容是否相等。 不使用排序 不考虑元素位置 例: [1, 2, 3] 和 [1, 3, 2] // true [1, 2, 3] 和 [1, 2, 4] // false 思考几秒:有了😀😀 1. 直接遍历✍ 直接遍历第...
继续阅读 »

题目


给定两个数组,判断两数组内容是否相等。



  • 不使用排序

  • 不考虑元素位置


例:


[1, 2, 3] 和 [1, 3, 2] // true
[1, 2, 3] 和 [1, 2, 4] // false


思考几秒:有了😀😀


1. 直接遍历✍



  • 直接遍历第一个数组,并判断是否存在于在第二个数组中

  • 求差集, 如果差集数组有长度,也说明两数组不等(个人感觉比上面的麻烦就不举例了)


const arr1 =  ["apple", "banana", 1]
const arr2 = ["apple", 1, "banana"]

function fn(arr1, arr2) {
// Arrary.some: 有一项不满足 返回false
// Arrary.indexOf: 查到返回下标,查不到返回 -1
if (arr1.length !== arr2.length) {
return false;
}
return !arr1.some(item => arr2.indexOf(item)===-1)
}

fn(arr1,arr2) // true


  • 细心的小伙伴就会发现:NaN 会有问题


const arr1 =  ["apple", "banana", NaN]
const arr2 = ["apple", NaN, "banana"]

function fn(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
return !arr1.some(item => arr2.indexOf(item)===-1)
}

fn(arr1,arr2) // false


Arrary.prototype.indexOf() 是使用的严格相等算法 => NaN值永远不相等


Array.prototype.includes() 是使用的零值相等算法 => NaN值视作相等




  • 严格相等算法: 与 === 运算符使用的算法相同

  • 零值相等不作为 JavaScript API 公开, -0和0 视作相等,NaN值视作相等,具体参考mdn文档:


image.png



  • 使用includes


const arr1 =  ["apple", "banana", NaN]
const arr2 = ["apple", NaN, "banana"]

function fn(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
return !arr1.some(item => !arr2.includes(item))
}

fn(arr1,arr2) // true

使用includes 确实可以判断NaN了,如果数组元素有重复呢?


// 重复的元素都是banana
const array1 = ["apple", "banana", "cherry", "banana"];
const array2 = ["banana", "apple", "banana", "cherry"];
// 或者
// 一个重复的元素是banana, 一个是apple
const array1 = ["apple", "banana", "cherry", "banana"];
const array2 = ["banana", "apple", "apple", "cherry"];


由上可知:这种行不通,接下来看看是否能从给数组元素添加标识入手


2. 把重复元素标识编号✍


这个简单:数组 元素重复 转换成val1, val2


function areArraysContentEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}

// 重复数组元素 加1、2、3
const countArr1 = updateArray(arr1)
const countArr2 = updateArray(arr2)

/**
*
* @param {*} arr 数组 元素重复 转换成val1, val2
* @returns
*/

function updateArray(arr) {
const countMap = new Map();
const updatedArr = [];

for (const item of arr) {
if (!countMap.has(item)) {
// 如果元素是第一次出现,直接添加到结果数组
countMap.set(item, 0);
updatedArr.push(item);
} else {
// 如果元素已经出现过,添加带有编号的新元素到结果数组
const count = countMap.get(item) + 1;
countMap.set(item, count);
updatedArr.push(`${item}${count}`);
}
}
return updatedArr;
}
const flag = countArr1.some(item => !countArr2.includes(item))
return !flag
}

const array1 = ["apple", "banana", "cherry", "banana"];
const array2 = ["banana", "apple", "banana", "cherry"];

areArraysContentEqual(array1, array2) // true

// 其实这种存在漏洞的
const array3 = ["apple", "banana", "cherry", "banana", 1, '1', '1' ];
const array4 = ["banana", "apple", "banana", "cherry", '1', 1, 1];
// 应该是false
areArraysContentEqual(array3, array4) // true

因为把判断的 转为了字符串 updatedArr.push(${item}${count}) 所以出问题了


3. 统计元素次数(最终方案)✍


function areArraysContentEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}

// 创建计数对象,用于记录每个元素在数组中的出现次数
const countMap1 = count(arr1)
const countMap2 = count(arr2)

// 统计数组中的元素出现次数
function count(arr = []) {
const resMap = new Map();
for (const item of arr) {
resMap.set(item, (resMap.get(item) || 0) + 1);
}
return resMap
}
// 检查计数对象是否相等
for (const [key, count] of countMap1) {
if (countMap2.get(key) !== count) {
return false;
}
}

return true;
}

const array1 = ["apple", "banana", "cherry", "banana", 1, '1', '11', 11];
const array2 = ["banana", "apple", "banana", "cherry", '1', 1, '11', 11];

areArraysContentEqual(array1, array2) // true


注意事项


这个题需要注意:



  • 先判断长度,长度不等 必然不等

  • 元素可重复

  • 边界情况考虑

    • '1' 和 1 (Object的key是字符串, Map的key没有限制)

    • NaN

    • null undefined




结语:


如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻


因为收藏===会了


如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾


作者:程序员小易
来源:juejin.cn/post/7290786959441117243
收起阅读 »

如何将电脑上的“小电影”隐藏为一张图片?这波操作绝了!!

大家好,我是冰河~~ 最近,有很多小伙伴想跟我学渗透。平时时间确实太忙了,除了要研发公司项目外,写公号,写博客,录视频,写书稿,维护开源项目,几乎占据了我全部的业余时间。目前确实没有太多的时间教大家,今天,就暂时给大家分享一个小技巧吧,如何彻底隐藏电脑中的“小...
继续阅读 »

大家好,我是冰河~~


最近,有很多小伙伴想跟我学渗透。平时时间确实太忙了,除了要研发公司项目外,写公号,写博客,录视频,写书稿,维护开源项目,几乎占据了我全部的业余时间。目前确实没有太多的时间教大家,今天,就暂时给大家分享一个小技巧吧,如何彻底隐藏电脑中的“小电影”,让你的女朋友再也不能发现你电脑中的小秘密!


实现效果:你女朋友打开文件是一张图片,你打开却是各种“小电影”~~


好了,我们开始吧!


首先,准备好一张图片,还有一个对你来说的很重要的“电影”文件夹,如图所示。



电影文件夹中的内容如下所示。



接下来,将电影文件夹压缩为1.rar文件,如下所示。



然后新建一个名称为copy_image.bat的脚本文件,文件内容如下所示。


copy 1.jpg/b+1.rar=2.jpg


双击运行copy_image.bat的脚本文件,会生成一张2.jpg文件,如下所示。



接下来,只保留2.jpg文件,其他文件和文件夹全部删除。



可以看到,就只剩下这个图片了,我们打开这张图片。



可以看到,它确实只是一张图片。那么问题来了:我们要看“小电影”怎么办? 接下来,是重点。


如果你想看里面的“小电影”,那只需要把图片的后缀名从.jpg修改为.rar,如下所示。



双击打开2.rar文件,如下所示。



可以看到,里面都是你珍藏多年的“小电影”啦。为了保险起见,看完,还是把文件的后缀名改回.jpg吧 ~~


你学会了吗?欢迎在文末留言讨论~~


好了,今天的分享就到这里,我是冰河,我们下期见~~


作者:冰_河
来源:juejin.cn/post/7290741663643254836
收起阅读 »

领导说我工作 3 年了只会 CRUD

在老东家工作 3 年了,公司的业务和技术栈相对熟练得差不多了。 领导觉得我能够委以重任,便把一个新项目交给我负责,另外指派一名同事协助我。 项目的重点在于数据的交互比较多,以及每天大量的数据同步和批量操作,不能出错。 队友建议以短、平、快为主,能够使用已有现成...
继续阅读 »

在老东家工作 3 年了,公司的业务和技术栈相对熟练得差不多了。


领导觉得我能够委以重任,便把一个新项目交给我负责,另外指派一名同事协助我。


项目的重点在于数据的交互比较多,以及每天大量的数据同步和批量操作,不能出错。


队友建议以短、平、快为主,能够使用已有现成的技术就用现成的技术。直接面向过程开发是人们最为舒适,是人为本能的习惯。由于他有这一种能够处理好的决心,便把数据批量操作这块委托于他。


查看了以往公司现成一些写法,一部分是直接面向 SQL 写法批量插入,面对增量同步则先查出,存在的更新,不存在的插入。一部分是通过 Kafka 和后台任务原子操作。


理论上这么操作结果也能成,但是看到修改记录,我就知道面临的需求变了很多变化很快,导致大量的更改。私底下询问负责人也了解出了太多问题,原本一劳永逸赶紧写完结果反而投入了更多的精力和时间。


出于预防心理,也对那位同事进行了提醒并且加以思考再下手。


不到一个月,我们就把项目上线了,并且没有出现数据上的错误,得到了领导的表扬。


我们也提前收场,做一些小的优化,其余时间在摸鱼。


一段时间之后,麻烦便接踵而至,其一就是开始数据量暴增,那位同事在做增量同步时进行了锁表操作,批量操作需要一些时间,在前台读取时出现响应超时。


其二就是增量同步要调整,以主库或第三方来源库为主,出现数据更新和删除的需要同步操作。


同事目前的主力放在了新项目上,把一些零散的时间用来调整需求和 bug,结果越处理,bug 出现的越多,不是数量过多卡死就是变量不对导致数据处理不对。


于是到了某一时刻终于爆发,领导找到我俩,被痛批一顿,工作这么久就只会 CRUD 操作,来的实习生都会干的活,还养你们干什么。


当然,要复盘的话当然有迹可循。我想碰见这种情况还真不少,首次开发项目时一鼓作气,以“短、平、快” 战术面向过程开发,短时间内上线。


但是,一个软件的生命周期可不止步于上线,还要过程运维以及面对变化。


导致在二次开发的时候就脱节了,要么当时写法不符合现有业务,要么改动太多动不动就割到了大动脉大出血,要么人跑了...


所以我们会采用面向对象,抽象化编程,就是用来保稳定,预留一部分来应付变化,避免牵一发而动全身。


挨完骂,也要开始收拾烂摊子。


于是我打算重新组装一个通用的方法,打算一劳永逸。


首先我们定义一个接口通用思维 IDbAsyncBulk。由于源码已经发布到了github,所以一些注释写成了英文,大致也能看出蹩脚英文的注释。


public interface IDbAsyncBulk
    {
        /// <summary>
        /// default init.
        /// use reflect to auto init all type, to lower case database fileds,and  default basic type.
        /// if ignore some fileds,please use DbBulk,Ignore property to remarkable fileds.
        /// if other operating,need user-defined to init operate.
        /// </summary>
        /// <typeparam name="T">Corresponding type</typeparam>
        Task InitDefaultMappings<T>();

        /// <summary>
        /// batch operating
        /// </summary>
        /// <typeparam name="T">will operate object entity type.</typeparam>
        /// <param name="connection_string">database connecting string.</param>
        /// <param name="targetTable">target table name. </param>
        /// <param name="list">will operate data list.</param>
        Task CopyToServer<T>(string connection_string, string targetTable, List<T> list);

        /// <summary>
        /// batch operating
        /// </summary>
        /// <typeparam name="T">will operate object entity type.</typeparam>
        /// <param name="connection">database connecting string.need to check database connecting is openning.
        /// if nothing other follow-up operate, shouldn't cover this connecting.</param>
        /// <param name="targetTable">target table name.</param>
        /// <param name="list">will operate data list.</param>
        Task CopyToServer<T>(DbConnection connection, string targetTable, List<T> list);

        /// <summary>
        /// renew as it exists,insert as it not exists.
        /// follow up : 
        /// 1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection_string">connecting string</param>
        /// <param name="keys">mapping orignal table and target table fileds,need primary key and data only,if not will throw error.</param>
        /// <param name="targetTable">target table name.</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        /// <param name="insertmapping">need to insert column,if is null,just use Mapping fileds,in order to avoid auto-create column</param>
        /// <param name="updatemapping">need to modify column,if is null,just use Mapping fileds</param>
        Task MergeToServer<T>(string connection_string, List<string> keys, string targetTable, List<T> list, string tempTable = null, List<string> insertmapping = null, List<string> updatemapping = null);

        /// <summary>
        /// renew as it exists,insert as it not exists.
        /// follow up : 
        /// 1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection">database connecting string.need to check database connecting is openning.</param>
        /// <param name="keys">mapping orignal table and target table fileds,need primary key and data only,if not will throw error.</param>
        /// <param name="targetTable">target table name.</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        /// <param name="insertmapping">need to insert column,if is null,just use Mapping fileds,in order to avoid auto-create column</param>
        /// <param name="updatemapping">need to modify column,if is null,just use Mapping fileds</param>
        Task MergeToServer<T>(DbConnection connection, List<string> keys, string targetTable, List<T> list, string tempTable = null, List<string> insertmapping = null, List<string> updatemapping = null);

        /// <summary>
        ///  batch update operating。
        /// 1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection_string">connecting string</param>
        /// <param name="where_name">matching 'where' compare fileds.</param>
        /// <param name="update_name">need to update fileds.</param>
        /// <param name="targetTable">target table name</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        Task UpdateToServer<T>(string connection_string, List<string> where_name, List<string> update_name, string targetTable, List<T> list, string tempTable = null);

        /// <summary>
        ///  batch update operating。
        /// 1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection_string">connecting string</param>
        /// <param name="where_name">matching 'where' compare fileds.</param>
        /// <param name="update_name">need to update fileds.</param>
        /// <param name="targetTable">target table name</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        /// <param name="createtemp"> create temporary table or not </param>
        Task UpdateToServer<T>(DbConnection connection, List<string> where_name, List<string> update_name, string targetTable, List<T> list, string tempTable = nullbool createtemp = true);

        /// <summary>
        /// renew as it exists,insert as it not exists.original table not exist and  target table exist will remove.
        /// 1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// 4.will remove data that temporary data not exist and target table exist.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection_string">connecting string</param>
        /// <param name="keys">mapping orignal table and target table fileds,need primary key and data only,if not will throw error.</param>
        /// <param name="targetTable">target table name</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        /// <param name="insertmapping">need to insert column,if is null,just use Mapping fileds,in order to avoid auto-create column</param>
        /// <param name="updatemapping">need to modify column,if is null,just use Mapping fileds</param>
        Task MergeAndDeleteToServer<T>(string connection_string, List<string> keys, string targetTable, List<T> list, string tempTable = null, List<string> insertmapping = null, List<string> updatemapping = null);

        /// <summary>
        /// renew as it exists,insert as it not exists.original table not exist and  target table exist will remove.
        ///  1.create temporary table
        /// 2.put data into temporary table.
        /// 3.merge data to target table.
        /// 4.will remove data that temporary data not exist and target table exist.
        /// </summary>
        /// <typeparam name="T">data type</typeparam>
        /// <param name="connection">database connecting string.need to check database connecting is openning.</param>
        /// <param name="keys">mapping orignal table and target table fileds,need primary key and data only,if not will throw error.</param>
        /// <param name="targetTable">target table name</param>
        /// <param name="list">will operate data list.</param>
        /// <param name="tempTable">put data into temporary table,default name as 'target table name + # or _temp'</param>
        /// <param name="insertmapping">need to insert column,if is null,just use Mapping fileds,in order to avoid auto-create column</param>
        /// <param name="updatemapping">need to modify column,if is null,just use Mapping fileds</param>
        Task MergeAndDeleteToServer<T>(DbConnection connection, List<string> keys, string targetTable, List<T> list, string tempTable = null, List<string> insertmapping = null, List<string> updatemapping = null);

        /// <summary>
        /// create temporary table
        /// </summary>
        /// <param name="tempTable">create temporary table name</param>
        /// <param name="targetTable">rarget table name</param>
        /// <param name="connection">database connecting</param>
        Task CreateTempTable(string tempTable, string targetTable, DbConnection connection);
    }

解释几个方法的作用:



InitDefaultMappings:初始化映射,将目标表的字段映射到实体,在批量操作时候会根据反射进行一一匹配表字段;


CopyToServer:批量新增,在符合数据表结构时批量复制到目标表,采用官方 SqlBulkCopy 类结合实体简化操作。


MergeToServer:增量同步,需指定唯一键,存在即更新,不存在则插入。支持指定更新字段,指定插入字段。


UpdateToServer:批量更新,需指定 where 条件,以及更新的字段。


MergeAndDeleteToServer:增量同步,以数据源和目标表进行匹配,目标表存在的则更新,不存在的则插入,目标表存在,数据源不存在则目标表移除。


CreateTempTable:创建临时表。



增加实体属性标记,用来标记列名是否忽略同步数据,以及消除数据库别名,大小写的差异。


 /// <summary>
    /// 数据库批量操作标记,用于标记对象属性。
    /// </summary>
    public class DbBulkAttribute : Attribute
    {
        /// <summary>
        /// 是否忽略。忽略则其余属性不需要设置,不忽略则必须设置Type。
        /// </summary>
        public bool Ignore { getset; }

        /// <summary>
        /// 列名,不设置则默认为实体字段名小写
        /// </summary>
        public string ColumnName { getset; }

    }

实现类,目前仅支持 SqlServer 数据库,正在更新 MySql 和 PGSql 中。然后需要定义BatchSize(default 10000)、BulkCopyTimeout (default 300)、ColumnMappings,分别是每批次大小,允许超时时间和映射的字段。


/// <summary>
    /// sql server batch
    /// </summary>
    public class SqlServerAsyncBulk : IDbAsyncBulk
    {
        /// <summary>
        /// log recoding
        /// </summary>
        private ILogger _log;
        /// <summary>
        ///batch insert size(handle a batch every time )。default 10000。
        /// </summary>
        public int BatchSize { getset; }
        /// <summary>
        /// overtime,default 300
        /// </summary>
        public int BulkCopyTimeout { getset; }
        /// <summary>
        /// columns mapping
        /// </summary>
        public Dictionary<stringstring> ColumnMappings { getset; }
        /// <summary>
        /// structure function
        /// </summary>
        /// <param name="log"></param>
        public SqlServerAsyncBulk(ILogger<SqlServerAsyncBulk> log)
        {
            _log = log;
            BatchSize = 10000;
            BulkCopyTimeout = 300;
        }
        
        //...to do

使用上也非常的简便,直接在服务里注册单例模式,使用的时候直接依赖注入。


 //if you use SqlServer database, config SqlServerAsyncBulk service.
services.AddSingleton<IDbAsyncBulk, SqlServerAsyncBulk>();

public class BatchOperate
{
  private readonly IDbAsyncBulk _bulk;
  public BatchOperate(IDbAsyncBulk bulk)
  {
    _bulk = bulk;
  }
}

以 user_base 表举两个实例,目前测试几十万数据也才零点几秒。


 public async Task CopyToServerTest()
        {
            var connectStr = @"Data Source=KF009\SQLEXPRESS;Initial Catalog=MockData;User ID=xxx;Password=xxx";
            await _bulk.InitDefaultMappings<UserBaseModel>();
            var mock_list = new List<UserBaseModel>();
            for (var i = 0; i < 1000; i++) {
                mock_list.Add(new UserBaseModel
                {
                    age = i,
                    birthday = DateTime.Now.AddMonths(-i).Date,
                    education = "本科",
                    email = "xiaoyu@163.com",
                    name = $"小榆{i}",
                    nation = "
",
                    nationality="
中国"
                });
            }
            await _bulk.CopyToServer(connectStr, "
user_base", mock_list);
        }

public async Task MergeToServerTest()
        {
            var connectStr = @"Data Source=KF009\SQLEXPRESS;Initial Catalog=MockData;User ID=sa;Password=root";
            await _bulk.InitDefaultMappings<UserBaseModel>();
            var mock_list = new List<UserBaseModel>();
            for (var i = 0; i < 1000; i++)
            {
                mock_list.Add(new UserBaseModel
                {
                    age = i,
                    birthday = DateTime.Now.AddMonths(-i).Date,
                    education = "本科",
                    email = "mock@163.com",
                    name = $"小榆{i}",
                    nation = "汉",
                    nationality = "中国"
                });
            }
            var insertMapping = new List<string> { "birthday""education""age""email""name""nation""nationality" };
            var updateMapping = new List<string> { "birthday""education""age""email"};
            await _bulk.MergeToServer(connectStr,new List<string> {"id"}, "user_base", mock_list,null, insertMapping, updateMapping);
        

到这里,也已经完成了批量数据操作啦,不用再面对大量的sql操作啦。面向 sql 开发一时确实爽,但是面临变化或者别人接手的时候,是很痛苦的。


具体实现细节内容过多,篇幅有限暂时不全部展示,有兴趣或者尝试的伙伴可以进 github 进行参考。



github👉:github.com/sangxiaoyu/… 💖



作者:桑小榆呀
来源:juejin.cn/post/7290361767141376057
收起阅读 »

别傻啦!工作这件事就是你拿钱实现别人的梦想!

工作这件事,从根本上说,就是别人花钱请你实现 ta 的梦想。 既然是别人花钱实现 ta 的梦想,那你觉得是 ta 的意愿更重要一些还是你自己的意愿更重要一些(注意,这其实是一句反问句)? 你上班的路上去一个早餐摊买早餐,早餐摊既可以卖煎饼也可以卖鸡蛋灌饼。你给...
继续阅读 »

工作这件事,从根本上说,就是别人花钱请你实现 ta 的梦想。


既然是别人花钱实现 ta 的梦想,那你觉得是 ta 的意愿更重要一些还是你自己的意愿更重要一些(注意,这其实是一句反问句)?


你上班的路上去一个早餐摊买早餐,早餐摊既可以卖煎饼也可以卖鸡蛋灌饼。你给早餐摊老板十块钱说给我做个鸡蛋灌饼,他说对不起,我的梦想是成一个优秀的煎饼maker,所以我不能给你做鸡蛋灌饼,只能给你做煎饼。


你听了这句话肯定心里一万句草泥马崩腾而过:“你的梦想关我屁事啊!我就想吃鸡蛋灌饼!老子花了钱的!你想做煎饼maker你自己花钱去做啊,为啥花我的钱给你做煎饼!”


如果这个早餐摊老板说啥非要坚持只做煎饼不做鸡蛋灌饼会发生什么?你肯定会立刻离开他然后去别的地方买你想吃的东西。也就是说,交易会取消。


说回来,工作的本质就是别人花钱请你实现ta的梦想,所以ta扔给你的事情一定是围绕着ta的目标展开的,你对这些工作感受如何是次要的,毕竟人家花钱了。如果你觉得他扔给你的工作太多或者太憋屈,那就离开,交易取消。


当然大部分老板都不会彻底不顾及你的感受,因为要是彻底把你惹毛了,谁替他实现梦想呢?所以他也会在一定程度上顾虑你的感受,哪怕对 ta自己的目标有负面影响。比如老板希望你 7✖️24小时给公司工作,然而他也知道你这样工作三天就得累死,所以也会安排你休息。你们双方彼此互相退让,会达成最终的一致,然而这个“一致”的天平一定不是正好在你俩中间的,而是偏向你的老板的,因为人家花钱了。


再换句话说,只要你还拿着老板的钱,那你老板的目标就比你的目标更重要。


前几天有一个刚刚毕业的小朋友找我聊天,他去参与了一个销售岗位的面试,这家公司的HR问他为什么从上一家公司离职。他是这么跟HR说的:“我在上一家公司销售的产品是一个纪念币,纪念币印着阿根廷足球队,然而金币上印着两个星(代表阿根廷得过两次世界杯冠军)。然而我去推销这个金币的时候,阿根廷已经三星了,这种情况我怎么可能卖的出去?”然后他问我,他这么跟HR说,HR会怎么想。


我回答他说:“HR会想,他上一家公司的产品有缺点,所以他就完全卖不出去。我们家的产品也有很多缺点,所以这个人来了我们家肯定也卖不出去。这个候选人 fail。”


小朋友听了我的话,一瞬间蔫儿了。然后他想了想,问我如果是我会怎么说。


我跟他说,天底下一切商品都是有缺点的,连全世界最畅销的商品都不例外,即使天底下最成功的公司,走进公司内部看都是一地鸡毛的。产品有缺点就卖不出去,按照这个逻辑,那世界上就不该有销售这个岗位。


换一个角度去想,假设你是老板,你有一个完美无瑕的商品,谁见谁夸,所有人都抢着买,那我雇佣销售干什么?我买一堆自动售货机不就行了吗?因为我的产品不够完美,所以才需要销售人员推销,所以在老板的心里,销售人员需要解决我的产品“不完美”的问题。具体怎么解决,那是你的问题,而不是我的问题,我花钱雇佣你就是为了让你解决这个问题的。


然而在刚才那位小朋友的心里这个事情却是反过来的,老板应该把他的产品变“完美”,要不我卖的很辛苦。在他的逻辑里,老板仿佛应该为他”工作轻松“而服务。这就是一个以自我为中心的本末倒置的心态,抱着这样的心态在职场混,就难免混的很惨了。


拿人钱财替人消灾,既然拿了工资,就应该优先考虑组织遇到的难题是什么,组织需要我解决的难题是什么。组织有了好结果,组织才会把好结果回馈给我,我自己才能好。这才是这个世界运转的基本逻辑。


在我看来,职场人里90%以上的抱怨,都是因为没有想清楚这个最基本的逻辑。


前两天有人在我的帖子下面大吐苦水,说现在的团队晋升都是优先“老人”,“新人”要等很久,所以这个情况对“新人”太不公平啦。言下之意就是公司应该改革晋升制度,让新人更容易晋升一些。


兄弟,老板当初创办了这家公司,就是为了赚钱的。所以老板一切行为的根本目的就是赚钱,设计“晋升”制度就为了更好的赚钱。赚钱是目的,“晋升制度”只是手段。公司的运营情况一旦发生了变化,公司需要调整制度,老板是该优先考虑赚钱的难易度,还是该优先考虑你的晋升难易度?


我相信有的人看到这里就会说我是“精神资本家”,明明也是给人打工的,咋了老胳膊肘往外拐给资本家说话。(loser的智力全点在编名词上了)


我之所以这么想,是因为我明白一个很基本的道理:“这个世界不是围绕着我运转的。


在这个芸芸众生的苍茫世界中,我只是一个微不足道的尘芥。我每天从拥挤的13号线地铁上下来,几千人从我的身边匆忙走过,根本没有人会在乎我。北京城2300万常驻人口,全中国14亿人口,乃至全世界70亿人口,没有人亏欠我什么


我作为一个成年人,就应该自力更生养活自己。这几千万的陌生人里,有一个人跟我说一个月给我几万块钱,换我给他打工赚钱,在我点头答应的那一刻,这就是两个成年人之间平等的合约,他不欠我的,就像我也不欠他的一样


如果有一天我觉得他给我的钱太少,配不上我的劳动,我就大大方方的结束这场交易。


有一天他雇佣了一个比我年轻好多岁的人,付的工资比给我的还多。我听说了以后一定会心里很不是滋味,但是我也说不上什么。因为钱是人家的,人家爱给谁给谁,爱给多少给多少,这是人家的自由,人家不欠我的。


老板跟我谈好,如果我能给他赚更多钱,他就给我付更多的工资,这是一场公平的交易。我绝不会觉得我什么都不需要做,只是干等着,老板就欠我一个“晋升”。


别人都是自私自利的,我特别理解他们,原因很简单,我自己也是自私自利的。别人就是应该先为他们自己考虑,有空闲才会考虑一下我,因为遇到利益分配的时候,我也会优先考虑我自己。人类就是这种生物,你是人,你的老板也是人。我如果想要别人优先考虑一下我,我就必须让我找他心里变得重要。而想让我在别人心里变得重要,就得优先满足别人的利益。


那些觉得其他人应该优先考虑他的人,全是心智未成年的“巨婴”。



大家有兴趣可以阅读一下《我有一个好主意》的文章



作者:马可奥勒留
来源:juejin.cn/post/7290475242274603042
收起阅读 »

晋升成功了

大家好呀,我是楼仔。 上次给大家说的晋升,昨天结果出来了,成功晋升到 17 级,下面就给大家讲讲晋升的注意事项,以及一些思考,希望能给大家一点启发。 先直接给出 4 点经验: 答辩材料,金字塔原理 + 突出重点; 提前反复卡表练习,重要场合需写逐字稿; 提前...
继续阅读 »

大家好呀,我是楼仔。


上次给大家说的晋升,昨天结果出来了,成功晋升到 17 级,下面就给大家讲讲晋升的注意事项,以及一些思考,希望能给大家一点启发。


先直接给出 4 点经验:



  • 答辩材料,金字塔原理 + 突出重点

  • 提前反复卡表练习,重要场合需写逐字稿

  • 提前准备评委问题,做到心中有数、手中有策

  • 面对提问,沉重冷静,先思考,再回答


4 周多的准备


小米的晋升材料是 PPT,这里其实有个误区,很多同学都是把材料写完后,再找领导指导,如果你的材料问题很大,再去大量返工,时间肯定不够。


我先花 1 周把材料框架写完,PPT 里面的内容可以用文字简单代替,再找老板过了一遍,保证整体框架和思路没有问题。


提前写好材料框架,然后找领导指导,先保证内容不跑偏。


那材料如何写呢?可以借鉴金字塔原理


先用一页 PPT 写你所有的业绩产出,然后拆解到每个项目,最后用 STAR 原则去拆解每个项目。



当然,你也可以用结果导向,先写达成结果,再说明自己遇到的挑战,以及采取的行动:结果 -> 困难和挑战 -> 行动,也是非常好的陈述方式。


不同的方式,表达效果完全不一样,选择自己最适合的就行。


后来又花 2 周准备材料内容,再给老板 Check 一遍,调整优化后,基本能达到终版要求。


晋升材料讲解时长 15 分钟,为了表达流畅,且精准卡时,每一页 PPT 需要讲哪些重点,都会先写在逐字稿上,然后反复练习,做到不超时。


有同学习惯对着逐字稿念,这个一定需要完全脱稿。


我都忘记自己练习了多少遍,开车的时候反复听录音,有时第三人称视角会让你发现很多问题。


最后就是问题准备,因为最后还有 10 分钟的评委提问,这个最难,也最不好把控。


每一页 PPT,评委可能提哪些问题,涉及哪些细节,需要提前想清楚,最后一天要答辩时,我都一直准备这块内容。


前期的准备,可以给你临场发挥,提供很多素材。


25 分钟答辩


答辩当天,看到结束答辩的同学,有的满头大汗,有的被评委问的一脸懵逼。


隔壁同桌特搞笑:我本来不紧张的,站在门口等待时,听到里面评委提问,突然就紧张起来。



快到我的时候,我一直听歌,选了首赵英俊的《送你一朵小红花》,在门口等待时把音乐调大了一点,凑到耳边再自我沉浸一会。


等到我的时候,打开材料、投屏、计时、答辩,15 分钟,一切刚刚好。


最后是 10 分钟的评委提问环节,3 个问题,前面 2 个我准备过,问题不大,就是回答的层次不够清晰。


最后一个问题很开放,之前完全没有考虑过,不难,但很重要,答得比较糟糕,第二天被老板拉去复盘。


归零心态,下一站启航


对于晋升结果,我其实并没有那么看重,之前就和同事吃饭聊天:如果这次晋升没有成功,我也能坦然接受。


这个其实源于自己之前的一段经历。


之前在小米其它部门做得非常不错,因为业务调整,整个团队散了,当时有过离开小米的打算,后来兜兜转转来到现在的部门。


有点像坐过山车,先在山顶,突然降到谷底,然后又回到山顶,让我思考了很多。



成绩和荣誉都带不走,只有拥有的个人能力,才属于自己。


也许,只有自己经历过,才能体会深刻。


除此之外,我也学会了“归零”。


归零,意味着放下、打破、重构。


如果一个人沉迷于以往的光环、荣耀、地位、平台,那他就再也不会进步。


归零,也是一种空杯心态。


想象一个玻璃杯倒满了水,如果我们继续往里倒,水会溢出。



之前的成绩和荣誉,我会将其尘封,“清空”杯子里面的水,关注自己的短板和不足,不断提升。


这种心态已保持 2 年。


一切过往,皆是序章


新的职级,新的要求,再给自己一些时间,相信会有不一样的成长。


作者:楼仔
来源:juejin.cn/post/7290826271830327331
收起阅读 »

逃离国企,我好快乐!

一入国企深似海,捆绑越多,你走得越难,有的人甚至终身被困于此,任人搓揉捏扁。离职就要走得干脆点,不要回头,通常更烦的事情可能还在后头,早走早解脱! 1.国企怎样的? 有人说进国企,基本等于金饭碗 emm,这是个很大的误区,事业编考进去国企的领导才是,我们这些签...
继续阅读 »

一入国企深似海,捆绑越多,你走得越难,有的人甚至终身被困于此,任人搓揉捏扁。离职就要走得干脆点,不要回头,通常更烦的事情可能还在后头,早走早解脱!


1.国企怎样的?


有人说进国企,基本等于金饭碗


emm,这是个很大的误区,事业编考进去国企的领导才是,我们这些签劳动合同的打工人,跟别的企业打工人没区别!


只不过,国企特别注重社会影响,相对于其他企业很少会裁员而已,这不代表不裁员。之前公司架构调整时就直接砍了一个事业部的人,不过n+1给到位,人家整个部门还开开心心聚餐去 。羡慕嫉妒恨,怎么不把我裁了?我也想要n+1。


国企福利待遇很不错


的确,之前挺不错,包三餐和夜宵(好多家餐馆任选,饭堂有汤有水果有酸奶有糖水,一顿可以选六道菜,麦当劳吃到厌),每月一次下午茶和一袋果蔬,季度生日会各种水果点心等,说实话,这么多好吃的,好多人入职不久就日渐圆润,然后公司员工戏称这里是养猪场。


之前有各种补贴,过节有过节费和活动,我还曾盲盒抽中过个很不错的键盘!


节日礼包直接顺丰快递邮寄回家,有人才政策的公司帮你还分期的免费手机电脑,还有公租房、暑假儿童托管班、挂靠公司的学位、免费每月150G的号码套餐等,各方面想得很周到,囊括你生活的众多方面,让你与公司各种福利紧密结合,以至于你想走也要思虑再三。


2.在国企的日子,我都干了什么?


我所在的团队主要搞医疗相关业务的,趁着疫情的机遇,公司发展得特别顺,所以日子很滋润。


比如21年广州荔湾区疫情突发时,我们也加班加点搞了个流动人群分析和网格热力的可视化大屏,22年初西藏疫情时,公司除了组织员工捐赠防疫物资,还派人支援,给他们弄了个公卫事件平台,以及分析大屏和传播链图谱,22年尾疫情大爆发,一堆人进方舱医院,我们团队负责了37个方舱医院的大屏和公卫平台……


然而,疫情开放后,这些业务没有了,人手过剩又没钱,然后又是架构调整,又是下放地市,又是捣鼓出各种开源节流的方案,很快,一堆福利都削减没了,骚操作越来越多。


3.我的裸辞为后面埋坑了


之前我写了《我裸辞了,但是没走成!》,引起了很多人的讨论,而我没有回复任何一个人,因为我觉得当初写那篇文的自己就是个傻逼!


留下来的第一个月月底!公司直接开会宣布要砍工资,绩效只发70%,呵呵!这触及大家的根本利益,我们团队真的太过乖了,不情不愿也都签名了!别的团队都抗议,集体不签,然后他们被拉去谈话,最终还是签名了!


这只是个开始,然后裁了一波员,把一些外包员工给砍了,包括我旁边那位很厉害的建模师,但是就是不动我们这些自有员工。因为自有员工工资很便宜,没必要动。


我们工资全靠福利补贴和年终奖拉平互联网工资水平线。现在来看,这就是隐形炸弹,给了公司很大的操作空间。福利现在砍了好多,我们的收入直线下降,加上绩效也砍了一截,日子更难过了!


而今年没什么大项目和好的业务,注定今年年终奖凉凉,所以补贴多和大头年终奖不一定是好事,劳动合同上没有明文规定的收入都是大陷阱,一旦公司要动,你去走法律途径也没理!


然后9月底,趁着国庆假期前,公司没人性地再砍一波,绩效只发60%!这下彻底炸了,我们组好几个人愤慨组队发邮件反馈不同意!


听说别的部门直接闹罢工抗议了,这次大家不再默默忍受,充分表达了不满!


然后周一早上,领导轮流约谈每人,意思很明确,你不同意也得同意,公司只是通知你一声,不论你是否同意,都会执行的!


4.没有未来还苟个鬼!


很多人说别那么功利,要看长远,国企稳定,不怎么加班,压力相对少,已经很不错了!


有些人追求安稳,牺牲点钱还能接受。道理我懂,但现在不是牺牲一点钱,是牺牲很多钱!傻逼才会被当水鱼一样宰!


之前我提辞职已经在领导那里留下案底,不出所料的,我第二季度拿了个B,加上我不愿参加党员事务,只想好好搞技术,然后积分倒数,根据党员规则,倒数不能拿B+和A,意味着我第三季度只能是B。这样恶性循环,那么我永远不能升职加薪,这日子已经没有盼头了!


最近我主导一个BI项目的重构,原本只是梳理成新架构,将功能和界面优化,现在直接变成产品推倒从零开始。


最可怕的是,产品不懂技术,还对原BI平台功能一问三不知,她们只看界面样式,然后她们很喜欢照搬照抄大厂优秀的产品,很喜欢想一套是一套,宗旨就是往复杂的方向一去不复返,恨不得每个功能组件都定制化一个个手动开发,还特别喜欢那种一层嵌一层没完没了的鸡肋操作,不考虑复用性,不考虑我们这边开发的实际情况,也没有想过逻辑通不通的问题,如果要按着这样开发就要耗费大量时间!


然而,领导要在短时间内看到成果,我作为主开发,要把控进度和规划任务,其他组员都比较佛系,然后我就成了那个去争取,去battle的出头鸟。


然后产品每次会说,这是基于用户角度设计的,XX大厂的产品就这么设计,为什么人家能这样搞,你搞不了?


呵呵!人家多少人,我们多少人,人家打磨这个产品多久,我们有多少时间?老想一口吃成个大胖子,就没点逼数吗?


最烦的是,每期评审都喜欢基于以前的功能修改新增,就不能一次性确认吗?导致好些原本写好的功能,还有可能要推翻重写!啊啊啊,想打人!


她们的不专业让我很烦躁,沟通也变得凶!然后我被批评注意点情绪管理,但我发现温和沟通也是没用的,因为人家根本不理。


1696341844174.png


下期个鬼!下期还不是我们来搞!


1696333287130.png


没想清楚就让你开搞,然后做完又不要,浪费大家时间!


我不否认我的工作方式也有问题,估计很多人都烦死我了!与其内耗自己不如一起内耗,相互伤害啊!


最近情绪很暴躁,加上我身体也发出了相应警告,严重爆痘,不停出红疹,每天都要吃抗过敏药压着,这样的情况持续了三四个月,感觉这样下去我迟早会崩溃!


公司现在明摆着就是不愿给裁员赔偿,耗着你,逼你走!


钱少事多,今年白干了,我累了,也不想耗下去了,然后果断选择撤离,提了离职,这次走得很坚决。


重构也完成了,现在只是修复bug继续优化,也对得起新组长的挽留!


我的亲戚说,打份工而已,不要太认真!


b1441fb2ac39e52a039bffc18e2c8b6.jpg


不愧是职场老鸟,真知灼见啊!


5.职场生存指南


1. 不要局限于现在的技术,保持学习。


你参与的项目可能只用到一部分的技术,而市场上要求的技术面往往更多,长久以往,你会被限死在这片技术领域,逐渐被养废,导致自己失去市场竞争力。


2. 领导的话听听就算了,别当真!


上司很喜欢PUA现在外面的情况不好混,他都没去外面混过,怎么知道外面不好混?只不过是因为他自己走不了,所以得拉多几个人一起落水,这样他心里就平衡多了!


3. 福利可以有,但不能过分依赖!


过分依赖会让自己失去主动权!尽量减少自己与公司的捆绑,避免被公司拿捏,要为自己的未来预留后路,即便走也能无后顾之忧。


4. 面向领导的开发,技术能力不是升职加薪的重点,给领导创造价值才是!


比如领导在大领导面前展示你做的这个产品给公司赚了多少钱,得到了赞许,那么你可能就能得到好的绩效考核,如果你做的东西,没什么业务,一直内部团队应用,自己玩自己的,那么就是没产出,没价值,别想升职加薪了!


注意!!!给别的部门团队创造价值不属于给领导创造价值,反而可能会给领导添堵,因为人家领导成果多了,自家领导压力就大了!分清敌我关系!除非是合作项目,成果共享那种!


5. 尝试与别的部门团队的人认识,人脉等于机会!


我通过给公司软考群整理资料,认识了别的团队的人,也因此在之前考虑转部门团队的时候能找人打听情况。有时候不是这个公司不好,而是团队里某些人讨厌,当你想要逃离时,可以考虑转部门团队,如果你对他们有利用价值,他们将是你要用到的贵人!


让我狂笑一下,啊哈哈哈哈~


啦啦啦!我解脱啦!啦啦啦!我好快乐!


作者:敲敲敲敲暴你脑袋
来源:juejin.cn/post/7290758270577557539
收起阅读 »

136. 只出现一次的数字

题目 题解 考察的是位运算 —— 异或(^),相同为 0,不同为 1 1^0 = 1,1^1 = 0 则直接对数据所有元素执行 ^ 操作,最终的就是结果 class&nbs...
继续阅读 »

题目





题解





  • 考察的是位运算 —— 异或(^),相同为 0,不同为 1



  • 1^0 = 1,1^1 = 0



  • 则直接对数据所有元素执行 ^ 操作,最终的就是结果


class Solution {
    public int singleNumber(int[] nums) {

        int res = 0;

        for (int num : nums) {
            res = res ^ num;
        }

        return res;
    }
}

作者:程序员小航
来源:mdnice.com/writing/7bb65e0150154b28b4777a1ea6e2784b
收起阅读 »

JavaScript 基础(一)

web
变量 变量概述 数据类型 运算符 算数运算符 递增递减运算符 比较运算符 逻辑运算符 赋值运算符 运算符优先级 选择结构 流程控制(顺序,分支,循环) 小结 数组 函数 函数基本概念 arguments 函数声明方式 Java...
继续阅读 »

变量


变量概述


image.png


数据类型


image.png
image.png


运算符


算数运算符


image.png


递增递减运算符


image.png


比较运算符


image.png


逻辑运算符


image.png


赋值运算符


image.png


运算符优先级


image.png


选择结构


流程控制(顺序,分支,循环)


image.png


小结


image.png


数组


image.png


函数


函数基本概念


image.png
image.png
image.png


arguments


image.png


函数声明方式


image.png


JavaScript作用域


image.png


预解析


image.png
image.png


sum(1)(2,3)(4,5,6) js的链式调用


function sum(){
let arr = Array.prototype.slice.call(arguments);
fn = function(){
let arr1 = Array.prototype.slice.call(arguements)
return sum.apply(null,arr.concat(arr1))
}
//`reduce()` 方法将数组缩减为单个值。
//`reduce()` 方法为数组的每个值(从左到右)执行提供的函数。
//函数的返回值存储在累加器中(结果/总计)。
//注释:对没有值的数组元素,不执行 `reduce()` 方法。
//注释:`reduce()` 方法不会改变原始数组。
fn.toString = function(){
return arr.reduce((value,n)=>{
return value+n
})
}
return fn
}

//柯里化的高级实现
function curry(func){
return curried(...args){
if(args.length >= func.length){
return func.apply(this,args);
}else{
return function(...args2){
return curried.apply(this,args.concat(args2))
}
}
}
}

面试点


image.png


sum(1)(2)(3)怎么实现


面试的时候,面试官让我讲一个这个怎么实现
当时想到的是嵌套闭包,调用返回值,进行累加


//这个是我在其他文章上看到的代码
function sum(a){
function add(b){
a =a+b
return add;
}
add.toString = function(){
return a;
}
return sum;


查询了一下,涉及到了一个知识点,函数柯里化


//正常的函数
function sum(a,b){
var sum = 0;
sum = a+ b;
console.log(sum);
}

//柯里化函数
function curry(a){
return function(b){
console.log(a+b)
}
}
const sum = curry(1);
// 这个过程分为三步
// step1:
// addCurry(1)
// 返回下面的函数
// ƒ (arg) {
// return judge(1, arg);
// }
// step2:
// addCurry(1)(2)
// 返回下面的函数
// ƒ (arg) {
// return judge(1,2, arg);
// }
// step3:
// addCurry(1)(2)(3)
// 返回并执行下面的函数
// return fn(1,2,3);
// 最终得到结果6

函数柯里化


把接收多个参数的函数变换成接受一个单一参数的函数,并且但会接收余下的采纳数并且返回结果的新函数的技术。eg:它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)


参数复用


用于正则表达式的检验


//记录思路
柯里化检验函数curry
const check = curry();
生成工具化函数,进行验证
const checkphone = check(/'正则表达式'/)
检验电话号码
checkphone('122333232');

延迟计算


返回的函数都不会立即执行,而是等待调用(开发者调用)

动态生成函数


比如说,在dom结点中每每绑定一次事件,都需要对环境进行判断,再去绑定这个事件;将这个过程进行柯里化,在使用前进行一次判断

const addEvent = (function() {
if (window.addEventListener) {
return function(ele) {
return function(type) {
return function(fn) {
return function(capture) {
ele.addEventListener(type, (e) => fn.call(ele, e), capture);
}
}
}
}
} else if (window.attachEvent) {
return function(ele) {
return function(type) {
return function(fn) {
return function(capture) {
ele.addEventListener(type, (e) => fn.call(ele, e), capture);
}
}
}
}
}
})();

// 调用
addEvent(document.getElementById('app'))('click')((e) => {console.log('click function has been call:', e);})(false);

// 分步骤调用会更加清晰
const ele = document.getElementById('app');
// get environment
const environment = addEvent(ele)
// bind event
environment('click')((e) => {console.log(e)})(false);
//来自于[一文搞懂Javascript中的函数柯里化(currying) - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/120735088)

柯里化函数后虽然代码比较冗长,但是它的适用性增加了

柯里化封装


eg : sum(1,2,3...)


    //函数柯里化封装(这个封装可以直接复制走使用)
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function () {
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this, fn, newArgs);
} else {
return fn.apply(this, newArgs);
}
}
}

//需要被柯里化的函数
function multiFn(a, b, c) {
return a * b * c;
}

//multi是柯里化之后的函数
var multi = curry(multiFn);
console.log(multi(2)(3)(4));
console.log(multi(2, 3, 4));
console.log(multi(2)(3, 4));
console.log(multi(2, 3)(4));
//转载自[Javascript高级篇之函数柯里化 - 掘金 (juejin.cn)](https://juejin.cn/post/7111902909796712455)


Function.prototype


四个方法:apply,bind,call,toString


Function.prototype.bind()


定义


bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。(MDN)


const module = {
x: 42,
getX: function () {
return this.x;
},
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// Expected output: undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// Expected output: 42


Function.prototype.apply()


function 的实例的apply()方法会以给定的this值和作为数组(或类数组对象)提供arguments调用该函数


const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max);
// Expected output: 7
const min = Math.min.apply(null, numbers);
console.log(min);
// Expected output: 2

语法


apply(thisArg) apply(thisArg, argsArray)


参数


//argsArray --- 用于指定调用func时的参数,或者如果不需要向函数提供参数,则为null或undefined

//thisArg --- 调用func 时提供的this值,如果函数不处于严格对象,则null 和undefined 会被替换成全局对象,原始值会被转换成对象

返回值


使用指定的this值和参数调用函数的结果


Function.prototype.call()


Function 实例的 call() 方法会以给定的 this 值和逐个提供的参数调用该函数


//示例
function Product(name, price) {
this.name = name;
this.price = price;
}

function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// Expected output: "cheese"

//语法
call(thisArg)
call(thisArg, arg1)
call(thisArg, arg1, arg2)
call(thisArg, arg1, arg2, /* …, */ argN)


Function.prototype.toString()


Function 实例的 toString() 方法返回一个表示该函数源码的字符串。


function sum(a, b) {
return a + b;
}

console.log(sum.toString());
// Expected output: "function sum(a, b) {
// return a + b;
// }"

console.log(Math.abs.toString());
// Expected output: "function abs() { [native code] }"

放一个JavaScript的链接


转载自:http://www.cnblogs.com/wangdan0915…


作者:有玺鹤呖
来源:juejin.cn/post/7288962917399035956
收起阅读 »

秋招过半零Offer怎么办?

参加今年秋招的同学都知道,尤其是双非本科更是体验深刻。9 月份至今,面试寥寥无几、笔试也不是很多,大中小公司 Offer 没拿下一个。作为应届生的我们,该怎么办呢? 1.调整好心态 这个世界上有两种事:一种是你能掌控的,另一种是你不能掌控的。我们只能做好我...
继续阅读 »

参加今年秋招的同学都知道,尤其是双非本科更是体验深刻。9 月份至今,面试寥寥无几、笔试也不是很多,大中小公司 Offer 没拿下一个。作为应届生的我们,该怎么办呢?


1.调整好心态


这个世界上有两种事:一种是你能掌控的,另一种是你不能掌控的。我们只能做好我们能掌控的事,而对于我们掌控不了的事,不用太关注,也不用太在意。因为它本身已经超出了你的掌控范围了,你无论如何努力、你无论如何在乎,也改变不了任何结果,所以不必在它上面花费太多的时间和精力,没有任何回报,反而是自增烦恼。


而对于找工作也是一样,你积极去投简历、积极去面试、积极去复习、积极去笔试,这些都是你能掌控的事。而对于面试机会少、用人单位录取严苛这些事,已经超出了你的掌控范围,所以这些事不要太在意。


得之我幸,失败了就当积累经验了,之后再好好复习,再去尝试其他家公司就行,你要相信,该来的总会来的。


即使最坏的结果,也无非是秋招没有找到合适的工作,那还有明年 3 月份的春招呢。


2.积极投递简历


积极简历也就是“海投简历”,它是拿到 Offer 最关键的步骤,没有之一。 如果你的面试机会少、如果你还没有一个保底 Offer,那么此时此刻,没有什么比【海投简历】更重要的事了。


2.1 什么是海投简历?


海投简历是指,你要把你能找到的、你能看到的所有和你岗位相关的职位都投递一遍(简历)。


举个例子,例如你在 Boss 上投递 Java 研发工程师的工作,那么就搜索“Java”,然后把你能找到的(看到的)所有公司,且没投递的公司(投递的公司用 Excel 记录下来),全部(打招呼)投递一遍简历。


注意:不用去看 HR 发布的职位要求,很多公司发布的职位要求是比较高的,但大部分情况下,她们都会减低标准,给更多应聘者笔试和面试的机会。所以说,不要看到很高的职位要求就退缩了,任何机会都不要放过,海投就是投递所有和你职位相关的所有公司,一家都不放过,因为他的失败影响不大,但万一成功了就有工作了。


2.2 海投简历什么时候结束?


答:海投简历通常是到 11 月中下旬,或拿到第一个保底 Offer 之后,才会逐渐停止,所以做好打持久战的准备,没有任何事是一蹴而就的。


3.积极复习


在没有笔试和面试的时间里,除了每天海投简历之外,还要做好复习工作,因为只有做好它,才能保证一旦有面试机会,你才能把握住机会。


积极备战期间一定要把面试的理论知识、常见的面试题系统的过一遍。




PS:复习 Java 面试题可以去我的小破站:www.javacn.site



4.尝试投递更多岗位


投递更多岗位可以从以下三个维度出发:





  1. 投递测试开发工程师或测试工程师:学完 Java 知识之后,除了可以投递 Java 研发工程师之外,还可以投递测试开发工程师或测试工程师的岗位,因为他们笔试题和面试题大部分都是 Java 知识,所以可以投递更多相关技术岗的职位。



  2. 投递实习岗职位:如果实在没有正式岗的面试机会,也可以投递一下实习岗的职位。它的好处是,第一,增加面试经验;第二,先找一个实习,得到一个实习经历,下次春招就更容易拿到 Offer 了,而且实习也有可能直接转正,所以实习岗位也是需要投递的。不过要注意的是,拿到了实习岗的 Offer 之后,实习的时机要推迟到正式秋招之后,这样还有更多的时间,找其他更合适的工作。



  3. 不要挑城市:有些人投递简历的时候,只投自己的意向城市,而对于这两年的情况,我的建议是所有城市的合适岗位全投。万事开头难,三年以内的开发经验是最难找工作的,所以先保证能够入行是最重要的。并且拿到 Offer 了,如果实在不想去,那主动权还在你手里。所以不要挑城市,猛投就是了。


小结


今年这种情况,秋招过半,双非本科无实习经历,现在面试和笔试机会少是正常现象。但越是这种情况,越不能自乱阵脚,调整好心态、积极投递简历、积极复习、尝试投递更多岗位,即使秋招真的没有机会上岸,做好这几点来年还有春招呢。


不要慌、不要慌,太阳落了有月光。把握好自己能掌控的事情就已经胜利了,其他的事不归我管,那是老天爷的事。

作者:王磊
来源:mdnice.com/writing/65012ed330e24d3bb38eadab12778c18
收起阅读 »

总有一些人儿啊,你是打心眼里的喜欢哟

是确定性,是秩序,是安定的感觉。 上篇文章,我们聊了杭州亚运会那些松弛感满满的小姐姐们。 欣赏节目、体验愉悦的同时,我们也应该清楚,这些是表演效果,不是她们的日常工作状态。 万一误解,感觉为什么 只有我 守着安静的沙漠 等待着花开 只有我...
继续阅读 »

是确定性,是秩序,是安定的感觉。





上篇文章,我们聊了杭州亚运会那些松弛感满满的小姐姐们。


欣赏节目、体验愉悦的同时,我们也应该清楚,这些是表演效果,不是她们的日常工作状态。


万一误解,感觉为什么



只有我 守着安静的沙漠
等待着花开
只有我 看着别人的快乐
竟然会感慨



就走歪了。


任何工作,都会有挑战。


上点价值说的话,热力学第二定律提到,熵只会增加,只会越来越混乱,能量只能从高往低传。


工作,是一种创造秩序的、逆熵的现象,是对自然规律的反抗。


这也决定了我们必须付出努力,而且过程绝对不会轻松愉快。


但是也不用觉得灰心,因为生命本身,也是一个逆熵的过程。


没有挑战的生活,一片苍白。


东哥离开东北之前,是在设计院工作。


工作能力恰好在天花板之前,一切都轻车熟路。


但那样的工作状态,一天天的重复,也很无聊。


只有挑战和压力,才能把一个人的潜力,激发出来。


玩的更嗨更精彩。


今天在读书会,分享了一个概念,社交资产。


什么是社交资产?




社交资产可以从两个角度思考。


一个是主业之外,有一定难度、需要长期投入的爱好。


这种爱好,往往需要经年的磨砺,和长久投入巨大的能量。


同时在从事这种活动的过程中,体会到滚滚心流,进入到忘我的状态。


比如跑步。


东哥早先年就喜欢长跑。不论风吹雨打、骄阳霜雪。


东北的冬天,零下20多度,滴水成冰,也会专门开车去南湖公园跑步。


10公里下来,前胸后背的外衣,都结满了冰。


再比如现在的写作。


为了写好文章可谓殚精竭虑,常常是做梦都在琢磨。


好像有灵感了,一下子坐起来,却发现是黄粱一梦,刚才的东西早忘了个精光。


就记得是一个贼牛逼的灵感,哎~


这种爱好,具有社会性。


无论和谁聊起来,哪怕他对这件事情一无所知,也会不由得有点佩服。


就像刀总,更绝,喜欢马拉松。


我完全不能理解这个运动的意义和价值。


跑步应该是为了让自己更健康,而马拉松的起源,是跑死的雅典士兵。


说明这个运动,是要命的。咱玩点啥不好?


但每次聊起来,也是发自内心的佩服。


佩服的,是他的毅力吗?


不是。


是热爱,是生命力。


和他们在一起,你会感受到松弛、外放和自信。


是确定性,是秩序,是安定的感觉。


这份蓬勃发展生机勃勃的生命力,能打动几乎所有人。




资本的定义,是你消费后剩余的资源。


你用它进行投资,期待更好的回报。


从这个角度讲,社交资本就是我们互动过程中,不断利他,积累起来的人际关系资源。


积累的多了,就会有丰厚的回报。


就比如同样的一件事情,为什么有的人总是有人帮,有的人总是遭遇冷场?


就是因为这种大家愿意帮的人,平时积累了大量的社交资产。


他们不一定拥有太多金钱、权力,但大家会心甘情愿的,想帮他做一些事情。


怎么做到的?利他主义。


一个利他的人,即使在无利可图或不期待任何回报的情况下,也会关心和帮助别人。


对身边的人,不要吝啬你的善良。


在任何可以帮助到别人的地方,都可以力所能及的提供一些帮助。


大家都是聪明人,真正的善意不会被长期无视。


播下的种子,总有天开花结果。


网络上也好,现实中也罢,我们每个人都需要积累自己的社交资产。


你帮助别人就是存款,你要求别人帮助就是取款。


如果平时没有存款,需要取款用的时候,就会遭遇尴尬。


这也是我们一直以来说的,心怀善意,不要刻意。


去释放善意,有成人之美,时时多想想,自己能给对方提供什么价值


东哥这一路走来,非常幸运,遇到了很多热心帮助的朋友。


比如一起飞群友马骏。


当年是他领路,让我进了接触到了经济学,了解了城市的演变,才有了后来离开东北、移居深圳的一系列故事。


也才有了现在的东哥在湾区这个号。


比如一起飞群友冯凡。


当年是在他的推荐下,加入了张是之老师的社群,能有机会跟着大佬们一起投资,收获有了丰厚的收益。


这些年敢东奔西走,去香港留学,换好几个城市,一部分底气就是有当年投资的收益托底。


虽然直到现在,都没能有机会和他在现实中见面,但时时想起,也是非常感激。


接收善意的同时,东哥也在努力向他们学习,释放善意,结交和成就身边的人。


最近好多文章,就是有朋友写发微信咨询。


三言两语又说不清楚,就干脆写篇文章详细解释下。


用自己的专长,帮别人解决一些实际问题,用自己吃过的亏,让别人少走一些弯路。


如果你也有问题想咨询,欢迎微信上联系东哥。




今天我们聊了社交资产。


社交资产可以从两个角度解读。


一个角度是主业之外,一个长期的、持续的爱好。


人们会在这个爱好中,体验到蓬勃的生命力,体验到秩序。


另一个角度是善良,是利他思维。


没有谁的善良会被长久的无视,种下的种子,总会有开花的那天。


心怀善意,释放善意。


如果因为自己的存在,让身边的人变得更好更强,就等于为自己创造了一个更好的微生态。


然后在这个更好的生态里,一起积累,慢慢变富。



作者:jetorz
来源:mdnice.com/writing/c0d7bd2bfd7649878cf011e57239be4b
收起阅读 »

为网站配置SSL

HTTPS (全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在HTTP 的基础下加...
继续阅读 »

HTTPS (全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在HTTP 的基础下加入SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层(在 HTTP与 TCP 之间),这个系统提供了身份验证与加密通讯方法。



现状

















证书申请




除了向公有云申请证书, 也可使用 自签名openssl生成的证书,但方便起见还是使用云厂商提供的证书.








一般免费版,只有一年有效期.到期需要重新申请&更换














Nginx配置




将证书文件上传至/usr/local/openresty/nginx/conf/cert目录下.




博客项目当前的conf配置如下:


server {
    listen      80;
    server_name dashen.tech www.dashen.tech;
    access_log  /var/log/blog.access.log main;
    error_log  /var/log/blog.error.log;

  location / {
        root        /home/ubuntu/cuishuang.github.io;
        index       index.html;
        expires     1d;
        add_header  Cache-Control public;
        access_log  off;
    }
}


新增启用https的配置:
server {
      listen        443 ssl;                                                 
      server_name    dashen.tech www.dashen.tech;  #域名                         
      ssl_certificate      /usr/local/openresty/nginx/conf/cert/shuang_blog.pem;  #证书路径     
      ssl_certificate_key  /usr/local/openresty/nginx/conf/cert/shuang_blog.key;  #key路径             
      ssl_session_cache    shared:SSL:1m;   #s储存SSL会话的缓存类型和大小                       
      ssl_session_timeout  5m; #会话过期时间 

      access_log  /var/log/blog.access.log main;
      error_log  /var/log/blog.error.log;

location / {
        root        /home/ubuntu/cuishuang.github.io;
        index       index.html;
        expires     1d;
        add_header  Cache-Control public;
        access_log  off;
    }                                                     
  }



删掉之前的conf. 重启nginx,访问https://www.dashen.tech[1],已能正常访问.




再访问之前的网址https://dashen.tech[2],则







配置将http访问自动跳转到https




再增加一段配置:


server {
    listen      80;
    server_name dashen.tech www.dashen.tech;
    access_log  /var/log/blog.access.log main;
    error_log  /var/log/blog.error.log;

    return      301 https://$server_name$request_uri; #这是nginx最新支持的写法

  location / {
        root        /home/ubuntu/cuishuang.github.io;
        index       index.html;
        expires     1d;
        add_header  Cache-Control public;
        access_log  off;
    }
}

参考: Nginx强制跳转Https[3]


再次重启nginx,这时请求https://dashen.tech[4]就可以跳转到https://www.dashen.tech[5]




但因为网站下有部分资源使用了http,所以浏览器依然没有变为安全锁,


可参考Hexo启用https加密连接[6],


也可右键查看哪些请求使用了http,将其修改为https即可~





参考资料


[1]

https://www.dashen.tech: https://www.dashen.tech

[2]

https://dashen.tech: https://dashen.tech

[3]

Nginx强制跳转Https: https://www.jianshu.com/p/116fc2d08165

[4]

https://dashen.tech: https://dashen.tech

[5]

https://www.dashen.tech: https://www.dashen.tech

[6]

Hexo启用https加密连接: https://note.youdao.com/web/#/file/recent/note/WEBe69d252eb353dd5ee0210d053ec0cc3a/



作者:fliter
来源:mdnice.com/writing/3257fabc35eb44a7a9be93bd809ffeca
收起阅读 »

lstio在微服务框架中的使用

在云原生时代,微服务架构已经成为企业构建灵活、可扩展和高可用系统的首选方案。但是,微服务也带来了一系列新的挑战,包括服务发现、负载均衡、安全、监控等。Istio是一款开源的服务网格,它通过提供丰富的特性帮助开发者轻松应对这些挑战。在本文中,我们将探索Istio...
继续阅读 »

在云原生时代,微服务架构已经成为企业构建灵活、可扩展和高可用系统的首选方案。但是,微服务也带来了一系列新的挑战,包括服务发现、负载均衡、安全、监控等。Istio是一款开源的服务网格,它通过提供丰富的特性帮助开发者轻松应对这些挑战。在本文中,我们将探索Istio在微服务框架中的使用原理,并深入分析与Spring Cloud的集成案例。


Istio的使用原理


Istio通过将智能代理(Envoy)注入到每个微服务的Pod中,从而实现微服务之间的网络通信的拦截和管理。Envoy代理负责处理服务与服务之间的交互,这样就能在不修改微服务业务代码的情况下,实现流量管理、安全、监控等功能。


源码结构


Istio的源码由Go语言编写,主要包括以下组件:



  1. Envoy Proxy:由C++编写,负责流量的代理和管理。

  2. Pilot:提供服务发现和流量管理功能。

  3. Mixer:负责策略控制和遥测数据收集。

  4. Citadel:提供服务间通信的安全认证和授权功能。


实例:Istio与Spring Cloud集成


以下是一个简单的示例,展示如何在Spring Cloud微服务中使用Istio。


在Istio和Spring Cloud的集成场景中,你需要在Kubernetes集群中部署Spring Cloud应用,并且确保Istio的Envoy代理被注入到应用的Pod中。以下是一个详细步骤和配置说明。


1. 安装Istio


首先确保你已经在Kubernetes集群中安装了Istio并启用了自动sidecar注入。如果还没有安装,可以按照Istio的官方文档进行安装和配置。


2. 准备Spring Cloud应用的Docker镜像


确保你的Spring Cloud应用已经被打包为Docker镜像,并推送到Docker镜像仓库中。例如:


docker build -t myrepo/springcloud-service:v1 .
docker push myrepo/springcloud-service:v1

3. 创建Kubernetes Deployment配置文件**


创建一个YAML配置文件,用于部署Spring Cloud应用。注意,我们在Pod的metadata.annotations中添加了sidecar.istio.io/inject: "true",用于启用Istio sidecar自动注入。


例如,创建一个名为springcloud-service-deployment.yaml的文件,内容如下:


apiVersion: apps/v1
kind: Deployment
metadata:
name: springcloud-service
spec:
replicas: 3
selector:
matchLabels:
app: springcloud-service
template:
metadata:
labels:
app: springcloud-service
annotations:
sidecar.istio.io/inject: "true" # 启用Istio sidecar自动注入
spec:
containers:
- name: springcloud-service
image: myrepo/springcloud-service:v1 # 使用你的Spring Cloud应用镜像
ports:
- containerPort: 8080 # 应用的端口号

4. 部署Spring Cloud应用到Kubernetes集群**


使用kubectl命令行工具部署应用:


kubectl apply -f springcloud-service-deployment.yaml

这会在Kubernetes集群中创建一个新的Deployment,运行你的Spring Cloud应用,并且每个Pod中都会注入Istio的Envoy代理。


5. 检查部署状态**


使用以下命令查看Pod的状态,确保所有Pod都已经正常运行,并且Istio的Envoy代理也被正确注入。


kubectl get pods

你应该能看到类似如下的输出,其中2/2表示每个Pod中有两个容器(你的应用和Istio Envoy代理)都已经正常运行。


NAME                                       READY   STATUS    RESTARTS   AGE
springcloud-service-5c79df6f59-9fclp 2/2 Running 0 5m
springcloud-service-5c79df6f59-g6qlc 2/2 Running 0 5m
springcloud-service-5c79df6f59-wvndz 2/2 Running 0 5m

至此,你的Spring Cloud应用已经被成功部署在Istio服务网格中,可以利用Istio提供的各种特性来管理和监控应用的运行。


配置Istio资源


当你的 Spring Cloud 应用已经成功部署在 Istio 服务网格中后,你需要配置 Istio 资源以管理服务间的流量、安全、策略和遥测等功能。以下我们将具体说明如何配置 Istio 资源,主要涉及 VirtualService 和 DestinationRule。


1. VirtualService**


VirtualService 定义了访问一个服务的路由规则。你可以控制根据不同的请求属性(例如 URL、请求头等)将流量路由到不同的服务或服务的不同版本。


创建 VirtualService 配置文件


以下是一个 virtual-service.yaml 的示例,该文件定义了一个简单的路由规则,将所有发送到 springcloud-service 服务的流量路由到标有 "v1" 标签的 Pod。


apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: springcloud-service
spec:
hosts:
- springcloud-service
http:
- route:
- destination:
host: springcloud-service
subset: v1

这里 hosts 定义了这个 VirtualService 的作用域,即它将控制哪些服务的流量。http 定义了 HTTP 流量的路由规则,destination 定义了匹配的流量将被路由到哪里。


2. DestinationRule


DestinationRule 定义了 Pod 的子集和对这些子集的流量的策略。通常和 VirtualService 一起使用,用于细粒度控制流量。


创建 DestinationRule 配置文件


以下是一个 destination-rule.yaml 的示例,定义了 springcloud-service 服务的两个子集:v1 和 v2。


apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: springcloud-service
spec:
host: springcloud-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2

在这里,我们定义了两个子集 v1 和 v2,分别匹配标签为 version=v1version=v2 的 Pod。这样你就可以在 VirtualService 中使用这些子集来控制流量。


应用 Istio 配置


将 VirtualService 和 DestinationRule 的配置文件应用到 Kubernetes 集群中:


kubectl apply -f virtual-service.yaml
kubectl apply -f destination-rule.yaml

通过配置 Istio 的 VirtualService 和 DestinationRule,您可以轻松控制和管理在 Istio 服务网格中运行的 Spring Cloud 应用的流量。你可以实现各种高级的流量管理功能,如金丝雀发布、蓝绿部署、流量镜像、故障注入等,而无需更改应用的代码。


代码示例


下面是一个基于Spring Cloud的简单微服务应用代码示例。


@RestController
public class HelloWorldController {

@RequestMapping("/hello")
public String hello() {
return "Hello, World!";
}

}

在Istio环境中,你不需要更改Spring Cloud应用的代码。Envoy代理会自动处理服务间的通信,你只需要使用Istio的配置文件定义流量路由规则、策略等。


总结


Istio与Spring Cloud的结合为开发者提供了一种强大的方式来部署、管理和扩展微服务应用。Istio的流量管理、安全认证和遥测数据收集功能使得开发者能够更加关注业务逻辑的开发,而不是底层的网络通信和安全问题。希望这个更详细的指南能帮助你更好地理解Istio与Spring Cloud的集成。


作者:一只爱撸猫的程序猿
来源:juejin.cn/post/7290485584069001231
收起阅读 »

大专生自学前端求职历险记

关于我 由于高中的游手好闲、不学无术,没有考上大学。去了一所专科学校,本以为自己能够浪子回头,在学校好好学习。可惜的是,来到一个陌生又充满诱惑的城市后,迅速的迷失了自己,天天埋头打游戏,学习的事情早已抛之脑后。 一晃眼,到了2020年,疫情的接踵而至,让我这个...
继续阅读 »

关于我


由于高中的游手好闲、不学无术,没有考上大学。去了一所专科学校,本以为自己能够浪子回头,在学校好好学习。可惜的是,来到一个陌生又充满诱惑的城市后,迅速的迷失了自己,天天埋头打游戏,学习的事情早已抛之脑后。


一晃眼,到了2020年,疫情的接踵而至,让我这个本来没有任何技术、学历的“闲散人士”更加雪上加霜。豪不夸张的说,当时去实习,就差跪着求人家要我,说自己不要薪资。经历过一个月后,也就是2020年5月底,我找到了一份前端开发工作,从此开启了我的前端开发工作之旅。


在专科学校里的时间,我并没有意识到社会市场的残酷,甚至天真的认为自己还是能够辛苦点的找到一份工作。可是,现实给了我当头一棒,没有技术、没有学历、疫情打击。那一段时间应该是真的认知自己的时间,家里也没什么闲钱供我去培训班,我也不知道我出去能干嘛。去看了一圈市场,与跟同学的了解,了解到了前端开发工作,所以就一股脑扎进这个行业当中。


求职之旅


跟大多数人一样,并不知道应该从何处下手,当时在我的认知当中就知道一个 JQuery,所谓的 MVVM 框架简直是一无所知。点开小破站,找到点击率最高的视频,开始自学起来。


了解到一点框架的皮毛、然后死记硬背一点基础,统统写进简历当中。


所以我的学习曲线是如图下所示


graph TD
框架 --> 框架基础 --> JS,CSS,HTML

跟大多数人一样,我是直接通过框架起手学习的前端。导致了我对于问题的处理能力几乎为零,遇到问题直接就双手离开键盘。看不懂,是真的看不懂(如果有相同感受的可以在评论抠一个 1)。


对着视频学了十天左右,写了一个 demo,屁颠颠的去求职。结果也是可想而知,人家也不是傻子一眼识破。四处碰壁,简历丢出去,根本没人看。兜兜转转持续了一个月左右,终于有一家小公司愿意给一个面试机会,马不停蹄的出发去面试,坐了一个小时左右的地铁抵达一个破旧不堪的写字楼,当时要不是看到周围还有一个高校,我还以为我去了一个搞传销的地方。。。推开一个破旧的们,一个很小的房间,两个人坐在里面给我面试。我也很直白的说自己只会一点点皮毛,他们也很直白的告诉我:我们条件有限,相当于是各取所需。其实老实说,我挺感动的,没有给我画大饼,也很直白的说我图他们要我,他们图我不要啥钱。


最终,我也算是如愿找到了这份实习工作,一个月 2000。也算是不错的结果了。


实习项目开发


去到公司以后,也马不停蹄的开始了开发工作。首先就是让我从一个简单的后台管理系统开始入手。但是问题也来了,我根本不知道什么叫管理系统,连项目搭建我都不会,然后就是两眼一抹黑。不停的去百度,查看如何搭建一个后台管理系统。


老实说,我当时连路由是什么我都不清楚,更别说加一堆乱七八糟的功能在里面了。哪个过程可想而知,多么的折磨人。经历了半个月,模板被我折腾起来了一个简单的样子,对着人家的管理系统样子进行拙劣的模仿。但是 bug 满天飞也是避免不了的问题。并且没有丝毫的设计可言,纯纯的依托答辩。


最后的最后,实在是看不下去了(包括我自己),去网上扒了一个模板开始自己去折腾。为什么一开始不考虑使用模板呢?因为我看不懂代码,下不去手。


虽然最后跌跌撞撞的项目启动起来了,但是也算是我第一次项目开发的经历吧。后续持续的添加一些功能,改动一些简单的样式,还好老板也很佛系,没有为难我,基本上没有魔改模板。所以也算是顺利的完成了后台管理系统的开发任务。


小插曲


在实习工作的期间,在技术群中认识了一个很牛的大佬。经常我在群里问一些傻逼问题(因为自己基础太差了),但是他都会很耐心的给我讲解,甚至是下班后抽出时间给我远程讲课。也算是我的半个引路人吧,让我知道了如何去玩儿前端。在这里手动抠一个感谢🙏🙏🙏。


步入正轨


在经历过第一个项目开发后,也算是知道了框架应该如何去玩儿(也就是知道了框架的 api 如何去调用)。也知道了如何去学好前端,所以慢慢的回头去了解基前端的三大基础知识 js css html


其实我相信很多人跟我一样,开始都是赶鸭子上架的形式去开发项目,遇到问题束手无策;遇到 bug 不知道如何去排查;遇到不知道如何去实现。。。最后我也总结出了问题所在,那就是基础的不扎实,学习顺序的问题,导致了这些问题。


啰嗦一句


哪怕是现在,我有时候跟网友聊天的时候也能听到一些让人不能理解的观点:前端那么简单有什么难度?前端不就是写写页面?前端。。。。


从我的观点出发而言,前端这个岗位确实是属于,宽进严出。想入行确实很容易,毕竟像我这样啥也不懂的,通过十来天的学习都能去做前端开发的事情。


但是,但是,但是,重要的话说三遍,前端的简单是因为它的入行门槛低。但是入门和会还是有本质的区别,绝大多数前端开发工作都是写 后台管理系统,这种开发,都是直接套用现成模板与组件就能够写。如果是定制化开发,脱离了后台管理系统的开发,那还是有手就行吗?


继续步入正轨


在工作的时间中,也认识了很多互联网大厂的大牛:滴滴、网易、腾讯等,经常厚着脸皮去请教他们。但是他们回应最多的是:多看基础,看书!


大佬们都这么说,那还等什么!直接开始行动。



  • 绿宝书:犀牛书

  • 红宝书:javascript高级程序设计

  • 黄宝书:你不知道的js


直接搞起来!虽然我很讨厌看书,但是看到自己实习的 2k 工资,我还不动起来,那可能真就废了。


所以每天下班后,回家翻开书籍,开始看。果不其然,一看就打瞌睡,生涩、枯燥的知识内容。没办法,继续去请教如何看书学习,得到的答案就是:好记性,不如烂笔头。


然后读书的时候,边看边写,跟做笔记一样。效果果然好多了,没那么容易打瞌睡。而且我也买了一些零食(口香糖、耐嚼的肉干之类的)边看边吃,让自己集中注意力。总之是为了能够学到真知识,想尽了各种办法。


半个月后,看了几章节基础,感觉确实潜移默化的改变了一些。写代码的时候不会那么的茫然;反复调试的次数少了一些;知道了更多好用的 api ,代码质量有一定的提高。


读书笔记分享


读书笔记


在这里分享一篇,自己从零开始写的一些笔记。不过自己已经停更很久了。


实习总结


经过两个月的实习后,时间也来到了 2020年7月,我毕业了。我也学到了很多东西,但是我觉得,这样子的工作状态并不是我喜欢的。


回学校简单收拾了一下,也决定了辞职。去找一份更加有前途的工作,当然这里肯定有很多人疑惑:你凭什么啊?确实是如此,包括我的父母,也是很疑惑并且还质疑的问道:你上几个月班,忘了自己的实际情况了?


我也开始反思,自己真的就那么的蠢、那么的不堪吗?


果断辞职


经过我的深思熟虑后,还是在毕业后辞职了。在出租屋沉淀了一个月,这一个月基本上每天只睡了五六个小时,其余时间都花在了基础的夯实上面,狠狠的补充前端基础知识。每天醒来就是:看书、写 demo、请教大佬,每天如此,孜孜不倦。


一个月后,整理自己的简历,然后又开始了自己的求职之旅。


二次求职


求职之路,也并没有自己想的那么顺利。别人也没有因为我简历写的东西多了那么一点可怜的东西而青睐你。


我也在开始反思,自己的辞职是否正确。因为我的本质问题并没有解决:没有学历、没有经验。期间也在自我怀疑、自我安慰,也在凌晨的时候,抓耳挠腮,头发也在开始一大把一大把的掉。


就这样持续了一个月左右,我终于又收到了一份面试邀请。马不停蹄的前去面试,结果却出乎我的意料,他们并没有问我八股文,反而是对我所说的经历感兴趣。我也是添油加醋的说了一顿我的实习经历、辞职后的这一个月的学习经历。


最后的最后,他们通过了我的初试。给我说需要老大亲自面试,我开始很忐忑。但是见到老大后,他是一个很和蔼的老师,并没有刁难我,也没有问我刁钻问题,只是跟我谈了一下基本情况、了解了我的基本情况,就通过了我的二次面试。


二次求职之旅结果


我很幸运,因为,让我去打工的地方是一个资源丰富的高校。我的老大也是院长,初次面试的两位也是两位老师。我也如愿以偿的又有了一份新的工作,接触到了极其丰富的资源。


老师们也很愿意教授知识,让我的技术再次的突飞猛进。


开发项目:



  • 北京冬奥会水立方保电系统

  • 基于负荷聚合的园区能量态势感知与交易系统

  • 电压暂降仿真模拟系统


薪资变化


毕业后,我的薪资也算是以每年翻倍的涨幅进步。也算是我的学习换来的回报吧。还是挺不错的~


现在


截至目前,经过三年零两个月的工作时间,也算是勉强迈入了初级前端开发的门槛吧。不断的学习中,也在积极的参与开源的贡献。



这些都是本人参与开发、贡献的项目,有兴趣可以点开看看。如果觉得有用也可以点一个小星星🌟~~~


最后


学习确实是一个枯燥的过程,也是一个很痛苦的过程。包括自己,如果不是那些大佬对我的帮助,我也不会那么快的进步。最后还是很衷心的感谢他们对我的帮助~


作者:Account_Ray
来源:juejin.cn/post/7282170455682908218
收起阅读 »

你写的 CSS 太过冗余,以至于我对它下手了!

web
:is() 你是否曾经写过下方这样冗余的CSS选择器: .active a, .active button, .active label { color: steelblue; } 其实上面这段代码可以这样写: .active :is(a, button...
继续阅读 »

:is()


你是否曾经写过下方这样冗余的CSS选择器:


.active a,
.active button,
.active label {
color: steelblue;
}

其实上面这段代码可以这样写:


.active :is(a, button, label) {
color: steelblue;
}

看~是不是简洁了很多!


是的,你可以使用 :is() 对选择器的任何部分进行分组,例如,你可以对如下代码:


.section h2,
.aside h2,
.nav h2 {
color: steelblue;
}

进行转换:


:is(.section, .aside, .nav) h2 {
color: steelblue;
}

但是 :is() 不仅对父选择器和子选择器有用,它也可以选择多个相邻的选择器,比如:


button:is(:focus, :hover, :active) {
color: steelblue;
}

button:is(.active, .pressed) {
color: lightsteelblue;
}

上述代码等价于:


button:focus, button:hover, button:active {
color: steelblue;
}

button.active, button.pressed {
color: lightsteelblue;
}

:where()


:where() 是一个与 :is() 非常相似的伪类,也值得注意。它们看起来非常相似:


:where(.section, .aside, .nav) h2 {
color: steelblue;
}

但区别在于 :where 的权重为 0,而:is() 总是会采用列表中最特高的选择器的权重。例如,你知道下面的 CSS 代码中的按钮是什么颜色吗?


:is(html) button {
color: red;
}

:where(html) button {
color: blue;
}

在上面的例子中,虽然以 :where() 开头的块在以 :is() 开头的块下面,但 :is() 块具有更高的权重


:has()


一个相关但非常不同的伪类是:has():has() 允许选择包含匹配选择器(或选择器集)的子元素的父元素


:has() 的一个示例是不显示下划线的情况下包含图像或视频的链接:


a { text-decoration: underline }

/* 链接有下划线,除非它们包含图像或视频 */
a:has(img, video) {
text-decoration: none;
}

现在,如果默认情况下我们的 a 标记有下划线文本,但其中有图像或视频,则任何匹配的锚元素的下划线将被删除。


你也可以结合 :is() 使用:



:is(a, button):has(img, video) {
text-decoration: none;
}

我们还需要预处理器吗?


现在你可能会说“SCSS可以做到这一点!,你甚至可能更喜欢它的语法:


.active {
button, label, a {
color: steelblue;
}
}

说的没错,这很优雅。但是,CSS 似乎现在已经都能获取到我们曾经需要SCSS(或其他预处理器)才能获得的特性。


CSS 变量也是 CSS 本身的另一个不可思议的补充,它回避了一个问题:就是什么时候或者多久你真的需要预处理程序:


.active :is(a, button, label) {
--color: steelblue;
color: var(--steelblue);
}

这并不是说预处理器没有它们的用例和优点。


但我认为在某个时间点上,它们确实是处理任何重要CSS的强制要求,而现在情况不再如此了。


惊喜


我想说的是,CSS的未来仍然是光明的。CSS 工作组正积极致力于直接向CSS中添加嵌套选择器。他们正在积极地在3种可能的语法之间进行选择:


/* 1 */
article {
font-family: avenir;
& aside {
font-size: 1rem;
}
}

/* 2 */
article {
font-family: avenir;
} {
aside {
font-size: 1rem;
}
}

/* 3 */
@nest article {
& {
font-family: avenir;
}
aside {
font-size: 1rem;
}
}

你最喜欢哪一个?


庆幸的是,第 1 种已经被官方采纳!所以我们可能很快就会看到一个非常像 scss 的嵌套语法。


浏览器支持


目前所有主流浏览器都支持 :is():where() 伪类:


image.png
但是,需要注意,我们在这里提到的 :has() 伪类没有相同级别的支持,所以使用 :has() 时要小心:


image.png


作者:编程轨迹
来源:juejin.cn/post/7212079828480016442
收起阅读 »

神奇的点击事件

神奇的点击事件 你知道我们在 document 中,用鼠标点击一次页面会发生什么吗? 可能你会告诉我,会触发一个或多个的 click 事件监听器,然后运行这个监听器的回调函数。 但是,这个过程中还有一些奇怪事情的一起出现了... 固定的事件触发顺序 在可注册的...
继续阅读 »

神奇的点击事件


你知道我们在 document 中,用鼠标点击一次页面会发生什么吗?


可能你会告诉我,会触发一个或多个的 click 事件监听器,然后运行这个监听器的回调函数。


但是,这个过程中还有一些奇怪事情的一起出现了...


固定的事件触发顺序


在可注册的 Document Event 中,click 事件自然是最常见的事件。
不过点击一次页面并不是只会触发 click 事件,而是会触发许多个不同但又十分接近事件。
比如也算是老朋友的 mousedownmouseup 等等。


如果说我们在同一个元素上注册多个如: mousedownpointdown 等与点击相关的事件监听器,这时候它们会按照什么顺序触发呢?
触发的时候会是固定顺序的吗?


做一个小小的实验就可以知道,它们的触发顺序是固定的,且与注册顺序无关:



后续所有的 el 与 events 皆为此项不再赘述,
且在做出不同的测试时保证其节点不会存在上一个测试所遗留的事件监听器。(懒得写注销事件代码啦~)



const el = document.getElementById('app');
// 组合事件集合
const events: (keyof HTMLElementEventMap)[] = [
'click',
'mousedown',
'mouseup',
'pointerdown',
'pointerup',
'touchstart',
'touchend',
];
// 注册组合事件
for (const event of events) {
el.addEventListener(event, e => {
console.log('触发了 ->', event);
});
}

PC 端触发顺序为


pointerdown -> mousedown -> pointerup -> mouseup -> click


PC端触发顺序.gif


移动端触发顺序为


pointerdown -> touchstart -> pointerup -> touchend -> mousedown -> mouseup -> click


移动端触发顺序.gif


如上可知,pointerdown 始终是第一触发的事件(比 touchstart 还快)!而在移动端,mousedownmouseup 都是在 touchend 之后触发的。
也就是说,当我们在触摸的时候,mousedown 无法触发,而是在手指离开屏幕的时候才会被触发!


高贵的 click 事件


那如果在始终我们在第一个触发的 pointerdown 事件中阻止了事件的默认行为,会发生什么呢?


再来一次小实验看看:


for (const event of events) {
el.addEventListener(event, e => {
if (event === 'pointerdown') {
e.preventDefault();
}
console.log('触发了 ->', event);
});
}

click事件的差异.gif


这个时候神奇的事情发生了,紧随其后的touchstartpointeruptouchend依旧触发了,
mousedownmouseup 事件被阻止了,
但是属于最后触发的 click 事件却依然成功的触发了!


这就是 click 的高贵血统吗?


阻塞与异步也无法阻止事件触发的固定顺序


我们知道,浏览器是单线程的,所以在执行一个任务的时候,其他任务都会被阻塞。


那我们触发一个调皮的事件的时候让线程阻塞了,后续事件的触发顺序发生变化吗?


for (const event of events) {
el.addEventListener(event, e => {
console.time(`执行事件 ${event} 1`);
let n = 0;
// 主线程阻塞130ms左右
for (let i = 0; i < 1000000; i++) {
// 浮点数运算
n += i ** ((Math.PI ** Math.PI) ** Math.PI);
}
console.timeEnd(`执行事件 ${event} 1`);
});
el.addEventListener(event, e => {
console.time(`执行事件 ${event} 2`);
let n = 0;
// 主线程阻塞130ms左右
for (let i = 0; i < 1000000; i++) {
// 浮点数运算
n += i ** ((Math.PI ** Math.PI) ** Math.PI);
}
console.timeEnd(`执行事件 ${event} 2`);
});
}

固定顺序.gif


可以看到,事件的触发顺序依旧是固定的,并不会因为事件的阻塞这种小事而产生动摇,
后续的事件需要等待前面的事件执行完毕之后才会被触发。


这看起来显而易见,因为事件的触发也属于主线程的任务,所以当主线程被阻塞的时候,事件的触发也会被阻塞。


那如果我们把事件的执行变成异步函数呢?还是会这么如我们所愿吗?


for (const event of events) {
el.addEventListener(event, async e => {
console.time(`执行事件 ${event}`);
let n = 0;
// 多了一个 0 所以是 1300ms~
for (let i = 0; i < 10000000; i++) {
n += i ** ((Math.PI ** Math.PI) ** Math.PI);
}
console.timeEnd(`执行事件 ${event}`);
console.log('触发了 ->', event);
await new Promise(resolve => {
setTimeout(() => {
console.log('Promise触发了 ->', event);
resolve(0);
}, 100);
});
});
}

异步事件与阻塞.gif


从 log 中可得出,
参与下一个主线程执行的 Promise 的 log 会在 click 事件之后触发直接一起触发
(也可中得出 setTimeout 的局限性),
但当前主线程应当触发的事件与其触发的顺序依旧无法被撼动。


事件执行与页面渲染不能不说的关系


我们知道,浏览器的渲染是由主线程来完成的,所以当主线程被阻塞的时候,页面的渲染也会被阻塞。
当在一条事件链中修改了多次的 DOM 时,有关页面实际的绘制也将会被推迟到最后一次修改之后。


// 加一个transition看看效果
el.style.transition = 'all 1s';
for (const event of events) {
el.addEventListener(event, e => {
console.time(`执行事件 ${event}`);
let n = 0;
// 少了一个 0 所以是 130ms 啦~
for (let i = 0; i < 1000000; i++) {
n += i ** ((Math.PI ** Math.PI) ** Math.PI);
}
const backgroundColor = el.style.backgroundColor;
// 简单的切换一下背景色
el.style.backgroundColor = backgroundColor === 'black' ? 'white' : 'black';
console.log('触发了 ->', event, el.style.backgroundColor);
console.timeEnd(`执行事件 ${event}`);
});
}

阻塞与绘制.gif


当事件触发完毕之前,el.style.backgroundColor 虽然被修改了,但页面的实际绘制被推迟,
只有其事件所有的执行结束之后,才姗姗来迟的展示出实际切换后的效果。


如果点的快就会导致各种鬼畜的现象:


闪烁的BackgroundColor.gif


渲染因为下一次事件的触发而导致被迫推迟,从而让页面看起像是闪烁了一下,
这样的用户体验是十分糟糕的。




如果说 backgroundColor 的修改只是一个属于**重绘(Repaint)小事的话,
那么如果我们在事件中修改了属于
回流(Reflow)**会咋样?


// 加一个transition看看效果
el.style.transition = 'all 1s';
// 加个bg
el.style.backgroundColor = '#eee';
for (const event of events) {
el.addEventListener(event, e => {
console.time(`执行事件 ${event}`);
let n = 0;
// 少了一个 0 所以是 130ms 啦~
for (let i = 0; i < 1000000; i++) {
n += i ** ((Math.PI ** Math.PI) ** Math.PI);
}
// 默认100vh
const height = el.style.height || el.getBoundingClientRect().height;
// 调整高度
el.style.height = parseInt(height.toString()) - 300 + 'px';
console.log('触发了 ->', event);
// style对象上的
console.log('style.height ->', el.style.height);
// 实际渲染的(DOMRect)
console.log('getBoundingClientRect().height ->', el.getBoundingClientRect().height);
console.timeEnd(`执行事件 ${event}`);
});
}

事件触发中的回流与重绘.gif


显然,当事件触发的时候,el.style.height 作为一个对象被修改了并且保存了下来,
但是 el.getBoundingClientRect().height 也就是实际渲染的结果其实并没有被修改。
所以虽然属于回流的绘制阶段,但页面同样也是需要等到事件执行完毕之后才会被重新渲染。


其中的与浏览器绘制相关的奥妙可以看看这篇文章:
浏览器的回流与重绘 (Reflow & Repaint)


总结


在这篇文章中,我们主要讲了一下浏览器的事件触发顺序,
以及主线程中事件触发的阻塞会有什么样的效果。


异步事件并不能阻止用户重复的触发事件,
所以我们应当在发起请求的时候尽量限制用户的操作,避免重复的触发事件。


想要提高用户的体验,我们在事件触发的时候,应当尽量避免阻塞主线程,
并且减少注册相同类型的事件触发器,避免重复的触发事件导致占用过多的执行时间。


作者:AntPro
来源:juejin.cn/post/7170735590991167502
收起阅读 »

愿内卷早日结束!

上个周末幸得空闲时间和爱人去图书馆学习看书,整理了一下思绪,回忆了一下这两年自己的心态变化,成长经历,学习状态,时间管理等,于是乎我发现自己变懒了,趁着今天反思一下自己,也希望能给大家有一些警示所用吧。 状态 随着年龄的增长和周遭事物的快速变化以及自己肩上的担...
继续阅读 »

上个周末幸得空闲时间和爱人去图书馆学习看书,整理了一下思绪,回忆了一下这两年自己的心态变化,成长经历,学习状态,时间管理等,于是乎我发现自己变懒了,趁着今天反思一下自己,也希望能给大家有一些警示所用吧。


状态


随着年龄的增长和周遭事物的快速变化以及自己肩上的担子越来越重,我发现自己很难再进入长时间的学习状态。这种学习状态也是我们经常说的心流,即长时间心无旁骛的专心看书,没有频繁的 CPU 线程切换,也不用保存上一秒的内存状态。


由于年龄的不断增大,我发现自己的记忆和理解能力确实在衰退,这种衰退的现象可能起源于不经常用脑导致的脑细胞组织衰减所致,脑细胞衰减就导致思考能力变弱,思考能力变弱就会导致越来越不愿意动脑,这是一种负面循环,很可能会使老年痴呆提前到来。人最重要的是大脑,而我们对大脑的开发和利用却少的可怜。


不知道大家有没有经历过这样一种情况,每天都很多人找你,你看似很匆忙,但是晚上回想一下自己一天的经过却发现做的事情大多数重复性且可替代性很强的工作,而当你一天很快进入工作状态却没人打断你,你勤加动脑你会发现自己能解决很多难题,会有很多创造性的 idea 出现,会觉得自己非常有成就感,这就是一种心流状态。


上面是两种不同情况之间的差距,真实情况其实是第一种:每天有无数个毫无意义的会议和很多人来找你,你自己很难进入心流状态。每天担心自己的绩效在这个季度会不会垫底,会不会存在被优化的风险,导致自己一天天的忧心忡忡,拒绝思考,喜欢做简单且可替代性强的工作来争取绩效,从而产生工作中的内卷 ...... 陷入负面循环。


还有就是手机对我们的控制和吃瓜心态的优先级正在变的越来越高,不摸鱼也不会吃瓜,不吃瓜也不会摸鱼,这也是一种循环,你想摸鱼你才会吃瓜,你吃瓜的时候你肯定正在摸鱼,这就是一种热点效应,中国老百姓就是喜欢看热闹,无非就是形式变了,把现实中聚在一起看热闹搬到了网上变成大家围观吃瓜。所以为啥每次微博只要一崩肯定就是 xx 明星又出轨了这种生活作风性质的烂批事儿,你除了向他键盘伤害之外,还能跟广大的网友有啥关系?你爱看无非就是人性罢了,而进入心流状态是一种逆人性的事情,但很可怕的是这种逆人性的事情在变得越来越少。


编码是需要创造和思考的,而程序员的美好愿景不就是 debug the world 吗?我们不能陷入毫无意义只想划水吃瓜的猎奇怪圈中,我们需要及时跳出来,也许终究一生我们都只是柴米油盐酱醋茶的普通人,但是我们写过的代码,带过的人,都会是这个行业中浓墨重彩的一比,就像 GitHub 尘封在北极中的代码是一样的。



在平时的工作和生活中,要让自己多多进入心流,减少外界事物对自己的干扰,进入心流状态,让自己静下心来,思考问题的深度就会加深,会让自己沉浸在一种状态下,一种持续精进的状态中。



怪圈


最近经常听到一些读者不经意间的讨论,cxuan 最近更文的频率慢了很多,我承认确实是这样的。那么为什么会这样呢?其实这些话我很早讲,但是奈何没有找到一个合适的时机,所以趁着今天,我也想说明一下。


其实我陷入了一种怪圈,一种我想写出更优秀的作品,但是写出这种作品需要以大量的基础知识作为铺垫,所以我就想要吸收更多的知识,看更多的书,读更多的文章,汲取大量的营养,但是谁也没法保证在吸收这么多知识后就一定能写出优质的文章,有可能我写的越来越屎。但是有一点确定的是,在吸收大量知识的同时,我是无法持续更文的,但是不写文章就会变的焦虑,导致越来越没信心吸收更多的知识。最终导致文章也断更了,知识也没学到多少。


就比如我是 Java 程序员,也许我写关于 Java 类型的文章会好很多,但是我偏偏想要写操作系统、C/C++ 、计算机网络、汇编等方面的文章,这就需要吸收大量的只是并揉碎了给大家讲出来,不过这需要大量的时间和精力。也许单纯的写 Java 方向的文章会好很多,但是谁叫我愿意呢?理论上我必须承受这些知识带给我的压力,我必须要挤出来更多的时间来接纳,但是实际情况是我躺平了。


躺平的原因有很多种,但是我只接受一种:我变懒了。


我一直以为工作不忙就会有更多的时间做自己的事情,但实际情况是工作不忙就会有更多的时间划水摸鱼,到点打卡下班。再加上结婚之后暂无要孩子的打算,于是自己心态变了。这是很可怕的一件事情,屠龙少年,终成恶龙。


再加上我现在又有健身的兴趣,但是我为满足我的兴趣和工作以及学习的总时间并没有变多,所以我的兴趣就会挤占其他项目的时间,导致我最近的时间管理这块变得很糟糕。


希望我自己能及时调整心态,合理平衡兴趣、工作和学习的时间,慢慢找回之前的状态。人若无名便可专心练剑,时刻让自己保持一种空杯心态。


寒潮


今年对互联网行业最大的一个冲击就是裁员潮和 HC 的锁紧,随着互联网脚步放缓,很多毕业生毕业找不到工作,很多培训班也爆雷。但是由于计算机这几年鼓吹的太狠,所以毕业季有很多毕业生同时抢一个 offer,因此越来越内卷,所以现在的互联网环境可以说是半死不活了。但是这种环境下,对真正优秀的毕业生来说还是影响不太大,还是有一些岗位在招人,不过对于大多数同学来讲,能上岸就抓紧上岸,先活着再生活。考研的人数也是一年比一年多,现在大学生都不好意思跟人说自己是大学生了,因为遍地都是研究生,甚至博士都已经见怪不怪了。


就拿石家庄某个高效来说,二本学校招聘教师 120 个岗位无一例外全是博士学历起,令人卷到只想骂人。


我还依稀记得一年前互联网在高位的时候,应届毕业生年薪 package 50w 已经不算什么大新闻了,再看看与现在的对比,令人唏嘘,无非是在风口浪尖罢了,并不是真正的能力。


那么如何破局呢?其实谁都无法给出准确的答案,我们能做的只是丈量好脚下的步数,不过还是有一些建议可以采取的。


精进基础知识


再过多强调基础知识都不为过,但很多人依然认识不到其重要性,很多同学都以为开发就是会写代码就完事儿了,玩玩框架做做增删改查就是全部工作内容,只不过现实是不给你转身的机会的,你看看现在的就业环境是只会增删改查就能找到一份称心如意的工作吗?就拿面试来说,两年前面试还是卷一些框架用法、了解基本原理即可,再看看这两年面试直接卷深层次的内容和应用实现,底层原理等。


基础知识是一通百通的,比如你了解计算机网络的分层设计之后就知道网络协议的每一层打包的目的是什么,Socket 为什么是端口通信的大门?ping 的实现原理,为什么要发 ECHO_REQUEST 包?为什么有的电脑插入网线不用配置 IP 就能直接上网?点击 http://www.google.com 背后的实现过程是怎样的?操作系统为什么要分为三大抽象?


再好比你在搞并发编程,你就得知道为什么读写要遵循 MESI 协议?Java 实现的各种并发工具类其实都是操作系统读写的几种模型罢了,以及线程和进程的本质区别是啥,管程是啥?等等,要学的内容太多了。可以说这个行业你不得不随时都准备学习,可以说是把终身学习理念贯彻最彻底的行业。


掌握核心技术


今年很多大厂对内都进行了人员优化,比如前段时间虾皮裁员毁约闹的挺大的,只不过裁掉和优化的都是边缘部门和边缘开发同学,也就是不赚钱没有盈利前景的那些部门。核心岗位的核心开发还是公司的支柱,所以这里建议大家还是要向公司的核心业务、核心部门靠拢,这才是一家互联网公司的全部核心。也就是说要让自己具有核心竞争力和不可替代性,也要有随时离开的本领。


一言以蔽之,多看书,多实践,向公司核心技术和核心业务靠拢,覆巢之下无完卵,大家加油。



作者:程序员cxuan
来源:juejin.cn/post/7161241480663662606
收起阅读 »

程序员增强自控力的方法

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法: 1. 设定明确的目标和计划 制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任...
继续阅读 »

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法:


1. 设定明确的目标和计划


制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任务清单、时间追踪工具等,来帮助我们控制时间并更有效地完成任务。


2. 掌控情绪


作为程序员,我们需要面对很多挑战和压力,容易受到情绪的影响。因此,掌握情绪是一个非常重要的技能。可以通过冥想、呼吸练习、运动等方法,来帮助我们保持冷静、积极和乐观的心态。


3. 管理焦虑和压力


焦虑和压力是我们常常遇到的问题之一,所以我们需要学会如何管理它们。我们可以使用放松技巧、适度锻炼、交流沟通等方法,来减轻我们的焦虑和压力。


4. 培养自律习惯


自律是一个非常重要的品质。我们可以通过设定目标、建立规律和强化自我控制等方式,来培养自律习惯。


5. 自我反思和反馈


经常进行自我反思和反馈可以帮助我们更好地了解自己的优缺点和行为模式。我们可以使用反馈工具或与他人交流,来帮助我们成长和改进。


6. 持续学习和自我发展


程序员需要不断学习和自我发展,以保持竞争力和提升自己的技能。通过阅读书籍、参加培训、探究新技术等方式,可以帮助我们持续成长,增强自我控制力。


结论


自控力是我们工作和生活中重要的的品质之一,可以帮助我们更好地应对各种挑战和压力。通过设定目标、掌控情绪、管理焦虑和压力、培养自律习惯、自我反思和反馈、持续学习和自我发展等方法,我们可以帮助自己增强自我控制能力并提高工作效率。


作者:郝学胜
来源:juejin.cn/post/7241015051661312061
收起阅读 »

修电脑屏幕记

21年的时候媳妇买了台联想小新16Pro,最近发现屏幕闪,查了查售后政策,好在屏幕质保两年。 找维修点 从高德地图里搜联想售后很正常吧!看看图片带着Lenovo的图片,是不是感觉是官方售后?拨打电话打到的是总部,贴心的给你预约好到店时间,是不是感觉服务也是不错...
继续阅读 »

21年的时候媳妇买了台联想小新16Pro,最近发现屏幕闪,查了查售后政策,好在屏幕质保两年。


找维修点


从高德地图里搜联想售后很正常吧!看看图片带着Lenovo的图片,是不是感觉是官方售后?拨打电话打到的是总部,贴心的给你预约好到店时间,是不是感觉服务也是不错的?


image-20230607160956374.png


到店


这个店的位置很奇怪,感觉是在一个办公楼里,上电梯需要刷卡?当时就有点懵,不是开门做生意吗,为什么给客户增加成本。


好不容易到店,发现店里的样子和图片上完全不一样,最重要的是没有联想logo。而且我看到他们竟然还在修其它品牌的机器。其实心里已经有点怀疑他们是不是官方维修点了。


但是他们能查到这台电脑的相关信息,我想可能是合作方吧。抱着来都来了,能修就行的心理,让师傅检查机器。


检查


后续骚操作就来了:




  1. 沟通困难:问我们屏幕有问题的时候是什么样子的。给解释有的时候花屏、有的时候闪屏。但师傅总是盯着当前的闪屏在说,完全不提花屏的事情。




  2. 售后政策不熟悉:上来就说屏幕没法售后,扯什么主板啊、整机啊、保一年啊什么的。跟他说了政策是保两年,然后才再次查了一下售后政策,发现真是两年。




  3. 闪屏原因:看了一眼屏幕,说还是不能走质保,因为屏幕损坏是我们挤压造成的,屏幕已经翘起来了。本来听他说挤压,我还在想我那傻媳妇是不是把电脑撞到了,但他说屏幕翘起来了,我好好看了一下,完全没有挤压和翘起痕迹,让他给指一下,也没指明白,就扯屏幕不会自己坏的,肯定是我们挤压的。




问不走质保,多少钱?答:一千多。


问多多少?答:1700


让给出个鉴定报告,人就不见了。


找官方售后


我觉得这家店有问题,找到联想官方售后电话,这家店根本没在他们系统里。


让我们去联想官方店铺。


到了联想官方店,人家检测之后,直接走质保就给免费换了。


思考


后面我想了一下




  1. 对于这种图片和实际情况不符的,直接远离。这种不符已经能说明很多事情了。无论对店铺还是对人!




  2. 我本来以为随着网络的发展,依靠信息差赚钱的情况很少了,结果自己差点着了道。






  • 人家利用大家对官方售后的不明确进行引流




  • 到店后利用大部分人对机器不了解,随意进行判断、维修




这个钱赚的挺昧良心的。对我们的父母辈而言,他们可能真的就听售后人员的安排了。



  1. 信任很重要




  • 信任真的值钱。每个人都想和值得信任的人做买卖。官方店就是靠谱!




  • 信任能提升效率:去官方对大部分小白而言,是效率极高的,不用做一堆防坑攻略





  1. 勇敢:如果大家觉得事情不对,就勇敢、理智的说,谎言是可以被戳破的


希望大家都能躲过这些坑!!!


作者:程序员麻辣烫
来源:juejin.cn/post/7241838768016490556
收起阅读 »

你没有必要完全辞去工作

我认为我们可以而且应该探索生活中的许多事情,我写这篇文章是为了展示成为一名创客和拥有一份全职工作不仅是可能的,而且使得你可用的机会多样化,这可以让你更加敏锐,务实与坚定。 在这篇文章中,我想解决三个关键概念。 首先是针对那些认为自己时间不够的人,以及为什么我觉...
继续阅读 »

我认为我们可以而且应该探索生活中的许多事情,我写这篇文章是为了展示成为一名创客和拥有一份全职工作不仅是可能的,而且使得你可用的机会多样化,这可以让你更加敏锐,务实与坚定。


在这篇文章中,我想解决三个关键概念。


首先是针对那些认为自己时间不够的人,以及为什么我觉得这种观念往往是错误的。


第二是强调坚持工作的好处,以及为什么成功人士最擅长的是降低风险,而不是最大化。


最后,第三部分将指出出一些我认为我们都可以在思维方式上做出的改进--超越单纯地创业和全职工作,这些概念希望能帮助你在一个更大的空间里进行优化,或者也许完全去除这个限制。


1.“我没有足够的时间”


美国人平均工作时长为 8.8 小时,这源于工业革命,并一直延续到 21 世纪,这处于一般的考量,而不是深思熟的考虑。罗伯特·欧文 (Robert Owen) 精心设计了“八小时劳动、八小时娱乐、八小时休息”的说法,努力让人们在合理的时间内工作,同时仍然能有效地运营工厂。


尽管世界和劳动力发生了翻天覆地的变化,但这种“工作时间”和“我的时间”的概念至今仍然存在。我在这里不是要质疑 40 小时模型(已经有太多资源了 - 谁还没有听说过 4 小时工作周?),而是质疑对“我的时间”的看法。


对许多人来说,长时间的工作意味着他们有权享受这种 "我的时间",并将 "我的时间 "设计得与 "工作时间 "尽可能地不同。对许多人来说,它看起来很像这样。Netflix and chill


但是,如果我们不再将“我的时间”想象成放松时间,而是完全按照它的标题:是时候专注于自己并与您的目标保持一致,那会怎样呢?如果你需要休息,那就休息吧。但如果你的目标是有朝一日成为一名企业家,那么应该投入大量的“我的时间”来实现这一目标,因为它不会自行发生。 “我的时间”不应该只是不累人的活动,而是任何可以帮助个人达到他们希望进入的未来状态的活动。


每天大约有 16 个小时分配给工作和睡眠,每个人大约有 8 个小时可以分配给“我的时间”,如果使用得当,每年将近 3000 个小时可以取得很多成就。


Sleep, commute, work, repeat. 睡觉、通勤、工作,如此重复。


Sleep, commute, work, repeat. 睡觉、通勤、工作,如此重复。



“Most people overestimate what they can do in a day, but underestimate what they can do in a year.”

“大多数人高估了他们一天能做的事情,却低估了他们一年能做的事情。”



还有一种误解是,为了建立一个可持续的业务,你需要花费大量的时间才能达到目的。虽然确实需要付出大量的努力,但最重要的是长期持续的努力。大多数人低估了复利的这个概念。


compound-interest.jpg


The power of compound interest. 复利的力量。


看看下面的等式:



  • 1.01³⁶⁵ = 37.8

  • 1.10³⁰ = 17.5


在一年内每天坚持改善你的业务(或生活)1%,比一个月内每天改善 10% 的效果要好一倍。坚持不懈加上复利的力量是强大的。



“如果一切都是最重要的,那么就没有什么是重要的。”



我认为,大多数人在生活中要么没有清楚地确定优先事项,要么将所有事情都考虑在内。虽然我相信雄心壮志,但成功的一个关键步骤是确定核心的优先事项,并消除在此之外的噪音。


最主要的优先事项是动态的,可以随着时间的推移而改变,但我认为,在一个特定的时间,你真的不能有超过 3 个核心重点。


设定这些重点之后,就是要改变行为,按照这些重点生活。再次,如果大多数人要客观地反思他们是如何花费时间的,他们会得到这样的结果。


1_nALgHXJAKmPcyqO10XvvCw.jpg


典型的一天。


对我来说,这就是我的个人优先事项随着时间的推移而发生的变化:



  • 2017 年:工作、旅行、人际关系

  • 2018 年:工作、学习编码、构建副业

  • 2019 年:工作、扩展副业、分享想法(写作、演讲)


为了在创作项目的同时维持一份全职工作,我不得不排除干扰。例如,我不看电视。我不通勤。我目前没有恋爱。这些都是主动的选择。


当然,其中一些东西将是暂时的(例如:人际关系),但我也注意到我在生活中重新引入的东西,以及它是否会促进、带走,或成为我的北极星之一。


我认为这个概念也可以被认为是分层的时间投资。对于你所做的任何事情,如果它有助于你的北极星,就把它看作是一级投资。对于那些对你的成长完全没有贡献的事情,也许可以把它标记为第四级。这并不意味着你不能跨层花费时间,但你花在每个层级上的时间应该反映出你对它们的关心程度。


示例(这对任何人来说都是一个独立的练习):



2. 坚持工作的好处


希望上一节有助于说服你,你有足够的时间全职工作,同时创建副业,或者说,如果你调整你的价值观→优先事项→行为,就可以在你的生活中融入更多的东西。在这一节中,我希望能表达为什么保持全职工作可以是一件美好的事情。


付费学习



“Some workplaces are definitely broken, but the entire workforce isn’t.”



我经常听到有人说 "我等不及要出去 "这样的话,指的是辞掉工作,最终自己当老板。在辞职之前,要考虑为什么要辞掉工作。通常情况下,这不是他们有全职工作的问题,而是他们所从事的特定工作,或许是他们所汇报的特定人员。


所有的人都应该努力找到一份能够赋予他们权力、激励他们并让他们在某些方面得到成长的工作。大公司实际上保证了这一点--你很少是公司里最聪明的人,你当然也不会是公司里每个方面都最有能力的人


在我的 "日常工作 "中,我可以不断向比我更聪明的人学习,并为此获得报酬。我还面临着我的副业项目根本不会遇到的挑战,我经常需要学习如何与他人一起解决这些挑战。我鼓励人们有意识地设计他们的职业道路,以掌握从硬到软的新技能。如果你最终决定在未来自立门户,那么这两者都会很重要。


随着劳动力变得更加活跃,在协同处理自己的项目的同时向他人学习的能力是许多人正在开发的。事实上,我在 Twitter 上对数百人进行了调查,发现相当多的人都在这样做。


保持新鲜的想法和清晰的头脑


在学习之外,保持一份 FT 工作还有其他实实在在的好处,可以帮助你建立一个更可持续的副业。


根据个人经验,我发现把我的工作和副业分开,使我仍能在两者中找到独立的乐趣。每当我从一个环境切换到另一个环境,特别是在创业方面,它仍然是 "有趣的"。


我认为这特别是因为在目前的状态下,创业不是我的生命线。我希望有一天它确实成为更有意义的东西,但就目前而言,我可以在不受立即赚钱需求影响的情况下就我的项目做出决定。


更重要的是,我可以专注于通过我真正关心的项目来表达自己,而不是专注于可能产生美元的东西,通过这个过程,我贴近我的价值观。换句话说,我可以专注于创造价值,而不是专门去获取价值,类似于 Gumroad 的创始人 Sahil Lavingia 如何转向做这件事,或者 Warby Parker 的创始人如何确保金钱不会战胜他们的价值观。



“开始之前我们是四个朋友,我们承诺公平对待彼此比成功更重要。” —— 亚当·格兰特



结合以上几点,当我意识到一个项目没有任何价值时,我可以放弃一个项目或理性思考,我也不需要拿 VC 的钱或倾向于我不相信的投资者。



“在一个领域拥有安全感让我们可以自由地在另一个领域独创。通过在财务上覆盖我们的基地,我们摆脱了出版半生不熟的书籍、出售劣质艺术品或开展未经考验的业务的压力。” —— 亚当·格兰特



最后,我可以在技能学习方面投入适当的时间。我把这比喻为这样一个概念:上市公司不太关注通过创新创造长期价值,而是关注下一个季度的收入数字。我是一只私人股票,可以专注于我自己和我的技能,目的是为了长期建设它们。


换句话说,我的表达和创意之间的明确区分与我的生命线分离,我认为这有助于做出更有效的决定。


进行大量试验,然后全力以赴



“企业家这个词,正如经济学家理查德·坎蒂隆创造的那样,字面意思是“风险承担者”。 —— 亚当·格兰特



有一个普遍的误解,认为企业家都是“冒险者”,你需要“全力以赴”才能成功。在亚当·格兰特的著作 Originals (中文名:《离经叛道:不按常理出牌的人如何改变世界》)中,这两者都被证明是错误的;企业家不一定是冒险者,而是更善于评估风险和对冲他们的赌注。



“当 Pierre Omidyar 创立 eBay 时,这只是一种爱好;在接下来的九个月里,他一直以程序员的身份工作,直到他的在线市场为他赚的钱比他的工作还多才离开。最好的企业家不是风险最大化者。 “他们在冒险中承担了风险。” —— 亚当·格兰特



Grant 还引用了 Joseph Raffiee 和 Jie Feng 的另一项研究,该研究从 1994 年到 2008 年对 5000 多名美国人提出了以下问题:“当人们开始创业时,他们最好是继续工作还是辞掉日常工作?”


结果呢?他们发现,那些离开工作岗位的人这样做不是出于经济需要,而是出于纯粹的自信。然而,那些更不确定的人比更喜欢冒险的人失败几率要低 33%。


另一项研究表明,那些在 Fast Company 最具创新力排行榜上名列前茅的企业家也倾向于坚持他们的日常工作,包括著名企业家 Phil Knight(耐克)、Steve Wozniak(苹果)以及谷歌创始人 Larry Page 和 Sergey Brin。


奈特当了 5 年的会计师,同时从他的后备箱里卖鞋,沃兹尼亚克继续在惠普工作,谷歌人继续在斯坦福大学攻读博士学位。这些只是书中的一些原作——Grant 还引用了类似的故事,包括 Brian May 在加入 Queen 之前研究天体物理学,John Legend 即使在发行他的第一张专辑后仍然担任管理顾问,Spanx 创始人 Sara Blakely 销售传真机,她的公司原型和规模最终成为世界上最年轻的白手起家的亿万富翁,著名作家斯蒂芬金在他的第一个故事发表后担任了 7 年的看门人、教师和加油站服务员。


我们都有多种激情,我认为生活就是在有意义的时候进行战略转型。无需立即从一个场景切换到另一个场景。人们可能认为冒险者很酷,但在另一边取得成功更酷。


3. 重构你的思维方式


无论你是否选择全职工作,同时探索副业项目,我认为我们都可以更有效地打开我们的思想,接受不同的思维方式。本节将涉及一些我认为我们可以停止限制自己和他人的方式。


世间安得两全法


人们喜欢把东西装进盒子里。你会听到人们总是使用名词或形容词作为明确的标签:



  • 技术还是非技术

  • 快乐或悲伤

  • 职员或企业家


看到我要去哪里了吗?尽管有这些标签,但我相信几乎所有东西都可以用某种曲线表示;特别是在技能习得方面。例如,你什么时候真正“成为”程序员?


1_MEZ99GbXHnSq27aDQaBxcQ.jpeg


真正的创造性思维者不再用二元思维,而是能够将这些曲线的概念内化。他们把事情看成一个斜坡、楼梯或维恩图,而不是一系列的盒子。当你消除二元对立时,你就能更清楚地看到其他选择,比如慢慢增加你对副业的时间投入,而不是立即辞职。


合理规划您的生活


我认为,如果有人认为自己的工作效率已经达到全球最高水平,那是非常天真的。事实是,我们都有改进的余地,不仅是在更快/更干净方面,而且是在做出更好地决定,删除那些首先不应该出现在我们盘子里的工作。


如果您选择从事多项工作,请确保您对所有这些都有独立的 KPI。人们倾向于在企业中这样做,但这个概念在我们的个人生活中却很少见。你能量化过去一年你在自己身上投入了多少时间吗?大多数人做不到。


如果两者都没有 KPI,那么没有明确 KPI 的那个自然会被搁置一旁,或者得不到应有的关注。


我还认为理解“元工作”的概念很重要。我对元工作的定义如下:“如果你连续一年做那个活动,你的生活会有什么不同吗?”


让我详细说明。


如果明年我每天都回复电子邮件,我的生活会不会发生重大变化?换句话说,我会从 A 搬到 B 吗?答案是不。


洗衣服、买杂货或做指甲等事情也是如此。哦,是的,Netflix 也一样。


还有第二种类型的任务,我将其标记为绝对任务。如果始终如一地完成,您可能会看到您的技能或生活发生重大变化。例如:如果你每天阅读一年,你的知识储备、创造力和阅读速度都可能会提高。如果你每天锻炼,你的健康无疑会有所改善。同样,如果您每天花 1 小时学习编码,到年底您将拥有全新的技能组合。


虽然元任务在生活中是不可避免的,但要确保你的生活目标不是元的,它们需要是绝对的。当你创建当天的待办事项清单时,确保至少有一件事是绝对的(记住:1.01³⁶⁵=37.8)。当然,当你可以时:尽可能多地将元任务自动化。元任务在很多方面都可以成为分心的代名词,除非它们给你的生活带来某种独立的快乐。


一夜成名的神话


最后,我想澄清最后一个误解:没有一夜成名这回事。这种误解源于媒体的运作方式。


TechCrunch 永远不会写 X 人如何用 Y 年时间引导一个可持续的非独角兽企业,遵守其价值观并尊重人们的隐私。离群索居者很耀眼,但他们仍然是离群索居者。


直到几年前,我才真正理解持续攀登的概念。我以为每一个成功的人都说要付出很多工作和努力,这只是在为他们的运气自我辩护。



"当我们惊叹于那些为创造力提供动力并推动世界变革的原创者时,我们往往会认为他们是由不同的布料剪成的。" -- 亚当-格兰特



现实情况是,构建任何有价值的东西都需要时间。当然,全职工作可能需要更长的时间来构建,但这没关系。


如果你目前有全职工作,不要把自己放在一个框框里,而是要开始为你觉得有趣的想法工作。完美的想法永远不会出现,,所以我鼓励每个人开始每周花 1 小时来研究他们认为有吸引力的想法,并逐渐增加,直到你处于一个可以让他们全职工作的地方。将你的生命线(你的工作)与你的项目分开,这种精神上的清醒可能是最健康和最周到的做法。


记住,你没有成为企业家的时刻,所以没有必要为了将自己定义为企业家而辞掉工作。



原文链接:You Don't Need to Quit Your Job to Make


作者:Steph | Smith 斯蒂芬 |史密斯



作者:宇宙之一粟
来源:juejin.cn/post/7208750099836289079
收起阅读 »

Token无感知刷新,说说我对解决方案的理解~

web
前言 大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心。 大家设想一下,如果有一个超级大的表单页面,用户好不容易填完了,然后点击提交,这个时候请求接口居然返回401,然后跳转到登录页。。。那用户心里肯定是一万个草泥马...
继续阅读 »

前言


大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心。



大家设想一下,如果有一个超级大的表单页面,用户好不容易填完了,然后点击提交,这个时候请求接口居然返回401,然后跳转到登录页。。。那用户心里肯定是一万个草泥马~~~


所以项目里实现token无感知刷新是很有必要的~



这几天在项目中实践了一套token无感知刷新的方案,其实也有看了一下网上那些解决方案,也知道这类的方案已经烂大街了,但是感觉不太符合我想要的效果,主要体现在以下几个方面:



  • 逻辑都写拦截器里,耦合性高,不太好

  • 接口重试的机制做的不太好

  • 接口并发时的逻辑处理做的不太好


我为什么不想要让这套逻辑耦合在拦截器里呢?一方面是因为,我想要写一套代码,在很多的项目里面可以用,把代码侵入性降到最低


另一方面,因为我觉得token无感知刷新涉及到了接口重发,我理解是接口维度的,不应该把这套逻辑放在响应拦截器里去做。。我理解重发之后就是一个独立的新接口请求了,不想让两个独立的接口请求相互有交集~


所以我还是决定自己写一套方案,并分享给大家,希望大家可以提提意见啥的,共同进步~



温馨提示:需要有一些Promise基础



思路


其实大体思路是一样的,只不过实现可能有差别~就是需要有两个 token



  • accessToken:普通 token,时效短

  • refreshToken:刷新 token,时效长


accessToken用来充当接口请求的令牌,当accessToken过期时效的时候,会使用refreshToken去请求后端,重新获取一个有效的accessToken,然后让接口重新发起请求,从而达到用户无感知 token 刷新的效果


具体分为几步:



  • 1、登录时,拿到accessTokenrefreshToken,并存起来

  • 2、请求接口时,带着accessToken去请求

  • 3、如果accessToken过期失效了,后端会返回401

  • 4、401时,前端会使用refreshToken去请求后端再给一个有效的accessToken

  • 5、重新拿到有效的accessToken后,将刚刚的请求重新发起

  • 6、重复1/2/3/4/5



有人会问:那如果refreshToken也过期了呢?


好问题,如果refreshToken也过期了,那就真的过期了,就只能乖乖跳转到登录页了~


Nodejs 模拟 token


为了方便给大家演示,我用 express 模拟了后端的 token 缓存与获取,代码如下图(文末有完整代码)由于这里只是演示作用,所以我设置了



  • accessToken:10秒失效

  • refreshToken:30秒失效



前端模拟请求


先创建一个constants.ts来储存一些常量(文末有完整源码)



接着我们需要对axios进行简单封装,并且模拟:



  • 模拟登录之后获取双 token 并存储

  • 模拟10s后accessToken失效了

  • 模拟30s后refreshToken失效了



理想状态下,用户无感知的话,那么控制台应该会打印


test-1
test-2
test-3
test-4

打印test-1、test-2比较好理解


打印test-3、test-4是因为虽然accessToken失效了,但我用refreshToken去重新获取有效的accessToken,然后重新发起3、4的请求,所以会照常打印test-3、test-4


不会打印test-5、test-6是因为此时refreshToken已经过期了,所以这个时候双token都过期了,任何请求都不会成功了~


但是我们看到现状是,只打印了test-1、test-2




不急,我们接下来就实现token无感知刷新这个功能~


实现


我的期望是封装一个class,这个类提供了以下几个功能:



  • 1、能带着refreshToken去获取新accessToken

  • 2、不跟axios拦截器耦合

  • 3、当获取到新accessToken时,可以重新发起刚刚失败了的请求,无缝衔接,达到无感知的效果

  • 4、当有多个请求并发时,要做好拦截,不要让多次去获取accessToken


针对这几点我做了以下这些事情:



  • 1、类提供一个方法,可以发起请求,带着refreshToken去获取新accessToken

  • 2、提供一个wrapper高阶函数,对每一个请求进行额外处理

  • 3/4、维护一个promise,这个promise只有在请求到新accessToken时才会fulfilled


并且这个类还需要支持配置化,能传入以下参数:



  • baseUrl:基础url

  • url:请求新accessToken的url

  • getRefreshToken:获取refreshToken的函数

  • unauthorizedCode:无权限的状态码,默认 401

  • onSuccess:获取新accessToken成功后的回调

  • onError:获取新accessToken失败后的回调


以下是代码(文末有完整源码)



使用示例如下



最后实现了最终效果,打印出了这四个文本




完整代码


constants.ts


// constants.ts

// localStorage 存储的 key
export const LOCAL_ACCESS_KEY = 'access_token';
export const LOCAL_REFRESH_KEY = 'refresh_token';

// 请求的baseUrl
export const BASE_URL = 'http://localhost:8888';
// 路径
export const LOGIN_URL = '/login';
export const TEST_URL = '/test';
export const FETCH_TOKEN_URL = '/token';


retry.ts


// retry.ts

import { Axios } from 'axios';

export class AxiosRetry {
// 维护一个promise
private fetchNewTokenPromise: Promise<any> | null = null;

// 一些必须的配置
private baseUrl: string;
private url: string;
private getRefreshToken: () => string | null;
private unauthorizedCode: string | number;
private onSuccess: (res: any) => any;
private onError: () => any;

constructor({
baseUrl,
url,
getRefreshToken,
unauthorizedCode = 401,
onSuccess,
onError,
}: {
baseUrl: string;
url: string;
getRefreshToken: () => string | null;
unauthorizedCode?: number | string;
onSuccess: (res: any) => any;
onError: () => any;
}
) {
this.baseUrl = baseUrl;
this.url = url;
this.getRefreshToken = getRefreshToken;
this.unauthorizedCode = unauthorizedCode;
this.onSuccess = onSuccess;
this.onError = onError;
}

requestWrapper<T>(request: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
// 先把请求函数保存下来
const requestFn = request;
return request()
.then(resolve)
.catch(err => {
if (err?.status === this.unauthorizedCode && !(err?.config?.url === this.url)) {
if (!this.fetchNewTokenPromise) {
this.fetchNewTokenPromise = this.fetchNewToken();
}
this.fetchNewTokenPromise
.then(() => {
// 获取token成功后,重新执行请求
requestFn().then(resolve).catch(reject);
})
.finally(() => {
// 置空
this.fetchNewTokenPromise = null;
});
} else {
reject(err);
}
});
});
}

// 获取token的函数
fetchNewToken() {
return new Axios({
baseURL: this.baseUrl,
})
.get(this.url, {
headers: {
Authorization: this.getRefreshToken(),
},
})
.then(this.onSuccess)
.catch(() => {
this.onError();
return Promise.reject();
});
}
}


index.ts


import { Axios } from 'axios';
import {
LOCAL_ACCESS_KEY,
LOCAL_REFRESH_KEY,
BASE_URL,
LOGIN_URL,
TEST_URL,
FETCH_TOKEN_URL,
} from './constants';
import { AxiosRetry } from './retry';

const axios = new Axios({
baseURL: 'http://localhost:8888',
});

axios.interceptors.request.use(config => {
const url = config.url;
if (url !== 'login') {
config.headers.Authorization = localStorage.getItem(LOCAL_ACCESS_KEY);
}
return config;
});

axios.interceptors.response.use(res => {
if (res.status !== 200) {
return Promise.reject(res);
}
return JSON.parse(res.data);
});

const axiosRetry = new AxiosRetry({
baseUrl: BASE_URL,
url: FETCH_TOKEN_URL,
unauthorizedCode: 401,
getRefreshToken: () => localStorage.getItem(LOCAL_REFRESH_KEY),
onSuccess: res => {
const accessToken = JSON.parse(res.data).accessToken;
localStorage.setItem(LOCAL_ACCESS_KEY, accessToken);
},
onError: () => {
console.log('refreshToken 过期了,乖乖去登录页');
},
});

const get = (url, options?) => {
return axiosRetry.requestWrapper(() => axios.get(url, options));
};

const post = (url, options?) => {
return axiosRetry.requestWrapper(() => axios.post(url, options));
};

const login = (): any => {
return post(LOGIN_URL);
};
const test = (): any => {
return get(TEST_URL);
};

// 模拟页面函数
const doing = async () => {
// 模拟登录
const loginRes = await login();
localStorage.setItem(LOCAL_ACCESS_KEY, loginRes.accessToken);
localStorage.setItem(LOCAL_REFRESH_KEY, loginRes.refreshToken);

// 模拟10s内请求
test().then(res => console.log(`${res.name}-1`));
test().then(res => console.log(`${res.name}-2`));

// 模拟10s后请求,accessToken失效
setTimeout(() => {
test().then(res => console.log(`${res.name}-3`));
test().then(res => console.log(`${res.name}-4`));
}, 10000);

// 模拟30s后请求,refreshToken失效
setTimeout(() => {
test().then(res => console.log(`${res.name}-5`));
test().then(res => console.log(`${res.name}-6`));
}, 30000);
};

// 执行函数
doing();


结语 & 加学习群 & 摸鱼群


我是林三心



  • 一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手;

  • 一个偏前端的全干工程师;

  • 一个不正经的掘金作者;

  • 一个逗比的B站up主;

  • 一个不帅的小红书博主;

  • 一个喜欢打铁的篮球菜鸟;

  • 一个喜欢历史的乏味少年;

  • 一个喜欢rap的五音不全弱鸡


如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点


image.png


作者:Sunshine_Lin

链接:juejin.cn/post/728169…

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


作者:Sunshine_Lin
来源:juejin.cn/post/7289741611809587263
收起阅读 »

认识车载神器-Android Auto

什么是Android Auto 首先,Android Auto 不是 OS。它是集成在 Android OS 里的 feature。当通过 USB、Wi-Fi 将 Android Phone 连接到支持 Android Auto 的车机上后,Android O...
继续阅读 »

什么是Android Auto


首先,Android Auto 不是 OS。它是集成在 Android OS 里的 feature。当通过 USB、Wi-Fi 将 Android Phone 连接到支持 Android Auto 的车机上后,Android OS 将自动加载支持 Auto 模式下的 App 并将图像投屏到车机屏幕上。


Android-Auto示意图


跟苹果的 CarPlay、百度的 CarLife、小米的 CarWith 一样,其本质上是投屏。Phone 提供计算、渲染,车机只是 Display,Display 和按键回传 Input 的事件,Phone 处理好之后将新的帧数据回传进行 Display。


如何使用Android Auto


Google官网已经明确介绍了使用 Android Auto 的步骤




  1. 确保您的汽车或售后音响与 Android Auto 兼容;




  2. 手机上必须安装 Android Auto 应用,Android 10 以下的手机可以到 Google Play 下载安装,Android 10 及以上内置了 Android Auto;


    Auto设置界面.png




  3. 使用 USB 数据线将手机连接到汽车,然后在汽车显示屏上查看 Android Auto;


    Auto界面




虽然简单的三个步骤,但使用Android Auto有一个大前提:



  • 使用 Android Auto 的手机需要使用Google服务框架


因此需要通过GMS认证,国内汽车品牌基本不支持 Android Auto,一些沿用了国外车机系统的合资车型可能会支持 Android Auto。


关于 Android Auto 支持的汽车和音响品牌,可查阅官网资料,里面列举得很详细。


如何开发Android Auto支持的应用


Google Developer 官网已经将 Android for Cars 的开发流程和规范写得很详细了,这里就不再详细赘述了,把官方的内容简单归纳一下,并列出一些注意项:



  • 我们可以基于 Android Auto 开发媒体应用(音乐,视频)、即时通讯应用、地图导航应用、并且有相应的测试方案和分发方案;

  • Google针对 Android Auto 应用专门提供了SDK,即 Android for Cars App Library。为了兼容非 Car 的设备集成到了 AndroidX 中;

  • Android Auto 不支持自定义 UI,你的应用只负责与车载屏幕进行数据和事件交互,因此,所有的 Android Auto 应用都长得大同小异;

  • 开发的 Android Auto 应用必须经过 Google Play Store 分发,否则屏幕是不显示的,Google Play Store 有四个分发渠道:internal、closed testing、open testing、production,分别对应内部、内测、公测、产品,开发调试阶段用 internal 渠道即可;

  • 因为车载场景事关驾驶员生命安全,所以 Google 对 Android Auto 应用审核很严格。所有支持 Android Auto 的应用,必须满足质量规范才可能通过 Google Play Store 的审核;

  • 音乐app可参考官方开发的uamp,它是支持 Android Auto 的;

  • 国产手机基本都把 Android Auto 应用给删减掉了,所以都需要手动安装,但 Android Auto 启动时会安装谷歌服务框架,因此,第一次使用 Android Auto 需要科学上网。

  • 在使用国产手机调试 Android Auto 时,会出现车机屏幕黑屏的情况,原因可能是没有经过 Google Play Store 分发,也有可能是其他未知原因,因此,建议使用 pixel 手机进行开发调试;


Android Auto与Android Automotive的区别




  • Android Auto是 Android 的扩展功能,包含 Android Auto 应用、支持 Android Auto 的Apps,车机屏幕,缺一不可;




  • Android Automotive是基于 Android 定制的适用于车载的OS,简称 AAOS,归属于AOSP项目,编译的时候选择Automotive的target combo即可;


    automotive桌面




国内汽车厂商普遍使用的Android Automotive,主要原因有:



  • 可以不需要通过GMS认证;

  • 兼容 Android Phone 和 Android Auto 的应用;

  • 独立的系统,不需要手机投屏,开发App和扩展车载功能非常方便;


参考链接


Android for Cars 概览

Android Auto

androidx.​car.​app

Android 车机初体验:Auto,Automotive 傻傻分不清楚?

Android Auto 开发指北


作者:小迪vs同学
来源:juejin.cn/post/7290372531218628649
收起阅读 »

没大项目经验,哪里冒出来的项目亮点?

前言 全球行业那么多,每个都需要软件开发,每个都需要数字化转型,但是互联网这个场景说白了只是其中的一个分支而已,世界上没有那么多高大上、高并发的项目给你做。 项目亮点的背景意义 讲项目亮点其实是为了面试,面试要讲项目,怎么讲呢。讲项目其实就是讲故事。项目从哪来...
继续阅读 »

前言


全球行业那么多,每个都需要软件开发,每个都需要数字化转型,但是互联网这个场景说白了只是其中的一个分支而已,世界上没有那么多高大上、高并发的项目给你做。


项目亮点的背景意义


讲项目亮点其实是为了面试,面试要讲项目,怎么讲呢。讲项目其实就是讲故事。项目从哪来到哪去,做了哪些事情,其中你做了什么;能够清晰条理的讲出来,面试官就知道你这个是个什么业务场景,也知道你确确实实参与了这个项目。


这个亮点讲的是什么?大家都知道面试官会经常问:项目中你遇到过哪些困难,你是怎么去解决它的!这里其实是一个比较让人容易陷入误区的地方,就前面也说了,没有那么多高并发,没有那么多大项目给你做过,那怎么办呢?


先说一下我个人的了解,就目前外企和这个非互联网体系面试的时候,主要考察的是这个人能不能做事情,要通过面试来了解他是如何解决问题的,解决问题的方式一定不是抛出一堆名词来。而是什么呢?“需求最初是什么样的,我们如何挖掘这个需求,我们挖掘后真实的需求是什么样的,我们做了怎么样的调查,我们给出了哪些方案,它们都有什么利弊,然后进行取舍,最后根据实际情况和老板的期望,我们是如何解决这个问题的,我们最终给出了什么方案“。


如果你真正把一个项目搞透的话,前面的这个故事你是可以讲的很出彩,很完整的。这样一个完整的过程是可以充分证明:首先项目是实实在在你完整的参与下来的,参与度很高。然后你也能够识别到关键问题的所在,同时这也充分的展示了你这个思维的逻辑性,你是一个可以work的人。


其实不管有没有做过大项目,上面这些要求也是必须达到的,它能证明你是一个有逻辑,能够有条理处理问题的人。有大项目固然是好事,但是没有的话,把自己手头项目挖掘出来,这也比凭空捏出来的项目要实实在在很多,因为这个故事讲的6不6,它是能够体现出你的参与程度的。


如果是抄别人的,其实比较容易能听出来,但是抄别人的项目,作为一个提升自己技术的手段,我还是比较认同的。但如果拿去面试的话,一个是确实它可能会重复,另一个可能细节问题你就经不起提问了。


工程师含义


挺多朋友经常在抱怨,自己每天有做不完的CRUD(增删改查)。说到这,其实我自己以前就是个CRUD BOY。当时每天就是写业务,这几乎占据了我80%以上的时间,现在回顾起来,这其实是一个非常值得反思的点,就是工程师这三个字代表的是什么含义。


年少无知的我以前不懂得它的含义,做了两个项目,就觉得自己不含糊了,十几万的项目是我一个实习生做的,凭什么我才拿2000块钱,那业务都是我写的,我感觉自己能单干,我能支楞起来。


后来真的有机会自己单干了,才明白当初为什么一个2000块钱的实习生,能撑起十几万的项目。原来那时候老板知道我菜,不相信我自己能搞定,他就把任务都拆解完了才给到我,前面的人已经把种种困难都克服完了,到我这儿就只剩执行了,就只剩CRUD了,所以我就成了CRUD BOY了,就是这么回事。如果你的工作,只是每天毫无挑战的一些重复工作,那么可能是前面有人在帮你顶着,那么你替前面的这个人分忧,就是成就自己亮点的最捷径的一种方式。


最后


我的感受就是做项目和做人是一样的,最重要的是把自己当成主角,真正用心的参与其中,平凡的生活其实到处也都是闪光点的,希望大家都能够找到好的工作。


作者:程序员Winn
来源:juejin.cn/post/7290021456879484991
收起阅读 »

类的布局——方法缓存hash表

回顾一下class的结构:struct objc_class : objc_object { // Class ISA; // 继承自 struct objc_object Class superclass; cache_t cache;...
继续阅读 »

回顾一下class的结构:struct objc_class : objc_object {

    // Class ISA;			// 继承自 struct objc_object
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
class_rw_t *data() const {
return bits.data();
}
};

不难发现,在objc_class结构中,有一个cache_t类型的成员变量cache

其结构如下:struct bucket_t {

private:
IMP _imp;
SEL _sel;
};
struct cache_t {
private:
uintptr_t _bucketsAndMaybeMask;
union {
struct {
uint32_t _unused;
uint16_t _occupied;
uint16_t _flags;
};
preopt_cache_t * _originalPreoptCache;
};
};
// objc-cache.m
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;

struct bucket_t *cache_t::buckets() const {
uintptr_t addr = _bucketsAndMaybeMask;
return (bucket_t *)(addr & bucketsMask);
}
// 哈希表已用
mask_t cache_t::occupied() const {
return _occupied;
}
// 哈希表容积
unsigned cache_t::capacity() const {
return mask() ? mask()+1 : 0;
}
mask_t cache_t::mask() const {
uintptr_t maskAndBuckets = _bucketsAndMaybeMask;
return maskAndBuckets >> maskShift;
}

通过查看cache_tbucket_t的结构以及其实现,可以很清晰的看到类的整个缓存表的内容。

接下来用lldb简单验证下:

//  CacheClass.h
@interface CacheClass : NSObject
- (void)cacheMethodA;
- (void)cacheMethodB:(NSString *)str;
- (void)cacheMethodC:(NSUInteger)integer andString:(NSString *)str;
@end

// CacheClass.m
#import "CacheClass.h"
@implementation CacheClass
- (void)cacheMethodA {
NSLog(@"%s", __func__);
}
- (void)cacheMethodB:(NSString *)str {
NSLog(@"%@===>%s", str, __func__);
}
- (void)cacheMethodC:(NSUInteger)integer andString:(NSString *)str {
NSLog(@"%lu====>%@===>%s", integer, str, __func__);
}
@end

// 调用
CacheClass *cacheObj = [[CacheClass alloc] init];
[cacheObj cacheMethodA];
NSLog(@"===================="); // 此行断点

  • 获取CacheObj的isa
(lldb) x/1gx cacheObj
0x281da0bc0: 0x0100000102c2d101
(lldb) p/x 0x0100000102c2d101 & 0x0000000ffffffff8ULL
(unsigned long long) $4 = 0x0000000102c2d100
  • 读取cache_t结构体:
(lldb) x/2gx 0x0000000102c2d100+0x10		// 此处+0x10是因为cache是在`objc_class`结构体的第16字节处开始。
0x102c2d110: 0x0001000281f850c0 0x8010000200000000 // 0x0001000281f850c0 为 _bucketsAndMaybeMask的值 0x8010000200000000 为 联合体的值
  • 获取bucket_t数组的首地址:
(lldb) p/x ((uintptr_t)1 << (48-4))-1		// 计算 bucketsMask 的值
(unsigned long) $4 = 0x00000fffffffffff
(lldb) p/x 0x0001000281f850c0 & 0x00000fffffffffff // _bucketsAndMaybeMask & bucketsMask
(long) $5 = 0x0000000281f850c0 // bucket_t数组的首地址
  • 获取bucket_t数组的count:
(lldb) p/x (0x0001000281f850c0 >> 48) + 1		// 相当于调用cache_t::capacity()函数
(long) $9 = 0x0000000000000002
  • 输出bucket_t数组的内容:
(lldb) x/4gx 0x0000000281f850c0
0x281f850c0: 0x58226481ad686ff0 0x00000001b0870410
0x281f850d0: 0x3c04798102c25d08 0x0000000102c26f97
  • 以{IMP,SEL}的结构验证:
(lldb) p (char *)0x00000001b0870410
(char *) $10 = 0x00000001b0870410 "init"
(lldb) dis -a 0x58226481ad686ff0
libobjc.A.dylib`-[NSObject init]:
0x1ad686ff0 <+0>: ret
0x1ad686ff4 <+4>: udf #0x0
0x1ad686ff8 <+8>: udf #0x0
(lldb) p (char *)0x0000000102c26f97
(char *) $11 = 0x0000000102c26f97 "cacheMethodA"
(lldb) dis -a 0x3c04798102c25d08
MethodCacheDemo`-[CacheClass cacheMethodA]:
0x102c25d08 <+0>: sub sp, sp, #0x30
0x102c25d0c <+4>: stp x29, x30, [sp, #0x20]
0x102c25d10 <+8>: add x29, sp, #0x20
0x102c25d14 <+12>: stur x0, [x29, #-0x8]
0x102c25d18 <+16>: str x1, [sp, #0x10]
0x102c25d1c <+20>: mov x9, sp
0x102c25d20 <+24>: adrp x8, 2
0x102c25d24 <+28>: add x8, x8, #0x363 ; "-[CacheClass cacheMethodA]"
0x102c25d28 <+32>: str x8, [x9]
0x102c25d2c <+36>: adrp x0, 3
0x102c25d30 <+40>: add x0, x0, #0x90 ; @"%s"
0x102c25d34 <+44>: bl 0x102c26344 ; symbol stub for: NSLog
0x102c25d38 <+48>: ldp x29, x30, [sp, #0x20]
0x102c25d3c <+52>: add sp, sp, #0x30
0x102c25d40 <+56>: ret

从输出结果可以看出,缓存数组位置是正确的,类CacheClass缓存了两个方法,分别为:-[NSObject init]-[CacheClass cacheMethodA]

用代码获取:

#if __arm64__
#if TARGET_OS_EXCLAVEKIT
#define ISA_MASK 0xfffffffffffffff8ULL
#elif __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#define ISA_MASK 0x007ffffffffffff8ULL
#else
#define ISA_MASK 0x0000000ffffffff8ULL
#endif
#endif

uintptr_t _isaForObject(NSObject *obj) {
if (obj == nil) return 0;
struct _object {
BytePtr isa;
};
struct _object *obj_ptr = (struct _object *)(__bridge void *)obj;
return (uintptr_t)((uintptr_t)obj_ptr->isa & ISA_MASK);
}

typedef uint32_t mask_t;
//cache_t源码模仿
const uintptr_t maskShift = 48;
const uintptr_t maskZeroBits = 4;
const uintptr_t maxMask = (((uintptr_t)1 << (64 - maskShift))-1);
const uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1; //0x100000000000-1 = 0xfffffffffff
struct bucket_t {
IMP _imp;
SEL _sel;
};
struct cache_t {
uintptr_t _bucketsAndMaybeMask; // 8
union {
struct {
uint32_t _unused;
uint16_t _occupied;
uint16_t _flags;
};
uintptr_t _originalPreoptCache; // 8
};
};
struct bucket_t *buckets(struct cache_t *cache) {
return (struct bucket_t *)(cache->_bucketsAndMaybeMask & bucketsMask);
}
uint32_t mask(struct cache_t *cache) {
return (uint32_t)(cache->_bucketsAndMaybeMask >> maskShift);
}
uint32_t capacity(struct cache_t *cache) {
return mask(cache) ? mask(cache)+1 : 0;
}
mask_t occupied(struct cache_t *cache) {
return cache->_occupied;
}

void _printMethodCaches(id obj) {
printf("============================\n");
uintptr_t isa = _isaForObject(obj);

// 读取cache结构体
struct cache_t *cache = (struct cache_t *)(isa + 0x10);

// 读取bucket_t
struct bucket_t *bucket_array = buckets(cache);

// 获取count
uint32_t count = capacity(cache);

// 获取已缓存数
uint32_t occupied_count = occupied(cache);

printf("哈希表容积:%u\t\t\t已缓存方法数:%u\n",count, occupied_count);

// 输出缓存内容
for (int c = 0; c < count; c++) {
struct bucket_t *bucket = (bucket_array + c);
printf("imp->sel:0x%lx->%s\n", (intptr_t)bucket->_imp, sel_getName(bucket->_sel));
}
printf("============================\n");
}

// 调用
CacheClass *cacheObj = [[CacheClass alloc] init];
[cacheObj cacheMethodA];
_printMethodCaches(cacheObj);

// 输出
============================
哈希表容积:2 已缓存方法数:2
imp->sel:0x4f5fd201ad686ff0->init
imp->sel:0xe9344e810494dc50->cacheMethodA
============================

需要注意的是,缓存哈希表有一个扩容的过程,当缓存方法超过了哈希表容积时,就会触发扩容,此时,之前的缓存并不会被复制到新的hash表中,而是重新还是缓存!

例如上面的调用修改为如下:

    CacheClass *cacheObj = [[CacheClass alloc] init];
[cacheObj cacheMethodA];
[cacheObj cacheMethodB:@"B"];

_printMethodCaches(cacheObj);

则输出为:

============================
哈希表容积:4 已缓存方法数:1
imp->sel:0x0-><null selector>
imp->sel:0x0-><null selector>
imp->sel:0xf85bf08100269c6c->cacheMethodB:
imp->sel:0x0-><null selector>
============================

从上面的输出可以看出,调用方法-[CacheClass cacheMethodB:]时,触发了缓存表扩容;扩容过程中,它舍弃了原缓存表中的方法,仅缓存了当前方法(-[CacheClass cacheMethodB:])。


收起阅读 »

一个前端实习生在美团的成长小结

一些前言 这是第二篇有关实习的小结,上一篇是在蔚来离职的时候写的。 也就是这一篇:一个前端实习生在蔚来的成长小结 离职的时候我在朋友圈是这样写的: Delay Version..... 半年的实习生活结束了 主题词可能是:“成长,反思,感恩” 这半年里有太多...
继续阅读 »

一些前言


这是第二篇有关实习的小结,上一篇是在蔚来离职的时候写的。


也就是这一篇:一个前端实习生在蔚来的成长小结


离职的时候我在朋友圈是这样写的:



Delay Version.....


半年的实习生活结束了


主题词可能是:“成长,反思,感恩”


这半年里有太多的第一次


第一次出省实习,第一次根据 prd 写代码,第一次上线,第一次领工资,第一次一个人在外生活那么长时间~


这半年里有太多的感动


感谢直线经理对我的照顾,从租房买药到个人成长,感谢组内同事耐心地解答我一个又一个愚蠢的问题(doge


这半年里有不少的思考


会感觉自己有些偏航,与自己所想的成长状态有些出入,许愿自己能在接下来的半年里探索出满意的生活状态。


以上,有缘再见啦~



我个人认为在 2023 的后半年里,我真的探索到了自己喜欢的生活方式。


通过亲人,朋友,过往的经历,所看的事物,慢慢构建起了自己的一套价值体系,能够圆融自洽,消化外界的冲击,同时还能通过和朋友定期的见面得到支持。


整理好自己之后,我们来聊聊在美团的故事。


蔚来与美团的区别


image.png


从蔚来来到美团之后,给我的感受就是:“这也太不一样了吧。”


考勤与强度


在蔚来那边,如果不是赶 C 端的业务的话,我平时走的都蛮早的,一般 10-6 这个时间段就 ok 了。在美团这边就是正常 10-8,因为要等到晚上八点钟拿餐补再下班(悲


基础建设


第二个特别明显的点就是美团的基础建设方面是真的做到了跨部门跨团队,很多东西可以直接复用,不需要重复造轮子,包括 CI/CD,组件库,还有一些基础的解决方案;


然后美团的文档真的超级超级多而全,我从这里面学到了不少东西。


第二段实习-美团基础研发平台~


9461697267534_.pic.jpg


接下来就来到了这篇文章的正题,聊聊在美团的一些收获与成长。


一些感谢


我觉得自己很幸运,又遇到了一个很好的组,组里面的人完全不 push,而且会很耐心的和你去讲你并不熟悉的点,不会因为你不会就说你怎么怎么样。


感谢 mentor 和 leader,还有一起实习一起约饭的小伙伴们,这四个月过的真的很开心。


个人成长


技术方面



  1. 因为我进去的那个组已经存在了很久了,需求很多都是在慢慢推进,技术驱动可能更加明显,就有好多好多优化的场景,在理解业务的基础之上去做一些相关的优化。



我接触的主要有两种优化方式:



  • 第一种是直接更改技术的实现方案,通过比对几种实现的优劣,再结合业务场景进行选择。

  • 第二种则是在代码层面的优化,诸如 v-if/v-show 这种,如果做得好了,优化的效果其实是相当可观的。





  1. 除了技术本身,我可能对“数据”这种业务场景更加了解了,这些了解包括了对于一些业务术语的理解,还包括了对于 echarts以及虚拟列表这些常见工具与优化的使用。




  2. 当然啦,还成功的在组内进行了一次技术分享。主要是围绕着我们组内经常使用的“装饰器”,我的分享囊括了基础的用法,再到三种装饰器的原理,还有在VueReact这些框架中的应用。




软技能方面




  1. 来了美团之后养成了一个习惯,基本我每开一个需求,我就会开一个文档,记录排期、技术难点、开发过程中不知道的点。这种习惯让我在整个开发的过程中效率提升了好多,我可以随时去查阅自己已经做过的部分,了解当时的想法。




  2. 第二点呢可能是在写代码的管理方面,学了一些对于端口的判断(lsof -i 8080)之类的,除此之外还有



    • 写代码的时候,没写完的部分可以列一个 TODOcommit 之前检查一下。

    • yarn start:test 之类的环境配置




  3. 还组织了一次 60-70 人的大团建,虽然是帮着 leader 去直接和团建机构做的对接,但是这个过程中也感受到了乙方的耐心和卑微,还有“做生意就是很难”的无奈。




生活


虽然美团这边实习 💰 给的确实不多,但是中秋的月饼和实习生的活动我算是都赶上了,会觉得人文活动其实还是蛮丰富的。


又因为美团在杨浦这边,离五角场那边并不远,我就总是去五角场那边酒吧喝酒qwq


还有还有,美团这边有健身房,所以我会比较固定的一周去三次,基本就是跑步加上一些基本的拉伸(在努力成为优秀的sigma(笑


9401697267529_.pic.jpg


ENDING...


后面因为个人身体的原因,这段实习只持续了大概四个月的时间,和最开始约定的时间有些出入。仔细想想,说遗憾其实也没有那么多遗憾,人生就是这样来去匆匆,经历一场离别,然后搭上列车,赶往下一场离别。


有缘再见啦,美团~


image.png


作者:阳树阳树
来源:juejin.cn/post/7289666055829192719
收起阅读 »

一个前端实习生在蔚来的成长小结

此文章2332字,预计花费时间7-11分钟。 一、聊聊工作氛围 & 个人成长 1. 这是我的期许 “所谓前途,不过是以后生活的归途。” 这是我人生中的第一段技术实习,之前并不是一心做技术的,所以为了探索自己喜欢的事情在某小公司做过翻译实习,并且在交行...
继续阅读 »

此文章2332字,预计花费时间7-11分钟。


image.png


一、聊聊工作氛围 & 个人成长


1. 这是我的期许


“所谓前途,不过是以后生活的归途。”


这是我人生中的第一段技术实习,之前并不是一心做技术的,所以为了探索自己喜欢的事情在某小公司做过翻译实习,并且在交行做过金融实习(其实就是纯打杂)。


image.png


我很喜欢这样一段话: “我曾以为我的23岁会手提皮包西装革履,但我还是穿着休闲裤,带着十几岁的意气行事,幼稚又成熟;我曾以为我的23岁会性格外向,做事圆滑,但我连最简单的亲情都处理不好;我曾以为我的23岁会和喜欢的人看山河大海落日余晖,但没想道周围的人谈婚论嫁都近在眼前,我还在路上找自己。”


我一直在探索着自己的边界,在能闯能疯的年纪反复横跳,寻找着自己的热爱与期许。在真正从事这个行业之后,我发现了我对于这个岗位的喜爱,当你看着一个个实际的视图出现于自己的手中,你会有一种莫名其妙的成就感,这种感觉难以描述的程度就好像你要向一个完全不看vtuber的人描述你对嘉然的喜爱。


2. 工作氛围:这里是一个乌托邦(适合摸鱼学习的好地方!)


说实话,我最开始预期是每天九点来上班,九点下班的(因为看学长们实习都好辛苦的样子)。


来了之后发现完全不是,每天十点上班,六点下班(我当然是准点跑路)



实习两个月左右的时候接的一个需求,第一天是另一个前端实习生来搞,后来他要跑路,leader就把活给我了。


周四,后端六点把接口给另一个前端实习生。


另一个前端实习生:“明天再说”


周五我来接这个活,我边画页面边让他加字段。


然后提完了,六点他给我改好的接口,让我看看有没问题


我:“下周再说”。


后端:“前端是不是,都很快乐啊[流泪]”



image.png


最开始因为我对 react 不是特别熟悉,leader 让我看着组内文档学了半个月,才开始了第一个需求。


leader 没有给我指定 mentor,所以当我有问题的时候,我看组内谁没开会(或者有时间)就会去问,都能得到很耐心的解答,这点来说还是很有安全感的。


然后每天都会跟着老板和大老板一起去吃饭,有时听他们说说自己的事情,有时听听他们对某个语言的看法,也算有不少收获。


值得一提的是刚入职三天部门就开始团建了,从周五下午五点玩到了第二天凌晨两点,炫了一只烤全羊,然后就开始电玩篮球各种 happy,后面玩狼人杀我次次狼人,大老板也总觉得我是狼人,我次次和他对着刚(乐)



马上就要第二次团建了,可惜参加不了呜呜呜



在团建上 leader 说我是从五个面试感觉都 ok 的人里面选出来的(当时我超惊喜的)


还有几件有趣的事情值得一提



第一件事情是中午和 leader 散步,他说:“你干了两个月这里的情况也看到,很难接触到同龄的小姐姐的,找对象的优先级应该要提高了。”


我:“说的对说的对。”


当时我心里就暗暗想着,这是我不想找吗?这tm是我找不到啊(悲)


第二件事情是我有事开了自己的热点,热点的名字叫:“要失业了咋办呐。


被同事发到了前端大群里。


同事:“这是谁的啊?”


我:“是实习生的(悲)”



3. 个人成长:“不卑不亢,低调务实”


最开始入职当然会担心一些七的八的,诸如这样说会不会不太客气,这样搞会不会让老板不爽,后来和老板还有大老板一起吃饭之后发现他们人都挺随和的,没什么架子,他们更多的关心的是这件事情做的怎么样。


大老板曾经在周会上说:“这个事情可以做的慢一些,这是能力上的问题,这个可以商量,但是如果到了约定的日期没有交付,这就有问题了。 ”这个是说的务实。


然后就是为人处事方面了,自己有时候挺跳脱的,没有什么边界感,在实习和他们一起吃饭的时候我就回默默的听着,有些问题大家都不会问,算是看着看着就成长了。


回校远程的时候我写了这样一段话:



去打工吧,去打上海冬夜准时下班,踩雪走回家的工。


去打工吧,去打一边聊天一边发现,这个产品也是清华✌️的工。


去打工吧,去打测试前一天,人都走光了,mentor陪我赶工到半夜的工。


去打工吧,去打部门团建,大leader带我们玩狼人杀到凌晨两点,超级尽兴的工。


冴羽曾在一次读书会上分享:“开眼界就像开荤一样,只有见过了才会产生饥饿感。”


打工虽然让我变成了稍不注意就会摆烂的成年人,但大平台汇聚了很多丰富有趣的同事,让我看到了截然不同的经历与一波三折的人生。


不知道是不是部门的原因,我这边总是十六五准点上下班。


我现在依然处于打工真香的阶段,不用早起,不用日复一日的和同龄人卷同一件事,身边的人年岁不同,人生阶段也不相同,卷不到一起去。


我还在路上~



image.png


4. 代码方面 learning


说实话看到组内项目的时候体会到了不少的震撼,看着组内的项目之后真的就感觉自己写的东西和玩具一样,每次写完项目,都会兴冲冲的找组内的哥哥姐姐帮忙 CR,然后 CR 出一堆问题,自己在一个一个的修改,把这些规范点记周报上,总之就是学到了很多很多。


timeLine 大概是这样的



  • 前两周熟悉 react 写小 demo

  • 然后以两周一个需求的速度给咱活干~


记得第二次写完一个稍微有点复杂的需求,带着我做这个需求的 mentor 还夸了我一波(骄傲)


5. 对于技术和业务的想法


大leader组织组内 vau 对齐的时候我仔细的听了听,我们的很多东西都需要落地,相比来说技术只是一个实现的手段,并不是做这个的目的。


但怎么说呢,我个人还是对技术本身抱有很大的期许的,希望自己能够变得很厉害,参与到很多的开源项目中,我坚信代码可以改变世界。


二、展望未来



实习不去字节,就像读四大名著不看红楼梦,基督徒不看圣经,学相对论不知道爱因斯坦,看vtuber不看嘉然今天吃什么,这个人的素养与精神追求不足,成了无源之水,无本之木。他的格局就卡在这里了,只能度过一个相对失败的人生!




  • 话是这么说啦,但最后还是没有成功去到字节,但是我是字节不折不扣的舔狗,后面再看吧。

  • 字节给我发面试一定是喜欢我(普信)


下面这段是之前写的



离开的契机也很简单,我在小红书实习的同学跑路了,然后要找继任,顺手把我的简历投过去了,然后我顺手面了一下小红书,小红书顺手给我发了个Offer(bushi,然后就去小红书了。



image.png


小红书确实Offer了,但是老板和我约谈了很久,我决定继续远程实习,在这篇文章发布的当天,我已经实习了 一百四十天,我相信,我的旅途还在继续。


image.png


三、写在最后


不知不觉就实习快半年了啊


我真的非常感谢遇到的leader和同事,感恩遇到的每一位愿意拉我一把的人。


在这段时间里学到了好多一个人学习学不到的东西啊。


那么这就是我在蔚来的实习小结啦!


感谢阅读~


作者:阳树阳树
来源:juejin.cn/post/7228245665334198333
收起阅读 »

在这个大环境下我是如何找工作的

蛮久没更新了,本次我想聊聊找工作的事情,相信大家都能感受到从去年开始到现在市场是一天比一天差,特别是在我们互联网 IT 行业。 已经过了 18 年之前的高速发展的红利期,能做的互联网应用几乎已经被各大公司做了个遍,现在已经进入稳定的存量市场,所以在这样的大背景...
继续阅读 »

蛮久没更新了,本次我想聊聊找工作的事情,相信大家都能感受到从去年开始到现在市场是一天比一天差,特别是在我们互联网 IT 行业。
已经过了 18 年之前的高速发展的红利期,能做的互联网应用几乎已经被各大公司做了个遍,现在已经进入稳定的存量市场,所以在这样的大背景下再加上全世界范围内的经济不景气我想每个人都能感受到寒意。


我还记得大约在 20 年的时候看到网上经常说的一句话:今年将是未来十年最好的一年。


由于当时我所在的公司业务发展还比较顺利,丝毫没有危机意识,对这种言论总是嗤之以鼻,直到去年国庆节附近。


虽然我们做的是海外业务,但是当时受到各方面的原因公司的业务也极速收缩(被收购,资本不看好),所以公司不得不进行裁员;
其实到我这里的时候前面已经大概有 2~3 波的优化,我们是最后一波,几乎等于是全军覆没,只留下少数的人维护现有系统。


这家公司也是我工作这么多年来少数能感受到人情味的公司,虽有不舍,但现实的残酷并不是由我们个人所决定的。


之后便开始漫长的找工作之旅,到现在也已经入职半年多了;最近看到身边朋友以及网上的一些信息,往往是坏消息多于好消息。


市场经历半年多的时间,裁员的公司反而增多,岗位也越来越少,所以到现在不管是在职还是离职的朋友或多或少都有所焦虑,我也觉得有必要分享一下我的经历。


我的预期目标


下面重点聊聊找工作的事情;其实刚开始得知要找工作的时候我并不是特别慌,因为当时手上有部分积蓄加上公司有 N+1 的赔偿,同时去年 10 月份的时候岗位相对于现在还是要多一些。


所以我当时的目标是花一个月的时间找一个我觉得靠谱的工作,至少能长期稳定的工作 3 年以上。


工作性质可以是纯研发或者是偏管理岗都可以,结合我个人的兴趣纯研发岗的话我希望是可以做纯技术性质的工作,相信大部分做业务研发的朋友都希望能做一些看似“高大上”的内容。
这一点我也不例外,所以中间件就和云相关的内容就是我的目标。


不过这点在重庆这个大洼地中很难找到对口工作,所以我的第二目标是技术 leader,或者说是核心主程之类的,毕竟考虑到 3 年后我也 30+ 了,如果能再积累几年的管理经验后续的路会更好走一些。


当然还有第三个选项就是远程,不过远程的岗位更少,大部分都是和 web3,区块链相关的工作;我对这块一直比较谨慎所以也没深入了解。


找工作流水账


因为我从入职这家公司到现在其实还没出来面试过,也不太知道市场行情,所以我的想法是先找几家自己不是非去不可的公司练练手。



有一个我个人的偏好忘记讲到,因为最近的一段时间写 Go 会多一些,所以我优先看的是 Go 相关的岗位。



第一家


首先第一家是一个 ToB 教育行业的公司,大概的背景是在重庆新成立的研发中心,技术栈也是 Go;


我现在还记得最后一轮我问研发负责人当初为啥选 Go,他的回答是:



Java 那种臃肿的语言我们首先就不考虑,PHP 也日落西山,未来一定会是 Go 的天下。



由于是新成立的团队,对方发现我之前有管理相关的经验,加上面试印象,所以是期望我过去能做重庆研发 Leader。


为此还特地帮我申请了薪资调整,因为我之前干过 ToB 业务,所以我大概清楚其中的流程,这种确实得领导特批,所以最后虽然没成但依然很感谢当时的 HR 帮我去沟通。


第二家


第二家主要是偏年轻人的 C 端产品,技术栈也是 Go;给我印象比较深的是,去到公司怎么按电梯都不知道🤣



他们办公室在我们这里的 CBD,我长期在政府赞助的产业园里工作确实受到了小小的震撼,办公环境比较好。



当然面试过程给我留下的印象依然非常深刻,我现在依然记得我坐下后面试官也就是 CTO 给我说的第一句话:



我看过你的简历后就决定今天咱们不聊技术话题了,直接聊聊公司层面和业务上是否感兴趣,以及解答我的疑虑,因为我已经看过你写的很多博客和 GitHub,技术能力方面比较放心。



之后就是常规流程,聊聊公司情况个人意愿等。


最后我也问了为什么选 Go,这位 CTO 给我的回答和上一家差不多😂


虽然最终也没能去成,但也非常感谢这位 CTO,他是我碰到为数不多会在面试前认真看你的简历,博客和 GitHub 都会真的点进去仔细阅读👍🏼。



其实这两家我都没怎么讲技术细节,因为确实没怎么聊这部分内容;这时就突出维护自己的技术博客和 GitHub 的优势了,技术博客我从 16 年到现在写了大约 170 篇,GitHub 上开源过一些高 star 项目,也参与过一些开源项目,这些都是没有大厂经历的背书,对招聘者来说也是节约他的时间。





当然有好处自然也有“坏处”,这个后续会讲到。


第三家


第三家是找朋友推荐的,在业界算是知名的云原生服务提供商,主要做 ToB 业务;因为主要是围绕着 k8s 社区生态做研发,所以就是纯技术的工作,面试的时候也会问一些技术细节。



我还记得有一轮 leader 面,他说你入职后工作内容和之前完全不同,甚至数据库都不需要安装了。



整体大概 5、6 轮,后面两轮都是 BOSS 面,几乎没有问技术问题,主要是聊聊我的个人项目。


我大概记得一些技术问题:



  • k8s 相关的一些组件、Operator

  • Go 相关的放射、接口、如何动态修改类实现等等。

  • Java 相关就是一些常规的,主要是一些常用特性和 Go 做比较,看看对这两门语言的理解。


其实这家公司是比较吸引我的,几乎就是围绕着开源社区做研发,工作中大部分时间也是在做开源项目,所以可以说是把我之前的业余爱好和工作结合起来了。


在贡献开源社区的同时还能收到公司的现金奖励,不可谓是双赢。


对我不太友好的是工作地在成都,入职后得成渝两地跑;而且在最终发 offer 的前两小时,公司突然停止 HC 了,这点确实没想到,所以阴差阳错的我也没有去成。


第四家


第四家也就是我现在入职的公司,当时是我在招聘网站上看到的唯一一家做中间件的岗位,抱着试一试的态度我就投了。
面试过程也比较顺利,一轮同事面,一轮 Leader 面。


技术上也没有聊太多,后来我自己猜测大概率也和我的博客和 Github 有关。




当然整个过程也有不太友好的经历,比如有一家成都的“知名”旅游公司;面试的时候那个面试官给我的感觉是压根没有看我的简历,所有的问题都是在读他的稿子,根本没有上下文联系。


还有一家更离谱,直接在招聘软件上发了一个加密相关的算法,让我解释下;因为当时我在外边逛街,所以没有注意到消息;后来加上微信后说我为什么没有回复,然后整个面试就在微信上打字进行。


其中问了一个很具体的问题,我记得好像是 MD5 的具体实现,说实话我不知道,从字里行间我感觉对方的态度并不友好,也就没有必要再聊下去;最后给我说之所以问这些,是因为看了我的博客后觉得我技术实力不错,所以对我期待较高;我只能是地铁老人看手机。


最终看来八股文确实是绕不开的,我也花了几天时间整理了 Java 和 Go 的相关资料;不过我觉得也有应对的方法。


首先得看你面试的岗位,如果是常见的业务研发,从招聘的 JD 描述其实是可以看出来的,比如有提到什么 Java 并发、锁、Spring等等,大概率是要问八股的;这个没办法,别人都在背你不背就落后一截了。


之后我建议自己平时在博客里多记录八股相关的内容,并且在简历上着重标明博客的地址,尽量让面试官先看到;这样先发制人,你想问的我已经总结好了😂。


但这个的前提是要自己长期记录,不能等到面试的时候才想起去更新,长期维护也能加深自己的印象,按照 “艾宾浩斯遗忘曲线” 进行复习。


选择



这是我当时记录的面试情况,最终根据喜好程度选择了现在这家公司。


不过也有一点我现在觉得但是考虑漏了,那就是行业前景。


现在的 C 端业务真的不好做,相对好做的是一些 B 端,回款周期长,同时不太吃现金流;这样的业务相对来说活的会久一些,我现在所在的公司就是纯做 C 端,在我看来也没有形成自己的护城河,只要有人愿意砸钱随时可以把你干下去。


加上现在的资本也不敢随意投钱,公司哪天不挣钱的话首先就是考虑缩减产研的成本,所以裁员指不定就会在哪一天到来。


现在庆幸的是入职现在这家公司也没有选错,至少短期内看来不会再裁员,同时我做的事情也是比较感兴趣的;和第三家有些许类似,只是做得是内部的基础架构,也需要经常和开源社区交流。


面对裁员能做的事情


说到裁员,这也是我第一次碰上,只能分享为数不多的经验。


避免裁员


当然第一条是尽量避免进入裁员名单,这个我最近在播客 作为曾经的老板,我们眼中的裁员和那些建议 讲到在当下的市场情况下哪些人更容易进入裁员名单:



  • 年纪大的,这类收入不低,同时收益也没年轻人高,确实更容易进入名单。

  • 未婚女性,这点确实有点政治不正确,但确实就是现在的事实,这个需要整个社会,政府来一起解决。

  • 做事本本分分,没有贡献也没出啥事故。

  • 边缘业务,也容易被优化缩减成本。


那如何避免裁员呢,当然首先尽量别和以上特征重合,一些客观情况避免不了,但我们可以在第三点上主动“卷”一下,当然这个的前提是你还想在这家公司干。


还有一个方法是提前向公司告知降薪,这点可能很多人不理解,因为我们大部分人的收入都是随着跳槽越来越高的;但这些好处是否是受到前些年互联网过于热门的影响呢?


当然个人待遇是由市场决定的,现在互联网不可否认的降温了,如果你觉得各方面呆在这家公司都比出去再找一个更好,那这也不失为一个方法;除非你有信心能找到一个更好的,那就另说了。


未来计划


我觉得只要一家公司只要有裁员的风声传出来后,即便是没被裁,你也会处于焦虑之中;要想避免这种焦虑确实也很简单,只要有稳定的被动收入那就无所谓了。


这个确实也是说起来轻松做起来难,我最近也一直在思考能不能在工作之余做一些小的 side project,这话题就大了,只是我觉得我们程序员先天就有自己做一个产品的机会和能力,与其把生杀大权给别人,不如握在自己手里。


当然这里得提醒下,在国内的企业,大部分老板都认为签了合同你的 24 小时都是他的,所以这些业务项目最好是保持低调,同时不能影响到本职工作。



欢迎关注作者公众号于我交流🤗。



作者:crossoverJie
来源:juejin.cn/post/7246570594991718455
收起阅读 »

5分钟回顾webpack的前世今生

web
引言 模块化编程是软件设计的一个重要思想。在JavaScript中,处理模块一直是个问题,由于浏览器只能执行JavaScrip、CSS、HTML 代码,所以模块化的前端代码必须进行转换后才能运行。例如 CommonJS 或 AMD,甚至是ECMA 提出的 Ja...
继续阅读 »

引言


模块化编程是软件设计的一个重要思想。在JavaScript中,处理模块一直是个问题,由于浏览器只能执行JavaScrip、CSS、HTML 代码,所以模块化的前端代码必须进行转换后才能运行。例如 CommonJS 或 AMD,甚至是ECMA 提出的 JavaScript 模块化规范——ES6 模块,这些模块系统要么是在浏览器无法运行,要么是无法被浏览器识别和加载,所以针对不同的模块系统,就需要使用专门的工具将源代码转换成浏览器能执行的代码。


整个转化过程被称为构建,构建过程就是“模块捆绑器”或“模块加载器”发挥作用的地方。


Webpack是JavaScript模块捆绑器。在Webpack之前,已经有针对各类型的代码进行编译和构建的流程,例如使用Browserify对CommonJS模块进行编译和打包,然后将打包的资源通过HTML去加载;或者通过gulp进行任务组排来完成整个前端自动化构建。


但是这些方式的缺点是构建环节脱离,编译、打包以及各类资源的任务都分离开。


Webpack模块系统的出现,能将应用程序的所有资源(例如JavaScript、CSS、HTML、图像等)作为模块进行管理,并将它们打包成一个或多个文件并进行优化。Webpack的强大和灵活性使得其能够处理复杂的依赖关系和资源管理,已经成为了构建工具中的首选。


本文主要来扒一扒Webpack的发展进阶史,一起来看看Webpack是如何逐渐从一个简单的模块打包工具,发展成一个全面的前端构建工具和生态系统。


webpack发展历程


webpack从2012年9月发布第一个大版本至2020年10月一共诞生了5个大的版本,我们从下面一张图可以清晰具体地看到每一个版本的主要变化

Webpack发展史.png

Webpack 版本变化方向



  1. Webpack 1:在此之前多是用gulp对各个类型的编译任务进行编排,最后在Html文件中将各种资源引用进来,而Webpack的初始版本横空出世,凭借如下其功能、理念、内核等优点成为众多前端构建工具的最新选择。



  • 理念:一切皆资源,在代码中就能能对Html、Js、Css、图片、文本、JSON等各类资源进行模块化处理。

  • 内核:实现了独有的模块加载机制,引入了模块化打包和代码分割的概念。

  • 功能:集合了编译、打包、代码优化、性能改进等以前各类单一工具的功能,成为前端构建工具标准选择。

  • 特点:通过配置即可完成前端构建任务,同时支持开发者自定义LoaderPlugin对Webpack的生态进行更多的扩展。



  1. Webpack 2: Webpack 2的在第一个版本后足足过了4年,其重点在于满足更多的打包需求以及少量对打包产物的优化



  • 引入对ES6模块的本地支持。

  • 引入import语法,支持按需加载模块。

  • 支持Tree Shaking(无用代码消除)。



  1. Webpack 3:Webpack 3提供了一些优化打包速度的配置,同时对打包体积的优化再次精益求精



  • 引入Scope Hoisting(作用域提升),用于减小打包文件体积。

  • 引入module.noParse选项,用于跳过不需要解析的模块。



  1. Webpack 4:Webpack 4带来了显著的性能提升,同时侧重于用户体验,倡导开箱即用



  • 引入了mode选项,用于配置开发模式或生成模式,减少用户的配置成本,开箱即用

  • 内置Web Workers支持,以提高性能



  1. Webpack 5:Webpack 5继续在构建性能和构建输出上进行了改进,且带来跨应用运行时模块共享的方案



  • 支持WebAssembly模块,使前端能够更高效地执行计算密集型任务。

  • 引入了文件系统持久缓存,提高构建速度

  • 引入Module Federation(模块联邦),允许多个Webpack应用共享模块


webpack打包后的代码分析


为了更方便理解后续章节,我们先看一下Webpack打包后的代码长什么样(为了方便理解,这里以低版本Webpack为例,且不做过多描述)


jsx
复制代码
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};

/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ /* 省略 */
/******/ }

/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;

/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;

/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";

/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {/*省略*/})
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {/*省略*/})
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {/*省略*/})
/******/ ]);

可以看到其实入口文件就是一个IIFE(立即执行函数),在这个IIFE里核心包括两块:



  1. 模块系统:Webpack 在IIFE里实现了模块系统所需要的Module、Require、export等方法组织代码。每个模块都被包装在一个函数内,这个函数形成了一个闭包,模块的作用域在这个闭包内。

  2. 模块闭包IIFE的入参即是Modules,它是一个数组,数组的每一项则是一个模块,每个模块都有自己的作用域。模块和模块之间通过Webpack的模块系统可以进行引用。


webpack的发展长河中,笑到最后和沦为历史

笑到最后:OccurrenceOrderPlugin



有趣的是该插件在Webpack 1叫做OccurenceOrderPluginWebpack 2才更名为OccurrenceOrderPluginWebpack 3则不需要手动配置该插件了。


插件作用:用于优化模块的顺序,以减小输出文件的体积。其原理基于模块的使用频率,将最常用的模块排在前面,以便更好地利用浏览器的缓存机制。


有了前面对于Webpack打包后的代码分析,OcurrenceOrderPlugin的优化效果也就很好理解了。它的原理主要基于两个概念:模块的使用频率模块的ID



  1. 模块的使用频率:OccurrenceOrderPlugin 插件会分析在编译过程中每个模块的出现次数。这个出现次数是指模块在其他模块中被引用的次数。插件会统计模块的出现次数,通常情况下,被引用次数更多的模块将被认为更重要,因此会更早地被加载和执行。

  2. 模块的 ID:Webpack 使用数字作为模块的 ID,OccurrenceOrderPlugin 插件会根据模块的出现次数,为每个模块分配一个优化的 ID。这些 ID 的分配是按照出现次数从高到低的顺序进行的,以便出现次数较多的模块获得较短的 ID,这可以减小生成的 JavaScript 文件的大小。假设一共有100个模块,最高的频率为被引用100次,则减小文件体积200B。(确实好像作用很小,但是作为最贴近用户体验的前端er,不应该是追求精益求精嘛)


这个插件的主要目标是减小 JavaScript 文件的体积,并提高加载性能,因为浏览器通常更倾向于缓存较小的文件。通过将频繁使用的模块分配到较短的 ID,可以减小输出文件的体积,并提高缓存的效率。


笑到最后:Scope Hoisting


过去 Webpack 打包时的一个取舍是将 bundle 中各个模块单独打包成闭包。这些打包函数使你的 JavaScript 在浏览器中处理的更慢。相比之下,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者预编译所有模块到一个闭包中,提升你的代码在浏览器中的执行速度。


而Scope Hoisting 就是实现以上的预编译功能,通过静态分析代码,确定哪些模块之间的依赖关系,然后将这些模块合并到一个函数作用域中。这样,多个模块之间的函数调用关系被转化为更紧凑的代码,减少了函数调用的开销。这样不仅减小了代码体积,同时也提升了运行时性能。


Scope Hoisting 的原理是在 Webpack 的编译过程中自动进行的,开发人员无需手动干预。要启用 Scope Hoisting,你可以使用 Webpack 4 版本中引入的 moduleConcatenation 插件。在 Webpack 5 及更高版本中,Scope Hoisting 是默认启用的,不需要额外的配置。


CommonsChunkPlugin的作用和不足,为何会被optimization.splitChunks所取代


CommonsChunkPlugin 插件,是一个可选的用于建立一个独立chunk的功能,这个文件包括多个入口 chunk 的公共模块。主要配置项包含


json
复制代码
{
name: string, // or
names: string[],
filename: string,
minChunks: number|Infinity|function(module, count) => boolean,
chunks: string[],
// 通过 chunk name 去选择 chunks 的来源。chunk 必须是 公共chunk 的子模块。
// 如果被忽略,所有的,所有的 入口chunk (entry chunk) 都会被选择。

children: boolean,
deepChildren: boolean,
}

通过上面的配置项可以看到虽然CommonsChunkPlugin将一些重复的模块传入到一个公共的chunk,以减少重复加载的情况,尤其是将第三方库提取到一个单独的文件中,但是其首要依赖是通过Entry Chunk进行的。在Webpack4以及更高的版本当中被optimization.splitChunks所替代,其提供了配置让webpack根据策略来自动进行拆分,被替代的原因主要有以下几点:



  1. 灵活度不足:在配置上相对固定,只能将指定 Entry Chunk的共享模块提取到一个单独的chunk中,可能无法满足复杂的代码拆分需求。

  2. 配置复杂:需要手动指定要提取的模块和插件的顺序,配置起来相对复杂,开发者需要约定好哪些chunk可以被传入,有较高的心智负担。而optimization.splitChunks只需要配置好策略就能够帮你自动拆分。


因此在Webpack 4这个配置和开箱即用的版本里,它自然也就“香消玉损”。只能遗憾地看到一句:


the CommonsChunkPlugin 已经从 Webpack v4 legato 中移除。想要了解在最新版本中如何处理 chunk,请查看 SplitChunksPlugin


被移除的DedupePlugin


这是 Webpack 1.x 版本中的插件,用于在打包过程中去除重复的模块(deduplication),其原理不知道是通过内容hash,还是依赖调用关系图。但是在Webpack 2中引入了Tree Shaking功能,则不再需要了。原因有以下几点:



  • Tree Shaking控制更精确:能通过静态分析来判断哪些代码是不需要的,实现了更细力度的优化。

  • Scope Hositing减少了重复模块:Webpack 3引入了Scope Hositing,将模块包裹在函数闭包中,进一步减少了重复模块的依赖


因此我们在Webpack的文档中看到:



DedupePlugin has been removed


不再需要 Webpack.optimize.DedupePlugin。请从配置中移除。



总结


或许有些插件你已经看不到它的身影,有些特性早已被webpack内置其中。webpack从第一个版本诞生后一直致力于以下几个方面的提升:



  1. 性能优化:通过去除重复代码、作用域提升、压缩等方式减少代码体积和提高运行时性能。

  2. 构建提效:通过增量编译、缓存机制、并行处理等提升打包速度。

  3. 配置简化:通过内置必要的特性和插件以及简化配置提升易用性。


作者:古茗前端团队
来源:juejin.cn/post/7289718324858355769
收起阅读 »

”调试小技巧,让未来更美好“

web
① 自动打断点(抛异常时自动断点) 偶然一次可能不小心打开某个设置选项,可能设置了英文又不知道是打开了什么,只知道当每次打开F11打开控制台调试看数据的时候,就是不会自动停在某个位置,又不知道怎么停掉,怀疑会不会是安装了什么谷歌插件或者是油猴哪个脚本代码写错写...
继续阅读 »

自动打断点(抛异常时自动断点)


偶然一次可能不小心打开某个设置选项,可能设置了英文又不知道是打开了什么,只知道当每次打开F11打开控制台调试看数据的时候,就是不会自动停在某个位置,又不知道怎么停掉,怀疑会不会是安装了什么谷歌插件或者是油猴哪个脚本代码写错写了什么。


不小心打钩了断点调试的遇到未捕获的异常时暂停,或者在遇到异常时暂停这两个选项其中一个。就有可能导致了谷歌的调试器暂停,取决于这个网站有没有一些异常触发到这一点,勾选上每次异常浏览器会帮我们打断点。


image.png


所以解决办法就是把谷歌浏览器中的这两个勾去掉,如果不是你本意打开想要调试网站中一些异常的报错。


image.png


一键重发请求(不用每次重新请求就刷新页面)


排查接口的时候,需要重新请求该接口,不必要每次重新刷新页面去请求试接口里面传参对不对返回来的数据对不对。重发请求很简单,右击该接口重发xhr即可。


image.png


image.png


③ 断点调试+debugger+console+try...catch


(1) console.log


找bug解决bug是很重要滴。console.log-输出某个变量值是非常非常常用的,只要做码农一定得会各种语言的输出消息和变量的语句,方便我们查看和调试。


(2) debugger(不用每次都console)


在代码某个语句后面或者前面输debugger


在我入行到在学校生涯那段时间都不知道debugger;这玩意,有一次项目有一个比较棘手不知道怎么解决的问题,甲方公司项目负责人开会重点讲了那个问题,就见他这里输一下dubugger,那里输一个debugger,当时就觉得那玩意很神(反正意识上只要我们不懂的东西刚开始接触都是这样,这里神那里神的,接触久了就觉的也就那样不过如此,很平常),最后也没看出什么来。


debugger就是在某个状态下,用这个debugger;语句在那里断一下点,然后当下,上下文的状态和值都可以在查看,哪个分支导致变量状态错误。


使用debugger可以查看:



  • 作用域变量

  • 函数参数

  • 函数调用堆栈

  • 代码整个执行过程(从哪一句到哪一句的)

  • 如果是异步promise async...await 等这种的话就需要在then和catch里面debugger去调试


(3) try...catch 捕获异常


try {
// 可能会抛出异常的代码
} catch {
// 处理所有异常的代码
}

try...catch捕获异常,包括运行时错误和自定义以及语法错误。


try...catch中还可以在某些情况下用throw在代码中主动抛出异常。


try {
// 可能会抛出异常的代码

if (某某情况下) throw '某某错误提示信息'

} catch {
// 处理所有异常的代码
} finally {
// 结束处理用于清理操作
}

image.png


④ 联调查看接口数据


image.png


如上图这个接口,如果想要复制接口preview里面的数据,


除了去Responese里面去找我们需要的某个值去选择复制之外(这个有个缺点就是要找值不直观),还可以右击某个值,然后通过点击store object as global variable(存储为全局变量) 获取。


image.png


当我们点击了之后,控制台就会出现tempXX这个变量。


image.png


我们就只需要在控制台输入copy(temp3)copy(要复制的json名),在粘贴板上就有这个json数据了。



💡
全局方法copy()在console里copy任何你能拿到的数据资源。



image.png


⑤ 后端接口数据返回json


这个有时候有的同学有可能碰到类似这种的JSON数据{\"name\":\"John\",\"address\":\"123 Main St, City\"}


解决方法


直接打开控制台console,输入 JSON.parse("{"name":"John","address":"123 Main St, City"}"),这样


image.png


如果你想复制下来用,直接跟上面我们用copy这好碰上,赋值加上一个copy就可以了。


image.png


这样这个值就在粘贴板上了。


总结


报错和bug,多多少少会贯穿我们的职业生涯中,如何定位问题、解决问题,加以调试,是我们必须也是不能不必备的技能。


当你捕获bug的时候.gif



☎️ 希望对大家有所帮助,如有错误,望不吝赐教,欢迎评论区留言互相学习。



作者:盏灯
来源:juejin.cn/post/7288963208396603450
收起阅读 »

我是如何走上程序员的道路的

封面图 学生时代的照片,想来已是十几年前的事情了 学生时代 从2009年9月开始读大学,当然也不是什么好大学,位于中原腹地的一所三流大学,所学专业是经济学中的《审计》。 上学的时候其实也不知道审计这个专业到底是做什么的,当时只是根据大学老师的讲述,大抵是可以...
继续阅读 »

封面图


image.png


学生时代的照片,想来已是十几年前的事情了


学生时代


从2009年9月开始读大学,当然也不是什么好大学,位于中原腹地的一所三流大学,所学专业是经济学中的《审计》。


上学的时候其实也不知道审计这个专业到底是做什么的,当时只是根据大学老师的讲述,大抵是可以到企业中做个会计师什么的,当然我们的专业课除了《审计》之外,《会计》《成本会计》《经济法》《税法》《财务管理》之类的都有。


学生时代对于我这种普通家庭的学生,大部分时候都是比较迷茫的,虽然每天都去上课,也很努力的去学习,识记老师讲课的内容,然后参加各种专业考试,会计证,助理会计师之类的考试,但是其实对于自己将来到底想要成为什么样的人,将来做什么样的工作,过怎样的生活,都没有一个完整的概念,只是随着日子一天一天的逝去,随波逐流而已,对于这种状态,我现在通常用一个词来概括:局限性


局限性我给它的定义是:在个人所处的时代,环境以及个人认知水平有限的情况下,个人所能做事情的极限。简而言之,就是所有的事情都是命里注定的一个圈,超出了这个圈的范围,别的事情就做不了,这就是局限。


在这种局限下浑浑噩噩的度过了三年的时光,除了和班里同学一同度过这三年时光能够留下一些快乐的回忆之外。还有一件事情我觉得值得聊一下,就是我对英语比较感兴趣,这种兴趣表现在我喜欢在没课的时候去蹭英语系的外教课,喜欢听那个胖胖的外交讲课,有时候也到讲台上和他们一起做一些互动,讲一些英文,练习一下自己的口语。


等他们下课了,clay,那个外教的名字叫clay,是一个胖胖的60多岁的老头,我喜欢在他等班车的的时候跟他聊会儿天,内容我也记不得是什么了,大抵是些怎么学好英语之类的话,他也非常和蔼,只是说:dont warry,you need more pritice之类的,也有些是他在中国收养了很多孩子,住在大学路上什么的。


外语系的姑娘都很漂亮,当然,这可能也是我去蹭英语课的一个原因。


日子就这么一天一天的过去,上课,逃课,上课,逃课,逃课去蹭课。逃课出去玩,忽然有一天发现,自己马上就要毕业了,然后发现大家都开始准备找工作了,于是自己也开始准备简历,去人才市场,去各种招聘会~


那里能有什么好工作呢~


工作


好在是2012年的8月份吧,被一家单位录取了,名义上是做财务,其实是被派到外地做出纳去了,地址在东莞分理处。


彼时的想法是,好好工作两年,然后混个分理处的主管什么的,其实自己非常清楚自己本身也不适合做主管,好在分理处的同时都是年轻人,比较好相处。


于是在这里待了有一年多,分理处的其他人主要是做销售工作,基于工作性质的不稳定性,人员流失率很大,后来我就也离职了。


进京


从那里离职之后,去了四川的一家建筑单位,驻扎在项目部,职务是会计,主要工作内容是帮忙做些单位的内账,负责企业的报税及项目部的部分出纳工作。


彼时已经是2014年了,淘宝等线上购物平台已经非常流行了,听说当时的美工等工作也非常吃香,于是打算自学photoshop,将来转成美工,做些淘宝店铺的装修等工作。


可惜美工没有做成,恰逢建筑项目上需要进行融资,跟银行进行贷款,所以当时有一项工作是用photoshop伪造发票,将金额10万的发票,改成100万,甚至更大的金额。修改以后用打印机打印出来,公司用于向银行贷款等。


说是项目部,其实就是旁边就是工地,工地的生活非常艰苦,管理人员还好一些,一线工人每天干的都是体力活,确实挺辛苦的,于是到了2014年底,就提出了离职。


离职之后,辗转就到了北京。


到了北京之后,找了一个主要做供应链的单位,对接的是外企,职位名称忘了,主要负责回复一些英文文件,偶尔也需要通过电话和老外做些沟通,问他们要一些文件什么的。


幸运的是认识了一个做前端的同事。这时候是2015年,这个同事高中没毕业,做前端,薪资当时是12000,让我很是羡慕,有时候开玩笑跟我说你也转前端吧。


2015年,当时正是前端市场非常火的时候,移动互联网正在兴头上,于是下定决心开始学习前端,从最简单的html标签,css样式,Javascript开始,一点一点的去背,去学习相关的知识,也从简单的仿站开始练习,各种布局,样式属性之类的,写完了就拿给他看,虽然写的不怎么样~


就这么持续了大概有一年吧~


转前端


好在功夫不负有心人,2016年开始试着找一些前端相关的工作,最终还是找到了一份工作,那时候还没有现在的各种框架。


记得第一份前端工作的项目是一个javaweb的项目,然后还有一个用appcan做的混合开发的移动端项目。


就这样从看别人的代码,到自己写一些代码,从纯粹的前端三剑客html+css+js,慢慢的写到了前端三大框架vue+react+ng 。


转眼间从事前端工作也这么些年了,越写越觉得自己对前端的兴趣越浓,因为它有很多新的东西,虽然我不一定每个知识点都能弄的明白,但是当你思考时,那种沉浸其中的感觉是妙不可言的。


我也感谢这个行业,虽然它不能让我大富大贵,但是它起码做到了这些年让我衣食无忧。


最后


单纯的从技术方向出发,前端的方向很多,web,小程序,客户端,移动端,每个领域都有很多值得探索的东西。


虽然我不是科班出身,但是在这些年的学习和探索中,它教会了我很多东西。


最后更多的想说的可能只有两个字:感谢


感谢那些帮助过我的人;


感谢那些年遇到的苦难;


也感谢自己的坚持~


希望每个人在自己的人生中都能得偿所愿~


谢谢~


作者:前端那些年
来源:juejin.cn/post/7162205132292096037
收起阅读 »

谁还没个靠bug才能正常运行的程序😌

web
最近遇到一个问题,计算滚动距离,滚动比例达到某界定值时,显示mask,很常见吧^ _ ^ 这里讲的不是这个需求的实现,是其中遇到了一个比较有意思的bug,靠这个bug才达到了正确效果,以及这个bug是如何暴露的(很重要)。 下面是演示代码和动图 <!DO...
继续阅读 »

最近遇到一个问题,计算滚动距离,滚动比例达到某界定值时,显示mask,很常见吧^ _ ^


这里讲的不是这个需求的实现,是其中遇到了一个比较有意思的bug,靠这个bug才达到了正确效果,以及这个bug是如何暴露的(很重要)。


下面是演示代码和动图


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.container {
width: 300px;
max-height: 300px;
background-color: black;
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
overflow-y: auto;
}
.child {
width: 260px;
height: 600px;
margin: 0px 20px;
background-color: pink;
position: relative;
}
.flag {
position: absolute;
width: 100%;
height: 25px;
background-color: blueviolet;
color: aliceblue;
text-align: center;
line-height: 25px;
font-size: 14px;
left: 0;
right: 0;
}
.top {
top: 0;
}
.bottom {
bottom: 0px;
}
</style>
</head>

<body>
<div class="container">
<div class="child">
<div class="flag top">top</div>
<div class="flag bottom">bottom</div>
</div>
</div>
</body>
</html>


20230927105849_rec_.gif




开始计算啦,公式:滚动比例 = 滚动距离 / 可滚动距离


滚动距离: $0.scrollTop


可滚动距离: $0.scrollHeight - $0.offsetHeight


即:scrollRatio = scrollTop / (scrollHeight - offsetHeight)


滚动到底部,计算结果是 300 / (600 - 300) = 1


image.png


我们需要拿scrollRatio某界定值(比如0.1)作大小的比较,计算是true还是false(用isShow = scrollRatio < 某界定值来保存)。


这里一切正常。




不正常的情况出现了


就是没有出现滚动条的情况,即.child的高度没有超过.container的高度时,把.child的高度设成.containermax-height,就没有滚动条了(下面讲的情景也都是没有滚动条的情况)。


image.png


这个时候再去计算,得到了NaN,以至于 NaN < 0.1 = false


image.png


因为isShow的预期就是false,所以一直都没有发现这个bug。




那么它是如何暴露的呢?


后来新的需求给.container加了border。演示一下加border,然后再去计算:


image.png


发现没,这时候$0.offsetHeight的高度把border的高度也算进去了,结果就成了true,这不是想要的结果 ❌。




然后就是一番查验


offsetHeight是一个元素的总高度,包括可见内容的高度、内边距(padding)、滚动条的高度(如果存在)以及边框(border)的高度。


而我们这里只需要可见的高度,就可以用到另一个属性了clientHeight


clientHeight是指元素的可见内容区域的高度,不包括滚动条的高度和边框的高度。它仅包括元素的内部空间,即内容加上内边距。


image.png


当然这也只是继续使除数为0,然后得到结果为NaN,不过bug已经暴露出来了,后面就是一些其他的优化啦~




总结 + 复习(盒模型 box-sizing)


发现没有,offsetHeightclientHeight的区别,就像盒模型中的标准盒模型怪异盒模型的区别:


box-sizing: content-box(默认,标准盒模型):宽度和高度的计算值都 不包含 内容的边框(border)和内边距(padding)。添加padding和border时, 使整个div的宽高变大。


box-sizing: border-box(怪异盒模型):宽度和高度的计算值都 包含 内容的边框(border)和内边距(padding)。添加padding和border时, 不会 使整个div的宽高变大。


这样讲是不是加深一下对这两种属性的印象


^ - ^


作者:aomyh
来源:juejin.cn/post/7283087306603823116
收起阅读 »

一年空窗期后我是如何准备面试的?

web
在此之前我是自由职业者,满打满算一年空窗期,在被动收入不稳定,心想还是需要一份收入来维持日常生活开销,再去考虑打造自己的额外收入。 前前后后从准备到上岸历时一个半月,今天从三个方面分享这个过程我做了什么? 心态 做事情之前,心态很重要,我遇事很少否定自己,在...
继续阅读 »

在此之前我是自由职业者,满打满算一年空窗期,在被动收入不稳定,心想还是需要一份收入来维持日常生活开销,再去考虑打造自己的额外收入。



前前后后从准备到上岸历时一个半月,今天从三个方面分享这个过程我做了什么?


心态


做事情之前,心态很重要,我遇事很少否定自己,在我看来,别人可以做到的,自己也可以,虽然一年空窗,基本上不接触技术,写代码量远不如以前,但又不是要上天或者造原子弹,取决于谁执行力强,谁付出的时间多,仅此而已。


换作以前,相信大部分的同学去找半个月都可以入职自己期望的岗位,看了一下网上的情绪,行情在这个环境下的确蛮消极的,很多人找了几个月都没有上岸的,当然我自己也有感受到,简历丢出去之后没有声音,并且在各大招聘网站上坑位也减少了,相比两三年前如日中天的行情,难免会有这类情绪。


但我没有那么焦虑,为什么呢?其一是我心态比较好,其二是跟我的定位有关。


定位


第一个是我要找的岗位定位为中高级开发,而这类人在市场上来看一直都是稀缺资源,其他行业也如此。


第二个是薪酬范围定位在20k-25k范围,给不到我会觉得工作没劲,累点没关系,主要还是相信自己可以胜任。


第三个是前期投几个低于期望值的试试水,了解一下目前行情顺便找找感觉。


所以,接下来我只需要把目标定位在寻找中高级开发岗位即可,完善自己达到这个能力要求,下面是行动计划,细看下来你会发现这又是个PDCA


计划


我把计划分为这几个模块:


1. 简历优化


我一开始是不会写简历的,因为中间没有跳过槽,也没定时更新,所以就随便拿了以前的模板改了改时间和项目就开始投了,简历回复少不说,即使有机会面试了也没有把简历提到的讲清楚,结果可想而知。


后面想想不行,虽然没写过,但是我会看简历啊,之前带团队有时候一天要看上百份简历,大概知道面试官青睐哪些人才,优化之后断断续续才有面试。


其次是我在面试过程结束时问面试官哪些地方还需要提升的,不少也会反馈简历问题,诸如:



  • 管理工作内容太笼统了,看不出具体做了什么

  • 没有说清楚空窗期做了什么

  • 没有体现出你在项目中做了什么

  • ......


知道自己问题之后,前后迭代了大概十几个版本,越到后面的质量越高,直至我入职之后,还有一个目标企业发来邀请。


2. 技能要求


前端领域涉及到这么多技能,需要有方向进行准备,分享一下我是如何分类:


基础:



  • 前端三大件:HTML、CSS、JS

  • 主流框架:Vue2/Vue3、React

  • 状态管理:Vuex、redux、mobox

  • 构建工具:webpack、Vue-cli、Vite、gulp

  • 前端安全:xss、csrf、cors 常见问题和防御措施


进阶:



  • JS运行机制、事件轮询机制、浏览器运行原理

  • 前端性能监控、前端异常监控如何做?

  • 前端工程化体系包含哪些

  • 前端性能优化手段:页面性能、构建性能、代码性能

  • Vue、React核心原理

  • 基础算法数据结构

  • Http协议


面对上面的技术基础类,主要是刷官方文档+常见面试题,这些更多是概念性的东西,在这里就不多说了,相信大家手上多少都有八股文资料,如果没有可以私信我。


而面对进阶类,首先总结自己项目中用到了哪些,吃透它。其次,面对不太熟悉的板块如HTTP网络,我会通过专栏学习或者一些讲得好的课程来弥补。


除了上面的方法,还有一种我常用的技巧来覆盖知识盲区,就是下面要说的模拟面试,几乎适用于任何技能面试。


3. 模拟面试


这里要说的模拟面试并不是找一些大佬一对一模拟训练,而是换位思考(当然能够模拟面试效果更好啦~)。


即把自己想象成面试官,在考察某一个知识点的时候,你会问自己什么问题呢?


举2个栗子🌰


对于用Vue的同学,我会问:



  • vue diffreact diff有什么区别?

  • 为什么v-for中建议带:key,有什么情况下是可以不带的?

  • 写过组件或者插件吗,有什么注意点?

  • vue-router原理是什么


结合一些热门的话题,我会问:



  • vue2vue3对比,你觉得主要区别是什么?

  • vue2vue3在性能优化做了什么?两者的构建产物有什么区别?

  • 如果你去学vue3,你会从哪里开始,怎么学?


除了以上我给自己虚构的问题之外,还有诸如vue生命周期啊、组件通信啊等等基础肯定是要会的,我会刷文档或虚构题目,这些比较简单,搞懂就行了。


对于设计模式,我也问了自己几个问题:



  • 你知道的设计模式有哪些,知道他们的应用场景吗(解决了什么问题)?

  • 在工作中用到的设计模式有哪些?说说它们的优劣势

  • Vue中用了哪些设计模式?

  • 观察者和发布订阅有什么不同?


基本上这个薪酬范围的设计模式,搞懂了以上问题大差不差。


再来说说这种方式有什么优势?


首先,问题是通过我们自身思考提出并主动寻求解决的,这本身已经存在闭环了,有利于我们理解一个知识点。其次,我们思考提出某个问题,意味着大脑🧠的神经元网络中有存在某些游离神经节点,它没有被连接到一起,随着提出并解决的问题越多,连接起来的网络就越大,这就形成了所谓的知识网络,相比没有目的刷题,它的持久性更强,更能抗遗忘。


总结


结束之前,再分享面试过程中的一个小插曲,当时面了一家小企业,终面的时候面试官问我期望薪酬,就报了18k,但是面试官说给不到,17k考不考虑?我当时没有回绝,就说回去考虑一下。


回去考虑一番之后,我根据当时岗位给到的薪资范围,加上当时家里事情比较多,想先稳定下来再考虑其他的,打算接了这个offer准备上班,突然闹了个乌龙,HR说老板那边重新定了价,只能给到16k,我说还能这么操作?这不明摆着欺负老实人嘛?


想了想如果接了这个offer,岂不是比之前离职时更低,更别说对比以前的同事了。心里忍不下这口气,以至于那两周,每天都撸到一点钟,功夫不负有心人,最后顺利上岸了!


分享几点个人觉得比较关键的:



  • 永远相信自己,心态很重要,不仅仅面试,它贯穿人的一生

  • 简历真实,不玩心思,例如空窗期这种,如实说明

  • 吃透简历内容,不留疑点

  • 面试过程中不着急回答问题,可以先澄清问题动机,不要为了回答而拼凑答案

  • 前面几次不通过没关系,但一次要比一次好


以至于如何备战高级开发,等我升级了再来分享~


最后,祝愿所有航海者都能够顺利靠岸!!!


注:由于最近比较多朋友私信我咨询简历优化建议或者八股文,可以加我的微信followJavaScript,丢简历过来即可,备注“掘金”。


作者:寻找奶酪的mouse
来源:juejin.cn/post/7285915718666944547
收起阅读 »

从钱塘江边到余杭塘河

把两个月前 钱江边的聚会小酌 成功移到余杭塘河。三巡五味后,一行三人在偌大的校园走着聊着。在滨江某知名支付公司的测试经理,感叹管理层间人浮于事站队排位;在阿里的前端程序员,困顿于今年未能升P竞争压力山大。而我,定位为服务端却在走全栈路线,也在纠结更换技术栈,看...
继续阅读 »

把两个月前 钱江边的聚会小酌 成功移到余杭塘河。三巡五味后,一行三人在偌大的校园走着聊着。在滨江某知名支付公司的测试经理,感叹管理层间人浮于事站队排位;在阿里的前端程序员,困顿于今年未能升P竞争压力山大。而我,定位为服务端却在走全栈路线,也在纠结更换技术栈,看不清寻不到一个更满意充满想象力的未来。


似乎吐槽和患得患失,多过幸福和豪气干云。而回想这一切刚开始时的忐忑不安踌躇满志,绝没想到,会在一个雨过方晴的深夜,有这样一番对话。


4年前的今天,我离开家乡,从济南坐上一节南下的橙皮车。当慢慢悠悠走过十余个小时,从微山湖夕阳落下到寒山寺晨钟响彻,我来到了曾一见倾心风云际会的魔都,一只怯怯什么也不会的菜鸟,正式开启了必将色彩斑斓的职业生涯。


此前几天,第一次用58找房,还不知有那么多套路。我发了100多条短信,大多如泥牛入海不见回声。偶有的几条,也是中介答复:“便宜的前几天租出去了,还有套价格贵一些的”。我终在豆瓣小组,在车来到济宁时收到了这条回复。满是欣喜打开,充满失望关闭。


此前几个月,我放弃割舍许多,不曾随我的相当多数同学,去那几家中字头央企,也不曾青眼相向,可以一辈子安稳无虞的公务员事业编,而选择了一家未曾听闻的“互联网”公司。我知道,当我听到这三个字,郁结于胸口怏怏不乐的大石顿被粉碎,我知道,我的第一家公司,不会再有其他。然而回宿舍后我做的第一件事,是查证这家公司是否为诈骗组织皮包公司。我也在暗暗忧心,空有一腔热爱,就真能做得好吗?兴趣真的可以打败科班无视专业?


我在仙霞路工程技术大学的国家级创业园,度过了终生难忘的两年,也完美回答了上面的疑惑。又在两年前魔都落户安家周折太巨,选择如候鸟,随潮离沪来杭。


四年里,我以平均一年一家的频率,换过四家公司,薪资较最初翻转500%。四年前,创新创业之风荡涤全国,o2o风云乍起,p2p方兴未艾。而今,内外部趋势交织,猪从风口跌落,独角沦为毒角。我也面临职业和薪资门卡:我是否还能心平静气在一家单位多呆几年,沉淀而不颓废?我如何还能保持在基数较大的情况下,使薪资以较高速率增长?我有哪些要学哪些要放,我要走哪条职业路线?…


实在有太多要学,我给自己的目标是:除去工作所用,还要试图从数学和物理角度,理解背后机理;要紧跟潮流,对新出现的技术和事物保有热切好奇;要外语足够优秀,能无障碍阅读英语文档。…


从没想过,有一天“学习”也会成为甜蜜负担。但我清楚,并不是所有的职业,所有的从业者,都有我们这样可以不断学习不断提高,如果愿意可以练就一身本事的机会。我会在无边学海中偶尔迷失,但却清楚,一样技能可以一陈不变从生到死,那会让我因无成就感和提高的满足而生不如死。


我的感触是,从毕业到30岁,真的太短太短了。只有夜以继日只有目不见睫。几年里,太多的熬夜让我皮肤不复当年紧致滑腻,太多的久坐让我发福增重,或许还有双眼疲劳颈椎酸痛。…我会注意保重,但当解决一个问题,当当搞懂一样东西,那种感觉,像一个木匠做出一件工艺品,抖落浑身木屑站起时的满足。所有的疲乏,所有的周折劳累,都将烟消云散。


回瞰这几年,我总会在他们看不见的地方,感谢B哥L哥H哥,但我还想感谢并告诫自己:我很勇敢,也因而幸运,在一个几百年不遇的信息时代,如果我还因循守旧患得患失,畏葸不前混吃等死,那既是对自己得辜负,也是对时代得辜负。


作者:fliter
来源:juejin.cn/post/7281651969247166527
收起阅读 »