月份:2020年4月

CoreDNS解析异常记录

CoreDNS解析异常记录 

异常情况:集群是用kubespray部署的4个worknode,coredns默认部署2个deployment。今天发现部署了coredns的node上的pod正常解析内部域名,而另外2个未运行coredns的node却无法解析。

 配置文件:

 下图中我们看到coredns2个pod分别在node1与node2上,只要分配到这2节点上的deployment都可以正常解析。

其他节点无法解析:

 处理过程:

正常来说所有的pod都是通过coredns来进行集群内域名解析的,我也搞不清楚为啥其他两个node没有跑coredns则就无法解析后面再研究。所以我临时的解决方法是扩容coredns让每个node都跑。

1、修改 ConfigMap 中的 dns-autoscaler(coredns自动扩容保证高可用)

kubectl edit configmap dns-autoscaler --namespace=kube-system

2、修改key:linear

  • coresPerReplica: 按照核心数目来计算副本集(replicas = cores / coresPerReplica)
  • nodesPerReplica:按照节点数目来计算副本集(replicas = nodes / nodesPerReplica)
  • min:最小副本数(默认为2,我先有4个节点改为4)
  • max:最大副本数
  • preventSinglePointFailure:防止单点故障

公式

replicas = max( ceil( cores × 1/coresPerReplica ) , ceil( nodes × 1/nodesPerReplica ) )

 

 

 

[Abp vNext 入坑分享] – 2.简化项目结构

一、简要说明

本篇文章根据我自己的需要对项目结果进行简化,让项目结构更符合我自己的要求,同时让项目跑起来。仅供参考

二、具体步骤

2.1卸载掉对我来说目前使用不上的项目,identityserver,mongodb,httpapi.client,以及对应的test项目

 

 

 

 

2.2删除掉下图红框中的包与类文件,由于我后期会创建一个独立的项目来做migrations,所以不需要在HttpApi.Host里面直接使用EF相关的操作,而且个人认为这样会模糊掉abpvnext的层级,混乱了层级的职责。

 

 

 

上图的报错都是由于我删除了相关的包引起的,因此跳转到相关文件中,把所有的报错行,全部删除。同时由于我没有启用redis的服务,所以要把下图的redis服务也先行注释掉。

2.3将启动模式修改成:如下图,同时把原来输出日志到文件的模式修改成:console(),以便在控制台时能很直观的看到Log。调试项目,则可以启动成功了。

 

 

 

 

 

2.4在src下面增加DbMigrations类库,注意:此处只能选择.netcore类型的类库,不能是只属于netstandard这样会导致无法使用。创建后的样子如下,这样就可以使用此项目进行migration操作了,且不会影响主线代码。

 

 

 

1.关于DbM_LearnDbContext这个类,如果你能保证整个项目的所有开发人员都只能使用codefirst进行开发的话,可以直接继承主的LearnDbContext,这样LearnDbContext的所有DbSet都会得到继承,则不需要再重复写dbset。

2.若是codefirstdbfirst混用的情况,则不要继承

3.单纯dbfirst的话,此项目可以去掉

Unity ML-agents 一、初次尝试

前言

曾在高二寒假的时候,跟表哥在外面玩,当时他问我有没有想过以后要做什么,我愣了一下,回答不上来。是的,从没想过以后要做什么,只是一直在完成学校、老师安排的任务,于是那之后半年,我一直在思考,大学要学什么。在大二下期中之后,我觉得自己还是对游戏更感兴趣,便想到以后想做游戏。于是,高考后填志愿,填的都是计算机专业。在大一的时候,自学了一段时间的 Unity,到大二在实验室接触强化学习之后,就想着用 RL 来做游戏 AI,但后来一直在做数据挖掘相关的内容,基本上以参加比赛为主。直到去年参加上海的谷歌开发者节,了解到 ML-agents 之后,就十分的想尝试。

然后由于疫情,直到现在还在家里咸鱼,前段时间一边咸鱼一边投简历,奈何自己水平太低又偏偏想投算法岗,直到现在也没有几次面试机会 orz。最近就想继续当年未完成的 Unity 的学习,顺便学习 ML-agents,回到原点,重新出发。

