深度优先

这个家伙好懒,除了文章什么都没留下

0%

阅读目录

.NET开源目录:【目录】本博客其他.NET开源项目文章目录

本文原文地址:http://www.cnblogs.com/asxinyu/p/dotnet_Opensource_project_TinyMapper.html

临时更新:感谢@ 郭明锋的意见,为了避免新手误解,这里说明一下,Tiny Mapper的映射关系在Bind的时候,其实是将映射关系保存在一个静态的私有字典中,大家在使用的时候可以不需要每次都配置或者Bind。方法比较灵活,例如:你可以在实体类中封装一个方法,在方法内部进行映射关系的配置;你还可以将系统所有的配置映射关系放在一起,在使用前初始化一次就可以了。

回到目录

1.Tiny Mapper基本介绍

Tiny Mapper是一个.net平台开源的对象映射组件。说到.NET平台的对象映射,大名鼎鼎的可能就是AutoMapper。那先看看Tiny Mapper的特点

1.1 难以置信的快速

这是Tiny Mapper标榜最大的特点之一:快。那究竟有多快,我们看看官方的测试截图,由于时间紧张,我没有进行测试对比,有空试试。

1.2 非常容易使用

使用TinyMapper非常简单,它的设计简单,使用NuGet包,可以快速安装使用,DLL也才50K大小不到,所以非常轻量级。

1.3 开源

TinyMapper是一个开源项目,所以你可以参与更新和自己修改代码。

回到目录

2.Tiny Mapper 基本使用

首先,通过NuGet下载安装Tiny Mapper,如下图:

然后添加引用:

1
2
3
using Nelibur.ObjectMapper;

using Nelibur.ObjectMapper.Bindings;
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person
{
    public String Id { get; set; }
    public String Name { get; set; }
    public Int32 Age { get; set; }
}

public class PersonDto
{
    public String Id { get; set; }
    public String Name { get; set; }
    public Int32 Age { get; set; }
}

接下来,我们准备2个要相互映射的类型,Person和PersonDto,结构分别如下:

那我们在代码例如如何把其中一个类Person的对象映射为PersonDto呢?看看Tiny Mapper的使用,很简单就2行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>最简单的2个类型之间的映射</summary>
static void Test1()
{
    TinyMapper.Bind<Person,PersonDto>();
    //实例化一个Person对象
    var person = new Person
    {
        Id = Guid.NewGuid().ToString(),
        Name = "John",
        Age = 22
    };  
    //映射
    var personDto = TinyMapper.Map<PersonDto>(person);
}

运行看看结果:

3.Tiny Mapper 指定配置使用

有时候对象的字段名称并不一样,而且可能要忽略某些字段,这个时候就要使用更加灵活的配置了。

看例子,为了演示,我们特意修改2个类型的字段名称不一样.如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person
{
    public String Id { get; set; }
    public String Name { get; set; }
    public Int32 Age { get; set; }
}

