Springhead

Je voudrais mourir pendant mon sommeil.

0%

指路入门教程地址

乐理差不多全还给老师了,想重新系统学习一遍。

音程关系:
C(全)D(全)E(半)F(全)G(全)A(全)B(半) C

1.大三和弦

开心组 4+3
第二个音是根音的大三度(间隔两个全音,e.g. do 和 mi)

2.小三和弦

伤心组 3+4
第二个音是根音的小三度(间隔一个全音+一个半音,e.g. re 和 fa)

3.和弦命名

1)根音 和弦类型
2)起点 音程关系

例: #Fm

#F为根音 升fa为根音;m代表第二个音是根音的小三度la;第三个音则为根音的纯五度(三个全音+一个半音)升do,即第三个音为第二个音的大三度。

也就是 升fa,la,升do

4.调式音阶

调式:一群在乐理上亲近的、好听的音
1)主音 音阶主角、从哪开始
2)音程关系 调式音阶的性质

(这个方便记忆,可以记C大调和A小调,都是白键)

关系大小调:指调号相同、音列关系相同、主音高度不同的两个调式(一个大调式、一个小调式子)。【这里没太看懂

把一个调式音阶内所有的音作为根音,搭配其他的调内音各做一个三和弦,排列组合出的七个和弦是相互兼容的。
旋律也使用相同的调式音阶来写。

和弦级数表示法:

例:A大调的五级和弦
1)A大调的构成音:A(全)B(全)C#(半)D(全)E(全)F#(全)G#(半)A
2)五级和弦对应的根音:E
3)使用E推大三和弦:E、G#、B(在A大调的调式音阶内),若使用E推小三和弦E、G、B(G不在A大调的调式音阶内)
4)所以这里是E大三和弦,即E和弦。

p.s.一个规律:大调中,一四五级用大三和弦,二三六级用小三和弦

5.复杂和弦

七和弦:在三和弦的基础上末尾用原来的方法加上一个音。根音与最高音成七度关系。
大七度:从根音到冠音中查看两音之间有几个半音,有一个半音的就是大七度。
小七度:从根音到冠音中查看两音之间有几个半音,有两个半音的就是小七度。

【这一段说实话没太看懂

大调音阶七和弦规律:

小调音阶七和弦规律:

5.和弦功能

1.I级和弦有让音乐开启或终止的功能。/IV(4)级和弦有让音乐开启的功能。
2.V级属七和弦有强烈的使音乐回到I级和弦的倾向。

五级 - 一级 = 四级 (都有过渡倾向)

流行区常见构成:I/IV+(中间部分)+V7(先5后1)

和弦进行:
1)递归流:453621
2)顺路流:15364125(卡农进行)【这个没听懂
3)6451:欧美王道
4)6451:小室进行
5)1645:doo-wop进行


七和弦的部分还是有点不太明白,过两天打算再看一遍,这个教程真的好牛。。。对小白醍醐灌顶的感觉。

隔离太痛苦了决定学点感兴趣的东西。最近在看一些关于微服务的东西。

这几天在看一个极客时间上的老课,记一点看不懂+看得懂的笔记,基本是拿来当关键词搜索用的。课的地址课程目录

1.数据库

  • 微服务会拆分数据库,每个微服务对自己的数据库的数据做增删改操作。对于有关联关系的查询操作:一是可以冗余一些数据,或者建宽表,或通过es来进行;二是服务要提供粗粒度的查询接口(批量查询),而不是循环调用rpc。

2.服务的定义、服务的发布和订阅、服务的监控、服务的治理、故障的定位

3.拆分

  • 建议的标准是按照每个开发人员负责不超过 3 个大的服务为标准,毕竟每个人的精力是有限的,所以在拆分微服务时,可以按照开发人员的总人数来决定。
  • 性能聚类,性能要求高的并发大的和性能要求低的并发小的,要分开为不同的服务,这样部署和运行都独立,好维护。

