根据Gartner的说法,aPaaS是应用程序平台即服务的缩写,它是一种云服务,可为应用程序服务提供开发和部署环境。aPaaS平台提供了以下功能:迭代构建应用程序、即时提供应用软件、按需扩展应用程序以及集成应用程序与其他服务。
在前云时代,“中间件”一词用于描述在分布式应用程序中的支持数据通信和管理的软件。基础架构和应用程序首先以IaaS(基础架构即服务)和SaaS(软件即服务)的形式在云计算服务中,如阿里云和钉钉。后来,中间件服务也可以在云中使用,但是中间件包含了很多东西,例如应用程序开发,应用程序部署,集成服务,标识服务和其他平台服务。所有这些服务都集中在PaaS(平台即服务)下,如Azure。在这种情况下,那些主要提供应用程序开发和部署的服务就被称为aPaaS(例如白码无代码开发平台),而PaaS则是将整个中间件视为服务产品范围。

像快速应用程序平台这样的应用程序平台为您提供了迭代和快速更改所需要的所有工具,并为您的项目采用正确技术。它们具有非常直观的拖放功能特性,可随时用于开发应用程序。
使用aPaaS可以大大减少开发和部署时间。它们能够从旧系统轻松集成新的应用程序功能,这使得更新和改进可以变得很容易。
许多aPaaS平台都提供水平和垂直的可伸缩性。您可以轻松地升级和添加数据库,并在应用程序中部署新内容。
提供额外的软件服务,例如操作系统、数据库、安全性和漏洞管理、API和集成基础结构以及在云上的许多部署选项,这些都有强大的安全支持。
一些aPaaS平台简化了应用程序的供应和安装,但它们并不能真正使组织以业务速度交付应用程序。一个好的aPaaS平台应为整个应用程序生命周期带来速度和敏捷性,为无摩擦的企业应用程序交付铺平了道路。您可以根据自己的需要选择具有相应功能的aPaaS平台。
拖放式UI创建可加快开发和变更管理
连接遗留系统中独立的数据库,API,Web服务和数据
创建响应式Web应用程序和混合移动应用程序,以便在所有设备和外形上都可以使用该应用程序
能够自定义所有方面的功能,包括HTML,CSS,JavaScript和后端服务
一键式部署和选项,可在本地以及私有和公共云中运行
能够提供身份验证程序集成并控制不同级别的授权

aPaaS是软件开发的未来。Adrian Bridgwater表示:“您可以使用aPaaS进行最初设计时最初没有想到的其他事情”。有了aPaaS,完整的Web应用程序生命周期就可以得到现有支持:构建,测试,部署,管理和更新。
由于底层基础结构已经编写、测试和优化,该平台允许进行低代码开发,而无需开发人员或熟练的IT人员来开发应用程序,因此企业内任何人具有相关的开发知识就可以完成此工作。
]]>GIT基本上是目前最为先进的分布式版本控制系统,通过GIT能够非常方便的管理文件多个版本,能够实现版本的回滚,比对等功能,并且支持分布式也就是多人协同工作。
GIT也是目前使用作为广泛的版本控制软件,大名鼎鼎的Github网站能直接与GIT对接,使用GIT上传代码到Github之中。
通常来说,Linux系统使用各自版本对应的包管理工具可以非常方便的安装GIT。例如sudo apt-get install git,但安装之后会有一些设置需要配置。
安装GIT之后比较常见的一个问题,就是中文乱码,可以通过在命令行中设置解决:
git config --global core.quotepath false由于GIT支持多用户协作,所以在使用GIT之前,还需要配置身份信息。首先需要在GIT中配置你的用户名和邮箱地址:
git config --global user.name "jason" // 名字git config --global user.email "jason@xxx.com" // 邮箱在使用GIT之前,得掌握一个仓库的概念,也就是repository。这个repository也就是一个目录,是GIT管理的单位,在一个repository中,所有文件的新建、修改、删除都会被GIT跟踪到,并加以管理,以便在以后进行还原等操作。
所以,使用GIT,首先要创建一个repository。
创建一个仓库,主要可以有两种方式。
init的创建方式为从零开始创建一个仓库,首先需要有一个目录,使用cd进入到我们想要创建仓库的目录中,然后使用以下命令:
git init即可将当前目录转化为一个repository。目录中会出现一个.git目录,里面保存着所有的版本信息。
除了自己从零开始创建仓库外,还可以使用别人的远程仓库来创建,例如Github上有许多项目代码,都可以使用这种方式拷贝下来。
git clone git://git.kernel.org/pub/scm/.../linux.git这样的话会在当前目录生成一个一模一样的仓库。
创建好仓库之后,就可以在仓库之中开始使用命令来控制此仓库文件的版本了。
在使用这些命令之前,还有几个GIT的基础概念需要掌握,分别是:工作区(working directory),暂存区(stage),分支,版本库。
工作区指的其实就是我们平常我们修改文件,查看文件的地方。暂存区则是类似于一个中转的区域,被叫做stage或者index。在工作区中修改了内容,那么首先需要先将修改提交到暂存区中,积累一定的修改数量,汇集成一个版本之后,再一起提交到具体的分支中。GIT管理的项目中,有分支这个说法,可以理解为具体的开发方向,GIT仓库在初始化的时候会默认创建一个master分支,你的文件版本就实际保存在这些分支之中。暂存区和分支合起来称为整个版本库。在仓库中使用git status命令可以查看当前仓库的状态:
[root@acmestudio jason]# git status# On branch master# Changes not staged for commit:# (use "git add <file>..." to update what will be committed)# (use "git checkout -- <file>..." to discard changes in working directory)## modified: jason/settings.py## Untracked files:# (use "git add <file>..." to include in what will be committed)## setup.pyno changes added to commit (use "git add" and/or "git commit -a")其中主要列出了仓库当前所处的分支,被修改了的文件,以及没有被跟踪的文件。
参数:
-s - 以短格式显示仓库状态。在GIT仓库之中,虽然我们说所有的文件都可以被跟踪,但是这只限于文本文件的修改,GIT无法跟踪二进制文件的修改。
同时,在跟踪之前也需要先将文件添加到仓库的索引中,也就是说,使用add命令添加到索引中的文件,才会被GIT跟踪。在每次你新建或者修改了文件之后,需要你使用add命令将这个文件先添加到暂存区之中。
git add filename运行了此命令之后,未跟踪文件将会从Untracked files:中转移到Changes not staged for commit:中。
有些时候,可能修改的文件比较多,一个个去用add命令去添加比较麻烦,所以也可以用*来匹配文件名,以下命令可以将所有未被跟踪的文件添加到暂存区中:
git add *在将新文件或者修改过后的文件添加到暂存区之后,就可以使用commit命令将其正式提交到仓库了。但是要注意的是,commit提交到仓库的文件状态,是最后一次执行add时文件的状态,而不是执行commit时文件的状态。
所以,在提交文件之前,最好都先使用git status检查一下,有没有需要添加的文件还没有用add添加到暂存区中。然后就可以运行命令了:
git commit直接运行此命令后,会跳出一个编辑界面,一般默认是使用vim。如下所示:
# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.## On branch master# Your branch is up to date with 'origin/master'.## Changes to be committed:# modified: "hello.md"这里其实是需要你输入一些关于此次commit的一些信息,对此次代码提交做一定的标识,方便以后如果需要还原版本的时候清楚代码的改动。对此信息保存退出后,则commit提交成功。
参数:
-a - 虽然说可以使用add命令对commit提交的暂存区做很精细的改动,但是当提交的文件非常多的时候,则add起来会比较的麻烦。所以commit提供了-a参数,使用此参数,则会自动将已被追踪的修改过的文件添加到缓存区中,不用再手动add添加了。
-m - commit提交的时候需要输入信息,有时候如果希望输入的信息比较少,则可以使用-m参数直接在命令行输入。如下所示:
git commit -m 'message'如果需要移除仓库中已经被追踪的文件,那么最好使用GIT提供的rm命令来删除,会更加安全:
git rm filename此删除命令会将磁盘上的文件一并删除,在commit后,此文件将不会再被追踪。
参数:
-f - 如果删除的文件已经被修改过,或者已经被添加到暂存区中,那么则需要用-f参数强制删除。这是一个保护措施,因为还未被提交的修改不会被保存下来,是无法恢复的。
--cached - 如果希望某个仓库中的文件不再被GIT跟踪,但是依然被保存在磁盘里,这种时候可以使用--cached来删除。在错误的添加了文件到仓库中后,这个参数非常有用。
git rm --cached filename使用GIT最大的一个好处是,GIT会将你提交的每个commit保存下来,以供你以后在出现问题后,能够非常方便的回滚版本。回滚版本的其中一个命令就是reset。
在你将一些文件使用add命令添加到暂存区之后,使用git status命令查看状态时可以看到提示,如果想将添加到暂存区的文件取消暂存则可以使用以下命令:
git reset HEAD <file>...在这里,HEAD代表的是最近一次的commit,此命令的意思则是将指定文件回滚到最近一次commit提交的状态。
如果没有指定文件的话,那么将会回滚整个仓库的状态,如下:
git reset HEAD版本表示:
回滚的时候可以指定回滚的版本,版本的表示方式有三种,默认情况下都是指向最近一次提交:
HEAD - 最近一个提交HEAD^ - 上一次提交(倒数第二次提交)HEAD^ ^ - 倒数第三次提交HEAD^^^ - 倒数第四次的提交HEAD~0 - 最近一个提交HEAD~1 - 上一次提交(倒数第二次提交)HEAD~2 - 倒数第三次提交HEAD~3 - 倒数第四次的提交SHA1版本号。参数:
--mixed - GIT的默认模式,使用此模式的时候,会清空暂存区,将回滚的内容全部恢复成未暂存的状态。也就是说不会修改任何本地工作区文件,只会回滚index和清空暂存区。--soft - 使用此模式,同样不会修改任何本地工作区文件,与--mixed的区别主要在于,其会将回滚的内容放入暂存区中。--hard - 此模式是一个比较危险的命令,使用此模式,将会把仓库彻底还原到commit的状态。如果你的暂存区和工作区中有修改了但未提交的内容,将会彻底丢失,所以谨慎使用此模式。git diff命令可以查看两次文件内容有什么不同。使用以下命令可以查看工作区和版本库中最新版本的区别。
git diff HEAD -- <filename>在这里--表示的是工作区,HEAD表示的是最近一次commit提交的版本,还可以用--cached代表暂存区。
在没有指定的情况下,是默认查看工作区和暂存区的区别:
git diff <filename>使用git log命令,将会用以下的格式输出提交的commit日志记录,如果记录较多的话,需要按q键退出查看。
$ git logcommit 69b8e6b3ebff7b84d6190a374475a20482d4c3ba (HEAD -> master, origin/master, origin/HEAD)Author: jonson <jonson@xxx.com>Date: Thu Nov 21 17:16:53 2018 +0800 add git branch partcommit 28056c5055ef9ed4156b74713c0205e8fde44713Author: jonson <jonson@xxx.com>Date: Thu Nov 21 15:21:30 2018 +0800 complete basic git commandcommit 6f6aae904ad7551d49ab952e9e3afae70bc93c50Author: jonson <jonson@xxx.com>Date: Thu Nov 21 17:19:46 2018 +0800 add git参数:
--oneline - 每条commit日志只显示一行内容:
$ git log --oneline69b8e6b (HEAD -> master, origin/master, origin/HEAD) add git branch part28056c5 complete basic git command6f6aae9 add git--skip - 指定跳过前面几条日志:
$ git log --skip=4 --onelineb9922fc add gitedd4594 change the python file namea9cded2 add git article-[length] - 指定输出的日志数量
$ git log --oneline -269b8e6b (HEAD -> master, origin/master, origin/HEAD) add git branch part28056c5 complete basic git command--pretty= - 使用其他格式显示提交信息,可选项有:oneline、short、medium、full、fuller、email、raw,默认为medium。
--graph - 在左侧以图形的方式显示提交的commit变动,更清晰的展示分支的合并等信息。
--decorate - 展示更多的信息,例如HEAD、分支名、tag。
--author - 通过提交者的名字来搜索提交信息。
--grep - 从提交的关键字搜索提交信息。
-p - 通过路径搜索提交信息
git log -p -- config/my.config在GIT中还有一个非常方便的功能,就是打标签,可以给某个特定的commit进行标记。比较广泛的一个方式使用它来标记版本号。使用以下命令将会给当前分支最新的一个commit打上tag。
git tag <tagname>如果你需要指定给某个commit打tag的话,则需要你在命令后面加上commit的id。
使用以下命令可以查看tag的信息:
git tag # 查看本地所有taggit show <tagname> # 查看指定tag的详细信息git ls-remote --tags <remotename> 查看远程所有tag需要注意的是,我们创建的tag都是只存在于本地的,所以如果要把tag同步到远程仓库的话,需要额外单独的使用命令同步tag。
git push <remotename> <tagName> # 推送单个tag到远程仓库git push <remotename> --tags # 推送所有未推送的tag到远程仓库参数:
a - 指定tag的名字。-m - 给tag添加上备注的信息,与commit的信息类似。-d - 这个参数代表删除tag。需要注意的是如果要删除远程的tag,则需要本地删除后,再push到远程仓库。在GIT之中,有分支的概念。在这里举一个例子,你希望在你的工作项目上新增添一个功能,那么你就可以在当前项目的基础上新开一个分支,然后在这个专门的分支上开发的你新功能,而原来的工作项目不受任何影响。等到你的新功能开发完毕通过测试后,就可以将这个分支与之前的工作项目分支合并了。
这种开发方式,能够将工作从开发主线上分离开来,避免工作时影响到工作主线。
由于GIT的分支实现原理跟指针类似,所以创建切换合并分支都是非常迅速的。GIT也非常鼓励新建一个分支去完成任务,任务完成后和主分支合并,然后删除掉这个新分支,这样使用下来与直接在主分支工作是差不多的,但是安全性要高不少。
首先,直接使用git branch命令是查看当前仓库的分支:
git branch如果在git branch命令后面跟上一个名字,则可以在当前仓库新建一个分支:
git branch working也可以使用当前分支的某历史版本创建分支,这样的话需要指定具体的commit的ID:
git branch working 169d2dc需要注意的是,仓库一般默认会有一个master分支,这个分支其实并没有什么特殊,跟其他新建的分支没有什么区别,只是在git init时默认会创建这样一个分支,大部分人也懒得去修改。
参数:
-d - 如果在创建之后需要删除一个分支,可以加上此参数。
git branch -d working在创建了分支之后,我们所处的依然是之前的分支,要切换到新的分支的话,依然是需要我们手动切换的。
git checkout working参数:
-b - 加上这个参数之后,则代表直接创建一个分支,并且切换到这个分支,也就是说可以省略掉git branch这个步骤。在创建了分支之后,大部分情况下最终都是要合并的,也就是将分支修改的内容和另一个分支的修改内容合并到一起。
使用git merge命令将可以把某一分支与当前分支合并到一起:
git merge working如果两个分支之间没有冲突的话,那么分支的合并将会非常简单,GIT会自行决定如何合并两个分支。但是如果两个分支之间有文件冲突的话,也就是说两个分支内都对同一个文件进行了修改这种类似的操作,GIT将无法决定保留哪一个分支的内容。
因为在逻辑层面上,也需要由你自己来决定,在冲突的情况下,保留哪个分支的内容。在这种情况下,合并的时候会显示类似以下的内容:
CONFLICT (content): Merge conflict in a.txtAutomatic merge failed; fix conflicts and then commit the result.在冲突的文件内,GIT会将两个分支的内容都放在了一起,由你自行修改:
<<<<<<< HEADi am master=======hello, i am working>>>>>>> working可以看到====分割上方的是当前分支的内容,下方是合并的working分支的内容。此时由你自行修改,处理完冲突之后,add添加好就可以提交了。
在前面讲的用法基本上都是本地的GIT用法,但是使用GIT很大的一个优势是可以多人协作,同时完成项目,那么这基本必然要涉及到远程仓库的使用。远程仓库可以自己在服务器上搭建,也可以使用一些其他人提供的仓库托管服务,例如Github这个全球最大的同性交友网站。
使用init命令生成的仓库中,是没有配置远程仓库的,需要自行配置。而如果是使用clone获取的仓库,则会将来源的远程仓库默认配置为一个名为origin的远程仓库,这个远程仓库没有什么特殊,只是默认起名而已。在一些比较复杂的多人合作项目中,会配置有多个远程仓库。
使用git remote命令即可查看当前仓库有配置哪些远程仓库:
$ git remoteorigin如果你需要添加新的远程仓库,那么可以使用以下命令:
git remote add <shortname> <url> <shortname>是你给这个远程分支起的名字,这个名字只会在本地起作用。
以下还有一些显示与删除等命令:
git remote show [remote-name] # 显示远程仓库详细的信息git remote rename old_name new_name # 重命名远程仓库git remote rm remote_name # 删除远程仓库参数:
-v - 会显示远程仓库的url。在配置了远程仓库之后,就可以从远程仓库拉取内容了。这个命令会访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。
git fetch [remote-name]如果需要只拉取某个分支的内容,需要在后面加上分支的名称。
git fetch origin master # 拉取远程仓库origin中的master分支git fetch origin master:temp # 拉取远程仓库origin中的master分支,并命名为temp分支需要注意的是,fetch这个命令只是将版本库中的内容拉取下来,并不会自动合并和修改你工作区中的内容,需要你自行手动合并。
之后需要合并拉取的内容到工作区的话,需要使用git merge命令。
git merge FETCH_HEAD这里的FETCH_HEAD是一个版本链接,记录在本地的一个文件中,指向着目前已经从远程仓库取下来的分支的末端版本。
一般来说一个比较常见且安全的使用方式如下:
git fetch origin master:tmp # 在本地新建一个temp分支,并将远程origin仓库的master分支代码下载到本地temp分支git diff tmp # 来比较本地代码与刚刚从远程下载下来的代码的区别git merge tmp # 合并temp分支到本地的master分支git branch -d temp # 如果不想保留temp分支 可以用这步删除如果觉得使用fetch命令比较麻烦,且确定远程仓库的内容可以安全合并的话,那么可以使用pull命令。pull命令其实是一个混合命令,相当于把git fetch和git merge这两个命令合并到了一起,一个命令直接解决问题。
git pull origin在多人协作完成项目时,本地工作完成后,需要推送到远程仓库中,这个时候需要使用git push命令来进行推送。这个命令的用法如下所示:
git push <远程主机名> <本地分支名>:<远程分支名>如果当前分支只有一个远程分支,那么主机名与分支名都可以省略:
git push如果当前分支与远程分支存在追踪关系,则可以省略分支名,只留主机名,例如:
git push origin如果只省略远程分支名,则表示将分支退送到与之存在追踪关系的分支,如果远程分支不存在,则创建新的远程分支:
git push origin master如果只省略本地分支名,则代表删除指定远程分支:
git push origin :master在使用GIT管理项目时,我们项目里常常会有一些文件是不需要纳入版本管理的,例如Mac系统的.DS_Store之类的默认文件,又比如Python的运行生成的__pycache__目录。
如果这种文件较多时,我们添加文件将变得比较的麻烦,所以GIT给我们提供了一个方式,可以忽略掉指定的文件。
首先是配置全局忽略,GIT管理的仓库都能起效。配置的方式有几种,我们这里主要使用.gitignore文件来进行配置。
首先我们需要创建一个.gitignore文件,这个文件放在哪里都可以,但推荐的位置是直接放到家目录中:
touch .gitignore然后,我们需要将此文件配置到GIT中(如果有修改文件路径的话,这里需要相应的修改):
git config --global core.excludesfile ~/.gitignore最后,我们再来编辑.gitignore文件,将需要忽略的文件写到这个文件中即可,文件内格式如下所示:
.DS_Store__pycache__*.pyc.vscode如果需要按照具体的项目来配置特定的忽略文件的话,那么可以配置一个.gitignore文件直接放到仓库的根目录即可。
作者:王南北丶
链接:https://www.jianshu.com/p/0e9d07ec76f9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1、闭环思维
闭环的本质是你要给人一种确定性,交代给别人或别人交代给自己的事,肯定会有一个确定的结果。
它体现在行为上,就是 “凡事有交代,件件有着落,事事有回音。”共可分为三个层次:
① 沟通闭环:主动反馈过程,积极反馈结果。
② 能力闭环:能力与承诺匹配,既不盲目承诺,也不碰到挑战就躲。
③ 结果闭环:抱着对结果负责的态度,做到自己的最大极限。
这三种层次闭环的要求是逐层递进的。
你要做到能力闭环,就必然会做到沟通闭环,因为你只有不断地主动反馈,才会让能力更好地匹配承诺。你要做到结果闭环,就必然要做到能力闭环、沟通闭环,否则你就没办法对结果负责。
2、风险储备思维
风险储备是富人相较于穷人,大公司相较于小公司的巨大优势。
对穷人或小微企业而言,风险储备是一种奢侈,是一种可望而不可即的宝贵资源,因此他们不得不在风险储备不足的情况下,以九死一生的做法去博很低很低的那么一点成功概率。
2019年诺贝尔经济学奖的三位得主阿比吉特·巴纳吉、埃丝特·迪弗洛和迈克尔·克雷默,他们在《贫穷的本质:你为什么摆脱不了贫穷》一书中研究出的贫穷本质之一就是:
穷人因为没有任何风险储备,不得不采取侥幸的心态与短视的行为,同时又因无法承担任何小错误所导致的后果,从而陷入贫穷陷阱里难以跳出。
他们看到的穷人走出贫穷陷阱的唯一方式就是,一代人甚至两代人牺牲自己作为下一代的风险储备,但这种方式对付出的一代人而言又是多么地残忍呢?
因此一旦你有了做风险储备的条件,请万分珍惜,认真做好风险储备。
我从2014年底创业以来,最重要的经营原则之一就是确保公司的现金流在零收入的情况下,至少能维持6~12个月的支出,一旦低于这个警戒线,我就会千方百计地提前去筹钱了。
这也是你这些年来历经几次大的业务转型和今年的特殊情况,公司经营依然能够有条不紊的关键原因之一。
3、 有针对性地提高惯性思维的质量
无论你对惯性思维的印象有多么负面,比如思维局限啦、缺乏创新啦、顽固不化啦,你都无法摆脱它。
因为你的生理构造已经决定了,你永远也无法摆脱惯性思维。
虽然大脑大约只占你人体质量的2%。
但即使坐着不动,它也会消耗人体热量的20%,全身耗氧量的25%,以及消耗掉肝脏储存血糖的75%。
哪怕在发呆,你的大脑每分钟也需要0.1卡路里的热量,而当你集中精力进行思考的时候,大脑每分钟消耗的能量则是1.5卡路里。
而你每一次反惯性思维的思考,都需要大脑消耗更多的能量。
一旦消耗过多,就会出现心理学上的“自我损耗(ego depletion)”现象,你的自我控制力会大大下降,从而更依赖惯性思考。