public class PersonDto
{
    public String Id { get; set; }
    //注意这里的字段名称:UserName
    public String UserName { get; set; }
    public Int32 Age { get; set; }   
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>使用配置项指定和忽略字段</summary>
static void Test2()
{
    TinyMapper.Bind<Person, PersonDto>(config =>
    {
        config.Ignore(x => x.Id);//忽略ID字段
        config.Bind(x => x.Name, y => y.UserName);//将源类型和目标类型的字段对应绑定起来
        config.Bind(x => x.Age, y => y.Age);//将源类型和目标类型的字段对应绑定起来
    });
    var person = new Person
    {
        Id = Guid.NewGuid().ToString(),
        Name = "John",
        Age = 22
    };
    var personDto = TinyMapper.Map<PersonDto>(person);
}

看看效果如何?我们指定的Id字段忽略映射,以及将源对象的字段映射到目标对象的指定字段了: 映射代码和配置如下所示:

回到目录

4.Tiny Mapper复杂类型使用

有时候对象的类型里面嵌套着对象,或者数组等等,其实这个时候的使用和上述完全一样,这里只是举个例子,大家看看而已:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Person
{
    public String Id { get; set; }
    public String Name { get; set; }
    public Int32 Age { get; set; }
    public Address Address { get; set; }
    public List<String> Emails { get; set; }
}

public class PersonDto
{
    public String Id { get; set; }
    public String UserName { get; set; }
    public Int32 Age { get; set; }
    public Address Address { get; set; }
    public List<String> Emails { get; set; }
}

public sealed class Address
{
    public string Phone { get; set; }
    public string Street { get; set; }
    public string ZipCode { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>更复杂对象的映射例子</summary>
static void Test3()
{
    TinyMapper.Bind<Person, PersonDto>(config =>
    {
        config.Ignore(x => x.Id);//忽略ID字段
        //将源类型和目标类型的字段对应绑定起来
        config.Bind(x => x.Name, y => y.UserName);
        config.Bind(x => x.Age, y => y.Age);
        config.Bind(x => x.Address, y => y.Address);
        config.Bind(x => x.Emails, y => y.Emails);
    });
    var person = new Person
    {
        Id = Guid.NewGuid().ToString(),
        Name = "John",
        Age = 22,
        Address = new Address() { Phone = "1880393", Street = "Shanghai", ZipCode = "121212" },
        Emails = new List<string>() { "aaa@bb.com", "acx@cc.com" }
    };
    var personDto = TinyMapper.Map<PersonDto>(person);
}

效果如下: 映射的代码和第3节类似:

回到目录

5.资源

Tiny Mapper官方网站:http://tinymapper.net/

github项目地址:https://github.com/TinyMapper/TinyMapper

本文使用的代码下载:TinyMapperDemo.rar

几乎所有编程语言中都提供了”生成一个随机数”的方法,也就是调用这个方法会生成一个数,我们事先也不知道它生成什么数。比如在.Net中编写下面的代码:

1
2
3
Random rand = newRandom();

Console.WriteLine(rand.Next());

运行后结果如下:

Next()方法用来返回一个随机数。同样的代码你执行和我的结果很可能不一样,而且我多次运行的结果也很可能不一样,这就是随机数。

一、陷阱

看似很简单的东西,使用的时候有陷阱。我编写下面的代码想生成100个随机数:

1
2
3
4
5
for(int i=0;i<100;i++)
{
    Random rand = new Random();
    Console.WriteLine(rand.Next());
}

太奇怪了,竟然生成的”随机数”有好多连续一样的,这算什么”随机数”呀。有人指点”把new Random()”放到for循环外面就可以了:

1
2
3
4
5
Random rand = newRandom();
for(int i=0;i<100;i++)
{            
    Console.WriteLine(rand.Next());
}

运行结果:

确实可以了!

二、这是为什么呢?

这要从计算机中”随机数”产生的原理说起了。我们知道,计算机是很严格的,在确定的输入条件下,产生的结果是唯一确定的,不会每次执行的结果不一样。那么怎么样用软件实现产生看似不确定的随机数呢?

生成随机数的算法有很多种,最简单也是最常用的就是 “线性同余法”: 第n+1个数=(第n个数*29+37) % 1000,其中%是”求余数”运算符。很多像我一样的人见了公式都头疼,我用代码解释一下吧,MyRand是一个自定义的生成随机数的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyRand
 {
    private int seed;
    public MyRand(int seed)
   {

    this.seed = seed;
   }

  public int Next()
   {
     int next = (seed * 29 + 37) % 1000;
     seed = next;
     return next;
  }
}
1
2
3
4
5
MyRand rand = newMyRand(51);
for (int i = 0; i < 10; i++)
 {
    Console.WriteLine(rand.Next());
 }

如下调用:

执行结果如下:

生成的数据是不是看起来”随机”了。简单解释一下这个代码:我们创建MyRand的一个对象,然后构造函数传递一个数51,这个数被赋值给seed,每次调用Next方法的时候根据(seed * 29 + 37) % 1000计算得到一个随机数,把这个随机数赋值给seed,然后把生成的随机数返回。这样下次再调用Next()的时候seed就不再是51,而是上次生成的随机数了,这样就看起来好像每一次生成的内容都很”随机”了。注意”%1000”取余预算的目的是保证生成的随机数不超过1000。

当然无论是你运行还是我每次运行,输出结果都是一样的随机数,因为根据给定的初始数据51,我们就可以依次推断下来下面生成的所有”随机数”是什么都可以算出来了。这个初始的数据51就被称为”随机数种子”,这一系列的516、1、66、951、616……数字被称为”随机数序列”。我们把51改成52,就会有这样的结果:

三、楼主好人,跪求种子

那么怎么可以使得每次运行程序的时候都生成不同的”随机数序列”呢?因为我们每次执行程序时候的时间很可能不一样,因此我们可以用当前时间做”随机数种子”

1
2
3
4
5
MyRand rand = newMyRand(Environment.TickCount);
for (int i = 0; i < 10; i++)
 {
    Console.WriteLine(rand.Next());
 }

Environment.TickCount为”系统启动后经过的微秒数”。这样每次程序运行的时候Environment.TickCount都不大可能一样(靠手动谁能一微秒内启动两次程序呢),所以每次生成的随机数就不一样了。

当然如果我们把new MyRand(Environment.TickCount)放到for循环中:

1
2
3
4
5
for (int i = 0; i < 100; i++)
 {
    MyRand rand = newMyRand(Environment.TickCount);
    Console.WriteLine(rand.Next());
 }

运行结果又变成”很多是连续”的了,原理很简单:由于for循环体执行很快,所以每次循环的时候Environment.TickCount很可能还和上次一样(两行简单的代码运行用不了一毫秒那么长事件),由于这次的”随机数种子”和上次的”随机数种子”一样,这样Next()生成的第一个”随机数”就一样了。从”-320”变成”-856”是因为运行到”-856”的时候时间过了一毫秒。

四、各语言的实现

我们看到.Net的Random类有一个int类型参数的构造函数:

public Random(int Seed)

就是和我们写的MyRand一样接受一个”随机数种子”。而我们之前调用的无参构造函数就是给Random(int Seed)传递Environment.TickCount类进行构造的,代码如下:

public Random() : this(Environment.TickCount)
{
}

这下我们终于明白最开始的疑惑了。

同样道理,在C/C++中生成10个随机数不应该如下调用:

1
2
3
4
5
6
int i;
for(i=0;i<10;i++)
{
    srand( (unsigned)time( NULL ) );
    printf("%d\n",rand());
}

而应该:

1
2
3
4
5
6
srand( (unsigned)time( NULL ) ); //把当前时间设置为"随机数种子"
int i;
for(i=0;i<10;i++)
{         
    printf("%d\n",rand());
}

Java学习者可能会提出问题了,在Java低版本中,如下使用会像.Net、C/C++中一样产生相同的随机数: 五、”奇葩”的Java

1
2
3
4
5
for(int i=0;i<100;i++)
{
    Random rand = new Random();
    System.out.println(rand.nextInt());
}

因为低版本Java中Rand类的无参构造函数的实现同样是用当前时间做种子:

public Random() { this(System.currentTimeMillis()); }

但是在高版本的Java中,比如Java1.8中,上面的”错误”代码执行却是没问题的:

为什么呢?我们来看一下这个Random无参构造函数的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Random()
{
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
privatestaticfinal AtomicLong seedUniquifier  = new AtomicLong(8682522807148012L);

这里不再是使用当前时间来做”随机数种子”,而是使用System.nanoTime()这个纳秒级的时间量并且和采用原子量AtomicLong根据上次调用构造函数算出来的一个数做异或运算。关于这段代码的解释详细参考这篇文章《解密随机数生成器(2)——从java源码看线性同余算法

最核心的地方就在于使用static变量AtomicLong来记录每次调用Random构造函数时使用的种子,下次再调用Random构造函数的时候避免和上次一样。

六、高并发系统中的问题

前面我们分析了,对于使用系统时间做”随机数种子”的随机数生成器,如果要产生多个随机数,那么一定要共享一个”随机数种子”才会避免生成的随机数短时间之内生成重复的随机数。但是在一些高并发的系统中一个不注意还会产生问题,比如一个网站在服务器端通过下面的方法生成验证码:

Random rand = new Random();

Int code = rand.Next();

当网站并发量很大的时候,可能一个毫秒内会有很多个人请求验证码,这就会造成这几个人请求到的验证码是重复的,会给系统带来潜在的漏洞。

再比如我今天看到的一篇文章《当随机不够随机:一个在线扑克游戏的教训》里面就提到了”由于随机数产生器的种子是基于服务器时钟的,黑客们只要将他们的程序与服务器时钟同步就能够将可能出现的乱序减少到只有 200,000 种。到那个时候一旦黑客知道 5 张牌,他就可以实时的对 200,000 种可能的乱序进行快速搜索,找到游戏中的那种。所以一旦黑客知道手中的两张牌和 3 张公用牌,就可以猜出转牌和河牌时会来什么牌,以及其他玩家的牌。”

这种情况有如下几种解决方法:

  1. 把Random对象作为一个全局实例(static)来使用。Java中Random是线程安全的(内部进行了加锁处理);.Net中Random不是线程安全的,需要加锁处理。不过加锁会存在会造成处理速度慢的问题。而且由于初始的种子是确定的,所以攻击者存在着根据得到的若干随机数序列推测出”随机数种子”的可能性。
  1. 因为每次生成Guid的值都不样,网上有的文章说可以创建一个Guid计算它的HashCode或者MD5值的方式来做种子: new Random(Guid.NewGuid().GetHashCode()) 。但是我认为Guid的生成算法是确定的,在条件充足的情况下也是可以预测的,这样生成的随机数也有可预测的可能性。当然只是我的猜测,没经过理论的证明。
  1. 采用”真随机数发生器”,快看下一节分解!

    七、真随机数发生器

根据我们之前的分析,我们知道这些所谓的随机数不是真的”随机”,只是看起来随机,因此被称为”伪随机算法”。在一些对随机要求高的场合会使用一些物理硬件采集物理噪声、宇宙射线、量子衰变等现实生活中的真正随机的物理参数来产生真正的随机数。

当然也有聪明的人想到了不借助增加”随机数发生器”硬件的方法生成随机数。我们操作计算机时候鼠标的移动、敲击键盘的行为都是不可预测的,外界命令计算机什么时候要执行什么进程、处理什么文件、加载什么数据等也是不可预测的,因此导致的CPU运算速度、硬盘读写行为、内存占用情况的变化也是不可预测的。因此如果采集这些信息来作为随机数种子,那么生成的随机数就是不可预测的了。

在Linux/Unix下可以使用”/dev/random”这个真随机数发生器,它的数据主来来自于硬件中断信息,不过产生随机数的速度比较慢。

Windows下可以调用系统的CryptGenRandom()函数,它主要依据当前进程Id、当前线程Id、系统启动后的TickCount、当前时间、QueryPerformanceCounter返回的高性能计数器值、用户名、计算机名、CPU计数器的值等等来计算。和”/dev/random”一样CryptGenRandom()的生成速度也比较慢,而且消耗比较大的系统资源。

当然.Net下也可以使用RNGCryptoServiceProvider 类(System.Security.Cryptography命名空间下)来生成真随机数,根据StackOverflow上一篇帖子介绍RNGCryptoServiceProvider 并不是对CryptGenRandom()函数的封装,但是和CryptGenRandom()原理类似。

八、总结

有人可能会问:既然有”/dev/random” 、CryptGenRandom()这样的”真随机数发生器”,为什么还要提供、使用伪随机数这样的”假货”?因为前面提到了”/dev/random” 、CryptGenRandom()生成速度慢而且比较消耗性能。在对随机数的不可预测性要求低的场合,使用伪随机数算法即可,因为性能比较高。对于随机数的不可预测性要求高的场合就要使用真随机数发生器,真随机数发生器硬件设备需要考虑成本问题,而”/dev/random”、CryptGenRandom()则性能较差。

转自:https://www.cnblogs.com/straycats/p/7637373.html

实测可行!

===============================================

2017/11/12_第6次修改 ccb_warlock

更新说明:

2017/11/12:五中增加管理员账号的内容

2017/11/8:修复3.6中命令错误的问题;

2017/10/20:3.4中增加“途径2:从官方获取RPM包后上传到/root目录下”的内容;4.5中增加安装patch的命令;2.2中增加git clone老版本的命令;

2017/10/18:在“环境准备”中增加硬件环境的描述;

2017/10/9:增加3.5的内容;

===============================================

作为公司层面使用gitlab,进行维护时一定要考虑升级、备份、迁移/恢复的事情,代码安全是研发部门的重心之一,故我也整理了下面的内容供维护人员参考。

升级:http://www.cnblogs.com/straycats/p/7707359.html
本地备份:http://www.cnblogs.com/straycats/p/7671204.html
远程备份:http://www.cnblogs.com/straycats/p/7672692.html
迁移/恢复:http://www.cnblogs.com/straycats/p/7702271.html

gitlab接触了一段时间,觉得是一个很不错的开源产品(gitlab的社区版),值得我整理一篇部署教程作为记录。

安装全程选择的用户为root。

一、环境准备
操作系统:centos7(minimal,www.centos.org下载的包是CentOS-7-x86_64-Minimal-1708.iso)
CPU:2核
内存:6G
说明:硬件根据我查到的资料(http://www.cnblogs.com/gnool/p/6128546.html),结合使用人数的考虑,故选择了2核6G的配置,实际配置时需要根据gitlab使用人数的负载来决定硬件配置。

1.1 更换阿里yum(个人习惯)

步骤:

1)下载wget

1
yum install -y wget

2)备份默认的yum

1
mv /etc/yum.repos.d /etc/yum.repos.d.backup

3)设置新的yum目录

1
mkdir /etc/yum.repos.d

4)下载阿里yum配置到该目录中

1
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

5)重建缓存