4.跨服务事务的一致性问题(分布式事务)

  • 简单的跨微服务间调用,TCC模式就可以了,如果是异步或长时间执行的调用事务,用SAGA模式。

TCC模式: TCC模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。(两阶段)

SAGA模式: 长事务解决方案。冲正。

1)Choreography(分布式的实现方式):通过事件机制实现的,每个服务都监听自己所关心的事件,每个服务执行后会发送相应的事件,监听此事件的服务执行相应的处理逻辑。
2)Orchestration(集中式的实现方式):是通过状态机来实现的整体控制,定义整体的处理流程,不同状态下需要触发的动作。


5.用户端监控(功能业务)、接口监控、资源监控(api依赖的资源,如redis)、基础监控(机器资源)。Prometheus、Dubbo、Flower。

6.服务发布和引用的那些坑

  • 在一个服务被多个服务消费者引用的情况下,由于业务经验的参差不齐,可能不同的服务消费者对服务的认知水平不一,比如某个服务可能调用超时了,最好可以重试来提供调用成功率。但可能有的服务消费者会忽视这一点,并没有在服务引用配置文件中配置接口调用超时重试的次数,因此最好是可以在服务发布的配置文件中预定义好类似超时重试次数,即使服务消费者没有在服务引用配置文件中定义,也能继承服务提供者的定义。

7.K8S的服务注册

K8S的服务注册和发现


8.监控系统实现方案

  • 日志监控推荐用ELK
  • Metrics参数监控推荐用promethus+gafana
  • 调用链监控推荐用skywalking
  • 业务监控推荐用业务开发+gafana

9.服务调用失败都有哪些处理手段

超时:以 99.9% 或者 99.99% 的调用都在多少毫秒内返回为准。

重试

双发:P90,备份请求要设置一个最大重试比例,最大重试比例可以设置成15%。