初试 ML-agents

环境配置

既然要尝试,肯定免不了环境配置
目前我的环境为:

  1. Win10
  2. Tensorflow 2.0
  3. ML-agents 0.15.0
  4. Unity 2019.3.1f1
    关于 Tensorflow 2.0 的安装,参考我之前的博客,Unity 的安装,推荐先下载 Unity Hub,通过 Unity Hub 可以管理不同版本的 Unity,下载戳这里

ML-agents 的安装有两种方式
一是直接 pip

pip install mlagents

二是从官方 github 中 clone 整个项目,然后 cd 到目录中

pip install -e ./ml-agents-envs
pip install -e ./ml-agents

到这里,环境配置就算完成了

跑个 Demo 先吧

在 Unity Hub 中导入刚刚从 github 上 clone 的项目

选择 Unity 版本之后打开

然后打开 3DBall 这个场景

点击运行的话可以直接看到效果

接着,开始尝试自己训练,打开命令行,进入到之前 clone 下来的项目目录中,并在目录中创建一个名为 summaries 的文件夹

然后输入

mlagents-learn config/trainer_config.yaml --run-id=test --train

在出现图标之后,切换到 Unity 运行项目,就可以看见开始训练了

可以看出来一开的效果是很差的,完全控制不好,这次训练大概训练 40w+ 步,到后面就很稳了

注:项目目录中尽量不要出现中文,下午在尝试的时候一直报错,后来更改目录之后发现成功了,不清楚是不是路径中有中文的原因
报错内容

 "The Unity environment took too long to respond. Make sure that :\n"
mlagents_envs.exception.UnityTimeOutException: The Unity environment took too long to respond. Make sure that :
         The environment does not need user interaction to launch
         The Agents are linked to the appropriate Brains
         The environment and the Python interface have compatible versions.

训练完成后,我们可以在 models 目录下看到刚刚训练好的模型,重命名一下,然后把模型拖到 TFModel 目录中

接着打开 prefabs 中的 3DBall,点击其中的 agent,然后将 Behavior 改成我们刚刚拖到 TFModel 目录中的模型

点击运行,发现控制的十分稳定,跟一开始差不多。至此,初试完毕。

小节

这里我们安装配置了 ML-agnets 的相关环境,并运行了个 Demo 来熟悉了遍流程,后面将开始尝试自己搭建环境,训练 AI,也不知道能不能捣鼓出来,23333。

kafka消息分区机制原理

背景

kafka如何支撑海量消息的集中写入?

答案就是消息分区。

核心思想是:负载均衡,采用合适的分区策略把消息写到不同的broker上的分区中;

其它的产品中有类似的思想。

比如monogodb, es 里面叫做 shard;   hbase叫region,  cassdra叫vnode;

消息的三层结构

如下图:

即  topic -> partition -> message ;

topic是逻辑上的消息容器;

partition实际承载消息,分布在不同的kafka的broke上;

message即具体的消息。

分区策略

round-robin轮询

消息按照分区挨个的写。

randomness随机分区

随机的找一个分区写入,代码如下:

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());

key

相同的key的消息写到固定的分区中

自定义分区

必须完成两步:

1,自定义分区实现类,需要实现org.apache.kafka.clients.producer.Partitioner接口。

主要是实现下面的方法:

int partition(String topic, Object key, byte[] keyBytes, 
              Object value, byte[] valueBytes, Cluster cluster);

比如按照区域分区。

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return partitions.stream().filter(p -> isSouth(p.leader().host()))
    .map(PartitionInfo::partition).findAny().get();

2,显示配置生产者端的参数partitioner.class为具体的类

系统默认:如果消息有key,按照key分区策略,否则按照轮询策略。

小结

kafka的分区实现消息的高吞吐量的主要依托,主要是实现了写的负载均衡。可以指定各种负载均衡算法。
负载均衡算法非常重要,需要极力避免消息分区不均的情况,可能给消费者带来性能瓶颈。

小结如下:

