注册
环信即时通讯云

环信即时通讯云

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

环信开发文档

环信FAQ

环信FAQ

集成常见问题及答案
RTE开发者社区

RTE开发者社区

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

技术讨论区

技术交流、答疑
资源下载

资源下载

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

iOS Library

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

Android Library

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

第三方库并不是必须的

iOS
前言 我在Lyft的八年间,很多产品经理以及工程师经常想往我们 app 里添加第三方库。有时候集成一个特定的库(比如 PayPal)是必须的,有时候是避免去开发一些非常复杂的功能,有时候仅仅只是避免c重复造轮子。 虽然这些都是合理的考量,但使用第三方库的风险和...
继续阅读 »

前言


我在Lyft的八年间,很多产品经理以及工程师经常想往我们 app 里添加第三方库。有时候集成一个特定的库(比如 PayPal)是必须的,有时候是避免去开发一些非常复杂的功能,有时候仅仅只是避免c重复造轮子。


虽然这些都是合理的考量,但使用第三方库的风险和相关成本往往被忽视或误解。在某些情况下,风险是值得的,但是在决定冒险之前,首先要能够明确的定义风险。为了使风险评估更加的透明和一致,我们制定了一个流程来衡量我们将其集成到app有多大的风险。


风险


大多数大型组织,包括我们,都有某种形式的代码审查,作为开发实践的一部分。对这些团队来说,添加一个第三方库就相当于添加了一堆由不属于团队成员开发,未经审查的代码。这破坏了团队一直坚持的代码审查原则,交付了质量未知的代码。这给app的运行方式以及长期开发带来了风险,对于大型团队而言,更是对整体业务带来了风险。


运行时风险


库代码通常来说,对于系统资源,和app拥有相同级别的访问权限,但它们不一定应用团队为管理这些资源而制定的最佳实践。这意味着它们可以在没有限制的情况下访问磁盘,网络,内存,CPU等等,因此,它们可以(过度)将文件写入磁盘,使用未优化的代码占用内存或CPU,导致死锁或主线程延迟,下载(和上传!)大量数据等等。更糟糕的是他们会导致崩溃,甚至崩溃循环


其中许多情况直到 app 已经上架才被发现,在这种情况下,修复它需要创建一个新版本,并通过审核,这通常需要大量时间和成本。这种风险可以通过一个变量控制是否调用来进行一定程度的控制,但是这种方法也并非万无一失(看下文)。


开发风险


引用一个同事的话:“每一行代码都是一种负担”,对不是你自己写的代码而言,这句话更甚。库在适配新技术或API时可能很慢,这阻碍了代码开发,或者太快,导致开发的版本过高。


库在采用新技术或API时可能很慢,阻碍了代码库,或者太快,导致部署目标太高。每当 Apple 和 Google 每年发布一个新 OS 版本时,他们通常要求开发人员根据SDK的变化更新代码,库开发人员也必须这样做。这需要协调一致的努力、优先事项的一致性以及及时完成工作的能力。


随着移动平台的不断变化,以及团队(成员)也不是一成不变,这将会成为一个持续不断的风险。当被集成的库不存在了,而库又需要更新时,会花很多时间来决定谁来做。事实证明一旦一个库存在,就很少也很难被移除,因此我们将其视为长期维护成本。


商业风险


如同我上面所说,现代的操作系统并没有对 app 代码和库代码进行区分,因此除了系统资源之外,它们还可以访问用户信息。作为 app 的开发者,我们负责恰当的使用这部分信息,也需要为任何第三方库负责。


如果用户给了 Lyft app 地理位置授权,任何第三方库也将自动得获得授权。他们可以将那些(地理位置)数据上传到自己服务器,竞对服务器,或者谁知道还有什么地方。当一个库需要我们没有的权限时,那问题就更大了。


同样,一个系统的安全取决于其最薄弱的环节,但如果其中包含未经审核的代码,那么你就不知道它到底有多安全。你精心设计的安全编码实践可能会被一个行为不当的库所破坏。苹果和谷歌实施的任何政策都是如此,例如“你不得对用户追踪”。


减少风险


当对一个库(是否)进行使用评估时,我们首先要问几个问题,以了解对库的需求。


我们内部能做么?


有时候我们只需要简单的粘贴复制真正需要的部分。在更复杂的场景中,库与自定义后端通信,我们对该API进行了逆向,并自己构建了一个迷你SDK(同样,只构建了我们需要的部分)。在90%的情况下,这是首选,但在与非常特定的供应商或需求集成时并不总是可行。


有多少用户从该库中受益?


在一种情况下,我们正在考虑添加一个风险很大的库(根据下面的标准),旨在为一小部分用户提供服务,同时将我们的所有用户都暴露在该库中。 对于我们认为会从中受益的一小部分客户,我们冒了为我们所有用户带来问题的风险。


这个库有什么传递依赖?


我们还需要评估库的所有依赖项的以下标准。


退出标准是什么?


如果集成成功,是否有办法将其转移到内部? 如果不成功,是否有办法删除?


评价标准


如果此时团队仍然希望集成库,我们要求他们根据一组标准对库进行“评分”。下面的列表并不全面,但应该能很好地说明我们希望看到的。


阻断标准


这些标准将阻止我们从技术上或者公司政策上集成此库,在进行下一步之前,我们必须解决:


过高的 deployment target/target SDKs。 我们支持过去4年主流的操作系统(版本),所以第三方库至少也需要支持一样多。


许可证不正确/缺失。 我们将许可文件与应用捆绑在一起,以确保我们可以合法使用代码并将其归属于许可持有人。


没有冲突的传递依赖关系。 一个库不能有一个我们已经包含但版本不同的传递依赖项。


不显示它自己的 UI 。 我们非常小心地使我们的产品看起来尽可能统一,定制用户界面对此不利。


它不使用私有 API 。 我们不愿意冒 app 因使用私有 API 而被拒绝的风险。


主要关注点


闭源。 访问源代码意味着我们可以选择我们想要包含的库的哪些部分,以及如何将该源代码与应用程序的其余部分捆绑在一起。 对于我们来说,一个封闭源代码的二进制发行版更难集成。


编译时有警告。 我们启用了“警告视为错误”,具有编译警告的库是库整体质量(下降)的良好指示。


糟糕的文档。 我们希望有高质量的内联文档,外部”如何使用“文档,以及有意义的更新日志。


二进制体积。 这个库有多大?一些库提供了很多功能,而我们只需要其中的一小部分。尤其是在没有访问源码权限的情况下,这通常是一个全有或全无的情况。


外部的网络流量。 与我们无法控制的上游服务器/端点通信的库可能会在服务器关闭、错误数据被发回等时关闭整个应用程序。这也与我上面提到的隐私问题相同。


技术支持。 当事情不能正常工作时,我们需要能够报告/上报问题,并在合理的时间内解决问题。开源项目通常由志愿者维护,也很难有一个时间线,但至少我们可以自己进行修改。这在闭源项目是不可能的。


无法禁用。 虽然大多数库特别要求我们初始化它,但有些库在实例化时更“主动”,并且在我们不调用它的情况下可以自己执行工作。这意味着当库导致问题时,我们无法通过功能变量或其他机制将其关闭。


我们为所有这些(和其他一些)标准分配了点数,并要求工程师为他们想要集成的库汇总这些点数。虽然默认情况下,低分数并不难被拒绝,但我们通常会要求更多的理由来继续前进。


最后


虽然这个过程看起来非常严格,在许多情况下,潜在风险是假设的,但我们有我在这篇博文中描述的每个场景的实际例子。将评估记录下来并公开,也有助于将相对风险传达给不熟悉移动平台工作方式的人,并证明我们没有随意评估风险。


此外,我不想声称每一个第三方库本质上都是坏的。事实上,我们在Lyft使用了很多:RxSwiftRxJavaBugsnagSDKGoogle MapsTensorflow,以及一些较小的用于非常特定的用例。但所有这些要么都经过了充分审查,要么我们已经决定风险值得收益,同时对这些风险和收益的真正含义有了清晰的认识。


最后,作为一个专业开发人员提示:始终在库的API之上创建自己的抽象,不要直接调用它们的API。这使得将来替换(或删除)底层库更加容易,再次减轻了与长期开发相关的一些风险。


作者:Swift社区
链接:https://juejin.cn/post/7145368397096681485
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

所有开发者注意,苹果审核策略有变

iOS
这里每天分享一个 iOS 的新知识,快来关注我吧 访问敏感数据的 App 新规 苹果最近在 Apple Developer 上发了篇新闻公告,对需要访问用户敏感数据的 App 增加了审核要求。 这件事的缘由是苹果发现有一小部分 API 可能会被开发者滥用,通过...
继续阅读 »


这里每天分享一个 iOS 的新知识,快来关注我吧


访问敏感数据的 App 新规


苹果最近在 Apple Developer 上发了篇新闻公告,对需要访问用户敏感数据的 App 增加了审核要求。


这件事的缘由是苹果发现有一小部分 API 可能会被开发者滥用,通过信息指纹收集有关用户设备的信息。


早在今年 6 月的 WWDC23 上苹果就宣布,开发人员需要在其应用程序的隐私清单中声明使用某些 API 的原因,目前正式放出了这份需要声明的 API 列表。


新规详情


从今年(2023年)秋天开始,大概是 9 月中旬左右,如果你将你的 App 上传到 App Store Connect,你的应用程序使用到了需要声明原因的 API(也包括你引入的第三方 SDK),但是你没有在隐私清单文件中添加原因,那么 Apple 会给你发送一封警告性的邮件。


从 2024 年春季开始,大概是 3 月左右,没有在隐私清单文件中说明使用原因的 App 将会被拒审核。


需要声明原因的 API 有哪些?


1、NSUserdefaults 相关 API


这个 API 是被讨论最多争议最大的,因为几乎每个 App 都会用到,而且因为有沙盒保护,每个 app 的存储空间是隔离的,这都要申报理由,的确十分荒谬。


2、获取文件时间戳相关的 API

  • creationDate

  • modificationDate

  • fileModificationDate

  • contentModificationDateKey

  • creationDateKey

  • getattrlist(::::_:)

  • getattrlistbulk(::::_:)

  • fgetattrlist(::::_:)

  • stat

  • fstat(::)

  • fstatat(::::)

  • lstat(::)

  • getattrlistat(::::::)


3、获取系统启动时间的 API


大多数衡量 App 启动时间的 APM 库会用到这个 API。

  • systemUptime

  • mach_absolute_time()


4、磁盘空间 API

  • volumeAvailableCapacityKey

  • volumeAvailableCapacityForImportantUsageKey

  • volumeAvailableCapacityForOpportunisticUsageKey

  • volumeTotalCapacityKey

  • systemFreeSize

  • systemSize

  • statfs(::)

  • statvfs(::)

  • fstatfs(::)

  • fstatvfs(::)

  • getattrlist(::::_:)

  • fgetattrlist(::::_:)

  • getattrlistat(::::::)


5、活动键盘 API


这个 API 可以来确定当前用户文本输入的主要语言,有些 App 可能会用来标记用户。

  • activeInputModes

如何在 Xcode 中配置


由于目前 Xcode 15 正式版还没有发布,下边的操作是在 Beta 版本进行的。


在 Xcode 15 中隐私部分全部归类到了一个后缀为 .xcprivacy 的文件中,创建项目时默认没有生成这个文件,我们先来创建一下。


打开项目后,按快捷键 Command + N 新建文件,在面板中搜索 privacy,选择 App Pirvacy 点击下一步创建这个文件。



这个文件是个 plist 格式的面板,默认情况下长这样:



然后点击加号,创建一个 Privacy Accessed API TypesKey,这是一个数组,用来包含所有你 App 使用到需要申明原因的 API。



在这个数组下继续点击加号,创建一个 Item,会看到两个选项:

  • Privacy Accessed API Type:用到的 API 类型

  • Privacy Accessed API Reasons:使用这个 API 的原因(也是个数组,因为可能包含多个原因)



这两个 Key 都创建出来,然后在 Privacy Accessed API Type 一栏点击右侧的菜单,菜单中会列出上边提到的所有 API,选择你需要申报的 API,我这里就拿 UserDefault 来举例:



然后在 Privacy Accessed API Reasons 一览中点击加号,在右侧的选项框中选择对应的原因,每个 API 对应的原因都会列出来,可以到苹果的官方文档上查看这个 API 的原因对应的是哪个,比如 UserDefault 对应的是 CA92.1,我这里就选择这个:



到此,申报原因就完成了,原因不需要自己填写,直接使用苹果给出的选项就可以了,还是蛮简单的。


参考资料


[1]


公告原文: developer.apple.com/news/?id=z6…


[2]


需要在 App 内声明的 API 列表: developer.apple.com/documentati…


[3]


API 列表对应的原因: developer.apple.com/documentati…


作者:iOS新知
链接:https://juejin.cn/post/7267091810379759676
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Java仿抽奖系统

Java仿抽奖系统 前言 今天也是刚看完最近挺火的电影《孤注一掷》,也是亲眼的看到了,一个完整的家庭,是如何因为赌,而导致分崩离析,最后导致走向破碎的。 一旦涉及到电子的东西,很多东西都是变得可以控制的。这个作为程序员的我们是最清楚的,同时现在的反诈宣传,做的...
继续阅读 »

Java仿抽奖系统


前言


今天也是刚看完最近挺火的电影《孤注一掷》,也是亲眼的看到了,一个完整的家庭,是如何因为赌,而导致分崩离析,最后导致走向破碎的。


一旦涉及到电子的东西,很多东西都是变得可以控制的。这个作为程序员的我们是最清楚的,同时现在的反诈宣传,做的也是非常的到位,当时剧中哪位女警说的话,影响也非常的深刻。人都有贪心和不甘心,这也就是赌能真正抓住人的东西


好了不说那么多了,下面看一个简易的程序的代码实现


代码实现


首先我们定义一些常量


private static final int PRIZE_LEVELS = 4; // 奖品级别数量
private static final int[] PRIZE_AMOUNTS = {1, 10, 100, 1000}; // 奖品金额
private static final double[] WINNING_RATES = {10, 0, 0, 0}; // 中奖率

public static void main(String[] args) {
       // 设定中奖率
       double winningRate = 0.1;

       // 抽奖
       int prize = drawLottery(winningRate);

       // 发放奖品
       if (prize > 0) {
           System.out.println("恭喜你中奖了!奖金:" + prize + "元");
      } else {
           System.out.println("很遗憾,未中奖");
      }
  }

   // 抽奖方法
   private static int drawLottery(double winningRate) {
       Random random = new Random();
       int prize = 0;

       // 根据奖品级别逐级判断中奖
       for (int i = 0; i < PRIZE_LEVELS; i++) {
           // 生成0到1之间的随机数,判断是否中奖
           if (random.nextDouble() < winningRate * WINNING_RATES[i]) {
               prize = PRIZE_AMOUNTS[i];
               break;
          }
      }

       return prize;
  }
}

一个简单的抽奖程序。我们根据这个进行一些修改,更加的客观真实,我们加上已经有的金额和权重,让他更像是真正的赌。


我们加入权重以及自己的现金


private static double[] WEIGHTS;
// 自己的现金余额
static int cashBalance = 1000;

之后我们进行这样设计


   public static void main(String[] args) {
       // 计算权重
       calculateWeights();

       // 自己的现金余额
       int cashBalance = 1000;

       // 抽奖一次
       drawLottery(cashBalance);
  }

public static void calculateWeights() {
   WEIGHTS = new double[WINNING_RATES.length];
   double totalWeight = 0;

   // 计算总权重
   for (double rate : WINNING_RATES) {
       totalWeight += rate;
  }

   // 计算每个奖品级别的权重
   for (int i = 0; i < WEIGHTS.length; i++) {
       WEIGHTS[i] = WINNING_RATES[i] / totalWeight;
  }
}

public static void drawLottery() {
   Random random = new Random();
   double randomValue = random.nextDouble();

   int prizeIndex = 0;
   double cumulativeWeight = 0;

   // 根据随机值选择对应的奖品级别
   for (int i = 0; i < WEIGHTS.length; i++) {
       cumulativeWeight += WEIGHTS[i];
       if (randomValue <= cumulativeWeight) {
           prizeIndex = i;
           break;
      }
  }

   // 判断是否中奖
   if (random.nextDouble() <= WINNING_RATES[prizeIndex]) {
       int prizeAmount = PRIZE_AMOUNTS[prizeIndex];
       System.out.println("恭喜您中奖了!获得奖金:" + prizeAmount + "元");
       cashBalance += prizeAmount;
  } else {
       System.out.println("很遗憾,未中奖。");
  }

   // 更新现金余额
   cashBalance -= COST_PER_DRAW;
   System.out.println("抽奖后的现金余额:" + cashBalance + "元");
}

可以看出,我们这里规定的是20元抽奖一次,最高能达到1000元。


image-20230821164510692


运行一次后发现从原来的升值到了5000,


可是当你一旦陷入进去的话,只要我们稍微修改一下中奖率


image-20230821164625143


就会不断的去输。



赌博有害健康,需要我们每个人去

作者:小u
来源:juejin.cn/post/7270173541457723452
制止


收起阅读 »

别再用unload了:拥抱浏览器生命周期API

web
以前的前端页面很少有涉及页面的生命周期相关的话题,我们最熟悉的还是这几个load, beforeunload, unload 事件,用来监听页面加载完、离开页面之前、页面卸载事件并做一些相应的处理,比如保存需要持久化的数据,或者上报一些埋点数据。首先我们先理下...
继续阅读 »

以前的前端页面很少有涉及页面的生命周期相关的话题,我们最熟悉的还是这几个load, beforeunload, unload 事件,用来监听页面加载完、离开页面之前、页面卸载事件并做一些相应的处理,比如保存需要持久化的数据,或者上报一些埋点数据。首先我们先理下现有问题:


unload事件的问题


上面的这些事件(特别是unload)会有以下一些问题:



  • 用户主动触发的事件,无系统自动触发的一些状态监控

  • 无法稳定触发,特别是在手机端

  • 前进/后退时无法进入浏览器缓存(back/forward cache),导致用户来回切换时加载慢

  • 用户离开时埋点数据可能无法稳定上报

  • 无法追踪用户在页面上的完整生命周期


虽然现在计算机的算力硬件越来越强,但是现在我们同时需要处理的事情越来越多,开的Tab也就越来越多,一直被人诟病的比如chrome的疯狂吃内存的问题也很是头疼,接近顶配的电脑可能很快就被chrome给耗尽😂,所以现在的chrome版本会根据系统资源消耗程度来对不活动的Tab冻结或直接销毁来释放内存及减少电量的消耗。


针对这些问题现代浏览器提供了多个生命周期相关的事件来让开发者可以监听,并在触发时做相应处理。这里主要介绍几个最常用的而且对埋点相对比较重要的节点的生命周期事件。


页面是否可见: visibilitychange


visibilitychange事件涉及的场景会比较多,主要有用户切换tab导航到其他页面关闭Tab最小化窗口手机端切换app等,可在document上添加该事件,回调里通过document.visibilityState可以知道当前Tab是否隐藏了:


document.addEventListener(
'visibilitychange',
(e) => {
const isTabVisible = document.visibilityState === 'visible'
console.log('tab is visible: ', isTabVisible)
},
{
capture: true,
}
)

以上事件需要在捕获阶段进行监听(包括下面讲到的其他相关事件),避免被业务代码阻止冒泡,而且有些是window层面的,没有冒泡的阶段,所以需要在capture阶段执行。


页面的加载与离开: pageshow/pagehide


首先,这事件名是真的很容易让人理解错😭(pageshow/pagehide感觉才是上面visibilitychange所表示的意义)。


pageshow主要在页面新加载或被浏览器冻结后重新解冻时触发, 可通过e.persisted来确定是如何加载的:


window.addEventListener(
'pageshow',
(e) => {
// true 为之前冻结现在解冻,false相当于重新加载或新加载
console.log('e.persisted: ', e.persisted)
},
{
capture: true,
}
)

pagehide本质上是对unload事件的真正替换且具有更稳定的触发时机,我们可以在这里对一些埋点事件或者其他一些小批量的数据进行上报(sendBeacon或fetch, 下面讲到),这里还有个需要注意的是visibilitychange触发范围更广,也就是页面进来和离开时也会触发,和pageshow/pagehide同时触发(都触发时在这2个之前),所以如果业务需要区分页面离开还是用户仅仅是切换tab时,需要在不同的事件回调里做不同的处理。


页面离开时的数据上报


当需要在用户离开页面时(pagehide触发时)稳定的上报一些数据,我们一般会使用navigator.sendBeacon()方法:


// queued = true 说明浏览器接收请求,马上会上报出去,false的话可能业务要做其他处理
const queued = navigator.sendBeacon('/api', data)

当然sendBeacon有大小限制,一般在64KB以下,超出很可能会失败,所以我们在这里上报时要控制大小,如果数据量较大,建议提前上报一部分,比如在visibilitychange时(用户切换tab时)先上报一部分,确保只留64KB以下的数据放到最后。
除了sendBeacon之外我们还可以用fetch上报,通过设置keepalive: true来达到sendBeacon一样的效果,当然也有一样的大小限制,这里可以做兼容处理:


function send(body) {
if (navigator.sendBeacon) {
navigator.sendBeacon('/api', body)
return
}
fetch('/api', {
body,
method: 'POST',
keepalive: true
})
}

其他生命周期事件


除了上面常用的生命周期之外,浏览器的生命周期API还提供了页面的其他状态如:freeze, resume,freeze一般在用户导航到其他页面并且满足进入back/forward cache的条件时,或者用户在其他tab,浏览器根据系统资源自动使其进入冻结状态,当用户通过浏览器的返回按钮或重新切回到该Tab时,会触发resume事件说明页面继续,紧接着会触发pageshow事件,说明页面又进来了。关于更多的说明可以参考Page Lifecycle API


电脑息屏、休眠时


还有一些场景是电脑休眠或者只是关闭屏幕时,页面的生命周期以及页面里的一些定时器会有哪些变化?


休眠比较简单,定时器、网络连接这些都会被暂停,如果不想丢失数据,需要在pagehide做处理;息屏时可在document.visibilityState !== 'visible'时做处理,相当于页面不可见了,而且页面里的定时器不会被停掉但是可能会被浏览器延时处理,比如正常代码里是5s执行一次,此时可能会变成30s或者1min执行一次来节省资源。


总结


以上主要介绍了如何使用现代浏览器提供的生命周期API来在页面的不同阶段做相应的处理,pagehide事件主要用来替换unload, 关于beforeunload事件在有些应用中还是需要的,但是我们应该有选择性的添加该事件及在合适的时间移除监听该事件,比如未保存的数据已经保存完毕时可以移除,当又有新改动时再监听。


还有很多其他的生命周期事件可以让开发者能对用户在页面里/外的整个生命周期有更好的理解,以此来分析并提升网站的整体体验。笔者后面会写一篇如何跟踪用户在浏览器里的同一tab/不同tab之间的来回操作、记录并上报,并能够在后台进行session回放的文章,通过这些扩展能力相较于纯粹的埋点能更好地理

作者:jasonboy7
来源:juejin.cn/post/7269983642155663419
解用户行为以此来优化产品体验🍻。

收起阅读 »

一个写了3年半flutter的小伙,突然写了2个月uniapp的感悟!

前言 因为某些原因,在过去的三年半时间,我除了flutter之外,很少接触其他的框架,期间除了学习了Android(主要是Kotlin、jetpack)、GoLang Gin之外基本上很少接触其他的框架。而在最近的两个月,突然来了一个要求用uniapp实现的项...
继续阅读 »

前言


因为某些原因,在过去的三年半时间,我除了flutter之外,很少接触其他的框架,期间除了学习了Android(主要是Kotlin、jetpack)、GoLang Gin之外基本上很少接触其他的框架。而在最近的两个月,突然来了一个要求用uniapp实现的项目,在接下这个前,我是有些抵触的。第一点是觉得自己短期内去学一个新的框架,学到的东西不足以完成整个项目,第二点是不想脱离舒适圈。当然,最后我还是选择了直面困难,不然您也看不到这篇文章了🤣。


本文更多的是帮助您解决是否要学习uni-app或flutter框架的这一问题,以及两个框架的一些代码对比。如果您想要判断的是一个新项目该使用哪个框架,那么本文就不是很合适了~


跨平台层面的对比感悟


在Flutter刚出来的这几年,经常会在各种跨平台框架对比的文章下,看到将其与uni-app进行比较。当时我也没有在意太多,以为uni-app也是个差不多的“正经”跨平台框架,但当我打开uni-app官网的时候,我震惊了,因为我看到了这样一句话:一套代码编到15个平台,这不是梦想。我瞬间就傻眼了,这么nb?Flutter不也才横跨六大平台 ?在仔细一想,不对啊,这哪来的15个平台?再仔细一看,然后我的心中只剩下一万个省略号了,横跨一堆小程序平台是吧...



学习成本的对比感悟


1. 开发语言的不同

Flutter,要求开发者学习dart,了解dart和flutter的API,最好还会写点原生。而uni-app只需要学Vue.js,没有附加专有技术。所以从学习一个框架来看,很明显uni-app的学习成本很低。而从我个人的角度去分析,当年我只是一个刚入编程世界的菜鸡中的菜鸡,只学了半年的html+css+js和半年的java。抛开学了1个月的SpringBoot,Flutter可以算是我学习的第一个框架,当时我是直接上手学的Flutter,没有去单独学习dart,因为和java很相似。个人觉得学习成本也还好,如果你喜欢这个框架的话~而最近两个月学习uni-app,我也确实是感受到了学习成本很低,基本上看了看文档,就直接上手了,很多组件的名字也是和flutter大差不差。就是写css有点难受🤣,好在flex布局和flutter的rowcolumn用法一样,基本上半小时就能把基本的、简单的页面布局写好了。


2. 第三方插件&社区氛围

截至目前2023.7,flutter在github上有155K的star,uni-app有着38.4K的star。从star的数量也可以看出一个框架的热度,很明显,flutter是远高于uni-app的(毕竟uni-app的主要使用场景还是在国内小程序中)。对于第三方插件呢Flutter有着pub.dev,uni-app有插件市场,但相比Flutter呢可能略显不足。


3. 开发工具的使用

Flutter可以选择vscode或者android studio等来进行开发,uni-app可以选择HBuilderX,当然也可以使用vscode,用什么开发工具其实大差不差,如果你一直使用vscode,那么你对工具的使用会更加的熟悉,而如果你和我一样,用的是android studio,再去使用HBuilderX,说实话,有点点难受...例如我最常用的Alt+回车(提示),crtl+alt+l(代码格式化)。当然,反过来也是一样的(●'◡'●)


编码实现对比


1. 布局区别


  • 代码整体结构:Flutter使用Widget层级嵌套来构建用户界面,也是被很多人所不喜欢的嵌套地狱(这一点因人而异,根据自己的习惯和代码风格)。 uni-app 使用 Vue.js 的组件化布局方式,templatestylescripttemplate 定义了组件的 HTML 结构,style 定义了组件的样式,script 定义了组件的行为。

  • 布局原理区别:Flutter 中的布局是基于约束的,可以使用Constraints来控制小部件的最大和最小尺寸,并根据父级小部件的约束来确定自身的尺寸。uni-app则是,可以使用类似于 CSS 中 Flex 弹性布局的方式来控制组件的排列和布局。通过设置组件的样式属性,如 display: flexflexjustify-content 等,可以实现垂直和水平方向上的灵活布局。当然flutter也有和flex差不多的rowcolumn

  • 自定义布局:Flutter支持自定义布局,可以通过继承 SingleChildLayoutDelegateMultiChildLayoutDelegate 来实现自定义布局,而uni-app目前并没有直接提供类似的专门用于自定义布局的机制,不过uni-app常见的做法是创建一个自定义组件,并在该组件的 template 中使用各种布局方式、样式和组件组合来实现特定的布局效果。


2. 状态管理的区别

Flutter 提供了内置的状态管理机制,最常见的就是通过setState来管理小部件的状态,uni-app是利用Vue.js的响应式数据绑定和状态管理,通过 data 属性来定义和管理组件的状态。


3. 开发语言的区别与联系

区别:众所周知,JavaScript 是一门弱类型的语言,而 Dart 是强类型的语言(dart也支持一些弱类型,Dart中弱类型有var, Object 以及dynamic)。Dart有类和接口的概念,并支持面向对象编程,如果你喜欢 OOP 概念,那么你会喜欢使用 Dart 进行开发,此外,它还支持接口、Mixin、抽象类和静态类型等,这一点对写过java的朋友很友好,而JavaScript则支持基于原型的面向对象编程。Dart和JavaScript还有一个重要的区别就是:Dart是类型安全的,使用AOT和JIT编译器编译。


联系:从一个学习这个两个语言的角度去看, 两者都支持异步编程模型,如 Dart 的 async/await和 JavaScript 的 Promiseasync/await,这就非常友好了。


4. 一个简单的计数器例子,更好的理解他们直接的区别以及相关的地方:


Flutter代码:


import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
)
;
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'
You have pushed the button this many times:',
)
,
Text(
'$_counter',
style:
Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton:
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: '
Increment',
child: const
Icon(Icons.add),
),
)
;
}
}

uniapp代码:


<template>
<view class="container">
<text class="count">{{ count }}text>
<view class="buttons">
<button class="btn" @tap="incrementCounter">+button>
view>
view>
template>

<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
incrementCounter() {
this.count++;
},
},
};
script>

<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100vh;
background-color: #f0f0f0;
}

.count {
display: flex;
justify-content: center;
align-items: center;
font-size: 48px;
font-weight: bold;
height: 100%;
}

.buttons {
display: flex;
width: 100vw;
flex-direction: row;
justify-content: flex-end;
}

.btn {
width: 108rpx;
height: 108rpx;
font-size: 24px;
display: flex;
justify-content: center;
align-items: center;
margin: 8px;
background-color: #2196F3;
color: #fff;
border-radius: 50%;
}
style>

总结


从App开发的角度来看,uni-app的最大价值在于让国内庞大的Vue开发群体也能够轻松地开发“高性能”的App,不用去承担flutter或react native的学习成本,短时间内开发一款简单的偏展示类的app的话,uni-app肯定是首选,小公司应该挺受益的。再加上uni-app可以同时开发多端小程序,就足以保证在国内有足够的市场。但是稍微有点动效或者说有video、map之类的app,那么要慎重考虑,个人觉得挺限制的。不过很多时候技术并不是一个项目选型第一标准,适合才是,uni-app很适合国内,毕竟试错成本低...


注:本文仅为一个写了几年flutter小伙,突然写了2个月uniapp的感悟,存在一定个人主观,有错误欢迎指出😘

作者:编程的平行世界
来源:juejin.cn/post/7261162911615926331

收起阅读 »

优化重复冗余代码的8种方式

前言 大家好,我是田螺。好久不见啦~ 日常开发中,我们经常会遇到一些重复代码。大家都知道重复代码不好,它主要有这些缺点:可维护性差、可读性差、增加错误风险等等。最近呢,我优化了一些系统中的重复代码,用了好几种的方式。感觉挺有用的,所以本文给大家讲讲优化重复代码...
继续阅读 »

前言


大家好,我是田螺。好久不见啦~


日常开发中,我们经常会遇到一些重复代码。大家都知道重复代码不好,它主要有这些缺点:可维护性差、可读性差、增加错误风险等等。最近呢,我优化了一些系统中的重复代码,用了好几种的方式。感觉挺有用的,所以本文给大家讲讲优化重复代码的几种方式。

  • 抽取公用方法
  • 抽个工具类
  • 反射
  • 泛型
  • 继承和多态
  • 设计模式
  • 函数式
  • AOP

1. 抽取公用方法


抽取公用方法,是最常用的代码去重方法~


比如这个例子,分别遍历names列表,然后各自转化为大写和小写打印出来:


public class TianLuoExample {

public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "TianLuo");

System.out.println("Uppercase Names:");
for (String name : names) {
String uppercaseName = name.toUpperCase();
System.out.println(uppercaseName);
}

System.out.println("Lowercase Names:");
for (String name : names) {
String lowercaseName = name.toLowerCase();
System.out.println(lowercaseName);
}
}
}

显然,都是遍历names过程,代码是重复的,只不过转化大小写不一样。我们可以抽个公用方法processNames,优化成这样:


public class TianLuoExample {

public static void processNames(List<String> names, Function<String, String> nameProcessor, String processType) {
System.out.println(processType + " Names:");
for (String name : names) {
String processedName = nameProcessor.apply(name);
System.out.println(processedName);
}
}

public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "TianLuo");

processNames(names, String::toUpperCase, "Uppercase");
processNames(names, String::toLowerCase, "Lowercase");
}
}

2. 抽工具类


我们优化重复代码,抽一个公用方法后,如果发现这个方法有更多共性,就可以把公用方法升级为一个工具类。比如这样的业务场景:我们注册的时候,修改邮箱,重置密码等,都需要校验邮箱


实现注册功能时,用户会填邮箱,需要验证邮箱格式


public class RegisterServiceImpl implements RegisterService{
private static final String EMAIL_REGEX =
"^[A-Za-z0-9+_.-]+@(.+)$";

public boolean registerUser(UserInfoReq userInfo) {
String email = userInfo.getEmail();
Pattern pattern = Pattern.compile(EMAIL_REGEX);
Matcher emailMatcher = pattern.matcher(email);
if (!emailMatcher.matches()) {
System.out.println("Invalid email address.");
return false;
}

// 进行其他用户注册逻辑,比如保存用户信息到数据库等
// 返回注册结果
return true;
}
}

密码重置流程中,通常会向用户提供一个链接或验证码,并且需要发送到用户的电子邮件地址。在这种情况下,也需要验证邮箱格式合法性


public class PasswordServiceImpl implements PasswordService{

private static final String EMAIL_REGEX =
"^[A-Za-z0-9+_.-]+@(.+)$";

public void resetPassword(PasswordInfo passwordInfo) {
Pattern pattern = Pattern.compile(EMAIL_REGEX);
Matcher emailMatcher = pattern.matcher(passwordInfo.getEmail());
if (!emailMatcher.matches()) {
System.out.println("Invalid email address.");
return false;
}
//发送通知修改密码
sendReSetPasswordNotify();
}
}

我们可以抽取个校验邮箱的方法出来,又因为校验邮箱的功能在不同的类中,因此,我们可以抽个校验邮箱的工具类


public class EmailValidatorUtil {
private static final String EMAIL_REGEX =
"^[A-Za-z0-9+_.-]+@(.+)$";

private static final Pattern pattern = Pattern.compile(EMAIL_REGEX);

public static boolean isValid(String email) {
Matcher matcher = pattern.matcher(email);
return matcher.matches();
}
}

//注册的代码可以简化为这样啦
public class RegisterServiceImpl implements RegisterService{

public boolean registerUser(UserInfoReq userInfo) {
if (!EmailValidatorUtil.isValid(userInfo.getEmail())) {
System.out.println("Invalid email address.");
return false;
}

// 进行其他用户注册逻辑,比如保存用户信息到数据库等
// 返回注册结果
return true;
}
}

3. 反射


我们日常开发中,经常需要进行PO、DTO和VO的转化。所以大家经常看到类似的代码:


    //DTO 转VO
public UserInfoVO convert(UserInfoDTO userInfoDTO) {
UserInfoVO userInfoVO = new UserInfoVO();
userInfoVO.setUserName(userInfoDTO.getUserName());
userInfoVO.setAge(userInfoDTO.getAge());
return userInfoVO;
}
//PO 转DTO
public UserInfoDTO convert(UserInfoPO userInfoPO) {
UserInfoDTO userInfoDTO = new UserInfoDTO();
userInfoDTO.setUserName(userInfoPO.getUserName());
userInfoDTO.setAge(userInfoPO.getAge());
return userInfoDTO;
}

我们可以使用BeanUtils.copyProperties() 去除重复代码BeanUtils.copyProperties()底层就是使用了反射


    public UserInfoVO convert(UserInfoDTO userInfoDTO) {
UserInfoVO userInfoVO = new UserInfoVO();
BeanUtils.copyProperties(userInfoDTO, userInfoVO);
return userInfoVO;
}

public UserInfoDTO convert(UserInfoPO userInfoPO) {
UserInfoDTO userInfoDTO = new UserInfoDTO();
BeanUtils.copyProperties(userInfoPO,userInfoDTO);
return userInfoDTO;
}

4.泛型


泛型是如何去除重复代码的呢?给大家看个例子,我有个转账明细和转账余额对比的业务需求,有两个类似这样的方法:


private void getAndUpdateBalanceResultMap(String key, Map<String, List> compareResultListMap,
List balanceDTOs
) {
List<TransferBalanceDTO> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
tempList.addAll(balanceDTOs);
compareResultListMap.put(key, tempList);
}

private void getAndUpdateDetailResultMap(String key, Map<String, List> compareResultListMap,
List detailDTOS
) {
List<TransferDetailDTO> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
tempList.addAll(detailDTOS);
compareResultListMap.put(key, tempList);
}

这两块代码,流程功能看着很像,但是就是不能直接合并抽取一个公用方法,因为类型不一致。单纯类型不一样的话,我们可以结合泛型处理,因为泛型的本质就是参数化类型.优化为这样:


private  void getAndUpdateResultMap(String key, Map<String, List> compareResultListMap, List accountingDTOS) {
List tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
tempList.addAll(accountingDTOS);
compareResultListMap.put(key, tempList);
}

5. 继承与多态


假设你正在开发一个电子商务平台,需要处理不同类型的订单,例如普通订单和折扣订单。每种订单都有一些共同的属性(如订单号、购买商品列表)和方法(如计算总价、生成订单报告),但折扣订单还有特定的属性和方法


在没有使用继承和多态的话,会写出类似这样的代码:


//普通订单
public class Order {
private String orderNumber;
private List products;

public Order(String orderNumber, List products) {
this.orderNumber = orderNumber;
this.products = products;
}

public double calculateTotalPrice() {
double total = 0;
for (Product product : products) {
total += product.getPrice();
}
return total;
}

public String generateOrderReport() {
return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
}
}

//折扣订单
public class DiscountOrder {
private String orderNumber;
private List products;
private double discountPercentage;

public DiscountOrder(String orderNumber, List products, double discountPercentage) {
this.orderNumber = orderNumber;
this.products = products;
this.discountPercentage = discountPercentage;
}

public double calculateTotalPrice() {
double total = 0;
for (Product product : products) {
total += product.getPrice();
}
return total - (total * discountPercentage / 100);
}
public String generateOrderReport() {
return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
}
}

显然,看到在OrderDiscountOrder类中,generateOrderReport() 方法的代码是完全相同的。calculateTotalPrice()则是有一点点区别,但也大相径庭。


我们可以使用继承和多态去除重复代码,让DiscountOrder去继承Order,代码如下:


public class Order {
private String orderNumber;
private List products;

public Order(String orderNumber, List products) {
this.orderNumber = orderNumber;
this.products = products;
}

public double calculateTotalPrice() {
double total = 0;
for (Product product : products) {
total += product.getPrice();
}
return total;
}

public String generateOrderReport() {
return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
}
}

public class DiscountOrder extends Order {
private double discountPercentage;

public DiscountOrder(String orderNumber, List products, double discountPercentage) {
super(orderNumber, products);
this.discountPercentage = discountPercentage;
}

@Override
public double calculateTotalPrice()
{
double total = super.calculateTotalPrice();
return total - (total * discountPercentage / 100);
}
}

6.使用设计模式


很多设计模式可以减少重复代码、提高代码的可读性、可扩展性.比如:



  • 工厂模式: 通过工厂模式,你可以将对象的创建和使用分开,从而减少重复的创建代码

  • 策略模式: 策略模式定义了一族算法,将它们封装成独立的类,并使它们可以互相替换。通过使用策略模式,你可以减少在代码中重复使用相同的逻辑

  • 模板方法模式:模板方法模式定义了一个算法的骨架,将一些步骤延迟到子类中实现。这有助于避免在不同类中重复编写相似的代码


我给大家举个例子,模板方法是如何去除重复代码的吧,业务场景:



假设你正在开发一个咖啡和茶的制作流程,制作过程中的热水和添加物质的步骤是相同的,但是具体的饮品制作步骤是不同的



如果没有使用模板方法模式,实现是酱紫的:


public class Coffee {
public void prepareCoffee() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addCondiments();
}

private void boilWater() {
System.out.println("Boiling water");
}

private void brewCoffeeGrinds() {
System.out.println("Brewing coffee grinds");
}

private void pourInCup() {
System.out.println("Pouring into cup");
}

private void addCondiments() {
System.out.println("Adding sugar and milk");
}
}

public class Tea {
public void prepareTea() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}

private void boilWater() {
System.out.println("Boiling water");
}

private void steepTeaBag() {
System.out.println("Steeping the tea bag");
}

private void pourInCup() {
System.out.println("Pouring into cup");
}

private void addLemon() {
System.out.println("Adding lemon");
}
}

这个代码例子,我们可以发现,烧水和倒入杯子的步骤代码,在CoffeeTea类中是重复的。


使用模板方法模式,代码可以优化成这样:


abstract class Beverage {
public final void prepareBeverage() {
boilWater();
brew();
pourInCup();
addCondiments();
}

private void boilWater() {
System.out.println("Boiling water");
}

abstract void brew();

private void pourInCup() {
System.out.println("Pouring into cup");
}

abstract void addCondiments();
}

class Coffee extends Beverage {
@Override
void brew() {
System.out.println("Brewing coffee grinds");
}

@Override
void addCondiments() {
System.out.println("Adding sugar and milk");
}
}

class Tea extends Beverage {
@Override
void brew() {
System.out.println("Steeping the tea bag");
}

@Override
void addCondiments() {
System.out.println("Adding lemon");
}
}

在这个例子中,我们创建了一个抽象类Beverage,其中定义了制作饮品的模板方法 prepareBeverage()。这个方法包含了烧水、倒入杯子等共同的步骤,而将制作过程中的特定步骤 brew() 和 addCondiments() 延迟到子类中实现。这样,我们避免了在每个具体的饮品类中重复编写相同的烧水和倒入杯子的代码,提高了代码的可维护性和重用性。


7.自定义注解(或者说AOP面向切面)


使用 AOP框架可以在不同地方插入通用的逻辑,从而减少代码重复。


业务场景:


假设你正在开发一个Web应用程序,需要对不同的Controller方法进行权限检查。每个Controller方法都需要进行类似的权限验证,但是重复的代码会导致代码的冗余和维护困难


public class MyController {
public void viewData() {
if (!User.hasPermission("read")) {
throw new SecurityException("Insufficient permission to access this resource.");
}
// Method implementation
}

public void modifyData() {
if (!User.hasPermission("write")) {
throw new SecurityException("Insufficient permission to access this resource.");
}
// Method implementation
}
}

你可以看到在每个需要权限校验的方法中都需要重复编写相同的权限校验逻辑,即出现了重复代码.我们使用自定义注解的方式能够将权限校验逻辑集中管理,通过切面来处理,消除重复代码.如下:


@Aspect
@Component
public class PermissionAspect {

@Before("@annotation(requiresPermission)")
public void checkPermission(RequiresPermission requiresPermission) {
String permission = requiresPermission.value();

if (!User.hasPermission(permission)) {
throw new SecurityException("Insufficient permission to access this resource.");
}
}
}

public class MyController {
@RequiresPermission("read")
public void viewData() {
// Method implementation
}

@RequiresPermission("write")
public void modifyData() {
// Method implementation
}
}

就这样,不管多少个Controller方法需要进行权限检查,你只需在方法上添加相应的注解即可。权限检查的逻辑在切面中集中管理,避免了在每个Controller方法中重复编写相同的权限验证代码。这大大提高了代码的可读性、可维护性,并避免了代码冗余。


8.函数式接口和Lambda表达式


业务场景:



假设你正在开发一个应用程序,需要根据不同的条件来过滤一组数据。每次过滤的逻辑都可能会有些微的不同,但基本的流程是相似的。



没有使用函数式接口和Lambda表达式的情况:


public class DataFilter {
public List<Integer> filterPositiveNumbers(List numbers) {
List<Integer> result = new ArrayList<>();
for (Integer number : numbers) {
if (number > 0) {
result.add(number);
}
}
return result;
}

public List<Integer> filterEvenNumbers(List numbers) {
List<Integer> result = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
result.add(number);
}
}
return result;
}
}

在这个例子中,我们有两个不同的方法来过滤一组数据,但是基本的循环和条件判断逻辑是重复的,我们可以使用使用函数式接口和Lambda表达式,去除重复代码,如下:


public class DataFilter {
public List<Integer> filterNumbers(List numbers, Predicate predicate) {
List<Integer> result = new ArrayList<>();
for (Integer number : numbers) {
if (predicate.test(number)) {
result.add(number);
}
}
return result;
}
}


我们将过滤的核心逻辑抽象出来。该方法接受一个 Predicate函数式接口作为参数,以便根据不同的条件来过滤数据。然后,我们可以使用Lambda表达式来传递具体的条件,这样最终也达到去除重复代码的效果啦.


最后


我是捡田螺的小男孩,大家如果觉得看了本文有帮助的话,麻烦给个三连(点赞、分享、转发)支持一下哈。最近我在工作中,用了其中的几种方式,去优化重复代码。下一篇文章,我打算出一篇后端思维系列的文章,基于业务代码,手把手教大家去除重复代码哈。一起加油~~

作者:捡田螺的小男孩
来源:juejin.cn/post/7270026656663322685

收起阅读 »

消息太大,kafka受不了

前言 上周在进行自测的时候,kafka抛出一个RecordTooLargeException异常,从名字我们可以直接看出是消息太大了,导致发不出去而抛出异常,那么怎么应该怎么解决这个问题呢,其实很简单,要么将消息拆分得小一点,要么调节kafka层面的参数,依然...
继续阅读 »

前言


上周在进行自测的时候,kafka抛出一个RecordTooLargeException异常,从名字我们可以直接看出是消息太大了,导致发不出去而抛出异常,那么怎么应该怎么解决这个问题呢,其实很简单,要么将消息拆分得小一点,要么调节kafka层面的参数,依然它抛出这个异常,那么就证明超过了某个参数的阈值,由此我们可以有两种方式来处理这个问题,但是一切还要从我们的业务背景和数据结构去看这个问题。


业务背景


我们这边会将数据写入文件,通过FTP的方式,没产生数据,就往FTP里面追加,而这些数据都是需要保证不丢失的,由于业务的发展,我这边需要专门去处理这些文件,然后通过kafka投递给下游系统,所以自然需要解析文件,还得一条一条的解析后发送。


问题出现


一开始我看到文件都比较小,所以处理方式是只有这个文件的数据全部解析完成并成功投递kafka,那么我这边才记录这个文件处理成功,但是处理了很多个大文件过后,发现数据条数对不上,看日志是RecordTooLargeException异常,因为上面的处理方式是文件处理完成并全部投递到kafka才记录文件解析完成,所以这是有问题的,一个大文件可能有即使上百万条数据,难免会遇到很大的数据,所以只要一条没解析成功,那么后面的数据就不去解析了,这个文件就不算解析成功,所以应该要设计容错,并对数据进行监控和补偿。


处理问题


在得知是某些数据过大的问题,我就DEBUG去看源码,在kafka生产端的KafkaProducer类中,发现问题出在下面这方法中。



ensureValidRecordSize方法就是对消息的大小进行判断的,参数size就是我们所发送的消息的字节数,maxRequestSize就是允许消息的最大字节,因为没有进行设置,所以这个值使用的是默认值,默认为1M,所以就应该将maxRequestSize这个参数进行重新设置。


因为我们使用的是SpringBoot开发,于是通过yml方式配置,但是发现spring-kafka没提示这个属性,于是只有写一个Kafka的配置类,然后再读取yml文件内容进行配置


配置类


yml文件



通过上面的配置后,我们看到我将max.request.size参数的值设置为10M,这需要根据实际情况来,因为我在处理的过程中发现像比较大的数据行也只有个别。


如果在实际使用过程中数据比较大,那么可能需要拆分数据,不过如果数据不能拆分,那么我们应该考虑消息压缩方式,将数据压缩后再发送,然后在消费者进行解压,不过这种压缩是我们自己实现的,并不是kafka层面的压缩,kafka本身也提供了压缩功能,有兴趣可以了解一下。


扩展


上面设置了max.request.size参数,我们在上面的截图代码中看到第二个判断中有一个参数totalMemorySize,这个值是缓冲区大小,我们发送的消息并不会马上发送kafka服务端,而是会先放在内存缓冲区,然后kafka通过一个线程去取,然后发送,可通过buffer.memory设置,这个值的默认值为32M,所以我们在设置max.request.size的时候也要考虑一下这个值。


总结


有必要对kafka进行比较深一点的学习,这样在出现问题的时候能够快速定位,并且合理解决,当然,在业务处理的时候要充分考虑可能出现的问题,做好容错和相应的补偿方案。


今天的分享就到这里,感谢你的观

作者:刘牌
来源:juejin.cn/post/7269745800178286627
看,我们下期见

收起阅读 »

从读《微信背后的产品观》到思考前端工程师的“35岁”

我是一名前端开发者,同时兼任pm职责,近半年在公司负责升级一直在开发与运营的一个B端的Saas商城系统; 在对需求剖析、需求抽象、每个字段含义的推敲的时候,我越发对微信这样简洁、自然的产品产生兴趣与共鸣。 也会情不自禁的赞叹微信清晰明了、谨慎内敛的结构化产品思...
继续阅读 »


我是一名前端开发者,同时兼任pm职责,近半年在公司负责升级一直在开发与运营的一个B端的Saas商城系统;


在对需求剖析、需求抽象、每个字段含义的推敲的时候,我越发对微信这样简洁、自然的产品产生兴趣与共鸣。


也会情不自禁的赞叹微信清晰明了、谨慎内敛的结构化产品思路。


虽然微信这么多年上线了如此多功能与特性,但是产品简洁而克制的灵魂从来没有改变,哪怕对于60岁的人来说,也几乎不存在用不好微信的情况。


而我做为微信的使用者,广义技术上的开发者,无论是产品还是技术维度,都让我越加佩服与引起共鸣(恶心的小程序开发除外)。


昨天终于行动了起来,把这本知名的 《微信背后的产品观》 找出来并读了起来,与其叫一本书,不如说是一个演讲的记录,很短,大约2小时就读完了,主要内容是2012年微信4.0发布时候,张小龙长达8个多小时的公开演讲的内容;


不同于其他产品经理的书籍,他们会告诉你各种方法论、科学分析方法,张小龙截然相反的采用了一种极其浪漫的方式去看待产品,去理解所谓的用户需求,我认为这种产品思路的领先是微信这么多年在社交领域立于不败之地的根本。


虽然在最后,张小龙看似补刀似的说:我所说的,都是错的;但是这恰恰就是他的产品理念,所谓产品没有任何科学方法,完全来自于对人,对人类群体的理解,对自己的拷问与质疑。


我也斗胆推荐大家看看这本书,很快就能看完,人类之所以存在信息差,就是因为不知道,或许看完这本书,你会打开新的思路,某些问题也能豁然开朗。


借此机会,我也想与大家分享一下,这几年我作为一名前端开发者的迷茫与努力


业务前端开发者的困境



我只是一名普通学历,普通的业务前端开发者,所以以下仅是我的个人感觉,不代表所有前端开发者。



目前在一个小型互联网公司的saas电商部门下,主要职责是前端开发组长,我们公司的主营业务不是saas电商,所以这几年算不上受到到很强的市场冲击,平时会管理几人的小团队,我18年毕业至今,一直在这家公司。


而我大学毕业之后一直从事前端开发方向,在我工作1-3年的阶段,我都保持着对技术的热情,主要是因为尝到了学习技术的甜头,那时候我坚定的认为下一个阶段是全栈,在工作之余我花费了大量的精力学习技术;但是后面我就发现了一个很现实的问题,公司需要大家更好分工协作,所以高级别的项目后端是绝对轮不到一个前端去开发开发;并不是说能力不行,而是人的精力有限,前后端都干,还要管团队,是忙不过来的。


在这样的环境下,关于全栈技术的学习,我也越发疑虑,逐渐走到了大多数业务前端开发者的临界点。



  • 业务前端的35岁危机在普通人身上是存在的,我们的年纪、精力、外部压力都不允许你永远征战一线,并始终保持高竞争力。

  • 业务前端的上限很低,大多数努力的前端开发者可以在3 - 5年内触摸到业务前端的上限,职位也就是前端组长。

  • 技术纵深是很好的选择,但是受限于综合实力(英语、计算机基础、天分),普通人可以达到的纵深远比想象的要浅。

  • 技术学会了,但是用不上,也会慢慢被遗忘;demo级别的应用无法让开发者对某一项技术有深刻理解。

  • 在业务开发场景下的前端,永远是没有灵魂的大头兵,上限低就意味着待遇相对较低、可替代性相对较强。

  • 代码写的越多,与人交流的机会越少,对于几十年的人生而言,这是一件很没安全的事情。


总结一下,就是因为兴趣而走的前端技术路径开始越来越窄,前路开始越来越看不清,时间推着我向前,这不禁让我低头沉思,下一步究竟要怎么走?


说回产品



到目前为止我也依旧不确定前路怎么走,接下来的一些结论,只是我的一些探索。



在大学后期,我隐约感觉技术路线并非我所擅长的时候,我有目的的学习了微观经济学、企业管理、竞争战略相关的知识,从而间接接触到了互联网产品,也就是pm。


我很快就感受到了pm的魅力:创造,我恰好是一个喜欢新鲜事物的人,在技术上总喜欢优化、迭代、升级,亲手构建出优美并且有价值的产品极具满足感,而前端开发者与用户的距离比任何一个岗位都要近,甚至可以说:前端开发者决定了用户体验。


如果一个人同时具备前端开发 + 产品的能力是不是还不错?



  • 消除技术与产品的认知壁垒,后续我们做到了,在我们公司,产品和技术从来不吵架

  • 如果有能力决定产品走向,开发者就可以是一名有灵魂的大头兵,甚至晋升军衔。


也就是说,我逐渐不再下探技术,而是走向用户


走向用户,并不意味着开发者要放弃对技术的学习,技术很重要,技术能力依旧是核心竞争力,而是随着我对技术的理解逐渐深入,开始越发清晰的了解到,究竟什么样的能力是前端开发者最需要的,什么样的能力边际收益是最高的。



在我从事前端开发第2年至今,我一直都有在产品这方面作出努力与尝试。


关于这几年产品的结果,大概可以用这句话来形容:


100个想法中,80个想法死在在调研与分析阶段,15个想法死在在demo阶段,最后落地5个想法,其中4个反响平平,只有1个还算成功。


虽然绝大部分都是失败,但是站在此刻回头来看,这几年的产品的学习将我的思维高度提升了很多,综合能力也提升了很多,因为很多想法初期是没有团队介入的,凡事都需要亲力亲为,需要思考需求、写最小demo、UI设计、沟通,而上线后,有需要又要为产品负责,就需要进行数据分析,线上数据的观察,等等....,这其实比写代码累多了。


这些进步不像程序的学习有一个可量化的指标,这样的软实力很多的是一种感觉,虽然依旧是时而迷茫,但是也偶尔会有一些收获。


虽然我做的产品决策越来越多,公司与同事给予的信任也越来越多,所以在产品上的舞台也是越来越大,而看了《微信背后的产品观》,里面的想法非常符合我对产品的理解,当然我的理解是相对浅显与张小龙没法比的,不过张小龙对产品的理念,以及他对需求的理解,这样一套浪漫的方法论,真的非常有魅力,这也是为什么,我看完最后决定写这篇文章。


跨越“技术”思维


这几年,我的老板经常会找我聊天,因为我和他提过对产品很有兴趣,在前两年,他反复和我提一句话技术为业务服务,我当时觉得我理解这句话了。


我想,这不是废话,写代码最终都是为了公司的项目,为了更好更快的完成公司需求,我要狠狠的学习技术。


之后来随着我写的代码越来越多,我对这句话逐渐有了新的理解。


之前过于执着于技术,总是站在写代码的角度去理解,而这句话的侧重点是业务,或者我们换个词交付


并不是技术推动交付,而是在推动交付的因素中,技术是其中之一,我们可以衍生出很多类似的话;设计为业务服务、产品为业务服务....


所以技术的目的并非技术,而是交付价值。


人们总是不自知的放大自己在团队中的价值,这样只会蒙蔽大家的视野,走到更高处,对很多事物将会有不一样的理解。



低纬度的技术思维,走向高纬度的业务(交付)思维。


最后


其实我本来只是觉得读了还不错的一本书,不复盘、不留下点什么反思会达不到学习的目的,写着写着就想到了自己的职业,想到了这几年的经历;


总结性的话不说太多,希望可以帮助到屏

作者:狗阿木
来源:juejin.cn/post/7270331527506100264
幕前你,我们共同成长,共同进步。

收起阅读 »

人情世故职场社会生存实战篇(一)

这个专栏文章都是群友问的实际问题,我在这里分享出来,希望可以帮助到大家,欢迎大家踊跃发言 1、问:老师,我们领导今天请我吃饭,我送了两瓶梦之蓝,一条中华,临走他问了我一下这酒多少钱,有点奇怪,他问酒多少钱是什么意思?另外走的时候非要给我一箱枣,我感觉去了人家家...
继续阅读 »

这个专栏文章都是群友问的实际问题,我在这里分享出来,希望可以帮助到大家,欢迎大家踊跃发言


1、问:老师,我们领导今天请我吃饭,我送了两瓶梦之蓝,一条中华,临走他问了我一下这酒多少钱,有点奇怪,他问酒多少钱是什么意思?另外走的时候非要给我一箱枣,我感觉去了人家家里提东西很正常,拿人家的枣是不礼貌的,何况不值两个钱,有点奇怪。


答:领导问你这两瓶酒多少钱?他想听到的答案是:这两瓶酒是朋友送的,我也不喜欢喝酒,麻烦你帮我消化一下吧。领导想听这句话,醉翁之意不在酒嘛。临走的时候他把这个大枣送给你,这个枣肯定是别人送给他的,这个大枣他不想要,他想让你帮他消化一 下,那这个枣你是要接住的,你说太好了,我老婆最喜欢吃枣了,这样的话,你就满足了领导的精神需求,他就感觉到你特别懂事儿。也就是领导想跟你做一个良性的循环和互动。


2、问:老大经常分给我费时又费力的垃圾项目,别的同事都是简单高效的赚钱项目,我认为是随机分的,我该怎么拒绝领导的安排啊?


答:你手里有2个项目,一个可以轻轻松松赚10万,一个累死才能赚8000。现在有人找你要项目?有人送了你阿弥陀佛,有人送了你烟酒,有人送了你美女,有人送了你5W真金白银,有人送了你烟酒茶美女+五W。最后你把项目给谁啊?你觉得是随机的,你急什么啊?下次能不能轮到你啊?找人算一卦试试,比如有些考试啊,面试和笔试都是私人订制,然后拉一群沙雕陪着走个形式,这样显得更公平。公平是相对而言,不是绝对的。


3、问:下周即将和我们公司大老板开会,参会的有同级别的其他中心经理,还有我们的直属总监,要求我们经理都说一下各自中心今年的工作计划。请教您,在会议上说工作计划,有没有什么技巧?有需要注意的地方吗?


答:1、领导安排的工作,我全落实了。


2、领导的目标也是我的目标,领导安排什么,我做什么,我力求每一步工作都做扎实 ,都做到位。


3、我会跟领导搞好关系,我会跟同事搞好关系,在领导的帮助下,还有同事的帮助下,去为公司创 造更多的利润。


就是用这三句话,在里面转圈圈就行了。


4、问:请教一个事,我是一个老师,我平时很注重提升自己,会不断学习各种课程,所以经常会在办公室学到很晚,然后我感觉有同事在背后不断嘲讽我,她们经常问我,你不是晚自习没有课程吗,怎么还在这里呀,就是类似这种,我感觉很反感。


答:同事为什么会在背后嘲讽你呀,因为你不懂人性。你要进步没有错,但你不能给身边的人压力。你要进步,在家里你学到凌晨2点都无所谓,但在公共场合,不要展现你的野心,如此会给别人造成心理上的压迫感。其次你要学会回话,会示弱。人家问你:晚上不是没课程吗?怎么还在这里呀?你说,我笨鸟先飞,下周的课我要提前准备,XX老师你的教案可以借我参考吗,听其他老师说,你的教案做的很好。你这样子给她回话,以后,她就不会在背后说你坏话了。


5、问:在单位,直接大领导一直很挺我,因为各种原因并没有走得很近,单位副职一直不太认可我。这次选上了大领导的秘书(有竞争对手的情况下),我是否应该送礼给大领导?


答:在这种情况下,你不要送礼,这时候送礼对你来说根本不是加分,他反而减分。万一被你的竞争对手看到呢,把你给举报了,肯定对你是不利的,那你这个时候应该怎么样做呢,非常简单。以前怎么样,现在你还怎么样,你现在不要乱了这个方寸。你要是送礼表态的话,你早都应该送了,你非要等到最后再送礼,那人家觉得你临时抱佛脚,你这个人怎么那么势利啊?这个时候,你要做的,你想一想,你能够帮他们做点什么,为他们创造一些什么价值,这些价值有可能是物质方面的,有可能是精神层面的,就是你要一直旺他们,替他们排忧解难,你就顺着这个点往前跑,你100%的能立起来。


6、问:如果领导和你聊骚,你又不想牺牲自己,你该怎么办?


答:只要你在一个位阶和势差比你大的人手下工作,他们总能创造各种有利的机会。男人最懂男人,狗改不了吃屎,你只要被得手了,对于他来说,你的结局大致已经有一个雏形了。如果他和你聊骚,你就把他对你的聊骚内容截图,他发一次,你截图一次,找个隐秘的空间保存起来。如果他不微信,打电话怎样?录音啊,傻呀!以后有需要动动或者求到他的地方,没有什么比这个更有分量了。或者,你直接点他一下,你说,领导,给我发的信息,我好多都截图(录音)保存了,以后我不想再存了,到此为止吧。领导我相信您是一个睿智的人,希望能跟您多聊点正面的,积极的东西。


7、问:如何搞关系?


关系是跑出来的,跑关系三个字,点出了人际交往的秘密。关系不是等出来的,是天天跑,多多跑,关系就有了。好处有多到位,机会就有多到位。很多傻子被君子之交淡如水严重洗脑,脑子坏掉了,不知道君子只出现在书里面,忘记了人情往来中的尊重和礼仪。


处处感恩,处处逢源,处处遇贵人。时时送出好处,处处遇见活雷锋。不要空手套白狼、四两拨千斤、以小博大,谁这么教你谁就是把你当成傻X呢。有求于人的的时候,就要舍得投入,舍得砸钱,舍得亲近。这个时候不舍不得,等事情过去了,再怎么投入,也意义不大了。


求人办事,最忌讳临时抱佛脚。如果遇到这种情况,最好找个中间人帮忙。备好双份礼物,一份给引荐人,一份给办事人。但是一定要明白,如果事情事能够办下来,并不是因为礼物,而是中间人的关系网与情面的作用。所以啊,对咱有用的人,必须要细水长流地去维护。人生在世,需要有靠山。


天生的靠山可遇不可求,可求的靠山都是自己主动经营来的。你打牌、抽烟、喝酒,不是因为喜欢,而是因为别人喜欢。只做自己喜欢的事,只能和自己玩,只有做了别人喜欢的事,才能和别人玩到一块。


8、问:老师,为什么你说能不送礼就尽量不要送礼?


答:能不送礼就不送礼。尤其是职场。送了,就要持续送。送着送着,你不送了,收礼的人觉得你看不起他了,你不重视他了,你过桥拆河。以后他会弄你。你是怎么死的,都不知道。咱不想送礼了,咱遇到对方了,咱彬彬有礼就行了。不要做啥深度链接。你用不上他,为什么给他送礼呀,难道你贱啊?职场中,很多人,一开始觉得好玩,也想锻炼一下自己,就去给领导送礼了,送了一年,发现自己的福利没啥变化,就不想送了,第二年,他发现他过得比以前更不好了,他也不知道为什么!关于送礼,第一,我的看法就是,能不送,就不送,送了,就不能断,你送着送着不送了,领导觉得你是不是看不起他了,还是觉得他没用?啥,既然觉得我没用了,那我就要拿出点手段让你重新认识我!人心难测!


9、问:我们公司现在在搞改革,很多岗位会涉及到变动,甚至被安排分配到下一级公司干活。现在新来的上司是我四年前的老领导,他之前被调去别的单位了。我跟他以前关系蛮好,期间就过节短信问候,现在我想找他,让他帮我继续留在现在这个层级干活,你觉得他会帮我嘛?要怎么开口?


答:这个帮不帮暂时还不知道,你需要带上心意,烧香拜神 投石问路。你现在也犯了多数人都会犯的错误,以至于到了关键时刻非常被动。


第一个就是临时抱佛脚,看的不够远,总以为太阳可以一直照耀着你,不懂的提前去布局,铺设你日后能用得到的关系站点,导致你现在关键性的节点就缺乏了很重要的助力。


第二个,那就是不懂的主动去搭关系,你的老领导调回来的那天,你为什么不去拜访、祝贺?为什么不提这猪头去拜庙门?现在才想到人家。不过,如果他之前帮助过你的,一般还会帮助你第二次,第三次,所以,你带上心意,去拜访一下他,事到临头,见面了把礼物放下,你先回望过去,把彼此的关系拉进,然后谈谈自己的想法,你说 老领导,我的事情劳烦您费心了,这事成不成,没有关系,很久没有见过领导了,这点小心意,请领导不要嫌弃。


10、问:我找一个领导帮忙和单位打招呼,因为这一次有大调整,请求帮忙调整一下,之前与帮忙这位领导都是过节礼仪性的人情,这次找这个领导帮忙,领导答应帮忙,简历也给了,但是一直没给反馈,我第二次找,他正好周末和我们单位的领导们一起吃饭,他说会帮我说,现在都没给反馈,是不是我要去意思意思,我又担心送了,万一这领导也没帮上忙咋办?


答:我送你一句话吧:送礼是从承担风险开始的,而不是从刻意追求回报率开始的!其实,在我看来,你不适合送礼,因为你的的觉悟还没有到。觉悟不到的东西,你是拿不住也做不好的。


送礼,原本就是风险投资。咱不是说送礼了,100%有回报率。但送礼有回报率的概率至少在50%以上。上211/985的概率有多少呀?听说985全国只有2%,211占5%(是我粘贴复制过来的,数据真假不知道啊)。都说读书改变命运,但通过读过改变命运的概率有多大?3%。通过送礼改变命运的概率有多大?50%。


生活中,开窍的人非常少。开窍的人,都是直接给我送红砖,说领导辛苦了,事成不成,遇到您认识您,就是我最大的福气。这样的人,都是开窍的人。习惯性承担风险,习惯性承担责任。可惜这样的人实在太少了。


送礼,是从习惯性承担风险开始的。而不是说我送了,对方必须100%给我一个说法。建议你带上心意,去拜访一下他,他收了,会告诉你一两句有用的话,自然,你就知道下一步怎么样了。


欢迎同学们关注本专栏,会持续更新社会上的人情世故,有问题的同学也欢迎留言~


作者:公z号_纵横潜规则
来源:juejin.cn/post/7267576629295022080
>如果对你有帮助的话给个关注吧~

收起阅读 »

七夕节那天,我被裁员了

前言 先祝jym昨天七夕节快乐吧,有情人终成眷属 一直想在掘金上发文但迟迟未行动,上学时在b乎写了一段时间一直切换不来平台。看着b乎仅存的几百号关注者到这边只能从0开始很是不情愿( ̄Д ̄)ノ,也行吧就从零开始了,我的工作似乎也得从零开始了。该死的仪式感... ...
继续阅读 »

前言


先祝jym昨天七夕节快乐吧,有情人终成眷属


一直想在掘金上发文但迟迟未行动,上学时在b乎写了一段时间一直切换不来平台。看着b乎仅存的几百号关注者到这边只能从0开始很是不情愿( ̄Д ̄)ノ,也行吧就从零开始了,我的工作似乎也得从零开始了。该死的仪式感...


image.png


难忘的七夕


如标题所见,七夕节被通知裁员了。单身狗今天受到双重痛击。说的是公司迫于压力需要节流,部门会裁1/4。而我恰好就是那个“幸运儿”。其实从前些时间就早有风向,先是ui,到测试,终于到开发人员了,而到了开发最先动刀的果然还就是鼠鼠前端。


23届真的太惨了。在这里从实习到转正也快干了1年了,但才刚拿了一个月转正工资就遇到调整,实属难绷。


应届生身份也没了,工作年限也达不到,双重叠buff了属于是,老实说刚听到这消息的时候自己还是懵的,也就和往常一样上班怎么今天就这么突然开这样的会议呢?


而后是无奈,但是又有点兴奋,正好可以逼自己再出去外面探探,看看市场如何。

回家路上,打开手机刷了刷boss,要么招24的要么一到两年工作经验的,一下子又把我难住了。。。我怎么这么背啊5555


整个晚上我都在思考人生,我在想假如自己不是读计算机会如何?我是不是能没负担的尝试各种工作,自己真的要在程序员这条赛道一条路走到黑吗?


程序员的工作又何尝不是围墙,技术类的工作是建立起了行业壁垒,在外行人看来能将代码变成程序是一件很酷的事,而正是这道壁垒,让外行的人想进来,里面的人想走却又鼓不起勇气。


image.png


是啊 我除了写代码还能干什么呢?难道真的放弃学了这么久在外行人看来很厉害的技术去做其他工作吗?(脱不下的长衫又穿上了)


总结


我一直认为无论发生什么事,一切都是上天最好的安排,世上无所谓输赢祸福,关键是从中有所收获,写几点感悟吧:



  • 拥抱不确定性是踏入社会后最需要学会的。读书时每个人都是按部就班,除了学习剩下的挑战就是安排到日程上的考试,而步入社会每天遇到的事情都不一样,so be water my friend 随机应变 随遇而安

  • 负能量可以有,适当摆烂一下,但睡一觉起来就得调整好心态了,享受生活不要被蛋疼的事影响

  • 不要畏手畏脚,敞开自己而不是压抑自己,一辈子很短,很多事情不去尝试就来不及了

  • 灵活就业或许才是这个时代下的答案?不知道

  • 行动起来最重要,如前面说的想在掘金发文却拖延了很久,而正是这回吐槽却让我奋笔疾书了起来,命运的齿轮或许就此转动,后面我会督促自己继续更开发过程中的杂记、生活记录

  • 接下来的安排:先编辑个简历再出发,然后整理一下这一年来开发的东西,记录一下


结语


我也不知道后面会如何,走一步是一步吧,通知还没正式下来。如果读到这里的你觉得我我是个有趣的人,那么点个关注吧,这也是我更文的动力


失败并不存在

作者:前端咖啡豆
来源:juejin.cn/post/7270152997166252071
,关键是故事仍在继续......

收起阅读 »

iOS 陀螺仪技术的应用探究

iOS
本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢。 前言 陀螺仪是一种硬件传感器,能够感知设备的旋转和方向变化。...
继续阅读 »

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢。



前言


陀螺仪是一种硬件传感器,能够感知设备的旋转和方向变化。它通常通过MEMS(微机电系统)技术来实现,内部包含了微小但高精度的陀螺仪器件、加速度计和磁力计等传感器,可以实时地感知设备在空间中的旋转角度和方向。


在iOS系统中,可以通过CoreMotion框架来访问陀螺仪的数据。在开发iOS应用程序时,可以使用CoreMotion框架提供了一个CMMotionManager类,该类可以用来获取设备的运动数据,包括陀螺仪数据、加速度计数据等。


iOS陀螺仪的精度和灵敏度通常比较高,可以实现比加速度计更加准确的姿态估计和方向识别,也可以帮助开发者实现更加真实的虚拟现实和增强现实应用。同时,iOS陀螺仪的实时响应和低功耗特性,也使得它在移动应用程序开发中得到了广泛的应用和认可。


基础知识


在开发前,有几个基础的知识点,我们需要事先了解,这对我们后期开发会有更好的帮助


三轴方向


在 iOS 中,陀螺仪传感器的三轴方向通常遵循右手系的规则。具体来说:

  • x 轴:表示设备绕着横轴旋转。当设备的屏幕朝上时,x 轴指向设备的右侧;当设备的屏幕朝下时,x 轴指向设备的左侧
  • y 轴:表示设备绕着纵轴旋转。当设备的屏幕朝上时,y 轴指向设备的顶部;当设备的屏幕朝下时,y 轴指向设备的底部
  • z 轴:表示设备绕着竖轴旋转。当设备的屏幕朝上时,z 轴指向设备的正面;当设备的屏幕朝下时,z 轴指向设备的背面



姿态信息


陀螺仪用于侦测设备沿三个轴为中线所旋转时的角速度,故有了三个姿态信息,分别为 pitch (纵倾), roll (横倾) 和 yaw (横摆)

  • pitch(俯仰角):表示设备绕着 x 轴旋转的角度,也称为纵倾角。当设备正面朝上时,俯仰角为 0°;当设备向上仰起时,俯仰角为正值;当设备向下倾斜时,俯仰角为负值
  • roll(横滚角):表示设备绕着 y 轴旋转的角度,也称为横倾角。当设备正面朝上时,横滚角为 0°;当设备向右侧倾斜时,横滚角为正值;当设备向左侧倾斜时,横滚角为负值
  • yaw(偏航角):表示设备绕着 z 轴旋转的角度,也称为横摆角。当设备正面朝北时,偏航角为 0°;当设备逆时针旋转时,偏航角为正值;当设备顺时针旋转时,偏航角为负值
  • CMRotationMatrix 结构体表示设备绕X、Y、Z轴的旋转矩阵,可用于描述设备在三维空间中的方向和旋转状态, 这里再细讲该结构体中9个元素所代表的含义


陀螺仪的使用

import CoreMotion

let motionManager = CMMotionManager()
if motionManager.isGyroAvailable {
motionManager.gyroUpdateInterval = 0.1
motionManager.startGyroUpdates(to: OperationQueue.main) { (data, error) in
if let gyroData = data {
let rotationRateX = gyroData.rotationRate.x
let rotationRateY = gyroData.rotationRate.y
let rotationRateZ = gyroData.rotationRate.z

print("Rotation Rate X: \(rotationRateX)")
print("Rotation Rate Y: \(rotationRateY)")
print("Rotation Rate Z: \(rotationRateZ)")
}
}
} else {
print("Gyroscope is not available.")
}

关键类解析


CMDeviceMotion


CMDeviceMotion 是一个 Core Motion 框架中的类,用于表示设备的运动和姿态信息。通过 CMDeviceMotion 类,可以获取到设备在三维空间中的加速度、旋转速度、重力加速度、旋转矩阵以及设备的姿态信息等,以便进一步进行处理和计算。


下面是 CMDeviceMotion 类中常用的属性和方法:

  • attitude 属性:表示设备的姿态信息,包括俯仰角(pitch)、横滚角(roll)和偏航角(yaw)等信息。
  • userAcceleration 属性:表示设备在三维空间中的加速度,即不包括重力加速度的加速度
  • rotationRate 属性:表示设备在三维空间中的旋转速度
  • gravity 属性:表示设备在三维空间中的重力加速度,即不包括设备加速度的重力加速度

需要注意的是,在使用 CMDeviceMotion 类时,需要首先创建一个 CMMotionManager 对象,并设置其属性和回调函数,以便获取设备的运动和姿态信息。此外,由于设备运动和姿态信息的获取涉及到多个传感器的协同工作,因此在使用时需要考虑传感器的准确性和稳定性,以避免误差和不良体验。


CMAttitude


CMAttitude 表示设备在三维空间中的姿态信息,包括设备的旋转、倾斜、方向等信息。在 iOS 开发中,可以通过 CMMotionManager 获取设备的姿态信息,然后将其保存为 CMAttitude 对象,并使用其中的各个属性来进行相应的处理和计算。


CMAttitude 类中的主要属性如下:

  • pitch:设备绕 x 轴的旋转角度,单位为弧度
  • roll:设备绕 y 轴的旋转角度,单位为弧度
  • yaw:设备绕 z 轴的旋转角度,单位为弧度
  • quaternion:设备的四元数表示,用于表示设备的旋转状态,包括旋转角度和旋转轴等信息
  • rotationMatrix:设备的旋转矩阵表示,用于表示设备在三维空间中的旋转状态

其中,pitchroll 和 yaw 属性是最基本的属性,用于表示设备绕 x、y、z 轴的旋转角度。一般来说,可以通过这三个属性来进行设备的姿态检测和相应的处理。其余的属性包括四元数、旋转矩阵,都可以用于更加精确和复杂的姿态检测和处理。


应用场景

  • 姿态估计和方向识别:通过陀螺仪获取设备旋转的角度和方向,可以实现设备的姿态估计和方向识别,广泛应用于游戏、导航、运动感知等领域
  • 图像校正和稳定:通过将陀螺仪中的旋转信息应用于图像处理,可以实现图像校正和稳定,提高图像质量和用户体验
  • 虚拟现实和增强现实:通过与其他传感器的结合和数据处理,可以实现更加真实的虚拟现实和增强现实应用,如3D游戏、AR导航、AR应用等
  • 运动检测和姿态跟踪:通过结合加速度计和地磁计等传感器的信息,可以实现设备的运动检测和姿态跟踪,如步数统计、运动轨迹记录、体感游戏等
  • 安全防护和权限控制:通过使用陀螺仪的数据,可以实现设备的安全防护和权限控制,如设备锁定、用户身份验证、数据加密等

作者:万链飞空
链接:https://juejin.cn/post/7239715294230183993
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

iOS:NSNotification.Name从OC到Swift的写法演进

iOS
前言 在闲来无事的时候,我会抽时间看看Foundation、UIKit等相关库的Swift代码说明与注释。说实话,有的时候看起来真的很乏味,也不容易理解。 不过有的时候也会觉得Apple这么设计API真是书写的简单漂亮,一脸佩服,今天给大家分享的就是从NSNo...
继续阅读 »

前言


在闲来无事的时候,我会抽时间看看Foundation、UIKit等相关库的Swift代码说明与注释。说实话,有的时候看起来真的很乏味,也不容易理解。


不过有的时候也会觉得Apple这么设计API真是书写的简单漂亮,一脸佩服,今天给大家分享的就是从NSNotification.Name学习一种代码编写方式,并且已经在我自己的项目中进行类似这种写法的落地实战分享。


OC时代的通知名写法


NSNotification想必大家都使用过,在iOS中处理跨非相邻页面、一对多的数据处理的时候,我们通常就是通过发通知传参,告之相关页面处理逻辑。


一般情况下,如果可以避免NSNotification的时候,我都会尽量避免使用,当然,既然系统给你了这个方法,那么在合适的场景使用也会妙手生花。


当然,对于NSNotification的通知名的管理,其实是一个看似简单,实际上可以做得非常优雅的事情。


特别是从OC过渡到Swift的过程,这段简单的代码,其实进行了很大的演变,我们不妨来看看。


下面这个是我早期写OC代码的时候,发送一个通知:

[[NSNotificationCenter defaultCenter] postNotificationName:@"CancelActivateSuccessNotification" object:nil];

大家注意看,通知名,我就是非常简单的使用硬编码字符串@"CancelActivateSuccessNotification" 来表示,硬编码的缺点就不用我多说了,编译器是不会给提示的,写错了,甚至连通知事件都没法收到,总之,这种写法是不好的。


于是,看看系统代码以及AFNetworking,我们会看见这样一种写法:


系统通知名:

UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;

AFNetworking的通知名,也是学习系统通知名的写法进行的扩展:


.h文件




.m文件 



看起来并不是太高明?也许确实如此,只不过通过.h与.m的分隔,将一个硬编码字符串变成了一个全局可以引用、IDE可以快速键入的方式,但是它至少让调用变得简单与安全,这样就足够了。


于是乎,OC时代通知名的写法,我们基本上都会用以上这种方式进行编写:


.h

UIKIT_EXTERN NSString *const CancelActivateSuccessNotification;

.m

NSString *const CancelActivateSuccessNotification = @"CancelActivateSuccessNotification";

Swift时代还是这么写吗?


Swift时代的通知名写法


其实Swift的早期,基本上还是沿用着OC的这一套写法来写通知名,不过在Swift4.2之后就迎来比较大的改变,让我们来看看调用的API与源码:

open func post(name aName: NSNotification.Name, object anObject: Any?)

open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil)

发通知的时候,通知名被一个NSNotification.Name类型代替了,我们进去追着NSNotification.Name看:




大家一定要记住这种编码的书写方式,先送上结论:

  • 可以在一个类型里面再定义一个类型,大家可以自己尝试。
  • 什么时候嵌套?为何要这么写?当嵌套的类型与外层定义的类型有着较强关联的时候可以这么写。

说完了这些,我们可以看到在Swift中,发通知,通知名不再是一个字符串了,而是一个NSNotification.Name类型了。


那么在开发过程中,我们如何使用呢?我们不妨还是从系统提供的API开始找:




因为Swift可以随处编写一个类的分类,于是在一个类的分类中定义好该类的通知名这种书写方式随处可见,这样的好处就是通知名与类紧紧联系在一起,一来便于查找,二来便于绑定业务类型。

NotificationCenter.default.post(name: UIApplication.didFinishLaunchingNotification, object: nil)

上面这个通知一发出,通过通知名我就知道是涉及UIApplication的操作行为。


说完了系统提供的API,我们再来看看一些知名第三方库是怎么定义吧,这里以Alamofire为例:




Alamofire保持了和系统API一样的风格来定义通知名。


我们再来看看Kingfisher




Kingfisher是在NSNotification.Name分类中扩展了通知名。


顺带说一下,我自己管理与编写通知名是这样的:

extension Notification.Name {
    enum LoginService {
        /// 退出
        static let logoutNotification = NSNotification.Name("logoutNotification")
    }
}
NotificationCenter.default.post(name: .LoginService.logoutNotification, object: nil)

通过在NSNotification.Name分类中进行二级业务扩展,细化通知名。


至于大家更喜欢哪一种写法,那就是仁者见仁智者见智的事情了。


总结


本篇文章从NotificationCenter发通知的通知名开始,对OC到Swift的写法演进进行梳理与说明,举了系统API和著名第三方库的例子,给大家讲解如何写好并管理好NSNotification.Name


吐槽


掘金的这个编辑器,我直接从Xcode里面复制粘贴代码的体验真的很不友好,导致我比较长的代码都是截图,只有较少的代码使用的代码块。


自己写的项目,欢迎大家star⭐️


RxStudy:RxSwift/RxCocoa框架,MVVM模式编写wanandroid客户端。


GetXStudy:使用GetX,重构了Flutter wanandroid客户端。


作者:season_zhu
链接:https://juejin.cn/post/7249624466149376058
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Swift 中async/await 简单使用

iOS
在 Swift 5.5 中,终于加入了语言级别的异步处理 async/await,这应该会让用回调闭包写异步调用方法时代彻底结束了! 这篇文章就简单总结一下这个功能使用吧。 异步函数 所谓异步,是相对于同步而言,这是一种执行任务的方式,同步的执行任务,任务需...
继续阅读 »

在 Swift 5.5 中,终于加入了语言级别的异步处理 async/await,这应该会让用回调闭包写异步调用方法时代彻底结束了!



这篇文章就简单总结一下这个功能使用吧。


异步函数


所谓异步,是相对于同步而言,这是一种执行任务的方式,同步的执行任务,任务需要一个一个的顺序执行,前边的好了,后边的才能运行。而异步就不是这样,它不需要等待当前任务执行完成,其他任务就可以执行。


在 Swift 5.5 中,添加了 async 关键字,标记这个函数是一个异步函数。

func getSomeInfo() async -> String { ... }
/// 可以抛出错误的异步函数
func getSomeInfoWithError() async throws -> String { ... }


这里需要注意的是,如果我们想调用异步函数,就必须在其他异步函数或者闭包里面使用 await关键字。

func runAsyncFunc() async {
let info = await getSomeInfo()
...
}

func runAsyncErrorFunc() async throws {
let info = try await getSomeInfoWithError()
...
}

实际使用异步函数的时候,我们是无法在同步函数里使用的,这时Swift会报错。要使用的话,就需要我们要提供了一个异步执行的环境 Task

func someFunc() {
Task {
runAsyncFunc()
}
}


异步序列


如果一个序列中的每个信息都是通过异步获取的,那么就可以使用异步序列的方式遍历获取。前提是序列是遵守AsyncSequence协议,只要在for in 中添加 await关键字。

let asyncItems = [asyncItem1, asyncItem2, asyncItem3]
for await item in asyncItems { ... }

多个异步同时运行


这个可以使用叫做异步绑定的方式,就是在每个存储异步返回信息的变量前边添加async

async let a = getSomeInfo()
async let b = getSomeInfo()
async let c = getSomeInfo()
let d = await [a, b, c]
...

这时运行的情况就是 a b c 是同时执行的,也就是所说的并行执行异步任务,即并发。


结构化并发


上边在提到在同步函数中使用异步函数,我们需要添加一个Task,来提供异步运行的环境。 每个 Task 都是一个单独任务,里面执行一些操作,这操作可以是同步也可以是异步。多个任务执行时,可以把它们添加到一个任务组TaskGroup中,那么这些任务就有了相同的父级任务,而这些任务Task又可以添加子任务,这样下来,任务之间就有了明确的层级关系,这也就是所谓的结构化并发


任务和任务组


任务组可以更为细节的处理结构化并发,使用方式如下,就在任务组中添加单个任务即可。

func someTasksFunc() {
Task {
await withTaskGroup(of: String.self) { group in
group.addTask {
let a = await getSomeInfo()
...
}
group.addTask {
let b = await getSomeInfo()
...
}
}
}
}

从运行的方式来说,这种使用任务组的情况和异步绑定的效果一样,简单的异步任务,完全可以使用异步绑定的方式。而任务和任务组是为更为复杂的并发情况提供支持,比如任务的优先级,执行和取消等。


如果异步函数是可抛出错误的,使用withThrowingTaskGroup就行。


解决数据竞争的Actor


在并发过程中,对于同一属性数据的读取或者写入,有时会有奇怪的结果,这些由于在不同的线程,同时进行了操作。消除这种问题的方式,就是使用 Swift 提供的 Actor类型。 一个和类差不多的类型,但是对自身可变内容进行了隔离。

actor SomeInfo {
var info: String
}

外部在访问其info属性时,actor 做了隔离,每次只能一个线程访问,直接访问就会报错。而且外部不能进行修改,只有内部才能修改。


外部访问方式就是需要异步执行,在异步环境中,添await

let content = SomeInfo(info: "abc")
let info = await content.info)
...

总结


以上就是Swift 5.5 async/await的简单使用了。了解了这些,就可以日常开发中替代闭包回调,告别回调地狱和处理不完的 completionHandler了。😎
目前官方已经在已有闭包回调处理异步的地方,都增加async异步版本,自行查看文档了解吧。。


另外附上一些很有帮助的文章地址,这些地方都有更为详尽的说明,参考学习起来!


Swift 5.5 有哪些新功能?


http://www.hackingwithswift.com/articles/23…


Swift 并发初步


onevcat.com/2021/07/swi…


并发


swiftgg.gitbook.io/swift/swift…


作者:WeiYou
链接:https://juejin.cn/post/7250375035634090043
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

人走茶凉?勾心斗角?职场无友谊?

你和同事之间存在竞争关系 要不要把工作关系维护成伙伴关系 明枪暗箭防不胜防 背后捅刀子往往最不设防 大家是否在职场上交友是有也遇到过以上困扰呢? 不要在职场上交“朋友”,而是要寻找“盟友”。 这两者的区别在于应对策略: 我们会愿意为“朋友”牺牲自己的利益,像是...
继续阅读 »

你和同事之间存在竞争关系


要不要把工作关系维护成伙伴关系


明枪暗箭防不胜防


背后捅刀子往往最不设防


大家是否在职场上交友是有也遇到过以上困扰呢?


不要在职场上交“朋友”,而是要寻找“盟友”。


这两者的区别在于应对策略:


我们会愿意为“朋友”牺牲自己的利益,像是一张年卡。


而结交“盟友”就是为了一起争取更多利益,《孔乙己》说得好:“这次是现钱,酒要好。”


所以,在职场上的“受欢迎”和社交场、朋友圈上的“受欢迎”之间有着本质的区别:


你和你的同事未必真心喜欢彼此,但在日常相处当中能够客气、友善地交往。


大家需要寻找盟友时会第一个想到你,在争斗冲突时会尽量绕开你,这就是一种非常理想的“受欢迎”状态。 不要在职场上寻求友谊和爱,这件事是不对的。


在这里给大家列出一个在职场上受欢迎的清单。


1.实力在及格线以上


这是一切的前提。职场新人要“先活下来,再做兄弟”,稳住了工作能力这个基本面,才有资格和同事谈交情。


实力不够的人会拖累整个团队、增加所有人的工作量,大家恨都来不及,绝对不会和他称兄道弟。


实力强可以表现为实力本身,在初级职位上,也可以表现为潜力。


极少数特别强大的人可能从一开始就能很好地完成工作,但是大部分人在新加入一个团队时都需要经过一段时间的磨合,在这个过程中有欠缺和不足都是正常的,你所表现出来的敬业精神、学习能力和进步的速度才是大家对你进行评价的关键。


刚入职的新人,对于要做的事情完全没有概念,但是为人极勤奋又上进,给他布置的任务会完成得特别扎实,每一天都在飞快地进步。这样的人在职场上永远都能收获一大把来自他人的橄榄枝。


2.比较高的自尊水平


高自尊的人对自己评价高,要求也高,又能够带着欣赏的眼光去看周围的人,他们不光是很好的父母、伴侣和朋友,同时也是职场上最好的结盟对象。


高自尊的人往往拥有很多优秀的品质,同时他们也能够理解“大局”,和他们合作不用在鸡毛蒜皮的细节上纠缠推诿,可以把精力全部用来开疆拓土,极大地降低团队的内耗。


如果你是一个高自尊的人,在日常生活中表现出了自律和很好的品行,就会收获高自尊同类的赞赏。有些低自尊的人可能会认为你的言行是在“装X”,别犹豫,把他们从你的结交名单当中划掉,高自尊会帮你筛掉一批最糟糕的潜在合作者。


如果你是一个部门的领导者,记得要维护高自尊的下属,他们都是潜在的优秀带队者,给他们一个位子就可以坐上来自己动,给他们一点精神鼓励和支持,他们就会变得无所不能。


即使高自尊的手下可能某些地方让你感到嫉妒或者冒犯(这是常见的,嫉妒是每个人都一定会有的情感),也绝对不要默许或者纵容低自尊的妄人跑去伤害他们,否则会伤了大家的心,事业就难以成功了。


“朕可以敲打丞相,但你算什么东西”就是对这种低自尊妄人最好的态度。


3.嘴严,可靠


在任何一个群体当中,多嘴多舌的人都不会受到尊重,而在职场上,嘴不严尤其危险。


如果你是一个爱说是非的人,围绕在你周围的只会是一帮同样没正事、低级趣味的家伙。你会被打上“不可靠”的标记,愿意和你交流的人越来越少,大家等着看你什么时候因为多嘴闯祸,而强者根本不会和你为伍。


有些同学曾经给我留言说,自己很内向,不知道如何跟同事拉近关系。内向的人最适合强调自己的“嘴严”和“可靠”,在职场上,这两项品质远比“能说会道”更让人喜欢。


4.随和,有分寸


体面的人不传闲话,也不会轻易对旁人发表议论。


“思想可以特立独行,生活方式最好随大流”,这是对自己的要求,而他人的生活方式是不是合理,不是我们能评价的。


哪怕是最亲近的人,都未必能知晓对方的全部经历和心里藏着的每一件小事。在职场上大家保持着客气有礼的距离,就更不可能了解每个人做事的出发点和逻辑,“看不懂”是正常的,但是完全没有必要“看不惯”。如果还要大发议论,把自己的“看不惯”到处传播,你的伙伴就只会越来越少。


有人说在北上广深这样的大城市,人和人之间距离遥远,缺人情味,太冷漠。


这不是冷漠,而是对“和自己不一样”的宽容,这份宽容就是我们在向文明社会靠拢的标志。


5.懂得如何打扮


还记得斯大林的故事吗?在他离开校园之后,从头到脚都经过精心设计,不是为了精神好看,而是要让自己看起来就像一位投身革命事业的进步青年。


有句老话叫做“先敬罗衣后敬人”,本意是讽刺那些根据衣饰打扮来评价一个人的现象。我们自己在做判断的时候要尽量避免受到这类偏见的影响,但是对他人可能存在的偏见一定要心中有数。人是视觉动物,穿着打扮是“人设(人物设定)”的一部分,在我们开口说话之前,衣饰鞋袜就已经传达了无数信息。


想要成为职场当中受欢迎的人,穿着打扮的风格就要和公司的调性保持一致,最安全的做法是向你的同事靠拢。


在一个风格统一的群体当中,“与众不同”这件事自带攻击性。如果在事业单位之类的上年纪同事比较多的地方上班,马卡龙色的衣服和颜色夸张的口红,最好等到下班时间再上身。


这不是压抑天性,而是自我保护和职业精神。


6.和优秀的人站在一起


在职场上,优秀的人品质都是相似的:勤奋,自律,不断精进。如果发现了这样的同事,就要尽量和他们保持良好关系。


但是,单纯的日常沟通并不足以让你们成为盟友,正式结盟往往是通过利益交换和分享:当你遇到棘手的工作任务,就可以主动邀请对方共同跟进,同时将一部分利益让出去。愉快的合作是关系飞跃的最好契机。


优秀的人能认可的,通常也都是自己的同类。如果你能获得他们的称许和背书,在同事当中的地位自然会有所提升。


7.知道如何求助


前两天有一位关系户同学留言说,自己即将去实习,因为家人的关系可以得到一些行业资深专家的指点,问自己应该如何表现,是不是不懂就要问,像“好奇宝宝”一样,对方就会觉得自己好学上进。


我告诉她说,不要上去就问,有任何疑惑都先用搜索引擎找一下答案,如果找不出来,再带着你搜到的细节去询问那些资深前辈。


互联网时代有个很大的变化,就是人们获取信息的成本大大降低。善用搜索引擎寻找答案,就能更快、更精准、更全面地找到自己想要的东西,这种方式比跑到对方工位边用嘴问效率高得多。


凡事都问,只会让人觉得你的文字阅读能力有限,同时既不把自己的时间当回事,也不尊重别人的时间。尤其对方还是行业中的专家,他们的时间一定比实习生的宝贵多了。如果网上找不到答案,再带着细节去仔细咨询,这样的请教才是高效的,才能证明你是一个“好学上进”的人。


职场不是校园,不会再有一群老师专门负责手把手地教你,不轻易占用其他同事的时间会让你成为一个自立、有分寸、受尊重的人。毕业之后,你取得进步的速度、最终的上升空间,都和使用搜索引擎寻找答案的能力呈正相关。


8.技巧地送出小恩小惠


小恩小惠带两个“小”字,并不意味着这是一种微末小技。事实上,即使是最普通的零食,只要讲究得法,都可以送到人心里。


你的同事当中有没有因为宗教信仰而忌口的情况?


甲和乙爱吃辣,丙和丁爱吃甜,是不是两种口味都来上一点?


要留心同事的自我暴露,最好是用一个小本本记下来,关键时刻可能派上大用场。大家都是成年人,不会像孩子一样轻易被小恩小惠打动,打动我们的往往是“你把我放在心上”的温暖。


9.良好的情绪管理能力


很多时候这是个隐藏特征,但是自带“一票否决”属性:平时表现得沉着稳重,周围同事们不会有特别明显的感觉,然而歇斯底里和失控只要有一次,之前苦心经营的人设就会全面崩塌。情绪不稳定的人一般没人敢惹,但是也没人会在意了:你会被视为一个“病人”,很难再有大的发展。


已经发泄出去的情绪不能收回来,这个时候不要反复陷入纠结和悔恨,待在情绪里不出来,钱花出去了就不要去想,不要去比价。


如果情绪失控了,应该立刻做到的是原谅自己,然后考虑如何不再有下一次失控。要知道大多数人一辈子都至少会换三四次工作,了不起是换个地方,重新再来。


有的人特别幸运,天生长得好看,容易被人喜欢。


如果不是让人眼前一亮的高颜值人士,就不要太心急了。


成为一个自律、行为可以预期的人,也能慢慢地被别人喜欢。


人生很长,

作者:程序员小高
来源:juejin.cn/post/7255589558996992059
被人喜欢这件事,我们不用赶时间。

收起阅读 »

开发者不需要成为 K8s 专家!!!

之前有一篇文章 “扯淡的DevOps,我们开发者根本不想做运维!” 得到了许多开发者的共鸣,每一个开发人员,都希望能够抛却运维工作,更专注于自己开发的代码,将创意转化为令人惊叹的应用。然而事不尽如人意,到了云原生时代,开发者的运维工作似乎并没有减少,而是变成了...
继续阅读 »

之前有一篇文章 “扯淡的DevOps,我们开发者根本不想做运维!” 得到了许多开发者的共鸣,每一个开发人员,都希望能够抛却运维工作,更专注于自己开发的代码,将创意转化为令人惊叹的应用。然而事不尽如人意,到了云原生时代,开发者的运维工作似乎并没有减少,而是变成了在 K8s 上的应用部署和管理。


对运维人员来说,只需要维护好底层的 K8s,便可以在弹性、便捷性上得到巨大提升。然而 K8s 对于我们开发者而言还是太复杂了,我们还需要学习如何打包镜像以及 K8s 相关知识。许多时间都浪费在了应用部署上,我们真的需要成为 K8s 专家吗?我只是想部署一个应用至于那么复杂吗?你是否曾想过,能否有平台或方法,让我们不必成为 K8s 专家,甚至都不需要懂 K8s 就能部署好你的应用,轻松管理应用?


实际面临的问题


对于我们开发者而言,总会遇到以下不同的场景,也许是公司层面的问题、又或是业务层面的问题,也许现在用传统部署方式很简单,但随着业务增长,又不得不迁移。而面对这些问题,我们也要发出自己的声音。




  • 身处小公司,没有专门的运维。需要程序员自己负责写 Dockerfile + YAML + Kustomize 然后部署到 k8s 上面。除了工作量以外,还面临 K8s 自身的复杂性,对于多套业务,Dockerfie、Yaml、CI、CD 脚本占据了绝大部分的工作量。不写这些行不行?




  • 公司内的微服务越来越复杂,在写代码的基础上还得考虑各个服务之间的通信、依赖和部署问题,毕竟除了我们开发者以外,运维人员也不会比你更熟悉微服务之间的复杂依赖。也许已经开始尝试 Helm ,但是编写一个完整的 Chart 包依然是如此复杂,还可能面临格式问题、配置解耦不完全导致的换个环境无法部署问题。时间全写 Yaml 了。不额外编写 Helm Chart,直接复制应用行不行?




  • 在大型企业内部,正处于在传统应用迁移到云环境的十字路口。面对多种集群的需求、现有应用的平稳迁移、甚至一些公共的模块的复用如何做都将成为我们需要解决的问题。不要每次都重新开发,把现有的应用或模块积累下来行不行?




在这些场景下,我们大量的时间都消耗在额外的 Dockerfile、Yaml、Helm Chart 这些编写上了。K8s 很好,但似乎没解决我们开发者的问题,我们开发者用 K8s 反而变得更加复杂。不说需要额外编写的这些文件或脚本,单单是掌握 K8s 的知识就需要耗费大量时间和精力。
这些问题真的绕不过去吗?我觉得不是。来了解下 Rainbond 这个不需要懂 K8s 的云原生应用管理平台吧。谁说只有成为 K8s 专家后,才能管理好你的应用?


为什么是 Rainbond?


Rainbond 是一个不需要懂 K8s 的应用管理平台。不用在服务器上进行繁琐操作,也不用深入了解 K8s 的相关知识。Rainbond 遵循“以应用为中心”的设计理念。在这里只有你的业务模块和应用。每一个业务模块都可以从你的代码仓库直接部署并运行,你不是 K8s 专家也可以管理应用的全生命周期。同时利用 Rainbond 的模块化拼装能力,你的业务可以灵活沉淀为独立的应用模块,这些模块可以随意组合、无限拼装,最终构建出多样化的应用系统。


1. 不懂 K8s 部署 K8s 行不行?


行! 对于很多初学者或者开发人员来说,如果公司内部已经搭建好了可用的 K8s 平台,那么这一步不会是需要担心的问题。但是对于一些独立开发者而言,却很难有这样的环境,而 Rainbond 就提供了这样的解决方案,在 Linux 服务器上,只需要先运行一个 Docker 容器,访问到 Rainbond 控制台后,再输入服务器的 IP 地址,即可快速部署一套完整的 K8s 集群。


add_cluster


如果这还是太复杂,那么可以尝试使用 Rainbond 的快速安装,只需要一个容器和 5 分钟时间,就能为你启动一个带 K8s 集群的平台,而你在平台上部署的业务也都会部署到这个集群中。


2. 不想或不会写 Dockerfile、Yaml 等文件能不能部署应用?


能! Rainbond 支持自动识别各类开发语言,不论你是使用哪种开发语言,如Java、Python、Golang、NodeJS、Dockerfile、Php、.NetCore等,通过简单的向导式流程,无需配置或少量配置,Rainbond 都能够将它们识别并自动打包为容器镜像,并将你的业务快速部署到 K8s 集群中进行高效管理。你不再需要编写任何与代码无关的文件。只需要提供你的代码仓库地址即可。


source_code_build


3. 各类业务系统如何拼装?


在 Rainbond 中,不同的业务程序可以通过简单的连线方式进行快速编排。如果你需要前端项目依赖后端,只需打开编排模式,将它们连接起来,便能迅速建立依赖关系,实现模块化的拼装。这为你的应用架构带来了极大的灵活性,无需复杂的配置和操作,即可快速构建复杂的应用系统。


同时如果你已经实现了完整的业务程序,它可能包含多个微服务模块,你还可以将其发布到本地的组件库实现模块化的积累。下次部署时可以直接即点即用,且部署后可以与你其他应用程序再次进行拼装。实现无限拼装组合的能力。


component_assembly


4. 不会 K8s 能不能管理部署好的应用?


没问题! Rainbond 提供了面向应用的全生命周期管理运维,不需要学习 Kubectl 命令,也不需要知道 K8s 内复杂的概念,即可在页面上一键管理应用内各个业务模块的批量启动、关闭、构建、更新、回滚等关键操作,同时还支持应用故障时自动恢复,以及应用自动伸缩等功能。同时还支持应用 http 和 tcp 策略的配置,以及相应的证书管理。


app_manage


如何使用?


在 Linux 终端执行以下命令, 5 分钟之后,打开浏览器,输入 http://<你的IP>:7070 ,即可访问 Rainbond 的 UI 了。


curl -o install.sh https://get.rainbond.com && bash ./install.sh

作者:Rainbond开源
来源:juejin.cn/post/7268539925086519353

收起阅读 »

Amazon SageMaker 让机器学习轻松“云上见”!

最近,“上云探索实验室”活动 正在如火如荼进行,亚马逊云、“大厂”背景加持,来看看它们有什么新鲜技术/产品? 本篇带来对 shouAmazon SageMaker 的认识~ 闻名不如一见 首先,开宗明义,shouAmazon SageMaker 是什么? 从...
继续阅读 »

最近,“上云探索实验室”活动 正在如火如荼进行,亚马逊云、“大厂”背景加持,来看看它们有什么新鲜技术/产品?


本篇带来对 shouAmazon SageMaker 的认识~


闻名不如一见


首先,开宗明义,shouAmazon SageMaker 是什么?




官网我们可以了解到:Amazon SageMaker 是一项帮助开发人员快速构建、训练和部署机器学习 (ML) 模型的托管服务。


其实,类比于:就像咱们开发人员经常把代码部署 Github Page 上,自动构建、运行我们的代码服务。


当下,我们可以把 AIGC 领域火爆的模型共享平台 —— Hugging Face 中的模型放到 Amazon SageMaker 中去部署运行、进行微调等~


如果你还不了解 HuggingFace?那抓紧快上车!




就这张小黄脸,目前已经估值 20 亿美元,它拥有 30K+ 的模型,5K+ 的数据集,以及各式各样的 Demo,用于构建、训练最先进的 NLP (自然语言处理)模型。


是的,如果你:


1、不想关心硬件、软件和基础架构等方面的问题


2、想简化操作机器学习模型的开发流程


3、想灵活选择使用自己的算法和框架以满足不同业务需求


就可以 把目光投向 Amazon SageMaker,用它的云服务来部署你想要用的 HuggingFace 模型等~




百思不如一试


Amazon SageMaker 可以全流程的帮助我们构建机器学习模型,这样真的会省下很多心力(少掉几根头发)~


具体实践中,我们知道在数据预处理过程中,在训练模型之前,需要做一系列操作,比如:缺失值处理、数据归一化和特征选择等,Amazon SageMaker 提供了很好的预处理和转换数据的工具,助力快速完成这些工作。


在模型选择环节,Amazon SageMaker 提供多种内置的机器学习算法和框架,你可以根据数据集和任务类型选择合适的模型。


还有,提高模型性能是我们需要特别关注的,Amazon SageMaker 让你可以指定调优的目标和约束条件,系统会自动搜索最优的参数组合,这就很智能、很舒服。


模型训练完后,Amazon SageMaker 也自带易用的模型部署和监控功能;


一整套下来,训练模型感觉就像呼吸一样自然~


官网教程写的很清晰,还有很多视频讲解:# Amazon SageMaker - 入门手册


这里不赘述,仅看实战中代码表示,感受一二:


如何在Amazon SageMaker 上部署Hugging Face 模型?




1、首先,在 Amazon SageMaker 中创建一个 Notebook 实例。可以使用以下代码在 Amazon SageMaker 中创建 Notebook 实例:

import sagemaker from sagemaker
import get_execution_role

role = get_execution_role()

sess = sagemaker.Session()

# 创建 Notebook 实例
notebook_instance_name = 'YOUR_NOTEBOOK_INSTANCE_NAME'
instance_type = 'ml.t2.medium'
sagemaker_session = sagemaker.Session()
sagemaker_session.create_notebook_instance(notebook_instance_name=notebook_instance_name,instance_type=instance_type,role=role)


2、其次,下载 Hugging Face 模型,你可以使用以下代码下载 Hugging Face 模型:

!pip install transformers

from transformers import AutoTokenizer, AutoModelForSequenceClassification

# 下载 Hugging Face 模型
model_name = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

3、创建一个推理规范,以指定部署的模型。可以参照以下代码创建推理规范:

from sagemaker.predictor import Predictor
from sagemaker.serializers import JSONSerializer
from sagemaker.deserializers import JSONDeserializer
from sagemaker.tensorflow.serving import Model

# 创建推理规范
class HuggingFacePredictor(Predictor):
def __init__(self, endpoint_name, sagemaker_session):
super().__init__(endpoint_name, sagemaker_session, serializer=JSONSerializer(),
deserializer=JSONDeserializer())

model_name = "huggingface"
model_data = "s3://YOUR_S3_BUCKET/YOUR_MODEL.tar.gz"
entry_point = "inference.py"
instance_type = "ml.m5.xlarge"
role = sagemaker.get_execution_role()

model = Model(model_data=model_data,
image_uri="763104351884.dkr.ecr.us-east-1.amazonaws.com/huggingface-pytorch-inference:1.6.0-transformers4.0.0",
role=role,
predictor_cls=HuggingFacePredictor,
entry_point=entry_point)

predictor = model.deploy(initial_instance_count=1, instance_type=instance_type)


4、测试部署模型

data = {"text": "I love using Amazon SageMaker!"}
response = predictor.predict(data)

print(response)

过程就是,创建 NoteBook => 下模型 => 指定模型、设定推理脚本 => 测试部署,足够简洁~


人人都能上云,人人都能训练机器模型~


不知道大家发现没有,其实现在的编程开发都逐渐在“云”化,不仅是机器学习,普通开发也是;类似低代码平台,开发不再是一点点复制代码、拼凑代码、修改代码,更多是通过自动化平台“点点点”就能构建自己想要的服务了!拥抱“云”平台,省心又省力,也在拥抱未来~


防守不如亮剑


目前市面上同类型的产品也有一些,比如:


1、Google Cloud AI Platform:Google提供的全托管的机器学习平台,可以帮助用户构建、训练和部署机器学习模型。


2、Microsoft Azure Machine Learning:微软提供的一款全托管的机器学习平台,可以帮助用户构建、训练和部署机器学习模型。


3、IBM Watson Studio:IBM提供的一款机器学习工具和服务平台,可以帮助用户构建、训练和部署机器学习模型。此外它还提供了数据可视化、模型解释和协作等功能。


它们与Amazon SageMaker 类似,具有全托管和自动化的特点,同时提供了多种算法和框架供用户选择。但尺有所长、寸有所短,咱们不妨用表格来一眼对比它们各自的优缺点:



可以看到,Amazon SageMaker 的配置更简单;


至于谈到:“需掌握 AWS 服务”,其实它也好上手,类比于阿里云,AWS 是亚马逊云服务,全球顶流、百万客户、加速敏捷,即使不用来开发机器模型,也建议体验、上手其它云服务产品~具体参见aws.amazon.com




活动福利环节


刚好,最近正在进行“上云探索实验室”活动;6月27-28日亚马逊云科技中国峰会上海现场,展区5楼【开发者会客厅】有互动活动:


现场为开发者提供了部分 Amazon codewhisperer 账号,开发者可以到现场直接进行代码体验,参与体验问卷回复可获得社区定制周边礼品一份。


同时开发者也可于现场进行专业版账号注册,完成注册即可获得定制周边礼品一份或免费 Serverlesspresso 咖啡券一张。


欢迎大家注册~~


话不多少,先冲为敬~




作者:掘金安东尼
链接:https://juejin.cn/post/7244526264618680380
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

什么?同一张图在Flutter和iOS原生渲染出来效果不一样?

iOS
最近遇到了一个很奇怪的现象,同样的一张图在iOS原生能正常显示,但是在Flutter就显得很暗 具体往下看 现象 可以很清晰的看到iOS对于原图的还原度要比Flutter好太多 思考 看到这种场景顿时懵逼,明明是一张图,不至于Flutter这么拉胯吧,但是这...
继续阅读 »

最近遇到了一个很奇怪的现象,同样的一张图在iOS原生能正常显示,但是在Flutter就显得很暗
具体往下看


现象


截屏2023-08-18 16.49.37.png


可以很清晰的看到iOS对于原图的还原度要比Flutter好太多


思考


看到这种场景顿时懵逼,明明是一张图,不至于Flutter这么拉胯吧,但是这个现象是做了这么久的Flutter第一次遇见(或者是之前没太仔细看小的icon,这次大图渲染更加直观。。),所以觉得这个事情或许没那么复杂。于是开始怀疑是Impeller的问题,但是我尝试了@恋猫de小郭的文章里的
在iOS工程中修改info.plist


<key>FLTEnableImpeller</key>
<true />

尝试关闭Impeller,但是并没有什么效果,所以初步怀疑Impeller的思路或许不太对,所以就开始看之前的在Flutter显示正常的图,最后发现了一个微妙之处,在图片的简介里有一个我不懂的地方不一样,就是颜色描述文件


WechatIMG394.jpg


这里询问chatgpt颜色描述文件:sRGB IEC61966-2.1Display P3有什么区别,得到的回答是



sRGB IEC61966-2.1和Display P3是两种不同的颜色描述文件,它们主要用于不同的显示设备和应用场景。它们的区别主要在于颜色范围和应用领域。



  1. sRGB IEC61966-2.1:sRGB是一种标准的RGB颜色空间,广泛用于互联网、数字摄影和普通的显示设备上。sRGB IEC61966-2.1的颜色范围相对较窄,适合一般图像显示,它的色域较小,能够在大多数常见显示设备上提供相对一致的颜色显示。sRGB通常是图像文件的默认颜色描述文件。

  2. Display P3:Display P3是一种更宽广的颜色空间,偏向于更鲜明和逼真的色彩展示。它主要用于高级的显示设备,如苹果公司的Retina显示器。相对于sRGB,Display P3能够呈现更宽广的颜色范围,对于鲜艳和饱和度较高的颜色效果更为明显。 总结来说,sRGB IEC61966-2.1适合一般的互联网应用和常见显示设备,而Display P3则适用于高级显示设备,如高分辨率显示器和专业图形处理工作。在选择使用哪一种颜色描述文件时,需要考虑图像的应用场景和目标设备的能力来做出合适的选择。



呃。。好像看不出来咋回事,后来看到一篇文章说到Flutter对于Display P3的支持问题,具体意思就是原因就是 Flutter 直接把 Display P3 色域当做 sRGB 色域的图像处理了,而没有做色域转换,这一下就真相大白了~。


解决办法


文章中提到可以让原生来处理图片


CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
NSUInteger frameCount = CGImageSourceGetCount(src);
if (frameCount > 0) {
NSDictionary *options = @{(__bridge NSString *)kCGImageSourceShouldCache : @YES,
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @NO
};
NSDictionary *props = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(src, (size_t) 0, (__bridge CFDictionaryRef)options));
NSString *profileName = [props objectForKey:(NSString *) kCGImagePropertyProfileName];
if ([profileName isEqualToString:@"Display P3"]) {

NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destRef = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)data, kUTTypePNG, 1, NULL);

NSMutableDictionary *properties = [NSMutableDictionary dictionary];
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(1);
properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(0);

properties[(__bridge NSString *)kCGImagePropertyNamedColorSpace] = (__bridge id _Nullable)(kCGColorSpaceSRGB);
properties[(__bridge NSString *)kCGImageDestinationOptimizeColorForSharing] = @(YES);

CGImageDestinationAddImageFromSource(destRef, src, 0, (__bridge CFDictionaryRef)properties);

CGImageDestinationFinalize(destRef);
CFRelease(destRef);
return data;

}
}

return imageData;

这里偷懒了,因为找UI小姐姐让她切图的时候调整一下就可以了~,最后的解决方案是UI根据设计稿导出sRGB IEC61966-2.1类型的图片,同时这个图片的色值是向Display P3

作者:Jerry815
来源:juejin.cn/post/7268539503907307520
code>靠拢的,至此问题解决。

收起阅读 »

新一代前端工具链Rome:革新前端开发

web
在前端开发领域,每时每刻都在涌现着各种新的工具和框架。而 Rome,作为一款新一代前端工具链,引起了广泛的关注和热议。它不仅提供了卓越的性能,还整合了各种强大的功能,使前端开发变得更加高效。本文将深入介绍 Rome,并为你提供一些代码示例,帮助你更好地了解和使...
继续阅读 »

在前端开发领域,每时每刻都在涌现着各种新的工具和框架。而 Rome,作为一款新一代前端工具链,引起了广泛的关注和热议。它不仅提供了卓越的性能,还整合了各种强大的功能,使前端开发变得更加高效。本文将深入介绍 Rome,并为你提供一些代码示例,帮助你更好地了解和使用这个令人激动的工具。


image.png
image.png


Rome 是什么?


Rome 是一个全新的前端工具链,旨在重新定义前端开发体验。它是一个一站式解决方案,涵盖了许多前端开发中常见的问题和任务。Rome 的主要目标是提供一致性和高性能,以加速前端开发流程。它的核心特点包括:


1. 依赖管理


Rome 提供了强大的依赖管理系统,可用于管理你的项目中的依赖关系。它支持 JavaScript、TypeScript 和 Flow,并能够准确地分析和处理依赖项,以确保你的项目始终保持一致。


// 安装依赖
rome deps add react

// 查看依赖树
rome deps list

这个特性非常有用,因为它将所有的依赖关系都纳入统一管理,无需依赖其他工具。


2. 代码格式化


Rome 自带了一个先进的代码格式化工具,可帮助你统一项目中的代码风格。无需争论缩进或分号,Rome 将自动处理这些问题。


# 格式化整个项目
rome format

代码格式化对于团队协作和维护项目非常重要,它可以消除代码风格的争议,使代码更易读。


3. 静态类型检查


Rome 集成了强大的静态类型检查器,可以在编码过程中捕获潜在的类型错误,提高代码质量和可维护性。它支持多种类型系统,包括 TypeScript、Flow 等。


// 检查类型
rome check

这个特性有助于减少运行时错误,提前发现潜在的问题。


4. 构建工具


Rome 提供了一套强大的构建工具,可用于将你的代码编译成浏览器可执行的代码。这有助于优化性能并减小最终部署包的大小。


# 构建项目
rome build

Rome 的构建工具支持多种目标,你可以轻松地将项目构建成不同的输出格式。


5. 包管理


Rome 不仅支持 JavaScript 包管理,还可以处理 CSS、图片、字体等多种资源。这意味着你可以在一个地方管理所有资源,而无需额外的工具。


// 导入 CSS 文件
import './styles.css';

这个特性使得资源管理变得更加一致和方便。


Rome 的安装和配置


现在,让我们来看看如何安装 Rome 并进行基本配置。


步骤 1:安装 Rome


你可以使用 npm 或 yarn 安装 Rome。这里以 npm 为例:


npm install -g rome

安装完成后,你可以在终端中运行 rome -v 来确认 Rome 是否成功安装。


步骤 2:初始化项目


在你的项目目录中,运行以下命令来初始化一个 Rome 项目:


rome init

这将创建一个 .romerc.js 文件,其中包含了


项目的配置信息。


步骤 3:配置选项


你可以编辑 .romerc.js 文件来配置 Rome 的选项,以满足你的项目需求。例如,你可以指定项目的目标环境、依赖管理方式、构建选项等。


// .romerc.js
module.exports = {
target: 'browser',
module: {
type: 'commonjs',
},
build: {
minify: true,
},
};

使用 Rome


一旦你的项目配置好了,就可以开始使用 Rome 提供的工具来进行开发。以下是一些常用的命令和示例:


运行 linter 来检查代码风格和潜在问题。


rome check

运行格式化程序来自动格式化你的代码。


rome format

运行类型检查以确保类型安全。


rome typecheck

构建你的项目。


rome build

运行你的应用程序。


rome run

自定义和插件


Rome 还支持自定义插件和配置。你可以编写自己的插件,或者将现有的插件集成到你的项目中,以满足特定需求。


// .romerc.js
module.exports = {
custom: {
myPlugin: {
enabled: true,
options: {
// 自定义选项
},
},
},
};

在这个示例中,我们启用了一个名为 "myPlugin" 的自定义插件,并提供了一些自定义选项。


Rome发展前景




  1. 性能优化: Rome 的核心目标之一是提供高性能。未来,它将不断进行性能优化,以确保更快的构建和更快的开发体验。随着前端项目变得越来越复杂,性能优化将成为前端开发的重要问题,Rome 在这方面有望发挥重要作用。




  2. 更好的类型检查: Rome 集成了静态类型检查器,帮助开发者在编码阶段捕获潜在的类型错误。未来,这个类型系统可能会进一步增强,提供更多的类型推断和错误检查功能。




  3. 更多的插件和扩展: Rome 的自定义插件和配置功能使得开发者可以根据自己的需求扩展 Rome。随着社区的不断壮大,预计会有更多的插件和扩展出现,为开发者提供更多的选择和解决方案。




  4. 更广泛的应用领域: Rome 目前主要用于前端开发,但未来可能会扩展到其他领域,如后端开发或跨平台开发。这将使 Rome 成为一个更加通用的工具链,适用于各种不同类型的项目。




  5. 更丰富的文档和教程: 随着 Rome 的发展,预计会有更多的文档、教程和学习资源出现,帮助开发者更好地掌握和使用 Rome。这将有助于扩大 Rome 的用户群体。




  6. 更强大的生态系统: Rome 的生态系统将继续扩大,包括各种开发工具、编辑器插件和整合解决方案。这将使 Rome 成为一个完整的开发生态系统,为开发者提供一站式的解决方案。




总之,Rome作为一个新一代前端工具链,充满了潜力,未来有望在前端开发领域取得更多的成功和影响力。开发者可以继续关注 Rome 的发展,利用它来提高前端开发效率和质量。


结语


Rome 是一个前端工具链的新星,它为前端开发者提供了许多强大的功能,以提高开发效率和代码质量。虽然 Rome 还在不断发展和改进中,但它已经展现出了巨大的潜力。无论你是新手还是经验丰富的前端开发者,都值得一试 Rome,看看它如何改变你的前端开发流程。在使用 Rome 时,记得查阅官方文档以获取更多详细信息和示例代码。希望这篇文章能帮助你入门 Ro

作者:侠名风
来源:juejin.cn/post/7269745800178925603
me 并开始在你的项目中使用它。

收起阅读 »

JS 不写分号踩了坑,但也可以不踩坑

web
踩的坑 写一个方法将秒数转为“xx天xx时xx分xx秒”的形式 const ONEDAYSECOND = 24 * 60 * 60 const ONEHOURSECOND = 60 * 60 const ONEMINUTESECOND = 60 functi...
继续阅读 »

踩的坑


写一个方法将秒数转为“xx天xx时xx分xx秒”的形式


const ONEDAYSECOND = 24 * 60 * 60
const ONEHOURSECOND = 60 * 60
const ONEMINUTESECOND = 60

function getQuotientandRemainder(dividend,divisor){
const remainder = dividend % divisor
const quotient = (dividend - remainder) / divisor
return [quotient,remainder]
}

function formatSeconds(time){
let restTime,day,hour,minute
restTime = time
[day,restTime] = getQuotientandRemainder(restTime,ONEDAYSECOND)
[hour,restTime] = getQuotientandRemainder(restTime,ONEHOURSECOND)
[minute,restTime] = getQuotientandRemainder(restTime,ONEMINUTESECOND)
return day + '天' + hour + '时' + minute + '分' + restTime + '秒'
}
console.log(formatSeconds(time)) // undefined天undefined时undefined分NaN,NaN秒

按照这段代码执行完后,day、hour、minute这些变量得到的都是 undefined,而 restTime 则好像得到一个数组。

问题就在于 13、14、15、16 行之间没有添加分号,导致解析时,没有将这三行解析成三条语句,而是解析成一条语句。最终的表达式就是这样的:


restTime = time[day,restTime] = getQuotientandRemainder(restTime,ONEDAYSECOND)[hour,restTime] = getQuotientandRemainder(restTime,ONEHOURSECOND)[minute,restTime] = getQuotientandRemainder(restTime,ONEMINUTESECOND)

那执行的过程相当于给 restTime 进行赋值,表达式从左往右执行,最终表达式的值为右值。最右边的值就是 getQuotientandRemainder(restTime,ONEMINUTESECOND),由于在计算过程中 restTime 还没有被赋值,一直是 undefined,所以经过 getQuotientandRemainder 计算后得到的数组对象每个成员都是 NaN,最终赋值给 restTime 就是这样一个数组。


分号什么时候会“自动”出现


有时候好像不写分号也不会出问题,比如这种情况:


let a,b,c
a = 1
b = 2
c = 3
console.log(a,b,c) // 1 2 3

这是因为,JS 进行代码解析的时候,能够识别出语句的结束位置并“自动添加分号“,从而能够解析出“正确”的抽象语法树,最终执行的结果也就是我们所期待的。

JS 有一个语法特性叫做 ASI (Automatic Semicolon Insertion),就是上面说到的”自动添加分号”的东西,它有一定的插入规则,在满足时会为代码自动添加分号进行断句,在我们不写分号的时候,需要了解这个规则,才能不踩坑。(当然这里说的加分号并不是真正的加分号,只是一种解析规则,用分号来代表语句间的界限)


ASI 规则


JS 只有在出现换行符的时候才会考虑是否添加分号,并且会尽量“少”添加分号,也就是尽量将多行语句合成一行,仅在必要时添加分号。


1. 行与行之间合并不符合语法时,插入分号


比如上面那个自动添加分号的例子,就是合并多行时会出现语法错误。

a = 1b = 2 这里 1b 是不合法的,因此会加入分号使其合法,变为 a = 1; b = 2


2. 在规定[no LineTerminator here]处,插入分号


这种情况很有针对性,针对一些特定的关键字,如 return continue break throw async yield,规定在这些关键字后不能有换行符,如果在这些关键字后有了换行符,JS 会自动在这些关键字后加上分号。
看下面这个例子🌰:


function a(){
return
123
}
console.log(a()) // undefined

function b(){
return 123
}
console.log(b()) // 123

在函数a中,return 后直接换行了,那么 return 和 123 就会被分成两条语句,所以其实 123 根本不会被执行到,而 return 也是啥也没返回。


3. ++、--这类运算符,若在一行开头,则在行首插入分号


++ 和 -- 既可以在变量前,也可以在变量后,如果它们在行首,当多行进行合并时,会产生歧义,到底是上一行变量的运算,还是下一行变量的运算,因此需要加入分号,处理为下一行变量的运算。


a
++
b
// 添加分号后
a
++b

如果你的预期是:


a++ 
b

那么就会踩坑了。


4. 在文件末尾发现语法无法构成合法语句时,会插入分号


这条和 1 有些类似


不写分号时需要注意⚠️


上面的 ASI 规则中,JS 都是为了正确运行代码,必须按照这些规则来分析代码。而它不会做多余的事,并且在遵循“尽量合并多行语句”的原则下,它会将没有语法问题的多行语句都合并起来。这可能违背了你的逻辑,你想让每行独立执行,而不是合成一句。开头贴出的例子,就是这样踩坑的,我并不想一次次连续的对数组进行取值🌚。

因此我们要写出明确的语句,可以被合并的语句,明确是多条语句时需要加上分号。


(如果你的项目中使用了某些规范,它不想让你用分号,别担心,它只是不想让你在行尾用分号,格式化时它会帮你把分号移到行首)像这样:


// before lint
restTime = time;
[day, restTime] = getQuotientandRemainder(restTime, ONEDAYSECOND);
[hour, restTime] = getQuotientandRemainder(restTime, ONEHOURSECOND);
[minute, restTime] = getQuotientandRemainder(restTime, ONEMINUTESECOND);

// after lint
restTime = time
;[day, restTime] = getQuotientandRemainder(restTime, ONEDAYSECOND)
;[hour, restTime] = getQuotientandRemainder(restTime, ONEHOURSECOND)
;[minute, restTime] = getQuotientandRemainder(restTime, ONEMINUTESECOND)

参考


收起阅读 »

生病的35岁程序员

这两天又生病了,应该来说我这差不多一个月生了三回病。一回病是感冒,然后第二次呢是新冠的二阳,然后第三次就是昨天发烧,后面被检测是肺炎。 俗话说,病来如山倒,病去如抽丝。我觉得一旦当生病的时候,就会开始怀疑人生,去思考人生,当病好的时候,又把自己生病时候的病痛忘...
继续阅读 »


这两天又生病了,应该来说我这差不多一个月生了三回病。一回病是感冒,然后第二次呢是新冠的二阳,然后第三次就是昨天发烧,后面被检测是肺炎。


俗话说,病来如山倒,病去如抽丝。我觉得一旦当生病的时候,就会开始怀疑人生,去思考人生,当病好的时候,又把自己生病时候的病痛忘得一干二净,好像疼痛就不曾存在过。


可能当你十几岁或者二十几岁的时候,你对生病两个字没有特别的深刻的概念,也就是你会把生病认为是一个很平常的事情,甚至很多人几年都不会有一种影响生活的大病。


可是你到了30多岁的时候,你生病的时候,你就开始思考人生了。因为到了30岁,整体的抵抗力可能已经开始下降了,不像二十几岁的青年,对一切病毒免疫,所以你对于病痛的体感会更加的明显。同时上有老下有小,你会思考,如果万一自己就这么挂掉的话,小孩,媳妇,老人怎么办?这个世界如果没有了我又会是怎么样的?是整个世界陷入一片混沌,还是我的意识就此消亡?我不存在的日子里面,地球和人类又会怎么发展?我是不是又会错过很多令人惊奇的事情?


对于大部分的职业来说,比如医生、律师、公务员,30岁左右应该是黄金时间,30岁左右是对于整个行业最应该有发言的时候。刚毕业的时候,一路荆棘和摸索,因为前面几年沉淀了很多经验,踩了很多坑,而到了30多岁,就是应该对人生和对自己的工作有一定洞见的时候。这个时候的男人女人,既有经历又有能力,应该能够更好的去输出给社会自己的价值。


而程序员确实不一样,很多人会羡慕程序员的高薪,高工资,高福利等等。但实际上很多程序员确实是在透支自己的生命和体力。比如长期996就是一种极度不正常的状态。当然这还算好的,在早点之前可能有极大的概率是要求007的。我的运气比较好,我基本上没有做过996的工作,但是我基本上是995。有极少的一段时间里面我是005。也就是我周末基本上没有加过班,这个也是我最值得庆幸的,当然有很多程序员可能就没有这么好的待遇了,很多中小型公司长期在996里不能自拔,一方面被老板的饼所吸引,另外一方面人在江湖,身不由己,谁不想快速改变自己的命运,而概念命运的稻草,就是程序员“狭隘”的眼光里看到的那点金元宝。


当然我觉得我的工作本身相对来说没有想象中的那么忙,但同样我觉得这个行业里面会给我们灌输或者浸润一种不太健康的状态。比如说你正常情况下来说你9:00下班,稍微折腾一下10点,11点你可能就能够睡觉了。但实际上却非常的难,因为一个人在一天的工作以后会进入一种疲劳状态,你就不自觉地想去放松一下,也就是叫报复性的刷手机。所以在9点~11点甚至12点这个时间段里面,你会去想办法来弥补出来自己在工作自己的那种休闲和放松。


所以9:00下班了情况下,你可能需要12点才能够真正地睡着。而正常的作息时间,朝九晚五才是一种最最最健康的工作时间。互联网给我们程序员带来了一种疲劳感,而且特别多的熬夜,而熬夜本身对身体就是有一种最大的伤害。这种伤害是持久的,并且在一定的时间里面,它甚至是不可逆的。


就如同慢性病一样,就因为它的慢,所以才导致了被很多人忽视,但是一旦这种慢性病叠加到了一定的程度以后,它就会变成了一种不治之症。比如空气污染,比如噪音,比如光污染,比如甲醛污染,这些所谓的社会上面的一些问题给我们带来的都是慢性的伤害,我们短期内没办法看到它的效果,但是时间一长一定会有极大的伤害。熬夜也是一样,我们这短期的一两天的熬夜根本不会影响什么,甚至像很多大学生通宵熬夜,第二天照样精力满满。但是量变引起质变,我们长时间的熬夜终究会带来反噬的效果,这一天可能会早,可能会晚,但它一定是有极度的伤害的。


所以我现在看来,我觉得一切都是有成本的。比如你选择了传统的行业,你可能赚到的钱不会多,但是大概率是可以持续的,到了互联网行业你可能赚得比较多,但大概率可能是不可持续的。也就是说不管怎么样,你付出的是你的时间和身体的成本。


我一个朋友他在前两年说了一句话,他说他完全不羡慕用时间去换取高工资的。我刚开始的时候还不理解,我认为如果你能赚到越来越多的钱,那你不就牛逼吗?现在我回想起来确实是的,这种不值得去鼓励,因为你付出的代价也非常的大。


所以如果以现在的视角看来的话,我觉得在这个行业里面你很难做到被这个行业不会被感染。你可能很多生活习惯和思维能力全部会被这个行业所渗透。就包括了很多不好的习惯,就比如说缺少运动,比如说熬夜,当然还有所谓的职场PUA。


所以我这里面也想跟大家分享一点经验,第一个就是可能还是要多运动,运动是能够缓解很多疲劳感以及增强免疫力的。我记得我之前在坚持运动和锻炼的时候,基本上没有生过特别大的病,只是偶尔极少数的感冒,并且也不会有那么严重。


第二个真的还是要养成一个良好的生活习惯,比如早睡早起。虽然我觉得现在已经有点难了,但是要改正起来确实也是有办法的,否则等待你的可能就是各种猝死,各种大病。这里不是危言耸听,虽然概率很小,但是很多程序员过了35岁,你就会发现他根本就不能够接受这种高强度,高压力,高时间的工作。而且并不是他主观上不能接受,因为对于30多岁的程序员来说,上有老下有小,其实经济压力也非常的大,但是身体各方面的信号已经告诉了他,不大适合这种高强度的加班,否则等待他的只是更恶的恶果。


第三就是要尽量减少被行业的影响。在加班文化,在熬夜文化盛行的今天,我们确实要真正问一下自己,这种是不是好的,我们能不能有办法去杜绝?在完全没有办法的情况下,我们能不能通过这样极少数的空隙,我们好好休息,好好锻炼?


最后,希望大家身体健康。

作者:ali老蒋
来源:juejin.cn/post/7269794410572922934

收起阅读 »

关于离职那些事的有感而发

一、面对离职的心态 每当有同事离职,在群里写下感谢的话语,最后来一句“后会有期”。 下面一定是一条又一条的“苟富贵、勿相忘”的祝福语,洋洋洒洒排起了长龙,婉若一首悲壮的赞歌。 而里面一定没有我。 其中的心态非常微妙,在平时,同事的输出我会积极点赞,分享知识会...
继续阅读 »

一、面对离职的心态


每当有同事离职,在群里写下感谢的话语,最后来一句“后会有期”。


下面一定是一条又一条的“苟富贵、勿相忘”的祝福语,洋洋洒洒排起了长龙,婉若一首悲壮的赞歌。



而里面一定没有我。


其中的心态非常微妙,在平时,同事的输出我会积极点赞,分享知识会打赏鼓励,可一旦离职,实在没办法说些积极的话,为什么呢?


可能是因为不再年轻了吧,时间改变了很多东西,毕竟见得多了,所以看得开了,也放得下了。


我10年正式参加工作,第一家是个小公司,公司虽小,五脏俱全,该有的工种都有。


其中,有个设计师很对我的胃口,很多设计理念不谋而合,我认同他的设计,他认同我的实现,可谓合作上的好搭档。


可是某一天,没有任何征兆的,其突然离职了,就很突然的那种,那种突然就好像,吃着火锅唱着歌,突然就被麻匪给劫了。


明明说好的一起做个伟大的产品的,你却潇洒出走不回头。



现在想想,当时那种遭遇背叛,遭受欺骗的感受是有些“幼稚”的,混合了过多的主观情感,是不理性的表现。


托尔斯泰曾经说过,友谊好比一壶开水,一旦离开了炉子,就会逐渐凉了下来,朋友尚且如此,更何况只是工作关系的同事?


当然,这种对于新人入职和同事离职变得足够理性的心态并不是一蹴而就的。


你要知道,当付出了很多心血、寄予诸多希望的同事突然离职,但是其另谋高就的地方并没有上升一个台阶,那种心情……就像辛苦种的白菜被猪拱了一样难受,久而久之,心态自然就麻了。


铁打的营盘流水的兵,走了就走了,无需留念。


人在职场,最重要的还是自我的提升,淡薄的人情牵系可有可无,至少对于我而言是这样的。


共谋事,自然互帮互助,共同成长,可你要是另谋高就,那就祝你一路顺风,心中祝福,行动上就别指望了。


OK,个人部分唠叨完了,下面从旁观者的角度,讲讲关于如何避免离职,以及催促离职的一些看法,纯属个人看法,不一定正确,仅供参考。


二、如何降低离职率


如果希望团队成员不要离职那么频繁,我觉得可以从下面几个方面入手(不能控制的部分,如公司福利,薪资什么的,不在讨论之列)。


1. 招聘把控


说两点。


其一,尽量不要选择工作一年一换,连换三四年的人,论迹不论心。


其二,如实介绍公司和团队的现状,不要捧太高,否则入职后发现和预期差别较大,肯定留不住的。


2. 关注成长


根据我的大胆预估,90%的技术人员都是项目驱动成长型人才。


如果不做项目,或者做些没什么技术含量的项目,其技术成长就是0。


所以,作为管理者,需要关注下面每个人的成长情况,很多人会比较内向,有想法也不会主动说出来,此时,就需要管理者时不时一对一沟通,或者通过其他方式收集信息,或者对成员的状态有敏锐的观察。


这就比较考验管理者的水平了。


当然,也是有策略可以帮助下面的人成长的。


一是项目轮换制,但要注意度,且不可频繁,或者为了轮换而轮换,跨度也不要太大,要和对方兴趣契合。


比方说王二是Web的,张三十做Node的,王二对Node并不感兴趣,但是你觉得全栈对个人发展有好处,就强行安排王二去做Node相关的工作,这就不合适。


二是孵化内部项目,团队总是需要一些基础建设的东西,或者说提效的工具等,可以立为项目,让下面的人都参与。


通常这类内部项目无需考虑兼容性和代码美不美丽,因此,可以满足开发人员使用新技术的爽感,一举多得。


三是建立好专业培训流程,基础内容可以固化,在内部系统中沉淀,业务技术和前沿发展可以使用分享会的形式输出。


虽然实际效果可能就那样,但是,重要的是让下面的人知道团队有这份心,或者说,让下面的人觉得自己成长了。


不过,我有必要提醒下,千万不要为了让团队成员干得开心,必有成长而过度保护。


什么意思呢?


身在商业公司,在一个大的团体中,必然是有所权衡,在技术使用上是需要有所克制的。


比方说对外项目使用过于新颖的技术,或者还不成熟的技术,或者生僻的技术,虽然干活的人开心了,但是产品呢,以后维护的人呢?


又比方说项目组希望你可以多做几个运营活动帮忙拉新,你不能说运营活动没有技术含量,对技术成长没帮助,就推脱或拒绝。


3. 不要过度倾向某个人


团队中有个人你非常看好,工作积极,产出高,质量好,你要作为骨干培养,你特别害怕他离职。


于是,各种资源都向他倾斜,每次绩效都是前列。


这种做法是危险的,过度倾向某个人,对于团队的稳定性是非常大的考验。


因为会让人觉得不公平,除非你能保证信息足够的透明,此人的工作与产出有足够了信服力。


说到信息透明,不得不单独讲一下。


4. 信息顺畅,上传下达


​如果管理者,可以在团队内部建立一种机制,也就是信息可以触及到每一个人,无论是上下传达,还是左右互通的信息,那你的团队一定会非常的健康,效率绝对不会差。


古代的昏君之所以是昏君,就是奸臣拦截了下面的信息,只让皇帝知道希望皇帝知道的事情,本质上就是信息不顺畅导致。


大到国家,小到团队,道理都是类似的。


这里举一个我知道的例子。


有一个很勤勉的同行,接到了很多需求,然后他连续数周都干活到凌晨,实在累得受不了,然后提了离职。


领导知道后都傻了,怎么突然离职了,干得好好的。


细问才知道,领导根本不知道需求方绕过他给同事安排了很多“紧急”需求,这名同事也没有向上反馈活太多,需要帮助,以为是领导默许的。


这就是信息不顺畅导致的,我估计,这名同行的周报也没有好好写。


5. 管理者自身的提高


兵熊熊一个,将熊熊一窝。


管理者需要不断提高自身的管理水平,要不断提高自己的影响力和话语权。


一方面可以通过业绩支撑来提高,另一方面可以通过日常表现去强化,即表现出足够的管理素养。


公平公正,信息传达,人文关怀等。


具体不展开,大家可以去找找一些资深前端管理人员写的心得,会有不少的帮助。


6. 文化与氛围建设


即要通过一些制度或手段,让团队成员平时产生更多的交集。


常见且效果不错的方法就是团建,而最常见的团建方式就是一起吃饭。


或者邀请其他部门,或者外部的人进行分享。


总之,能够让大家聚在一起方法,都是可以尝试的。


但是需要注意不要用力过猛,比方说安排周末时间团建,或者吃饭都是自掏腰包,那就适得其反了。


记住,我们的目的是让下面的人都感觉到是团队中的一员,精神有所寄托自然可以有效降低离职的意愿。


三、如何让人主动离职


林子大了,什么样的鸟都有。


自然,团队中可能会有不合适的员工,例如,总是传递负面情绪,上班总是玩游戏刷股票,代码质量总是很糟糕。


这样的员工显然是不适合待在团队的。


如果是我,直接找HR,谈N+1开除,无需拖泥带水,我的风格就是雷厉风行。


但是,咳咳,N+1毕竟成本高,所以,就有些公司或者个人有些小心思,有没有办法让某个不合适的员工主动离职呢?


对于这个问题,我也不知道怎么办,我又没做过这样的事情。


随后我网上搜寻了一些方法,简单整理了下。


首先,先礼后兵。


指出问题,希望达到什么样的结果。


如果结果好,那就说明员工进步了,自然也就没有离职一说。


如果还是和之前一样不行,那就低绩效,可以连续低绩效。


然后工作这块,可以安排边缘工作,重复工作。


通常,对自己还有些要求的人此时就会选择更合适的地方。


但有些人就是觉得好死不如赖活着,就算每月拿死工资,不干正事也可以,那可以找HR,他们经验丰富,知道该怎么办。


至于其他一些方法……咳咳,我什么都不知道。


OK,以上就是我想说的内容,也是一时之间的有感而发,欢迎点赞,欢迎

作者:张鑫旭
来源:juejin.cn/post/7269792419544170548
评论交流。


(完)

收起阅读 »

情人节这天,跟女友 研究点餐小程序的目录 是怎么搞的?

web
今天情人节(七夕特辑) 🏩 背景(餐厅) 现在去餐厅订餐,桌角那里差不多都会有一个二维码,叫你扫码自己手机上点菜。扫码进去,要么是公众号的,要么就是小程序。 那我们来看看一个以下案例: 除了点餐系统外,还有一些文档目录用到这种方式的目录导航锚点。比如说...
继续阅读 »

今天情人节(七夕特辑)



🏩 背景(餐厅)



现在去餐厅订餐,桌角那里差不多都会有一个二维码,叫你扫码自己手机上点菜。扫码进去,要么是公众号的,要么就是小程序。



那我们来看看一个以下案例:


Kapture 2023-08-21 at 16.16.17.gif


除了点餐系统外,还有一些文档目录用到这种方式的目录导航锚点。比如说文档,或者掘金的右侧的目录也用到了同样的效果。


Kapture 2023-08-22 at 09.31.45.gif


🔪 剖析原理


监听scroll事件,获取分类最开始的offsetTop,拿当前页面的scrollTop跟这些offsetTop比较,到了就把左边菜单栏的那个分类变颜色。至于点击,点到哪个分类,就找到对应的右侧分类的标题的offsetTop,通过window.scrollTo(0, 要点击分类的右侧内容的offsetTop)就可以使得内容对应滚动到该位置。


🥬 上菜实操


数据结构:【nestjs返回的数据】如若只是测试,可以写死某些数据即可。


image.png


image.png


http://127.0.0.1:5173/


界面布局:【左(分类)右(内容)】分为两个区域,两边均可滚动。


image.png


<template>
<div class="order">
<div class="category">
<div
v-for="(item, key) in goods"
:class="{
'category-name': true,
active: currentKey === key,
}"

:key="key"
@click="changeCategory(key)"
>
{{ item.categoryName }}
</div>
</div>
<div ref="content" class="content" @scroll="handleScroll">
<div
v-for="(item, key) in goods"
:key="key"
class="content-item"
ref="categoryRefs"
>
<div class="title">{{ item.categoryName }}</div>
<div
class="each-item"
v-for="good in item.goodsList"
:key="good.goodsCode"
>
<div class="image-url">
<img :src="good.imagePathSmall" />
</div>
<div class="desc">
<div class="goods-name">{{ good.goodsName }}</div>
<div class="goods-slogan">{{ good.goodsSlogan }}</div>

<div class="bottom">
<div>¥{{ good.goodsStandardList[0].acturalPrice }}</div>
<div>+</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

image.png


整体效果如下:


image.png


变量以及后端返回的商品列表:


image.png


逻辑:(一个是左侧点分类区域)、(一个是滚动右边内容,左侧分类要根据哪个分类变样式)


左侧点击分类区域:


image.png


content.value.scrollTo({
top: categoryRefs.value[key].offsetTop,
behavior: "smooth",
})

这里值得提的是,scrollTo加behavior方法进行滚动时,可以通过添加behavior参数来指定滚动的动画行为。(smooth 滚动行为具有平滑的动画效果,窗口会平滑地滚动到指定位置)


滚动右边内容:


image.png


当内容整体滚动的高度等于或者超过了某个分类的高度,那边就把左侧分类变样式就可以了,变样式通过key的方式来判断active: currentKey === key


注意点:


content.value.scrollTo({
top: categoryRefs.value[key].offsetTop,
behavior: "smooth",
});

因为我们设置的scrollTo是带有平滑滑动的属性的behavior: "smooth",所以导致点击左侧分类,自然而然也会触发到右侧的滚动事件,导致左侧跨多个点击的时候,中间所有的类都会改样式,问题如下:


Kapture 2023-08-22 at 10.46.59.gif


所以要解决这个问题,思路是,用一个变量,判断左侧点击彻底结束,右侧滚动事件才生效,解决办法如下:


image.png


最终效果


Kapture 2023-08-22 at 10.51.01.gif


🚶‍♀️ 总结消化


做点餐平台,重点(地图和定位服务支付平台和第三方支付配送跟踪);当然最后用户的体验设计也是很重要的,易用性、响应速度、交互设计的,都会影响到顾客当天来店里吃饭点餐的心情和体验。


以上是关于公众号或者小程序一般的点餐系统的大多数点餐页面滚动效果的研究。



爱在朝夕 不止七夕




☎️ 希望对大家有所帮助,如有错误,望不吝赐教,欢迎评论区留言互相学

作者:盏灯
来源:juejin.cn/post/7269786623813074996
习。


收起阅读 »

Stack Overflow 2023 开发者调查报告

iOS
众所周知,Stack Overflow 是全球最大的程序员问答社区,本篇带来它的 2023 开发者调查报告解析! 闲话少说,冲冲冲~ 2023 一共收集了 9 万份开发者的报告,他们反馈了自己正在使用的编程工具以及编程语言。完整的报告在:survey.stac...
继续阅读 »

众所周知,Stack Overflow 是全球最大的程序员问答社区,本篇带来它的 2023 开发者调查报告解析!


闲话少说,冲冲冲~


2023 一共收集了 9 万份开发者的报告,他们反馈了自己正在使用的编程工具以及编程语言。完整的报告在:survey.stackoverflow.co/2023




另外,今年与以往不一样的是对人工智能领域做了更加深入的调查,调查目的是想知道如今以 ChatGPT 为代表的 AIGC工具到底是否改变了开发人员的工作方式、还是只是一场炒作??详细报告在:hype-or-not-developers-have-something-to-say-about-ai 以及给出了一些见解 《developer-sentiment-ai-ml》(挖坑有空翻译~)


悄然变化


一些老程序员都习惯在 Stack Overflow 进行问答,从今年统计看,各个国家的回答率占比有所变化:美国仍然排第一、德国(增长30%)超越印度(下降50%)位列第二。


本次调查中,来自印度的开发人员平均年龄更加年轻,89% 低于 34 岁;而整体样本中,低于 34 岁的占比是 62%。


从整体角度来看,开发者年龄分布略有增长,有 37% 的程序员年龄大于 35 岁,而去年只有 31%;


今年的十大编程语言中,有三门语言的地位提高了,它们分别是:Python、Bash/Shell、C


《comparing-tag-trends-with-our-most-loved-programming-languages/》 了解到:过去三年,大家对 Python 的关注又在不断提升;




尤其是对于非职业的编程人员来说,Python 是一门相当不错的入手编程语言:




另外,C 语言重回台面,这个就很有意思了:尽管 C 是一门很古老的编程语言(始于1970),但在之前它从未进入开发者调查报告受欢迎语言的前十名,


C 语言作为一门基础语言,是嵌入式编程语言所需,从这个角度看,是不是意味着:设备的嵌入式编程开发近年也在急速发展?物联网正在发力。学习 C :Codecademy




薪资情况,调查显示 2023 年程序员整体收入将比去年增长约 10%;


其中最受欢迎的三种编程语言:JavaScript、HTML/CSS 和 Python,薪资中位数却出现了下降;


而一些小众语言,比如 APL 和 Crystal ,薪资增幅较大。由此推断,在 2023 年一些小众语言的程序员薪资上涨会更多!


期待使用


今年,在调查报告中还新增了一个概念区分,即“期望使用的编程语言”,在之前,我们只关注“受欢迎的语言”,这次还综合统计了大家的预期。




如图所示,Rust 是所有语言中最受欢迎且被期望继续使用的语言!有 80% 的 Rust 使用者选择将来会继续使用~(Rust 你用上了吗??)


而比如 JavaScript 使用者大约只有 60% 选择会继续使用它~~


薪资水平


另外,技术受欢迎,但是也肯定同样要考虑工资水平。其中,Rust、Elixir 和 Zig 语言的开发者薪资中位数比其它语言普遍高出 20%,年薪约 50+ 万人民币~ 薪资完整情况




(可惜没统计咱们的。。。)


欲知更多报告,观点在:《dev-survey-results-2023》


作者:掘金安东尼
链接:https://juejin.cn/post/7251199685128814649
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

年中总结、再品苏轼、怀古望今、胡思漫谈

-- 迟到年终总结/虽迟但到 -- 现在是公元2023年7月22日,不知觉,2023年已过一半有余。 在整 1000 年前,1023 年,北宋宋仁宗开启“天圣”元年,仁宗在位四十二年,搜揽天下豪杰,不可胜数,其中就包括文坛 T1 阵容 —— 苏轼。 仁宗可以说...
继续阅读 »

-- 迟到年终总结/虽迟但到 --


现在是公元2023年7月22日,不知觉,2023年已过一半有余。


在整 1000 年前,1023 年,北宋宋仁宗开启“天圣”元年,仁宗在位四十二年,搜揽天下豪杰,不可胜数,其中就包括文坛 T1 阵容 —— 苏轼。


仁宗可以说是苏轼最大的伯乐,认苏轼有宰相之才,为他的诗词/才华直连拍手叫好。


而仁宗之后,苏轼便逐渐踏上了他的一生被贬之路。


怀古


step1


苏轼先是和王安石政见不一,自请至杭州做通判,苏堤春晓、三潭印月,欲把西湖比西子,淡妆浓抹总相宜。


现实中与人气场不合、难以互存,而又不得不共事的时候,确实是一件不幸之事。


王安石改革激进、求快,苏轼体恤民情,当宋神宗选择王安石的时候,苏轼就知道了:道不同不相谋。


从杭州再到湖州,远离庙堂、过足山水之瘾,有道是:“待君诗百首,来写浙西春”。


step2


苏杭山水之后,再是乌台诗案,被文字狱、坐牢 100 多天,被贬黄州;


在黄州的四年多时间,从“寂寞沙洲冷”转变为“一蓑烟雨任平生”,他天生是乐观的,能够快速的整理、修复情绪;


最后,黄州成就了苏轼,写出了千古流传的《赤壁赋》,“天地之间,物各有主”,不是我等能占有的,假若想要有无限的时光、享用无尽的自然美景,只得是把自己交给自然,“耳得之而为声,目遇之而成色,取之无禁,用之不竭”。


我认为,这种豁达,可以解忧,现代人的忙碌、焦虑、急切心态等。


事物兴衰、时间流转,不是我等能掌控的,占有欲再强,最后也是赤条条来去无牵挂。


不如就是“适之”,你我“共适”当下,便就是永久的享受了。


step3


虽然,不多久,苏轼又被高太后启用,但随着这位迷妹去世,哲宗启用新派、打击旧派,苏轼又被贬到惠州;“日啖荔枝三百颗,不辞长作岭南人”,即使离皇帝/权利/显贵越来越远,但并不妨碍他这样快活的心态;


step4


一贬再贬,晚年苏轼被贬到海南儋州,可谓是:天涯海角。有一首《西江月》:



世事一场大梦,人生几度秋凉?夜来风叶已鸣廊。看取眉头鬓上。


酒贱常愁客少,月明多被云妨。中秋谁与共孤光。把盏凄然北望。



有人分析说这是在儋州所作,有人分析说在黄州所作;


个人感觉,前者说法可能性更大,人生之短促,壮志之难酬,确实悲凉,难有青壮年的狂放、通达。


望今


p1


现在年轻人也是很艰难的,虽绝大部分人都没有苏轼这样有才华的诗词表达,但对于这种人生变迁的体味肯定是闷在心头,千滋百味、无法言说的。


时间转眼就没,就像李白所说:朝如青丝、暮成雪;


没有什么是永恒的,可能在一家公司日复一日、勤勤恳恳工作两、三年,转眼间,因为某一天来了一个擅长 PUA 的小领导,愤懑之下就裸辞;或者某一天,突然就不想起早八了,不想麻木/盲目了,毅然离开,像是重启、代谢更新,又像是一种惋惜,恨不得志;


当然没时间啦。


早八到晚八,或者早十到晚十,都一样,最普通的,基本工作时间8小时,还要提前准备、通勤;晚归后还要休息调整思路;就算是一点不加班,工作也要消耗掉一天二分之一的时间。正常的,还要有睡眠时间,7个、8个小时左右,剩下的,也就3、4小时;再除去内务整理、社交交友、家人长谈,还有几个属于自己的时间,可以用来思考:文学、艺术、哲学、宗教、政治、科学等等?


只要是随着这个时间流去走,不去挣扎的话,真的时间一晃而过。就像有人所说的,普通人能忙于物质生活都已经足够累了,还有几个能在此之外,建设/丰富精神世界?


这是给自己找理由吗?时间就像海绵里的水,挤一挤还是有的?怎么挤?不是人人都能凿壁借光、或闻鸡起舞,不然这故事也没什么好值得人敬佩的了。


有一说是:中国人把吃做到了极致,而欧洲人把闲做到了极致。


我辈当负大任,这也是时代的洪流所决定,不是个人的决定;长达三年的疫情的洪流,其实也就在今年才结束,相信还有许多人被影响、没走出来,但没谁会在乎,时间就是如水、如刀,不谈感情,继续往前,即使青丝变成白发。


2023 年,不一样的是什么,听到了许多,比如常谈的公众号已死、前端已死、B站收益已死、xxx已死,whatever,几乎没人会收集自己表皮更新而产生的死皮吧,落在满地,灰尘皆是,该怎样,还是怎样。


还是想到 2018 我说的一句话,每当年中,每当盛夏:“常言道:韶光易逝、寸暑难留、然其虚无、何以擒之”。夏天很好,夏夜更好,但没人能无穷尽的享用。或许,也只有想到,苏轼所说:共适此时,才得宝藏。


故:怎样都好。


p2


具体一点,2023 年上半年有什么不一样,自己敲定了人生大事之一,当然无非几件其一:出生、上学、考学升学、大学、就业、结婚、生子、循环往复,外乎还有买房、买车等等,就像大你十岁的人,聊天时,一定会问类似这些大事上的问题。


其次,工作转型,之前是前端,现在是项目经理;写文方向转型,之前写具体的前端技术文章,现在写 AIGC 的专栏;偶尔,用 AIGC 发一发想要翻译的潮流前线技术文章,但囿于自己时间、囿于自己执行力、囿于其它事情的权重取舍,所以现状就是这么个现状。


另外:发了很多知乎、但有起伏,马上7级;做了AIGC抖音号,获赞1k+;还有在同花顺论股,发声就是在生产观点、观点即内容,有冲突,也有价值,也会带来认知变化,以及有可能生产产生财富。


还有,改书一事,来回修订好几次,有种有心无力的感觉,给到的压力不小,但是怀疑自己究竟能否COVER;工作上事情很多,KPI 的压力也是直接到项目,薪酬结构调整、增量激励等等,没有人会在之前问你:你准备好了吗?你对这件事怎么看?要不等等你?


能留一个下午,去回看这些事,都是一种“偷窃”之举,得之所幸。


p3


再到,重点看看“起伏”这个事。红楼梦的经典在于此,“训有方、保不定日后作强梁;择膏梁,谁承望流落烟花巷”;


苏轼的几次被召、几次被贬也在于此,哀吾生之须臾、羡长江之无穷;


长安三万里高适、李白也如此,十年寒窗、十年还乡、十年扬州、十年边塞、十年朝堂、十年流亡;人生又几个十年,少小十年、老弱十年、睡梦十年又十年、乱哄哄,你方唱罢我登场。


说回股市,上半年关注很多,我是在想:财富的本质在于生产力,现在觉得也在于流动性;打工人是无产者,唯一能有产的就是,把微薄的薪水几成放到股市,同市场共振,让钱成为一种资产,成为一种员工,为自己所用。所以投资是开公司,我们打工,被当做资产来估价、人是一种资产、钱更应该是一种资产,怎样选对方向,是一直需要去专研的,虽然看不太明白,股市是最复杂的混沌系统,不能预测,但也只能尽全力在无序中找有序。


找规律、模仿、是我们一直在做的,将信息整合、同步各方,也是我们一直在做的,殊途同归、并无新鲜。


p4


再看以后,无复多言。做好工作,做好精神建设,做好“负熵”。


1、坚持输出、生产内容,输出倒逼输入,生产带来财富、流动带来财富;


2、做好身体锻炼,25 岁以后,身体代谢下降,真的是一个客观现实;如果每天手表的三个圆环都难合拢,身体只会走下坡路吧;


3、正能量/乐观,待人待事,在随波逐流和坚守原则之间权衡、适之。


最后用苏轼最经典之一的词总结:“回首向来萧瑟处,归去,也无风雨”。


所以,2023 年中,I`m fine,Thanks all,And you?


作者:掘金安东尼
链接:https://juejin.cn/post/7258445326914076733
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

🐻优化GIF的内存加载

iOS
一、内存OOM问题 使用 UIImage.animatedImage(with:duration:) 方法:UIImage 类提供了一个便利的方法来加载并处理 GIF 图像,该方法可以将 GIF 图像转换为 UIImage 的动画表示。这种方法可以有效地管理内...
继续阅读 »

一、内存OOM问题


使用 UIImage.animatedImage(with:duration:) 方法:UIImage 类提供了一个便利的方法来加载并处理 GIF 图像,该方法可以将 GIF 图像转换为 UIImage 的动画表示。这种方法可以有效地管理内存,并且不需要手动处理每一帧的图像。但会存在内存问题,UIImage(contentsOfFile:) 虽然不会立即放入内存中,但显示时还是会加载到内存中。

if let gifURL = Bundle.main.url(forResource: "example", withExtension: "gif") {
let gifData = try? Data(contentsOf: gifURL)
let gifImage = UIImage.animatedImage(with: gifData)
imageView.image = gifImage
}

大量的GIF会导致OOM问题,一旦使用超过系统的阈值,就会崩溃。


二、使用FLAnimatedImageView可以有效的解决GIF内存暴涨的问题

  • FLAnimatedImageView 使用渐进式解码:FLAnimatedImageView 使用渐进式解码来加载 GIF 图片。渐进式解码允许在图片尚未完全加载时就开始显示并逐步增加清晰度。这意味着 FLAnimatedImageView 可以在加载 GIF 图片的同时,逐帧渲染和显示动画,而不需要等待整个 GIF 图片加载完成。这对于大型 GIF 图片特别有利,因为可以显著降低首次加载的延迟,并提高用户体验。
  • 内存优化:FLAnimatedImageView 在加载和显示大型 GIF 图片时进行了内存优化。它只会将当前帧所需的数据加载到内存中,并在显示下一帧时释放之前的帧数据,从而避免占用过多的内存。这有助于在加载大型 GIF 图片时降低内存使用,减少内存压力和 OOM 问题。

三、让FLAnimatedImageView支持网络GIF


FLAnimatedImageView 是用于显示 GIF 动画的 FLAnimatedImage 库中的特殊控件,它并不直接用于加载网络图片,但我们可以扩展方法为其增加加载网络图片的功能。

import FLAnimatedImage
import Kingfisher

extension FLAnimatedImageView {
func setGifImage(withURL url: URL) {
// 使用 Kingfisher 加载网络图片
self.kf.setImage(with: url, completionHandler: { result in
switch result {
case .success(let value):
// 成功加载图片,value.image 是 UIImage 类型
// 将加载的图片转换为 FLAnimatedImage 类型
let animatedImage = FLAnimatedImage(animatedGIFData: value.image.kf.gifRepresentation())
// 在 FLAnimatedImageView 中显示 GIF 动画
self.animatedImage = animatedImage
case .failure(let error):
// 加载图片失败,处理错误
print("Error loading image: (error)")
}
})
}
}

上述方法利用Kingfisher不仅添加了缓存,还能后直接显示来自网络的GIF图片。


四、测试效果


总体内存可以降低70%,CPU在迅速滑动时波动较大,大概为原来的1-2倍,但是停止滑动时降低为原来的50%左右。由于目前iPhone手机的CPU普遍较好,而内存较低;所以这种用CPU缓解内存压力的方法是可行的。


作者:熊大与iOS
链接:https://juejin.cn/post/7262151580898983994
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

总是跳转到国内版(cn.bing.com)?New Bing使用全攻略

你是否想要使用强大的(被削后大嘘)New Bing? 你是否已经获得了New Bing的使用资格? 你是否在访问www.bing.com/new时提示页面不存在? 你是否在访问www.bing.com时总是重定向到cn.bing.com而使用不了New Bin...
继续阅读 »

你是否想要使用强大的(被削后大嘘)New Bing?


你是否已经获得了New Bing的使用资格?


你是否在访问www.bing.com/new时提示页面不存在?


你是否在访问www.bing.com时总是重定向到cn.bing.com而使用不了New Bing?


New Bing的使用本来就不需要依赖科学上网,看完下面的教程,不论你卡在哪一步,你都可以成功使用New Bing。


3.13更新


根据大量评论反馈,微软似乎已经让本文中的方法:“修改请求头X-Forwarded-For来伪装请求地址”的方法失效了。现在微软已经可以检测到请求的源IP了,使用科学上网(代理)的方法依然可用,使用前需清空cookie,否则还是国内特供版Bing。没有办法科学上网的朋友,目前暂时帮不上忙了,如果未来有新方法,我仍然会更新在文档中。


一、加入New Bing的候选名单


现在的情况是:访问 http://www.bing.com/new,会自动跳转到cn.bing.com
如果你是Chrome或者Edge浏览器(下面以Edge举例,Chrome也同理)可以通过以下扩展的方式,修改请求Header来防止被重定向。 


打开浏览器的扩展,找到 管理扩展 按钮,在打开的页面左侧找到 获取 Microsoft Edge 扩展 并打开。
搜索 ModHeader ,安装下面这个扩展。




接着在已有扩展中,找到这个扩展并点击打开。 


打开扩展后就会弹出下面这个弹窗,点击 FILTER,选择 Request URL filter (在Chrome中是 URL filter

  


填写下面三个内容(分别是:X-Forwarded-For8.8.8.8.*://www.bing.com/.*)并确保都勾选。 



设置好后,再次访问 http://www.bing.com/new ,登录自己的微软账号,点击加入候选名单即可。

  


静静等待Microsoft Bing发来的 “你已中奖”的邮件,或者你微软的邮箱不是你常用的邮箱,时不时重新访问一下 http://www.bing.com/new 也可以看到你是否已经 “中奖”。


2023.3.6更新


在这里,可能会有一些朋友遇到重定向次数过多,请清除Cookie的问题,地址栏会有很多相同的zh-CN“后缀”,可以尝试以下方法:

  1. 点击Request URL filters一行的右方加号,并添加一个 .*://cn.bing.com/.* 。这时候Request URL filters中,同时存在两个筛选规则,包括www与cn两个
  2. 清除bing相关网站的cookie。在设置->cookie与网站权限->管理和删除cookie->查看所有网站cookie->右上方搜索bing,然后删除所有相关的条目
  3. 如果还有登录账号时遇到类似的问题。可以先按上一步Cookie,然后关闭扩展,在cn.bing.com中登录账号,再开启扩展访问正常版本试试。

二、下载Edge Dev 目前普通版Edge也可以了


获得资格后,首先需要解决的问题应该是下载Edge的DEV版本,在除了dev版本的Edge以外的任何浏览器中,均不能使用带有 Chat 功能的 New Bing。


通过下面这个链接,下载dev版本的Edge
http://www.microsoft.com/en-us/edge


不要直接点页面中大大的下载按钮(那个是普通版),找到下面这个部分并打开我框住的链接。 


在打开的页面也不要直接下载,往下找到下面这个图片的部分:



确保Edge图标上有 DEV 字样,点击右侧的下载适合自己电脑的版本(macOS、Windows、Linux)


三、访问New Bing


在你走完上面的流程后,访问 http://www.bing.com 即可看到上面的导航栏有Chat字样。

点击后,开始你的New Bing使用之旅吧。 



四、手机访问New Bing


现在(更新时间2023.2.28)微软已经将New Bing带上了手机。


现在有了更方面的访问途径,使用手机的Bing App使用New Bing的Chat功能。
但国内的应用商城应该是名叫“微软必应”的阉割版,我这里找到了微软官方的下载地址:Microsoft Bing


不过我并不是通过这种方式下载的APP,我在谷歌的应用商城下载的Bing APP,如果上述官网的地址下载的APP在账号已拥有测试资格的情况下仍没有Chat功能,请自行尝试谷歌商店下载。


五、结语


有问题可以评论中询问我,请确保你清楚地描述出你遇到的问题和尝试过的方法。New Bing、ChatGPT还有本文的作者我,都需要你具备基本的提问题的能力。我有看到部分评论(CSDN)的朋友仅简单问了一句话,我根本无从得知你的问题的现状,自然也无法解决你的问题。请你清楚地思考完下面几个问题:

  1. 你进行到哪一步卡住了?
  2. 你尝试了哪些方法?
  3. 你是否完整阅读了本文?

如果你的问题描述不清楚,恕我拒不回答。


另外如果有帮到你成功访问到New Bing,还请不要吝啬你的点赞和收藏 ^_^


作者:西木鹿亚
链接:https://juejin.cn/post/7202531472720592951
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

博客园宣布上线会员:弹尽粮绝,命悬一线

相比CSDN,博客园在商业化上还是比较克制的,没想到也到了乞求用户开会员得以存活的地步。博客园称,自从今年4月绝境求商之后,博客园苦苦支撑了4个月,能够支撑到现在,主要源于大家的捐助,一位园友的小额投资,以及天使投资方顺顺智慧的出手相救。博客园表示,在当前极其...
继续阅读 »
相比CSDN,博客园在商业化上还是比较克制的,没想到也到了乞求用户开会员得以存活的地步。

博客园称,自从今年4月绝境求商之后,博客园苦苦支撑了4个月,能够支撑到现在,主要源于大家的捐助,一位园友的小额投资,以及天使投资方顺顺智慧的出手相救。

博客园表示,在当前极其困难的情况下,其没有足够的力量在其他商业模式上搏一把,接下来博客园走出困境的唯一希望,取决于有多少园友选择成为VIP会员,目前已有140位会员。接下来,博客园将全力以赴开发更多会员权益。

自从今年4月绝境求商之后,园子苦苦支撑了4个月,能够支撑到现在,主要源于大家的捐助,一位园友的小额投资,以及天使投资方顺顺智慧的出手相救。感谢这段时间所有帮助过园子的朋友!

在当前极其困难的情况下,我们没有足够的力量在其他商业模式上搏一把,接下来园子走出困境的唯一希望,园子的命运就取决于——到年底有多少园友选择成为VIP会员。

园子生于偶然,长于天然,执于本然,困于漠然(商业化),难于突然(危机),接下来将交于自然——用户的自然选择。

不知道会有多少用户会选择帮助园子走出困境,但我们知道园子的存在与发展至今就是因为用户的选择。

不管结局如何,我们都无怨无悔,因为这是我们的选择,而且选择已经成为我们的使命。最无憾的人生之一就是找到自己的使命,并为之努力。

当2021年面临关站还是缴纳对园子来说巨额罚款的选择时,我们选择了坚持下去。

当2022年面临百度全面降权与收入暴降时,我们选择了坚持下去。

当2023年弹尽粮绝时,我们依然想选择坚持下去,但这一次如果没有足够多用户的支持,我们就没有足够的力量坚持下去。

会员上线,这是我们正式发布园子会员,之前放了一些链接,到目前已有140位会员,感谢这些园友的支持!

命悬一线,这根从用户到会员的感情线,聚少成多将成为园子的命运线。

接下来,我们会保持心态:谋事在园,成事在用户。

接下来,我们会满怀期待:有园千里来相会,会员万维来救园。

接下来,我们会火力全开:全力以赴开发更多会员权益,让园子的会员成为非常超值的会员。


来源:三言科技

收起阅读 »

Git stash 存储本地修改

前言 我们在开发的过程中经常会遇到以下的一些场景:当我们在 dev 分支开发时,可能需要临时切换到 master 拉分支修复线上 bug 或者修改其他分支代码,此时对于 dev 分支的修改,最常见的处理方式是将代码提交到远程后再切到对应的分支进行开发,但是如果...
继续阅读 »

前言


我们在开发的过程中经常会遇到以下的一些场景:

  • 当我们在 dev 分支开发时,可能需要临时切换到 master 拉分支修复线上 bug 或者修改其他分支代码,此时对于 dev 分支的修改,最常见的处理方式是将代码提交到远程后再切到对应的分支进行开发,但是如果 dev 分支的修改不足以进行一次 commit(功能开发不完整、还有 bug 未解决等各种原因),或者觉得提交代码的步骤过多,此时 dev 的修改就不好处理

  • 在开发阶段可能某个分支需要修改特定的配置文件才能运行,而其他分支不需要,那么当我们在这个分支和其他分支来回切换的时候,就需要反复的修改、回滚对应的配置文件,这种操作也是比较低效且麻烦的


而我们通过 Git 提供的 stash 存储操作,可以将当前分支的修改或者开发常用的配置文件存储至暂存区并生成一条存储记录,在需要使用时通过存储记录的索引直接取出,无需额外提交或单独保存,可以有效的解决以上的问题


Git stash 使用流程


1. 存储修改:我们可以使用 git stash 对 dev 的修改进行存储,存储修改后会生成一条存储记录




2. 查看存储记录:通过 git stash list 查看存储记录列表,存储记录的格式为:

stash@{索引值}:WIP on [分支名]: [最近的一次 commitId + 提交信息]



3. 多次存储记录相同:如果多次存储修改的过程中没有进行过 commit 提交,存储记录除了 索引值 之外将会完全相同,此时我们就无法快速辨识存储记录对应的修改内容




4. 存储记录标识


为了解决存储记录无法辨识问题,存储修改时可以用 git stash -m '标识内容' 对存储记录进行标识




此时我们再查看存储记录列表,就可以看到存储记录的标识,此时存储记录的格式为:

stash@{索引值}:on [分支名]: [标识内容]



5. 恢复存储:当我们在其他分支完成开发再回到 dev 分支时,就可以通过 git stash apply index 将指定的存储记录恢复至工作区,index 是存储记录的索引,未指定则恢复第一条存储记录




6. 删除存储


对于不再需要的存储记录,可以通过 git stash drop index 删除指定的存储记录,此时我们执行 git stash drop 删除第一条记录后再使用 git stash list 查看存储记录就已经少了一条了




如果所有的存储记录都不需要,可以使用 git stash clear 清除所有存储记录




Git Stash 命令


查看存储记录


查看存储记录列表

git stash list

查看 最近一次 存储记录的具体修改内容,即修改了哪些文件

git stash show

查看 指定索引 存储记录的具体修改内容

git stash show index
git stash show stash@{index}

存储修改


直接存储修改

git stash

存储修改,并添加备注

git stash -m '备注内容'

恢复存储记录



恢复存储记录的修改内容



恢复 最近一次 的存储记录

git stash apply

恢复 指定索引 的存储记录

git stash apply index
git stash apply stash@{index}

删除存储记录



对不需要的存储记录进行删除,可以删除部分或全部

  • 删除 最近一次 的存储记录
git stash drop
  • 删除 指定索引 的存储记录
git stash drop index
git stash drop stash@{index}
  • 删除所有的暂存修改
git stash clear

恢复并删除存储记录



恢复存储记录的同时删除对应的存储记录

  • 恢复并删除 最近一次 的存储记录
git stash pop
  • 恢复并删除 指定索引 的存储记录
git stash pop index
git stash pop stash@{index}

作者:蒋绾心
链接:https://juejin.cn/post/7269691627598479375
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

pnpm改造替换npm

Q: 为什么要迁移pnpm? 相比于npm,pnpm有一些优势:更快的安装速度: 在安装包时,pnpm使用了硬链接的方式,将已安装的包链接到新的目录下,而不是复制或下载包。这样,当你安装一个包的不同版本或者不同项目使用同一个包时,它们会共享已经安装的包,减少了...
继续阅读 »

Q: 为什么要迁移pnpm?


相比于npm,pnpm有一些优势:

  1. 更快的安装速度: 在安装包时,pnpm使用了硬链接的方式,将已安装的包链接到新的目录下,而不是复制或下载包。这样,当你安装一个包的不同版本或者不同项目使用同一个包时,它们会共享已经安装的包,减少了磁盘空间的占用,同时也加速了安装的速度。

  2. 更少的磁盘空间占用: 由于pnpm使用硬链接的方式共享已安装的包,因此相比于npm,pnpm占用更少的磁盘空间。

  3. 更好的本地缓存: pnpm会缓存包的元数据和二进制文件到本地缓存中,这样再次安装相同的包时,会从本地缓存中读取,而不是重新下载。这样可以提高安装包的速度,并减少网络带宽的消耗。

  4. 更好的多项目管理: pnpm可以管理多个项目的依赖,可以将相同的依赖安装在一个公共的位置,减少磁盘空间的占用,并且可以快速地切换项目之间的依赖关系。

  5. 更好的可重复性: pnpm使用了锁文件来保证安装包的版本一致性,同时也支持自定义的锁文件名称和路径。这样可以确保项目在不同的环境中的安装结果一致,增强了可重复性。


需要注意的是,pnpm相比于npm也存在一些缺点,例如兼容性问题、社区支持不如npm等。因此,在选择使用pnpm还是npm时,需要根据自己的实际需求和项目情况进行权衡。


Q: 上面提到的硬链接和符号链接是什么?


硬链接和符号链接都是文件系统中的链接方式,它们的作用是可以将一个文件或目录链接到另一个文件或目录上,从而实现共享或复制等功能。下面我来简单介绍一下它们的区别和示例。


硬链接


硬链接是指在文件系统中,将一个文件名链接到另一个文件上,使它们指向同一个物理数据块,也就是说,这两个文件名共享同一个inode节点。硬链接的本质是将一个文件名指向一个已存在的文件。


硬链接的特点:

  • 硬链接不能跨越不同的文件系统,因为inode节点只存在于一个文件系统中。
  • 硬链接可以看作是原文件的一个副本,它们的文件权限、拥有者、修改时间等都是相同的。
  • 删除硬链接并不会删除原文件,只有当所有的硬链接都被删除后,原文件才会被真正删除。

下面是一个硬链接的示例:

$ touch file1 # 创建一个文件
$ ln file1 file2 # 创建硬链接
$ ls -li file* # 查看文件inode节点
12345 -rw-r--r-- 2 user user 0 Apr 26 10:00 file1
12345 -rw-r--r-- 2 user user 0 Apr 26 10:00 file2

可以看到,file1和file2的inode节点是相同的,说明它们共享同一个物理数据块。


符号链接


也称之为软链接,符号链接是指在文件系统中,创建一个特殊的文件,其中包含了另一个文件的路径,通过这个特殊文件来链接到目标文件。符号链接的本质是将一个文件名指向一个路径。


符号链接的特点:

  • 符号链接可以跨越不同的文件系统,因为它们只是一个指向文件或目录的路径。
  • 符号链接指向的是目标文件或目录的路径,而不是inode节点,因此,目标文件或目录的属性信息可以独立于符号链接存在。
  • 删除符号链接不会影响目标文件或目录,也不会删除它们。

下面是一个符号链接的示例:

$ touch file1 # 创建一个文件
$ ln -s file1 file2 # 创建符号链接
$ ls -li file* # 查看文件inode节点
12345 -rw-r--r-- 1 user user 0 Apr 26 10:00 file1
67890 lrwxr-xr-x 1 user user 5 Apr 26 10:01 file2 -> file1

可以看到,file2是一个符号链接文件,它的inode节点和file1不同,而是一个指向file1的路径。


Q: 看到一些文章里说pnpm走的是硬链接,有的说用了软连接。到底走的是什么?


其实,pnpm是软连接和硬链接都用了。可以这么理解,pnpm在机器上某个地方存放安装好的所有依赖包,这些依赖包是独立于我们代码仓库的,这也是前面说的pnpm在安装速度和磁盘空间占用上的优点。而我们的代码库确实是先通过硬链接的方式来建立代码库和已安装过的依赖包之间的共享关系。可以打开代码库看到node_modules下有一个.pnpm文件夹,里面放的就是当前代码库建立的硬链接。




.pnpm下的文件都是一些名字很长的,长这样:




这里不用关心具体是什么,我们需要关心的是node_mpdules下我们认识的npm依赖包,它们正是通过软连接的方式来链接到.pnpm下的这些依赖包的。在vscode下,可以明显看到npm包后面的软连接标识:




如果想看一下这些软连接到底指向哪里的,可以:

# 进入node_modules目录
cd node_modules

# 枚举文件列表
ll 

 可以看到,这就是node_modules下软链接到.pnpm下的。


Q: 这个模式跟npm dedupe是不是很相似,有什么不同?


pnpm的硬链接模式和npm的dedupe功能是类似的,都是通过共享已安装的包来减少磁盘空间的占用,同时也可以提高安装包的速度。但它们之间还是存在一些不同:

  1. 原理不同: pnpm使用硬链接的方式共享已安装的包,而npm使用的是符号链接的方式共享已安装的包。硬链接是文件系统的一种特殊链接,它可以将一个文件链接到另一个文件上,使它们共享相同的内容。符号链接则是一个指向另一个文件或目录的特殊文件。

  2. 适用范围不同: pnpm的硬链接模式可以在多个项目之间共享已安装的包,而npm的dedupe功能只能在单个项目内共享已安装的包。

  3. 优势不同: pnpm的硬链接模式可以减少磁盘空间的占用和提高安装包的速度,而npm的dedupe功能只能减少磁盘空间的占用。

  4. 实现方式不同: pnpm使用了自己的包管理器和包存储库,而npm使用了公共的包管理器和包存储库。这也是导致它们之间存在差异的一个重要原因。


需要注意的是,无论是使用pnpm的硬链接模式还是npm的dedupe功能,都需要谨慎使用,以避免出现意外的错误。特别是在使用硬链接模式时,如果多个项目共享同一个包,需要注意不要在一个项目中修改了该包的文件,导致其他项目也受到影响。


Q: pnpm对于node版本有要求吗?


pnpm有对node版本的要求。官方文档中列出的最低支持版本是Node.js 10.x,推荐使用的版本是Node.js 14.x。如果使用的是较旧的Node.js版本,可能会导致安装和使用pnpm时出现错误。


我这里本来用的是Node14.x。因为其他原因,本次也给Node升级到16.x了。


Q: pnpm有类似npm ci的命令吗?



补充:npm ci主要是用于刚刚在download了一个仓库后,还没有node_modules的时候让npm完全根据package.json和package-lock.json的规范来install依赖包。相比较于直接走npm inpm ci会带来更精确的小版本版本号控制,因为npm i对于一些"^1.0.2"这样的版本号,可能会按照1.x.x这样的规范给你无感升级了,造成和之前某些包版本号之间的差异。
但是当本地已有node_modules的时候,就没办法用npm ci命令了。



是的,pnpm也有类似 npm ci 命令的功能,可以使用 pnpm install --frozen-lockfile 命令实现。它会根据 package-lock.jsonpnpm-lock.yaml 确定依赖关系,并且在安装期间不会更新任何包。此命令类似于 npm ciyarn install --frozen-lockfile 命令。


Q: pnpm@7搭配husky@8后commit一直失败怎么办?


这是因为hooks出问题了。某些代码库里会在commit时候会添加一些hook用来处理commit相关的事务,比如生成commit-id之类的。


husky@8后需要处理一下这个:

husky add .husky/commit-msg 'sh .git/hooks/commit-msg "$@"'

手动把之前.git/hooks下的脚本拷贝到.husky下。


友情提示:.git和.husky一般都是在项目根目录下的隐藏文件夹喲~


作者:liuniansilence
链接:https://juejin.cn/post/7232187022399389754
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Volatile 关键字

保证内存可见性 Java 内存模型分为了主内存和工作内存两部分,其规定程序所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(赋值、读取等)都必须在工作内存中进行,而不能直接读...
继续阅读 »

保证内存可见性


Java 内存模型分为了主内存和工作内存两部分,其规定程序所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(赋值、读取等)都必须在工作内存中进行,而不能直接读取主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递都必须经过主内存的传递来完成。 



这样就会存在一个情况,工作内存值改变后到主内存更新一定是需要一定时间的,所以可能会出现多个线程操作同一个变量的时候出现取到的值还是未更新前的值。


这样的情况我们通常称之为「可见性」,而我们加上 volatile 关键字修饰的变量就可以保证对所有线程的可见性。


这里的可见性是什么意思呢?当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。


为什么 volatile 关键字可以有这样的特性?这得益于 Java 语言的先行发生原则(happens-before)。简单地说,就是先执行的事件就应该先得到结果。


但是! volatile 并不能保证并发下的安全。


Java 里面的运算并非原子操作,比如 i++ 这样的代码,实际上,它包含了 3 个独立的操作:读取 i 的值,将值加 1,然后将计算结果返回给 i。这是一个「读取-修改-写入」的操作序列,并且其结果状态依赖于之前的状态,所以在多线程环境下存在问题。



要解决自增操作在多线程下线程不安全的问题,可以选择使用 Java 提供的原子类,如 AtomicInteger 或者使用 synchronized 同步方法。


原子性:在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量)才是原子操作。(变量之间的相互赋值不是原子操作,比如 y = x,实际上是先读取 x 的值,再把读取到的值赋值给 y 写入工作内存)



禁止指令重排


最开始看到「指令重排」这个词语的时候,我也是一脸懵逼。后面看了相关书籍才知道,处理器为了提高程序效率,可能对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。


指令重排是一把双刃剑,虽然优化了程序的执行效率,但是在某些情况下,却会影响到多线程的执行结果。比如下面的代码:


使用场景


从上面的总结来看,我们非常容易得出 volatile 的使用场景:

  1. 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
  2. 变量不需要与其他的状态变量共同参与不变约束。

比如下面的场景,就很适合使用 volatile 来控制并发,当 shutdown() 方法调用的时候,就能保证所有线程中执行的 work() 立即停下来。

volatile boolean shutdownRequest;
private void shutdown(){
shutdownRequest = true;
}
private void work(){
while (!shutdownRequest){
// do something
}
}

总结


说了这么多,其实对于 volatile 我们只需要知道,它主要特性:保证可见性、禁止指令重排、解决 long 和 double 的 8 字节赋值问题。


还有一个比较重要的是:它并不能保证并发安全,不要和 synchronize 混淆。


作者:PuddingSama
链接:https://juejin.cn/post/7234466978911993916
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

💢可恶!终究还是逃不过这些网站吗?

Documatic http://www.documatic.com/ Documatic 是一个高效的搜索引擎工具,旨在帮助开发人员轻松搜索他们的代码库以查找特定的代码片段,函数,方法和其他相关信息。这个工具旨在为开发人员节省宝贵的时间并增加生产力,可以在几...
继续阅读 »

Documatic


http://www.documatic.com/


Documatic 是一个高效的搜索引擎工具,旨在帮助开发人员轻松搜索他们的代码库以查找特定的代码片段,函数,方法和其他相关信息。这个工具旨在为开发人员节省宝贵的时间并增加生产力,可以在几秒钟内快速提供准确和相关的搜索结果。Documatic 是一个代码搜索工具,具有自然语言查询功能,可以简化新手和专家开发人员的代码库搜索。输入查询后,Documatic 会快速从代码库中获取相关的代码块,使您更容易找到所需的信息。 



Transform.tools


transform.tools/


Transform.tools 是一个网站,可以转换大多数内容,如 HTML 到 JSX,JavaScript 到 JSON,CSS 到 JS 对象等等。当我需要转换任何内容时,它真的节省了我的时间。 




Convertio


convertio.co/


Convertio - 在线轻松转换文件。超过 309 种不同的文档,图像,电子表格,电子书,档案,演示文稿,音频和视频格式。比如 PNG 到 JPEG,SVG 到 PNG,PNG 到 ICO 等等。 



Removebg


http://www.remove.bg/


Removebg 是一个令人惊叹的工具,可以轻松地删除任何图像的背景。RemoveBG 可以立即检测图像的主题并删除背景,留下透明的 PNG 图像,您可以轻松地在项目中使用。无论您是否从事平面设计,图片编辑或涉及图像的任何其他项目,我使用过这个工具太多次,我甚至不记得了。




Imglarger


imglarger.com/


Imglarger 允许您将图像放大高达 800%,并增强照片而不损失质量,这对摄影师和图像处理者特别有用。它是一个一体化的 AI 工具包,可以增强和放大图像。增加图像分辨率而不损失质量。




Code Beautify


codebeautify.org/


Code Beautify 是一个在线代码美化和格式化工具,允许您美化源代码。除了此功能外,它还支持一些转换器,如图像到 base64,不仅如此,它还有如下图像所示的大量功能:




Vercel


vercel.com/


Vercel 是前端开发人员的平台,为创新者提供构建即时 Web 应用程序所需的速度和可靠性。它是一个云平台,自动化开发和部署流程来构建无服务器 Web 应用程序。它提供诸如无服务器功能,静态站点托管,持续部署,自定义域名和 SSL 以及团队协作等功能。它有免费层和付费计划以获得更高级功能,并被许多流行的网站和 Web 应用程序使用。




作者:前端小蜗
链接:https://juejin.cn/post/7248440645688819769
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

一个28岁程序员入行自述和感受

我是一个容易焦虑的人,工作时候想着跳槽,辞职休息时候想着工作,休息久了又觉得自己每天在虚度光阴毫无意义,似乎陷入了一个自我怀疑自我焦虑的死循环了。我想我该做的点什么去跳出这个循环。。。 自我叙述 我相信,每个人都有一个自命不凡的梦,总觉得自己应该和别人不一样,...
继续阅读 »

我是一个容易焦虑的人,工作时候想着跳槽,辞职休息时候想着工作,休息久了又觉得自己每天在虚度光阴毫无意义,似乎陷入了一个自我怀疑自我焦虑的死循环了。我想我该做的点什么去跳出这个循环。。。


自我叙述


我相信,每个人都有一个自命不凡的梦,总觉得自己应该和别人不一样,我不可能如此普通,自己的一生不应该泯然众生,平凡平庸的度过。尤其是干我们it这一行业的,都有一个自己的程序员梦,梦想着,真的能够用 “代码改变世界”


入行回顾



你们还记得自己是什么时候,入行it行业的吗



我今年已经28岁了,想起来入行,还挺久远的,应该是2016入行的,我也算是半路出家的,中间有过武术梦 歌唱梦 但是电脑什么上学那会就喜欢玩,当然是指游戏,




武术梦




来讲讲我得第一个·梦,武术梦,可能是从小受到武打演员动作电视剧的影响,尤其那个时候,成龙大哥的电影,一直再放,我觉得学武术是很酷的一件事情,尤其那会上小学,还是初中我的体育还是非常好的,


然后我们家那个时候电视还是黑白的,电视机。哈哈哈😀电视台就那么几个,放来放去,有一个台一直重复放成龙电影,还有广告, 都是 学武术就到 xxxx学校, 我被洗脑了吧


于是真的让我爸,打电话质询了一下,可是好像他们这种武术学校都是托管式的,封闭式学习,听说很苦,,,,当然这不是重点,重点每年学费非常的贵,en~,于是乎我的这个梦想终止了,。。




歌唱梦




为啥会有唱歌想法,你猜对了,是被那个时候的好声音给影响了,那个时候好声音是真的很火,看的时候我一度以为我也可以上好声音,去当歌手然后出道,当明星,什么的。


不过不经历打击,怎么会知道自己的下线在哪里呢


我小学换了两到三个学校,到初中,再到高中,你们还记得自己读高中那会吗,高中是有专业选择的,入学军训完以后。


我们代班主任,和我们说有三个专业方向可以选择,艺术类,分美术,和唱歌,然后是文化类,然后艺术类就业考大学分数会低很多,然后一系列原因,哈哈哈,我就选择了歌唱班。


我最好伙伴他选择了,美术类就是素描。这里我挺后悔没有选择 美术类。


到了歌唱班,第一课就是到专业课有钢琴的教室,老是要测试每个同学的,音色和音高,音域
然后各自上台表演自己的拿手的一首歌,。我当时测试时候就是跟着老师的弹的钢琴键瞎唱,


表演的歌曲是张雨生《大海》 也就唱了高潮那么几句。。 😀现在想起来还很羞耻,那是我第一次在那么多人面前唱歌,


后面开始上课老师说我当时分班时候音色什么还不错,但学到后面,我是音准不太行,我发现。再加上我自己的从小感觉好像有点自卑敏感人格,到现在把,我唱歌,就越来越差,


当然我们也有乐理。和钢琴课,我就想主助攻乐理和钢琴,


但是我很天真


乐理很难学习,都是文科知识需要背诵,但是他也要有视唱,也就是唱谱子,duo,re,mi,fa,suo,la,xi,duo。。等,我发现我也学不进去


后面我又开始去学钢琴,但是钢琴好像需要一定童子功,不然可能很难学出来,于是我每天早上6点钟起来,晚上吃完饭就去钢琴教师抢占位置, 还得把门堵着怕人笑话,打扰我,


结果你们也猜到了,音乐方面天赋很重要,然后就是性格上面表演上面,要放得开,可是我第一年勉强撑过去了,后面第二年,专业课越来越多了,我感觉我越来越自卑~,然后成绩就越来越差,老师也就没太重视,嗯~好不容撑到了第二年下半年,放暑假,


但是老师布置任务暑假要自己去外面练钢琴,来了之后要考试,我还花钱去外面上了声乐课钢琴课,哎,我感觉就是浪费钱,,,,,因为没什么效果,性格缺陷加上天赋不行,基本没效果,那段时间我也很痛苦的,因为越来越感觉根本容入不进去班级体,尤其是后面高二,了专业课很多大部分是前面老师带着发生开嗓,后面自由练习,我也不好意思,不想练习,所以
到后面,高二下学习我就转学了,,,,


当然我们班转学的,不止我一个,还有一个转学的 和我一个寝室的,他是因为音高上不去,转到了文科班, 还有一个是挺有天赋,我挺羡慕的,但是人家挺喜欢学习,不喜欢唱歌什么,就申请转到了,文科班。 不过她转到文科班,没多久也不太好,后面好像退学了,,我一直想打听他的消息,都在也没打听到了




玩电脑




我对电脑的组装非常感兴趣,喜欢研究电脑系统怎么装,笔记本拆装,台式机拆装,我会拿我自己的的笔记本来做实验,自己给自己配台式机,自己给自己笔记本增加配置,哈哈哈哈。对这些都爱不释手。



这还是我很早时候,自己一点一点比价,然后去那种太平洋电脑城,电脑一条街,那种地去找人配置的。想想那时候配置这个电脑还挺激动,这是人生的第一台自己全部从零开始组装配的电脑,


本来打算,后面去电脑城上班,开一个笔记本维修,电脑装配的门面的,(因为自己研究了很多笔记本系统,电脑组装),可是好像听电脑城的人说,电脑组装什么的已经不赚钱了,没什么价格利润,都是透明的而且更新迭代非常的快,电脑城这种店铺也越来越少了,都不干了,没有新人再去干这个了,于是乎我的第一份工作失业 半道崩殂了,哈哈哈哈还没有开始就结束了。




学it




后面我又报名自学了,it编程,《xxx鸟》 但是学it我学起来,好像挺快的,挺有感觉的,入学前一个星期,要等班人数到齐才能开班,我们先来的就自己学习打字了,我每天都和寝室人,一起去打字,我感觉那段时间我过得挺开心和充实的,


后面我们觉得自带寝室不好,环境差,于是就几个人一起,搬出去住了,一起学习时候有一个年级26了,我和他关系还蛮好的,不过现在也没什么联系了,,,


学习时候,每次做项目时候我都是组长,那个时候原来是有成就感的,嗯,学习it好像改变了,我学唱歌那个时候,一些自卑性格,可能是遇到了一个好的老师吧


当然后面就顺利毕业,然后找到了工作了,,,


直到现在我还在it行业里


嗯~还想往下面写一点什么,,,下一篇分享一下我入门感受和经历吧


关注公众号,程序员三时 希望给你带来一点启发和帮助


作者:程序员三时
链接:https://juejin.cn/post/7230351646798643255
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

移动端页面加载耗时监控方案

iOS
本文阐述了个人对移动端页面加载耗时监控的一些理解,主要从:节点划分及对应的实现方案,线上监控注意点,后续还能做的事 三个方面来和大家分享。 前言 移动端的页面加载速度,作为最为影响用户体验的因素之一,是我们做移动端性能优化的重点方向之一。 而优化的效果体现,...
继续阅读 »

本文阐述了个人对移动端页面加载耗时监控的一些理解,主要从:节点划分及对应的实现方案,线上监控注意点,后续还能做的事 三个方面来和大家分享。



前言


移动端的页面加载速度,作为最为影响用户体验的因素之一,是我们做移动端性能优化的重点方向之一。


而优化的效果体现,需要置信的指标进行衡量(常见方法论:寻找方向->确定指标->实践->量化收益),而本文想要分享的就是:如何真实、完整、方便的获得页面加载时间,并会向线上监控环节,有一定延伸。


本文的示例代码都是OC(因为Java和kotlin我也不会😅),但相关思路和方案也适用于Android(Android端已实现并上线)。


页面加载耗时


常见方案


页面加载时长是一直以来大家都在攻坚的方向,所以市面上也有非常非常多的度量方案,从节点划分角度看:


较为基础的:ViewController 的 init -> viewDidLoad -> viewDidAppear


更进一步的:ViewController 的 init -> viewDidLoad -> viewDidAppear -> user Interactable


主流方案:ViewController 的 init -> viewDidLoad -> viewDidAppear -> view render completed -> user Interactable


还有什么地方可以改进的吗?


对于这些成熟方案,我还有什么可以更进一步的吗?主要总结为以下几个方面吧:

  • 完整反映用户体感

我们做性能优化,归根结底,更是用户体验优化,在满足功能需要的同时,不影响用户的使用体验。
所以,我个人认为,大多数的性能指标,都要考虑到用户体验这个方向;页面启动速度这一块,更是如此;而传统的方案,能够完整的反应用户体感吗?
我觉得还是有一部分的缺失的:用户主动发起交互到ViewController这个阶段。这一部分有什么呢,不就是直接tap触发的action里vc就初始化了吗?
实际在一些较为复杂、大型的项目中,并不然,中间可能会有很多其他处理,例如:方法hook、路由调度、参数解析、containerVC的初始化、动态库加载等等。这一部分的耗时,实际上也是用户体感的一部分,而这一部分的耗时,如果不加监控的话,也会对整体耗时产生劣化。(这里可能会有小伙伴问了,这些东西,不应该由各自负责的同学,例如负责路由的同学,自行监控吗?这里我想阐述的一个观点时,时长类的监控,如果由几个时间段拼接,相比于endTime - startTime,难免会产生gap,即,加入endTime = 10,startTime = 0,那么中间分成两段,很有可能endTime2 = 10,startTime2 = 6;endTime1 = 4,startTime1 = 0,造成总时长不准。总而言之,还是希望得到一个能够完整反映用户体感的时长。)

  • 数据采集与业务解耦

这一点其实市面上的很多方案已经做得很好了。解耦,一方面是为了,提效:避免后续有新的页面需要监控时,需要进行新的开发;另一方面,也是避免业务迭代对于监控数据的影响:如果是手动侵入性埋点,很难保证后续新增的耗时任务对监控数据不产生影响。
而本文方案,不需要在业务代码中插入任何代码,大都是通过方法hook来实现数据采集的;而对范围、以及匹配关系等的控制,也都是通过配置来完成的。


具体实现


节点确定&数据采集方式



根据一个页面(ViewController)的加载过程中,开发主要进行的处理,以及可能对用户体感产生影响的因素,将页面加载过程划分为如上图所示的11个节点,具体解释及实现方案如下:


1. 用户行为触发页面跳转

由于页面的跳转一般是通过用户点击、滑动等行为触发的,因此这里监听用户触摸屏幕的时间点;但有效节点仅为VC在初始化前的最后一次点击/交互。


具体实现
hook UIWidow 的 sendEvent:方法,在swizzle方法内记录信息;为了性能考虑,目前仅记录一个uint64_t的时间戳,且仅内存写;
注意这里需要记录手指抬起的时间,即 touch.phase == UITouchPhaseEnded,因为一般action被调用的时机就是此时;
同时,为了适配各种行为触发的新页面出现,还增加了一个手动添加该节点的方法,使一些较复杂且不通用,业务特性较强的初始化场景,也能够有该节点数据,且不依赖hook;但注意该手动方法为侵入式数据采集方式。


2. ViewController的初始化

具体实现:hook UIViewController或你的VC基类 的 - (instancetype)init 的方法;


3. 本地UI初始化

不依赖于网络数据的UI开始初始化。


这个节点,我实际上并没有在本次实现,这里的一个理想态是:将这部分行为(即UI初始化的代码),通过协议的方式,约束到指定方法中;例如,架构层面约束一个setupSubviews的接口,回调给各业务VC,供其进行基础UI绘制(目前这种方式再一些更复杂的业务场景下实现并运行较好);有这个基础约束的前提下,才能准确的采集我理想中该节点的耗时。而我目前所负责的模块,并没有这种强约束,而又不能简单的去认为所有基础UI都是在viewDidLoad中去完成的。因此需要 对原有架构的一定修改 或 能够保证所有基础UI行为都在viewDidLoad中实现,才能够实现该节点数据的准确采集。
因此2 ~ 3和3 ~ 4间的耗时,被融合为了一段2 ~ 4的耗时。


4. 本地UI初始化完成

不依赖于网络数据的UI初始化完成。


具体实现:监听主线程的闲时状态,VC初始化 节点后的首个闲时状态表示 本地UI初始化完成;(闲时状态即runloop进入kCFRunLoopBeforeWaiting


5. 发起网络请求

调用网络SDK的时间点。


这里描述的就是上面的节点划分图的第二条线,因为两条线的节点间没有强制的线性关系,虽然图中当前节点是放在了VC初始化平行的位置,但实际上,有些实现会在VC初始化之前就发起网络请求,进行预加载,这种情况在实现的时候也是需要兼容的。


具体实现:hook 业务调用网络SDK发起请求方法的api;这里的网络库各家实现方案就可能有较大差异了,根据自身情况实现即可。


6. 网络SDK回调

网络SDK的回调触发的时间点。


具体实现:hook 网络SDK向业务层回调的api;差异性同5。


7. send request

8. receive response

真正 发出网络请求 和 收到response 的时间点,用于计算真正的网络层耗时。
这俩和5、6是不是重复了啊?并不然,因为,网络库在接收到发起网络请求的请求后,实际上在端阶段,还会进行很多处理,例如公参的处理、签名、验签、json2Model等,都会产生耗时;而真正离开了端,在网上逛荡那一段,更是几乎“完全不可控”的状态。所以,分开来统计:端部分 和 网络阶段,才能够为后续的优化提供数据基础,这也是数据监控的意义所在


具体实现
实际上系统网络api中就有对网络层详细性能数据的收集

- (void)URLSession:(NSURLSession *)session 
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics;

根据官方文档中的描述

 

可以发现,我们实际上需要的时长就是从 fetchStartDateresponseEndDate 间的时间。
因此可以该delegate,获取这两个时间点。


9. 详细UI初始化

详细UI指,依赖于网络接口数据的UI,这部分UI渲染完成才是页面达到对用户可见的状态。


具体实现:这里我们认为从网络SDK触发回调时,即开始进行详细UI的渲染,因此该节点和节点6是同一个节点。


10. 详细UI渲染完成

页面对用户来说,真正达到可见状态的节点。


具体实现
对于一个常规的App页面来说,如何定义一个页面是否真正渲染完成了呢?


被有效的视图铺满


什么是有效视图呢?视频,图片,文字,按钮,cell,能向用户传递信息,或者产生交互的view;
铺满,并不是指完全铺满,而是这些有效视图填充到一定比例即可,因为按照正常的视觉设计和交互体验,都不会让整个屏幕的每一个像素点都充满信息或具备交互能力;而这个比例,则是根据业务的不同而不同的。
下面则是上述逻辑的实现思路:


确定有效视图的具体类
UITextView 
UITextField
UIButton
UILabel
UIImageView
UITableViewCell
UICollectionViewCell

主流方案中比较常见的,是前几种类,并不包括最后的两个cell;而这里为什么将cell也作为有效视图类呢?
首先,出于业务特征考虑,目前应用该套监控方案的页面,主要是以卡片列表样式呈现的;而且个人认为,市面上很多App的页面也都是列表形式来呈现内容的;当然,如果业务特征并不相符,例如全屏的视频播放页,就可以不这样处理。
其次,将cell作为有效视图,确实能够极大的降低每次计算覆盖率的耗时的。性能监控本身产生的性能消耗,是性能方向一直以来需要着重关注的点,毕竟你一个为了性能优化服务的工具,反而带来了不小的劣化,怎样也说不太过去啊😂~
我也测试了是否包含cell对计算耗时的影响:
下表中为,在一个层级较为复杂的业务页面,页面完全渲染完成之后,完成一次覆盖率达到阈值的扫描所需的时长。






















有效视图包含 cell不包含 cell
检测一次覆盖率耗时(ms)1~515~18
耗时减少15ms/次(83%)

而且,有效视图的类,建议支持在线配置,也可以是一些自定义类。


将cell作为有效视图,大家可能会产生一个新的顾虑:占位cell的情况,再具体点,就是常见的骨架图怎么办?骨架图是什么,就是在网络请求未返回的时候,用缓存的data或者模拟样式,渲染出一个包含大致结构,但不包含具体内容的页面状态,例如这种:





这种情况下,cell已经铺满了屏幕,但实际上并未完成渲染。这里就要依赖于节点的前后顺序了,详细UI是依赖于网络数据的,而骨架图是在网络返回之前绘制完成的,所以真正的覆盖率计算,是从网络数据返回开始的,因此骨架图的填充完成节点,并不会被错误统计未详细UI渲染完成的节点。
覆盖率的计算方式



如上图所示,开辟两个数组a、b,数组空间分别为屏幕长宽的像素数,并以0填充,分别代表横纵坐标;
从ViewController的view开始递归遍历他的subView,遇见有效视图时,将其frame的width和height,对应在数组a、b中的range的内存空间,都填充为1,每次遍历结束后,计算数组a、b中内容为1的比例,当达到阈值比例时,则视为可见状态。
示例代码如下:
- (void)checkPageRenderStatus:(UIView *)rootView {
if (kPhoneDeviceScreenSize.width <= 0 || kPhoneDeviceScreenSize.height <= 0) {
return;
}

memset(_screenWidthBitMap, 0, kPhoneDeviceScreenSize.width);
memset(_screenHeightBitMap, 0, kPhoneDeviceScreenSize.height);

[self recursiveCheckUIView:rootView];
}

- (void)recursiveCheckUIView:(UIView *)view {
if (_isCurrentPageLoaded) {
return;
}

if (view.hidden) {
return;
}

// 检查view是否是白名单中的实例,直接用于填充bitmap
for (Class viewClass in _whiteListViewClass) {
if ([view isKindOfClass:viewClass]) {
[self fillAndCheckScreenBitMap:view isValidView:YES];
return;
}
}

// 最后递归检查subviews
if ([[view subviews] count] > 0) {
for (UIView *subview in [view subviews]) {
[self recursiveCheckUIView:subview];
}
}
}

- (BOOL)fillAndCheckScreenBitMap:(UIView *)view isValidView:(BOOL)isValidView {

CGRect rectInWindow = [view convertRect:view.bounds toView:nil];

NSInteger widthOffsetStart = rectInWindow.origin.x;
NSInteger widthOffsetEnd = rectInWindow.origin.x + rectInWindow.size.width;
if (widthOffsetEnd <= 0 || widthOffsetStart >= _screenWidth) {
return NO;
}
if (widthOffsetStart < 0) {
widthOffsetStart = 0;
}
if (widthOffsetEnd > _screenWidth) {
widthOffsetEnd = _screenWidth;
}
if (widthOffsetEnd > widthOffsetStart) {
memset(_screenWidthBitMap + widthOffsetStart, isValidView ? 1 : 0, widthOffsetEnd - widthOffsetStart);
}

NSInteger heightOffsetStart = rectInWindow.origin.y;
NSInteger heightOffsetEnd = rectInWindow.origin.y + rectInWindow.size.height;
if (heightOffsetEnd <= 0 || heightOffsetStart >= _screenHeight) {
return NO;
}
if (heightOffsetStart < 0) {
heightOffsetStart = 0;
}
if (heightOffsetEnd > _screenHeight) {
heightOffsetEnd = _screenHeight;
}
if (heightOffsetEnd > heightOffsetStart) {
memset(_screenHeightBitMap + heightOffsetStart, isValidView ? 1 : 0, heightOffsetEnd - heightOffsetStart);
}

NSUInteger widthP = 0;
NSUInteger heightP = 0;
for (int i=0; i< _screenWidth; i++) {
widthP += _screenWidthBitMap[i];
}
for (int i=0; i< _screenHeight; i++) {
heightP += _screenHeightBitMap[i];
}

if (widthP > _screenWidth * kPageLoadWidthRatio && heightP > _screenHeight * kPageLoadHeightRatio) {
_isCurrentPageLoaded = YES;
return YES;
}

return NO;
}

但是也会有极端情况(类似下图) 


无法正确反应有效视图的覆盖情况。但是出于性能考虑,并不会采用二维数组,因为w*h的量太大,遍历和计算的耗时,会有指数级的激增;而且,正常业务形态,应该不太会有类似的极端形态。


即使真的会较高频的出现类似情况,也有一套备选方案:计算有效视图的面积 占 总面积 的比例;该种方式会涉及到UI坐标系的频繁转换,耗时也会略差于当前的方式。


在某些业务场景下,例如 无/少结果情况,关于页面等,完全渲染后,也无法达到铺满阈值。
这种情况,会以用户发生交互(同 1、用户行为触发页面跳转 的获取方式)和 主线程闲时状态超过5s (可配)来做兜底,看是否属于这种状态,如果是,则相关性能数据不上报,因为此种页面对性能的消耗较正常铺满的情况要低,并不能真实的反应性能消耗、瓶颈,因此,仅正常铺满的业务场景进行监控并优化,即可。


扫描的触发时机

以帧刷新为准,因为只有每次帧刷新后,UI才会真正产生变化;出于性能考虑,不会每帧都进行扫描,每间隔x帧(x可配,默认为1),扫描一次;同时,考虑高刷屏 和 大量UI绘制时会丢帧 的情况,设置 扫描时间间隔 的上下限,即:满足 隔x帧 的前提下,如果和上次扫描的时间差小于 下限,仍不扫描;如果 某次扫描时,和上次扫描的时间间隔 大于 上限,则无论中间隔几帧,都开启一次扫描。


11. 用户可交互

用户可见之后的下一个对用户来说至关重要的节点。如果只是可见,然后就疯狂占用主线程或其他资源,造成用户的点击等交互行为,还是会被卡主,用户只能看,不能动,这个体感也是很差的;


具体实现:详细UI渲染完成 后的 首次主线程闲时状态。


监控方案


这里由于各家的基建并不相同,因此只是总结一些小的建议,可能会比较零散,大家见谅。

  1. 建议采样收集
  2. 首先,数据的采集或者其他的新增行为/方法,一定是会产生耗时的,虽然可能不多,但还是秉着尽善尽美的原则,还是能少点就少点的,所以数据的采集,包括前面的hook等等一切行为,都只是随机的面向一部分用户开放,降低影响范围; 而且,如果数据量极大,全量的数据上报,其实对数据链路本身也会产生压力、增加成本。 当前,采样的前提是基本数据量足够,不然的话,采样样本量过小,容易对统计结果产生较大波动,造成不置信的结果。

    1. 可配置

    除了基本的是否开启的开关之外,还有其他的很多的点 需要/可以/建议 使用线上配置控制。个人认为,线上配置,除了实现对逻辑的控制,更重要的一个作用,就是出现问题时及时止损。 举一些我目前使用的配置中的例子: - 有效视图类 - 渲染完成状态,横纵坐标的填充百分比阈值 - 终态的兜底阈值 - VC的类名、对应的网络请求 等等。

    1. 本地异常数据过滤

    由于我们的样本数据量会非常大,所以对于异常数据我们不需要“手软”,我们需要有一套本地异常数据过滤的机制,来保证上报的数据都是符合要求的;不然我们后续统计处理的时候,也会因此出现新的问题需要解决。


后续还能做的事


这一部分,是对后续可实现方案的一个美好畅想~


1)页面可见态的终点,不只是覆盖率

其实,实际业务场景中,很多cell,即使绘制完,并渲染到屏幕上,此时,用户可见的也没有达到我们真正希望用户可见的状态,很多内容,都还是一个placeholder的状态。例如,通过url加载的image,我们一般都是先把他的size算好,把他的位置留好,cell渲染完就直接展示了;再进一步,如果是一个视频的播放卡片,即使网络图片加载好了,还要等待视频帧的返回,才能真正达到这张卡片的业务终态\color{red}{业务终态}(求教这里标红后如何能够让字体大小一致)。


这个非常后置,而且我们端上可能也影响不了什么的节点,采集起来有意义吗?


我觉得这是一个非常有价值的节点。一直都在说“技术反哺业务”,那么业务想要用户真正看到的那个终态,就是很重要的一环;因此,用户能在什么时间点看到,从业务角度说,能够影响其后续的方案设计(表现形式),完善用户体感对业务指标的影响;从技术角度说,可以感知真实的全链路的表现(不只是端),从而有针对性的进行优化。


如何获取到所有的业务终态呢?


这里一定是和业务有所耦合的,因为每个业务的终态,只有业务自身才知道;但是我们还是要尽量降低耦合度。
这里可以用协议的方式,为各个业务增加一个达到终态的标识,那么在某个业务达到终态之后,设置该标识即可,这里就是唯一对业务的侵入了;然后和计算覆盖率类似,这里的遍历,是业务维度(这里想象为卡片更好理解一点),只有全部业务的标识都ready之后,才是真正达到业务上的终态。


2)性能指标 关联 业务行为

其实,现在性能监控,各类平台,各个团队,或多或少的都在做,我相信,性能数据采集的代码,在工程中,也不仅仅只有一份;这个现状,在很多成一定规模的互联网公司中都可能存在。


而如果您和我一样,作为一个业务团队,如何在不重复造轮子的情况下,夹缝中求生存呢?


我个人目前的理解:将 性能表现 与 业务场景 相关联。


帧率、启动耗时、CPU、内存等等,这些性能指标数据的获取,在业界都有非常成熟的方案,而且我们的工程里,一定也有相关的代码;而我们能做的,仅仅是,调一下人家的api,然后把数据再自己上传一份(甚至有的连上传都包含了),就完事了吗?


这样我觉得并不能体现出我们自建监控的价值。个人理解,监控的意义在于:暴露问题 + 辅助定位问题 + 验证问题的解决效果


所以我们作为业务团队,将 性能数据 和 我们的业务做了什么 bind 到一起了,是不是就能一定程度上完成了上面的目的呢?


我们可以明确,我们什么样的业务行为,会影响我们的性能数据,也就是影响我们的用户基础体验。这样,不仅会帮助我们定位问题的原因,甚至会影响产品侧的一些产品能力设计方案。


完成这些建设之后,可能我们的监控就可以变成这样,甚至更好的状态: 



3)完善全链路对性能表现的关注

性能数据的关注、监控,不应该仅仅在线上阶段,开发期 → 测试期 → 线上,全链路各个环节都应该具有。

  • 目前各家都比较关注线上监控,相信都已经较为完善;

  • 测试期的业务流程性能脚本;对于测试的性能测试方案,开放应该参与共建或者有一定程度的参与,这样才能从一定程度上保证数据的准确性,以及双方性能数据的相互认可;

  • 开发期,目前能够提供展示实时CPU、FPS、内存数据的基础能力的工具很常见,也比较容易实现;但实际上,在日常开发的过程中,很难让RD同时关注需求情况与性能数据表现。因此,还是需要一些工具来辅助:例如,我们可以对某些性能指标,设置一些阈值,当日常开发中,超过阈值时,则弹窗提醒RD确认是否原因、是否需要优化,例如,详细UI绘制阶段的耗时阈值是800ms,如果某位同学在进行变更后,实际绘制耗时多次超越该值,则弹窗提醒。


作者:XTShow
链接:https://juejin.cn/post/7184033051289059384
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

设计模式-01.简单工厂方法

iOS
这是我尝试写的第一篇文章,以软件开发的设计模式开始,记录一下自己的理解与心得,方便以后回过头来查看。以简单工厂开始: 什么是简单工厂? 简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,它提供了一种简单的方法来创建对象,而不需...
继续阅读 »

这是我尝试写的第一篇文章,以软件开发的设计模式开始,记录一下自己的理解与心得,方便以后回过头来查看。以简单工厂开始:


什么是简单工厂?



简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,它提供了一种简单的方法来创建对象,而不需要直接暴露对象的创建逻辑给客户端。



UML 类图


以计算器为例子,拥有加减乘除功能,画出类图:



具体示例

// 运算符接口
protocol Operation {
    var numberA: Double { set get }
    var numberB: Double { setget }
    func calculate() -> Double
}

// 加法运算类
struct OperationAdd: Operation {
    var numberA: Double = 0.0
    var numberB: Double = 0.0
    func calculate() -> Double {
        return numberA + numberB
    }
}

// 减法运算类
struct OperationSub: Operation {
    var numberA: Double = 0.0
    var numberB: Double = 0.0
    func calculate() -> Double {
        return numberA - numberB
    }
}

// 乘法运算类
struct OperationMul: Operation {
    var numberA: Double = 0.0
    var numberB: Double = 0.0
    func calculate() -> Double {
        return numberA * numberB
    }
}

// 除法运算类
struct OperationDiv: Operation {
    var numberA: Double = 0.0
    var numberB: Double = 0.0
    func calculate() -> Double {
        if numberB != 0 {
            return numberA / numberB
        }
        return 0
    }
}

// 简单工厂类
class OperationFactory {
    static func createOperate(_ operate: String) -> Operation? {
        switch operate {
        case "+":
            return OperationAdd()
        case "-":
            return OperationSub()
        case** "*":
            return OperationMul()
        case "/":
            return OperationDiv()
        default: return nil
        }
    }
}

// 客户端调用
// 加法运算
var addOperation = OperationFactory.createOperate("+")
addOperation?.numberA = 1
addOperation?.numberB = 2
addOperation?.calculate()

// 减法运算
var subOperation = OperationFactory.createOperate("-")
subOperation?.numberA = 1
subOperation?.numberB = 2
subOperation?.calculate()

// 乘法运算
var mulOperation = OperationFactory.createOperate("*")
mulOperation?.numberA = 1
mulOperation?.numberB = 2
mulOperation?.calculate()

// 除法运算
var divOperation = OperationFactory.createOperate("/")
divOperation?.numberA = 1
divOperation?.numberB = 2
divOperation?.calculate()

简单工厂方法总结


优点:

  • 将对象的创建逻辑集中在工厂类中,降低了客户端的复杂度。
  • 隐藏了创建对象的细节,客户端只需要关心需要创建何种对象,无需关心对象是如何创建的。
  • 可以通过修改工厂类来轻松添加新的产品类

缺点:

  • 如果产品的类太多,会导致工厂类中的代码变得很复杂,难以维护。
  • 添加新产品时,需要修改工厂类,也就是会在OperationFactory类中新增case语句,这违背了开闭原则。

总体而言,简单工厂模式适用于创建对象的逻辑相对简单,且产品类的数量较少的场景。对于更复杂的对象创建和对象之间的依赖关系,可以考虑使用其他创建型设计模式,如工厂方法模式或抽象工厂模式。


作者:SamuelWu
链接:https://juejin.cn/post/7267091509953855543
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

SwiftUI精讲:Tabs 标签页组件的实现

iOS
大家好,我们又见面了~今天给大家带来 Tabs标签页组件在SwiftUI中的实现方式。在本文中,我依然会采用一种循序渐进的方式来进行讲解,这其实也是我的实现思路,希望能帮到需要的朋友。 在看本文之前,我强烈建议你先阅读我的上一篇文章 SwiftUI精讲:自定...
继续阅读 »

大家好,我们又见面了~今天给大家带来 Tabs标签页组件在SwiftUI中的实现方式。在本文中,我依然会采用一种循序渐进的方式来进行讲解,这其实也是我的实现思路,希望能帮到需要的朋友。



在看本文之前,我强烈建议你先阅读我的上一篇文章 SwiftUI精讲:自定义 Tabbar 组件 (包含过渡效果),因为有一些重复的知识点在上篇中已经讲过了,本文再讲的话难免会有些乏味,我希望每次写下的文章都有一些新的知识点~


1.Tabs组件的实现


我们先创建 Componets 文件夹,并在其中创建 tabs 文件,我们先简单地创建一个list,并将内容遍历渲染出来,如下所示:


1-1:大致UI的实现

import SwiftUI

struct TabItem: Identifiable {
var id:Int
var text:String
}

struct tabs: View {
let list:[TabItem]
@State var currentSelect:Int = 0
var body: some View {
ScrollView(.horizontal,showsIndicators: false) {
HStack {
ForEach(list) { tabItem in
Button{
currentSelect = tabItem.id
} label: {
HStack{
Spacer()
Text(tabItem.text)
.padding(.horizontal,12)
.fixedSize()
Spacer()
}
}
}
}
.frame(minWidth: UIScreen.main.bounds.width)
}
}
}

struct tabs_Previews: PreviewProvider {
// 创建一些测试数据
static let list = [
TabItem(id:1,text:"关注"),
TabItem(id:2,text:"推荐"),
TabItem(id:3,text:"热榜"),
TabItem(id:4,text:"头条精选"),
TabItem(id:5,text:"后端"),
TabItem(id:6,text:"前端")
]
static var previews: some View {
tabs(list: list)
}
}

这里加上 .frame(minWidth: UIScreen.main.bounds.width) 是为了保证在标签只有两三个的时候,我依然希望它们处于一个均匀布局的状态。代码运行后如图所示:



接着我们加上下划线样式,代码如下所示:

import SwiftUI

struct TabItem: Identifiable {
var id:Int
var text:String
}


struct tabs: View {
let list:[TabItem]
@State var currentSelect:Int = 1
var body: some View {
ScrollView(.horizontal,showsIndicators: false) {
HStack {
ForEach(list) { tabItem in
Button{
currentSelect = tabItem.id
} label: {
HStack{
Spacer()
Text(tabItem.text)
.padding(EdgeInsets(top: 8, leading: 12, bottom: 10, trailing: 12))
.fixedSize()
Spacer()
}
.background(
VStack{
if(currentSelect == tabItem.id){
Spacer()
Rectangle()
.fill(Color(hex: "#1677ff"))
.frame(height: 2)
.padding(.horizontal,12)
.cornerRadius(2)
}
}

)

}
}
}
.frame(minWidth: UIScreen.main.bounds.width)
}
}
}

struct tabs_Previews: PreviewProvider {
// 创建一些测试数据
static let list = [
TabItem(id:1,text:"关注"),
TabItem(id:2,text:"推荐"),
TabItem(id:3,text:"热榜"),
TabItem(id:4,text:"头条精选"),
TabItem(id:5,text:"后端"),
TabItem(id:6,text:"前端")
]
static var previews: some View {
tabs(list: list)
}
}

细心的朋友可能会发现,我的代码里面出现了 Color(hex: "#1677ff"),这是因为我们对Color结构进行了拓展,让它支持16进制颜色的传递,如下所示:

extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}

self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}

代码运行后的效果如图所示:



我们再对字体方面进行优化,我们希望点击后的字体颜色和大小和点击前保持不一致,我们对代码做出修改,如下所示:

HStack{
Spacer()
Text(tabItem.text)
.padding(EdgeInsets(top: 8, leading: 12, bottom: 10, trailing: 12))
.fixedSize()
.foregroundColor(currentSelect == tabItem.id ? Color(hex: "#1677ff") : Color(hex: "#333"))
// 新增
.font(.system(size: currentSelect == tabItem.id ? 20 : 17))
// 新增
.fontWeight(currentSelect == tabItem.id ? .bold : .regular)
Spacer()
}

更改后的效果如图所示:



好了,我们一个普通的tab组件就写完了,完结撒花。


接下来我们需要给下划线添加相应的过渡效果,类似于掘金的下划线移动过渡。如果有从事web端开发的朋友们,我们可以想一下,在web端我们是怎么实现类似的效果的?是不是要通过一些计算,然后赋值给下划线 css的 left 值,或者是 translateX 值。在SwiftUI中,我们压根不用这么麻烦,我们可以使用 matchedGeometryEffect 来轻易的做到相应的效果!


1-2:下划线过渡效果实现


我们对代码稍微修改下,详细的步骤我会在图中进行标注,如下图所示:




接着我们按下 command + R ,运行 Simulator 来查看对应的效果:




可以发现,我们其实已经取得了我们想要的效果。但是由于 tab 在激活的时候,文字对应的动画看着十分晃眼,很讨人厌。如果希望只保留下划线的过渡效果,而不要文字的过渡效果,该怎么做呢?


很简单,我们只需要添加 .animation(nil,value:UUID()) 即可,如下所示:

Text(tabItem.text)
.padding(EdgeInsets(top: 8, leading: 12, bottom: 10, trailing: 12))
.fixedSize()
.foregroundColor(currentSelect == tabItem.id ? Color(hex: "#1677ff") : Color(hex: "#333"))
.font(.system(size: currentSelect == tabItem.id ? 20 : 17))
.fontWeight(currentSelect == tabItem.id ? .bold : .regular)
// 新增
.animation(nil,value:UUID())

现在看起来是不是正常多了? 



1-3:自动滚动到对应位置


大致UI画得差不多了,接下来我们需要在点击比较靠后的tab时,我们希望 ScrollView 能帮我们滚动到对应的位置,我们该怎么做呢?
答案是引入 ScrollViewReader, 使用 ScrollViewProxy中的scrollTo方法,代码如下所示:

struct tabs: View {
let list:[TabItem]
@State var currentSelect:Int = 1
@Namespace var animationNamespace

var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal,showsIndicators: false) {
HStack {
ForEach(list) { tabItem in
Button{
withAnimation{
currentSelect = tabItem.id
}
} label: {
HStack{
Spacer()
Text(tabItem.text)
.padding(EdgeInsets(top: 8, leading: 12, bottom: 10, trailing: 12))
.fixedSize()
.foregroundColor(currentSelect == tabItem.id ? Color(hex: "#1677ff") : Color(hex: "#333"))
.font(.system(size: currentSelect == tabItem.id ? 20 : 17))
.fontWeight(currentSelect == tabItem.id ? .bold : .regular)
.animation(nil,value:UUID())

Spacer()
}
.background(
VStack{
if(currentSelect == tabItem.id){
Spacer()
Rectangle()
.fill(Color(hex: "#1677ff"))
.frame(height: 2)
.padding(.horizontal,12)
.cornerRadius(2)
.matchedGeometryEffect(id: "tab_line", in: animationNamespace)
}
}

)

}
}
}
.frame(minWidth: UIScreen.main.bounds.width)
}
.onChange(of: currentSelect) { newSelect in
withAnimation(.easeInOut) {
scrollProxy.scrollTo(currentSelect,anchor: .center)
}
}
}
}
}

在代码中,我们利用 scrollProxy.scrollTo 方法,轻易地实现了滚动到对应tab的位置。效果如下所示:




呜呼,目前为止,我们已经完成了一个不错的tabs组件。接下来在ContentView中,我们引入该组件。由于我们需要在父视图中知道tabs中currentSelect的变化,我们需要把子组件的 @State 改成 @Binding,同时为了避免 preview报错,我们也要做出对应的修改,如图所示:




1-4:结合TabView完成手势滑动切换


日常我们在使用tabs标签页的时候,如果需要支持用户通过手势进行切换标签页的操作,我们可以结合TabView一起使用,代码如下所示:

import SwiftUI

struct ContentView: View {
let list = [
TabItem(id:1,text:"关注"),
TabItem(id:2,text:"推荐"),
TabItem(id:3,text:"热榜"),
TabItem(id:4,text:"头条精选"),
TabItem(id:5,text:"后端"),
TabItem(id:6,text:"前端"),
]


@State var currentSelect:Int = 1
var body: some View {
VStack(spacing: 0){
tabs(list:list,currentSelect:$currentSelect)
TabView(selection:$currentSelect){
ForEach(list){tabItem in
Text(tabItem.text).tag(tabItem.id)
}
}.tabViewStyle(.page(indexDisplayMode: .never))
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

效果如下所示:



至此,我们总算是完成了一个能满足大部分需求的Tabs组件啦~


2. Tabs组件的拓展


2-1:Tabs组件的吸顶


仅仅实现一个简单的效果怎么够,这不符合笔者精讲技术的精神,我们还要结合日常的业务进行思考。比如,我现在想要在页面滚动的时候,我希望tabs组件能够自动吸顶,应该怎么去实现呢?


首先我们新建View文件夹,在其中放置一些视图组件,并在组件中,添加一些文本,如图所示:



接着我们先思考一下,如何在SwiftUI中做出一个吸顶的效果。这里我使用了 LazyVStack + Section的方式来做。但是有个问题,TabView被包裹在Section里面时,TabView的高度会丢失。我将会在 ScrollView 的外层套上 GeometryReader 来解决这个问题,以下为代码展示:

import SwiftUI

struct ContentView: View {
let list = [
TabItem(id:1,text:"关注"),
TabItem(id:2,text:"推荐"),
TabItem(id:3,text:"热榜")
]

@State var currentSelect:Int = 1
var body: some View {
NavigationView{
GeometryReader { proxy in
ScrollView{
LazyVStack(spacing: 0, pinnedViews:.sectionHeaders) {
Section(
header:tabs(list:list,currentSelect:$currentSelect)
.background(.white)
){
TabView(selection:$currentSelect){
ForEach(list){tabItem in
VStack{
switch currentSelect{
case 1:
Attention()
case 2:
Recommend()
case 3:
Hot()
default:
Text("")
}
}
.tag(tabItem.id)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(minHeight:proxy.size.height)
}
}
}
}
.navigationTitle("Tabs组件实现")
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

效果如图所示:



2-2:下拉刷新的实现


要实现下拉刷新的功能,我们可以使用ScrollView并结合.refreshable 来实现这个效果,代码如下所示:

import SwiftUI

struct ContentView: View {
let list = [
TabItem(id:1,text:"关注"),
TabItem(id:2,text:"推荐"),
TabItem(id:3,text:"热榜")
]

@State var currentSelect:Int = 1
var body: some View {
NavigationView{
GeometryReader { proxy in
ScrollView{
LazyVStack(spacing: 0, pinnedViews:.sectionHeaders) {
Section(
header:tabs(list:list,currentSelect:$currentSelect)
.background(.white)
){
TabView(selection:$currentSelect){
ForEach(list){tabItem in
ScrollView{
switch currentSelect{
case 1:
Attention()
case 2:
Recommend()
case 3:
Hot()
default:
Text("")
}
}
.tag(tabItem.id)
.refreshable {
print("触发刷新")
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(minHeight:proxy.size.height)
}
}
}
}
.navigationTitle("Tabs组件实现")
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

在这里要注意 .refreshable 是 ios15 才能使用的,使用时要考虑API的兼容性。效果如图所示:



至此,我们已经完成了一个很不错的Tabs标签页组件啦。感谢你的阅读,如有问题欢迎在评论区中进行交流~


作者:KSHMR的小粉丝
链接:https://juejin.cn/post/7208016902039207993
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

离职交接,心态要好

话说今年经历了几次项目交接?主动和被动的都算! 01 实在是没想到,都到年底快收尾的时候,还要突然接手离职人员的项目; 不断拉扯和管理内心情绪,避免原地裂开; 年度中再次经历突发的交接事宜,并且团队要在极短的时间内完成所有事项的交接流程; 毫无征兆的变动必然...
继续阅读 »

话说今年经历了几次项目交接?主动和被动的都算!




01



实在是没想到,都到年底快收尾的时候,还要突然接手离职人员的项目;


不断拉扯和管理内心情绪,避免原地裂开;


年度中再次经历突发的交接事宜,并且团队要在极短的时间内完成所有事项的交接流程;


毫无征兆的变动必然会引起一系列问题,最直接的就是影响团队现有节奏进度,需要重新调整和规划;


人员的小规模变动,对部门甚至公司产生的影响是显而易见的,道理都懂;


但是从理性上思考,这个问题并非是无解的,是可以在各个团队中,进行内部消化的;


而人力减少带来的成本降低,以及确保公司的可持续,这是极具确定性的,也是核心目的;


所以感性上说,这个梦幻的职场,可能真的是"爱了";



02



如果是常规情况下的离职流程,交接并不是一件复杂的事情,因为有时间有心情来处理这事,好聚好散;


然而最骚的是,奇袭一般的裁员手段,几分钟谈话结束直接走人;


丝毫不顾及由此带来的影响,认定留下的人应该兜底相应的责任,实现无缝接坑;


当然并不是什么公司都有底气这么做的,大部分还是在裁员通知后,留有一定的时间处理交接事项;


对于交的过程是否有质量,完全看接的一方是否聪明;


从感性上分析,都已经被裁了自然要牢牢把握摸鱼的机会,根本不会在意交出的事项谁来维护,不反越防线就不错了;


而压力会直接传送后闪现到接的人正上方;



03



面对被动离职的交接,确实很难妥善处理,情绪化容易导致事情变质,能真正理性对待的并不多;


交接涉及到三方的核心利益:公司、交出人、接手人,不同角度对待这件事件,态度完全不同;


公司,并不关心交接的质量,只要项目有人兜底即可;


交出方,感性上说直接敷衍交接单上的流程即可,并不在意后续的影响;


接手方,项目交接完成后的第一责任人,可能会关心项目的质量状况;


至于说接手的人能否有时间,有能力,有心情接下这种天降大任,可能除了自己以外,不到出问题的时候关注的很少;


因为项目交接过程没有处理好,从而导致后续的事故与甩锅,情绪化的现象并不少见;


如果是在内部矛盾突出的团队中,由此引发的离职效应也并不少见;



04



人的情绪真的是很奇怪,能让复杂的事情变的简单,也能让简单的事情变的离谱;


情绪上头的时候,事情本身是否真的复杂就已经不太重要了;


接手方最大的问题在于吃力不讨好,如果接了一个质量奇差的项目,意味之后很长一段时间内,工作状态都会陷入混乱的节奏中;


对于大部分研发团队来说,都是存在排期规划的,如果被交接的项目横插一脚,重新调规划影响面又偏大;


向上反馈,多半是回答一句:自行消化;


何谓自行消化,就是占用空闲时间处理,比如下班后,比如周末,比如摸鱼,这些都是对工作情绪的持续伤害;


最终兜底的个人或者团队,可能需要带着夜宵去公司搬砖;



05



吐槽归吐槽,裂开归裂开,成熟的搬砖人不该表现出明显的情绪化;


先捋一捋在面对离职交接时的注意事项,虽然说离职后有一个过渡期,但是真正涉及交接的时间通常一周左右;


作为接手一方,自然期待的是各种文档齐全,对于坑坑洼洼的描述足够清楚;


然而对于被离职的交出方,会带着若隐若现的情绪化状态,很难用心处理交接事项,能不挖坑就已经是良心队友了;


接手方作为后续的兜底人员,兜不住就是一地鸡毛;


如果兜住了呢?那是职责所在、理所应当、不要多想、安心搬砖;



06



面对项目交接,这种隔三差五个月就会突发的事,完全可以用一套固定的模式和节奏去执行;


强烈建议:不排斥、不积极、不情绪化;


但是在处理的过程中要理性且严谨,这样可以规避掉许多可能出现的麻烦,毕竟签了交接单,从此该项目问题根本甩不开;


职场几年,在多次"交"与"接"的角色转换过程中,总结以下几点是研发需要注意的;


P1:文档,信息的核心载体;


不管项目涉及多少文档,照单全收;


如果文档严重缺失甚至没有,直接在交接单上写明情况,并且得加粗划重点展示;


文档和项目的维护极有可能是线性不相关,但是手有文档心里不慌,因为方便后续再把项目交接给其他人;


所以,敷衍一时爽,出事火葬场;



07



P2:代码工程,坑与不坑全看此间;


接到手里的项目,是否会导致情绪崩塌,全看项目代码工程的质量,遇上一堆烂摊子,心情会持续的跌跌跌,然后裂开;


直接把人打包送走的情况也并不少见;


如果代码工程质量极高,架构设计稳定,组件集成比较常规,分包井然有序,悬着的情绪可以适当下落;


P3:库表设计,就怕没注释;


对于数据库层面的设计,与代码工程和业务文档三者相辅相成,把握其中的主线逻辑即可;


但前提是表的设计得有清晰的注释,如果是纯中式英文混搭拼音,且缺乏注释,必然会成为解决问题的最佳卡点;


P4:核心接口,应当关注细节;


从项目的核心业务中选出2-3个复杂的接口读一读;需要将注意点放在细节逻辑上,给内心积蓄一丢丢解决问题的底气;


熟悉接口的基本思路:请求从客户端发出,业务服务的处理逻辑,对数据层面的影响,最终响应的主体;



08



P5:遗留问题,考验职场关系的时候到了;


公司一片祥和的时候,员工之间还可以做做样子;


但是已经走到了一别两宽的地步,从感性上来说只要不藏着掖着就行,还想窥探别人安稳摸鱼的秘密,确实想的不错;


老练的开发常干的事,为了解决某个问题临时上线一段代码,处理好后关闭触发的入口,但是会保留代码主体;


这还算常规操作,最骚的是在本地写一段脚本工具解决线上的问题;


这些隐藏的接口和脚本只有开发的人自己清楚,如果不给个说明文档,这不单是挖坑,还顺手倒了一定比例的水进行混合;


P6:结尾事项,寒暄几句还是要的;


安全意识好的公司,会对员工的账号权限做好备份,以便离职时快速处理,不会留下风险隐患;


在所有权限关闭之后,接手人就可以在交接单上完成签字仪式;


交接完成后还是得适当的寒暄几句,万一接了个坑,转头就得再联系也不稀奇,所以职场留一线方便语音再连线;



09



年度收到的离职交接,已经累计好几份,对这种事情彻底麻了;


事来了先兜着,等兜不住的时候自然会有解决办法;


抗拒与烦躁都不会影响流程的持续推进,这种心态需要自己用清醒的意识不断的说服自己;


最后想探讨一个话题,跟项目前负责人联系,用什么话术请教问题,才能显得不卑不亢?



作者:知了一笑
来源:juejin.cn/post/7157651258046677029
>END


收起阅读 »

MP4 是不是该退休了?

web
背景 对于视频的在线播放,根据视频内容的传输模式可以分为点播和直播,分别用于预先录制内容的传输和实时传输,比如新闻报道、体育赛事都属于直播场景,电影、电视剧、课程视频都属于点播场景。 在 2000 年代初期,Flash 技术开始在 Web 上流行起来,它成为在...
继续阅读 »

背景


对于视频的在线播放,根据视频内容的传输模式可以分为点播直播,分别用于预先录制内容的传输和实时传输,比如新闻报道、体育赛事都属于直播场景,电影、电视剧、课程视频都属于点播场景。


在 2000 年代初期,Flash 技术开始在 Web 上流行起来,它成为在网页上展示视频的主要选择,因为当时没有其他方式能够在浏览器上流式的传输视频。


image.png


随着 HTML5 技术的逐渐成熟,HTML5 的 video 标签开始允许在没有 Flash 插件的情况下在浏览器中直接播放视频。


视频的在线播放主要的技术环节在于视频的解码、显示效率以及数据的传输效率,而 HTML5 的 video 标签将这两个环节进行解藕,开发人员不需要关心视频数据的解码、显示,只需要关心如何去优化数据的获取。


在点播的场景下,因为视频数据已经提前准备好,开发人员只需要制定 video 标签的 src 属性为对应的视频资源地址即可,但是在一些复杂的场景下比如需要根据网络状况做自适应码率、需要优化视频的首屏时间等,那么则需要对视频的一些规格参数以及相关的技术点做进一步的了解。


视频基础


视频帧率


视频的播放原理类似于幻灯片的快速切换。


image.png
每一次画面的切换称作为一帧,而帧率表示每秒切换的帧数,单位数 FPS,人类对画面切换频率感知度是有一个范围的,一般 60 FPS 左右是一个比较合适的范围,但这也需要结合具体的场景,比如在捕捉一个事物快速变化的瞬间时,需要准备足够的帧数才能捕捉到细微的变化,但是当需要拍摄一个缓慢的镜头效果时,帧率不需要太高。


帧率除了要考虑不通场景的播放内容,还需要结合播放设备的刷新频率,如果设备的刷新频率过低,多余的帧就会被丢弃。


视频分辨率


视频在播放时,显示在屏幕中的每一帧中的像素点数量都是相同的,像素是显示设备上发光原件的最小单位,最终呈现的画面是由若干个像素组合起来所展示的。


视频的分辨率是指视频每一帧画面的像素数量,通常以水平方向像素数量 x 垂直高度像素数量的形式表示。分辨率决定了图像的清晰度和细节程度,常见的分辨率有 1080P = 1920 * 1080,这是标准的纯高清分辨率,p 表示是逐行扫描的,与之对应的是 i 表示的是隔行扫描。


image.png


左边一列是逐行扫描,中间一列是隔行扫描,在隔行扫描中会丢失一些页面信息从而加快页面信息的收集。


当设备的分辨率高于视频的分辨率时,设备上的像素点就会多于视频显示所需的像素点,这时就会使用补间算法来为设备上那些未被利用的像素点生成色值信息,否则将导致屏幕上出现黑点,此时人从感官上就会觉得清晰度有所下降。如果视频的分辨率高于设备的分辨率时,则视频多出的信息会被丢弃。


视频格式


视频格式是一种特定的文件格式,用于存储和传输视频数据,它包含了视频图像、音频、字幕和其他相关媒体数据的编码信息,不同的视频格式采用不同的压缩算法和编码方式,以便在存储和传输的过程中有效的减少文件大小并保持高质量的图像。


常见的视频格式有 MP4、AVI、MOV 等,每种视频格式都有其特定的优势和使用场景,比如 MOV 在 Mac 系统上有很好的兼容性,适用于视频编辑。


MP4视频结构


MP4 文件由许多 Box 数据块组成,每个 Box 可以嵌套包含其他 Box,一级嵌套一级来存放媒体信息,这种层次化的结构使得 MP4 文件能够组织和存储各种不同类型的媒体数据和元数据,使其在播放和传输过程中具有灵活性和可扩展性。


image.png


虽然 Box 的类型非常多,但是并不是都是必须的,一般的 MP4 文件都是含有必须的 Box 和个别非必须 Box,下面使用 MP4Box.js 查看 MP4 的具体结构并介绍几个必须 Box:


image.png




  • ftyp


    File Type Box,一般在文件的开始位置,描述的文件的版本、兼容协议等。




  • mdat


    Media Data Box,媒体数据内容,是实际的视频的内容存储区域。该区域通常占整个文件99%+大小。


    image.png




  • moov


    MP4 的媒体数据信息主要存放在 Moov Box 中,是我们需要分析的重点。moov 的主要组成部分如下:




    • mvhd


      Movie Header Box,记录整个媒体文件的描述信息,如创建时间、修改时间、时间度量标尺、可播放时长等。


      image.png




    • udta


      保存自定义数据




    • track


      对于媒体数据来说,track 表示一个视频或音频序列,trak 区域至少存在一个,大部分情况是两个(音频和视频)。


      image.png






形象点来说,moov 可以比如成是整个视频的目录,想要播放视频的话,必须要先加载 moov 区域拿到视频文件目录才能播放视频内容。


为什么MP4视频首屏慢?


当我们在浏览器中打开一个 MP4 视频文件时,浏览器根据就会开始获取视频信息,下载视频 chunk,开始播放视频,通过抓包能够大致了解浏览加载视频过程:


image.png


从请求列表中可知,浏览器发送了三个请求,总耗时 55s ,该视频文件的 box 结构如下:


image.png


下面来具体看一下这三个请求:




  • 第一次请求


    image.png


    浏览器第一次请求时尝试通过 HTTP range request(范围请求)下载整个视频,但是实际只下载了 135 KB 整个请求就完成了,来分析一下具体流程:



    • 浏览器通过 Range: bytes= 0- 首先获取到了 ftyp 信息,这里 ftyp-box 大小为 32 字节;

    • 接下来继续尝试查找 free-box 区域,如果没有就跳过,这里 free-box 大小为 8 字节;

    • 接着尝试查找下一个区域(moov 或 mdat),结果不幸匹配到的区域是 mdat 区域,这时浏览器就会主动终止请求,尝试从尾部查找视频的 moov 区域,因为上面我们讲过 moov 作为视频文件的目录,在播放视频数据前必须先获取 moov 数据,紧接着开始了第二次请求。




  • 第二次请求


    在第一次请求中已经知道了整个视频文件的大小了,如何去确定请求的范围呢?由于 MP4 是由 Box 组成的,标准的 Box 开头的4个字节(32位)为这个 Box 的大小,该大小包括 Box Header 和 Box Body,这样浏览器在第一次请求后就可以确定文件中剩下未解析到的 Box 的开始的 Range 值了。


    image.png


    计算过程(单位字节):
    moov 大小 = 视频文件大小 - ftyp大小 - free大小 - mdat大小 = 22251375 - 32 - 8 - 22224468 = 26867。


    也就是说这一次请求的 range 的开始值最大值不能高于 22251375-26867 = 22251415。


    image.png


    可以看到发出去的请求 Range: bytes=22251374-∞ ,上面计算的 22251415-∞ 包含在内 ,请求到数据后,接下来就是解析 moov-box了,然后根据视频”目录“发起第三次请求。




  • 第三次请求


    根据第二次请求的 moov 解析后,开始下载”真正“的视频的内容准备播放,在第三次请求中,浏览器必须要缓存 4MB 左右才开始播放,




原因分析




  • 过多的数据请求。


    由于 MP4 文件的特殊性,浏览器必须先将 ftyp 、moov 等资源加载完毕之后才能去播放视频,而浏览器是从头部开始依次去加载这些资源,一旦视频资源存放顺序不对,浏览器会发送多次请求分别加载对应的资源。




  • 全量解析 moov


    播放 Mp4 音视频数据前需要先加载并解析 moov 数据,moov 的大小和视频长度成正比,更坏的情况是如果此时服务器没有配置 HTTP range request,浏览器无法跳过查找 moov 这一步,以至于需要下载整个文件。




如何借助HLS 优化视频播放的?


什么是HLS?


HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 http 的媒体流传输协议,用于实时音视频流的传输,HLS 最初是为苹果设备和平台(如iOS和macOS)设计的,但如今已被广泛应用于各种平台和设备上,成为流媒体传输的主要标准之一。


HLS 协议由三部分组成:http、m3u8、ts,这三部分中,http 是传输协议,m3u8 是索引文件,ts是音视频的媒体信息。


HLS的优势和特点是什么?




  • 分段传输


    HLS 将整个音频或视频流切分成短的分段,通常每个分段持续几秒钟,这种分段的方式使得视频内容可以逐段加载和播放,从而提供更好的适应性和流畅性。




  • 基于HTTP协议


    HLS 使用 http 协议进行数据传输,这意味着它能够在标准的 http 服务器上运行,不需要专门的流媒体服务器。




  • 自适应码率


    HLS 支持自适应码率,根据网络带宽和设备性能,动态地选择合适的分辨率和比特率,以提供更好的观看体验。




  • 多码率支持


    媒体源可以同时提供不同分辨率和比特率的视频流,使得用户可以根据网络状况选择合适的码率。




  • 兼容性好


    由于 HLS 使用标准的 HTTP 协议,它在各种设备和平台上具有很好的兼容性,包括苹果设备、Android 设备、PC、智能电视等。在使用 http 播放 MP4 视频时,需要代理服务器支持 http range request 以获取视频的某一部分,但不是所有的代理服务器都对此有良好的支持,而 HLS 不需要,它对代理服务器的要求小很多。




HLS为什么首屏比MP4快?


上面讲过如果要播放 MP4 需要等待整个 moov box 加载完成,这个过程比较消耗时间和带宽,而在 HLS 协议中,分段传输是一个非常重要的特性,HLS 将整个音视频流切分成多个小的分段(ts 文件),这些分段可以被独立的下载和播放。


具体来说,HLS 的工作流程如下:




  • 切分分段:


    原始的音视频流被切分成短小的分段( .ts 文件),每个分段都包含了一小段时间范围内的音视频数据。




  • m3u8 文件:


    服务器生成一个 .m3u8 文件,它是一个播放列表,包含了所有分段的信息,如地址、时长等。播放器通过请求 .m3u8 文件来获取分段列表。




  • 分段请求:


    播放器根据 .m3u8 文件中的分段信息,逐个请求并加载 .ts 分段。




  • 逐段播放:


    播放器逐个播放已经加载的分段,实现连续的音视频播放。




因此,HLS 首屏播放的实现方式不需要像 MP4 那样等待整个文件的基本信息加载完成,而是通过分段传输的方式逐段加载和播放。这使得首屏播放更快速和响应,同时也为流媒体的适应性提供了更好的支持。


为什么选择TS格式文件?


TS(Transport Stream,传输流)是一种封装的格式,它的全称为 MPEG2-TS,主要应用于数字广播系统,譬如 DVB、ATSC 与 IPTV,传输流最初是为广播而设计的,后来通过在标准的188字节数据包中添加4字节的时间码(TC),从而使该数据包成为192字节的数据包,使其适用于数码摄像机,录像机和播放器。


TS(Transport Stream)流在流媒体领域具有多种优点,使得它成为广泛应用于数字电视、流媒体、广播等领域的传输格式之一。以下是TS流的一些优点:




  • 分段传输:


    TS 流将媒体数据切分成小的分段(Packet),每个分段通常持续数毫秒至几十毫秒。这种分段传输使得数据能够按需传输和加载,从而实现快速启动播放和逐段加载,提高了用户体验。




  • 容错性强:


    每个 TS 分段都具有自己的包头信息和校验机制,这使得 TS 流具有较强的容错性。即使在传输过程中发生丢包或错误,也只会影响某个分段,不会影响整个媒体流的播放。




  • 多路复用:


    多路复用的目的一般为了在一个文件流中能同时存储视频、音频、字幕等内容,而TS 流就支持将多个音视频流混合在一个文件中,每个流都有自己的 PID(Packet Identifier)。这使得 TS 流适用于同时传输多个媒体流的场景,如电视广播、有线电视等,提高了传输效率。




  • 支持多种编码格式:


    TS 流可以支持多种音视频编码格式,如H.264、H.265、AAC、MP3等,使其能够适应各种类型的媒体内容。




M3U8格式文件的构成


以下为一个 m3u8 格式文件内容的示例:


#EXTM3U
#EXT-X-VERSION:6
#EXT-X-KEY:METHOD=AES-128,URI="<https://xxxx?token=xxx>"
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:19
#EXTINF:12.000,
<https://xxxx/test/1.ts>
#EXTINF:7.500,
<https://xxxx/test/2.ts>
#EXTINF:13.000,
<https://xxxx/test/3.ts>
#EXTINF:9.720,
<https://xxxx/test/4.ts>
#EXT-X-ENDLIST

在以上示例中包含了 m3u8 文件常见的字段下面为一个M3U8文件可包含的基本字段及含义解释:



  • #EXTM3U 表明该文件是一个 m3u8 文件。每个 M3U8 文件必须将该标签放置在第一行。

  • #EXT-X-VERSION 指定 M3U8 版本号。

  • #EXT-X-KEY 媒体片段可以进行加密,而该标签可以指定解密方法。例如在上面的示例中,该字段指定了加密算法为 AES-128,密钥通过请求 https:xxxx?token=xxx 获取,以用于解密后续下载的 ts 文件。

  • EXT-X-MEDIA-SEQUENCE: 第一个 TS 分片的序列号。每个 TS 分片都拥有一个唯一的整型序列号,每个 TS 分片序列号按出现顺序依次加 1,如果该分片未指定则默认序列号从 0 开始。对于视频点播资源该字段一般是 0,但是在直播场景下,这个序列号标识直播段的起始位置。

  • #EXT-X-TARGETDURATION: 每个 TS 分片的最大的时长,单位为秒。

  • #EXT-X-DISCONTINUITY: 该标签表明其前一个切片与下一个切片之间存在中断。

  • #EXT-X-PLAYLIST-TYPE: 指定流媒体类型。

  • #EXT-X-ENDLIST: M3
    作者:西陵
    来源:juejin.cn/post/7268658252567691322
    U8 文件结束符。

收起阅读 »

慎重选择~~第四家公司刚刚开我,加入重新找工作大队!!!

前景需知 这家公司是我的第四家公司,合同三年,6个月试用期,(当时入职时,谈过说可以提前转正,但是后续当作没这件事),然后7月25日,下午5点半,下班时候告诉我被开了。当天是我手上的一个新项目刚好完结,测试刚过,bug修复完毕,老板让人事通知我,被开了,说是没...
继续阅读 »

前景需知


这家公司是我的第四家公司,合同三年,6个月试用期,(当时入职时,谈过说可以提前转正,但是后续当作没这件事),然后7月25日,下午5点半,下班时候告诉我被开了。当天是我手上的一个新项目刚好完结,测试刚过,bug修复完毕,老板让人事通知我,被开了,说是没有新的项目了。当时我算了算应该是还有几天就转正了。


在职期间


总共是在职6个月差几天转正,期间一直是大小周,说是双休,加班没有任何补偿,然后9点到5.30.(从来没有5点半下班过,最早就是6点半吧,5点半下班会打电话给你,问你为啥下班那么早).然后在这家公司这么久,手上是写了3个新项目,翻新2个老项目,还有维护的。期间没有任何违纪行为,这肯定是一定的,不然也不会等到还有几天才把我开了。在职期间做的事,跟产品沟通为什么不能这么写,用户怎么交互比较合理,不必太过于麻烦,给后端沟通为什么要这个数据,为什么要这样,还要跟老板说 进度怎么样的,预计时间。因为没有测试,是所有员工用了以后提一个bug单,到我这里来,然后我统一看这是谁的问题,然后我去沟通,加上公司内部人员测试,很多东西产品出成那样,觉得不合理,也要给我,我去跟产品沟通,真是沟通成本大的要死,期间有一个要对接别人的app里的积分系统,对公到我们的积分体系里,还要我去对接,这不能找后端嘛?产品又甩给我了,最后又要我去跟第三方沟通,再给自己的后端沟通,成本是真的高啊,我真是有时候头大。听着有点小抱怨,但是吧,其实后面了还好,确实能让你学到很多东西,因为你很清楚这个项目的走向,以及问题,基本上所有东西有点围绕着前端做的感觉,反正每天都是被问,问到最后,无论是谁张嘴我都知道是什么个情况。反正学着接受就好了。


为什么会来到这家公司??


这家公司是我去年面过的一家公司,当时入职他们公司一天我就走了,为什么会走,就是因为代码累积,页面过于卡顿,前端没有任何标注,而且入职第一天,老板就要求改他们的东西,然后第二天就没去了,为什么今年去了,是因为去年这个老板也联系了我几次,说我可以去他们公司试试看,然后过年的前两天还在跟我说,我说那就去试试看看,然后年后那个老板也催着我入职,当时也不是没得选,朋友公司招人内推,他面我,说让我去。我当时主要是跟这个老板说好了,答应了,于是就回绝了我的朋友(真后悔啊,那是真后悔,真不如去朋友哪里了,现在还被开了,卸磨杀驴,我真气)。


在公司半年,我具体做了哪些东西


上面说做了3个新项目,翻新两个新项目。三个新项目是一个是可视化大屏项目,这个项目用的是(vue3加echarts,适配是用v-scae-screen这个组件做的,当然这时候就有人会问,你用这个组件 那其他屏幕的除了你用的这个分辨率,其他比例不对的分辨率,也会有问题,当然这个问题我也遇到了,但是也试了其他的几种方案,但是或多或少都有问题,所以我就选择了这个比较直接.原理## transform.scale(),更详细的可以看看这个组件。)还有一个是小程序的老师端批改作业,并给予点评。(uni-app加uview写的,这个直接上图片,有难点)


image.png
第三个项目也是uni-app写的,就是刚刚写完这个项目我被开了,真是太离谱了。也是一个小程序(uni-app加uview,然后益智类的,可以直接搜索头脑王者这个小程序,基本上是功能还原。不贴我的项目图了,好像我走的第二天就在审核了,主要是websocket长连接写的,因为是对战类,所以长连接时时保持通讯,也是有难点的,因为长连接要多页面保持又要实时获取信息,可以想一下怎么做)。 翻新的项目就不谈了,算是整个翻新,翻新是最累的,因为有的能用有的不能用,该封装封装,数据该处理处理,哦,中间遇到一个有趣的问题,就是el-tabs这个缓存机制,不知道为啥,v-if也不行.


目前的看法


7月25下午被开当天其实我很痛苦,当时人事说话也很过分,让我自己签申请离职说,这样的话赔偿你 0.5,如果不行,你可以去仲裁我们,然后如果我去仲裁,那么离职单,离职证明,赔偿,工资都没有,就拖着你,甚至老板恶言相向的告诉人事说,怎么可以在他的工作简历上留下这个不好的痕迹,影响他以后的工作。其实我听到这些话的时候我除了恶心,我什么话都说不出来,面对这个种情况,我咨询了,12333他们说,让我照常上班,他把你提出打开的软件,你就手动拍摄视频,然后自己打开,直至出示他把你辞退的证明,或者待够15天。我把这个事情实施以后,并且告知公司,仍然不给我出示离职证明,出了一张,辞退通知书,这个通知书我直接上图片,首先这个假,是个病假,是因为后端对我进行了侮辱,然后导致我气的头疼,然后我去请假,是给领导直接请的,她允许以后,我才中午下班是,离开的公司。
image.png为什么会给后端吵架,因为后端不处理逻辑,还要怪我什么都不给他说,什么都不给讲,这是我最气的点,我每次都要给他讲,为什么需要这个数据,为什么你要这么给我,需要什么,我每次都在他没写之前就进行沟通。他最后怪我没讲,并且侮辱我。有的人这时候会说,你为什么不他给你什么就要什么呢?然后自己处理逻辑。降低了耦合性,再往后说 你自己可以写一个node.js啊 为什么不呢?这些都挺对的,但是吧,你不能每次都这么处理问题吧。一个选择题,他应该给你abcd,结果给你1234,然后他要abcd,你说这个转换你做不做?你好说歹说他给你改了,然后一道题4个选项 我回答完以后,他给你答案你自己判断对错,这个逻辑前端写吗,当然也可以,如果他给你的答案是 1呢 1就是a,这时候你又该如何是好?可能你觉得我不信后端会这个对你,一定是你的问题,哈哈 上图片


image.png
09a0e6e51d7d19fc7695ef2bfc77740.jpg
是的没有错,我来教着写,这个时候大家可以喷我了,可以说,你怎么交后端写,你算什么东西,兄弟们,兄弟们,都是我的问题,实在是没办法了,写出了这样得东西 这个东西还能精简,这是只是我为了实现而写得逻辑。


image.png


反正一吐为快,目前是没找工作,下周找找看吧,缓解一下。


当下迷茫得点


希望大家给点建议,就是说因为没有遇到一个好的产品导致我现在想去做产品,我直接现在转产品工资会有一个大跳水,会少很多,但是我也愿意接受,可能是赌气吧,就真的想去做这个,让开发没那么难以沟通。也在想是不是继续前端,保持现状,但是就是想去转产品了,我现在24岁,前端3年多,我应该还有试错得机会,我真的不想在碰见这种情况了,真的好累,加上只是前端,人微言轻,只有出现问题,提出来的东西,才能被采纳,真的好难。所以我是有意愿转转看的,不知道各位怎么看?能评价就评价下,需要我爆雷得,我私信,他们目前好像又在招前端了,怕大家踩雷,在上海。


给大家得建议


就是入职前,还是要好好调查,然后不要只听片面之言,然后就是现状不好的,也不要气馁,就加油好吧,我都没气馁,顶住压力啊,还是

作者:想努力的菜菜
来源:juejin.cn/post/7262156717244530744
希望大家吃好喝好玩好,生活美满。

收起阅读 »

如何用canvas画出验证码

相信在平时的工作中,canvas肯定是我们不可或缺的伙伴,有很多业务场景都需要他来完成,闲来无事,今天我们就先说一下canvas如何画出验证码 首先,我们应该有一个canvas标签(注意:可以标签里面设置宽高,也可以在js里面设置,但是不建议在style样式...
继续阅读 »

相信在平时的工作中,canvas肯定是我们不可或缺的伙伴,有很多业务场景都需要他来完成,闲来无事,今天我们就先说一下canvas如何画出验证码



  • 首先,我们应该有一个canvas标签(注意:可以标签里面设置宽高,也可以在js里面设置,但是不建议在style样式里面去设置,因为会导致里面的元素大小和你设置的不一样)。

  • 然后我们需要有一个随机生成四位数code的一个方法


image.png



  • 然后我们还需要一个canvas的绘画方法


image.png



  • 因为当点击canvas的时候,canvas里面的code也就会变,这时候我们的随机生成四位数的方法就可以用上了。
    -上面这样已经可以在点击的时候生成二维码了,但是还是有一个问题,就是我们没有清空画布,所以导致了每次都是在之前的画布上面去生成,这样就出现文字叠加的问题,所以我们还需要清空画布

  • 所以还需要一个清空画布的方法,这里我采用的是改变画布的大小的方法清空画布


image.png



  • 以上就是完整的一套二维码生成的流程了,以下是完整代码


<template>
<div class="main">
<div class="head">
<el-input v-model="ipt" placeholder="Please input" />
<canvas id="canvas" width="100" height="40" @click="SelectNumber"></canvas>
</div>
<div class="foot">
<el-button type="primary">Primary</el-button>
</div>
</div>
<div>
</div>
</template>

<script setup>
import { nextTick, ref } from 'vue';
import { ElMessage } from 'element-plus'
let ipt = ref()
let num = ref()
// 生成二维码code
const generateCode = () => {
nextTick(() => {
const canvas = document.querySelector('#canvas')
const context = canvas.getContext('2d')
context.font = 'oblique 20px Arial'
context.fillStyle = '#fff'
context.rotate(Math.PI * 1.4 / 180)
context.fillText(num.value, 10, 25)
})
}
// 获取四位随机数
const generateNum = () => {
num.value = Math.floor(Math.random() * 4000 + 1000)
}
// 清空画布
const clearCanvas = () => {
nextTick(() => {
const canvas = document.querySelector('#canvas')
canvas.width = 100
})
}

// 触发画布切换验证码
const SelectNumber = () => {
clearCanvas()
generateNum()
generateCode()
}

const submit = () => {
if (num.value !== Number(ipt.value)) ElMessage.error('验证码输入错误')
else ElMessage.success('验证码输入正确')
}
generateNum()
generateCode()
</script><
作者:L的技术博客
来源:juejin.cn/post/7269290896214212642
/span>
收起阅读 »

不知什么原因,背调没过?

前两天写了一篇文章《电话背调,我给他打了8分》,跟大家聊了职场中沟通的一些基本原则和经验。背调时,同事没给打招呼,几乎也没什么私交,但出于“不坏别人好事”的原则,给了8分的评价。 在稍微大一些的公司中,背调是非常重要的环节。如果拿到了offer,上家公司已经离...
继续阅读 »

前两天写了一篇文章《电话背调,我给他打了8分》,跟大家聊了职场中沟通的一些基本原则和经验。背调时,同事没给打招呼,几乎也没什么私交,但出于“不坏别人好事”的原则,给了8分的评价。


在稍微大一些的公司中,背调是非常重要的环节。如果拿到了offer,上家公司已经离职,新公司还没入职,背调没通过,那就有点悲催了。所以,今天就跟大家聊聊入职背调中的一些注意事项。


第一,背调日趋严格


整体而言,背调是越来越严格了。当然,每家公司都不是为了背调而背调,这是劳民伤财的事,主要是因为履历包装的情况太严重。特别是有一部分刚毕业为了找到工作,通过简历、履历、学历等途径包装成2-3工作经验的情况时有发生。


还有就是,HR也有考核指标,HR在实际招聘的过程中会踩一些坑,为了避免类似的事情发生,会在既有的经验上进行迭代筛查条件。


一般背调有两种方式:体量小一些的公司,HR会给你留电话的人打电话核实;体量大一些的公司会直接委托三方来进行背调核实。


HR直接打电话的背调相对来说会简单一些,而且会有一些个人风格,我们暂且不提。而背调公司的风格一般比较统一。


第二,背调联系人


背调的过程一般会让写三类联系人:直接领导、人力和同事。大概率会背调之前两家公司的履历。


在填写时,你就需要慎重考虑了,基本上会挨个打电话询问你的情况的。所以,你写谁之前,最好先打个招呼,否则说你的坏话,那你就有些悲催了。像上篇文章中同事那样不打招呼的操作,是强烈不建议的。


另外,你写的这些联系人要能够联系得到才行。如果都联系不上,过的可能性就不大了。


第三,背调的过程


曾经多次作为上级领导参与背调,背调的核心点有几项(他,代表被背调的人):


确认身份:确认你是否是本人,是否是他的上级领导。同时,还会确认他的岗位信息,他是否带下属,下属多少人等。除了电话确认之外,甚至还会要求入职人员跟相关人要公司企业管理软件(钉钉、飞书等)中带有企业名称、填写人姓名的截图证明等。


表现评分:在工作表现、沟通表现等方面会有1-10分,询问各项的表现评分是多少。同时,在问题的涉及上还会有一些交叉认证的小策略。会涉及到:工作表现如何,与大家相处的如何,吃苦耐劳能力如何,抗压能力如何、离职原因是什么、你是否满意他的整体表现、是否有违规操作等等。


交叉确认:除了个人表现的评分确认之外,如果同一个公司的背调,还会交叉确认一下你留的其他人员是否也是这家公司的,是否是对应岗位的。


如果你预留的信息都是真实的,那么不用担心什么,跟填写联系人的打好招呼就行了。如果部分内容有出入,那可要交代清楚了。


另外,在工作中,平时与同事和上下级相处时,保持融洽的关系,留一个联系方式等也有一定的必要性。


第四,其他可能性


除了上面统一的背调流程之外,某些公司还会有更加严格的背调信息。这些信息是否违法违规暂且不说,但是是会出现的。如果你不care这份工作,可以拒绝提供的。


常见的有收入证明、工资流水、社保缴纳、征信报告等。


收入证明一般由上家公司出具并盖章,私企或关系比较好一些,可以适当调整。工资流水可以是银行打印的或下载的电子单据。社保缴纳可以提供查询到的流水。征信报告这个对于部分金融相关的行业会有一定要求,会引导你操作申请一份个人征信报告。


另外还有两项,大多数人可能不知道,但对于高端的一些岗位也会涉及到:HR的圈子和劳动诉讼。


HR是有自己的圈子和人脉的,而且可能比你想象的要广。如果你在上家公司,或者在圈子里名声不好,很可能会被问出来的。这个也没其他办法,自己的个人人设和职业素养问题了。


另外一个就是劳动诉讼,这个也是可以调查出来的,除了有专门的机构可以做这些事之外,某些诉讼可以在企业的“法律诉讼”中查到诉讼的另一方的。当然,如果曾经涉及到刑事案件用人单位也是可以查出来的。


最后


市场越来越卷,而打工人越来越不容易。在日常工作中保持良好的人际关系和职业素养,更多的还是为自己铺好后路。在面试找新工作时保持诚信,尽量避免出现撒一个谎,用一百个谎来圆的情况。


最后,无论怎样,都要有备选方案,既不能丢了西瓜捡了芝麻,更不能最后两手空空。

作者:程序新视界
来源:juejin.cn/post/7265999062242263100

收起阅读 »

电话背调,我给他打了8分

前段时间招聘的一位开发,待了两三周,拿到了京东的offer,离职了。在离职的后一天,接到了他新公司的背调电话,几乎每项都给他打了8分。这个分数打的有点虚,单纯只是为了不影响他下家的入职。离职之前,收到他在飞书上查看电话号码的消息,大概也猜到是在填写背调人信息,...
继续阅读 »

前段时间招聘的一位开发,待了两三周,拿到了京东的offer,离职了。在离职的后一天,接到了他新公司的背调电话,几乎每项都给他打了8分。这个分数打的有点虚,单纯只是为了不影响他下家的入职。

离职之前,收到他在飞书上查看电话号码的消息,大概也猜到是在填写背调人信息,但自始至终,他也没打一声招呼,让给个好评。

离职最后一天,办完手续,没跟任何人打一个招呼,不知什么时候就消失了。

当初他刚入职一周时,其实大家都已经看出他在沟通上有很大问题,还想着如何对他有针对性的安排工作和调整,发挥他的长处,避免他的短处。但没想到这么快就离职了。在他提离职时,虽没过多挽留,但给了一些过来人的建议,很明显也听不进去。

站在旁观者的角度来看,他的职业生涯或即将面临到的事几乎能看得清清楚楚,但他有自己的坚持,别人是没办法的。

就着这事,聊聊最近对职场上关于沟通的一些思考:

第一,忌固执己见

职场中最怕遇到的一种人就是固执己见的人。大多数聪明人,在遇到固执己见的人时,基本上都会在三言两语之后停止与其争辩。因为,人一旦在自己的思维层次形成思维闭环,是很难被说服的。

而对于固执己见的人,失去的是新的思维、新的思想、纠错学习的机会,甚至是贵人的相助。试想一下,本来别人好像给你提建议,指出一条更好的路,结果换来的是争辩,是抬杠,聪明人都会敬而远之,然后默默地在旁边看着你掉坑里。

真正牛的人,基本上都是兼听则明,在获得各类信息、建议之后,综合分析,为己所用。

第二,不必说服,尊重就好

站在另外一个方面,如果一件事与己无关,别人有不同的意见,或者这事本身就是别人负责,那么尊重就好,不必强行说服对方,不必表现自己。

曾看到两个都很有想法的人,为一件事争论好几天,谁也无法说服谁。一方想用权力压另一方,另一方也不care,把简单的事情激化,急赤白脸的。

其实争论的核心只是展现形式不同而已,最终只是在争情绪、争控制感、争存在感而已,大可不必。

对于成年人,想说服谁都非常难的。而工作中的事,本身就没有对错,只有优劣,大多数时候试一下就知道了。

有句话说的非常好,“成年人的世界只做筛选,不做教育”。如果说还能做点什么,那就是潜移默化的影响别人而已。

第三,不懂的领域多听少说

如果自己对一个领域不懂,最好少发表意见,多虚心学习、请教即可。任正非辞退写《万言书》的员工的底层逻辑就是这个,不懂,不了解情况,还草率提建议,只是哗众取宠、浪费别人时间。

如果你不懂一个领域,没有丰富的背景知识和基础理论支撑,在与别人沟通的过程中,强行提建议,不仅露怯,还会惹人烦。即便是懂,也需要先听听别人的看法和视角解读。

站在另一个角度,如果一个不懂的人来挑战你的权威,质疑你的决定,笑一笑就好,不必与其争辩。

郭德纲的一段相声说的好:如果你跟火箭专家说,发射火箭得先抱一捆柴,然后用打火机把柴点着,发射火箭。如果火箭专家看你一眼,就算他输。

第四,没事多夸夸别人

在新公司,学到的最牛的一招就是夸人。之前大略知道夸人的效果,但没有太多的去实践。而在新公司,团队中的几个大佬,身体力行的在夸人。

当你完成一件事时,夸“XXX,真牛逼!”,当你解决一个问题时,夸“还得是XXX,不亏是这块的专家”。总之,每当别人有好的表现时,总是伴随着夸赞和正面响应。于是整个团队的氛围就非常好。

这事本身也不需要花费什么成本,就是随口一句话的事,而效果却非常棒。与懂得“人捧人,互相成就彼此,和气生财”的人相处,是一种非常愉悦的体验。

前两天看到一条视频,一位六七岁的小姑娘指派正在玩游戏的父亲去做饭,父亲答应了。她妈妈问:你是怎么做到的?她说:夸他呀。

看看,这么小的小孩儿都深谙的人性,我们很多成人却不懂,或不愿。曾经以为开玩笑很好,现在发现“夸”才是利器,同时一定不要开贬低性的玩笑。

其实,职场中还有很多基本的沟通规则,比如:分清无效沟通并且及时终止谈话、适当示弱、认真倾听,积极反馈、少用反问等等。

当你留意和思考这些成型的规则时,你会发现它们都是基于社会学和心理学的外在呈现。很有意思,也很有用。

作者:二师兄
来源:mp.weixin.qq.com/s/GlTVKWsIRP--VKsZlv8TNA

收起阅读 »

程序员的这10个坏习惯,你中了几个?超过一半要小心了

前言 一些持续关注过我的朋友大部分都来源于我的一些资源分享和一篇万字泣血斩副业的劝诫文,但今年年后开始我有将近4个月没有再更新过。 有加过我好友的朋友私聊我问过,有些回复了有些没回复。 想通过这篇文章顺便说明一下个人的情况,主要是给大家的一些中肯的建...
继续阅读 »

前言



一些持续关注过我的朋友大部分都来源于我的一些资源分享和一篇万字泣血斩副业的劝诫文,但今年年后开始我有将近4个月没有再更新过。




有加过我好友的朋友私聊我问过,有些回复了有些没回复。




想通过这篇文章顺便说明一下个人的情况,主要是给大家的一些中肯的建议。



我的身体



今年年前公司福利发放的每人一次免费体检,我查出了高密度脂蛋白偏低,因为其他项大体正常,当时也没有太在意。




但过完年后的第一个月,我有一次下午上班忽然眩晕,然后犯恶心,浑身发软冒冷汗,持续了好一阵才消停。




当时我第一感觉就是颈椎出问题了?毕竟这是程序员常见的职业病。




然后在妻子陪伴下去医院的神经内科检查了,结果一切正常。




然后又去拍了片子看颈椎什么问题,显示第三节和第四节有轻微的增生,医生说其实没什么,不少从事电脑工作的人都有,不算是颈椎有大问题。




我人傻了,那我这症状是什么意思。




医生又建议我去查下血,查完后诊断出是血脂偏高,医生说要赶紧开始调理身体了,否则会引发更多如冠心病、动脉粥样硬化、心脑血管疾病等等。




我听的心惊胆战,没想到我才34岁就会得上老年病。




接下来我开始调理自己的作息和生活,放弃一些不该强求的,也包括工作之余更新博客,分享代码样例等等。




4个月的时间,我在没有刻意减肥的情况下体重从原先152减到了140,整个人也清爽了许多,精力恢复了不少。




所以最近又开始主动更新了,本来是总结了程序员的10个工作中的不良习惯。




但想到自己的情况,决定缩减成5个,另外5个改为程序员生活中的不良习惯,希望能对大家有警示的作用。



不良习惯


1、工作


1)、拖延症


不到最后一天交差,我没有压力,绝不提前完成任务,从上学时完成作业就是这样,现在上班了,还是这样,我就是我,改不了了。



2)、忽视代码可读性


别跟我谈代码注释,多写一个字我认你做die,别跟我谈命名规范,就用汉语拼音,怎样?其他人读不懂,关我什么事?



3)、忽视测试


我写一个单元测试就给我以后涨100退休金,那我就写,否则免谈。接口有问题你前端跟我说就行了发什么脾气,前后端联调不就这样吗?!



4)、孤立自己


团队合作不存在的,我就是不合群的那个,那年我双手插兜,全公司没有一个对手。



5)、盲目追求技术新潮


晚上下班了,吃完饭打开了某某网,看着课程列表中十几个没学完的课程陷入了沉默,但是首页又出现了一门新课,看起来好流行好厉害耶,嗯,先买下来,徐徐图之。



2、生活


1)、缺乏锻炼和运动


工作了一天,还加班,好累,但还是得锻炼,先吃完饭吧,嗯,看看综艺节目吧,嗯,再看看动漫吧,嗯,还得学习一下新技术吧,嗯,是手是得洗澡了,嗯,还要洗衣服,咦,好像忘记了什么重要的事情?算了,躺床上看看《我家娘子不对劲》慢慢入睡。



2)、加班依赖症


看看头条,翻翻掘金,瞅瞅星球,点点订阅号,好了,开始工作吧,好累,喝口水,上个厕所,去外面走走,回来了继续,好像十一点半了,快中午了,待会儿吃什么呢?


午睡醒了,继续干吧,看看头条,翻翻掘金,瞅瞅星球,点点订阅号,好了,开始工作吧,好累,喝口水,上个厕所,去外面走走,回来了继续,好像5点半了,快下班了,任务没完成。


算了,加加班,争取8点之前搞定。


呼~搞定了,走人,咦,10点了。



3)、忽视饮食健康


早上外卖,中午外卖,晚上外卖,哇好丰富耶,美团在手,简直就是舌尖上的中国,晚上再来个韩式炸鸡?嗯,来个韩式甜辣酱+奶香芝士酱,今晚战个痛快!



4)、缺乏社交活动


好烦啊,又要参加公司聚会,聚什么餐,还不是高级外卖,说不定帮厨今天被大厨叼了心情不好吐了不少唾沫在里面,还用上完厕所摸了那里没洗的手索性搅了一遍,最后在角落里默默看着你们吃。


吃完饭还要去KTV?继续喝,喝不死你们,另外你们唱得很好听吗?还不是看谁嗷的厉害!


谁都别跟我说话,尤其是领导,离我越远越好,唉,好想回去,这个点B站该更新了吧,真想早点看up主们嘲讽EDG。



5)、没有女朋友


张三:我不是不想谈恋爱,是没人看得上我啊,我也不好意思硬追,我也要点脸啊,现在的女孩都肿么了?一点暗示都不给了?成天猜猜猜,我猜你MLGB的。


李四:家里又打电话了,问在外面有女朋友了没,我好烦啊,我怎么有啊,我SpringCloudAlibaba都没学会,我怎么有?现在刚毕业的都会k8s了,我不学习了?不学习怎么跳槽,不跳槽工资怎么翻倍,不翻倍怎么买房,不买房怎么找媳妇?


王五:亲朋好友介绍好多个了,都能凑两桌麻将了,我还是没谈好,眼看着要30了,我能咋整啊,我瞅她啊。破罐破摔吧,大不了一个人过呗,多攒点钱以后养老,年轻玩个痛快,老了早点死也不亏,又不用买房买车结婚受气还得养娃,多好啊,以后两脚一蹬我还管谁是谁?



总结



5个工作坏习惯,5个生活坏习惯,送给我亲爱的程序员们,如果你占了一半,真得注意点了,别给自己找借口,你不会对不起别人,只是对不起自己。





喜欢的小伙伴们,麻烦点个赞,点个关注,也可以

作者:程序员济癫
来源:juejin.cn/post/7269375465319415867
收藏下,以后没事儿翻出来看看哈。

收起阅读 »

为什么WebSocket需要前端心跳检测,有没有原生的检测机制?

web
本文代码 github、gitee、npm 在web应用中,WebSocket是很常用的技术。通过浏览器的WebSocket构造函数就可以建立一个WebSocket连接。但当需要应用在具体项目中时,几乎都会进行心跳检测。 设置心跳检测,一是让通讯双方确认对方...
继续阅读 »

本文代码 githubgiteenpm



在web应用中,WebSocket是很常用的技术。通过浏览器的WebSocket构造函数就可以建立一个WebSocket连接。但当需要应用在具体项目中时,几乎都会进行心跳检测。


设置心跳检测,一是让通讯双方确认对方依旧活跃,二是浏览器端及时检测当前网络线路可用性,保证消息推送的及时性。


你可能会想,WebSocket那么简陋的吗,居然不能自己判断连接状态?在了解前先来回顾一下计算机网络知识。


相关的网络知识


TCP/IP协议族四层结构:




  • 应用层:决定了向用户提供应用服务时通信的活动。HTTP、FTP、WebSocket都在该层




  • (TCP)传输控制层:控制网络中两台主机的数据传输:将应用层数据(有必要时对应用层报文分段,例如一个完整的HTTP报文进行分段)发送到目标主机的特定端口的应用程序。给每个数据标记源端口、目标端口、分段后的序号。




  • (IP)网络层:将IP地址映射为目标主机的MAC地址,然后将TCP数据包(有必要时对数据分片)加入源IP、目标IP等信息后经过链路层扔到网络上让其找到目标主机。




  • 链路层:为IP网络层进行发送、接收数据报。将二进制数据包与在网线传输的网络电信号进行相互转换。





TCP是可靠的连接,握手建立连接后,发送方每发送一个TCP报文(对应用层报文分段后形成多个TCP报文),都会期望对方在指定时间里返回已收到的确认消息,如果超时没有回应,会重复发送,确保所有TCP报文可以到达对方,被对方按顺序拼接成应用层需要的完整报文。


WebSocket协议支持在TCP 上层引入 TLS 层,建立加密通信。



WebSocket与HTTP的异同:




  • WebSocket和HTTP一样是应用层协议,在传输层使用了TCP协议,都是可靠的连接。WebSocket在建立连接时,可以使用已有的HTTP的GET请求进行握手:客户端在请求头中将WebSocket协议版本等信息发生到服务器,服务器同意的话,会响应一个101的状态码。就是说一次HTTP请求和响应,即可轻松转换协议到WebSocket。




  • WebSocket可以互相发起请求。当有新消息时,服务器主动通知客户端,无需客户端主动向服务器询问。客户端也可以向后端发送消息。而HTTP中请求只能由客户端发起。




  • WebSocket是HTML5的内容,HTTP则是超文本传输协议,比HTML5诞生更早。




  • 在应用层,WebSocket的每个报文(在WebSocket中叫数据帧)会比HTTP报文(必须包含请求行、请求头、请求数据)更轻量。



    • WebSocket每个数据帧只有固定、轻量的头信息,不会有cookie等或者自定义的头信息。并且建立通讯后是一对一的,不需要携带验证信息。但握手时的HTTP请求会自动携带cookie。

    • WebSocket在应用层就会将大的数据分拆到多个数据帧,而HTTP不会拆分每个报文。




WebSocket与与WebRTC的异同:



  • WebRTC是一种通讯技术,由谷歌发起,被广大浏览器实现。用来建立浏览器和浏览器间的通讯,如视频通话等。而WebSocket是一种经过抽象的协议,可以实现为通讯技术。用来建立浏览器和服务器间的通讯。


协议中的心跳检测机制


从网上检索的答案,WebSocket大概有两种从协议角度出发的,检测对方存活的方式:




  1. WebSocket只是一个应用层协议规范,其传输层是TCP,而TCP为长连接提供KeepAlive机制,可以定时发送心跳报文确认对方的存活,但一般是服务器端使用。因为是TCP传输控制层的机制,具体的实现要看操作系统,也就是说应用层接收到的连接状态是操作系统通知的,不同操作系统的资源调度是不一样的,例如何时发送探测报文(不包含有效数据的TCP报文)检测对方的存活,频率是多久,在不同的系统配置下存在差异。可能是2小时进行一次心跳检测,或许更短。如果连续没有收到对方的应答包,才会通知应用层已经断开连接。这就带来了不确定性。同时也意味着其它依赖该机制的应用层协议也会被影响。也就是说要利用这个过程进行检测,客户端要修改操作系统的TCP配置才行,在浏览器环境显然不行。




  2. WebSocket协议也有自身的保活机制,但需要通讯双方的实现。WebSocket通讯的数据帧会有一个4位的OPCODE,标记当前传输的数据帧类型,例如:0x8表示关闭帧、0x9表示ping帧、0xA表示pong帧、0x1普通文本数据帧等。http://www.rfc-editor.org



    • 关闭数据帧,在任意一方要关闭通道时,发送给对方。例如浏览器的WebSocket实例调用close时,就会发送一个OPCODE为连接关闭的数据帧给服务器端,服务器端接收到后同样需要返回一个关闭数据帧,然后关闭底层的TCP连接。

    • ping数据帧,用于发送方询问对方是否存活,也就是心跳检测包。目前只有后端可以控制ping数据帧的发送。但浏览器端的WebSocket实例上没有对应的api可用。

    • pong数据帧,当WebSocket通讯一方接收到对方发送的ping数据帧后,需要及时回复一个内容一致,且OPCODE标记为pong的数据帧,告诉对方我还在。但目前回复pong是浏览器的自动行为,意味着不同浏览器会有差异。而且在js中没有相关api可以控制。




综上所述,探测对方存活的方式都是服务器主动进行心跳检测。浏览器并没有提供相关能力。为了能够在浏览器端实时探测后端的存活,或者说连接依旧可用,只能自己实现心跳检测。


浏览器端心跳检测的必要性


首先我们先了解一下,目前的浏览器端的WebSocket何时会自动关闭WebSocket,并触发close事件呢?



  • 握手时的WebSocket地址不可用。

  • 其它未知错误。

  • 正常连接状态下,接收到服务器端的关闭帧就会触发关闭回调。


也就是说建立正常连接后,中途浏览器端断网了,或者服务器没有发送关闭帧就关了连接,总之就是在连接无法再使用的情况下,浏览器没有接收到关闭帧,浏览器则会长时间保持连接状态。此时业务代码不去主动探测的话,是无法感知的。


另外通讯双方保持连接意味着需要长时间占用对方的资源。对于服务器端来说资源是非常宝贵的。长时间不活跃的连接,可能会被服务器应用层框架"优化"释放掉。


前端实现心跳检测


实例化一个WebSocket:


function connectWS() {
const WS = new WebSocket("ws://127.0.0.1:7070/ws/?name=greaclar");
// WebSocket实例上的事件

// 当连接成功打开
WS.addEventListener('open', () => {
console.log('ws连接成功');
});
// 监听后端的推送消息
WS.addEventListener('message', (event) => {
console.log('ws收到消息', event.data);
});
// 监听后端的关闭消息,如果发送意外错误,这里也会触发
WS.addEventListener('close', () => {
console.log('ws连接关闭');
});
// 监听WS的意外错误消息
WS.addEventListener('error', (error) => {
console.log('ws出错', error);
});
return WS;
}

let WS = connectWS();

心跳检测需要用到的实例方法:


// 发送消息,用来发送心跳包
WS.send('hello');
// 关闭连接,当发送心跳包不响应,需要重连时,最好先关闭
WS.close();

定义发送心跳包的逻辑:


准备



  • 申请一个变量heartbeatStatus,记录当前心跳检测状态,有三个状态:等待中,已收到应答、超时。

  • 监听WS实例的message事件,监听到就将heartbeatStatus改为:已收到应答。

  • 监听WS实例的open事件,打开后启动心跳检测。


检测




  • 启动一个定时器A。




  • 定时器A执行,1.修改当前状态heartbeatStatus为等待中;2.发送心跳包;3.启动一个定时器B。



    • 发送心跳包后,后端需要立刻推送一个内容一样的心跳应答包给前端,触发前端WS实例的message事件,继而将heartbeatStatus改为已收到应答。




  • 定时器B执行,检测当前heartbeatStatus状态:




    • 如果是已收到应答,证明定时器A执行后,服务器可以及时响应数据。继续启动定时器A,然后不断循环。




    • 如果是等待中,证明连接出现问题了,走关闭或者检测流程。






let WS = connectWS();
let heartbeatStatus = 'waiting';

WS.addEventListener('open', () => {
// 启动成功后开启心跳检测
startHeartbeat()
})

WS.addEventListener('message', (event) => {
const { data } = event;
console.log('心跳应答了,要把状态改为已收到应答', data);
if (data === '"heartbeat"') {
heartbeatStatus = 'received';
}
})

function startHeartbeat() {
setTimeout(() => {
// 将状态改为等待应答,并发送心跳包
heartbeatStatus = 'waiting';
WS.send('heartbeat');
// 启动定时任务来检测刚才服务器有没有应答
waitHeartbeat();
}, 1500)
}

function waitHeartbeat() {
setTimeout(() => {
console.log('检测服务器有没有应答过心跳包,当前状态', heartbeatStatus);
if (heartbeatStatus === 'waiting') {
// 心跳应答超时
WS.close();
} else {
// 启动下一轮心跳检测
startHeartbeat();
}
}, 1500)
}

优化心跳检测


心跳检测异常,但close事件没有触发,大概率是双方之间的网络线路不佳,如果立马进行重连,会挤兑更多的网络资源,重连的失败概率更大,也可能阻塞用户的其它操作。


但也不排除确实是连接的问题,如服务器宕机、意外重启,同时没有告知浏览器需要把旧连接关闭。


所以一发生心跳不应答,个人推荐的做法是,发生延迟后,提醒用户网络异常正在修复中,让用户有个心理准备。然后多发一两个心跳包,连续不应答再提示用户掉线了,是否重连。如果中途正常了,就不需要重连,用户体验更好,对服务器的压力也更小。


// 以上代码需要修改的地方

// 添加一个变量来记录连续不应答次数
let retryCount = 0

WS.addEventListener('message', (event) => {
const { data } = event;
console.log('心跳应答了,要把状态改为已收到应答', data);
if (data === '"heartbeat"') {
// 复位连续不应答次数
retryCount = 0
heartbeatStatus = 'received';
}
})

// 在等待应答的函数中添加重试的逻辑
function waitHeartbeat() {
setTimeout(() => {
// 心跳应答正常,启动下一轮心跳检测
if (heartbeatStatus === 'received') {
return startHeartbeat();
}
// 更新超时次数
retryCount ++;
// 心跳应答超时,但没有连续超过三次
if (retryCount < 3) {
alert('ws线路异常,正在检测中。')
return startHeartbeat();
}

// 超时次数超过三次
WS.close();
}, 1500)
}

最后,为了方便大家共同进步,本文已经把相关的逻辑封装为一个类,并且在npm中可下载玩一

作者:小龟壳阿特greaclar
来源:juejin.cn/post/7268864806558515237
下,也已经开源到github上。

收起阅读 »

《如何超过大多数人》——陈皓(左耳朵耗子)

提前声明本篇文章为转发文章,作者为陈浩(网名又叫左耳朵耗子),文章出处为:酷 壳 – CoolShell。 文章原文链接为:如何超过大多数人 ps:读这篇文章前先看看下面这段话,避免误导大家。 切记,这篇文章不要过度深思(任何东西都无法经得起审视,因为这世上...
继续阅读 »

提前声明本篇文章为转发文章,作者为陈浩(网名又叫左耳朵耗子),文章出处为:酷 壳 – CoolShell


文章原文链接为:如何超过大多数人



ps:读这篇文章前先看看下面这段话,避免误导大家。


切记,这篇文章不要过度深思(任何东西都无法经得起审视,因为这世上没有同样的成长环境,也没有同样的认知水平同时也没有适用于所有人的解决方案;也不要去急着评判里面列出的观点,只需代入到其中适度审视一番自己即可,能跳脱出来从外人的角度看看现在的自己处在什么样的阶段就行。具体怎么想怎么做全在你自己去不断实践中寻找那个适合自己的方案



正文开始:


当你看到这篇文章的标题,你一定对这篇文章产生了巨大的兴趣,因为你的潜意识在告诉你,这是一本人生的“武林秘籍”,而且还是左耳朵写的,一定有干货满满,只要读完,一定可以练就神功并找到超过大多数人的快车道和捷径……


然而…… 当你看到我这样开篇时,你一定会觉得我马上就要有个转折,告诉你这是不可能的,一切都需要付出和努力……然而,你错了,这篇文章还真就是一篇“秘籍”,只要你把这些“秘籍”用起来,你就一定可以超过大多数人。而且,这篇文章只有我这个“人生导师”可以写得好。毕竟,我的生命过到了十六进制2B的年纪,踏入这个社会已超过20年,舍我其谁呢?!


P.S. 这篇文章借鉴于《如何写出无法维护的代码》一文的风格……嘿嘿


相关技巧和最佳实践


要超过别人其实还是比较简单的,尤其在今天的中国,更是简单。因为,你只看看中国的互联网,你就会发现,他们基本上全部都是在消费大众,让大众变得更为地愚蠢和傻瓜。所以,在今天的中国,你基本上不用做什么,只需要不使用中国互联网,你就很自然地超过大多数人了。当然,如果你还想跟他们彻底拉开,甩他们几个身位,把别人打到底层,下面的这些“技巧”你要多多了解一下。


在信息获取上,你要不断地向大众鼓吹下面的这些事:



ps:是像大众哈。不要看错用在自己身上【狗头】


自己怎么做呢?反着做不就行了吗》》》


  • 让大家都用百度搜索引擎查找信息,订阅微信公众号或是到知乎上学习知识……要做到这一步,你就需要把“百度一下”挂在嘴边,然后要经常在群或朋友圈中转发微信公众号的文章,并且转发知乎里的各种“如何看待……”这样的文章,让他们爱上八卦,爱上转发,爱上碎片
  • 让大家到微博或是知识星球上粉一些大咖,密切关注他们的言论和动向…… 是的,告诉大家,大咖的任何想法一言一行都可以在微博、朋友圈或是知识星球上获得,让大家相信,你的成长和大咖的见闻和闲扯非常有关系,你跟牛人在一个圈子里你也会变牛。
  • 把今日头条和抖音这样的APP推荐给大家……你只需要让你有朋友成功地安装这两个APP,他们就会花大量的时间在上面,而不能自拔,要让他们安装其实还是很容易的,你要不信你就装一个试玩一会看看(嘿嘿嘿)。
  • 让大家热爱八卦,八卦并不一定是明星的八卦,还可以是你身边的人,比如,公司的同事,自己的同学,职场见闻,社会热点,争议话题,……这些东西总有一些东西会让人心态有很多微妙的变化,甚至花大量的时间去搜索和阅读大量的观点,以及花大量时间与人辩论争论,这个过程会让人上瘾,让人欲罢不能,然而这些事却和自己没有半毛钱关系。你要做的事就是转发其中一些SB或是很极端的观点,造成大家的一睦讨论后,就早早离场……
  • 利用爱国主义,让大家觉得不用学英文,不要出国,不要翻墙,咱们已经是强国了……这点其实还是很容易做到的,因为学习是比较逆人性的,所以,只要你鼓吹那些英文无用论,出国活得更惨,国家和民族都变得很强大,就算自己过得很底层,也有大国人民的感觉。

然后,在知识学习和技能训练上,让他们不得要领并产生幻觉

  • 让他们混淆认识和知识,以为开阔认知就是学习,让他们有学习和成长的幻觉……

  • 培养他们要学会使用碎片时间学习。等他们习惯利用碎片时间吃快餐后,他们就会失去精读一本书的耐性……
  • 不断地给他们各种各样“有价值的学习资料”,让他们抓不住重点,成为一个微信公众号或电子书“收藏家”……
  • 让他们看一些枯燥无味的基础知识和硬核知识,这样让他们只会用“死记硬背”的方式来学习,甚至直接让他们失去信心,直接放弃……
  • 玩具手枪是易用的,重武器是难以操控的,多给他们一些玩具,这样他们就会对玩具玩地得心应手,觉得玩玩具就是自己的专业……
  • 让他们喜欢直接得到答案的工作和学习方式,成为一个伸手党,从此学习再也不思考……
  • 告诉他们东西做出来就好了,不要追求做漂亮,做优雅,这样他们就会慢慢地变成劳动密集型……
  • 让他们觉得自己已经很努力了,剩下的就是运气,并说服他们去‘及时行乐’,然后再也找不到高阶和高效率学习的感觉……
  • 让他们觉得读完书”、“读过书”就行了,不需要对书中的东西进行思考,进行总结,或是实践,只要囫囵吞枣尽快读完就等同于学好了……

最后,在认知和格局上,彻底打垮他们,让他们变成韭菜。

  • 让他们尽可能地用拼命和加班,尽可能的996,并告诉他们这就是通往成功的唯一路径。这样一来,他们必然会被永远困在低端成为最低的劳动力

  • 让他们不要看到大的形势,只看到眼前的一亩三分地,做好一个井底之蛙。其实这很简单,就是不要告诉他还有另外一种活法,不要扩大他的认识……
  • 宣扬一夜暴富以及快速挣钱的案例,最好让他们进入“赌博类”或是“传销类”的地方,比如:股市、数字货币……要让他们相信各种财富神话,相信他们就是那个幸运儿,他们也可以成为巴菲特,可以成为马云……
  • 告诉他们,一些看上去很难的事都是有捷径的,比如:21天就能学会机器学习,用区块链就能颠覆以及重构整个世界等等……
  • 多跟他们讲一些小人物的励志的故事,这样让他们相信,不需要学习高级知识,不需要掌握高级技能,只需要用低等的知识和低级的技能,再加上持续不断拼命重复现有的工作,终有一天就会成功……
  • 多让他们跟别人比较,人比人不会气死人,但是会让人变得浮躁,变得心急,变得焦虑,当一个人没有办法控制自己的情绪,没有办法让自己静下心来,人会失去耐性和坚持,开始好大喜欢功,开始装逼,开始歪门邪道剑走偏锋……
  • 让他们到体制内的一些非常稳定的地方工作,这样他们拥有不思进取、怕承担责任、害怕犯错、喜欢偷懒、得过且过的素质……
  • 让他们到体制外的那些喜欢拼命喜欢加班的地方工作,告诉他们爱拼才会赢,努力加班是一种福报,青春就是用来拼的,让他们喜欢上使蛮力的感觉……
  • 告诉他们你的行业太累太辛苦,干不到30岁。让他们早点转行,不要耽误人生和青春……
  • 当他们要做决定的时候,一定要让他们更多的关注自己会失去的东西,而不是会得到的东西。培养他们患得患失心态,让他们认识不到事物真正的价值,失去判断能力……(比如:让他们觉得跟对人拍领导的马屁忠于公司比自我的成长更有价值)
  • 告诉他们,你现有的技能和知识不用更新,就能过好一辈子,新出来的东西没有生命力的……这样他们就会像我们再也不学习的父辈一样很快就会被时代所抛弃……
  • 每个人都喜欢在一些自己做不到的事上找理由,这种能力不教就会,比如,事情太多没有时间,因为工作上没有用到,等等,你要做的就是帮他们为他们做不到的事找各种非常合理的理由,比如:没事的,一切都是最好的安排;你得不到的那个事没什么意思;你没有面好主要原因是那个面试官问的问题都是可以上网查得到的知识,而不没有问到你真正的能力上;这些东西学了不用很快会忘了,等有了环境再学也不迟……

最后友情提示一下,上述的这些“最佳实践”你要小心,是所谓,贩毒的人从来不吸毒,开赌场的人从来不赌博!所以,你要小心别自己也掉进去了!这就是“欲练神功,必先自宫”的道理。


相关原理和思维模型


对于上面的这些技巧还有很多很多,你自己也可以发明或是找到很多。所以,我来讲讲这其中的一些原理。


一般来说,超过别人一般来说就是两个维度:

  1. 在认知、知识和技能上。这是一个人赖以立足社会的能力(参看《程序员的荒谬之言还是至理名言?》和《21天教你学会C++》)
  2. 在领导力上。所谓领导力就是你跑在别人前面,你得要有比别人更好的能力更高的标准(参看《技术人员发展之路》)

首先,我们要明白,人的技能是从认识开始,然后通过学校、培训或是书本把“零碎的认知”转换成“系统的知识”,而有要把知识转换成技能,就需要训练和实践,这样才能完成从:认识 -> 知识 -> 技能 的转换。


这个转换过程是需要耗费很多时间和精力的,而且其中还需要有强大的学习能力和动手能力,这条路径上有很多的“关卡”,每道关卡都会过滤掉一大部分人。比如:对于一些比较枯燥的硬核知识来说,90%的人基本上就倒下来,不是因为他们没有智商,而是他们没有耐心。


认知


要在认知上超过别人,就要在下面几个方面上做足功夫:


1)信息渠道。试想如果别人的信息源没有你的好,那么,这些看不见信息源的人,只能接触得到二手信息甚至三手信息,只能获得被别人解读过的信息,这些信息被三传两递后必定会有错误和失真,甚至会被传递信息的中间人hack其中的信息(也就是“中间人攻击”),而这些找不出信息源的人,只能“被人喂养”,于是,他们最终会被困在信息的底层,永世不得翻身。(比如:学习C语言,放着原作者K&R的不用,硬要用错误百出谭浩强的书,能有什么好呢?)


2)信息质量。信息质量主要表现在两个方面,一个是信息中的燥音,另一个是信息中的质量等级,我们都知道,在大数据处理中有一句名言,叫 garbage in garbage out,你天天看的都是垃圾,你的思想和认识也只有垃圾。所以,如果你的信息质量并不好的话,你的认知也不会好,而且你还要花大量的时间来进行有价值信息的挖掘和处理。


3)信息密度。优质的信息,密度一般都很大,因为这种信息会逼着你去干这么几件事,


a)搜索并学习其关联的知识


b)沉思和反省


c)亲手去推理、验证和实践……


一般来说,经验性的文章会比知识性的文章会更有这样的功效。比如,类似于像 Effiective C++/Java,设计模式,Unix编程艺术,算法导论等等这样的书就是属于这种密度很大的书,而像Netflix的官方blogAWS CTO的blog等等地方也会经常有一些这样的文章。


知识


要在知识上超过别人,你就需要在下面几个方面上做足功夫:


1)知识树(图)任何知识,只在点上学习不够的,需要在面上学习,这叫系统地学习,这需要我们去总结并归纳知识树或知识图,一个知识面会有多个知识板块组成,一个板块又有各种知识点,一个知识点会导出另外的知识点,各种知识点又会交叉和依赖起来,学习就是要系统地学习整个知识树(图)。而我们都知道,对于一棵树来说,“根基”是非常重要的,所以,学好基础知识也是非常重要的,对于一个陌生的地方,有一份地图是非常重要的,没有地图的你只会乱窜,只会迷路、练路、走冤枉路!


2)知识缘由。任何知识都是有缘由的,了解一个知识的来龙去脉和前世今生,会让你对这个知识有非常强的掌握,而不再只是靠记忆去学习。靠记忆去学习是一件非常糟糕的事。而对于一些操作性的知识(不需要了解由来的),我把其叫操作知识,就像一些函数库一样,这样的知识只要学会查文档就好了。能够知其然,知其所以然的人自然会比识知识到表皮的人段位要高很多。


3)方法套路学习不是为了找到答案,而是找到方法。就像数学一样,你学的是方法,是解题思路,是套路,会用方程式解题的和不会用方程式解题的在解题效率上不可比较,而在微积分面前,其它的解题方法都变成了渣渣。你可以看到,掌握高级方法的人比别人的优势有多大,学习的目的就是为了掌握更为高级的方法和解题思路


技能

要在技能上超过别人,你就需要在下面几个方面做足功夫:


1)精益求精。如果你想拥有专业的技能,你要做不仅仅是拼命地重复一遍又一遍的训练,而是在每一次重复训练时你都要找到更好的方法,总结经验,让新的一遍能够更好,更漂亮,更有效率,否则,用相同的方法重复,那你只不过在搬砖罢了。


2)让自己犯错。犯错是有利于成长的,这是因为出错会让人反思,反思更好的方法,反思更完美的方案,总结教训,寻求更好更完美的过程,是技能升级的最好的方式。尤其是当你在出错后,被人鄙视,被人嘲笑后,你会有更大的动力提升自己,这样的动力才是进步的源动力。当然,千万不要同一个错误重复地犯!


3)找高手切磋。下过棋,打个球的人都知道,你要想提升自己的技艺,你必需找高手切磋,在和高手切磋的过程中你会感受到高手的技能和方法,有时候你会情不自禁地哇地一下,我靠,还可以这么玩!


领导力


最后一个是领导力,要有领导力或是影响力这个事并不容易,这跟你的野心有多大,好胜心有多强 ,你愿意付出多少很有关系,因为一个人的领导力跟他的标准很有关系,因为有领导力的人的标准比绝大多数人都要高。


1)识别自己的特长和天赋。首先,每个人DNA都可能或多或少都会有一些比大多数人NB的东西(当然,也可能没有),如果你有了,那么在你过去的人生中就一定会表现出来了,就是那种大家遇到这个事会来请教你的寻求你帮助的现象。那种,别人要非常努力,而且毫不费劲的事。一旦你有了这样的特长或天赋,那你就要大力地扩大你的领先优势,千万不要进到那些会限制你优势的地方。你是一条鱼,你就一定要把别人拉到水里来玩,绝对不要去陆地上跟别人拼,不断地在自己的特长和天赋上扩大自己的领先优势,彻底一骑绝尘。


2)识别自己的兴趣和事业。没有天赋也没有问题,还有兴趣点,都说兴趣是最好的老师,当年,Linus就是在学校里对minx着迷了,于是整出个Linux来,这就是兴趣驱动出的东西,一般来说,兴趣驱动的事总是会比那些被动驱动的更好。但是,这里我想说明一下什么叫“真∙兴趣”,真正的兴趣不是那种三天热度的东西,而是那种,你愿意为之付出一辈子的事,是那种无论有多大困难有多难受你都要死磕的事,这才是“真∙兴趣”,这也就是你的“野心”和“好胜心”所在,其实上升到了你的事业。相信我,绝大多数人只有职业而没有事业的。


3)建立高级的习惯和方法。没有天赋没有野心,也还是可以跟别人拼习惯拼方法的,只要你有一些比较好的习惯和方法,那么你一样可以超过大多数人。对此,在习惯上你要做到比较大多数人更自律,更有计划性,更有目标性,比如,每年学习一门新的语言或技术,并可以参与相关的顶级开源项目,每个月训练一个类算法,掌握一种算法,每周阅读一篇英文论文,并把阅读笔记整理出来……自律的是非常可怕的。除此之外,你还需要在方法上超过别人,你需要满世界的找各种高级的方法,其中包括,思考的方法,学习的方法、时间管理的方法、沟通的方法这类软实力的,还有,解决问题的方法(trouble shooting 和 problem solving),设计的方法,工程的方法,代码的方法等等硬实力的,一开始照猫画虎,时间长了就可能会自己发明或推导新的方法。


4)勤奋努力执着坚持。如果上面三件事你都没有也没有能力,那还有最后一件事了,那就是勤奋努力了,就是所谓的“一万小时定律”了(参看《21天教你学会C++》中的十年学编程一节),我见过很多不聪明的人,悟性也不够(比如我就是一个),别人学一个东西,一个月就好了,而我需要1年甚至更长,但是很多东西都是死的,只要肯花时间就有一天你会搞懂的,耐不住我坚持十年二十年,聪明的人发明个飞机飞过去了,笨一点的人愚公移山也过得去,因为更多的人是懒人,我不用拼过聪明人,我只用拼过那些懒人就好了。


好了,就这么多,如果哪天你变得消极和不自信,你要来读读我的这篇文章,子曰:温故而知新。


作者:北洋
链接:https://juejin.cn/post/7207648496978870333
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Swift Enum 关联值嵌套的一些实践

iOS
前言 Swift 中的枚举很强大,算是一等公民。可以定义函数,也可以遵守协议、实现 extension 等等。 关联值也是 Swift 枚举的一大特性。基本用法如下:enum RequestResult { case success case ...
继续阅读 »

前言


Swift 中的枚举很强大,算是一等公民。可以定义函数,也可以遵守协议、实现 extension 等等。


关联值也是 Swift 枚举的一大特性。基本用法如下:

enum RequestResult {
case success
case failure(Error)
}

let result = RequestResult.failure(URLError(URLError.timedOut))
switch result {
case .success:
print("请求成功")
case .failure(let error):
print(error)
}

1、在需要关联值的 case 中声明关联值的类型。


2、在 switch 的 case 中声明一个常量或者变量来接收。


遇到的问题


一般情况下,上述的代码是清晰明了的。但在实际开发的过程中,遇到了以下的情况:关联值的类型也是枚举,而且嵌套不止一层。


比如下面的代码:

enum EnumT1 {
case test1(EnumT2)
case other
}

enum EnumT2 {
case test2(EnumT3)
case other2
}

enum EnumT3 {
case test3(EnumT4)
case test4
}

根据我们的需求,需要进行多次嵌套来进行类型细化。当进行枚举的声明时,代码还是正常的,简单明了。但当进行 case 判断时,代码就变得丑陋难写了。


比如,我只想处理 EnumT3 中的 test4 的情况,在 switch 中我需要进行 switch 的嵌套来处理:

let t1: EnumT1? = .test1(.test2(.test4))
switch t1 {
case .test1(let t2):
switch t2 {
case .test2(let t3):
switch t3 {
case .test4:
print("test4")
case default:
print("default")
}
default:
print("default")
}
default:
print("default")
}

这种写法,对于一个程序员来说是无法忍受的。它存在两个问题:一是代码臃肿,我的本意是只处理某一种情况,但我需要显式的嵌套多层 switch;二是枚举本身是不推荐使用 default 的,官方推荐是显式的写出所有的 case,以防出现难以预料的问题。


废话不多说,下面开始简化之路。


实践一


首先能想到的是,因为是对某一种情况进行处理,考虑使用 if + == 的判断来进行处理,比如下面这种写法:

if t1 == .test1(.test2(.test4)) { }

这样处理有两个不足之处。首先,如果对枚举用 == 操作符的话,需要对每一个枚举都遵守 Equatable 协议,这为我们带来了工作量。其次最重要的是,这种处理方式无法应对 test3 这种带有关联值的情况。

if t1 == .test1(.test2(.test3) { } 

如果这样写的话,编译器会报错,因为 test3 是需要传进去一个 Int 值的。

if t1 == .test1(.test2(.test3(20))) { }

如果这样写的话也不行,因为我们的需求是处理 test3 的统一情况(所有的关联值),而不是某一个具体的关联值。


实践二


经过在网上的一番搜查,发现可以用 if-case 关键字来简化写法:

if case .test1(.test2(.test3)) = t1 { }

这样就能统一处理 test3 这个 case 的所有情况了。如果想获取关联值,可以用下面的写法:

if case .test1(.test2(.test3(let i))) = t1 {
print(i)
}

对比上面的 switch 写法,可以看到,下面的这种写法既易懂又好写😁。


总结来说,当我们遇到关联值多层枚举嵌套的时候,又需要对某一种情况进行处理。那么可以采用实践二的做法来进行代码简化。


参考链接


作者:冯志浩
链接:https://juejin.cn/post/7267919801780994109
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

做点微小的工作,实现 iOS 日历和提醒事项双向同步

iOS
前言 作为一名资深谷粉和十年的 Android 用户,在 2020 年看着各家厂商在笔记本、手机、手表、耳机甚至是智能家居上不断推成出新,补齐数字生活的每一块拼图,辅以“生态化反”的概念牢牢绑住每一个入坑的用户,此时再看看自己手里孤身寡人的 Pixel 手机,...
继续阅读 »

前言


作为一名资深谷粉和十年的 Android 用户,在 2020 年看着各家厂商在笔记本、手机、手表、耳机甚至是智能家居上不断推成出新,补齐数字生活的每一块拼图,辅以“生态化反”的概念牢牢绑住每一个入坑的用户,此时再看看自己手里孤身寡人的 Pixel 手机,以及不知何时就被砍掉的 Pixelbook 系列,默默留下了悔恨的泪水。久苦于谷歌令人失望的硬件生态,我终于还是放弃了 Android 生态,转身拥抱苹果全家桶。苹果硬件生态品类齐全,多年深耕的软件生态和云服务也赋予了这些硬件无缝的使用体验。但有一点一直令我不解,那就是 iOS 的自带应用:日历和提醒事项,它们的事件竟不是相互联动的。而在谷歌套件中,只要一个任务在 Google Tasks 中被新增或是被勾选完成,就会自动同步到 Google Calendar 中,以方便用户进行日程安排或是日程回顾。虽然第三方应用如滴答清单、Sunsama 也提供了类似的功能,但为了原生(免费)体验,只能自己动手折腾了。


前提条件


为了在 iOS 上实现日历和提醒事项双向同步的效果,需要借助快捷指令,搭配 JSBox 写一个脚本,创建数据库来绑定和管理日历和提醒事项中各自的事件。

  1. iOS 14+;
  2. 愿意花 40 RMB 开通 JSBox 高级版;
  3. 不满足第2点,则需要设备已越狱,或者装有 TrollStore;

*破解 JSBox


步骤:

  1. 在 App Store 安装 JSBox;
  2. 通过越狱的包管理工具或者 TrollStore 安装 Apps Manager;
  3. 下载 JSBox 备份文件,在文件管理中长按该文件,选择分享,使用 Apps Manager 打开,在弹出的菜单中点取消;
  4. 在 Apps Manager 中的 Applications 选项卡中,选择 JSBox,点击 Restore 进行还原,即可使用 JSBox 高级版功能(在 JSBox 中的设置选项卡中不要点击“JSBox 高级版”选项,否则需要再次还原);



加载脚本


步骤:

  1. 下载 Reminders ↔️ Calendar 项目文件,在文件管理中长按该文件,选择分享,使用 JSBox 打开;
  2. 在日历和提醒事项中各自新建一个“test”列表,在提醒事项的“test”列表中新建一个定时事件;
  3. 返回 JSBox 中的 Reminders ↔️ Calendar 项目,点击界面下的“Sync now”按钮;
  4. 回到日历中查看事件是否同步成功;



设置项说明:

  1. 同步周期 —— 周期内的事件才会被同步;
  2. 同步备注 —— 是否同步日历和提醒事项的备注;
  3. 同步删除 —— 删除一方事件时,是否自动删除另一方对应的事件;
  4. 单边提醒 —— 日历和提醒事项的事件,谁创建谁通知,关闭则日历和提醒事项都会通知;
  5. 历史待办默认超期完成 —— 补录历史待办,是否默认为已完成;
  6. 提醒事项:默认优先级 —— 在日历创建的事件,同步到提醒事项时候默认的优先级;
  7. 日历:默认用时 —— 在提醒事项创建的事件,同步到日历时默认的时间间隔;
  8. 日历:快速跳转 —— 日历的事件是否在链接项中添加跳转到对应提醒事项的快速链接;
  9. 日历:显示剩余时间 —— 日历的事件是否在地点项中添加时间信息;
  10. 日历:完成变全天 —— 日历的事件是否在完成时,自动变成全天事件(这样日历视图就会将该项目置顶,方便查看未完成项目);



设置快捷指令


步骤:

  1. 打开快捷指令应用,选择自动化选项卡,点击右上角 + 号新增一个任务;
  2. 选择新建个人自动化,设置触发条件为打开应用,指定应用为日历和提醒事项,点击下一步;
  3. 点击按钮新增一个行动,选择执行 JSBox 脚本,在脚本名上填入“Reminders ↔️ Calendar”,点击右下角的 ▶️ 测试,如果输出成功则点击下一步;(注意区分执行 JSBox 脚本和执行 JSBox 界面);
  4. 关闭执行前询问的选项,点击右上角的完成保存任务;



总结


JSBox 是一款运行在 iOS 设备上的轻量级脚本编辑器和开发环境。它内置了大量的 API,允许用户使用 JavaScript 访问原生的 iOS API。另一款相似的应用 Scriptable 在语法的书写上更亲和,但其暴露的事件对象中缺少 last modified 字段,当信息不对称时,没有办法判断日历和提醒事项中事件的新旧。期待 Scriptable 的后续更新,毕竟它是免费的🤡。


作者:Shouduo
链接:https://juejin.cn/post/7258222037319172154
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

在国企做程序员怎么样?

本文已经收录到Github仓库,该仓库包含计算机基础、Java核心知识点、多线程、JVM、常见框架、分布式、微服务、设计模式、架构等核心知识点,欢迎star~ Github地址:github.com/Tyson0314/J… Gitee地址:gitee.com...
继续阅读 »

本文已经收录到Github仓库,该仓库包含计算机基础、Java核心知识点、多线程、JVM、常见框架、分布式、微服务、设计模式、架构等核心知识点,欢迎star~


Github地址:github.com/Tyson0314/J…


Gitee地址:gitee.com/tysondai/Ja…



有读者咨询我,在国企做开发怎么样?


当然是有利有弊,国企相对稳定,加班总体没有互联网多,不过相对而言,工资收入没有互联网高,而且国企追求稳定,往往技术栈比较保守,很难接触新的技术,导致技术水平进步缓慢。


下面分享一位国企程序员的经历,希望能给大家一些参考价值。



下文中的“我”代表故事主人公



我校招加入了某垄断央企,在里面从事研发工程师的工作。下面我将分享一些入职后的一些心得体会。


在国企中,开发是最底层最苦B的存在,在互联网可能程序员还能够和产品经理argue,但是在国企中,基本都是领导拍脑袋的决定,即便这个需求不合理,或者会造成很多问题等等,你所需要的就是去执行,然后完成领导的任务。下面我会分享一些国企开发日常。


1、大量内部项目


在入职前几个月,我们都要基于一种国产编辑器培训,说白了集团的领导看市场上有eclipse,idea这样编译器,然后就说咱们内部也要搞一个国产的编译器,所有的项目都要强制基于这样一个编译器。


在国企里搞开发,通常会在项目中塞入一大堆其他项目插件,本来一个可能基于eclipse轻松搞定的事情,在国企需要经过2、3个项目跳转。但国企的项目本来就是领导导向,只需给领导演示即可,并不具备实用性。所以在一个项目集成多个项目后,可以被称为X山。你集成的其他项目会突然出一些非常奇怪的错误,从而导致自己项目报错。但是这也没有办法,在国企中搞开发,有些项目或者插件是被要求必须使用的。


2、外包


说到开发,在国企必然是离不开外包的。在我这个公司,可以分为直聘+劳务派遣两种用工形式,劳务派遣就是我们通常所说的外包,直聘就是通过校招进来的校招生。


直聘的优势在于会有公司的统一编制,可以在系统内部调动。当然这个调动是只存在于规定中,99.9%的普通员工是不会调动。劳务派遣通常是社招进来的或者外包。在我们公司中,项目干活的主力都是外包。我可能因为自身本来就比较喜欢技术,并且觉得总要干几年技术才能对项目会有比较深入的理解,所以主动要求干活,也就是和外包一起干活。一开始我认为外包可能学历都比较低或者都不行,但是在实际干活中,某些外包的技术执行力是很强的,大多数项目的实际控制权在外包上,我们负责管理给钱,也许对项目的了解的深度和颗粒度上不如外包。


上次我空闲时间与一个快40岁的外包聊天,才发现他之前在腾讯、京东等互联网公司都有工作过,架构设计方面都特别有经验。然后我问他为什么离开互联网公司,他就说身体受不了。所以身体如果不是特别好的话,国企也是一个不错的选择。


3、技术栈


在日常开发中,国企的技术一般不会特别新。我目前接触的技术,前端是JSP,后端是Springboot那一套。开发的过程一般不会涉及到多线程,高并发等技术。基本上都是些表的设计和增删改查。如果个人对技术没啥追求,可能一天的活2,3小时就干完了。如果你对技术有追求,可以在剩余时间去折腾新技术,自由度比较高。


所以在国企,作为普通基层员工,一般会有许多属于自己的时间,你可以用这些时间去刷手机,当然也可以去用这些时间去复盘,去学习新技术。在社会中,总有一种声音说在国企呆久了就待废了,很多时候并不是在国企待废了,而是自己让自己待废了。


4、升职空间


每个研发类央企都有自己的职级序列,一般分为技术和管理两种序列。


首先,管理序列你就不用想了,那是留给有关系+有能力的人的。其实,个人觉得在国企有关系也是一种有能力的表现,你的关系能够给公司解决问题那也行。


其次,技术序列大多数情况也是根据你的工龄长短和PPT能力。毕竟,国企研发大多数干的活不是研发与这个系统的接口,就是给某个成熟互联网产品套个壳。技术深度基本上就是一个大专生去培训机构培训3个月的结果。你想要往上走,那就要学会去PPT,学会锻炼自己的表达能力,学会如何讲到领导想听到的那个点。既然来了国企,就不要再想钻研技术了,除非你想跳槽互联网。


最后,在国企底层随着工龄增长工资增长(不当领导)还是比较容易的。但是,如果你想当领导,那还是天时地利人和缺一不可。


5、钱


在前面说到,我们公司属于成本单位,到工资这一块就体现为钱是总部发的。工资构成分由工资+年终奖+福利组成。


1.工资构成中没有绩效,没有绩效,没有绩效,重要的事情说三遍。工资是按照你的级别+职称来决定的,公司会有严格的等级晋升制度。但是基本可以概括为混年限。年限到了,你的级别就上去了,年限没到,你天天加班,与工资没有一毛钱关系。


2.年终奖,是总部给公司一个大的总包,然后大领导根据实际情况对不同部门分配,部门领导再根据每个人的工作情况将奖金分配到个人。所以,你干不干活,活干得好不好只和你的年终奖相关。据我了解一个部门内部员工的年终奖并不会相差太多。


3.最后就是福利了,以我们公司为例,大致可以分为通信补助+房补+饭补+一些七七八八的东西,大多数国企都是这样模式。


总结


1、老生常谈了。在国企,工资待遇可以保证你在一线城市吃吃喝喝和基本的生活需要没问题,当然房子是不用想的了。


2、国企搞开发,技术不会特别新,很多时候是项目管理的角色。工作内容基本体现为领导的决定。


3、国企研究技术没有意义,想当领导,就多学习做PPT和领导搞好关系。或者当一个平庸的人,混吃等死,把时间留给家人,也不乏是一种好选择。


作者:程序员大彬
链接:https://juejin.cn/post/7182355327076007996
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

像支付宝那样“致敬”第三方开源代码

前言 通常我们在App中会使用第三方的开源代码,按照许可协议,我们应该在App中公开使用的开源代码并且附上对应的开源协议。当然,实际上只有少部分注重合规性的大厂才会这么干,比如下图是支付宝的关于界面的第三方信息。当然,对于小企业,基本上都不会放使用的第三方开源...
继续阅读 »

前言


通常我们在App中会使用第三方的开源代码,按照许可协议,我们应该在App中公开使用的开源代码并且附上对应的开源协议。当然,实际上只有少部分注重合规性的大厂才会这么干,比如下图是支付宝的关于界面的第三方信息。当然,对于小企业,基本上都不会放使用的第三方开源代码的任何信息。 



不过,作为一个有“追求”的码农,我们还是想对开源软件致敬一下的,毕竟,没有他们我都不知道怎么写代码。然而,我们的 App 里用了那么多第三方开源插件,总不能一个个找出来一一致敬吧?怎么办?其实,Flutter 早就为我们准备好了一个组件,那就是本篇要介绍的 AboutDialog


AboutDialog 简介


AboutDialog 是一个对话框,它可以提供 App 的基本信息,如 Icon、版本、App 名称、版权信息等。 



同时,AboutDialog还提供了一个查看授权信息(View Licenses)的按钮,点击就可以查看 App 里所有用到的第三方开源插件,并且会自动收集他们的 License 信息展示。所以,使用 AboutDialog 可以让我们轻松表达敬意。怎么使用呢?非常简单,我们点击一个按钮的时候,调用 showAboutDialog 就搞定了,比如下面的代码:

IconButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: '岛上码农',
applicationVersion: '1.0.0',
applicationIcon: Image.asset('images/logo.png'),
applicationLegalese: '2023 岛上码农版权所有'
);
},
icon: const Icon(
Icons.info_outline,
color: Colors.white,
),
),

参数其实一目了然,具体如下:

  • context:当前的 context
  • applicationName:应用名称;
  • applicationVersion:应用版本,如果要自动获取版本号也可以使用 package_info_plus 插件。
  • applicationIcon:应用图标,可以是任意的 Widget,通常会是一个App 图标图片。
  • applicationLegalese:其他信息,通常会放置应用的版权信息。

点击按钮,就可以看到相应的授权信息了,点击一项就可以查看具体的 License。我看了一下使用的开源插件非常多,要是自己处理还真的很麻烦。 



可以说非常简单,当然,如果你直接运行还有两个小问题。


按钮本地化


AboutDialog 默认提供了两个按钮,一个是查看授权信息,一个是关闭,可是两个按钮 的标题默认是英文的(分别是VIEW LICENSES和 CLOSE)。 



如果要改成本地话的,还需要做一个自定义配置。我们扒一下 AboutDialog 的源码,会发现两个按钮在DefaultMaterialLocalizations中定义,分别是viewLicensesButtonLabelcloseButtonLabel。这个时候我们自定义一个类集成DefaultMaterialLocalizations就可以了。

class MyMaterialLocalizationsDelegate
extends LocalizationsDelegate<MaterialLocalizations> {
const MyMaterialLocalizationsDelegate();

@override
bool isSupported(Locale locale) => true;

@override
Future<MaterialLocalizations> load(Locale locale) async {
final myTranslations = MyMaterialLocalizations(); // 自定义的本地化资源类
return Future.value(myTranslations);
}

@override
bool shouldReload(
covariant LocalizationsDelegate<MaterialLocalizations> old) =>
false;
}

class MyMaterialLocalizations extends DefaultMaterialLocalizations {
@override
String get viewLicensesButtonLabel => '查看版权信息';

@override
String get closeButtonLabel => '关闭';

}

然后在 MaterialApp 里指定本地化localizationsDelegates参数使用自定义的委托类对象就能完成AboutDialog两个按钮文字的替换。

return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AboutDialogDemo(),
localizationsDelegates: const [MyMaterialLocalizationsDelegate()],
);

添加自定义的授权信息


虽然 Flutter 会自动收集第三方插件,但是如果我们自己使用了其他第三方的插件的话,比如没有在 pub.yaml 里引入,而是直接使用了源码。那么还是需要手动添加一些授权信息的,这个时候我们需要自己手动添加了。添加的方式也不麻烦,Flutter 提供了一个LicenseRegistry的工具类,可以调用其 addLicense 方法来帮我们添加授权信息。具体使用如下:

LicenseRegistry.addLicense(() async* {
yield const LicenseEntryWithLineBreaks(
['关于岛上码农'],
'我是岛上码农,微信公众号同名。\f如有问题可以加本人微信交流,微信号:island-coder。',
);
});

这个方法可以在main方法里调用。其中第一个参数是一个数组,是因为可以允许多个开源代码共用一份授权信息。同时,如果一份开源插件有多个授权信息,可以多次添加,只要名称一致,Flutter就会自动合并,并且会显示该插件的授权信息条数,点击查看时,会将多条授权信息使用分割线分开,代码如下所示:

void main() {
runApp(const MyApp());
LicenseRegistry.addLicense(() async* {
yield const LicenseEntryWithLineBreaks(
['关于岛上码农'],
'我是岛上码农,微信公众号同名。如有问题可以加本人微信交流,微信号:island-coder。',
);
});

LicenseRegistry.addLicense(() async* {
yield const LicenseEntryWithLineBreaks(
['关于岛上码农'],
'使用时请注明来自岛上码农、。',
);
});
}



总结


本篇介绍了在 Flutter 中快速展示授权信息的方法,通过 AboutDialog 就可以轻松搞定,各位“抄代码”的码农们,赶紧用起来向大牛们致敬吧!


作者:岛上码农
链接:https://juejin.cn/post/7246328828837871677
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

微信(群)接入ChatGPT,MJ聊天机器人Bot

前言 微信接入ChatGPT机器人还是挺有必要的,不用被墙,可以直接问它问题,还可以接入微信群等一些实用的功能。 注意:微信接入ChatGPT机器人Bot,微信账号极其容易被封,请谨慎接入 注意:微信接入ChatGPT机器人Bot,微信账号极其容易被封,...
继续阅读 »

前言


微信接入ChatGPT机器人还是挺有必要的,不用被墙,可以直接问它问题,还可以接入微信群等一些实用的功能。



注意:微信接入ChatGPT机器人Bot,微信账号极其容易被封,请谨慎接入




注意:微信接入ChatGPT机器人Bot,微信账号极其容易被封,请谨慎接入




注意:微信接入ChatGPT机器人Bot,微信账号极其容易被封,请谨慎接入



首先你需要一个 OpenAI 的账号并且创建一个可用的 api key,这里不做过多介绍,有任何问题可以加博客首页公告处微信群进行沟通。


相关的聊天机器人Bot GitHub上有非常多的项目,不仅支持接入ChatGPT,还支持接入MJ画图等一些其他功能。


本篇介绍两个项目(我用的第一个 chatgpt-on-wechat 项目):


chatgpt-on-wechat 项目最新版支持如下功能:

  • 多端部署: 有多种部署方式可选择且功能完备,目前已支持个人微信,微信公众号和企业微信应用等部署方式
  • 基础对话: 私聊及群聊的消息智能回复,支持多轮会话上下文记忆,支持 GPT-3,GPT-3.5,GPT-4模型
  • 语音识别: 可识别语音消息,通过文字或语音回复,支持 azure, baidu, google, openai等多种语音模型
  • 图片生成: 支持图片生成 和 图生图(如照片修复),可选择 Dell-E, stable diffusion, replicate模型
  • 丰富插件: 支持个性化插件扩展,已实现多角色切换、文字冒险、敏感词过滤、聊天记录总结等插件
  • Tool工具: 与操作系统和互联网交互,支持最新信息搜索、数学计算、天气和资讯查询、网页总结,基于 chatgpt-tool-hub 实现

支持 Linux、MacOS、Windows 系统(可在Linux服务器上长期运行),同时需安装 Python。



建议Python版本在 3.7.1~3.9.X 之间,推荐3.8版本,3.10及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。


注意:Docker 或 Railway 部署无需安装python环境和下载源码



Windows、Linux、Mac本地部署


本地部署请参考官方文档,按照文档一步一步操作即可。


注意要安装相对应的环境,例如 Node、Python等,这里不做过多介绍,建议大家用 Docker 方式安装,无需关心环境问题,一个命令直接部署。


环境变量

# config.json文件内容示例
{
"open_ai_api_key": "YOUR API KEY", # 填入上面创建的 OpenAI API KEY
"model": "gpt-3.5-turbo", # 模型名称。当use_azure_chatgpt为true时,其名称为Azure上model deployment名称
"proxy": "127.0.0.1:7890", # 代理客户端的ip和端口
"single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复
"single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表
"group_chat_in_one_session": ["ChatGPT测试群"], # 支持会话上下文共享的群名称
"image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀
"conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数
"speech_recognition": false, # 是否开启语音识别
"group_speech_recognition": false, # 是否开启群组语音识别
"use_azure_chatgpt": false, # 是否使用Azure ChatGPT service代替openai ChatGPT service. 当设置为true时需要设置 open_ai_api_base,如 https://xxx.openai.azure.com/
"azure_deployment_id": "", # 采用Azure ChatGPT时,模型部署名称
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", # 人格描述
# 订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复,可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。
"subscribe_msg": "感谢您的关注!\n这里是ChatGPT,可以自由对话。\n支持语音对话。\n支持图片输出,画字开头的消息将按要求创作图片。\n支持角色扮演和文字冒险等丰富插件。\n输入{trigger_prefix}#help 查看详细指令。"
}

配置说明:


1.个人聊天

  • 个人聊天中,需要以 "bot"或"@bot" 为开头的内容触发机器人,对应配置项 single_chat_prefix (如果不需要以前缀触发可以填写 "single_chat_prefix": [""])
    • 机器人回复的内容会以 "[bot] " 作为前缀, 以区分真人,对应的配置项为 single_chat_reply_prefix (如果不需要前缀可以填写 "single_chat_reply_prefix": "")

    2.群组聊天

  • 群组聊天中,群名称需配置在 group_name_white_list 中才能开启群聊自动回复。如果想对所有群聊生效,可以直接填写 "group_name_white_list": ["ALL_GROUP"]
    • 默认只要被人 @ 就会触发机器人自动回复;另外群聊天中只要检测到以 "@bot" 开头的内容,同样会自动回复(方便自己触发),这对应配置项 group_chat_prefix
    • 可选配置: group_name_keyword_white_list配置项支持模糊匹配群名称,group_chat_keyword配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by evolay)
    • group_chat_in_one_session:使群聊共享一个会话上下文,配置 ["ALL_GROUP"] 则作用于所有群聊

    3.语音识别

  • 添加 "speech_recognition": true 将开启语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,该参数仅支持私聊 (注意由于语音消息无法匹配前缀,一旦开启将对所有语音自动回复,支持语音触发画图);
    • 添加 "group_speech_recognition": true 将开启群组语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,参数仅支持群聊 (会匹配group_chat_prefix和group_chat_keyword, 支持语音触发画图);
    • 添加 "voice_reply_voice": true 将开启语音回复语音(同时作用于私聊和群聊),但是需要配置对应语音合成平台的key,由于itchat协议的限制,只能发送语音mp3文件,若使用wechaty则回复的是微信语音。

    4.其他配置

  • model: 模型名称,目前支持 gpt-3.5-turbotext-davinci-003gpt-4gpt-4-32k (其中gpt-4 api暂未完全开放,申请通过后可使用)
    • temperature,frequency_penalty,presence_penalty: Chat API接口参数,详情参考OpenAI官方文档。
    • proxy:由于目前 openai 接口国内无法访问,需配置代理客户端的地址,详情参考 #351
    • 对于图像生成,在满足个人或群组触发条件外,还需要额外的关键词前缀来触发,对应配置 image_create_prefix
    • 关于OpenAI对话及图片接口的参数配置(内容自由度、回复字数限制、图片大小等),可以参考 对话接口 和 图像接口 文档,在config.py中检查哪些参数在本项目中是可配置的。
    • conversation_max_tokens:表示能够记忆的上下文最大字数(一问一答为一组对话,如果累积的对话字数超出限制,就会优先移除最早的一组对话)
    • rate_limit_chatgptrate_limit_dalle:每分钟最高问答速率、画图速率,超速后排队按序处理。
    • clear_memory_commands: 对话内指令,主动清空前文记忆,字符串数组可自定义指令别名。
    • hot_reload: 程序退出后,暂存微信扫码状态,默认关闭。
    • character_desc 配置中保存着你对机器人说的一段话,他会记住这段话并作为他的设定,你可以为他定制任何人格 (关于会话上下文的更多内容参考该 issue)
    • subscribe_msg:订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复, 可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。

    本说明文档可能会未及时更新,当前所有可选的配置项均在该config.py中列出。


    Railway部署



    Railway 每月提供5刀和最多500小时的免费额度,目前大部分账号已无法免费部署


    1. 进入 Railway
    2. 点击 Deploy Now 按钮。
    3. 设置环境变量来重载程序运行的参数,例如open_ai_api_keycharacter_desc

    Docker方式搭建


    如果想一直跑起来这个项目,建议在自己服务器上搭建,如果在自己本地电脑上搭建,电脑关机后就用不了啦,下面演示的是在我服务器上搭建,和在本地搭建步骤是一样的。


    环境准备

    1. 域名、服务器购买
    2. 服务器环境搭建,需要系统安装docker、docker-compose
    3. docker、docker-compose安装:blog.fanjunyang.zone/archives/de…

    创建相关目录


    我自己放在服务器中 /root/docker_data/wechat_bot 文件夹下面

    mkdir -p /root/docker_data/wechat_bot
    cd /root/docker_data/wechat_bot

    创建yml文件


    /root/docker_data/wechat_bot文件夹下面新建docker-compose.yml文件如下:

    version: '2.0'
    services:
    chatgpt-on-wechat:
    image: zhayujie/chatgpt-on-wechat
    container_name: chatgpt-on-wechat
    security_opt:
    - seccomp:unconfined
    environment:
    OPEN_AI_API_KEY: 'YOUR API KEY'
    MODEL: 'gpt-3.5-turbo'
    PROXY: ''
    SINGLE_CHAT_PREFIX: '["bot", "@bot"]'
    SINGLE_CHAT_REPLY_PREFIX: '"[bot] "'
    GROUP_CHAT_PREFIX: '["@bot"]'
    GROUP_NAME_WHITE_LIST: '["ChatGPT测试群", "ChatGPT测试群2"]'
    IMAGE_CREATE_PREFIX: '["画", "看", "找"]'
    CONVERSATION_MAX_TOKENS: 1000
    SPEECH_RECOGNITION: 'False'
    CHARACTER_DESC: '你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。'
    EXPIRES_IN_SECONDS: 3600
    USE_LINKAI: 'False'
    LINKAI_API_KEY: ''
    LINKAI_APP_CODE: ''

    运行yml文件


    进入/root/docker_data/wechat_bot文件夹下面,运行命令:docker-compose up -d


    或者在任意文件夹下面,运行命令:docker-compose -f /root/docker_data/wechat_bot/docker-compose.yml up -d


    然后服务就跑起来了,运行 sudo docker ps 能查看到 NAMES 为 chatgpt-on-wechat 的容器即表示运行成功。


    使用


    运行以下命令可查看容器运行日志,微信扫描日志中的二维码登录后即可使用:

    sudo docker logs -f chatgpt-on-wechat

    插件使用:

    如果需要在docker容器中修改插件配置,可通过挂载的方式完成,将 插件配置文件 重命名为 config.json,放置于 docker-compose.yml 相同目录下,并在 docker-compose.yml 中的 chatgpt-on-wechat 部分下添加 volumes 映射:

    volumes:
    - ./config.json:/app/plugins/config.json

    作者:Junyang
    链接:https://juejin.cn/post/7259249904777855013
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    收起阅读 »

    如何不花钱也能拥有一个属于自己的在线网站、博客🤩🤩🤩

    作为一个资深的切图仔,我们难免会享有一个自己的博客,去分享一些文章啊或者一些学习笔记,那么这时候可能就需要一台服务器去把我们的项目部署到网上了,但是考虑到服务器价格昂贵,也并没有必要去花这么多钱搞这么一个东西。那么 Github pages 就为我们解决了这么...
    继续阅读 »

    作为一个资深的切图仔,我们难免会享有一个自己的博客,去分享一些文章啊或者一些学习笔记,那么这时候可能就需要一台服务器去把我们的项目部署到网上了,但是考虑到服务器价格昂贵,也并没有必要去花这么多钱搞这么一个东西。那么 Github pages 就为我们解决了这么一个烦恼,他能让我们不花钱也能拥有自己的在线网站。


    什么是 GitHub Pages?


    GitHub Pages 是 GitHub 提供的一个托管静态网站的服务。它允许用户将自己的代码仓库转化为一个在线可访问的网站,无需复杂的服务器设置或额外的托管费用。通过 GitHub Pages,我们可以轻松地创建个人网站、项目文档、博客或演示页面,并与其他开发者和用户分享自己的作品。


    使用


    要想使用这个叼功能,我们首先要再 Gayhub 上面建立一个仓库,如下图所示:




    紧接着我们使用 create-neat 来创建一个项目,执行以下命令:

    npx create-neat mmm

    跟着提示选择相对应的项目即可,选择 vue 或者 react 都可以。




    当项目创建成功之后我们进入到该目录并安装相关依赖包:

    pnpm add gh-pages --save-dev

    并在 package.json 文件中添加 homepage 字段,如下所示:

    "homepage": "http://xun082.github.io/mmm"

    其中 xun082 要替换为你自己 github 上面的用户名,如下图所示: 



    而 mmm 替换为我们刚才创建的仓库名称。


    接下来在 package.json 文件中 script 字段中添加如下属性:

      "scripts": {
    "start": "candy-script start",
    "build": "candy-script build",

    "deploy": "gh-pages -d dist"
    },

    完整配置如下所示:




    完成配置后我们将代码先提交到仓库中,如下命令所示:

    git add .

    git commit -m "first commit"

    git branch -M main

    git remote add origin https://github.com/xun082/mmm.git

    git push -u origin main

    这个时候我们的本地项目已经和远程 GayHub 仓库关联起来了,那么我们这个时候可以执行如下命令:

    pnpm run build

    首先执行该命令对我们的项目进行打包构建。打包完成之后会生成如下文件,请看下图:




    接下来我们可以使用 gh-pages 将项目发布到网上面了

    pnpm run deploy

    使用该命令进行打包并且部署到网上,这个过程可能需要一点时间,可以在工位上打开手机开把王者了。
    当在终端里出现 published 字段就说明我们部署成功了:




    这个时候,访问我们刚才在 package.json 文件中定义的 homepage 字段中的链接去访问就可以正常显示啦!




    总结


    通过该方法我们可以不用花钱,也能部署一个属于自己的网站,如果觉得不错那就赶紧用起来吧!


    作者:Moment
    链接:https://juejin.cn/post/7264183910598590519
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    收起阅读 »