所以不要老想着去摆脱惯性思维了,而是转换思路:想办法提高惯性思考的质量。
就像大禹治水一样,不要总想着堵——堵狠了反而洪水更泛滥了——而是要顺着河道疏导,一疏导水反而就听话了。
那如何疏导大脑,让惯性思考的质量更高呢?
方法就是让更高质量的“框架”成为你的思考惯性。
举个例子:
同样接到一个推广书籍的任务,一般人惯性思考想到的可能是去微博推广、找公众号推送、在社群里发通知等等。
而我惯性思考想到的是,这次推广要达成多少本书的销售?要在多长时间内实现? 现有的公众号和社群在这段时间内能满足多少?是不是还有差距?如果有的话用哪些渠道补充最合适?
我的这个惯性思考框架就比上面的那个框架质量高一些。
4、元认知思维
元认知水平决定了你能否有效地评估自我水平,以及监控自己的思维和学习过程。
有什么高效方法,可以提高自我的元认知水平呢?
三个行之有效的方法。
▼ 1、构建自己的能力体系
要提升对自我的认知水平,一个很好的方式就是构建出自己的能力体系。
这样你就能更清楚地知道,自己当前的能力、技能、知识是怎样的,在不同的领域分别有哪些差距。

▼ 2、有意识地应用策略来学习
不同的学习策略在不同的情境下,学习效果会有天壤之别。
重复策略——比如反复背诵——在学习一门语言上有着独到的优势。
但在学习哲学时就不那么有效了,这时精加工策略的效果就远远好于重复策略了。
因此,你在学习时,如果能有意识地识别出自己正在应用的学习策略,并能根据不同情境有意识地调整不同的学习策略的话。
那么你的学习效率和学习效果都将大大提升。
▼ 3、显性化监控、评测和调节过程
无论你对自身水平做多少评测,也不管你的学习策略多么有效,在真正学习一个技能或知识时,你依然会发生偏差。
因为在目前的科技条件下,你依然没有能力完全认清这个世界的本质规律,以及认清你自己。
因此,在学习中你要刻意监控自己的学习过程,评测自己的学习效果,以及针对学习偏差进行调节。
这就是学习的元认知过程。
这个过程理解起来很容易,偶尔做一次也不难,但难就难在如何坚持持续去做。
因为绝大多数时候,你压根就意识不到你没有在监控学习过程。
等你发现学习效果不佳的时候,学习时间和资源已经全部被消耗完了,甚至整个人生都消耗掉2/3了。
因此,你需要养成一个学习的自我监控、评测、调节的习惯才行。
5、从三个层次提升换位思考能力
如何做到换位思考?
这事说起来容易,想做到却很难。
不过,我有一个有效的方法,可以从三个层次提升你的换位思考能力:
第一层:知不知
很多时候,你并不是不知道换位思考的重要性,而是意识不到自己其实没在换位思考。
有两个行之有效的方法,可以帮助你提醒自己:
1)在醒目的地方做提醒标识
比如,在座位的隔板上、电脑桌面上或者手机的日历提醒里记上“别忘了换位思考哦”, 经常提醒自己
2)每日做换位思考日复盘
比如,要求自己每天至少做到3次换位思考,并每天晚上将这些换位思考的场景用文字再复 述一遍。
第二层:能不能
意识到了换位思考,不代表你就能做好换位思考。
要真正成为换位思考的高手,你得从两方面入手:
① 想人所想
处理人际关系时,不要光看对方行为的表象,要多思考行为背后的原因或目的。
比如,对配合的事一拖再拖的同事,可能并不是没有协作意识,而是因为领导给他布置了 优先级更高的任务,他没有时间来处理你的事。
② 感同身受
分析出对方行为背后的原因后,不代表你就能感同身受,因为你每个人的成长环境、性格特征、人生阅历都各有不同。
所以你要丰富自己的阅历,深入了解各种不同类型的人,才能真正做到感同身受。
第三层:愿不愿
绝大多数时候,换位思考的本质还是为了自己。
站在别人的角度考虑问题、设身处地地理解他人,其最终目的还是为了自己能更好地与他人交往,或影响他人。
所以当这种换位思考的行为,和你的利益没有冲突,甚至有利时,你是会很乐意去做的。
但一旦发生利益冲突后,你就不得不面临愿不愿意让利的问题了。
比如,努力工作5年之后,你终于等到了一个升职机会,但跟你竞争的一位同事家里很困难,更需要这个机会。
你能做到让利成全别人吗?
……
如果你能做到的话,你就达到换位思考的第三层水平,也就是“圣人”层次了。
]]>FROM jenkins/jenkins:lts# Switch to root to install .NET Core SDKUSER root# Show distro information!RUN uname -a && cat /etc/*release# Based on instructiions at https://www.microsoft.com/net/download/linux-package-manager/debian9/sdk-current# Install dependency for .NET Core 6RUN apt-get updateRUN apt-get install -y curl libunwind8 gettext apt-transport-https# Based on instructions at https://www.microsoft.com/net/download/linux-package-manager/debian9/sdk-current# Install microsoft.qpgRUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpgRUN mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpgRUN sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list'# Install the .NET Core frameworkRUN apt-get updateRUN apt-get install -y dotnet-sdk-6.0RUN apt-get install -y nodejs# Install Flubu ToolsRUN dotnet tool install --global FlubuCore.Tool# Install Docker CLiENV DOCKERVERSION=20.10.9RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKERVERSION}.tgz \ && tar xzvf docker-${DOCKERVERSION}.tgz --strip 1 \ -C /usr/local/bin docker/docker \ && rm docker-${DOCKERVERSION}.tgz]]>管理归纳总结起来就是三板斧,拿结果、建团队、招聘与解聘,只要做好这三件事,我们的团队管理工作一定不要差到哪里去,以我的经验,相当一部分的团队管理者其实做的比较差,根本就不懂管理,也不去学如何管理,搞的团队乌烟瘴气,一团糟。
现在的企业中一定是结果导向的,你的结果做的不好,团队的成长和绩效也不会好到哪里,所以我们的第一板斧就是拿结果,要问团队要结果。
那怎么拿结果呢?大家可以看下我下面的思维导图