原创不易,点赞关注支持一下吧!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。
我会持续分享Java软件编程知识和程序员发展职业之路,欢迎关注,我整理了这些年编程学习的各种资源,关注公众号‘李福春持续输出’,发送’学习资料’分享给你!

每个优秀程序员都应遵循的代码原则和规范

本文由葡萄城技术团队原创并首发

转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

 

什么是优秀的程序员?

首先我们会先提出这个问题,如果你向10个人问这个问题,尽管可能答案不同,但是少有一点应该是一致的。而对我个人而言,一个优秀的程序员应该是一个能够充分理解需求,并能提出可行性解决方案通过团队协作向最终用户展示成果。而说到团队协作,就涉及到代码的可维护性,那么你该如何管理庞大的代码库?如果放任团队成员提交随意的代码,那么在项目中无论在bug修复还是新增功能,都将很难完成。

如果想要实现可维护这个目标,那么团队中的每个成员都应该保证提交整洁且可维护的代码。那么您应该让你的团队成员遵守一定的编码原则。遵守这些原则,可以使你和其他人的协作变得更容易。所以团队成员应该遵循什么样的规则呢?

童子军原则

童子军是美国社会针对未成年人的一种教育实践制度,加入童子军的小朋友都要学习并遵守一些规则,然后获得各种各样的勋章。其中一条规则是离开宿营地前进行清扫活动的原则,简洁明了:

Leave the campground cleaner than you found it.

假设某个小朋友来到某个宿营地,不幸发现地上有两处垃圾,然后他自己在接下来的日常活动中也制造了一处垃圾。那么当他离开时,不仅要清理掉自己的垃圾,还有处理早先小朋友留下的两处垃圾。而不是去寻找是谁丢的。应把注意力放在为下一个露营者创造更好的环境。

这个原则放到软件生产中则意味着让check in比check out时更整洁,至少不要让代码变得更糟糕。

(图片来源于网络)

避免重复

尽量在项目的开发过程中减少产出重复的代码、方法和类,多数的设计模式根本目的是为了减少代码重复,尽可能将重复使用的代码抽象封装,是提高代码的可重用性和可维护性的最佳方式之一。

功能独立

这里功能独立的意思是指,函数或方法尽可能简单,功能尽可能独立。

换句话来讲就是,一个方法最好只做一件事,如果你觉得你的代码过于复杂,该怎么做?抽方法。

初级程序员最常犯的错误就是在一个方法中包含了很多种要做的工作,这可能会在软件的生命周期带来灾难。

简单易懂的代码比“聪明“的代码更好

程序员作为社会中最聪明的群体之一,往往在写代码时也会产出一些炫技的代码块,这部分代码块过段时间再去看,就像谜一样存在于程序中,虽然很简洁,但并不易读。

例如:有些人在程序中喜欢使用三目运算而非if-else,虽然本身使用三目运算符没有问题,但存在嵌套情况时,那么对于后面的维护者就是一场噩梦,例如如下代码:

(A>B?(A>C?A:C):(B>C?B:C))

其实上面的代码等同于,显然下面的格式更易懂

if(A>B){
    (A>C?A:C)
}else{
    (B>C?B:C)
}

迪米特法则

迪米特法则是1987年秋天由lan holland在美国东北大学一个叫做迪米特的项目设计提出的,它要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则。它的意义旨在降低类和类之间的耦合,避免发生由于耦合度过大造成的因为一个类发生变化,而对另一个类造成影响。

YAGNI

YAGNI原则是指在开发时只需要将应用程序必需的功能包含进来,而不要试图添加任何其他你认为可能需要的功能。开发过程中为了应对将来可能的提出的需求,提前开发一些功能进去,我们通常会花不少时间成本在这些过度设计的功能开发上,但可能未来的两三年内这个设计根本没有用到。应把更多的精力花在更重要的功能开发上,适度假设未来需求的规划,加速后期功能迭代和代码维护。

总结

虽然上面提了很多原则和规范,但这些规范需要在长期在工作中的实践才能有更深的理解的。希望您能从本文中了解一些基础,最后,希望大家都能写出优美、规范的代码。

 