熔断:服务调用失败的次数达到一定阈值,那么断路器就会被触发,后续的服务调用就直接返回,也就不会再向服务提供者发起请求了。

  • (1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
  • (2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

10.微服务容量规划(扩容计算

  • 多加一个冗余字段,标识这是压测数据,压测结束后清理就行了,或者数据本来就有标识。

  • 数据库方面因为实现自动扩缩容的难度太大,我们目前是留有足够的冗余度,核心数据库都是4倍冗余


11.多机房部署

主从机房架构: 把所有的写请求都发给主机房,由主机房来负责写所有机房的缓存和本机房的数据库,而其他机房的数据库则通过 MySQL 的 binlog 同步机制实现数据同步。

独立机房架构: 联通和电信机房都有写请求,并通过一个叫 WMB 的消息同步组件把各自机房的写请求同步一份给对方机房,这样的话相当于每个机房都有全量的写请求。每个机房的处理机接收到写请求后更新各自机房的缓存,只有一个机房会更新数据库,其他机房的数据库通过 MySQL 的 binlog 同步机制实现数据同步。

数据一致性: 通过定时任务扫描es,通过比较不同机房相同requestid对应的处理状态,如果相同数据一致不做处理。如果不一致,进行重拾操作,达到一致。

疫情隔离在家无聊,最近看了不少杂书,因为一开始看的是茨威格的《一封陌生女人的来信》,导致一下子沉迷进了讲述各种情感的名著,一口气看了好多。我看书比较囫囵吞枣,记不住主要角色名字,所以记错剧情也很正常。而且我也不是什么感情充沛的人,大概只有一两句话,文笔超差,基本以记录为主。

《一封陌生女人的来信》:
看的时候各种说不出话,略过。茨威格一个男的为什么能这么了解女性心理?

《小王子》:
狐狸好可怜,但是玫瑰也很可怜。感觉全篇还是小孩子的那种感觉,虽然可能是以小写大,但我还是没办法严肃对待,所以看完挺平淡的。

《洛丽塔》:
全篇像是一个偏执的疯子的自言自语,就,挺震撼的,前期那种想要达成一个不可告人的目的的那种紧张感渲染得很成功。想哪天把电影找来看看,但估计挺多人看这书会感觉不适,不过对一个同人女来说这书其实尺度上可能也就那样

《喜宝》:
这本是莫名混进来的,算不上名著。我也看得挺平淡的,只能说感觉是这个作者的一贯人设剧情,要说看完有啥感触的话就是我突然又想好好学习了,为什么文学作品里的角色动不动就是哈佛牛津剑桥毕业的

《霍乱时期的爱情》:
就上海现在这个疫情读这本书倒还挺应景的,高中时读过同一个作者的《百年孤独》,说实话本来期待挺高的,但不知道为啥看完以后就挺失望的。本来读了书的标题而想象中的那种在混乱中的相守相伴居然并不存在。男女主之间的感情线甚至没有一开始乌尔比诺医生发生意外临死前的那句“只有上帝知道我有多爱你”来得震撼。

看到知乎上有人说费尔明娜一开始爱上的并不是阿里萨而是爱情本身,说得真是太对了。即便到了老年,我觉得费尔明娜也没爱上阿里萨,更多的是在失去了乌尔比诺之后抓住了唯一的救赎的那种迫切?反正看得我一愣一愣的。阿里萨也很奇怪,对费尔明娜与其说是一种爱倒更像是一种执念。反倒是莱昂娜·卡西亚尼对阿里萨的几十年帮助和陪伴更感人一些。这种清醒地知道阿里萨是什么货色但还是出于感激和爱的陪伴反而更加震撼一些。

相对于费尔明娜和阿里萨,还是更喜欢乌尔比诺和费尔明娜这种陪伴,在失去了最开始的激情之后甚至还会一起旅行来找回过去。还有长时间冷战最后以一句“让我留在这吧,的确有香皂。”结束,就很让人会心一笑。

因为这个作者一贯的文风,《霍乱时期的爱情》也是一大堆我个人认为的比较繁琐的语句构成的,导致我看得比较快,有很多细节都被跳读了,可能就导致我没有悟到这本书的精髓。反正在我看来这本书比起爱情的部分,它对于衰老的描写反而更加让人有感触,可能得等我老一点再看这本书才能有别的感受吧。

《茶花女》:
中途一度读不下去,因为觉得茶花女太可怜了,阿尔芒和玛格丽特都过于理想化了。但是两个人又真的试图去改变现状,就越看越难受。这本给我的感受比《霍乱时期的爱情》还要强烈的多,甚至买了本法语版的原装书,也不知道啥时候有水平完全读懂。

好久没看正经社论类的书了,啥时候囤一波再写吧。

在和甲方进行技术转移时遇到的一个坑,在我本地构建镜像完全没有问题,但是甲方那边各种报错。其中经历了无数的坑,但是记录一下一个最坑的。

使用相同仓库相同Dockerfile在甲方的机子上构建报了下面这个错,原本以为是甲方内部网络镜像源的问题,但是细问以后发现甲方的电脑能连外网。

1
Error parsing reference: "node:10.15.1 as build" is not a valid repository/tag: invalid reference format

迷幻的是,问了公司的后端小哥,他们后端的镜像之前在甲方机器上是正常构建的。而且甲方机器上也能正常pull这个node:10.15.1的镜像。遂开始研究起我们前端的Dockerfile到底有什么魔力能让甲方的电脑就是构建不成功。

反正最后搜来搜去发现了一个stack overflow上的提问,Dockerfile is not a valid repository/tag: invalid reference format

结论是前端的Dockerfile使用了multi-stage builds(多阶段构建),而这个特性是在docker的17.06版本引入的。而我本地的docker是20多,绰绰有余。于是赶紧问甲方机器docker版本,说实话我本来以为也就是低了一两个版本,万万没想到甲方的机器居然是1.13.1啊!!!多么古老的版本!反正最后升了一下级就可以正常build了。

都写到这了,主要是顺带学习一下这个多阶段构建,之前的Dockerfile基本都是cv之前其他项目的,只学习了里面的命令,没有注意到这个多阶段构建。感觉别人总结的挺好的就不想重新写一遍了,指路:Dockerfile的多阶段构建(multi-stage builds)。当然英语好的也可以直接看上面那个官方文档。

对于前端来说在构建时使用了node,但实际运行起来也就是一堆静态的js文件,很适合用多阶段构建来减少镜像体积。我感觉只要是有编译并且运行时不依赖编译环境的情况好像都挺适合多阶段构建的,是一个用来拯救动辄打一个g镜像的好办法。但我们后端用的python啊

虽然我也很想提起劲写点有意思的bug奇遇记,但事实是,最近几个月我真的完全提不起劲。

小区惨遭隔离,要至少坐七天牢。按理说在家办公应该挺爽的,但是真!的!好!无!聊!啊!

这几个月编程水平没什么长进倒是配流水线配得越来越熟练了。看着项目越来越多的环境,真的越看越脑子疼。尤其是一个功能所有分支都要,但是不同分支又不能直接乱合,需要一个个合到各个分支,同步起来真是恶心,因为实在太看不下去了就搞了个git flow的脚本自动提pr,但感觉好像也没方便多少,但我感觉至少我不操心同步问题了。

不过还是干了点好玩的事的,为了让自己这种死宅不会因为不动而猝死报了一个乒乓球班,虽然每次打完球喘半天。但奈何我太菜到现在还不能和人对打起来,结果被人吐槽功利心太重,但当你周围全是一些可以随意切菜的同学时,朋友,你难道不会焦虑吗。没错我就是功利心太重

对了,还认识了一个我一样脑子进水的倒霉蛋大佬,有幸被他拉进了一个游戏汉化组做苦力,群里的聊天内容让我深刻认知到自己脱宅已久,不知道steam上的补丁啥时候能出来。

其实想想让我无聊的主要原因可能还是因为最近卸载了微博。这两月的首页看得我整个人更丧了,干脆卸载为敬。

本来好不容易多出来这么多时间其实可以静下心来学自己感兴趣的东西,可惜就是静不下来,只能看看杂书解闷。脑子疼。

要形容我有多无聊呢,我甚至已经开始学习如何化妆了。

最近失眠真!的!好!严!重!啊!

为了拯救我已经逝去的心理健康,我决定找点能让自己开心起来的事情做。于是想起了一直被我遗忘在角落的leetcode账号,除了有点累,讲道理刷算法真的挺好玩的。不过也再一次深刻认知到自己到底有多菜

上上周还连着两天跑去刷了上海书城,空了一楼,剩下的几楼也空了一大半,看着挺难过的。

我童年为数不多的快乐就是逛书店了。记得小时候有段时间一有空就一个人坐车来市中心去逛外文书店,然后去每个楼层瞎逛。我那套三体实体书也是某次福州路打折买的。后来电商越来越发达,确实去福州路去的也少了。但还是经常带人一起去逛附近小巷的二手外文书店买打折原版书。

作为福州路扛把子的上海书城关门整修(这种清仓力度我真的怀疑它不会再开了),就感觉一下子少了点什么。而且重点是我最喜欢的kfc甜品站特供北海道冰激凌也不知道什么时候因为福州路甜品站关门而找不到了。

果然能让人快乐的事物都是转瞬即逝的。我到底什么时候才能成熟一点看淡这些离开啊。

不过比较好玩的是,逛书店的大家看到我带着行李箱,第一反应不是惊奇而是纷纷懊悔自己怎么也没带个箱子来装书。因为买太多还被友人吐槽你们上海买书都是论斤买的吗hhhhhhh

怎么又写了一篇文不对题的生活流水账

前阵子学了点杨波老师的k8s的入门课程,感觉讲得很清楚。最近公司内部正好在推GitOps,需要自己写一些yaml文件。干脆整理下之前学的东西。

其实看日期也能知道是10月创建的文章,硬是被我拖延到了今天才写。

下面可能包含很多本人错误的理解,容我先免个责。

以及墙裂建议看过杨波老师的入门课再去看官网文档,很多概念理解起来会快很多。指个路:
Kubernetes基本概念和应用 也就3个小时左右,两倍速更快

1.Pod

  • Pod 类似于共享名字空间和文件系统卷的一组 Docker 容器。

比较常见的是每个Pod一个容器,也会有一个Pod多个容器的情况。
当一个Pod有多个容器时,它的成员容器共享网络和存储。

一个简单的Pod资源定义文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

apiVersion: v1 #使用版本
kind: Pod #类型
metadata: #元数据
name: myweb #资源名字,在同一个namespace下必须唯一
labels:
name: myweb #标签
spec:
containers:
- name: myweb
image: kubeguide/tomcat-app: v1
ports:
- containerPort: 8080
env:
- name: MYSQL_SERVICE_HOST
value: 'mysql'
- name: MYSQL_SERVICE_PORT
value: '3306'

写不动了,哪天心情好了再继续。

2.NodePort

3.Service

4.ReplicaSet

5.Deployment

6.ClusterIP Service

7.ConfigMap

8.Ingress

9.Volume

因为好久没写了,又懒得动脑子写新东西,就打算整理一些工作中常用的git命令。
其实也是因为9月就要过去了,但我啥文章也没写过。。。


目录


1.清空所有没有加入暂存区的修改
2.清空已经加入暂存区的修改
3.改东西改到一半临时切分支
4.配置命令行代理
5.有几个分支,只想合并某个commit
6.拉取本地没有的远程分支
7.将本地某分支强推到到远程另外一个指定分支
8.本地切来切去干了一堆无用功想撤回
9.我只想回滚某个文件
10.虽然没有冲突,但我想手动合
11.git的global config不小心设置了两个相同的key,想删删不掉

1.清空所有没有加入暂存区的修改

一般可以用git checkout .作为后悔药,但是如果有新增或删除文件就需要下面这个命令了。

可以放弃所有修改、新增、删除文件

1
git checkout . && git clean -df

回到目录

2.清空已经加入暂存区的修改

如果已经不小心用了git add .把修改加入了暂存区,但是又中途不想要这个修改了,可以如下操作。
这个做法可能比较邪道,但是能用。

1
2
git add .
git stash

至于有没有强迫症,要不要删掉这个stash的内容就看自己习惯了。
回到目录

3.改东西改到一半临时切分支

经常遇到在某个功能分支上开发到一半被临时叫去主分支修bug,代码写了一半又因为各种原因不想提交,就可以用下面的命令暂存修改。

首先在当前分支输入如下命令暂存修改

1
2
git add .
git stash

然后切出去改东西,等全部操作完了再切到要把暂存内容放出来的分支,使用如下命令把暂存的东西放出来。

1
git stash pop

需要注意的是,用stash存的一个个内容可以理解成一个栈,先进后出。

也就是说,如果有多个分支都做了stash这个操作来暂存内容,那么你直接使用git stash pop放出暂存内容的时候,遵循先进后出的规则,放出的一定是你最后那次暂存的东西,而且也会自动从这个暂存栈删除这次暂存。但有的时候我们又希望跳着来。就可以用下面这个命令先查看这个暂存栈里的暂存记录。

1
2
3
4
5
git stash list

stash@{0}: WIP on branch1: ffb8e1b Merge pull request #202 from branch2
stash@{1}: WIP on branch2: fc36bd0 Merge pull request #171 from branch
stash@{2}: WIP on branch3: 404474c commit message

根据提示内容找到想要放出的暂存记录,使用如下命令放出暂存,不过使用下面这个命令是不会从暂存栈删除这条暂存记录的。

1
git stash apply stash@{1}

回到目录

4.配置命令行代理

其实这个命令自从github版本更新以后已经没什么意义了但是还是写下。

由于众所周知的原因,在国内如果是使用https的地址来git clone仓库到本地,比如这种地址https://github.com/用户名/仓库名.git,那么在使用git push时经常会遇到网络问题。
以前用windows系统的时候把代理配置手动改成全局就可以了,但是换了mac之后好像没啥用。后来才知道mac的命令行需要另外配置代理命令才能走代理push。实际的端口号需要找自己的ssr配置来看。我这个应该算是比较常见的配置。命令行回车一下再push就畅通无阻了。

1
2
export http_proxy=socks5://127.0.0.1:1086;
export https_proxy=socks5://127.0.0.1:1086;

但是这个命令自从上次github更新之后就没意义了,gayhub现在为了安全问题好像需要配置一堆东西才能使用https来push代码,因为我过于讨厌配置这件事,就直接把https改成ssh了。早知今日何必当初

下面这个是把原本的https换成ssh的命令。

1
git remote set-url origin (ssh-url)

回到目录

5.有几个分支,只想合并某个commit


实际场景中经常有基于某个分支开出的不同分支来应付不同项目的需求,有时候某个分支上更新的某个功能另外一个分支也需要,并且不希望吧这个分支上除了新功能以外的内容合过去,这个时候就可以用cherry-pick提交某个指定的commit。

1
2
3
4
git cherry-pick  要合过来的分支名 
//写分支名会合并这个分支最新的一次commit
//也可以写某个commit的hash值,如果是commit的hash值那么只会合并这个指定的commit
git log //可以用来查看当前分支的commit历史,用来查询commit的hash

如果cherry-pick遇到了冲突

1
2
3
4
// 先修改冲突
git status //查看修改
git add . //添加修改
git cherry-pick --continue //继续cherry-pick

回到目录

6.拉取本地没有的远程分支


经常有多人同时开发,然后需要拉取别人已经提交的开发好的分支进行修改。

需要先git fetch --all拉取远程所有分支。
然后使用git checkout 要拉取的分支名切到想拉的分支上,这时候本地已经是远程的那个分支了。

我怎么越写越敷衍了

7.将本地某分支强推到到远程另外一个指定分支


当各种奇怪操作导致了无可救药的问题时,就只能强推了。

一般情况下分支之间不会有太大不同,可以把本地能用的分支强推到线上的这个分支上,再用pr抢救下。但。。。架不住妖魔鬼怪的操作导致主分支完全不想要了,这个时候可以偷懒的用以下这个危险操作。

1
git push origin 本地分支名:想推送的远程分支名 -f 

回到目录

8.本地切来切去干了一堆无用功想撤回


输入git reflog查看时间顺序下的所有操作,大概长下面这样

1
2
3
1a8bebe (HEAD -> develop, origin/develop) HEAD@{0}: commit: fix: 🐛 change avatar
a09ce5a HEAD@{1}: commit: feat: 🎸 add something
f6b66b7 HEAD@{2}: commit: fix: 🎸 fix bug

找到想要回滚的时间点,比如我想回到commit: feat: 🎸 add something这个commit,对应的是HEAD@{1}

1
git reset --hard HEAD@{1}

这个时候所有的内容都会彻底回到这个commit的版本,新发生的变更将会丢失。需要注意的是,回滚的版本只能是已经提交了commit,如果啥也没提交,那reflog后啥也没有,那你也回不去了。所以记得勤提交。

这个操作配合git push origin 想回滚的分支名 -f就可以实现远程分支的回滚。

不过hard还是要慎用,虽然后悔药有用,但是你放弃的那些commit也不见了。就像脑子里进的那些水
回到目录

9.我只想回滚某个文件


这个也挺常见的,有时候改了一大堆,发现某个文件的某个版本才是对的,就只想回滚这个文件。
首先找到正确版本的文件所在的commit的hash值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git log ./README.md //想修改的文件路径

commit hash值1 (HEAD -> develop)
Merge: 12857b7 8fc2e03
Author: icey0126
Date: Tue Jun 29 16:01:41 2021 +0800

fix: 🐛 fix bug

commit hash值2
Author: icey0126
Date: Tue Jun 29 15:55:41 2021 +0800

fix: 🐛 fix bug2

找到了fix bug2这个commit的hash值。

1
git checkout hash值2 ./README.md //利用hash回滚特定文件,注意,这里为了方便操作,使用hash的前六位就可以

回滚后注意需要提交。

1
git commit -m '回滚README.md' 

回到目录

10.虽然没有冲突,但我想手动合


怎么会有这么奇怪的要求。还真有
在某个开发分支,想合并主分支时,虽然并没有冲突,但是想手动确认修改。可以配置如下命令。

1
git merge --no-commit --no-ff 要合进来的分支名

但这个命令有时候会不生效,我也不知道原因。我只是想凑个10
回到目录

11.git的global config不小心设置了两个相同的key,想删删不掉


突然想起来的一个比较常见的问题,有时候用git config --global key value设置全局配置时会不小心给同一个key设置了不同的value。

本来吧,用git config --global --unset key就可以删掉这个全局配置了,但是出现了同一个key多个value的时候就会报错不让你这么操作。

先用git config --global -l看看自己做了什么(看看全局配置了配了哪些参数)。

然后可以用正规的办法删掉这个key对应的所有value

1
git config --unset -all key

或者用不正规的办法,把这个key对应的值全部替换成我们设定的值。

1
git config --global --replace-all key "随便给个value"

由于git config的机制,只会保留一个相同的键值对,就可以通过正常的删除单个的操作来删除了。
回到目录

上周因为做了一个全文搜索的功能一直在烧脑细胞。
写接口真好玩、搜索真好玩、算关键词高亮坐标真好玩

但其实这篇不打算写正经内容。写关键词的时候再次发现自己的递归水平实在太过废物,还是同事帮忙一起看才搞定了高亮算法,不过好像被我搞出了新bug,手动doge。想找个时间集中刷一刷递归的题,为什么都快一年了我还是这么菜,悲伤。

虽然前两周搬到了公司40min路程的新家,但由于最近心情巨差导致几乎一直在生病都没时间好好学新东西,悲伤*2。

eve手游真是和它的端游一脉相承,至今没有找到它的正确玩法,不愧是游戏自由度标杆之一,玩不明白。

好在最近还有一两部喜欢的番出第二季了可以收获短暂的快乐。

前两天还和一个朋友在人广附近的几个公园暴走了2.5w步,这么热的天我们真是太强了。运动原来真的能使人快乐,虽然只有短暂的几小时作用,悲伤*3。

真是写了一篇巨无聊的自言自语流水账。

所以石之海为什么要明年一月才出!好吧,出了等看到承太郎结局我就要悲伤*4了。

前阵子因为需求改动,把之前写过的可配置抽奖转盘的抽奖判断逻辑重写了一遍,还解决了一个兼容性问题,简单记录下。

目录
一、setTimeout制作动画在Safari中掉帧
二、用状态机(伪)管理不同抽奖逻辑


一、setTimeout制作动画在Safari中掉帧

代码的实现逻辑是使用canvas根据奖品数量动态绘制转盘角度生成静止的一帧,然后使用定时器修改绘制的起始角度,每隔一段时间重绘一张转盘实现动画效果。

问题出在最开始是使用setTimeout实现这个定时绘制的功能,但是转动效果在苹果手机上非常糟糕,掉帧极其严重。在微信开发者工具上也复现不出问题,就猜测是Safari的兼容性问题,发现setTimeout和setInterval在Safari上的兼容性不大好。在同事的提示下换成了requestAnimationFrame,动画就流畅了很多。

至于原因懒得再总结了,我觉得这个人讲的很清楚。requestAnimationFram和setTimeout执行的先后

  • requestAnimationFrame 执行步伐跟着系统的绘制频率走,就是说屏幕分辨率 和 屏幕尺寸会影响requestAnimationFrame的回调函数执行时间。
  • setTimeout 执行只是在内存中通过设置一个间隔时间来运行代码,HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。同时setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,所以 setTimeout 的实际执行时机一般要比其设定的时间晚一些。

两者执行的快慢影响因素:

  • requestAnimationFrame受系统的绘制频率影响,即屏幕分辨率 和 屏幕尺寸
  • setTimeout 受任务队列和页面渲染有关

比较麻烦的点在于动画逻辑需要修改,大概修改如下。

使用setTimeout的大概写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const interval = 10;
const result = "一等奖";
for (let i = 1; i < condition; i += 1) {
const timeId = setTimeout(() => {

// 一些需要循环的操作
setStart(interval * i);

// 满足停止条件时结束,没错这动画我居然包了层promise写
if (i === condition) {
resolve(result);
clearTimeout(timeId);
}

}, 100);
}

使用requestAnimationFrame的大概写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const interval = 10;
const result = "一等奖";

let i = 1;
// 回调函数
function animloop() {
i += 1;
// 一些需要循环的操作
setStart(interval * i);

// 在没有满足条件前递归
if (i < condition) {
window.requestAnimationFrame(animloop);
}

// 满足停止条件时结束
if (i === condition) {
resolve(result);
}
}

// 第一帧渲染
window.requestAnimationFrame(animloop);

二、用状态机(伪)管理不同抽奖逻辑

因为这是个可配置的抽奖活动,导致灵活度非常高。点击抽奖按钮之后需要做的条件判断非常之多,而且因为可配置的原因也非常复杂.再加上需求的变化,原本分散在不同组件中的判断逻辑需要整合到一个按钮上,于是就干脆重写了判断逻辑。

比如点击抽奖按钮之后,需要查看用户的抽奖次数、信息填写情况、配置填写用户信息的时机、活动时间等等等。

原先的做法是在不同组件内进行判断,并将不同判断结果发到一个中心化的数据中心管理,再在不同需要判断的地方调用这些结果进行一层一层判断。这样写的麻烦之处在于有很多条件的判断是耦合的,不是很好拆,而且如果需求一改动,在耦合的判断逻辑下(而且还是不同组件),改起来很恶心。虽然主要原因还是我原先代码写的太烂了。

后来想起来隔壁后端同事之前在搞可配置的发布流程管理,这一层层的条件逻辑判断以及状态改变达到不同页面(对抽奖而言就是不同提示),似乎有点眼熟。记得之前听到测试妹子说是用有限状态机写的,于是走投无路(并没有)的我研究了起状态机。

鉴于我很懒,只摘录了我觉得比较有用的概念。

  • 第一个是 State ,状态。一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。
  • 第二个是 Event ,事件。事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。
  • 第三个是 Action ,动作。事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action 一般就对应一个函数。
  • 第四个是 Transition ,变换。也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。

比如抽奖流程:点击开始抽奖=>验证身份=>验证活动时间=>验证抽奖剩余次数=>抽奖

上面这个是我随便写的,大概意思是一个抽奖行为是有流程的,虽然只是一个点击按钮其实是有非常多状态的改变的。然后在不同状态下会有不同需要触发的行为,并且它还要改变到下一个状态。

当使用这套逻辑时,整个流程的判断就可以都放到一起,代码会好理解很多,改起来也没那么恶心了。

为啥说伪呢,因为下面这个是一个简陋实现版乞丐版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const [lotteryState, setLotteryState] = useState<string | undefined>();

const getState = async () => {
switch (lotteryState) {
case 'start':
if (条件1) {
setLotteryState('状态A');
} else {
store.reducers.doActionEnd;
setLotteryState('end');
}
break;
case '状态A':
if (条件2) {
store.reducers.doAction111;
setLotteryState('状态B');
} else {
store.reducers.doAction222;
setLotteryState('状态C');
}
break;
case '状态B':
if (条件3) {
store.reducers.doAction444;
setLotteryState('状态C');
} else {
store.reducers.doActionEnd;
setLotteryState('end');
}
break;
case '状态C':
if (条件4) {
store.reducers.doAction555;
setLotteryState('end');
} else {
store.reducers.doActionEnd;
setLotteryState('end');
}
break;
case 'end':
break;
default:
break;
}
}

const lottery = async () => {
setLotteryState('start');
};

return (
<img onClick={lottery}/>
);