下面的的思维导图是告诉我们在组建团队的过程中我们需要怎么做。

团队的人一定的活的,不能是死的,所以招聘解聘很重要,通过优胜劣汰保证团队的整体能力是向上发展的。在淘汰不合格的同事上一定要果断!

做成功交付一个大项目,是技术人员转管理的重要捷径,所以有这样的机会,大家一定要把握住,过程中的磕磕绊绊,委屈都不要太在意,只要项目成功了,我们就成功了,所以大项目一定是把握住!

团队管理人也需要做技术规划,我们做技术规划要从以下几个维度进行考虑

一旦我们从技术转管理,一般不会让我们从零开始组建团队的,一定是接手一个已经存在的团队的,所以盘人和盘事,我们一定要做好

期望我上面的总结可以给大家带来帮助,谢谢!
]]>Rust是一门赋予每个人构建可靠且高效软件能力的编程语言。可靠主要体现在安全性上。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软件。
以 windows 11 为例
下载 rustup-init.exe ,双击此可执行程序会打开一个命令行程序,此程序引导安装,具体安装过程:
Rust Visual C++ prerequisitesRust requires the Microsoft C++ build tools for Visual Studio 2013 orlater, but they don't seem to be installed.The easiest way to acquire the build tools is by installing MicrosoftVisual C++ Build Tools 2019 which provides just the Visual C++ buildtools: https://visualstudio.microsoft.com/visual-cpp-build-tools/Please ensure the Windows 10 SDK and the English language pack componentsare included when installing the Visual C++ Build Tools.Alternately, you can install Visual Studio 2019, Visual Studio 2017,Visual Studio 2015, or Visual Studio 2013 and during install selectthe "C++ tools": https://visualstudio.microsoft.com/downloads/Install the C++ build tools before proceeding.If you will be targeting the GNU ABI or otherwise know what you aredoing then it is fine to continue installation without the buildtools, but otherwise, install the C++ build tools before proceeding.Continue? (y/N) yWelcome to Rust!This will download and install the official compiler for the Rustprogramming language, and its package manager, Cargo.Rustup metadata and toolchains will be installed into the Rustuphome directory, located at: C:\Users\cml\.rustupThis can be modified with the RUSTUP_HOME environment variable.The Cargo home directory located at: C:\Users\cml\.cargoThis can be modified with the CARGO_HOME environment variable.The cargo, rustc, rustup and other commands will be added toCargo's bin directory, located at: C:\Users\cml\.cargo\binThis path will then be added to your PATH environment variable bymodifying the HKEY_CURRENT_USER/Environment/PATH registry key.You can uninstall at any time with rustup self uninstall andthese changes will be reverted.Current installation options: default host triple: x86_64-pc-windows-msvc default toolchain: stable (default) profile: default modify PATH variable: yes1) Proceed with installation (default)2) Customize installation3) Cancel installation>info: profile set to 'default'info: default host triple is x86_64-pc-windows-msvcinfo: syncing channel updates for 'stable-x86_64-pc-windows-msvc'info: latest update on 2022-01-20, rust version 1.58.1 (db9d1b20b 2022-01-20)info: downloading component 'cargo' 3.8 MiB / 3.8 MiB (100 %) 1.7 MiB/s in 2s ETA: 0sinfo: downloading component 'clippy' 1.6 MiB / 1.6 MiB (100 %) 1.5 MiB/s in 1s ETA: 0sinfo: downloading component 'rust-docs' 18.8 MiB / 18.8 MiB (100 %) 3.3 MiB/s in 5s ETA: 0sinfo: downloading component 'rust-std' 22.9 MiB / 22.9 MiB (100 %) 3.2 MiB/s in 7s ETA: 0sinfo: downloading component 'rustc' 65.2 MiB / 65.2 MiB (100 %) 493.2 KiB/s in 1m 14s ETA: 0sinfo: downloading component 'rustfmt' 2.2 MiB / 2.2 MiB (100 %) 631.2 KiB/s in 3s ETA: 0sinfo: installing component 'cargo'info: installing component 'clippy'info: installing component 'rust-docs' 18.8 MiB / 18.8 MiB (100 %) 1.9 MiB/s in 6s ETA: 0sinfo: installing component 'rust-std' 22.9 MiB / 22.9 MiB (100 %) 10.5 MiB/s in 2s ETA: 0sinfo: installing component 'rustc' 65.2 MiB / 65.2 MiB (100 %) 12.2 MiB/s in 5s ETA: 0sinfo: installing component 'rustfmt'info: default toolchain set to 'stable-x86_64-pc-windows-msvc' stable-x86_64-pc-windows-msvc installed - rustc 1.58.1 (db9d1b20b 2022-01-20)Rust is installed now. Great!To get started you may need to restart your current shell.This would reload its PATH environment variable to includeCargo's bin directory (%USERPROFILE%\.cargo\bin).Press the Enter key to continue.| 命令 | 说明 | 备注 |
|---|---|---|
| rustup doc | 打开官方指导文档 | |
| cargo new projectName | 创建一个rust工程 | 示例:cargo new firstRustProject |
| cargo run | 运行rust工程 | |
| cargo build | 编译rust工程 | 若增加了依赖,即修改了toml文件,需要重新编译 |
示例:
fn main() { println!("Hello, world!"); //变量默认是不可变的,加上 mut 关键字就可以重新赋值。 let mut x=5; println!("The value of x is {} ",x); x=6; println!("The value of x is {} ",x); //变量的隐藏 let money=100; println!("money is {}",money); let money =money+8; println!("money is {}",money); let money="一百元"; println!("money is {}",money); //常量使用 const 关键字声明,声明的时候必须指定数据类型,常量名全大写。 //不需要let , 不可使用mut 修饰 const MAX_PIONTS: u32=888; println!("The constant is {}",MAX_PIONTS); let result:char= a_function(88, 'M', false); println!("result is {}",result);}fn a_function(a:u64,b:char,c:bool)-> char{ println!("a is {}",a); println!("b is {}",b); println!("c is {}",c); return 'N';}输出:
Hello, world!The value of x is 5The value of x is 6money is 100money is 108money is 一百元The constant is 888a is 88b is Mc is falseresult is N整数,浮点,布尔,字符
可以将多个值放到一个数据类型中。
fn main() { println!("Hello, world!"); let q=3.0; let q:f32=5.00; let w=true; let r:bool =false; let t='🔣'; let tup :(i32,u64,bool) =(88,99,false); println!("元素1:{},元素2:{},元素3:{}",tup.0 , tup.1, tup.2); let arr:[u64;5]=[1,2,3,5,5]; let arr2=['E';9]; println!("arr piont 2 is :{}",arr[1]); println!("arr2 piont 2 is :{}",arr2[1]); //Vector:todo}和大多数编程语言一样, if - else if - else 表示条件语句,在 if 后面不用加括号。
fn main() { println!("Hello, world!"); //条件语句 let a = 12; let b; if a > 0 { b = 1; } else if a < 0 { b = -1; } else { b = 0; } println!("b is {}", b); //三元运算符 let x = 3; let number = if x > 0 { 1 } else { -1 }; println!("number 为 {}", number);}fn main() { println!("Hello, world!"); //循环 //while let mut number = 1; while number != 4 { println!("{}", number); number += 1; } println!("while cycle EXIT"); //for - 迭代器 let a = [10, 20, 30, 40, 50]; for i in a.iter() { println!("元素值为 : {}", i); } println!("for-iter cycle EXIT"); //for - 下标 let b = [10, 20, 30, 40, 50]; let mut length = b.len(); println!("b 数组的长度是:{}", length); for i in 0..length { println!("b[{}] = {}", i, b[i]); } println!("for-index cycle EXIT"); //loop 终止循环,并返回一个值 let s = ['R', 'U', 'N', 'O', 'B']; let mut i = 0; let location = loop { let ch = s[i]; if ch == 'B' { break i; } i += 1; }; println!(" \'B\' 的索引为 {}", location);}println!("print a small =_=*");花括号中套花括号就可以输出花括号
示例代码:
let mut name = String::from("cml"); println!("输出中带花括号:{{ {} }}", name);以上代码输出:
输出中带花括号:{ cml }println!("输出一个结构体,a={:?}", a);let mut guess = String::new();io::stdin().read_line(&mut guess).expect("无法读取行");所有权可以理解为命名空间+作用域+指针。
示例代码:
fn main() { println!("Hello, world!"); //01.基本数据类型(值类型)变量在栈空间中可以复制。先给x赋值9(let x = 9),将x赋值给y等同于直接给y赋值9(let y = x 等同于let y = 9) let x = 9; let y = x; //x is :9 , y is :9 println!("x is :{0} , y is :{1}", x, y); let c1 = 'M'; let c2 = c1; //c1 is :M , c2 is :M println!("c1 is :{0} , c2 is :{1}", c1, c2); //02.引用类型变量在堆空间中的“值引用”可以复制,但是存储在栈空间的“值”不可复制。因此,引用类型变量仅可被消费一次。 let s1 = String::from("hello"); let s2 = s1; //编译错误:use of moved value: `s1` //let s3 = s1; //编译错误:borrow of moved value: `s1` //println!("s1 is :{0} , s2 is :{1}", s1, s2); //03.引用类型变量可以通过“克隆”的方式复制。 let h1 = String::from("hello"); let h2 = h1.clone(); let h3 = h1.clone(); //h1 = hello, h2 = hello , h3 is :hello println!("h1 = {}, h2 = {} , h3 is :{}", h1, h2, h3); //04.特殊的数据类型--->类型的引用(即指针),使用 & 关键字表示 let k1 = String::from("hello"); //k1为String类型:std::String::String ; k2 为带String类型的指针类型:&std::String:String let k2 = &k1; let k3 = k2; //指针存放在栈中,便于理解可以将指针看作“特殊的值类型”。所以虽然k2已经赋值给了k3,任然可以赋值给k4。 let k4 = k2; //k1无法赋值给k5,因为k1已经被借用了。这是出于安全考虑,试想如果多个人都可以借用k1,意味着多个人可以修改k1,那势必对正在使用它的人正在进行的工作产生影响 // let k5 = k1; //编译错误:`k2` is a `&` reference, so the data it refers to cannot be borrowed as mutable //既然k2是指针类型,那么就不允许被修改,因为k2的“值”本身是借来的,如果修改了,那么势必对正在使用它的人正在进行的工作产生影响 //k2.push_str("world"); println!("k1 is {}, k2 is {}, k3 is :{} , k4 is : {}", k1, k2, k3, k4); //值类型(基本数据类型)变量也可以使用指针,但是一般不建议这样使用 //n1为i32类型;n2为&i32类型 let n1 = 8; let n2 = &n1; println!("n1 is :{} , n2 is :{}", n1, n2); //05.可变变量的指针。 let mut m1 = String::from("run"); let m2 = &mut m1; m2.push_str(",world"); println!("m2 is :{}", m2); //编译错误:cannot borrow `m1` as immutable because it is also borrowed as mutable //借出去后,所有权已不在拥有,所以无法被消费 // println!("m1 is :{} , m2 is :{}", m1, m2); //编译错误:cannot borrow `m2` as mutable, as it is not declared as mutable。cannot borrow as mutable //不可将借来的东西再借给别人 // let m3 = &mut m2; //06.rust中不允许出现空指针}以上代码输出:
Hello, world!x is :9 , y is :9c1 is :M , c2 is :Mh1 = hello, h2 = hello , h3 is :hellok1 is hello, k2 is hello, k3 is :hello , k4 is : hellon1 is :8 , n2 is :8m2 is :run,world切片是指向数据结构(字符串、集合)一部分内容的引用。
不愿意将rust中的切片理解为一种“类型”,实际上也不是;更不愿将rust中的切片理解为一种“集合”。暂且将切片理解成一种对象吧。例如 &s[0..5] 就是获取到了字符串 s 索引从0到5位置的元素,包含0不包含5。
rust中的切片部分主要是要理解索引和下标的概念。
示例代码:
fn main() { println!("Hello, world!"); let s = String::from("hello,world."); //ss 的数据类型为:&str let ss = &s[0..5]; println!("s is : {} , ss is : {}", s, ss); let arr = [1, 3, 5, 7, 9]; //start_part 的数据类型为:&[i32] let start_part = &arr[0..3]; let end_part = &arr[3..]; let full_part = &arr[..]; println!( "arr is : {:?} , start_part is : {:?} , end_part is : {:?} , full_part is : {:?}", arr, start_part, end_part, full_part );}以上代码输出:
Hello, world!s is : hello,world. , ss is : helloarr is : [1, 3, 5, 7, 9] , start_part is : [1, 3, 5] , end_part is : [7, 9] , full_part is : [1, 3, 5, 7, 9]struct 类似 java 中的类,用来自定义一种数据结构,这种数据结构一般用来描述生活中的某一个对象。struct 中可以包含属性和方法。使用结构体分为两步:首先需要定义一个结构体,然后需要实例化一个结构体,再然后才可以使用。
一旦结构体实例化的时候是可变的,即使用 mut 修饰, 那么结构体中所有的属性都将是可变的。
结构体属性:
示例代码:
/** * 人 */#[derive(Debug)]struct Person { //结构体属性:声明方式,属性名:数据类型;即使是最后一个属性,末尾也要加英文逗号;属性可以是另外一个结构体,但不能是本身;属性可以是一个元组 active: bool, name: String, email: String, sign_in_count: u64, nation: Nation, parent: Parent, empty: Empty, tuple: (u32, u32),}/** * 国家结构体 */#[derive(Debug)]struct Nation { name: String, area: String, time_zone: u64,}/** * 父母结构体 * 特殊的结构体,元组结构体。 */#[derive(Debug)]struct Parent(String, String);/** * 空结构体 */#[derive(Debug)]struct Empty;fn main() { println!("Hello, world!"); //实例化一个结构体 let p1 = Person { active: true, name: String::from("cml"), email: String::from("cnaylor@163.com"), sign_in_count: 99, nation: Nation { name: String::from("xm"), area: String::from("north"), time_zone: 8, }, parent: Parent(String::from("baba"), String::from("mama")), empty: Empty, tuple: (8, 8), }; //实例化一个可变结构体 let mut p2 = Person { active: true, name: String::from("cml"), email: String::from("cnaylor@163.com"), sign_in_count: 99, nation: Nation { name: String::from("xm"), area: String::from("north"), time_zone: 8, }, parent: Parent(String::from("baba"), String::from("mama")), empty: Empty, tuple: (8, 8), }; //给结构体实例重新赋值 p2.email = String::from("c@163.com"); let email = &p2.email; println!("p1 is : {:#?} , p2 is : {:#?} , email is :{}", p1, p2, email); #[derive(Debug)] struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { let u = User { email: email, //简写。函数参数和结构体属性名相同,可简写 // email, username, active: true, sign_in_count: 1, }; //从已创建的结构体实例创建实例 // let u2 = User { ..u }; let u3 = User { email: String::from("o@163.com"), ..u }; return u3; } let u = build_user(String::from("a@163.com"), String::from("tom")); println!("u is : {:?}", u);}以上代码输出:
Hello, world!p1 is : Person { active: true, name: "cml", email: "cnaylor@163.com", sign_in_count: 99, nation: Nation { time_zone: 8, }, parent: Parent( "baba", "mama", ), empty: Empty, tuple: ( 8, 8, ),} , email is :c@163.comu is : User { active: true, username: "tom", email: "o@163.com", sign_in_count: 1 }枚举表示某一个对象可能的值,实际使用中常用来表达:ip地址类型,订单状态,人员性别,实名认证方式等某一事物简短又不经常变化的“可选值”。
枚举成员:
use std::fs::File;fn main() { let f = File::open("hello.txt"); match f { Ok(file) => { println!("File opened successfully."); }, Err(err) => { println!("Failed to open the file."); } }}fn main() { enum Book { Papery(u32), Electronic(String) } let book = Book::Electronic(String::from("url")); if let Book::Papery(index) = book { println!("Papery {}", index); } else { println!("Not papery book"); }}Option 是一个标准库中的枚举,用来处理空值(null) 的情况。Option是一个泛型枚举,接受类型 T 。Option 要干的事情和java 中的 optional(java8新特性) 类似。
简言之:
Option 源码:
enum Option<T> { Some(T), None,}使用举例:
// 整数除法。fn checked_division(dividend: i32, divisor: i32) -> Option<i32> { if divisor == 0 { // 失败表示成 `None` 取值 None } else { // 结果 Result 被包装到 `Some` 取值中 Some(dividend / divisor) }}// 此函数处理可能失败的除法fn try_division(dividend: i32, divisor: i32) { // `Option` 值可以进行模式匹配,就和其他枚举类型一样 let result = checked_division(dividend, divisor); if result!=Option::None { //获取返回值 println!("Nice, result is :{:?}", result.unwrap()); } match checked_division(dividend, divisor) { None => println!("{} / {} failed!", dividend, divisor), Some(quotient) => { println!("{} / {} = {}", dividend, divisor, quotient) } }}fn main() { try_division(4, 2); try_division(1, 0);}以上代码输出:
Nice, result is :24 / 2 = 21 / 0 failed!Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。
vector 允许我们在一个单独的数据结构中储存多个值,所有值在内存中彼此相邻排列。vector 只能储存相同类型的值。
如果借助枚举,有时候 vector 也可以变相存储不同类型的值。
示例代码:
fn main() { println!("Hello, world!"); //新建一个 vector let mut v: Vec<i32> = Vec::new(); let v2 = vec![1, 2, 3]; println!("v is : {:?} , v2 is : {:?}", v, v2); //更新 v.push(888); v.push(111); v.push(222); v.push(333); //删除 v.remove(0); //查询 let two = v[1]; let two2 = &v[1]; //get方法返回的是Option let three = v.get(2); println!("two is : {} , three is : {:?}", two, three); //遍历 let arr = vec![100, 32, 57]; for i in &arr { println!("arr item for --> {}", i); } //借助枚举,vector中可以存储不同的数据类型 let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ]; let s = &row[1]; println!("row is : {:?} , s is : {:?} ", row, s);}#[derive(Debug)]enum SpreadsheetCell { Int(i32), Float(f64), Text(String),}以上代码输出:
Hello, world!v is : [] , v2 is : [1, 2, 3]two is : 222 , three is : Some(333)arr item for --> 100arr item for --> 32arr item for --> 57row is : [Int(3), Text("blue"), Float(10.12)] , s is : Text("blue")你没有看错,rust 中,字符串也是集合,是什么集合呢? 是“字符”的集合。注意:上面提到过字符是 rust 的基础标量类型,但是字符串不是标量类型,而是集合类型。
更新字符串的方式:
示例代码:
fn main() { println!("Hello, world!"); //创建 let mut s = String::new(); let data = "initial contents"; let ss = data.to_string(); let sss = String::from("你好!"); println!("s is : {} , ss is : {} , sss is : {}", s, ss, sss); //更新 let mut word = String::from("Aa"); word.push_str("Bb"); word.push('C'); word += "cDd"; word = format!("{}{}-{}", word, "Ee", "Ff"); println!("final word is : {} ", word); //不要使用下标访问 let p = String::from("hello"); let p1 = &p[0..3]; let k = String::from("你好我是陈明亮"); let k1 = &k[0..3]; //输出结果可能不是大多数人预期:p1 is : hel , k1 is : 你 println!("p1 is : {} , k1 is : {}", p1, k1); //遍历 word+="北京"; for i in word.chars() { println!("word ---> item is :{}", i); }}以上代码输出:
Hello, world!s is : , ss is : initial contents , sss is : 你好!final word is : AaBbCcDdEe-Ffp1 is : hel , k1 is : 你word ---> item is :Aword ---> item is :aword ---> item is :Bword ---> item is :bword ---> item is :Cword ---> item is :cword ---> item is :Dword ---> item is :dword ---> item is :Eword ---> item is :eword ---> item is :-word ---> item is :Fword ---> item is :fword ---> item is :bword ---> item is :Cword ---> item is :cword ---> item is :Dword ---> item is :dword ---> item is :Eword ---> item is :eword ---> item is :-word ---> item is :Fword ---> item is :fword ---> item is :北word ---> item is :京讲道理,rust 中的代码组织相比 java 、CSharp 这些所谓高级语言,要复杂的多。大概包含两个部分:命名空间和访问权限。
rust 中针对包管理主要有三个概念:包(package),箱(crate),模块(module)。三个概念形成一个树状结构,包中包含箱,箱中包含模块。
Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。
关键字:
工程目录文件结构:
│ main.rs│ other_rs_file.rs└─entity dept.rs mod.rs示例代码:
other_rs_file.rs:
pub mod other { #[derive(Debug)] pub struct User { //结构体属性默认是私有的,若外部需要访问需添加pub关键字 pub name: String, pub age: i32, } fn init_user(n: String) -> User { let u = User { name: String::from(n), age: 100, }; return u; } pub fn add_user(n: String, a: i32) -> bool { return false; } //枚举不用给元素添加 pub ,只要枚举是公开的,里面元素就是公开的 #[derive(Debug)] pub enum IpAddrKind { IPV4, IPV6, }}dept.rs:
#[derive(Debug)]pub struct Dept { pub name: String, pub no: i32,}mod.rs:
pub mod dept;main.rs:
mod other_rs_file;mod entity;fn main() { println!("Hello, world!"); /* * main.rs 同级 rs文件 * 引入其他rs文件,需先导入module ,通过mod名称(即文件名称)::对象名称 访问其内部对象 * 如果在其他文件内部定义了子 module , 导入方式不变,访问方式:mod名称::子mod名称::对象名称 * 导入时候只导入顶层mod */ let u = other_rs_file::other::User { name: String::from("cml"), age: 9, }; println!("实例化另外一个rs文件中定义的结构体,u is: {:?}", u); /* * main.rs 上级文件夹 rs 文件 * rust 默认将rs文件识别为一个 module 处理, 但是无法将文件夹识别为一个 module ,所以如果我们要访问entity文件夹中dept.rs文件中的对象,需要在entiry文件夹中新增一个mod.rs 并在其中定义 dept module。 */ let d=entity::dept::Dept{ name:String::from("技术部门"), no:5, }; println!("实例化另外一个rs文件中定义的结构体,d is: {:?}", d);}main输出:
Hello, world!实例化另外一个rs文件中定义的结构体,u is: User { name: "cml", age: 9 }实例化另外一个rs文件中定义的结构体,d is: Dept { name: "技术部门", no: 5 }示例代码:
Cargo.toml:
[package]name = "Crate"version = "0.1.0"edition = "2021"[dependencies]rand = "0.5.5"main.rs:
/** 导入标准库*/use std::fmt::Result as FmtResult;use std::io::Result as IoResult;// use std::cmp::Orderinguse std::*;use std::{self, cmp::Ordering, io};/** 导入外部第三方库*/use rand::{thread_rng, Rng};fn main() { println!("Hello, world!"); let fmtr = FmtResult::Ok; let ior = IoResult::Ok("成功"); println!("fmtr 无法打印 , ior is : {:?}", ior); //生成随机数 let mut rng = thread_rng(); let x: u32 = rng.gen(); println!("x is :{}", x);}工程结构:
│ Cargo.lock│ Cargo.toml│ hello.txt├─src│ main.rs示例代码:
use std::fs;use std::fs::File;use std::io;use std::io::Read;fn main() { println!("Hello, world!"); let mut f = File::open("hello.txt"); match f { Ok(file) => { println!("File opened successfully."); } Err(err) => { //打开失败 println!("Failed to open the file."); match err.kind() { //打开失败原因,不存在情况创建 io::ErrorKind::NotFound => { println!("File Not Found. soon create . "); File::create("hello.txt"); fs::write("hello.txt", "I am hello.txt"); } _ => { //其他原因直接抛 panic panic!("Failed to open the file."); } } } } //打印 txt内容 let mut file = std::fs::File::open("hello.txt").unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); print!("txt 的内容是:{}", contents);}以上代码输出:
Hello, world!Failed to open the file.File Not Found. soon create .txt 的内容是:I am hello.txt泛型这种编程语言得设计绝非 rust 独有的,实际上在C# , Java 这些语言中早就引入了泛型的设计思想。泛型即类型的泛化,在编写代码的时候并不知道具体的数据类型或者数据结构是什么样子的,而是定义一个标识,在运行时此标识可动态替换为实际的数据类型。很显然这种设计能够让开发人员避免编写很多相似的重复的代码。
在 rust 中,可以在如下位置编写泛型代码:
示例代码:
fn main() { println!("Hello, world!-->泛型"); /* * 泛型函数 */ let a = [2, 4, 6, 3, 1]; println!("数字数组中最大元素是 = {}", max(&a)); let b = ["A", "B", "C"]; println!("字符数组中最大元素是 = {}", max(&b)); /* * 泛型结构体 */ // i32 let p1 = Point { x: 1, y: 2 }; //f64 let p2 = Point { x: 1.0, y: 2.0 }; println!("p1 ={:?} , p2 = {:?}", p1, p2);}//求最大元素fn max<T: std::cmp::PartialOrd>(array: &[T]) -> &T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } return &array[max_index];}//泛型结构体#[derive(Debug)]struct Point<T> { x: T, y: T,}以上代码输出:
Hello, world!-->泛型数字数组中最大元素是 = 6字符数组中最大元素是 = Cp1 =Point { x: 1, y: 2 } , p2 = Point { x: 1.0, y: 2.0 }特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法
一个对象的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。
示例代码:
use std::time::{SystemTime, UNIX_EPOCH};fn main() { println!("Hello, world!--->特性(接口)"); //实例化Person let p = Person { name: String::from("cml"), age: 100, }; let d = p.describe(); println!("d = {}", d); let s = p.tostring(); println!("s={}", s); let t = p.nowTime(); println!("t ={:?}", t);}//定义一个描述接口trait Descriptive { fn describe(&self) -> String; //接口默认方法 fn nowTime(&self) -> SystemTime { let start = SystemTime::now(); return start; }}//定义一个 tostring 接口trait ToString { fn tostring(&self) -> String;}//定义一个结构体#[derive(Debug)]struct Person { name: String, age: u8,}//结构体的一个继承类实现了 Descriptive 接口impl Descriptive for Person { fn describe(&self) -> String { format!( "I am Person , name is : {} , age is : {}", self.name, self.age ) }}impl ToString for Person { fn tostring(&self) -> String { format!("Person:{{name:{},age:{}}}", self.name, self.age) }}以上代码输出:
Hello, world!--->特性(接口)d = I am Person , name is : cml , age is : 100s=Person:{name:cml,age:100}t =SystemTime { intervals: 132914629768805244 }rust 标准库中的 std::fs 可以用来操作文件
示例代码:
use std::fs;use std::fs::OpenOptions;use std::io::prelude::*;fn main() { println!("Hello, world!--->文件io"); //读取文件内容,一次性读取 //文件内容:This is a text file. let text = fs::read_to_string("text.txt").unwrap(); println!("txt文件的内容是:{}", text); //写入文件,追加 //追加完成文件内容: append(); println!("追加后,txt文件的内容是:{}", text); //写入文件,会覆盖 //覆盖之后文件内容: fs::write("text.txt", "FROM RUST PROGRAM").unwrap(); println!("覆盖写入后,txt文件的内容是:{}", text);}//追加fn append() -> std::io::Result<()> { let mut file = OpenOptions::new().append(true).open("text.txt")?; file.write(b" APPEND WORD")?; Ok(())}以上代码输出:
Hello, world!--->文件iotxt文件的内容是:This is a text file.追加后,txt文件的内容是:This is a text file.覆盖写入后,txt文件的内容是:This is a text file.面向对象编程(OOP)思想是一个概念,是一种思想指导,围绕“对象”展开,从万物皆是对象的角度出发,一个 crate , 一个 module ,一个 struct ,一个枚举等等都是一个个独立的对象,能自主表达一个事物、某种特征。而运用封装、继承、多态等手段可以让对象与对象之间产生某种联系,进而表达更多的事物,解决更多的问题。
面向对象思想是构建大型应用软件系统的基石。
rust 语言中 可以通过 结构体,枚举,特性(trait)等来实现oop思想。
rust 中的并发编程主要得益与 线程 、 消息传递和互斥锁。
Rust 语言是满足多线程特性的,所以 rust 可以满足 主–>子 多任务应用场景。
消息传递有点类似与消息队列,但消息队列一般跨进程或线程,而 rust 中的消息传递主要是主子线程中数据的传递。
示例代码:
use std::thread;use std::time::Duration;fn main() { println!("[主线程] Hello, world!--->线程"); //创建子线程 for item in 0..5 { println!("[主线程] 即将创建一个子线程,当前循环变量:{}", item); let child_thread = thread::spawn(|| { for i in 0..2 { println!("[子线程] hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); } for i in 0..3 { println!("[主线程] hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } //线程join let child1 = thread::spawn(|| { println!("[child-1]hi I am spawned thread!"); thread::sleep(Duration::from_millis(1)); }); let child2 = thread::spawn(|| { println!("[child-2]hi I am spawned thread!"); thread::sleep(Duration::from_millis(1)); }); child1.join().unwrap(); child2.join().unwrap(); println!("[main]hi I am main thread!");}以上代码输出:
[主线程] Hello, world!--->线程[主线程] 即将创建一个子线程,当前循环变量:0[主线程] 即将创建一个子线程,当前循环变量:1[子线程] hi number 0 from the spawned thread![主线程] 即将创建一个子线程,当前循环变量:2[子线程] hi number 0 from the spawned thread![主线程] 即将创建一个子线程,当前循环变量:3[子线程] hi number 0 from the spawned thread![主线程] 即将创建一个子线程,当前循环变量:4[子线程] hi number 0 from the spawned thread![主线程] hi number 0 from the main thread![子线程] hi number 0 from the spawned thread![子线程] hi number 1 from the spawned thread![子线程] hi number 1 from the spawned thread![子线程] hi number 1 from the spawned thread![子线程] hi number 1 from the spawned thread![主线程] hi number 1 from the main thread![子线程] hi number 1 from the spawned thread![主线程] hi number 2 from the main thread![child-1]hi I am spawned thread![child-2]hi I am spawned thread![main]hi I am main thread!以下示例演示了子线程获得了主线程的发送者 tx,并调用了它的 send 方法发送数据,然后主线程就通过对应的接收者 rx 接收到了发送的数据。
无法在子线程中发送而在另外一个子线程中接收
示例代码:
use std::sync::mpsc;use std::thread;fn main() { println!("Hello, world!--->消息传递"); let p1 = Person { active: true, name: String::from("cml"), email: String::from("cnaylor@163.com"), sign_in_count: 99, tuple: (8, 8), }; let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(p1).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {:?}", received);}#[derive(Debug)]struct Person { active: bool, name: String, email: String, sign_in_count: u64, tuple: (u32, u32),}以上代码输出:
Hello, world!--->消息传递Got: Person { active: true, name: "cml", email: "cnaylor@163.com", sign_in_count: 99, tuple: (8, 8) }互斥锁(mutex)是 mutual exclusion 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 锁(lock)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 保护(guarding)其数据。
以下示例演示了在主线程中创建一个值,并在多个子线程中修改此值,最终等所有子线程处理完毕,主线程打印最终值。为了让变量能够跨线程之间共享,引入了std::sync::Arc 这个结构体。
示例代码:
use std::sync::{Arc, Mutex};use std::thread;fn main() { println!("Hello, world!--->互斥锁"); //Arc 原子引用计数器,确保在多个线程中共享数据;counter初始值为0 let counter = Arc::new(Mutex::new(0)); println!("counter 初始值为:0"); //定义一个子线程集合 let mut handles = vec![]; for _ in 0..10 { //将counter值从主线程中克隆,并赋值给私有变量 counter let counter = Arc::clone(&counter); let handle = thread::spawn(move || { //获取互斥锁,并将counter值加一 let mut num = counter.lock().unwrap(); *num += 1; let thread_id = thread::current().id(); println!("[子线程:{:?}]中将 counter 的值修改为:{}", thread_id, num); }); //将创建的子线程存储到子线程集合中 handles.push(handle); } //将所有的子线程join到主线程,确保主线程等待所有子线程执行成功 for handle in handles { handle.join().unwrap(); } let result = *counter.lock().unwrap(); println!("Result: {}", result);}以上代码输出:
Hello, world!--->互斥锁counter 初始值为:0[子线程:ThreadId(2)]中将 counter 的值修改为:1[子线程:ThreadId(4)]中将 counter 的值修改为:2[子线程:ThreadId(3)]中将 counter 的值修改为:3[子线程:ThreadId(5)]中将 counter 的值修改为:4[子线程:ThreadId(8)]中将 counter 的值修改为:5[子线程:ThreadId(6)]中将 counter 的值修改为:6[子线程:ThreadId(7)]中将 counter 的值修改为:7[子线程:ThreadId(9)]中将 counter 的值修改为:8[子线程:ThreadId(10)]中将 counter 的值修改为:9[子线程:ThreadId(11)]中将 counter 的值修改为:10Result: 10本文示例代码维护在gitee上面:https://gitee.com/naylor_personal/rust-hello-world
打开代码仓库中的 Sport 文件夹,有惊喜!!!

截止到2021年,贝壳联网门店数超过5.1万家,同比增长8.7%,活跃门店数量超过4.5万家,同比增长4.4%;联网经纪人45.5万人,活跃经纪人人数超过40.6万人,占比接近 **90%**。
他们用一套叫ACN(Agent Cooperate Network,经纪人合作网络)的逻辑,打破经纪人、店东、品牌之间的边界,把经纪人之间头破血流的竞争变为合作,提升全行业效率。
逻辑是对的。用同向为竞,取代相向为争,做大盘子。通过减少恶性竞争,提高行业效率。实际也发现在不少门店的收入增长1天倍以上的同时,客户房子的成交天数也大幅缩短,从143减到了109天。
原来的房产经纪在业内大量充斥着信息不对称赚钱,用不存在的假房源吸引买家,再推销其它房子,故意隐瞒房子的各种缺点,只为成交,不让上下游见面,吃两头差价。但信息不对称,并不是这个行业的“真问题”。这个行业的真问题其实是:C端单次博弈,B端零和博弈。

什么叫C端单次博弈?你去旅游,走进一家海鲜餐厅,看到一条没有见过的鱼,于是问老板:老板,这是什么鱼啊?老板二话不说,立马从鱼缸里把鱼捞出来,“砰”的一声在地上摔死,然后告诉你这叫什么鱼。500元一斤,这条鱼10几斤,5000元。你傻了,说我就是问一问,你摔死它干嘛。然后想走。20多个人围了过来。你意识到,你被宰了。为什么我们常常在旅游景点,遇到这些的“宰客”现象呢? 因为“单次博弈”。
你站在海鲜餐厅老板的角度想想看。大部分进餐厅的客人都是游客,这辈子估计只来一次。你对他好也不会再来吃,你对他坏也不会再遇到。这时,你的最佳策略是什么?一定是宰死他为止啊!这就是单次博弈。单次博弈,会让人倾向于把短期利益最大化,而不计长期后果。 房产经纪行业,也存在大量的单次博弈。
整个房产经纪行业,已接近200万人从事该行业,受疫情影响,平均房产经纪人从业一年内离职率高达29.4%,较2020年上涨7.1个百分点。据统计,中国房地产经纪行业从业人员的月流失率**10%**以上。
和C端消费者的“单次博弈”,在房产交易行业的存在可能性很大,可以看做几乎是必然的。单次博弈的风险,让个别经纪人通过转行的方式化解对其个人的影响,但对整个房产经纪行业而言,却是剩下的从业人员“背锅”
什么叫B端零和博弈?这里有一块蛋糕。我吃你就没有了,你吃我就没有了。然后我们就打起来了。打到最后只有两种结果:要么我吃你饿着,要么你吃我饿着。 这属于零和博弈。
在房产经纪行业,中介费则催生了典型的零和博弈。
张三努力寻找客户,带客户看房子,介绍房子的优缺点,想要促成交易。就要临门一脚了,李四给客户打电话:这套房子找我买,中介费便宜1000元。客户和李四成交了。张三辛勤劳动,什么都没得到(0%)。李四暗地撬单,偷走所有果实(100%)。
在零和博弈中,赢家拿走全部,输家两手空空。
因此在房产经纪行业恶性竞争不断升级,各自互相撬单,甚至大打出手。那怎么办呢?张三要求,在我这里卖房的业主,必须签署独家委托协议。这似乎是阻止了互相撬单,恶性竞争。但是,因为独家委托协议的限制,帮着一起卖房子的人少了,成交就变慢了,甚至没卖出去,业主的利益受到连带损害。因为想要避免零和博弈,却让客户利益受损,行业效率下降。
C端服务单次博弈,B端竞争零和博弈,两大博弈问题是制约房产经纪行业发展的真问题。看到真问题,才能找到根本解。
什么是“根本解”?贝壳的CEO彭永东提出:改变博弈结构。把C端单次博弈,改变为重复博弈;把B端零和博弈,改变为多赢博弈。
房产经纪行业的真问题是:C端单次博弈,B端零和博弈。C端单次博弈的根源,是经纪人从业时间短;B端零和博弈的根源,是经纪人赢家通吃。
那么,解决这个问题的根本思路,也就出来了。那就是用一套行业合作机制:一是延长经纪人的从业时间,老客户的口碑效应显现,让经纪人可以从长期诚信中受益,减少C端单次博弈;二是经纪人合作卖房,按照贡献分配中介佣金,让所有的付出都可以得到相应的回报,减小B端零和博弈。
这套行业合作机制,就是贝壳提出的“ACN”。ACN是一套行业合作机制。这个合作机制,把经纪人的工作,分为了10个角色(房源方5个,客源方5个):
● 房源录入人是第一个把房源录入ACN的经纪人。这个人,可能是房源附近的一家门店的经纪人,也可能是以前把房子卖给业主,所以业主信任的经纪人。这个角色具体是谁,有一定的随机性。但这是整个房产交易的开始。
● 房源维护人是一个在房源附近、熟悉小区环境,可以陪同客户看房的经纪人。房源录入人,可能并不正好在房源附近。他可能只是认识业主,所以无法帮助房子成交,甚至无法陪同客户看房。
● 房源实勘人由专业的人员担任,因为对拍照、录制VR的专业性要求高,影响房屋成交。
● 委托备件人是负责看起来琐碎,但不能出错的事务性工作。比如获得业主签字的委托书、身份信息、房产证信息,然后上传到政府指定的系统。一个门店里的所有备件工作,可由一个人完成。
● 房源钥匙人,作为一个角色,它是独立的。他挑起了业主对经纪公司的信任。这个角色很关键。把钥匙放在谁手上,是要得到业主同意的。这个人很可能也是房源维护人,也陪同客户看房。
这是房源方的5个角色。实际操作中,一个经纪人可能会同时承担几个角色。在近一半的情况中,房源录入人和房源实勘人,就是一个人。甚至有时候,这5个角色都是一个人。但也有不少情况,房源录入人和房源维护人,不是同一个人。做贡献的是角色,得利益的是人。所以,一定要把角色分清楚。
这样,承担角色越多,得到利益越多。这样才公平。那这5个角色加在一起,能分到中介费的多少呢?每个城市都不一样。贝壳基本是一市一策。但大体上,在四成多一些。
另外的五成多,分给5个客源方角色。
● 客源推荐人。这是第一个接触、引入这个客户的人。不管客户是主动找上门,还是历史老客户推荐、朋友推荐,是客户推荐人把客户引入ACN合作网络里的,到平台上来成交的。
● 客源成交人。这是帮助客户找到满意的房子,并完成带看、成交、最终完成签约的人。大多时候,这个人也是客源推荐人。但也有不重合的。比如,客源推荐人经验少,店东会安排一个更高级别的经纪人,担任客源成交人。
● 客源合作人是辅助经纪人的角色,比如带看和交易的时候准备文件。这个角色,通常是比较年轻的经纪人。他分成比例也不高。但是,他可以跟着学习成长。
● 客源首看人是第一个带看房屋的人,比如经纪人A带客户看了一套房子。没看中。他又和经纪人B去看了几套其它房子。最后兜兜转转回来成交了最开始的那套,但成交人不是A。不过A,就是客源首看人。为什么要专门设置客源首看人呢?因为ACN是保护首看的。这样,才能有效预防抢单、撬单。
● 交易/金融顾问是在ACN统一的交易中心内,帮助业主、客户完成签约、贷款等交易手续的专员,交易/金融专员的角色可以帮助减少客源成交人的事务性工作,也降低风险。
这5个角色,分配五成多的佣金。那谁应该拿得更多呢?客源成交人。大约能拿到这五成多中的60%,也就是佣金总额的30%出头。(分配比率是长时间经过实践检验得出的,但是并非固定值)ACN在每个城市都有协会,根据本地的情况做微调,以达到最公平的均衡。
ACN带来了“重复博弈 + 多赢博弈”的胜利。贝壳先把链家所有真房源都放在ACN网络里,所有后面加入的房源即使是曾经竞争对手的经纪公司,都可以跟卖链家这些房源。
真房源,一直是链家的核心竞争力。这相当于我先把自家的肉全都端上桌,你可以一起吃。你们要不要也端两个菜上桌,让我们这顿宴席更丰盛,自己看吧。链家先干为敬,行业他人将信将疑。这样的情绪,一直延续了5个月。直到2018年10月。市场出现了转机。逐渐有人,端菜上桌了。
愿意端菜上桌,与大家分享,一定是因为他从分享中获益了。这是ACN逻辑能够成立,并且形成增强回路的关键标志。
2021年贝壳披露的数据显示,在链家的“基本盘”存量房业务,非链家门店GTV已达到9281亿元,快要追赶上链家的1.01万亿元;在新房业务上,非链家门店GTV更是达1.11万亿元,远超链家的2767亿元。贝壳平台上参与者间的合作不断增强,二手跨店合作率稳定提升至**75%,年末非链家门店贡献在售二手房房源量超过81%**。
分工带来效率,贝壳CEO彭永东说。以前要不然100%,要不然0%。在ACN模式下房产经纪面前有三个选择矩阵:1)低概率的100%;2)高概率的0%;3)确定的X%。越来越多房产经纪公司,选择确定的X%。这就不断促成行业协作,把盘子做大。交易盘子大了,B端零和博弈,就会切换为多赢博弈。
ACN网络10个角色的设定,也很好地解决了经纪人从业时间短的问题,从而解决了C端单次博弈的问题。如果你是一个新人,没有经验,签不了单,怎么办?那可以从“委托备件人”或者“客源合作人”,这样的事务性工作开始做起啊。分成虽然不高,但足以养活自己,同时不断向前辈学习,持续成长。在过去,新人加入行业的第一天,就要和业内最有经验的经纪人同台竞技。
这种一上来就打老怪的游戏,对新人非常不友好。要不然大获全胜,要不然尸骨无存。所以,从业时间当然短。而ACN的10个角色,就像一张“职业生涯地图”,让新人可以在不同难度、不同重要度的职位、职级上不断爬升,给了新人成长的时间。从业时间增加,从C端单次博弈模式,就会切换为重复博弈。从C端单次博弈,切换到重复博弈;从B端零和博弈,切换到多赢博弈。
任何一个商业模式,都包括三个部分:客户价值,资源能力,和盈利方式。贝壳的客户价值是:解决行业C端单次博弈,B端零和博弈的问题;资源能力是:ACN经纪人合作网络。贝壳挣的钱,来自ACN这个模式带来的三个全局性增量(Δ)。
一切的商业模式,都必须有全局性增量。如果没有,那所谓的商业模式,就是把你口袋的钱换到我口袋。ACN必须创造出全局性增量,才可以让贝壳增效、商家挣钱,才可以叫商业模式创新。
Δ1:从链家到贝壳的增量。链家,从2018年的GTV大约1万亿到2021年贝壳全年成交额(GTV)为3.85万亿元,GTV(Gross Transaction Volume),就是房产的成交总额。一套价值1000万的房子转手,GTV就是1000万。从单打独斗,到行业协作,贝壳介入了比链家更大的市场(1万亿 → 3.85万亿)。虽然链家在竞争中独拿全部,贝壳在合作中分享部分,但市场变大了,贝壳的收入规模,远远大于链家。从链家转向平台第一年,收入200多亿,到2021年营业收入为808亿元,增长3倍。
Δ2:体验口碑的持续增量。一边是贝壳带着45万经纪人,坚持做真房源,用重复博弈的思路延长经纪人从业时间;另一边是另外150万经纪人仍然用以前的套路,客户买房时会怎么选?当然会选服务更好的。这本身就是过去十几年链家在鱼龙混杂的经纪行业脱颖而出的原因,现在这些都到了贝壳,体验更好,贝壳和上面所有合作者就会有共同的增量。再进一步看,一个确定的趋势是中国未来存量房市场越来越大,经纪服务渗透率越来越高,这个市场会更大,口碑更好的贝壳就更有可能获得这部分新市场增量。
Δ3:从经纪到周边的增量。经纪业务,不是贝壳的核心。从经纪公司的中介费中分佣金,必然会导致我多你就少的博弈。贝壳CEO彭永东说,贝壳是希望通过经纪业务,接触到更大的市场,发现周边更大的可能性。彭永东预计,这个行业每产生1元钱的中介收入,就能触达2元钱的周边业务。这是更大的增量空间。
比如金融。比如装修。比如一手房。一手房过去在楼盘附近的售楼中心销售。但现在很多一手房上架了贝壳ACN。全国5.1万门店, 和45.6万经纪人一起帮你卖。45.6万人经纪人共同发掘了,大量分散的二线城市买省会城市,市中心租房买郊区新房的,时空距离远的需求,让一手房销售效率得到巨大提升。这就是从传统二手房经纪业务,走向周边,你想得到或者想不到的增量。只要创造了增量,下面就不是怎么挣钱的问题了,而只是怎么分钱的问题。
链家的创始人左晖曾说过“做难而正确的事。”做正确的事则需要直面行业的“真问题”。难的事则是需要找到问题的“根本解”。贝壳在进入房源市场,打造交易信息平台时,并没有回避行业中关于人性的博弈,而是利用交易行为中各类角色的分析和利润的分配,并主动邀请竞争对手加入平台打破原有零和博弈的局面。从创新的增量看,这打破了原有市场资源的局限,扩充了资源互通的圈子,不仅给予竞争对手信息和平台,也实现自身业务的可持续增长。
文章来源:贝壳找房-另类平台崛起
]]>docker pull mysql:5.7创建数据目录
mkdir /usr/local/dockerdata/mysql/confmkdir /usr/local/dockerdata/mysql/logsmkdir /usr/local/dockerdata/mysql/mysql设置编码为utf8mb4
cd /usr/local/dockerdata/mysql/conf
vi my.conf
需要手动添加上[client]和[mysql]。
[client]default-character-set = utf8mb4[mysql]default-character-set = utf8mb4[mysqld]character-set-client-handshake = FALSEcharacter-set-server = utf8mb4collation-server = utf8mb4_unicode_ciinit_connect='SET NAMES utf8mb4'default-time_zone = '+8:00' #默认时区配置sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION#设置数据库支持分组lower_case_table_names=1 #表名不区分大小写设置utf8编码只需要在 [mysqld]后添加 character_set_server=utf8docker run --restart always -p 3306:3306 --name mysql -v /usr/local/dockerdata/mysql/conf/my.conf:/etc/mysql/my.conf -v /usr/local/dockerdata/mysql/logs:/logs -v /usr/local/dockerdata/mysql/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7docker exec -it mysql bashmysql -uroot -p现在这样是无法在本地用工具登录访问的,现在要做两件事,一件事是将云服务器上的3306端口开放;另一件事是配置远程可以访问。
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'Admin123!' WITH GRANT OPTION;
flush privileges;
1.关闭防火墙
stemctl status firewalld 查看防火墙状态
service iptables stop 永久关闭防火墙
systemctl stop firewalld 暂时关闭防火墙
2.指定容器内部端口为3306
因为我容器中有其他应用的端口用到3306所以这里改了,若容器内没有把3306端口占用docker run --restart always -p 3306:3306 --name mysql -v /usr/local/dockerdata/mysql/conf/my.conf:/etc/mysql/my.conf -v /usr/local/dockerdata/mysql/logs:/logs -v /usr/local/dockerdata/mysql/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
3.进入 mysql.conf 把bind-address去掉注释同时改为0.0.0.0,并且加上port=3306。再重启mysql容器就可以了。
作者:陈煦缘
链接:https://www.jianshu.com/p/cb4ad4e824ff
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
企业主数据是用来描述企业核心业务实体的数据,比如客户、合作伙伴、员工、产品、物料单、账户等;它是具有高业务价值的、可以在企业内跨越各个业务部门被重复使用的数据,并且存在于多个异构的应用系统中。
企业主数据可以包括很多方面,除了常见的客户主数据之外,不同行业的客户还可能拥有其他各种类型的主数据,例如:对于电信行业客户而言,电信运营商提供的各种服务可以形成其产品主数据;对于航空业客户而言,航线、航班是其企业主数据的一种。对于某一个企业的不同业务部门,其主数据也不同,例如市场销售部门关心客户信息,产品研发部门关心产品编号、产品分类等产品信息,人事部门关心员工机构,部门层次关系等信息。
企业数据管理的内容及范畴通常包括交易数据、主数据以及元数据。
主数据管理是指一整套的用于生成和维护企业主数据的规范、技术和方案,以保证主数据的完整性、一致性和准确性。
集成、共享、数据质量、数据治理是主数据管理的四大要素,主数据管理要做的就是从企业的多个业务系统中整合最核心的、最需要共享的数据(主数据),集中进行数据的清洗和丰富,并且以服务的方式把统一的、完整的、准确的、具有权威性的主数据分发给全企业范围内需要使用这些数据的操作型应用和分析型应用,包括各个业务系统、业务流程和决策支持系统等。
主数据管理使得企业能够集中化管理数据,在分散的系统间保证主数据的一致性,改进数据合规性、快速部署新应用、充分了解客户、加速推出新产品的速度。从 IT 建设的角度,主数据管理可以增强 IT 结构的灵活性,构建覆盖整个企业范围内的数据管理基础和相应规范,并且更灵活地适应企业业务需求的变化。
以客户主数据为例,客户主数据是目前企业级客户普遍面临的一个问题,在大多数企业中,客户信息通常分散于 CRM 等各个业务系统中,而每个业务系统中都只有客户信息的片断,即不完整的客户信息,但却缺乏企业级的完整、统一的单一客户视图,结果导致企业不能完全了解客户,无法协调统一的市场行为,导致客户满意度下降,市场份额减少。因此,建立客户主数据系统的目的在于:
· 整合并存储所有业务系统和渠道的客户及潜在客户的信息:一方面从相关系统中抽取客户信息,并完成客户信息的清洗和整合工作,建立企业级的客户统一视图;另一方面,客户主数据管理系统将形成的统一客户信息以广播的形式同步到其他各个系统,从而确保客户信息的一致;
· 为相关的应用系统提供联机交易支持,提供客户信息的唯一访问入口点,为所有应用系统提供及时和全面的客户信息;服务于 OCRM 系统,充分利用数据的价值,在所有客户接触点上提供更多具有附加价值的服务;
· 实现 SOA 的体系结构:建立客户主数据系统之前,数据被锁定在每一个应用系统和流程中,建立主数据管理系统之后,数据从应用系统中被释放出来,并且被处理成为一组可重用的服务,被各个应用系统调用。
主数据管理系统与数据仓库系统是相辅相成的两个系统,但二者绝不是重复的,也不是互斥的。它们有很多共同之处:
但是,主数据管理系统和数据仓库 / 决策支持系统二者之间也存在很多不同:
虽然主数据管理系统和数据仓库系统异同共存,但是二者却有着紧密的联系,并且可以互为促进、互为补充。举例而言,数据仓库系统的分析结果可以作为衍生数据输入到 MDM 系统,从而使 MDM 系统能够更好地为操作型 CRM 系统服务。
在某些情况下,主数据管理系统和 ODS 系统可能容易被混淆,的确,从实时上来看,主数据管理系统和 ODS 系统存储的都是实时数据,但是二者存储的数据内容是全然不同的,主数据管理系统中不存储交易数据,比如银行客户的交易流水信息是不应该放在主数据管理系统中进行管理的,这与 MDM 与 ODS 的一个很大区别。举一个航空公司的例子,比如某个客户在电子商务网站上定了一张机票,产生一个订单,然后他又通过呼叫中心要求改签,这个场景中,两个系统之间要实现客户信息和订单信息的共享,其中客户信息共享通过MDM 系统来实现,而订单信息则需要采用 ODS 或其它手段进行共享,我们是不推荐把此类信息交由 MDM 系统来管理的。
主数据管理的典型应用有客户管理与产品管理,主数据管理在金融行业典型的应用就是企业级客户信息整合系统(Enterprise Custome Information Facility,简称ECIF),其目标是整合全行现有业务系统中的客户信息,保留客户的最新信息,为各应用系统提供完整的、共享的、一致的客户信息,建立企业级客户单一视图,在全行范围内为客户信息的使用和管理提供服务,为全行从“以产品为中心”的业务流程向“以客户为中心”的业务流程整合提供强有力的支持。
元数据管理作为企业数据资产管理中的一项核心技术,也将为主数据管理提供有力支撑,具体表现在:
元数据(Meta Data)是关于数据的数据,当人们描述现实世界的现象时,就会产生抽象信息,这些抽象信息便可以看作是元数据,元数据主要用来描述数据的上下文信息。通俗的来讲,假若图书馆的每本书中的内容是数据的话,那么找到每本书的索引则是元数据,元数据之所以有其它方法无法比拟的优势,就在于它可以帮助人们更好的理解数据,发现和描述数据的来龙去脉,特别是那些即将要从OLTP系统上升到DW/BI体系建设的企业,元数据可以帮他们形成清晰直观的数据流图,元数据是数据管控的基本手段。
元数据是为了提升共享、重新获取和理解企业信息资产的水平,元数据是企业信息管理的润滑剂,不对元数据进行管理或管理不得当,信息将被丢失或处于隐匿状态而难以被用户使用,数据集成将十分昂贵,不能对业务进行有效支撑。终端用户要识别相关的信息将十分困难,最终用户将失去对数据的信任。
元数据管理的范围将涵括数据产生、数据存储、数据加工和展现等各个环节的数据描述信息,帮助用户理解数据来龙去脉、关系及相关属性。按其描述对象的不同可以划分为三类元数据:技术元数据、业务元数据和管理元数据。这三种元数据的具体描述如下:
元数据管理范围的不应仅仅局限于企业数据仓库、数据集市以及管理分类应用的数据,还应该将企业的业务系统的元数据纳入进来就行统一的管理,真正做到从源头对元数据进行管理,作为对数据的完整生命周期进行管理。
1) 数据地图
数据地图展现是以拓扑图的形式对数据系统的各类数据实体、数据处理过程元数据进行分层次的图形化展现,并通过不同层次的图形展现粒度控制,满足开发、运维或者业务上不同应用场景的图形查询和辅助分析需要。
2) 元数据分析
血缘分析(也称血统分析)是指从某一实体出发,往回追溯其处理过程,直到数据系统的数据源接口。对于不同类型的实体,其涉及的转换过程可能有不同类型,如:对于底层仓库实体,涉及的是ETL处理过程;而对于仓库汇总表,可能既涉及ETL处理过程,又涉及仓库汇总处理过程;而对于指标,则除了上面的处理过程,还涉及指标生成的处理过程。数据源接口实体由源系统提供,作为数据系统的数据输入,其它的数据实体都经过了一个或多个不同类型的处理过程。血缘分析正是提供了这样一种功能,可以让使用者根据需要了解不同的处理过程,每个处理过程具体做什么,需要什么样的输入,又产生什么样的输出。
影响分析是指从某一实体出发,寻找依赖该实体的处理过程实体或其他实体。如果需要可以采用递归方式寻找所有的依赖过程实体或其他实体。该功能支持当某些实体发生变化或者需要修改时,评估实体影响范围。
实体关联分析是从某一实体关联的其它实体和其参与的处理过程两个角度来查看具体数据的使用情况,形成一张实体和所参与处理过程的网络,从而进一步了解该实体的重要程度。本功能可以用来支撑需求变更影响评估的应用.
实体差异分析是对元数据的不同实体进行检查,用图形和表格的形式展现它们之间的差异,包括名字、属性及数据血缘和对系统其他部分影响的差异等,在数据系统中存在许多类似的实体。这些实体(如数据表)可能只有名字上或者是在属性中存在微小的差异,甚至有部分属性名字都相同,但处于不同的应用中。由于各种原因,这些微小的差异直接影响了数据统计结果,数据系统需要清楚了解这些差异。本功能有助于进一步统一统计口径,评估近似实体的差异
指标一致性分析是指用图形化的方式来分析比较两个指标的数据流图是否一致,从而了解指标计算过程是否一致。该功能是指标血缘分析的一种具体应用。指标一致性分析可以帮助用户清楚地了解到将要比较的两个指标在经营分析数据流图中各阶段所涉及的数据对象和转换关系是否一致,帮助用户更好地了解指标的来龙去脉,清楚理解分布在不同部门且名称相同的指标之间的差异,从而提高用户对指标值的信任。
3) 辅助应用优化
元数据对数据系统的数据、数据加工过程以及数据间的关系提供了准确的描述,利用血缘分析、影响分析和实体关联分析等元数据分析功能,可以识别与系统应用相关的技术资源,结合应用生命周期管理过程,辅助进行数据系统的应用优化
4) 辅助安全管理
企业数据平台所存储的数据和提供的各类分析应用,涉及到公司经营方面的各类敏感信息。因此在数据系统建设过程中,须采用全面的安全管理机制和措施来保障系统的数据安全。
数据系统安全管理模块负责数据系统的数据敏感度、客户隐私信息和各环节审计日志记录管理,对数据系统的数据访问和功能使用进行有效监控。为实现数据系统对敏感数据和客户隐私信息的访问控制,进一步实现权限细化,安全管理模块应以元数据为依据,由元数据管理模块提供敏感数据定义和客户隐私信息定义,辅助安全管理模块完成相关安全管控操作。
5) 基于元数据的开发管理
数据系统项目开发的主要环节包括:需求分析、设计、开发、测试和上线。开发管理应用可以提供相应的功能,对以上各环节的工作流程、相关资源、规则约束、输入输出信息等提供管理和支持。
]]>6年前,我想写微信公众号,写了两个月,粉丝一直增长很慢,最终放弃了,而当时一起做公众号认识的朋友,现在已有人做到几十万粉丝,靠公众号实现自由职业。
4年前,我想学python,做数据分析,记着那个国庆7天假,没有出门,在家学了7天python,坚持了两周,虽然能简单编写几个程序,但总感觉对目前工作没有什么价值,放弃了。
1年前,我想学PPT,学了一个月,因为工作突然忙了起来,然后就忘记了。
我有时幻想,假如这些事情,有一件我能坚持下来,保持每天学两个小时,到现在也能有些成效,至少比现在的我要好吧。
这些事情,我为什么没有坚持住?
因为我太想短期就看到成效了,一旦短期看不到效果,动机下降,自然很快就放弃了。
很多事情,我们为什么很容易放弃?
因为现实打击了我们,我们没有像我们想象的那样飞速进步,原本信誓旦旦想达到目标的决心开始衰退,到最后,我们完全停了下来,不再重新开始了。
人们往往高估自己在短时间内做成事情的能力,而又往往低估自己可以在长时间内做成事情的能力。
当然,这些经历也并不是全没用处,至少让我发现我不适合做什么,我身上有那些弱点,该怎么去克服。
毕竟人生有些错误,有些弯路,总是是无法避免的,总是要去经历的。年轻的时候,把能犯的错误都犯了,这时的错误,成本低,船小好掉头。
所以,30岁的时候(其实,25岁的时候就该开始思考了,好吧,忘记了),应该时刻思考,35岁的自己想要成为怎样的人,然后从现在就去做,做好投资5年的准备。
当你真的用心去做的时候,你会发现,可能几个月后,一年后就会产生效果。如果没有产生效果,记得坚持下去。
那些杰出人物,都是通过年复一年的练习,在漫长而艰苦的过程中一步一步改进,终于练就了他们杰出的能力。
对大多数人而言,没有捷径可走。
我现在一直反思,很多事情,为什么我没有做成。因为我总想找捷径,总想短期就获得回报。直到最终发现,总想找捷径才是最大的弯路。
做人做事,眼光放长远一些,以五年为一个单位来规划自己的学习。
拿CPA、CFA证书来说,能考下来的人,基本都需要3—5年的时间,当然,更多的人是坚持一年就放弃了。
拿写作来说,现在那些大V,大多都默默的写了好几年,才有现在的成就。
30岁,开始做一件事情,不能因为目前每天没有回报就不去做。而应该思考的是,坚持做5年,有没有效果。
这也是为什么对18岁的人而言,上大学、读研是最好的投资,因为投资时间足够长,且短期看不到回报,基本上都是4年甚至6、7年才能看到回报。
对30岁年龄的人更是一样,尽快找到那个让你投资5年的事情,然后专注去做,坚持下去。
虽然我已经过了30岁,但我现在尝试做的一些事情,都会考虑坚持5年。
就投资而言,我现在买的一些股票和基金,基本都做好了持股5年以上的准备。
]]>
简单来说,个人收款码无法再用于商业用途。

这个时候就显得聚合支付的重要性了。
聚合支付,也称“融合支付”,是指只从事“支付、结算、清算”服务之外的“支付服务”。简单来说就是把微信支付的收款码和支付宝支付收款码聚合在一起形成一个二维码,微信扫描二维码就会跳转到微信支付,而支付宝扫码就会跳转到支付宝支付。
2014年开始,移动支付逐渐形成多主体竞争局面,二维码扣扫行为出现,多样化的支付场景催生了聚合支付。
2016年支付清算协会向支付机构下发《条码支付业务规范》(征求意见稿),二维码支付获监管机构认可,此时支付宝及微信加速线下渗透,聚合支付产品更为多样,且市场推广都较高,聚合支付进入规模爆发期,资本纷纷入局。
据零壹智库不完全统计,已备案机构在2015年和2016年共发生融资43笔,披露的融资总额超87亿元。
2017年下半年开始,监管趋严,但也意味着聚合支付终被监管机构认可。2018年至今,聚合支付加速洗牌,2020年备案制一出,行业迎来大清洗,进入合规化良性发展时期。
图1:聚合支付发展历程

资料来源:公开资料整理,零壹智库
据易观千帆数据显示,2014年我国聚合支付市场交易规模仅为0.1万亿,2015年飙升到了1.5万亿,同比增长1400%,这一时期O2O的发展已经到了繁荣且多样化阶段,移动互联网的兴起,移动支付多样化,场景碎片化,给聚合支付带来了机遇。
2016年聚合支付市场交易规模继续增长到8万亿,同比增长433.3%,增速仍维持高位。到2017年监管趋严,增速显著下降,但移动支付规模持续增长,经过多年的发展,聚合支付渗透率也在不断提高,聚合支付交易规模依然保持增长态势。
图2:聚合支付市场交易规模

数据来源:易观千帆,零壹智库
前面也说了由于法律的实施,个人收款码无法用于商业用途,同时在平时商户收款过程中,很多时候会有三个码,一个微信支付的收款码,一个支付宝支付的收款码,一个银联支付的收款码,不美观且不方便,用一个收款码代替三个收款码,更美观且更智能。
零方聚合支付系统,可以完美解决聚合支付的痛点,支持各个银行。包含了微信支付和支付宝支付。商户只要在后台申请二维码之后,就可以获得一个聚合支付后的二维码,可以完美的聚合在一起。通过微信和支付宝服务商的API支付接口,来聚合微信和支付宝,达到便捷的效果。
当商户收款后,接口服务商会首先收到款,然后可以通过提现接口把钱提现给商户,一步到位。
同时推出代理商功能,可以招聘代理帮你推广,推广的佣金和支付的手续费可以自定义,可以让代理员地推,每推一个商户,只要有收款,都会产生佣金。实现躺着赚钱!
废话不多说,来看看系统功能清单。

首先是首页大盘,可以看到一切统计数据。

下图分别是微信和支付宝扫码之后的样式。

添加代理商,帮你推广二维码,躺着赚钱!

如何申请聚合支付二维码?
下图是商户的注册功能和申请二维码的功能,可以点击创建收款码,填写收款页面的名称,即付款给“xxx”,即可创建聚合支付收款二维码。

手机也可以登录商户后台

支付后,还可以配置广告,还可以跳转。

订单详情

支付后的广告配置。

支持配置支付二维码样式

可以配置聚合支付收款设备,云喇叭,会有语音收款提示。

商户交易流水分润
微信、支付宝、银联为了推广自己的产品,会给聚合支付服务商一个较低的交易流水费率A(比如0.38%)。若是商户自己去申请收款码,则交易流水费率B会高一些。
费率A减去费率B(比如0.6%),就是聚合支付服务商能赚的差价(0.22%)。这个差价其实也不高,好过没有。如果签约的商户数量多、交易订单多,那么就可以细水长流赚得盆满钵满。
产品研发、升级和维护
服务商可以为客户提供技术支持,比如开发收银软件、扫码点餐小程序
提供增值服务
比如:顾客支付成功后就成为店铺会员、支付成功页面展示广告、门店粉丝经营、门店优惠资讯推荐等。
毕竟做聚合支付的,不能只做单一的支付工具,否则无法适应市场竞争,只有满足商户多元化的需求,才能让企业续命和做大做强。
硬件设备差价
商户在收款时,除了使用二维码贴纸这个情况不需要用到硬件设备,其余都是需要的。比如,扫码枪、小白盒、蓝牙音箱、刷脸机等等。
这些设备有些可以由商家自行网购,有些必须从服务商购买,因为个别设备是需要对接技术接口才能使用的,而不是服务商黑心。举个例子,比如刷脸机,因为刷脸需要人脸识别,而人脸识别是非常非常敏感的个人隐私,因此必须要使用微信或支付宝生产的刷脸机,以保证人脸隐私安全。同时,微信和支付宝也会有补贴。
代理商推广流水分润
代理商帮助商户接入聚合支付收款,随后商户只要使用这个码进行收款,代理商便可获得交易流水分润。这是一项长期的收入,也就是前面所说到的管道收益。简单介绍一下管道收益的意思:即使你不在工作,没有在付出,也能获得收入,得到回报。举个例子,假设小王加入了并成为代理商,他帮助一个商户接入了聚合支付,这个商户产生了1万元的交易流水,代理商至少可以获得15-20甚至更高的分润。那么如果代理商有100个商户,一天就可获得分润1500-2000元甚至更多,这只是打个最基础的比方,假设代理商有200、300、或者500以上的商户数,那么收入就会更高。
Zsh 完全兼容 bash,支持强大的智能命令补全功能,支持大量的界面主题和插件,功能和效率都极大的增强和提高。
在 Unix/Linux 的世界里,人机交互的工具就是 shell 了,由于各个发行版的系统默认 shell 都是 bash,所以 bash 的知名度是最高的。其实还有一款效率远超 bash 的 shell,叫做 zsh。zsh 同样历史悠久,发布于 1990 年,只比 bash 晚一年,而且 zsh 相比 bash 有很多功能上、性能上的改进。想要了解两者具体有那些差别,可以查看这篇文章。
zsh 虽然很优秀,但在初期配置上太过繁琐,流行率一直不高(SYSIN),直到 github 上 oh-my-zsh 项目的出现,使大家使用 zsh 的便捷性大大提高,从 macOS Catalina 版开始默认使用 zsh,使得 zsh 得到了更加广泛的关注。
很重要的一点,zsh 完全兼容 bash,如果你的 bash 脚本开头是以 !#/bin/bash 的话,在 zsh 下还是完全兼容 bash 的。
Mac 系统内置了几种 shell:
cat /etc/shells# List of acceptable shells for chpass(1).# Ftpd will not allow users to connect who are not using# one of these shells./bin/bash/bin/csh/bin/dash/bin/ksh/bin/sh/bin/tcsh/bin/zsh从 macOS Catalina 版开始,您的 Mac 将使用 zsh 作为默认登录 Shell 和交互式 Shell。您还可以在较低版本的 macOS 中将 zsh 设置为默认 Shell。
chsh -s /bin/zshsh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"下载 oh-my-zsh
git clone https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh备份原有 ~/.zshrc(如果有)
cp ~/.zshrc ~/.zshrc.bak从模板创建 zsh 配置文件
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc主题样式这里查看。
vi ~/.zshrc# 找到ZSH_THEME="robbyrussell"# 例如修改为ZSH_THEME="ys"快速修改:
sed -i '/^ZSH_THEME=.*/c ZSH_THEME="ys"' ~/.zshrc注:主题文件在 ~/.oh-my-zsh/themes 目录
默认 oh-my-zsh 命令自动补全功能如下:
输入 cd 按 tab 键,目录将自动列出,在按 tab 可以切换
要访问 /usr/local/bin 这个长路径,只需要 cd /u/l/b 按 tab 键自动补全
要访问 Desktop 文件夹,只需要 cd de 按 tab 键自动补全,或者查看 README.md,只需要 cat rea 自动更正补全
输入 kubectl 按 tab 键即可看到可用命令
输入 kill 按 tab 键会自动显示出进程的 process id
小技巧:
可以忽略 cd 命令,输入 .. 或者 ... 和当前目录名都可以跳转。
上述功能不需要额外的插件。
另外还有一些插件来增强命令补全,可根据需要启用:
zsh-autosuggestions:作用是根据历史输入命令的记录即时的提示,然后按键盘 → 即可补全。
(个人不推荐这个插件。)
git clone https://github.com/sangrealest/zsh-autosuggestions.git ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions编辑 ~/.zshrc,找到 plugins=(git) 这一行,修改为:
plugins=(git zsh-autosuggestions)
退出终端重新打开生效。
增强的实时自动命令补全插件:Incremental completion on zsh
(该插件对性能似乎有一点点影响,请根据需要启用(SYSIN)。)
作用如图:

mkdir $ZSH_CUSTOM/plugins/incrcurl -fsSL https://mimosa-pudica.net/src/incr-0.2.zsh -o $ZSH_CUSTOM/plugins/incr/incr.zshecho 'source $ZSH_CUSTOM/plugins/incr/incr.zsh' >> ~/.zshrcsource ~/.zshrc我们看下 git 的别名:
cat ~/.oh-my-zsh/plugins/git/git.plugin.zsh......alias g='git'alias ga='git add'alias gaa='git add --all'alias gapa='git add --patch'alias gau='git add --update'alias gav='git add --verbose'alias gap='git apply'alias gapt='git apply --3way'......自定义别名,在~/.zshrc中,最下面直接写即可。
# Example aliases# alias zshconfig="mate ~/.zshrc"# alias ohmyzsh="mate ~/.oh-my-zsh"alias ll='ls -lahFT'echo 'alias ll="ls -lahFT"' >> ~/.zshrc更强大的 alias 命令,比如下面命令,当你在 zsh 环境下输入 hello.py 即可直接用 vim 打开文件编辑,一个 tgz 的文件即可自动解压缩。
alias -s py=vimalias -s conf=vimalias -s tgz='tar zxvf'插件名称:zsh-syntax-highlighting
作用:命令错误会显示红色,直到你输入正确才会变绿色,另外路径正确会显示下划线。
安装:
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting##国内:#git clone https://gitee.com/null_454_5218/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting配置启用插件:
编辑 ~/.zshrc
以下部分加入插件的名字
plugins=( [plugins…] zsh-syntax-highlighting)
source ~/.zshrc生效。
$ hexo new "My New Post"More info: Writing
$ hexo serverMore info: Server
$ hexo generateMore info: Generating
$ hexo deployMore info: Deployment
]]>此文是简单介绍使用kos2进行项目的基本搭建以及基于Docker打包发布镜像
因为此框架是基于node.js的,安装node是必须的进行的第一步,这里不再详细介绍
安装koa(我们使用淘宝镜像进行安装)
npm install koa --save安装koa2项目生成器并创建项目
npm install koa-generator -gkoa2 myProcd myPronpm installkoa2 myPro用来生成项目的基本的项目架构
启动项目服务
npm start
到这里,项目初始化成功。
Dockerfile是由一系列命令和参数构成的脚本,一个Dockerfile里面包含了构建整个image的完整命令。Docker通过docker build执行Dockerfile中的一系列命令自动构建image.
表示过滤该类型的文件。类似git的.gitignore
在.dockerignore文件里面写入代码:
# Logslogs*.lognpm-debug.log*# Runtime datapids*.pid*.seed# Directory for instrumented libs generated by jscoverage/JSCoverlib-cov# Coverage directory used by tools like istanbulcoverage# nyc test coverage.nyc_output# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files).grunt# node-waf configuration.lock-wscript# Compiled binary addons (http://nodejs.org/api/addons.html)build/Release# Dependency directoriesnode_modulesjspm_packages# Optional npm cache directory.npm# Optional REPL history.node_repl_history.idea.node_modulesnode_modules.vscode在Dockerfile文件中写入一下代码:
#制定node镜像的版本FROM node:12-alpine#声明作者MAINTAINER FeynmanLoo#移动当前目录下面的文件到app目录下ADD . /app/#进入到app目录下面,类似cdWORKDIR /app#安装依赖RUN npm install#对外暴露的端口EXPOSE 3000#程序启动脚本CMD ["npm", "start"]构建镜像
使用build命令构造镜像,注意后面那个.不能少。
docker build -t docker_demo .robin:docker_demo robin$ docker build -t docker_demo .Sending build context to Docker daemon 39.94kBStep 1/7 : FROM node:8.9-alpine---> 406f227b21f5Step 2/7 : MAINTAINER robin---> Using cache---> 78d6cdbcfee2Step 3/7 : ADD . /app/---> 2cb30678612dStep 4/7 : WORKDIR /appRemoving intermediate container e51377081039---> c2b7d0f37d2dStep 5/7 : RUN npm install---> Running in da0c3946ca8dnpm notice created a lockfile as package-lock.json. You should commit this file.added 38 packages in 3.323sRemoving intermediate container da0c3946ca8d---> eecee87f10e2Step 6/7 : EXPOSE 3000---> Running in f3973cc168a4Removing intermediate container f3973cc168a4---> 2671a4c6deb4Step 7/7 : CMD ["npm", "start"]---> Running in dec529f754aaRemoving intermediate container dec529f754aa---> 6ec73793d353Successfully built 6ec73793d353Successfully tagged docker_demo:latest
等待镜像构造完成。然后使用 images命令查看镜像。

此时可以看到images已经构造完成。现在开始启动images,并测试。
#启动镜像 -d表示后台执行,-p 9000:3000表示指定本地的9000端口隐射到容器内的3000端口,docker_demo为镜像名称docker run -d -p 9000:3000 docker_demo#查看容器docker ps

此时浏览器打开http://localhost:9000/,如果如图所示。表示容器运行正常。

如果此时本地无法打开。可以使用log命令查看日志。根据日志修改对应出现的对方。
到这里,整个基于Koa2框架的项目搭建及Docker部署,到这里结束。
]]>二倍均值法(公平版)
发出一个固定金额的红包,由若干个人来抢,需要满足哪些规则?
假设剩余红包金额为M,剩余人数为N,那么有如下公式:
每次抢到的金额 = 随机区间 (0, M / N × 2)
这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。举个例子:
假设有10个人,红包总额100元。100/10×2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。90/9×2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。假设第二个人随机到10元,那么剩余金额是90-10 = 80 元。80/8×2 = 20, 所以第三个人的随机范围同样是(0,20 ),平均可以抢到10元。以此类推,每一次随机范围的均值是相等的。
static void Main(string[] args){ for (int i = 0; i < 10; i++) { var list = DivideRedPackage(100* 100, 10); Console.WriteLine(string.Join(",", list)); int count = 0; foreach (var item in list) { count += item; } Console.WriteLine(count); } System.Console.ReadKey();}/// <summary>/// 产生红包数组/// </summary>/// <param name="cashCount">红包总金额,单位分</param>/// <param name="peopleNumber">红包人数</param>/// <returns></returns>static List<int> DivideRedPackage(int cashCount, int peopleNumber){ List<int> redPackageList = new List<int>(); if (cashCount <= peopleNumber) { for (int i = 0; i < cashCount; i++) { redPackageList.Add(1); } return redPackageList; } Random random = new Random(GetRandomSeed()); int restCash = cashCount, restPeople = peopleNumber; for (int i = 0; i < peopleNumber - 1; i++) { var cash = random.Next(1, restCash / restPeople * 2); restCash -= cash; restPeople--; redPackageList.Add(cash); } redPackageList.Add(restCash); return redPackageList;}例如,产生的结果如下:
1960,189,234,1763,1211,1236,1340,53,1652,362100001032,1380,456,1885,608,857,1541,452,1273,51610000976,955,749,936,1990,1177,781,325,527,158410000794,935,272,216,2034,522,455,2313,2260,199100001376,1539,1292,614,443,1874,889,544,821,60810000914,15,877,1738,604,932,321,983,3106,51010000659,791,800,1066,788,908,991,2473,495,1029100001256,733,1385,667,1192,1237,455,105,2121,849100001941,1173,567,1280,1558,618,183,644,133,1903100001313,735,1198,1173,1288,522,1879,1155,59,67810000上述示例中需注意,Random是一个伪随机数生成器,在大多数 Windows 系统上,Random 类 (System) | Microsoft Docs 15 毫秒内创建的对象可能具有相同的种子值。
因此,如果New Random在循环中使用,就必须提供随机的种子值。
我们可以使用RNGCryptoServiceProvider 类 (System.Security.Cryptography) | Microsoft Docs类产生随机树种子。
具体代码如下:
/// <summary>/// 产生加密的随机数种子值/// </summary>/// <returns></returns>static int GetRandomSeed(){ byte[] bytes = new byte[4]; System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); rng.GetBytes(bytes); return BitConverter.ToInt32(bytes, 0);}线段切割法(手速版)
算法思路如下:
线段分割法就是把红包总金额想象成一条线段,而每个人抢到的金额,则是这条主线段所拆分出的子线段。
当N个人一起抢红包的时候,就需要确定N-1个切割点。
因此,当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。
随机的范围区间是(1, M)。当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。
需要注意一下两点:
具体代码如下:
class Program{ static List<int> DivideRedPackage(int cashCount, int peopleNumber) { List<int> redPackageList = new List<int>(); if (cashCount <= peopleNumber) { for (int i = 0; i < cashCount; i++) { redPackageList.Add(1); } return redPackageList; } Random random = new Random(GetRandomSeed()); int restPeople = peopleNumber; List<int> lineList = new List<int>(); while (restPeople > 1) { var line = random.Next(1, cashCount); if (lineList.Contains(line) == false) { lineList.Add(line); restPeople--; } } lineList.Sort(); redPackageList.Add(lineList[0]); for (int i = 0; i < peopleNumber - 2; i++) { var cash = lineList[i + 1] - lineList[i]; redPackageList.Add(cash); } redPackageList.Add(cashCount - lineList[lineList.Count - 1]); return redPackageList; } static int GetRandomSeed() { byte[] bytes = new byte[4]; System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); rng.GetBytes(bytes); return BitConverter.ToInt32(bytes, 0); } static void Main(string[] args) { for (int i = 0; i < 10; i++) { var list = DivideRedPackage(100 * 100, 10); Console.WriteLine(string.Join(",", list)); int count = 0; foreach (var item in list) { count += item; } Console.WriteLine(count); } System.Console.ReadKey(); }}输出结果如下:
409,2233,1843,546,983,679,1621,460,369,8571000050,472,281,603,577,1007,3929,38,591,245210000194,1241,675,209,3507,1714,1199,596,313,352100002127,578,16,2413,1332,586,91,260,465,2132100001015,1421,963,626,3031,955,171,1112,60,64610000118,352,1062,1128,8,374,1879,1707,1755,1617100002805,592,391,90,1468,392,2201,40,1426,59510000145,251,2910,59,1065,235,2761,997,1564,1310000814,1725,1886,39,696,202,44,992,3099,50310000828,1281,2402,579,380,2246,154,855,564,71110000]]>