Golang 性能测试 (3) 跟踪刨析 golang trace

简介

对于绝大部分服务,跟踪刨析是用不到的。但是如果遇到了下面问题,可以不妨一试:

  • 怀疑哪个协程慢了
  • 系统调用有问题
  • 协程调度问题 (chan 交互、互斥锁、信号量等)
  • 怀疑是 gc (Garbage-Collect) 影响了服务性能
  • 网络阻塞
  • 等等

坦白的讲,通过跟踪刨析可以看到每个协程在某一时刻在干什么。

做跟踪刨析,首先需要获取trace 数据。可以通过代码中插入trace, 或者上节提到的通过pprof 下载即可。

Example

Code

下面通过代码直接插入的方式来获取trace. 内容会涉及到网络请求,涉及协程异步执行等。

package main

import (
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"runtime/trace"
	"strconv"
	"sync"
	"time"
)


var wg sync.WaitGroup
var httpClient = &http.Client{Timeout: 30 * time.Second}

func SleepSomeTime() time.Duration{
	return time.Microsecond * time.Duration(rand.Int()%1000)
}

func create(readChan chan int) {
	defer wg.Done()
	for i := 0; i < 500; i++ {
		readChan <- getBodySize()
		SleepSomeTime()
	}
	close(readChan)
}

func convert(readChan chan int, output chan string) {
	defer wg.Done()
	for readChan := range readChan {
		output <- strconv.Itoa(readChan)
		SleepSomeTime()
	}
	close(output)
}

func outputStr(output chan string) {
	defer wg.Done()
	for _ = range output {
		// do nothing
		SleepSomeTime()
	}
}

// 获取taobao 页面大小
func getBodySize() int {
	resp, _ := httpClient.Get("https://taobao.com")
	res, _ := ioutil.ReadAll(resp.Body)
	_ = resp.Body.Close()
	return len(res)
}

func run() {
	readChan, output := make(chan int), make(chan string)
	wg.Add(3)
	go create(readChan)
	go convert(readChan, output)
	go outputStr(output)
}

func main() {
	f, _ := os.Create("trace.out")
	defer f.Close()
	_ = trace.Start(f)
	defer trace.Stop()
	run()
	wg.Wait()
}

编译,并执行,然后启动trace;

[~/blog]$ go build trace_example.go 
[~/blog]$ ./trace_example
[~/blog]$ go tool trace -http=":8000" trace_example trace.out 
2020/04/15 17:34:48 Parsing trace...
2020/04/15 17:34:50 Splitting trace...
2020/04/15 17:34:51 Opening browser. Trace viewer is listening on http://0.0.0.0:8000

然后打开浏览器,访问8000 端口即可。

Trace 功能

其中:
View trace:查看跟踪 (按照时间分段,上面我的例子时间比较短,所以没有分段)
Goroutine analysis:Goroutine 分析
Network blocking profile:网络阻塞概况
Synchronization blocking profile:同步阻塞概况
Syscall blocking profile:系统调用阻塞概况
Scheduler latency profile:调度延迟概况
User defined tasks:用户自定义任务
User defined regions:用户自定义区域
Minimum mutator utilization:最低 Mutator 利用率 (主要是GC 的评价标准, 暂时没搞懂)

goroutine 调度分析

下图包含了两种事件:

  1. 网络相关 main.create 触发网络写的协程,网络写操作的协程 writeLoop,然后等待网络返回。
  2. GC 相关操作

下面是web请求到数据,从epoll 中触发,然后readLoop协程响应,直接触发main.create 的协程得到执行。

当然我们也可以筛选协程做具体分析,从 Goroutine analysis 进入,选择具体的协程进行分析:

我们选择对 main.create 的协程做分析(这个协程略复杂,可以分析的东西比较多)

可以从图中看出,network 唤醒 readLoop 协程,进而readLoop 又通知了main.create 协程。

当然,我们也可以选择 main.convert 协程。可以看出协程被main.create 唤醒了(由于给chan 提供了数据)