1
2
yum clean all
yum makecache

6)升级所有包(改变软件设置和系统设置,系统版本内核都升级,故需要几分钟耐心等待)

1
yum update -y

1.2 安装vim(个人习惯)

1
yum install -y vim 

二、获取gitlab汉化包(要部署非汉化版,可以跳过这一块内容)
说明:gitlab中文社区版的项目,v7-v8.8是由Larry Li发起的“GitLab 中文社区版项目”(https://gitlab.com/larryli/gitlab),从 v8.9 之后,@xhang 开始继续该汉化项目(https://gitlab.com/xhang/gitlab)。

2.1 安装git

1
yum install -y git 

2.2 克隆获取汉化版本库
下载最新的汉化包

1
git clone https://gitlab.com/xhang/gitlab.git 

如果是要下载老版本的汉化包,需要加上老版本的分支,比如今天已经是10.0.4,我依旧想下载10.0.2,可以运行下面的语句

1
git clone https://gitlab.com/xhang/gitlab.git -b v10.0.2-zh 

2.3 查看该汉化补丁的版本

1
cat gitlab/VERSION 

我安装的时候正好是国庆,最新的版本才刚出,所以汉化版为10.0.2,故我选择原版也安装10.0.2。


三、部署社区版gitlab

3.1 安装gitlab的依赖项

1
yum install -y curl openssh-server openssh-clients postfix cronie policycoreutils-python

// 10.x以后开始依赖policycoreutils-python,我之前在使用9.x时还没有依赖该项。

3.2 启动postfix,并设置为开机启动

1
2
systemctl start postfix
systemctl enable postfix

3.3 设置防火墙

1
2
firewall-cmd --add-service=http --permanent
firewall-cmd --reload

3.4 获取gitlab的rpm包

途径1:通过清华开源镜像站

查看清华开源镜像站,有我需要的10.0.2的rpm包。


获取rpm包

1
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.0.2-ce.0.el7.x86_64.rpm 

途径2:从官方获取RPM包后上传到/root目录下

官方下载:https://packages.gitlab.com/gitlab/gitlab-ce/

因为要在centos7上安装版本10.0.2的gitlab,故找到gitlab-ce-10.0.2-ce.0.el7.x86_64.rpm下载到本地后,通过Bitvise SSH Client工具将rpm包上传到gitlab虚拟机的/root目录下。

说明:从下载速度和方便程度来说,走清华的镜像站的方式更优,但是我2017/10/20想下载gitlab10.0.4时,发现清华的镜像站最新只有10.0.3,故在增补“途径2”获取最新的安装包。

3.5 安装rpm包

1
rpm -i gitlab-ce-10.0.2-ce.0.el7.x86_64.rpm


根据提示,继续执行指令配置gitlab。

1
gitlab-ctl reconfigure 

3.6 修改配置文件gitlab.rb

1
vim /etc/gitlab/gitlab.rb

将external_url变量的地址修改为gitlab所在centos的ip地址。(因为我这台centos是16.5,故配成图中的地址)。

因为修改了配置文件,故需要重新加载配置内容。

1
2
gitlab-ctl reconfigure
gitlab-ctl restart

3.7 查看gitlab版本

1
head -1 /opt/gitlab/version-manifest.txt 

四、覆盖汉化包

4.1 停止gitlab服务

1
gitlab-ctl stop 

4.2 切换到gitlab汉化包所在的目录(即步骤二获取的汉化版gitlab)

1
cd /root/gitlab 

4.3 比较汉化标签和原标签,导出 patch 用的 diff 文件到/root下

1
git diff v10.0.2 v10.0.2-zh > ../10.0.2-zh.diff 

4.4 回到/root目录

1
cd 

4.5 将10.0.2-zh.diff作为补丁更新到gitlab中

1
2
yum install patch -y
patch -d /opt/gitlab/embedded/service/gitlab-rails -p1 < 10.0.2-zh.diff

4.6 启动gitlab

1
gitlab-ctl start 

4.7 重新配置gitlab

1
gitlab-ctl reconfigure 

五、设置管理员密码

管理员账号登录的用户名:root

(虽然登录后管理员的用户名为Administrator,但是实际登录的用户名是root)

方法一:网页方式

浏览器访问gitlab所在的ip,输入密码后点击 “Change your password”(如果进行汉化,点击“修改密码”)。

方法二:指令方式

1
gitlab-rails console production

稍等一会后会出现

1
2
3
4
irb(main):001:0> user = User.where(id: 1).first     // id为1的是超级管理员
irb(main):002:0>user.password = 'yourpassword' // 密码必须至少8个字符
irb(main):003:0>user.save! // 如没有问题 返回true
exit // 退出