除了可以分析goroutine 调度之外,还可以做网络阻塞分析,异步阻塞分析,系统调度阻塞分析,协程调度阻塞分析(下图)

自定义 Task 和 Region

当然,还可以指定task 和 Region 做分析,下面是官方举的例子:

//filepath:  src/runtime/trace/trace.go
ctx, task := trace.NewTask(ctx, "makeCappuccino")
trace.Log(ctx, "orderID", orderID)

milk := make(chan bool)
espresso := make(chan bool)

go func() {
        trace.WithRegion(ctx, "steamMilk", steamMilk)
        milk <- true
}()
go func() {
        trace.WithRegion(ctx, "extractCoffee", extractCoffee)
        espresso <- true
}()
go func() {
        defer task.End() // When assemble is done, the order is complete.
        <-espresso
        <-milk
        trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee)
}()

MMU 图

除此之外,还提供了Minimum Mutator Utilization 图 (mmu 图 )

mmu 图,数轴是服务可以占用cpu的百分比 (其他时间为gc操作)

从图中可以看出,在2ms之后,可利用的cpu逐步上升,直到接近100%.所以gc 毫无压力。

重点提醒

  1. 必须用chrome,并且高版本不行。我使用的是76.
  2. trace 的文件都比较大,几分钟可能上百兆,所以网络一定要好,或者使用本机做验证。
  3. 造作是 w 放大, s 缩小, a 左移, d 右移
  4. gc 的mmu 图解释 (备注下,还没有来得及看)https://www.cs.cmu.edu/~guyb/papers/gc2001.pdf

臭名昭著的手机验证码功能是如何实现的

前言

现在基本上各种手机APP注册都会用到手机验证码,包括一些PC端网站也会使用手机号作为唯一标识验证!

恰巧,小明的老板,让其开发一个用户注册的功能,并且强制用户注册绑定手机,美其名曰为了提升安全性,呵呵哒,就是为了多撸一点用户信息。

案例

一般来说,发送手机验证码不能过于频繁,前端发送按钮点击后一般会有一个60秒倒计时的功能。也就是说,如果用户点击发送一直没有收到验证码,只能60秒之后才可以进行重发。

那么问题来了,如果用户绕过前端,直接向后台API发送短信请求,然后写个无限循环脚本,相信不久你的短信账户就会发来预警提示短信(一般来说大的短信商都有预警设置功能)。

其实很简单,你只需要F12,查看发送请求就可以查找出后台请求地址,然后你可以在控制台输入相关JS代码,执行个十万遍,是不是很爽?

这里以七牛云为测试案例,打开注册页面,F12进入调试模式,输入手机号,手动点击发送,获取其短信发送后台请求地址。下面是七牛云的一个短信发送请求,撸主测试了一下,显然没有达到撸主的预期,毕竟是大厂,防御措施还是做的很牛逼的。

以下是JS脚本,复制粘贴到控制台回车就可以执行:

var data = {"operation":1,"is_voice":false,"mobile_number":"17762018888","captcha_type":2};
for (var i = 0; i < 10; i++) {
    $.ajax({
        type: 'POST',
		contentType: 'application/json;charset=UTF-8',
        data:JSON.stringify(data),
        url: 'https://portal.qiniu.com/api/gaea/verification/sms/send',
        success: function(data) {
            console.log(data)
        }
    });
}

控制台返回以下信息,前三次请求成功,后面的就出现了验证码校验并进行了限流操作。

{"code":200,"message":""}
{"code":200,"message":""}
{"code":200,"message":""}
{"code": 7209,"message":"captcha required"}
{"code": 7209,"message":"captcha required"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 7209,"message":"captcha required"}

撸主尝试刷新页面,随便输了一个手机号,再次点击发送,提示用户输入验证码,显然是加强了防备,触发了恶意请求认证拦截机制。

安全机制

对于开发者来说,他们不仅要考虑用户正常获取验证码的体验还要考虑短信接口的安全性,撸主总结了以下几点,希望对大家有所帮助。

  • 后台请求限流,对单位时间内发送频率做限制。
  • 验证码机制,切记不要一开始就限制验证码,体验及其不友好,触发限流以后开启验证码校验。
  • 监控日发送短信数量,触发一定的阈值做相应的处理,根据实际业务需求。
  • 验证码存储一定要保证key为手机号,切记不要以其它标识作为key,比如sessionId
  • 一定要设置验证码失效时间,比如五分钟,或者更短。
  • 验证码尽量保证短小精悍,四到六位即可。
  • 如果后台不做限制,切记前台一定要做个倒计时的限制,至少过滤一部分小白用户。

代码案例

给小伙伴分享一个简单的验证码生成、存储、失效代码案例:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Mobile {
    /**
     * 测试方便,这里设置了3秒失效
     */
    private static LoadingCache<String, String> caches = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(3, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String mobile) {
                    return "";
                }
            });

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Integer code = (int)((Math.random()*9+1)*100000);
        caches.put("17762018888",code.toString());
        System.out.println(caches.get("17762018888"));
        Thread.sleep(4000);
        System.out.println("是不是没了:"+caches.get("17762018888"));
    }
}

小结

重要的功能必须进行前后端校验,必要的时候一定要做好限流、黑名单等骚操作!!!

Linq中带有迭代索引的Select扩展方法,为啥知道的人不多呢?

一:背景

昨天在看C#函数式编程这本书的时候,有一处让我干着急,需求是这样: 给多行文字加上数字列表项。

针对这个需求你会如何快捷高效的给每个项目加上数字编号呢? 我看书中是这样实现的,如下代码


    public class Program
    {
        public static void Main(string[] args)
        {
            var list = new List<string>()
            {
                "cnblogs","csdn","zhihu","oschina"
            };

            var items = list.Zip(Enumerable.Range(1, list.Count + 1), (item, i) => $"{i}. {item}").ToList();

            items.ForEach(Console.WriteLine);
        }
    }

------------------- output -------------------
1. cnblogs
2. csdn
3. zhihu
4. oschina
Press any key to continue . . .

怎么说呢,需求能实现没有问题,但这里还是累赘了,因使用到了拉链函数Zip 和生成范围的Range,全纠缠到一块,有没有更简单粗暴的方式呢? 其实你只用Select的一个带迭代变量的重载方法就可以搞定,但现实中还是有很多的程序员不会使用或者说不知道,所以优化后的代码如下。


var items = list.Select((item, i) => $"{i + 1}. {item}").ToList();

------------------- output -------------------
1. cnblogs
2. csdn
3. zhihu
4. oschina
Press any key to continue . . .

二:源码探究

相信编码多年的我们无数次的在憎恨foreach没有带上索引,这么普罗大众的需求尽然都没有实现,而python这样的语言早就给实现了,为了解决这个不足,我还得需要单独定义一个变量在迭代时即时记录,烦不胜烦,就像下面这样。

            var index = 0;

            foreach (var item in list)
            {
                index++;
                Console.WriteLine(item);
            }

可能FCL类库程序员也深有体会,所以加了一个可以在迭代中获取当前index的绝妙方法,这么造福人类的方法却发现身边知道的人少之又少,小遗憾哈。

1. ILSpy查看源码

从下面代码的 SelectIterator 枚举类可以看到,其实在底层也是单独给你定义了一个index,然后逐一回调给你的回调函数,这种封装很有必要,不过全是高阶函数,功底很深哈!

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector)
{
	if (source == null)
	{
		throw Error.ArgumentNull("source");
	}
	if (selector == null)
	{
		throw Error.ArgumentNull("selector");
	}
	return SelectIterator(source, selector);
}

private static IEnumerable<TResult> SelectIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, int, TResult> selector)
{
	int index = -1;
	foreach (TSource item in source)
	{
		index = checked(index + 1);
		yield return selector(item, index);
	}
}

三:其他案例

其实有太多例子需要使用迭代器中的index值了,比如最近业务中要计算各个月份的环比率,用今天讲到的这个方法就可以非常完美的解决,简化后的代码如下。


        public static void Main(string[] args)
        {
            var list = new List<int>()
            {
                10, 20, 30,40,50, 60,70,80,90,100,110,120,
            };

            var rateList = list.Select((item, index) =>
            {
                return index == 0 ? 0 : Math.Round(((decimal)list[index] - list[index - 1]) / list[index - 1], 2);
            }).ToList();

            rateList.ForEach(Console.WriteLine);
        }

------------------- output -------------------
0
1
0.5
0.33
0.25
0.2
0.17
0.14
0.12
0.11
0.1
0.09
Press any key to continue . . .

好了,本篇来自于触景生情,希望您编码有帮助。

如您有更多问题与我互动,扫描下方进来吧~

再探CI,Github调戏Action手记——自动构建并发布到另一仓库

前言

接上文初探CI,Github调戏Action手记——自动构建并发布

在学习了Action的基本操作之后

接着我们来探索Action其他可能的功能

众所周知 只有用得到的技术学习的才会最快

我也是如此

在完成了当前仓库不同分支的构建发布后,我又有了新的需求 自动构建后发布到不同的仓库

正文

我们直接新建一个yml发布文件

在系统给我们生成的文件中我们可以看到基础语法的介绍

这里我结合自己的理解标注一下

在进行解读之前我们先了解一下基本概念

基本术语

  • workflow (工作流程)
  • job (任务) 一个workflow可以由多个不同的job组成
  • step (步骤) 每个job可以由多个step来组成
  • action(动作) 每个step又可以由多个action来组成

Action市场

由于持续集成大家的需求大部分可能都是相同的操作

所以github建立了一个Action市场

使得每个人编写的Action脚本都可以被其他人来引用

这就使得当我这种彩笔小白想要使用这些功能的时候而不用写出很复杂的脚本

而这整个持续集成的过程也就成为了不同的Action相组合的产物

使用方法也很简单,只需要使用uses关键字直接引用别人的库即可

uses userName/repoName

结合模板

然后我们来结合系统生成的基础模板来进行基本的解读

# This is a basic workflow to help you get started with Actions

name: CI  # 构建流程的名称


on: #触发该流程的方式
  push:
    branches: [ master ]  #触犯该流程的分支
  pull_request:
    branches: [ master ]

jobs:
  # 该任务当前仅包含了一个任务  名称是build
  build:    
    runs-on: ubuntu-latest #任务锁运行的工作环境

    # 该任务所包含的步骤
    steps:
    # 步骤所依赖的操作库 这里引用了官方发布的git操作库 目的是拉取当前库的代码
    - uses: actions/checkout@v2

    # 这里是一个单行命令的模板
    - name: Run a one-line script
      run: echo Hello, world!

    # 这里是一个多行命令的模板
    - name: Run a multi-line script
      run: |
        echo Add other actions to build,
        echo test, and deploy your project.

使用已有的库进行持续集成(当前库构建发布到另外的库)

到这里我们就可以开始进行自己的Action的组装了

首先我们先找一个有发布到其他Git库功能的Action

我们可以在github的市场搜索自己需要的Action

这里我使用的是s0/git-publish-subdir-action@master

点开这个库的主页我们可以在下方看到该库的使用说明

这里就不在赘述了

name: AutoBuild

on:
  push:
    branches: [ OneKeyVip-master ]
  pull_request:
    branches: [ OneKeyVip-master ]
jobs:
  
  build:
    name: build
    runs-on: ubuntu-latest    
    steps:    
    - uses: actions/checkout@v2    
    - name: npm install
      run: |
        npm install
        npm ci
    - name: npm build
      run: |
       npm run build
       cp README.MD ./publish/README.MD
       cp CHANGELOG ./publish/CHANGELOG

    - name: publish
      uses: s0/git-publish-subdir-action@master
      env:
        REPO: 目标库
        BRANCH: 目标分支
        FOLDER: 要发布的内容所在的文件夹        
        SSH_PRIVATE_KEY: ${{ secrets.publish }}

结语

至此我们就完成了从当前库发布到其他的库持续集成的脚本的编写

剩下的我们就可以不再关心代码的生成与发布了

可以愉快的码代码了

  • 友情链接