diff --git a/translated/tech/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md b/published/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md similarity index 67% rename from translated/tech/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md rename to published/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md index 21f386df00..7ddd3f33cc 100644 --- a/translated/tech/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md +++ b/published/20180430 3 practical Python tools- magic methods, iterators and generators, and method magic.md @@ -1,133 +1,106 @@ -3 个实用的 Python 工具:魔术方法,迭代器和生成器,以及方法魔术 +日常 Python 编程优雅之道 ====== -(to 校正者:magic) + +> 3 个可以使你的 Python 代码更优雅、可读、直观和易于维护的工具。 + ![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/serving-bowl-forks-dinner.png?itok=a3YqPwr5) -Python 提供了一组独特的工具和语言特性来帮助你使代码更加优雅,可读和直观。通过为正确的问题选择合适的工具,你的代码将更易于维护。在本文中,我们将研究其中的三个工具:魔术方法,迭代器和生成器,以及方法魔术。 + +Python 提供了一组独特的工具和语言特性来使你的代码更加优雅、可读和直观。为正确的问题选择合适的工具,你的代码将更易于维护。在本文中,我们将研究其中的三个工具:魔术方法、迭代器和生成器,以及方法魔术。 ### 魔术方法 魔术方法可以看作是 Python 的管道。它们被称为“底层”方法,用于某些内置的方法、符号和操作。你可能熟悉的常见魔术方法是 `__init__()`,当我们想要初始化一个类的新实例时,它会被调用。 -你可能已经看过其他常见的魔术方法,如 `__str__` 和 `__repr__`。Python 中有一整套魔术方法,通过实现其中的一些方法,我们可以修改一个对象的行为,甚至使其行为类似于内置数据类型,例如数字,列表或字典。 +你可能已经看过其他常见的魔术方法,如 `__str__` 和 `__repr__`。Python 中有一整套魔术方法,通过实现其中的一些方法,我们可以修改一个对象的行为,甚至使其行为类似于内置数据类型,例如数字、列表或字典。 让我们创建一个 `Money` 类来示例: + ``` class Money: -    currency_rates = { + currency_rates = { + '$': 1, + '€': 0.88, + } -        '$': 1, + def __init__(self, symbol, amount): + self.symbol = symbol + self.amount = amount -        '€': 0.88, + def __repr__(self): + return '%s%.2f' % (self.symbol, self.amount) -    } - -    def __init__(self, symbol, amount): - -        self.symbol = symbol - -        self.amount = amount - -    def __repr__(self): - -        return '%s%.2f' % (self.symbol, self.amount) - -    def convert(self, other): - -        """ Convert other amount to our currency """ - -        new_amount = ( - -            other.amount / self.currency_rates[other.symbol] - -            * self.currency_rates[self.symbol]) - -        return Money(self.symbol, new_amount) + def convert(self, other): + """ Convert other amount to our currency """ + new_amount = ( + other.amount / self.currency_rates[other.symbol] + * self.currency_rates[self.symbol]) + return Money(self.symbol, new_amount) ``` -该类定义为给定的符号和汇率定义了一个货币汇率,指定了一个初始化器(也称为构造函数),并实现 `__repr__`,因此当我们打印这个类时,我们会看到一个友好的表示,例如 `$2.00` ,一个带有货币符号和金额的 `Money('$', 2.00)` 实例。最重要的是,它定义了一种方法,允许你使用不同的汇率在不同的货币之间进行转换。 +该类定义为给定的货币符号和汇率定义了一个货币汇率,指定了一个初始化器(也称为构造函数),并实现 `__repr__`,因此当我们打印这个类时,我们会看到一个友好的表示,例如 `$2.00` ,这是一个带有货币符号和金额的 `Money('$', 2.00)` 实例。最重要的是,它定义了一种方法,允许你使用不同的汇率在不同的货币之间进行转换。 打开 Python shell,假设我们已经定义了使用两种不同货币的食品的成本,如下所示: + ``` >>> soda_cost = Money('$', 5.25) - >>> soda_cost - -    $5.25 + $5.25 >>> pizza_cost = Money('€', 7.99) - >>> pizza_cost - -    €7.99 - + €7.99 ``` 我们可以使用魔术方法使得这个类的实例之间可以相互交互。假设我们希望能够将这个类的两个实例一起加在一起,即使它们是不同的货币。为了实现这一点,我们可以在 `Money` 类上实现 `__add__` 这个魔术方法: + ``` class Money: -    # ... previously defined methods ... - -    def __add__(self, other): - -        """ Add 2 Money instances using '+' """ - -        new_amount = self.amount + self.convert(other).amount - -        return Money(self.symbol, new_amount) + # ... previously defined methods ... + def __add__(self, other): + """ Add 2 Money instances using '+' """ + new_amount = self.amount + self.convert(other).amount + return Money(self.symbol, new_amount) ``` 现在我们可以以非常直观的方式使用这个类: + ``` >>> soda_cost = Money('$', 5.25) - >>> pizza_cost = Money('€', 7.99) - >>> soda_cost + pizza_cost - -    $14.33 - + $14.33 >>> pizza_cost + soda_cost - -    €12.61 - + €12.61 ``` -当我们将两个实例加在一起时,我们得到第一个定义的货币的符号所表示的结果(to 校正者:这里意思是:得到的结果是第一个对象的符号所表示的。)。所有的转换都是在底层无缝完成的。如果我们想的话,我们也可以为减法实现 `__sub__`,为乘法实现 `__mul__` 等等。阅读[模拟数字类型][1]或[魔术方法指南][2]来获得更多信息。 +当我们将两个实例加在一起时,我们得到以第一个定义的货币符号所表示的结果。所有的转换都是在底层无缝完成的。如果我们想的话,我们也可以为减法实现 `__sub__`,为乘法实现 `__mul__` 等等。阅读[模拟数字类型][1]或[魔术方法指南][2]来获得更多信息。 我们学习到 `__add__` 映射到内置运算符 `+`。其他魔术方法可以映射到像 `[]` 这样的符号。例如,在字典中通过索引或键来获得一项,其实是使用了 `__getitem__` 方法: + ``` >>> d = {'one': 1, 'two': 2} - >>> d['two'] - 2 - >>> d.__getitem__('two') - 2 - ``` 一些魔术方法甚至映射到内置函数,例如 `__len__()` 映射到 `len()`。 + ``` class Alphabet: + letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -    letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - -    def __len__(self): - -        return len(self.letters) + def __len__(self): + return len(self.letters) >>> my_alphabet = Alphabet() - >>> len(my_alphabet) - -    26 - + 26 ``` ### 自定义迭代器 @@ -135,15 +108,14 @@ class Alphabet: 对于新的和经验丰富的 Python 开发者来说,自定义迭代器是一个非常强大的但令人迷惑的主题。 许多内置类型,例如列表、集合和字典,已经实现了允许它们在底层迭代的协议。这使我们可以轻松地遍历它们。 + ``` >>> for food in ['Pizza', 'Fries']:          print(food + '. Yum!') Pizza. Yum! - Fries. Yum! - ``` 我们如何迭代我们自己的自定义类?首先,让我们来澄清一些术语。 @@ -155,70 +127,52 @@ Fries. Yum! 呼!这听起来很复杂,但是一旦你记住了这些基本概念,你就可以在任何时候进行迭代。 我们什么时候想使用自定义迭代器?让我们想象一个场景,我们有一个 `Server` 实例在不同的端口上运行不同的服务,如 `http` 和 `ssh`。其中一些服务处于 `active` 状态,而其他服务则处于 `inactive` 状态。 + ``` class Server: -    services = [ - -        {'active': False, 'protocol': 'ftp', 'port': 21}, - -        {'active': True, 'protocol': 'ssh', 'port': 22}, - -        {'active': True, 'protocol': 'http', 'port': 80}, - -    ] - + services = [ + {'active': False, 'protocol': 'ftp', 'port': 21}, + {'active': True, 'protocol': 'ssh', 'port': 22}, + {'active': True, 'protocol': 'http', 'port': 80}, + ] ``` 当我们遍历 `Server` 实例时,我们只想遍历那些处于 `active` 的服务。让我们创建一个 `IterableServer` 类: + ``` class IterableServer: -     def __init__(self): -         self.current_pos = 0 -     def __next__(self): -         pass  # TODO: 实现并记得抛出 StopIteration - ``` 首先,我们将当前位置初始化为 `0`。然后,我们定义一个 `__next__()` 方法来返回下一项。我们还将确保在没有更多项返回时抛出 `StopIteration`。到目前为止都很好!现在,让我们实现这个 `__next__()` 方法。 + ``` class IterableServer: -     def __init__(self): -         self.current_pos = 0.  # 我们初始化当前位置为 0 -     def __iter__(self):  # 我们可以在这里返回 self,因为实现了 __next__ -         return self -     def __next__(self): -         while self.current_pos < len(self.services): -             service = self.services[self.current_pos] -             self.current_pos += 1 -             if service['active']: -                 return service['protocol'], service['port'] -         raise StopIteration -     next = __next__  # 可选的 Python2 兼容性 - ``` 我们对列表中的服务进行遍历,而当前的位置小于服务的个数,但只有在服务处于活动状态时才返回。一旦我们遍历完服务,就会抛出一个 `StopIteration` 异常。 -因为我们实现了 `__next __()` 方法,当它耗尽时,它会抛出 `StopIteration`。我们可以从 `__iter __()` 返回 `self`,因为 `IterableServer` 类遵循 `iterable` 协议。 + +因为我们实现了 `__next__()` 方法,当它耗尽时,它会抛出 `StopIteration`。我们可以从 `__iter__()` 返回 `self`,因为 `IterableServer` 类遵循 `iterable` 协议。 现在我们可以遍历一个 `IterableServer` 实例,这将允许我们查看每个处于活动的服务,如下所示: + ``` >>> for protocol, port in IterableServer(): @@ -231,274 +185,190 @@ service http is running on port 21 ``` 太棒了,但我们可以做得更好!在这样类似的实例中,我们的迭代器不需要维护大量的状态,我们可以简化代码并使用 [generator(生成器)][4] 来代替。 + ``` class Server: -     services = [ -         {'active': False, 'protocol': 'ftp', 'port': 21}, -         {'active': True, 'protocol': 'ssh', 'port': 22}, -         {'active': True, 'protocol': 'http', 'port': 21}, -     ] -     def __iter__(self): -         for service in self.services: -             if service['active']: -                 yield service['protocol'], service['port'] - ``` -`yield` 关键字到底是什么?在定义生成器函数时使用 yield。这有点像 `return`,虽然 `return` 返回值后退出函数,但 `yield` 会暂停执行直到下次调用它。这允许你的生成器功能在它恢复之前保持状态。查看 [yield 的文档][5]以了解更多信息。使用生成器,我们不必通过记住我们的位置来手动维护状态。生成器只知道两件事:它现在需要做什么以及计算下一个项目需要做什么。一旦我们到达执行点,即 `yield` 不再被调用,我们就知道停止迭代。 +`yield` 关键字到底是什么?在定义生成器函数时使用 yield。这有点像 `return`,虽然 `return` 在返回值后退出函数,但 `yield` 会暂停执行直到下次调用它。这允许你的生成器的功能在它恢复之前保持状态。查看 [yield 的文档][5]以了解更多信息。使用生成器,我们不必通过记住我们的位置来手动维护状态。生成器只知道两件事:它现在需要做什么以及计算下一个项目需要做什么。一旦我们到达执行点,即 `yield` 不再被调用,我们就知道停止迭代。 -这是因为一些内置的 Python 魔法。在 [Python 关于 `__iter__()` 的文档][6]中我们可以看到,如果 `__iter __()` 是作为一个生成器实现的,它将自动返回一个迭代器对象,该对象提供 `__iter __()` 和 `__next __( )` 方法。阅读这篇很棒的文章,深入了解[迭代器,可迭代对象和生成器][7]。 +这是因为一些内置的 Python 魔法。在 [Python 关于 `__iter__()` 的文档][6]中我们可以看到,如果 `__iter__()` 是作为一个生成器实现的,它将自动返回一个迭代器对象,该对象提供 `__iter__()` 和 `__next__()` 方法。阅读这篇很棒的文章,深入了解[迭代器,可迭代对象和生成器][7]。 ### 方法魔法 由于其独特的方面,Python 提供了一些有趣的方法魔法作为语言的一部分。 其中一个例子是别名功能。因为函数只是对象,所以我们可以将它们赋值给多个变量。例如: + ``` >>> def foo(): -        return 'foo' - >>> foo() - 'foo' - >>> bar = foo - >>> bar() - 'foo' - ``` 我们稍后会看到它的作用。 Python 提供了一个方便的内置函数[称为 `getattr()`][8],它接受 `object, name, default` 参数并在 `object` 上返回属性 `name`。这种编程方式允许我们访问实例变量和方法。例如: + ``` >>> class Dog: - -        sound = 'Bark' - -        def speak(self): - -            print(self.sound + '!', self.sound + '!') - + sound = 'Bark' + def speak(self): + print(self.sound + '!', self.sound + '!') >>> fido = Dog() >>> fido.sound - 'Bark' - >>> getattr(fido, 'sound') - 'Bark' >>> fido.speak - > - >>> getattr(fido, 'speak') - > >>> fido.speak() - Bark! Bark! - >>> speak_method = getattr(fido, 'speak') - >>> speak_method() - Bark! Bark! - ``` 这是一个很酷的技巧,但是我们如何在实际中使用 `getattr` 呢?让我们看一个例子,我们编写一个小型命令行工具来动态处理命令。 + ``` class Operations: -     def say_hi(self, name): -         print('Hello,', name) -     def say_bye(self, name): -         print ('Goodbye,', name) -     def default(self, arg): -         print ('This operation is not supported.') - if __name__ == '__main__': -     operations = Operations() -     # 假设我们做了错误处理 -     command, argument = input('> ').split() -     func_to_call = getattr(operations, command, operations.default) -     func_to_call(argument) - ``` 脚本的输出是: + ``` $ python getattr.py - > say_hi Nina - Hello, Nina - > blah blah - This operation is not supported. - ``` -接下来,我们来看看 `partial`。例如,**`functool.partial(func, *args, **kwargs)`** 允许你返回一个新的 [partial 对象][9],它的行为类似 `func`,参数是 `args` 和 `kwargs`。如果传入更多的 `args`,它们会被附加到 `args`。如果传入更多的 `kwargs`,它们会扩展并覆盖 `kwargs`。让我们通过一个简短的例子来看看: +接下来,我们来看看 `partial`。例如,`functool.partial(func, *args, **kwargs)` 允许你返回一个新的 [partial 对象][9],它的行为类似 `func`,参数是 `args` 和 `kwargs`。如果传入更多的 `args`,它们会被附加到 `args`。如果传入更多的 `kwargs`,它们会扩展并覆盖 `kwargs`。让我们通过一个简短的例子来看看: + ``` >>> from functools import partial - >>> basetwo = partial(int, base=2) - >>> basetwo - - >>> basetwo('10010') - 18 - # 这等同于 - >>> int('10010', base=2) - ``` -让我们看看这个方法魔术如何在我喜欢的一个[名为 `agithub`][10] 的库中的一些示例代码结合在一起的,这是一个(poorly name)REST API 客户端。它具有透明的语法,允许你以最小的配置快速构建任何 REST API 原型(不仅仅是 GitHub)。我发现这个项目很有趣,因为它非常强大,但只有大约 400 行 Python 代码。你可以在大约 30 行配置代码中添加对任何 REST API 的支持。`agithub` 知道协议所需的一切(`REST`、`HTTP`、`TCP`),但它不考虑上游 API。让我们深入到它的实现中。 +让我们看看在我喜欢的一个[名为 `agithub`][10] 的库中的一些示例代码中,这个方法魔术是如何结合在一起的,这是一个(名字起得很 low 的) REST API 客户端,它具有透明的语法,允许你以最小的配置快速构建任何 REST API 原型(不仅仅是 GitHub)。我发现这个项目很有趣,因为它非常强大,但只有大约 400 行 Python 代码。你可以在大约 30 行配置代码中添加对任何 REST API 的支持。`agithub` 知道协议所需的一切(`REST`、`HTTP`、`TCP`),但它不考虑上游 API。让我们深入到它的实现中。 以下是我们如何为 GitHub API 和任何其他相关连接属性定义端点 URL 的简化版本。在这里查看[完整代码][11]。 + ``` class GitHub(API): -     def __init__(self, token=None, *args, **kwargs): -         props = ConnectionProperties(api_url = kwargs.pop('api_url', 'api.github.com')) -         self.setClient(Client(*args, **kwargs)) -         self.setConnectionProperties(props) - ``` 然后,一旦配置了[访问令牌][12],就可以开始使用 [GitHub API][13]。 + ``` >>> gh = GitHub('token') - >>> status, data = gh.user.repos.get(visibility='public', sort='created') - >>> # ^ 映射到 GET /user/repos - >>> data - ... ['tweeter', 'snipey', '...'] - ``` -请注意,由你决定拼写正确的 URL,因为我们没有验证 URL。如果 URL 不存在或出现了其他任何错误,将返回 API 抛出的错误。那么,这一切是如何运作的呢?让我们找出答案。首先,我们将查看一个 [`API` 类][14]的简化示例: +请注意,你要确保 URL 拼写正确,因为我们没有验证 URL。如果 URL 不存在或出现了其他任何错误,将返回 API 抛出的错误。那么,这一切是如何运作的呢?让我们找出答案。首先,我们将查看一个 [`API` 类][14]的简化示例: + ``` class API: -     # ... other methods ... -     def __getattr__(self, key): -         return IncompleteRequest(self.client).__getattr__(key) -     __getitem__ = __getattr__ - ``` 在 `API` 类上的每次调用都会调用 [`IncompleteRequest` 类][15]作为指定的 `key`。 + ``` class IncompleteRequest: -     # ... other methods ... -     def __getattr__(self, key): -         if key in self.client.http_methods: -             htmlMethod = getattr(self.client, key) -             return partial(htmlMethod, url=self.url) -         else: -             self.url += '/' + str(key) -             return self -     __getitem__ = __getattr__ - class Client: -     http_methods = ('get')  # 还有 post, put, patch 等等。 -     def get(self, url, headers={}, **params): -         return self.request('GET', url, None, headers) - ``` -如果最后一次调用不是 HTTP 方法(如 'get'、'post' 等),则返回带有附加路径的 `IncompleteRequest`。否则,它从[ `Client` 类][16]获取 HTTP 方法对应的正确函数,并返回 `partial`。 +如果最后一次调用不是 HTTP 方法(如 `get`、`post` 等),则返回带有附加路径的 `IncompleteRequest`。否则,它从[`Client` 类][16]获取 HTTP 方法对应的正确函数,并返回 `partial`。 如果我们给出一个不存在的路径会发生什么? + ``` >>> status, data = this.path.doesnt.exist.get() - >>> status - ... 404 - ``` 因为 `__getattr__` 别名为 `__getitem__`: + ``` >>> owner, repo = 'nnja', 'tweeter' - >>> status, data = gh.repos[owner][repo].pulls.get() - >>> # ^ Maps to GET /repos/nnja/tweeter/pulls - >>> data - .... # {....} - ``` -以上是一些认真的方法魔术!(to 校正:这句话真的翻译得不行 ) +这真心是一些方法魔术! ### 了解更多 -Python 提供了大量工具,使你的代码更优雅,更易于阅读和理解。挑战在于找到合适的工具来完成工作,但我希望本文为你的工具箱添加了一些新工具。而且,如果你想更进一步,你可以在我的博客 [nnja.io][17] 上阅读有关装饰器,上下文管理器,上下文生成器和 `NamedTuple(译注:这是命名元组)` 的内容。随着你成为一名更好的 Python 开发人员,我鼓励你到那里阅读一些设计良好的项目的源代码。[Requests][18] 和 [Flask][19] 是两个很好的代码库来开始。 - +Python 提供了大量工具,使你的代码更优雅,更易于阅读和理解。挑战在于找到合适的工具来完成工作,但我希望本文为你的工具箱添加了一些新工具。而且,如果你想更进一步,你可以在我的博客 [nnja.io][17] 上阅读有关装饰器、上下文管理器、上下文生成器和命名元组的内容。随着你成为一名更好的 Python 开发人员,我鼓励你到那里阅读一些设计良好的项目的源代码。[Requests][18] 和 [Flask][19] 是两个很好的起步的代码库。 -------------------------------------------------------------------------------- @@ -508,7 +378,7 @@ via: https://opensource.com/article/18/4/elegant-solutions-everyday-python-probl 作者:[Nina Zakharenko][a] 选题:[lujun9972](https://github.com/lujun9972) 译者:[MjSeven](https://github.com/MjSeven) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 diff --git a/published/20180615 BLUI- An easy way to create game UI.md b/published/20180615 BLUI- An easy way to create game UI.md new file mode 100644 index 0000000000..f6514e9578 --- /dev/null +++ b/published/20180615 BLUI- An easy way to create game UI.md @@ -0,0 +1,59 @@ +BLUI:创建游戏 UI 的简单方法 +====== + +> 开源游戏开发插件运行虚幻引擎的用户使用基于 Web 的编程方式创建独特的用户界面元素。 + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/gaming_plugin_blui_screenshot.jpg?itok=91nnYCt_) + +游戏开发引擎在过去几年中变得越来越易于​​使用。像 Unity 这样一直免费使用的引擎,以及最近从基于订阅的服务切换到免费服务的虚幻引擎Unreal Engine,允许独立开发者使用 AAA 发行商相同达到行业标准的工具。虽然这些引擎都不是开源的,但每个引擎都能够促进其周围的开源生态系统的发展。 + +这些引擎中可以包含插件以允许开发人员通过添加特定程序来增强引擎的基本功能。这些程序的范围可以从简单的资源包到更复杂的事物,如人工智能 (AI) 集成。这些插件来自不同的创作者。有些是由引擎开发工作室和有些是个人提供的。后者中的很多是开源插件。 + +### 什么是 BLUI? + +作为独立游戏开发工作室的一员,我体验到了在专有游戏引擎上使用开源插件的好处。Aaron Shea 开发的一个开源插件 [BLUI][1] 对我们团队的开发过程起到了重要作用。它允许我们使用基于 Web 的编程(如 HTML/CSS 和 JavaScript)创建用户界面 (UI) 组件。尽管虚幻引擎Unreal Engine(我们选择的引擎)有一个实现了类似目的的内置 UI 编辑器,我们也选择使用这个开源插件。我们选择使用开源替代品有三个主要原因:它们的可访问性、易于实现以及伴随的开源程序活跃的、支持性好的在线社区。 + +在虚幻引擎的最早版本中,我们在游戏中创建 UI 的唯一方法是通过引擎的原生 UI 集成,使用 Autodesk 的 Scaleform 程序,或通过在虚幻社区中传播的一些选定的基于订阅的虚幻引擎集成。在这些情况下,这些解决方案要么不能为独立开发者提供有竞争力的 UI 解决方案,对于小型团队来说太昂贵,要么只能为大型团队和 AAA 开发者提供。 + +在商业产品和虚幻引擎的原生整合失败后,我们向独立社区寻求解决方案。我们在那里发现了 BLUI。它不仅与虚幻引擎无缝集成,而且还保持了一个强大且活跃的社区,经常推出更新并确保独立开发人员可以轻松访问文档。BLUI 使开发人员能够将 HTML 文件导入虚幻引擎,并在程序内部对其进行编程。这使得通过 web 语言创建的 UI 能够集成到游戏的代码、资源和其他元素中,并拥有所有 HTML、CSS、Javascript 和其他网络语言的能力。它还为开源 [Chromium Embedded Framework][2] 提供全面支持。 + +### 安装和使用 BLUI + +使用 BLUI 的基本过程包括首先通过 HTML 创建 UI。开发人员可以使用任何工具来实现此目的,包括自举bootstrapped JavaScript 代码、外部 API 或任何数据库代码。一旦这个 HTML 页面完成,你可以像安装任何虚幻引擎插件那样安装它,并加载或创建一个项目。项目加载后,你可以将 BLUI 函数放在虚幻引擎 UI 图纸中的任何位置,或者通过 C++ 进行硬编码。开发人员可以通过其 HTML 页面调用函数,或使用 BLUI 的内部函数轻松更改变量。 + +![Integrating BLUI into Unreal Engine 4 blueprints][4] + +*将 BLUI 集成到虚幻 4 图纸中。* + +在我们当前的项目中,我们使用 BLUI 将 UI 元素与游戏中的音轨同步,为游戏机制的节奏方面提供视觉反馈。将定制引擎编程与 BLUI 插件集成很容易。 + +![Using BLUI to sync UI elements with the soundtrack.][6] + +*使用 BLUI 将 UI 元素与音轨同步。* + +通过 BLUI GitHub 页面上的[文档][7],将 BLUI 集成到虚幻 4 中是一个轻松的过程。还有一个由支持虚幻引擎开发人员组成的[论坛][8],他们乐于询问和回答关于插件以及实现该工具时出现的任何问题。 + +### 开源优势 + +开源插件可以在专有游戏引擎的范围内扩展创意。他们继续降低进入游戏开发的障碍,并且可以产生前所未有的游戏内的机制和资源。随着对专有游戏开发引擎的访问持续增长,开源插件社区将变得更加重要。不断增长的创造力必将超过专有软件,开源代码将会填补这些空白,并促进开发真正独特的游戏。而这种新颖性正是让独立游戏如此美好的原因! + +-------------------------------------------------------------------------------- + +via: https://opensource.com/article/18/6/blui-game-development-plugin + +作者:[Uwana lkaiddi][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[geekpi](https://github.com/geekpi) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://opensource.com/users/uwikaiddi +[1]:https://github.com/AaronShea/BLUI +[2]:https://bitbucket.org/chromiumembedded/cef +[3]:/file/400616 +[4]:https://opensource.com/sites/default/files/uploads/blui_gaming_plugin-integratingblui.png (Integrating BLUI into Unreal Engine 4 blueprints) +[5]:/file/400621 +[6]:https://opensource.com/sites/default/files/uploads/blui_gaming_plugin-syncui.png (Using BLUI to sync UI elements with the soundtrack.) +[7]:https://github.com/AaronShea/BLUI/wiki +[8]:https://forums.unrealengine.com/community/released-projects/29036-blui-open-source-html5-js-css-hud-ui diff --git a/sources/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md b/sources/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md deleted file mode 100644 index 04a25a5cc1..0000000000 --- a/sources/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md +++ /dev/null @@ -1,1031 +0,0 @@ -BriFuture is translating - -The Extensive Guide to Creating Streams in RxJS -============================================================ - -![](https://cdn-images-1.medium.com/max/900/1*hj8mGnl5tM_lAlx5_vqS-Q.jpeg) - -For most developers the first contact with RxJS is established by libraries, like Angular. Some functions return streams and to make use of them the focus naturally is on operators. - -At some point mixing reactive and some of the non-reactive code seems practical. Then people get interested in creating streams themselves. Whenever you are dealing with asynchronous code or data processing, chances are that streams are a good option. - -RxJS offers numerous ways to create streams. Whatever situation you are facing, there is one perfect way for you to create a stream. You may not need them all, but knowing them can save you time and some code. - -I have put all possible options into four categories, based on their main purpose: - -* Stream existing data - -* Generate data - -* Interoperate with existing APIs - -* Combine and select from existing streams - -Note: The examples use RxJS 6 and can be different from older versions. Something that’s different for sure is the way you import the functions. - -RxJS 6 - -``` -import {of, from} from 'rxjs'; -``` - -``` -of(...); -from(...); -``` - -RxJS < 6 - -``` -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import 'rxjs/add/observable/from'; -``` - -``` -Observable.of(...); -Observable.from(...); -``` - -``` -//or -``` - -``` -import { of } from 'rxjs/observable/of'; -import { from } from 'rxjs/observable/from'; -``` - -``` -of(...); -from(...); -``` - -A note on the stream diagrams: - -* | means the stream completes - -* X means the stream terminates with an error - -* … means the stream goes on indefinitely - -* * * - -### Stream Existing Data - -You have some data and want to feed them into a stream. There are three flavors, all of which also allow you to provide a scheduler as the last argument (If you want to know more about schedulers you can take a look at my [previous article][5]). All resulting streams will be cold. - -#### of - -Use  _of_  if you have only one element or a few separate ones. - -``` -of(1,2,3) - .subscribe(); -``` - -``` -// Produces -// 1 2 3 | -``` - -#### from - -Use  _from_  if you have an array or  _Iterable_  and want all elements in it emitted to the stream. You can also use it to convert a promise to an observable. - -``` -const foo = [1,2,3]; -``` - -``` -from(foo) - .subscribe(); -``` - -``` -// Produces -// 1 2 3 | -``` - -#### pairs - -Streams key/value pairs of an object. Particularly useful if the object represents a dictionary. - -``` -const foo = { a: 1, b: 2}; -``` - -``` -pairs(foo) - .subscribe(); -``` - -``` -// Produces -// [a,1] [b,2] | -``` - -#### What about other data structures? - -Maybe your data is stored in a custom structure that does not implement the  _Iterable_  protocol or you have a recursive, tree-like structure. In those cases one of following options might be suitable: - -* Extract data to an array first - -* Use the  _generate_  function from the next section to iterate over the data - -* Create a custom stream (see that section) - -* Create an iterator - -Options 2 and 3 are explained later, so I focus on creating an iterator here. We can create a stream from an  _iterable_  by calling  _from_ . An  _iterable_  is an object that can deliver an iterator (see [this mdn article][6] if you are interested in the details). - -One simple way to create an iterator is a [generator function][7]. When you invoke a generator function, it returns an object that conforms to both the  _iterable_ protocol and the  _iterator_  protocol. - -``` -//Our custom data structure -class List { - add(element) ... - get(index) ... - get size() ... - ... -} -``` - -``` -function* listIterator(list) { - for (let i = 0; i console.log("foo"); -//prints foo after 5 seconds -``` - -Most of the time interval will be used to process data periodically: - -``` -interval(10000).pipe( - flatMap(i => fetch("https://server/stockTicker") -).subscribe(updateChart) -``` - -This will get new data every 10 seconds and update the screen. - -#### generate - -A more complex function that allows you to emit a sequence of any type. It has some overloads and I show you the most interesting one(s): - -``` -generate( - 0, // start with this value - x => x < 10, // condition: emit as long as a value is less than 10 - x => x*2 // iteration: double the previous value -).subscribe(); -``` - -``` -// Produces -// 1 2 4 8 | -``` - -You can also use it to iterate over values, if a structure does not implement the  _Iterable_  interface. Let’s try that with our list example from before: - -``` -const myList = new List(); -myList.add(1); -myList.add(3); -``` - -``` -generate( - 0, // start with this value - i => i < list.size, // condition: emit until we have processed the whole list - i => ++i, // iteration: get next index - i => list.get(i) // selection: get value from list -).subscribe(); -``` - -``` -// Produces -// 1 3 | -``` - -As you can see I have added another argument: The selector. It works like the  _map_  operator and converts the generated value to something more useful. - -* * * - -### Empty Streams - -Sometimes you need to pass or return a stream that does not emit any data. There are three functions, one for every possible situation. You can pass a scheduler to all functions.  _empty_  and  _throwError_  accept a scheduler as argument. - -#### empty - -Creates a stream that completes without emitting a value. - -``` -empty() - .subscribe(); -``` - -``` -// Produces -// | -``` - -#### never - -Creates a stream that never completes, but also never emits anything. - -``` -never() - .subscribe(); -``` - -``` -// Produces -// ... -``` - -#### throwError - -Creates a stream that fails without emitting a value. - -``` -throwError('error') - .subscribe(); -``` - -``` -// Produces -// X -``` - -* * * - -### Hook into existing APIs - -Not all libraries and all of your legacy code use or support streams. Luckily RxJS provides functions to bridge non-reactive and reactive code. This section discusses only patterns provided by RxJS for exactly that purpose. - -You may also be interested in this [extensive article][8] from [Ben Lesh][9] covering probably every possible way to interoperate with promises. - -#### from - -We already had that and I list it here too because it can be used to wrap a promise into an observable. - -``` -from(new Promise(resolve => resolve(1))) - .subscribe(); -``` - -``` -// Produces -// 1 | -``` - -#### fromEvent - -Adds an event listener to a DOM element and I am pretty sure you know that. What you may not know is that you can also use it with other types, e.g. a jQuery object. - -``` -const element = $('#fooButton'); // creates a jQuery object for a DOM element -``` - -``` -from(element, 'click') - .subscribe(); -``` - -``` -// Produces -// clickEvent ... -``` - -#### fromEventPattern - -In order to understand why we need this one if we already have fromEvent, we need to understand how fromEvent works. Take this code: - -``` -from(document, 'click') - .subscribe(); -``` - -It tells RxJS that we want to listen to click events from the document. During subscription RxJS finds out that document is an  _EventTarget_  type, hence it can call  _addEventListener_  on it. If we pass a jQuery object instead of document, then RxJS knows that it has to call  _on_  instead. - -This example using  _fromEventPattern_  basically does the same as  _fromEvent_ : - -``` -function addClickHandler(handler) { - document.addEventListener('click', handler); -} -``` - -``` -function removeClickHandler(handler) { - document.removeEventListener('click', handler); -} -``` - -``` -fromEventPattern( - addClickHandler, - removeClickHandler, -) -.subscribe(console.log); -``` - -``` -//is equivalent to -fromEvent(document, 'click') -``` - -RxJS itself creates the actual listener ( _handler_ ) and your job is to add and remove it. The purpose of  _fromEventPattern_  is basically to tell RxJS how to register and remove event listeners. - -Now imagine you use a library where you have to call a method named  _registerListener_ . We no longer can use  _fromEvent_  because it doesn’t know how to deal with it. - -``` -const listeners = []; -``` - -``` -class Foo { - registerListener(listener) { - listeners.push(listener); - } -``` - -``` - emit(value) { - listeners.forEach(listener => listener(value)); - } -} -``` - -``` -const foo = new Foo(); -``` - -``` -fromEventPattern(listener => foo.registerListener(listener)) - .subscribe(); -``` - -``` -foo.emit(1); -``` - -``` -// Produces -// 1 ... -``` - -When we call foo.emit(1) the listener from RxJS is called and it can emit the value to the stream. - -You could also use it to listen to more than one event type or connect with any API that communicates via callbacks, e.g. the WebWorker API: - -``` -const myWorker = new Worker('worker.js'); -``` - -``` -fromEventPattern( - handler => { myWorker.onmessage = handler }, - handler => { myWorker.onmessage = undefined } -) -.subscribe(); -``` - -``` -// Produces -// workerMessage ... -``` - -#### bindCallback - -This is similar to fromEventPattern, but it’s only meant for single values. That is the stream completes after the callback has been invoked . The usage is different as well – You wrap the function with bindCallback, then it magically returns a stream when it‘s called: - -``` -function foo(value, callback) { - callback(value); -} -``` - -``` -// without streams -foo(1, console.log); //prints 1 in the console -``` - -``` -// with streams -const reactiveFoo = bindCallback(foo); -//when we call reactiveFoo it returns an observable -``` - -``` -reactiveFoo(1) - .subscribe(console.log); //prints 1 in the console -``` - -``` -// Produces -// 1 | -``` - -#### websocket - -Yes, you can actually create a websocket connection and expose it as stream: - -``` -import { webSocket } from 'rxjs/webSocket'; -``` - -``` -let socket$ = webSocket('ws://localhost:8081'); -``` - -``` -//receive messages -socket$.subscribe( - (msg) => console.log('message received: ' + msg), - (err) => console.log(err), - () => console.log('complete') * ); -``` - -``` -//send message -socket$.next(JSON.stringify({ op: 'hello' })); -``` - -It’s really that easy to add websocket support to your application.  _websocket_ creates a subject. That means you can both subscribe to it in order to receive messages and send messages through it by calling  _next_ . - -#### ajax - -Just so you know it: Similar to websocket and offers support for AJAX requests. You probably use a library or framework with built-in AJAX support anyway. And if you don’t then I recommend using fetch (and a polyfill if necessary) instead and wrap the returned promise into an observable (see also the  _defer_  function below). - -* * * - -### Custom Streams - -Sometimes the already presented functions are not flexible enough. Or you need more control over subscriptions. - -#### Subject - -A subject is a special object that allows you to emit data to the stream and control it. The subject itself is also an observable, but if you want to expose the stream to other code it’s recommended to use the  _asObservable_  method. That way you cannot accidentally call the source methods. - -``` -const subject = new Subject(); -const observable = subject.asObservable(); -``` - -``` -observable.subscribe(); -``` - -``` -subject.next(1); -subject.next(2); -subject.complete(); -``` - -``` -// Produces -// 1 2 | -``` - -Note that values emitted before subscriptions are “lost”: - -``` -const subject = new Subject(); -const observable = subject.asObservable(); -``` - -``` -subject.next(1); -``` - -``` -observable.subscribe(console.log); -``` - -``` -subject.next(2); -subject.complete(); -``` - -``` -// Prints -// 2 -``` - -In addition to the regular subject RxJS provides three specialized versions. - -The  _AsyncSubject_  emits only the last value after completion. - -``` -const subject = new AsyncSubject(); -const observable = subject.asObservable(); -``` - -``` -observable.subscribe(console.log); -``` - -``` -subject.next(1); -subject.next(2); -subject.complete(); -``` - -``` -// Prints -// 2 -``` - -The  _BehaviorSubject_  allows you to provide a (default) value that will be emitted to every subscriber if no other value has been emitted so far. Otherwise subscribers receive the last emitted value. - -``` -const subject = new BehaviorSubject(1); -const observable = subject.asObservable(); -``` - -``` -const subscription1 = observable.subscribe(console.log); -``` - -``` -subject.next(2); -subscription1.unsubscribe(); -``` - -``` -// Prints -// 1 -// 2 -``` - -``` -const subscription2 = observable.subscribe(console.log); -``` - -``` -// Prints -// 2 -``` - -The  _ReplaySubject_  stores all emitted values up to a certain number, time or infinitely. All new subscribers will then get all stored values. - -``` -const subject = new ReplaySubject(); -const observable = subject.asObservable(); -``` - -``` -subject.next(1); -``` - -``` -observable.subscribe(console.log); -``` - -``` -subject.next(2); -subject.complete(); -``` - -``` -// Prints -// 1 -// 2 -``` - -You can find more information on subjects in the [ReactiveX documentation][10](that also offers additional links). [Ben Lesh][11] offers some insights on subjects in [On The Subject Of Subjects][12], as does [Nicholas Jamieson][13] [in RxJS: Understanding Subjects][14]. - -#### Observable - -You can create an observable by simply using the the new operator. With the function you pass in you can control the stream. That function is called whenever someone subscribe and it receives an observer that you can use like a subject, i.e. call next, complete and error. - -Let’s revisit our list example: - -``` -const myList = new List(); -myList.add(1); -myList.add(3); -``` - -``` -new Observable(observer => { - for (let i = 0; i { - //stream it, baby! -``` - -``` - return () => { - //clean up - }; -}) -.subscribe(); -``` - -#### Subclass Observable - -Before the advent of lettable operators this was a way to implement custom operators. RxJS extends  _Observable_  internally. One example is  _Subject_ , another is the  _publish_  operator. It returns a  _ConnectableObservable_  that provides the additional method  _connect_ . - -#### Implement Subscribable - -Sometimes you already have an object that holds state and can emit values. You can turn it into an observable if you implement the Subscribable interface that consists of only a subscribe method. - -``` -interface Subscribable { subscribe(observerOrNext?: PartialObserver | ((value: T) => void), error?: (error: any) => void, complete?: () => void): Unsubscribable} -``` - -* * * - -### Combine and Select Existing Streams - -Knowing how to create individual streams is not enough. Sometimes you are confronted with several streams but you only need one. Some of the functions are also available as operators, that’s why I won’t go too deep here. I can recommend an [article][15] from [Max NgWizard K][16] that even contains some fancy animations. - -One more recommendation: You can interactively play with combination operators on [RxMarbles][17] by dragging around elements. - -#### The ObservableInput type - -Operators and functions that expect a stream (or an array of streams) usually do not only work with observables. Instead they actually expect the argument to be of the type ObservableInput that is defined as follows: - -``` -type ObservableInput = SubscribableOrPromise | ArrayLike | Iterable; -``` - -That means you can e.g. pass promises or arrays without needing to convert them to observables first! - -#### defer - -The main purpose is to defer the creation of an observable to the time when someone wants to subscribe. This is useful if - -* the creation of the observable is computationally expensive - -* you want a new observable for each subscriber - -* you want to choose between different observables at subscription time - -* some code must not be executed before subscription - -The last point includes one not so obvious use case: Promises (defer can also return a promise). Take this example using the fetch API: - -``` -function getUser(id) { - console.log("fetching data"); - return fetch(`https://server/user/${id}`); -} -``` - -``` -const userPromise = getUser(1); -console.log("I don't want that request now"); -``` - -``` -//somewhere else -userPromise.then(response => console.log("done"); -``` - -``` -// Prints -// fetching data -// I don't want that request now -// done -``` - -Promises are executed immediately, whereas streams are executed when you subscribe. The very moment we call getUser, a request is sent even if we did not want that at that point. Sure, we can use from to convert a promise to an observable, but the promise we pass has already been created / executed. defer allows us to wait until subscription: - -``` -const user$ = defer(() => getUser(1)); -``` - -``` -console.log("I don't want that request now"); -``` - -``` -//somewhere else -user$.subscribe(response => console.log("done"); -``` - -``` -// Prints -// I don't want that request now -// fetching data -// done -``` - -#### iif - - _iif_  covers a special use case of  _defer_ : Deciding between two streams at subscription time: - -``` -iif( - () => new Date().getHours() < 12, - of("AM"), - of("PM") -) -.subscribe(); -``` - -``` -// Produces -// AM before noon, PM afterwards -``` - -To quote the documentation: - -> Actually `[iif][3]` can be easily implemented with `[defer][4]`and exists only for convenience and readability reasons. - -#### onErrorResumeNext - -Starts the first stream and if it fails continues with the next stream. The error is ignored. - -``` -const stream1$ = of(1, 2).pipe( - tap(i => { if(i>1) throw 'error'}) //fail after first element -); -``` - -``` -const stream2$ = of(3,4); -``` - -``` -onErrorResumeNext(stream1$, stream2$) - .subscribe(console.log); -``` - -``` -// Produces -// 1 3 4 | -``` - -This can be useful if you have more than one web service. In case the main one fails the backup service can be called automatically. - -#### forkJoin - -Lets streams run concurrently and emits their last values in an array when they are completed. Since only the last values of each streams are emitted it’s typically used for streams that only emit a single element, like HTTP requests. You want the requests run in parallel and do something when all have responses. - -``` -function handleResponses([user, account]) { - // do something -} -``` - -``` -forkJoin( - fetch("https://server/user/1"), - fetch("https://server/account/1") -) -.subscribe(handleResponses); -``` - -#### merge / concat - -Emits every value that is emitted by one of the source observables. - - _merge_  supports a parameter that let’s you define how many source streams are subscribed to concurrently. The default is unlimited. A value of 1 would mean listen to one source stream and when it’s completed subscribe to the next one. Since that is a very common scenario you RxJS provides an explicit function:  _concat_ . - -``` -merge( - interval(1000).pipe(mapTo("Stream 1"), take(2)), - interval(1200).pipe(mapTo("Stream 2"), take(2)), - timer(0, 1000).pipe(mapTo("Stream 3"), take(2)), - 2 //two concurrent streams -) -.subscribe(); -``` - -``` -// Subscribes to stream 1 and 2 only -``` - -``` -// prints -// Stream 1 -> after 1000ms -// Stream 2 -> after 1200ms -// Stream 1 -> after 2000ms -``` - -``` -// Stream 1 has completed, now subscribe to stream 3 -``` - -``` -// prints -// Stream 3 -> after 0 ms -// Stream 2 -> after 400 ms (2400ms from beginning) -// Stream 3 -> after 1000ms -``` - -``` - -merge( - interval(1000).pipe(mapTo("Stream 1"), take(2)), - interval(1200).pipe(mapTo("Stream 2"), take(2)) - 1 -) -// is equal to -concat( - interval(1000).pipe(mapTo("Stream 1"), take(2)), - interval(1200).pipe(mapTo("Stream 2"), take(2)) -) -``` - -``` -// prints -// Stream 1 -> after 1000ms -// Stream 1 -> after 2000ms -// Stream 2 -> after 3200ms -// Stream 2 -> after 4400ms -``` - -#### zip / combineLatest - -While  _merge_  and  _concat_  emit all values from the source streams individually, zip and combineLatest combine one value of each source stream and emit them together.  _zip_  combines the first values emitted by all(!) source streams, the second values and so on. This is useful if the contents of the streams are related. - -``` -zip( - interval(1000), - interval(1200), -) -.subscribe(); -``` - -``` -// Produces -// [0, 0] [1, 1] [2, 2] ... -``` - - _combineLatest_  is similar but combines the latest values emitted by the source streams. Nothing happens until all source streams have emitted at least one value. From then on every time a source stream emits a value, it is combined with the last values of the other streams. - -``` -combineLatest( - interval(1000), - interval(1200), -) -.subscribe(); -``` - -``` -// Produces -// [0, 0] [1, 0] [1, 1] [2, 1] ... -``` - -Both functions allow you to pass a selector function that can combine the elements to something else than an array: - -``` -zip( - interval(1000), - interval(1200), - (e1, e2) -> e1 + e2 -) -.subscribe(); -``` - -``` -// Produces -// 0 2 4 6 ... -``` - -#### race - -The first stream that emits a value is selected. So the resulting stream is essentially the fastest stream. - -``` -race( - interval(1000), - of("foo") -) -.subscribe(); -``` - -``` -// Produces -// foo | -``` - -Since  _of_  produces a value immediately it’s the faster stream and the stream that gets selected. - -* * * - -### Conclusion - -That have been a lot of ways to create observables. Knowing them is essential if you want to create reactive APIs or want to combine legacy APIs with reactive ones. - -I have presented you all options but much more could be said about all of them. If you want to dive deeper I highly recommend consulting the [documentation][20] or reading the suggested articles. - -Another interesting way to get insight is [RxViz][21]. You write RxJS code and the resulting streams are then displayed graphically and animated. - --------------------------------------------------------------------------------- - -via: https://blog.angularindepth.com/the-extensive-guide-to-creating-streams-in-rxjs-aaa02baaff9a - -作者:[Oliver Flaggl][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://blog.angularindepth.com/@abetteroliver -[1]:https://rxjs-dev.firebaseapp.com/api/index/Subscribable -[2]:https://rxjs-dev.firebaseapp.com/api/index/Subscribable#subscribe -[3]:https://rxjs-dev.firebaseapp.com/api/index/iif -[4]:https://rxjs-dev.firebaseapp.com/api/index/defer -[5]:https://itnext.io/concurrency-and-asynchronous-behavior-with-rxjs-11b0c4b22597 -[6]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols -[7]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* -[8]:https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875 -[9]:https://medium.com/@benlesh -[10]:http://reactivex.io/documentation/subject.html -[11]:https://medium.com/@benlesh -[12]:https://medium.com/@benlesh/on-the-subject-of-subjects-in-rxjs-2b08b7198b93 -[13]:https://medium.com/@cartant -[14]:https://blog.angularindepth.com/rxjs-understanding-subjects-5c585188c3e1 -[15]:https://blog.angularindepth.com/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams-20fce8e6511 -[16]:https://medium.com/@maximus.koretskyi -[17]:http://rxmarbles.com/#merge -[18]:https://rxjs-dev.firebaseapp.com/api/index/ObservableInput -[19]:https://rxjs-dev.firebaseapp.com/api/index/SubscribableOrPromise -[20]:http://reactivex.io/documentation/operators.html#creating -[21]:https://rxviz.com/ diff --git a/sources/tech/20180522 How to Run Your Own Git Server.md b/sources/tech/20180522 How to Run Your Own Git Server.md deleted file mode 100644 index 9a1ee8509a..0000000000 --- a/sources/tech/20180522 How to Run Your Own Git Server.md +++ /dev/null @@ -1,233 +0,0 @@ -translating by wyxplus -How to Run Your Own Git Server -====== -**Learn how to set up your own Git server in this tutorial from our archives.** - -[Git ][1]is a versioning system [developed by Linus Torvalds][2], that is used by millions of users around the globe. Companies like GitHub offer code hosting services based on Git. [According to reports, GitHub, a code hosting site, is the world's largest code hosting service.][3] The company claims that there are 9.2M people collaborating right now across 21.8M repositories on GitHub. Big companies are now moving to GitHub. [Even Google, the search engine giant, is shutting it's own Google Code and moving to GitHub.][4] - -### Run your own Git server - -GitHub is a great service, however there are some limitations and restrictions, especially if you are an individual or a small player. One of the limitations of GitHub is that the free service doesn’t allow private hosting of the code. [You have to pay a monthly fee of $7 to host 5 private repositories][5], and the expenses go up with more repos. - -In cases like these or when you want more control, the best path is to run Git on your own server. Not only do you save costs, you also have more control over your server. In most cases a majority of advanced Linux users already have their own servers and pushing Git on those servers is like ‘free as in beer’. - -In this tutorial we are going to talk about two methods of managing your code on your own server. One is running a bare, basic Git server and and the second one is via a GUI tool called [GitLab][6]. For this tutorial I used a fully patched Ubuntu 14.04 LTS server running on a VPS. - -### Install Git on your server - -In this tutorial we are considering a use-case where we have a remote server and a local server and we will work between these machines. For the sake of simplicity we will call them remote-server and local-server. - -First, install Git on both machines. You can install Git from the packages already available via the repos or your distros, or you can do it manually. In this article we will use the simpler method: -``` -sudo apt-get install git-core - -``` - -Then add a user for Git. -``` -sudo useradd git -passwd git - -``` - -In order to ease access to the server let's set-up a password-less ssh login. First create ssh keys on your local machine: -``` -ssh-keygen -t rsa - -``` - -It will ask you to provide the location for storing the key, just hit Enter to use the default location. The second question will be to provide it with a pass phrase which will be needed to access the remote server. It generates two keys - a public key and a private key. Note down the location of the public key which you will need in the next step. - -Now you have to copy these keys to the server so that the two machines can talk to each other. Run the following command on your local machine: -``` -cat ~/.ssh/id_rsa.pub | ssh git@remote-server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" - -``` - -Now ssh into the server and create a project directory for Git. You can use the desired path for the repo. - -Then change to this directory: -``` -cd /home/swapnil/project-1.git - -``` - -Then create an empty repo: -``` -git init --bare -Initialized empty Git repository in /home/swapnil/project-1.git - -``` - -We now need to create a Git repo on the local machine. -``` -mkdir -p /home/swapnil/git/project - -``` - -And change to this directory: -``` -cd /home/swapnil/git/project - -``` - -Now create the files that you need for the project in this directory. Stay in this directory and initiate git: -``` -git init -Initialized empty Git repository in /home/swapnil/git/project - -``` - -Now add files to the repo: -``` -git add . - -``` - -Now every time you add a file or make changes you have to run the add command above. You also need to write a commit message with every change in a file. The commit message basically tells what changes were made. -``` -git commit -m "message" -a -[master (root-commit) 57331ee] message - 2 files changed, 2 insertions(+) - create mode 100644 GoT.txt - create mode 100644 writing.txt - -``` - -In this case I had a file called GoT (Game of Thrones review) and I made some changes, so when I ran the command it specified that changes were made to the file. In the above command '-a' option means commits for all files in the repo. If you made changes to only one you can specify the name of that file instead of using '-a'. - -An example: -``` -git commit -m "message" GoT.txt -[master e517b10] message - 1 file changed, 1 insertion(+) - -``` - -Until now we have been working on the local server. Now we have to push these changes to the server so the work is accessible over the Internet and you can collaborate with other team members. -``` -git remote add origin ssh://git@remote-server/repo->path-on-server..git - -``` - -Now you can push or pull changes between the server and local machine using the 'push' or 'pull' option: -``` -git push origin master - -``` - -If there are other team members who want to work with the project they need to clone the repo on the server to their local machine: -``` -git clone git@remote-server:/home/swapnil/project.git - -``` - -Here /home/swapnil/project.git is the project path on the remote server, exchange the values for your own server. - -Then change directory on the local machine (exchange project with the name of project on your server): -``` -cd /project - -``` - -Now they can edit files, write commit change messages and then push them to the server: -``` -git commit -m 'corrections in GoT.txt story' -a -And then push changes: - -git push origin master - -``` - -I assume this is enough for a new user to get started with Git on their own servers. If you are looking for some GUI tools to manage changes on local machines, you can use GUI tools such as QGit or GitK for Linux. - -### Using GitLab - -This was a pure command line solution for project owner and collaborator. It's certainly not as easy as using GitHub. Unfortunately, while GitHub is the world's largest code hosting service; its own software is not available for others to use. It's not open source so you can't grab the source code and compile your own GitHub. Unlike WordPress or Drupal you can't download GitHub and run it on your own servers. - -As usual in the open source world there is no end to the options. GitLab is a nifty project which does exactly that. It's an open source project which allows users to run a project management system similar to GitHub on their own servers. - -You can use GitLab to run a service similar to GitHub for your team members or your company. You can use GitLab to work on private projects before releasing them for public contributions. - -GitLab employs the traditional Open Source business model. They have two products: free of cost open source software, which users can install on their own servers, and a hosted service similar to GitHub. - -The downloadable version has two editions - the free of cost community edition and the paid enterprise edition. The enterprise edition is based on the community edition but comes with additional features targeted at enterprise customers. It’s more or less similar to what WordPress.org or Wordpress.com offer. - -The community edition is highly scalable and can support 25,000 users on a single server or cluster. Some of the features of GitLab include: Git repository management, code reviews, issue tracking, activity feeds, and wikis. It comes with GitLab CI for continuous integration and delivery. - -Many VPS providers such as Digital Ocean offer GitLab droplets for users. If you want to run it on your own server, you can install it manually. GitLab offers an Omnibus package for different operating systems. Before we install GitLab, you may want to configure an SMTP email server so that GitLab can push emails as and when needed. They recommend Postfix. So, install Postfix on your server: -``` -sudo apt-get install postfix - -``` - -During installation of Postfix it will ask you some questions; don't skip them. If you did miss it you can always re-configure it using this command: -``` -sudo dpkg-reconfigure postfix - -``` - -When you run this command choose "Internet Site" and provide the email ID for the domain which will be used by Gitlab. - -In my case I provided it with: -``` -This e-mail address is being protected from spambots. You need JavaScript enabled to view it - - -``` - -Use Tab and create a username for postfix. The Next page will ask you to provide a destination for mail. - -In the rest of the steps, use the default options. Once Postfix is installed and configured, let's move on to install GitLab. - -Download the packages using wget (replace the download link with the [latest packages from here][7]) : -``` -wget https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.4-omnibus.1-1_amd64.deb - -``` - -Then install the package: -``` -sudo dpkg -i gitlab_7.9.4-omnibus.1-1_amd64.deb - -``` - -Now it's time to configure and start GitLabs. -``` -sudo gitlab-ctl reconfigure - -``` - -You now need to configure the domain name in the configuration file so you can access GitLab. Open the file. -``` -nano /etc/gitlab/gitlab.rb - -``` - -In this file edit the 'external_url' and give the server domain. Save the file and then open the newly created GitLab site from a web browser. - -By default it creates 'root' as the system admin and uses '5iveL!fe' as the password. Log into the GitLab site and then change the password. - -Once the password is changed, log into the site and start managing your project. - -GitLab is overflowing with features and options. I will borrow popular lines from the movie, The Matrix: "Unfortunately, no one can be told what all GitLab can do. You have to try it for yourself." - --------------------------------------------------------------------------------- - -via: https://www.linux.com/learn/how-run-your-own-git-server - -作者:[Swapnil Bhartiya][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://www.linux.com/users/arnieswap -[1]:https://github.com/git/git -[2]:https://www.linuxfoundation.org/blog/10-years-of-git-an-interview-with-git-creator-linus-torvalds/ -[3]:https://github.com/about/press -[4]:http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html -[5]:https://github.com/pricing -[6]:https://about.gitlab.com/ -[7]:https://about.gitlab.com/downloads/ diff --git a/sources/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md b/sources/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md deleted file mode 100644 index 05057f6669..0000000000 --- a/sources/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md +++ /dev/null @@ -1,65 +0,0 @@ -translating---geekpi - -4 add-ons to improve your privacy on Thunderbird -====== - -![](https://fedoramagazine.org/wp-content/uploads/2017/08/tb-privacy-addons-816x345.jpg) -Thunderbird is a popular free email client developed by [Mozilla][1]. Similar to Firefox, Thunderbird offers a large choice of add-ons for extra features and customization. This article focuses on four add-ons to improve your privacy. - -### Enigmail - -Encrypting emails using GPG (GNU Privacy Guard) is the best way to keep their contents private. If you aren’t familiar with GPG, [check out our primer right here][2] on the Magazine. - -[Enigmail][3] is the go-to add-on for using OpenPGP with Thunderbird. Indeed, Enigmail integrates well with Thunderbird, and lets you encrypt, decrypt, and digitally sign and verify emails. - -### Paranoia - -[Paranoia][4] gives you access to critical information about your incoming emails. An emoticon shows the encryption state between servers an email traveled through before reaching your inbox. - -A yellow, happy emoticon tells you all connections were encrypted. A blue, sad emoticon means one connection was not encrypted. Finally, a red, scared emoticon shows on more than one connection the message wasn’t encrypted. - -More details about these connections are available, so you can check which servers were used to deliver the email. - -### Sensitivity Header - -[Sensitivity Header][5] is a simple add-on that lets you select the privacy level of an outgoing email. Using the option menu, you can select a sensitivity: Normal, Personal, Private and Confidential. - -Adding this header doesn’t add extra security to email. However, some email clients or mail transport/user agents (MTA/MUA) can use this header to process the message differently based on the sensitivity. - -Note that this add-on is marked as experimental by its developers. - -### TorBirdy - -If you’re really concerned about your privacy, [TorBirdy][6] is the add-on for you. It configures Thunderbird to use the [Tor][7] network. - -TorBirdy offers less privacy on email accounts that have been used without Tor before, as noted in the [documentation][8]. - -> Please bear in mind that email accounts that have been used without Tor before offer **less** privacy/anonymity/weaker pseudonyms than email accounts that have always been accessed with Tor. But nevertheless, TorBirdy is still useful for existing accounts or real-name email addresses. For example, if you are looking for location anonymity — you travel a lot and don’t want to disclose all your locations by sending emails — TorBirdy works wonderfully! - -Note that to use this add-on, you must have Tor installed on your system. - -Photo by [Braydon Anderson][9] on [Unsplash][10]. - - --------------------------------------------------------------------------------- - -via: https://fedoramagazine.org/4-addons-privacy-thunderbird/ - -作者:[Clément Verna][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://fedoramagazine.org -[1]:https://www.mozilla.org/en-US/ -[2]:https://fedoramagazine.org/gnupg-a-fedora-primer/ -[3]:https://addons.mozilla.org/en-US/thunderbird/addon/enigmail/ -[4]:https://addons.mozilla.org/en-US/thunderbird/addon/paranoia/?src=cb-dl-users -[5]:https://addons.mozilla.org/en-US/thunderbird/addon/sensitivity-header/?src=cb-dl-users -[6]:https://addons.mozilla.org/en-US/thunderbird/addon/torbirdy/?src=cb-dl-users -[7]:https://www.torproject.org/ -[8]:https://trac.torproject.org/projects/tor/wiki/torbirdy -[9]:https://unsplash.com/photos/wOHH-NUTvVc?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText -[10]:https://unsplash.com/search/photos/privacy?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText diff --git a/translated/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md b/translated/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md new file mode 100644 index 0000000000..66b0e920e9 --- /dev/null +++ b/translated/tech/20170709 The Extensive Guide to Creating Streams in RxJS.md @@ -0,0 +1,1029 @@ +在 RxJS 中创建流的延伸教程 +============================================================ + +![](https://cdn-images-1.medium.com/max/900/1*hj8mGnl5tM_lAlx5_vqS-Q.jpeg) + +对大多数开发者来说,RxJS 是以库的形式与之接触,就像 Angular。一些函数会返回流,要使用它们就得把注意力放在操作符上。 + +有些时候,混用响应式和非响应式代码似乎很有用。然后大家就开始热衷流的创造。不论是在编写异步代码或者是数据处理时,流都是一个不错的方案。 + +RxJS 提供很多方式来创建流。不管你遇到的是什么情况,都会有一个完美的创建流的方式。你可能根本用不上它们,但了解它们可以节省你的时间,让你少码一些代码。 + +我把所有可能的方法,按它们的主要目的,分放在四个目录中: + +* 流式化现有数据 + +* 生成数据 + +* 使用现有 APIs 进行交互 + +* 选择现有的流,并结合起来 + +注意:示例用的是 RxJS 6,可能会以前的版本有所不同。已知的区别是你导入函数的方式不同了。 + +RxJS 6 + +``` +import {of, from} from 'rxjs'; +``` + +``` +of(...); +from(...); +``` + +RxJS < 6 + +``` +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/observable/from'; +``` + +``` +Observable.of(...); +Observable.from(...); +``` + +``` +//or +``` + +``` +import { of } from 'rxjs/observable/of'; +import { from } from 'rxjs/observable/from'; +``` + +``` +of(...); +from(...); +``` + +流的图示中的标记: + +* | 表示流结束了 + +* X 表示流出现错误并被终结 + +* … 表示流的走向不定 + +* * * + +### 流式化已有数据 + +你有一些数据,想把它们放到流中。有三种方式,并且都允许你把调度器当作最后一个参数传入(你如果想深入了解调度器,可以看看我的 [上一篇文章][5])。这些生成的流都是静态的。 + +#### of + +如果只有一个或者一些不同的元素,使用 _of_ : + +``` +of(1,2,3) + .subscribe(); +``` + +``` +// 结果 +// 1 2 3 | +``` + +#### from + +如果有一个数组或者 _可迭代的_ 对象,而且你想要其中的所有元素发送到流中,使用 _from_。你也可以用它来把一个 promise 对象变成可观测的。 + +``` +const foo = [1,2,3]; +``` + +``` +from(foo) + .subscribe(); +``` + +``` +// 结果 +// 1 2 3 | +``` + +#### pairs + +流式化一个对象的键/值对。用这个对象表示字典时特别有用。 + +``` +const foo = { a: 1, b: 2}; +``` + +``` +pairs(foo) + .subscribe(); +``` + +``` +// 结果 +// [a,1] [b,2] | +``` + +#### 那么其他的数据结构呢? + +也许你的数据存储在自定义的结构中,而它又没有实现 _Iterable_ 接口,又或者说你的结构是递归的,树状的。也许下面某种选择适合这些情况: + +* 先将数据提取到数组里 + +* 使用下一节将会讲到的 _generate_ 函数,遍历所有数据 + +* 创建一个自定义流(见下一节) + +* 创建一个迭代器 + +稍后会讲到选项 2 和 3 ,因此这里的重点是创建一个迭代器。我们可以对一个 _iterable_ 对象调用 _from_ 创建一个流。 _iterable_ 是一个对象,可以产生一个迭代器(如果你对细节感兴趣,参考 [这篇 mdn 文章][6])。 + +创建一个迭代器的简单方式是 [generator function][7]。当你调用一个生成函数(generator function)时,它返回一个对象,该对象同时遵循 _iterable_ 接口和 _iterator_ 接口。 + +``` +// 自定义的数据结构 +class List { + add(element) ... + get(index) ... + get size() ... + ... +} +``` + +``` +function* listIterator(list) { + for (let i = 0; i console.log("foo"); +// 5 秒后打印 foo +``` + +大多数定时器将会用来周期性的处理数据: + +``` +interval(10000).pipe( + flatMap(i => fetch("https://server/stockTicker") +).subscribe(updateChart) +``` + +这段代码每 10 秒获取一次数据,更新屏幕。 + +#### generate + +这是个更加复杂的函数,允许你发送一系列任意类型的对象。它有一些重载,这里你看到的是最有意思的部分: + +``` +generate( + 0, // 从这个值开始 + x => x < 10, // 条件:只要值小于 10,就一直发送 + x => x*2 // 迭代:前一个值加倍 +).subscribe(); +``` + +``` +// 结果 +// 1 2 4 8 | +``` + +你也可以用它来迭代值,如果一个结构没有实现 _Iterable_ 接口。我们用前面的 list 例子来进行演示: + +``` +const myList = new List(); +myList.add(1); +myList.add(3); +``` + +``` +generate( + 0, // 从这个值开始 + i => i < list.size, // 条件:发送数据,直到遍历完整个列表 + i => ++i, // 迭代:获取下一个索引 + i => list.get(i) // 选择器:从列表中取值 +).subscribe(); +``` + +``` +// 结果 +// 1 3 | +``` + +如你所见,我添加了另一个参数:选择器(selector)。它和 _map_ 操作符作用类似,将生成的值转换为更有用的东西。 + +* * * + +### 空的流 + +有时候你要传递或返回一个不用发送任何数据的流。有三个函数分别用于不同的情况。你可以给这三个函数传递调度器。_empty_ 和 _throwError_ 接收一个调度器参数。 + +#### empty + +创建一个空的流,一个值也不发送。 + +``` +empty() + .subscribe(); +``` + +``` +// 结果 +// | +``` + +#### never + +创建一个永远不会结束的流,仍然不发送值。 + +``` +never() + .subscribe(); +``` + +``` +// 结果 +// ... +``` + +#### throwError + +创建一个流,流出现错误,不发送数据。 + +``` +throwError('error') + .subscribe(); +``` + +``` +// 结果 +// X +``` + +* * * + +### 挂钩已有的 API + +不是所有的库和所有你之前写的代码使用或者支持流。幸运的是 RxJS 提供函数用来桥接非响应式和响应式代码。这一节仅仅讨论 RxJS 为桥接代码提供的模版。 + +你可能还对这篇出自 [Ben Lesh][9] 的 [延伸阅读][8] 感兴趣,这篇文章讲了几乎所有能与 promises 交互操作的方式。 + +#### from + +我们已经用过它,把它列在这里是因为,它可以封装一个含有 observable 对象的 promise 对象。 + +``` +from(new Promise(resolve => resolve(1))) + .subscribe(); +``` + +``` +// 结果 +// 1 | +``` + +#### fromEvent + +fromEvent 为 DOM 元素添加一个事件监听器,我确定你知道这个。但你可能不知道的是,也可以通过其它类型来添加事件监听器,例如,一个 jQuery 对象。 + +``` +const element = $('#fooButton'); // 从 DOM 元素中创建一个 jQuery 对象 +``` + +``` +from(element, 'click') + .subscribe(); +``` + +``` +// 结果 +// clickEvent ... +``` + +#### fromEventPattern + +要理解为什么有 fromEvent 了还需要 fromEventPattern,我们得先理解 fromEvent 是如何工作的。看这段代码: + +``` +from(document, 'click') + .subscribe(); +``` + +这告诉 RxJS 我们想要监听 document 中的点击事件。在提交过程中,RxJS 发现 document 是一个 _EventTarget_ 类型,因此它可以调用它的 _addEventListener_ 方法。如果我们传入的是一个 jQuery 对象而非 document,那么 RxJs 知道它得调用 _on_ 方法。 + +这个例子用的是 _fromEventPattern_,和 _fromEvent_ 的工作基本上一样: + +``` +function addClickHandler(handler) { + document.addEventListener('click', handler); +} +``` + +``` +function removeClickHandler(handler) { + document.removeEventListener('click', handler); +} +``` + +``` +fromEventPattern( + addClickHandler, + removeClickHandler, +) +.subscribe(console.log); +``` + +``` +// 等效于 +fromEvent(document, 'click') +``` + +RxJS 自动创建实际的监听器( _handler_ )你的工作是添加或者移除监听器。_fromEventPattern_ 的目的基本上是告诉 RxJS 如何注册和移除事件监听器。 + +现在想象一下你使用了一个库,你可以调用一个叫做 _registerListener_ 的方法。我们不能再用 _fromEvent_,因为它并不知道该怎么处理这个对象。 + +``` +const listeners = []; +``` + +``` +class Foo { + registerListener(listener) { + listeners.push(listener); + } +``` + +``` + emit(value) { + listeners.forEach(listener => listener(value)); + } +} +``` + +``` +const foo = new Foo(); +``` + +``` +fromEventPattern(listener => foo.registerListener(listener)) + .subscribe(); +``` + +``` +foo.emit(1); +``` + +``` +// Produces +// 1 ... +``` + +当我们调用 foo.emit(1) 时,RxJS 中的监听器将被调用,然后它就能把值发送到流中。 + +你也可以用它来监听多个事件类型,或者结合所有可以通过回调进行通讯的 API,例如,WebWorker API: + +``` +const myWorker = new Worker('worker.js'); +``` + +``` +fromEventPattern( + handler => { myWorker.onmessage = handler }, + handler => { myWorker.onmessage = undefined } +) +.subscribe(); +``` + +``` +// 结果 +// workerMessage ... +``` + +#### bindCallback + +它和 fromEventPattern 相似,但它能用于单个值。就在回调函数被调用时,流就结束了。用法当然也不一样 —— 你可以用 bindCallBack 封装函数,然后它就会在调用时魔术般的返回一个流: + +``` +function foo(value, callback) { + callback(value); +} +``` + +``` +// 没有流 +foo(1, console.log); //prints 1 in the console +``` + +``` +// 有流 +const reactiveFoo = bindCallback(foo); +// 当我们调用 reactiveFoo 时,它返回一个 observable 对象 +``` + +``` +reactiveFoo(1) + .subscribe(console.log); // 在控制台打印 1 +``` + +``` +// 结果 +// 1 | +``` + +#### websocket + +是的,你完全可以创建一个 websocket 连接然后把它暴露给流: + +``` +import { webSocket } from 'rxjs/webSocket'; +``` + +``` +let socket$ = webSocket('ws://localhost:8081'); +``` + +``` +// 接收消息 +socket$.subscribe( + (msg) => console.log('message received: ' + msg), + (err) => console.log(err), + () => console.log('complete') * ); +``` + +``` +// 发送消息 +socket$.next(JSON.stringify({ op: 'hello' })); +``` + +把 websocket 功能添加到你的应用中真的很简单。_websocket_ 创建一个 subject。这意味着你可以订阅它,通过调用 _next_ 来获得消息和发送消息。 + +#### ajax + +如你所知:类似于 websocket,提供 AJAX 查询的功能。你可能用了一个带有 AJAX 功能的库或者框架。或者你没有用,那么我建议使用 fetch(或者必要的话用 polyfill),把返回的 promise 封装到一个 observable 对象中(参考稍后会讲到的 _defer_ 函数)。 + +* * * + +### Custom Streams + +有时候已有的函数用起来并不是足够灵活。或者你需要对订阅有更强的控制。 + +#### Subject + +subject 是一个特殊的对象,它使得你的能够把数据发送到流中,并且能够控制数据。subject 本身就是一个 observable 对象,但如果你想要把流暴露给其它代码,建议你使用 _asObservable_ 方法。这样你就不能意外调用原始方法。 + +``` +const subject = new Subject(); +const observable = subject.asObservable(); +``` + +``` +observable.subscribe(); +``` + +``` +subject.next(1); +subject.next(2); +subject.complete(); +``` + +``` +// 结果 +// 1 2 | +``` + +注意在订阅前发送的值将会“丢失”: + +``` +const subject = new Subject(); +const observable = subject.asObservable(); +``` + +``` +subject.next(1); +``` + +``` +observable.subscribe(console.log); +``` + +``` +subject.next(2); +subject.complete(); +``` + +``` +// 结果 +// 2 +``` + +除了常规的 subject,RxJS 还提供了三种特殊的版本。 + +_AsyncSubject_ 在结束后只发送最后的一个值。 + +``` +const subject = new AsyncSubject(); +const observable = subject.asObservable(); +``` + +``` +observable.subscribe(console.log); +``` + +``` +subject.next(1); +subject.next(2); +subject.complete(); +``` + +``` +// 输出 +// 2 +``` + +_BehaviorSubject_ 使得你能够提供一个(默认的)值,如果当前没有其它值发送的话,这个值会被发送给每个订阅者。否则订阅者收到最后一个发送的值。 + +``` +const subject = new BehaviorSubject(1); +const observable = subject.asObservable(); +``` + +``` +const subscription1 = observable.subscribe(console.log); +``` + +``` +subject.next(2); +subscription1.unsubscribe(); +``` + +``` +// 输出 +// 1 +// 2 +``` + +``` +const subscription2 = observable.subscribe(console.log); +``` + +``` +// 输出 +// 2 +``` + +The _ReplaySubject_ 存储一定数量、或一定时间或所有的发送过的值。所有新的订阅者将会获得所有存储了的值。 + +``` +const subject = new ReplaySubject(); +const observable = subject.asObservable(); +``` + +``` +subject.next(1); +``` + +``` +observable.subscribe(console.log); +``` + +``` +subject.next(2); +subject.complete(); +``` + +``` +// 输出 +// 1 +// 2 +``` + +你可以在 [ReactiveX documentation][10](它提供了一些其它的连接) 里面找到更多关于 subjects 的信息。[Ben Lesh][11] 在 [On The Subject Of Subjects][12] 上面提供了一些关于 subjects 的理解,[Nicholas Jamieson][13] 在 [in RxJS: Understanding Subjects][14] 上也提供了一些理解。 + +#### Observable + +你可以简单地用 new 操作符创建一个 observable 对象。通过你传入的函数,你可以控制流,只要有人订阅了或者它接收到一个可以当成 subject 使用的 observer,这个函数就会被调用,比如,调用 next,complet 和 error。 + +让我们回顾一下列表示例: + +``` +const myList = new List(); +myList.add(1); +myList.add(3); +``` + +``` +new Observable(observer => { + for (let i = 0; i { + // 流式化 +``` + +``` + return () => { + //clean up + }; +}) +.subscribe(); +``` + +#### 继承 Observable + +在有可用的操作符前,这是一种实现自定义操作符的方式。RxJS 在内部扩展了 _Observable_。_Subject_ 就是一个例子,另一个是 _publisher_ 操作符。它返回一个 _ConnectableObservable_ 对象,该对象提供额外的方法 _connect_。 + +#### 实现 Subscribable 接口 + +有时候你已经用一个对象来保存状态,并且能够发送值。如果你实现了 Subscribable 接口,你可以把它转换成一个 observable 对象。Subscribable 接口中只有一个 subscribe 方法。 + +``` +interface Subscribable { subscribe(observerOrNext?: PartialObserver | ((value: T) => void), error?: (error: any) => void, complete?: () => void): Unsubscribable} +``` + +* * * + +### 结合和选择现有的流 + +知道怎么创建一个独立的流还不够。有时候你有好几个流但其实只需要一个。有些函数也可作为操作符,所以我不打算在这里深入展开。推荐看看 [Max NgWizard K][16] 所写的一篇 [文章][15],它还包含一些有趣的动画。 + +还有一个建议:你可以通过拖拽元素的方式交互式的使用结合操作,参考 [RxMarbles][17]。 + +#### ObservableInput 类型 + +期望接收流的操作符和函数通常不单独和 observables 一起工作。相反,他们实际上期望的参数类型是 ObservableInput,定义如下: + +``` +type ObservableInput = SubscribableOrPromise | ArrayLike | Iterable; +``` + +这意味着你可以传递一个 promises 或者数组却不需要事先把他们转换成 observables。 + +#### defer + +主要的目的是把一个 observable 对象的创建延迟(defer)到有人想要订阅的时间。在以下情况,这很有用: + +* 创建 observable 对象的开销较大 + +* 你想要给每个订阅者新的 observable 对象 + +* 你想要在订阅时候选择不同的 observable 对象 + +* 有些代码必须在订阅之后执行 + +最后一点包含了一个并不起眼的用例:Promises(defer 也可以返回一个 promise 对象)。看看这个用到了 fetch API 的例子: + +``` +function getUser(id) { + console.log("fetching data"); + return fetch(`https://server/user/${id}`); +} +``` + +``` +const userPromise = getUser(1); +console.log("I don't want that request now"); +``` + +``` +// 其它地方 +userPromise.then(response => console.log("done"); +``` + +``` +// 输出 +// fetching data +// I don't want that request now +// done +``` + +只要流在你订阅的时候执行了,promise 就会立即执行。我们调用 getUser 的瞬间,就发送了一个请求,哪怕我们这个时候不想发送请求。当然,我们可以使用 from 来把一个 promise 对象转换成 observable 对象,但我们传递的 promise 对象已经创建或执行了。defer 让我们能够等到订阅才发送这个请求: + +``` +const user$ = defer(() => getUser(1)); +``` + +``` +console.log("I don't want that request now"); +``` + +``` +// 其它地方 +user$.subscribe(response => console.log("done"); +``` + +``` +// 输出 +// I don't want that request now +// fetching data +// done +``` + +#### iif + + _iif 包含了一个关于 _defer_ 的特殊用例:在订阅时选择两个流中的一个: + +``` +iif( + () => new Date().getHours() < 12, + of("AM"), + of("PM") +) +.subscribe(); +``` + +``` +// 结果 +// AM before noon, PM afterwards +``` + +引用了文档: + +> 实际上 `[iif][3]` 能够轻松地用 `[defer][4]` 实现,它仅仅是出于方便和可读性的目的。 + +#### onErrorResumeNext + +开启第一个流并且在失败的时候继续进行下一个流。错误被忽略掉。 + +``` +const stream1$ = of(1, 2).pipe( + tap(i => { if(i>1) throw 'error'}) //fail after first element +); +``` + +``` +const stream2$ = of(3,4); +``` + +``` +onErrorResumeNext(stream1$, stream2$) + .subscribe(console.log); +``` + +``` +// 结果 +// 1 3 4 | +``` + +如果你有多个 web 服务,这就很有用了。万一主服务器开启失败,那么备份的服务就能自动调用。 + +#### forkJoin + +它让流并行运行,当流结束时发送存在数组中的最后的值。由于每个流只有最后一个值被发送,它一般用在只发送一个元素的流的情况,就像 HTTP 请求。你让请求并行运行,在所有流收到响应时执行某些任务。 + +``` +function handleResponses([user, account]) { + // 执行某些任务 +} +``` + +``` +forkJoin( + fetch("https://server/user/1"), + fetch("https://server/account/1") +) +.subscribe(handleResponses); +``` + +#### merge / concat + +发送每一个从源 observables 对象中发出的值。 + + _merge_  接收一个参数,让你定义有多少流能被同时订阅。默认是无限制的。设为 1 就意味着监听一个源流,在它结束的时候订阅下一个。由于这是一个常见的场景,RxJS 为你提供了一个显示的函数:_concat_。 + +``` +merge( + interval(1000).pipe(mapTo("Stream 1"), take(2)), + interval(1200).pipe(mapTo("Stream 2"), take(2)), + timer(0, 1000).pipe(mapTo("Stream 3"), take(2)), + 2 //two concurrent streams +) +.subscribe(); +``` + +``` +// 只订阅流 1 和流 2 +``` + +``` +// 输出 +// Stream 1 -> after 1000ms +// Stream 2 -> after 1200ms +// Stream 1 -> after 2000ms +``` + +``` +// 流 1 结束后,开始订阅流 3 +``` + +``` +// 输出 +// Stream 3 -> after 0 ms +// Stream 2 -> after 400 ms (2400ms from beginning) +// Stream 3 -> after 1000ms +``` + +``` + +merge( + interval(1000).pipe(mapTo("Stream 1"), take(2)), + interval(1200).pipe(mapTo("Stream 2"), take(2)) + 1 +) +// 等效于 +concat( + interval(1000).pipe(mapTo("Stream 1"), take(2)), + interval(1200).pipe(mapTo("Stream 2"), take(2)) +) +``` + +``` +// 输出 +// Stream 1 -> after 1000ms +// Stream 1 -> after 2000ms +// Stream 2 -> after 3200ms +// Stream 2 -> after 4400ms +``` + +#### zip / combineLatest + + _merge_ 和 _concat_ 一个接一个的发送所有从源流中读到的值,而 zip 和 combineLatest 是把每个流中的一个值结合起来一起发送。_zip_ 结合所有源流中发送的第一个值。如果流的内容相关联,那么这就很有用。 + +``` +zip( + interval(1000), + interval(1200), +) +.subscribe(); +``` + +``` +// 结果 +// [0, 0] [1, 1] [2, 2] ... +``` + +_combineLatest_ 与之类似,但结合的是源流中发送的最后一个值。直到所有源流至少发送一个值之后才会触发事件。这之后每次源流发送一个值,它都会把这个值与其他流发送的最后一个值结合起来。 + +``` +combineLatest( + interval(1000), + interval(1200), +) +.subscribe(); +``` + +``` +// 结果 +// [0, 0] [1, 0] [1, 1] [2, 1] ... +``` + +两个函数都让允许传递一个选择器函数,把元素结合成其它对象而不是数组: + +``` +zip( + interval(1000), + interval(1200), + (e1, e2) -> e1 + e2 +) +.subscribe(); +``` + +``` +// 结果 +// 0 2 4 6 ... +``` + +#### race + +选择第一个发送数据的流。产生的流基本是最快的。 + +``` +race( + interval(1000), + of("foo") +) +.subscribe(); +``` + +``` +// 结果 +// foo | +``` + +由于 _of_ 立即产生一个值,因此它是最快的流,然而这个流就被选中了。 + +* * * + +### 总结 + +已经有很多创建 observables 对象的方式了。如果你想要创造响应式的 APIs 或者想用响应式的 API 结合传统 APIs,那么了解这些方法很重要。 + +我已经向你展示了所有可用的方法,但它们其实还有很多内容可以讲。如果你想更加深入地了解,我极力推荐你查阅 [documentation][20] 或者阅读相关文章。 + +[RxViz][21] 是另一种值得了解的有意思的方式。你编写 RxJS 代码,产生的流可以用图形或动画进行显示。 + +-------------------------------------------------------------------------------- + +via: https://blog.angularindepth.com/the-extensive-guide-to-creating-streams-in-rxjs-aaa02baaff9a + +作者:[Oliver Flaggl][a] +译者:[BriFuture](https://github.com/BriFuture) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://blog.angularindepth.com/@abetteroliver +[1]:https://rxjs-dev.firebaseapp.com/api/index/Subscribable +[2]:https://rxjs-dev.firebaseapp.com/api/index/Subscribable#subscribe +[3]:https://rxjs-dev.firebaseapp.com/api/index/iif +[4]:https://rxjs-dev.firebaseapp.com/api/index/defer +[5]:https://itnext.io/concurrency-and-asynchronous-behavior-with-rxjs-11b0c4b22597 +[6]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols +[7]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* +[8]:https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875 +[9]:https://medium.com/@benlesh +[10]:http://reactivex.io/documentation/subject.html +[11]:https://medium.com/@benlesh +[12]:https://medium.com/@benlesh/on-the-subject-of-subjects-in-rxjs-2b08b7198b93 +[13]:https://medium.com/@cartant +[14]:https://blog.angularindepth.com/rxjs-understanding-subjects-5c585188c3e1 +[15]:https://blog.angularindepth.com/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams-20fce8e6511 +[16]:https://medium.com/@maximus.koretskyi +[17]:http://rxmarbles.com/#merge +[18]:https://rxjs-dev.firebaseapp.com/api/index/ObservableInput +[19]:https://rxjs-dev.firebaseapp.com/api/index/SubscribableOrPromise +[20]:http://reactivex.io/documentation/operators.html#creating +[21]:https://rxviz.com/ diff --git a/translated/tech/20171003 Streams a new general purpose data structure in Redis.md b/translated/tech/20171003 Streams a new general purpose data structure in Redis.md index b98b315b14..06b6cbc631 100644 --- a/translated/tech/20171003 Streams a new general purpose data structure in Redis.md +++ b/translated/tech/20171003 Streams a new general purpose data structure in Redis.md @@ -1,184 +1,183 @@ -streams:一个新的 Redis 通用数据结构 -================================== - -直到几个月以前,对于我来说,在消息传递的环境中,streams 只是一个有趣且相对简单的概念。在 Kafka 流行这个概念之后,我主要研究它们在 Disque 实例中的用途。Disque 是一个将会转变为 Redis 4.2 模块的消息队列。后来我发现 Disque 全都是 AP 消息,它将在不需要客户端过多参与的情况下实现容错和保证送达,因此,我认为 streams 的概念在那种情况下并不适用。 - -然而同时,在 Redis 中有一个问题,那就是缺省情况下导出数据结构并不轻松。它在 Redis 列表、排序集和发布/订阅(Pub/Sub)能力之间有某些缺陷。你可以权衡使用这些工具去模拟一个消息或事件的序列。 +Streams:一个新的 Redis 通用数据结构 +================================== -排序集是大量耗费内存的,不能自然的模拟一次又一次的相同消息的传递,客户端不能阻塞新消息。因为一个排序集并不是一个序列化的数据结构,它是一个元素可以根据它们量的变化而移动的集合:它不是很像时间系列一样的东西。 +直到几个月以前,对于我来说,在消息传递的环境中,streams 只是一个有趣且相对简单的概念。这个概念在 Kafka 流行之后,我主要研究它们在 Disque 案例中的效能。Disque 是一个消息队列,它将在 Redis 4.2 中被转换为 Redis 的一个模块。后来我决定用 Disque 来做全部的 AP 消息,也就是说,它将在不需要客户端过多参与的情况下实现容错和保证送达,这样一来,我更加确定地认为 streams 的概念在那种情况下并不适用。 -列表有另外的问题,它在某些特定的用例中产生类似的适用性问题:你无法浏览列表中部是什么,因为在那种情况下,访问时间是线性的。此外,没有任何的指定输出功能,列表上的阻塞操作仅为单个客户端提供单个元素。列表中没有固定的元素标识,也就是说,不能指定从哪个元素开始给我提供内容。 +然而在那时,在 Redis 中还存在一个问题,那就是缺省情况下导出数据结构并不轻松。在 Redis 列表、有序集和发布/订阅能力之间有某些缺陷。你可以权衡使用这些工具对一系列消息或事件进行建模。 + +有序集是非常耗费内存的,那自然就不能对投递的相同消息进行一次又一次的建模,客户端不能阻塞新消息。因为有序集并不是一个序列化的数据结构,它是一个可以根据元素分数的变化而移动位置的集合:难怪它不适合像时间序列这样的东西。 + +列表也有不同的问题,它在某些特定的用例中产生类似的适用性问题:你无法浏览列表中间的内容,因为在那种情况下,访问时间是线性的。此外,有可能是没有输出的,列表上的阻塞操作仅为单个客户端提供单个元素。而不是为列表中的元素提供固定的标识符,也就是说,无法做到从指定的那个元素开始给我提供内容。 + +对于一到多的负载,它有发布/订阅机制,它在大多数情况下是非常好的,但是,对于某些你不希望“即发即弃”的东西:保留一个历史是很重要的,而不是断开之后重新获得消息,也因为某些消息列表,像时间序列,用范围查询浏览是非常重要的:在这 10 秒范围内我的温度读数是多少? + +我尝试去解决上面的问题所使用的一种方法是,规划一个通用的有序集合,并列入到一个唯一的、更灵活的数据结构中,然而,我的设计尝试最终以生成一个比当前的数据结构更加矫揉造作的结果而结束。一个关于 Redis 数据结构导出的更好的事情是,让它更像天然的计算机科学的数据结构,而不是,“Salvatore 发明的 API”。因此,在最后我停止了我的尝试,并且说,“ok,这是我们目前能提供的东西了”,或许,我将为发布/订阅增加一些历史,或者将来对列表访问增加一些更灵活的方式。然而,每次在会议上有用户对我说“你如何在 Redis 中建模时间序列” 或者类似的问题时,我就感到很惶恐。 + +### 起源 + +在 Redis 4.0 中引入模块之后,用户开始去看,他们自己如何去修复这些问题。他们中的一个 —— Timothy Downs,通过 IRC 写信给我: + + \ 这个模块,我计划去增加一个事务日志式的数据类型 —— 这意味着大量的订阅者可以在不大量增加 redis 内存使用的情况下做一些像发布/订阅那样的事情 + \ 订阅者持有他们在消息队列中的位置,而不是让 Redis 必须维护每个消费者的位置和为每个订阅者复制消息 + +他的思路启发了我。我想了几天,并且意识到这可能是我们马上同时解决上面所有问题的一个契机。我需要去重新构思 “日志” 的概念。日志是个基本的编程元素,每个人都使用过它,因为它只是简单地以追加模式打开一个文件,并以一定的格式写入数据。然而 Redis 数据结构必须是抽象的。它们在内存中,并且我们使用内存并不是因为我们懒,而是因为使用一些指针,我们可以概念化数据结构并把它们抽象,以使它们摆脱明确的限制。例如,一般来说日志有几个问题:偏移不是逻辑化的,而是真实的字节偏移,如果你希望找到与条目插入时间相关的逻辑偏移量是多少?我们有范围查询可用。同样的,日志通常很难进行垃圾收集:在一个只能进行追加操作的数据结构中怎么去删除旧的元素?好吧,在我们理想的日志中,我们只需要说,我想要数字最大的那个条目,而旧的元素一个也不要,等等。 + +当我从 Timothy 的想法中受到启发,去尝试着写一个规范的时候,我使用了 Redis 集群中的 radix 树去实现,优化了它内部的某些部分。这为实现一个有效利用空间的日志提供了基础,而且仍然有可能在对数时间logarithmic time内访问日志得到该范围。就在那时,我开始去读关于 Kafka 的 streams 相关的内容,以获得适合我的设计的其它灵感,最后的结果是借鉴了 Kafka 消费组的概念,并且再次针对 Redis 进行优化,以适用于 Redis 在内存中使用的情况。然而,该规范仅停留在纸面上,在一段时间后我几乎把它从头到尾重写了一遍,以便将我与别人讨论所得到的许多建议一起增加到即将到来的 Redis 升级中。我希望 Redis 的 streams 是非常有用的,尤其对于时间序列来说,而不仅是用于事件和消息类的应用程序。 + +### 让我们写一些代码吧 + +从 Redis 大会回来后,在那个夏天,我实现了一个称为 “listpack” 的库。这个库是 `ziplist.c` 的继任者,它是表示在单个分配中的字符串元素列表的一个数据结构。它是一个非常专业的序列化格式,其特点在于也能够以逆序(从右到左)进行解析:在所有的案例中是用于代替 ziplists 所做的事情。 + +结合 radix 树和 listpacks 的特性,它可以很容易地去构建一个空间高效的日志,并且还是索引化的,这意味着允许通过 ID 和时间进行随机访问。自从这些准备就绪后,我开始去写一些代码以实现流数据结构。我最终完成了该实现,不管怎样,现在,Redis 在 Github 上的 “streams” 分支里面,它已经可以跑的很好了。我并没有声称那个 API 是 100% 的最终版本,但是,这有两个有趣的事实:一是,在那时,只有消费组是不行的,再加上一些不那么重要的命令去操作流,但是,所有的大的方面都已经实现了。二是,一旦各个方面比较稳定了之后 ,决定大概用两个月的时间将所有的流的工作回迁backport到 4.0 分支。这意味着 Redis 用户为了使用 streams,不用等到 Redis 4.2 发布,它们在生产环境中马上就可以使用了。这是可能的,因为作为一个新的数据结构,几乎所有的代码改变都出现在新的代码里面。除了阻塞列表操作之外:该代码被重构了,我们对于 streams 和列表阻塞操作共享了相同的代码,从而极大地简化了 Redis 内部。 + +### 教程:欢迎使用 Redis 的 streams + +在某些方面,你可以认为 streams 是 Redis 中列表的一个增强版本。streams 元素不再是一个单一的字符串,它们更多是一个字段fieldvalue组成的对象。范围查询成为可能而且还更快。流中的每个条目都有一个 ID,它是个逻辑偏移量。不同的客户端可以阻塞等待blocking-wait比指定的 ID 更大的元素。Redis 中 streams 的一个基本命令是 XADD。是的,所有 Redis 的流命令都是以一个“X”为前缀的。 -对于一到多的工作任务,有发布/订阅机制,它在大多数情况下是非常好的,但是,对于某些不想“即发即弃”的东西:保留一个历史是很重要的,而不是断开之后重新获得消息,也因为某些消息列表,像时间系列,用范围查询浏览是非常重要的:在这 10 秒范围内我的温度读数是多少? - -这有一种方法可以尝试处理上面的问题,我计划对排序集进行通用化,并列入一个唯一的、更灵活的数据结构,然而,我的设计尝试最终以生成一个比当前的数据结构更加矫揉造作的结果而结束。一个关于 Redis 数据结构导出的更好的想法是,让它更像天然的计算机科学的数据结构,而不是,“Salvatore 发明的 API”。因此,在最后我停止了我的尝试,并且说,“ok,这是我们目前能提供的”,或许,我将为发布/订阅增加一些历史信息,或者将来对列表访问增加一些更灵活的方式。然而,每次在会议上有用户对我说“你如何在 Redis 中模拟时间系列” 或者类似的问题时,我的脸就绿了。 - -### 起源 - -在 Redis 4.0 中引入模块之后,用户开始去看他们自己怎么去修复这些问题。他们中的一个,Timothy Downs,通过 IRC 写信给我: - - \ 这个模块,我计划去增加一个事务日志式的数据类型 —— 这意味着大量的订阅者可以在没有大量增加 redis 内存使用的情况下做一些像发布/订阅那样的事情 - \ 订阅者保持它在消息队列中的位置,而不是让 Redis 必须维护每个消费者的位置和为每个订阅者复制消息 - -这激发了我的想像力。我想了几天,并且意识到这可能是我们立刻同时解决上面所有问题的契机。我需要去重新构想 “日志” 的概念是什么。它是个基本的编程元素,每个人都使用过它,因为它只是简单地以追加模式打开一个文件,并以一定的格式写入数据。然而 Redis 数据结构必须是抽象的。它们在内存中,并且我们使用内存并不是因为我们懒,而是因为使用一些指针,我们可以概念化数据结构并把它们抽象,以允许它们摆脱明显的限制。对于正常的例子来说,日志有几个问题:偏移不是逻辑的,而是真实的字节偏移,如果你想要与条目插入的时间相关的逻辑偏移,我们有范围查询可用。同样的,日志通常很难进行垃圾收集:在一个只追加的数据结构中怎么去删除旧的元素?好吧,在我们理想的日志中,我们只是说,我想要编号最大的那个条目,而旧的元素一个也不要,等等。 - -当我从 Timothy 的想法萌芽,去尝试着写一个规范的时候,我使用了我用于 Redis 集群中的 radix 树实现,优化了它内部的某些部分。这为实现一个有效利用空间的日志提供了基础,而仍然可以用对数时间来访问范围。同时,我开始去读关于 Kafka 流以获得另外的灵感,它也非常适合我的设计,并且产生了一个 Kafka 消费组的概念,并且,理想化的话,它可以用于 Redis 和内存用例。然而,该规范仅维持了几个月,在一段时间后我几乎把它从头到尾重写了一遍,以便将我与别人讨论的即将增加到 Redis 中的内容所得到的许多建议一起升级。我希望 Redis 流是非常有用的,尤其对于时间序列,而不仅是用于事件和消息类的应用程序。 - -### 让我们写一些代码 - -从 Redis 大会回来后,在整个夏天,我实现了一个称为 “listpack” 的库。这个库是 ziplist.c 的继任者,那是一个表示在单个分配中的字符串元素列表的数据结构。它是一个非常特殊的序列化格式,其特点在于也能够以逆序(从右到左)解析:需要它以便在各种用例中替代 ziplists。 - -结合 radix 树和 listpacks,它可以很容易地去构建一个日志,它同时也是非常空间高效的,并且是索引化的,这意味着允许通过 ID 和时间进行随机访问。自从这些就绪后,我开始去写一些代码以实现流数据结构。我最终完成了该实现,不管怎样,现在,在 Github 上的 Redis 的 “streams” 分支里面,它已经可以跑起来了。我并没有声称那个 API 是 100% 的最终版本,但是,这有两个有趣的事实:一是,在那时,只有消费组是缺失的,加上一些不那么重要的命令去操作流,但是,所有的大的方面都已经实现了。二是,一旦各个方面比较稳定了之后 ,决定大概用两个月的时间将所有的流的工作向后移植到 4.0 分支。这意味着 Redis 用户为了使用流,不用等待 Redis 4.2,它们在生产环境马上就可用了。这是可能的,因为作为一个新的数据结构,几乎所有的代码改变都出现在新的代码里面。除了阻塞列表操作之外:该代码被重构了,我们对于流和列表阻塞操作共享了相同的代码,而极大地简化了 Redis 内部。 - -### 教程:欢迎使用 Redis 流 - -在某些方面,你可以认为流是 Redis 列表的一个增强版本。流元素不再是一个单一的字符串,它们更多是一个fieldvalue组成的对象。范围查询更适用而且更快。流中的每个条目都有一个 ID,它是一个逻辑偏移量。不同的客户端可以阻塞等待blocking-wait比指定的 ID 更大的元素。Redis 流的一个基本的命令是 XADD。是的,所有的 Redis 流命令都是以一个“X”为前缀的。 - ``` -> XADD mystream * sensor-id 1234 temperature 10.5 -1506871964177.0 +> XADD mystream * sensor-id 1234 temperature 10.5 +1506871964177.0 ``` - -这个 `XADD` 命令将追加指定的条目作为一个指定的流 “mystream” 的新元素。在上面的示例中的这个条目有两个域:`sensor-id` 和 `temperature`,每个条目在同一个流中可以有不同的域。使用相同的域名字可以更好地利用内存。一个有趣的事情是,域的排序是可以保证顺序的。XADD 仅返回插入的条目的 ID,因为在第三个参数中是星号(`*`),表示我们邀请命令自动生成 ID。一般情况下需求如此,但是,也可以去强制指定一个 ID,这种情况用于复制这个命令到被动服务器和 AOF 文件。 - -这个 ID 是由两部分组成的:一个毫秒时间和一个序列号。`1506871964177` 是毫秒时间,它仅是一个毫秒级的 UNIX 时间。圆点(`.`)后面的数字 `0`,是一个序列号,它是为了区分相同毫秒数的条目增加上去的。这两个数字都是 64 位的无符号整数。这意味着在流中,我们可以增加所有我们想要的条目,即使是在同一毫秒中。ID 的毫秒部分使用 Redis 服务器的当前本地时间生成的 ID 和流中的最后一个条目 ID 两者间的最大的一个。因此,对实例来说,即使是计算机时间向后跳,这个 ID 仍然是增加的。在某些情况下,你可以认为流条目的 ID 是完整的 128 位数字。然而,现实是,它们与被添加的实例的本地时间有关,这意味着我们可以在毫秒级的精度的范围随意查询。 - -如你想像的那样,以一个非常快速的方式去添加两个条目,结果是仅序列号增加。我可以使用一个 `MULTI`/`EXEC` 块去简单模拟“快速插入”,如下: - + +这个 `XADD` 命令将追加指定的条目作为一个指定的流 —— “mystream” 的新元素。在上面的示例中的这个条目有两个字段:`sensor-id` 和 `temperature`,每个条目在同一个流中可以有不同的字段。使用相同的字段名字可以更好地利用内存。需要记住的一个有趣的事情是,字段是保证排序的。XADD 仅返回插入的条目的 ID,因为在第三个参数中是星号(`*`),表示我们要求命令自动生成 ID。大多数情况下需要如此,但是,也可以去强制指定一个 ID,这种情况用于复制这个命令到被动服务器和 AOF 文件。 + +这个 ID 是由两部分组成的:一个毫秒时间和一个序列号。`1506871964177` 是毫秒时间,它仅是一个毫秒级的 UNIX 时间。圆点(`.`)后面的数字 `0`,是一个序列号,它是为了区分相同毫秒数的条目增加上去的。这两个数字都是 64 位的无符号整数。这意味着在流中,我们可以增加所有我们想要的条目,即使是在同一毫秒中。ID 的毫秒部分使用 Redis 服务器的当前本地时间生成的 ID 和流中的最后一个条目 ID 两者间的最大的一个。因此,举例来说,即使是计算机时间回跳,这个 ID 仍然是增加的。在某些情况下,你可以认为流条目的 ID 是完整的 128 位数字。然而,事实是,它们与被添加的实例的本地时间有关,这意味着我们可以在毫秒级的精度范围内随意查询。 + +正如你想的那样,以一个速度非常快的方式去添加两个条目,结果是仅一个序列号增加了。我可以使用一个 `MULTI`/`EXEC` 块去简单模拟“快速插入”,如下: + ``` > MULTI -OK +OK > XADD mystream * foo 10 -QUEUED -> XADD mystream * bar 20 -QUEUED -> EXEC -1) 1506872463535.0 -2) 1506872463535.1 +QUEUED +> XADD mystream * bar 20 +QUEUED +> EXEC +1) 1506872463535.0 +2) 1506872463535.1 ``` - -在上面的示例中,也展示了无需在开始时指定任何模式schema的情况下,对不同的条目使用不同的域。会发生什么呢?每个块(它通常包含 50 - 150 个消息内容)的每一个信息被用作参考。并且,有相同域的连续条目被使用一个标志进行压缩,这个标志表明与“这个块中的第一个条目的相同域”。因此,对于连续消息使用相同域可以节省许多内存,即使是域的集合随着时间发生缓慢变化。 - -为了从流中检索数据,这里有两种方法:范围查询,它是通过 `XRANGE` 命令实现的;以及流式,它是通过 XREAD 命令实现的。`XRANGE` 命令仅取得包括从开始到停止范围内的条目。因此,对于实例,如果我知道它的 ID,我可以取得单个条目,像这样: -``` -> XRANGE mystream 1506871964177.0 1506871964177.0 -1) 1) 1506871964177.0 - 2) 1) "sensor-id" - 2) "1234" - 3) "temperature" - 4) "10.5" -``` - -然而,你可以使用指定的开始符号 `-` 和停止符号 `+` 去表示最小和最大的 ID。为了限制返回条目的数量,它也可以使用 `COUNT` 选项。下面是一个更复杂的 `XRANGE` 示例: +在上面的示例中,也展示了无需指定任何初始模式schema的情况下,对不同的条目使用不同的字段。会发生什么呢?就像前面提到的一样,只有每个块(它通常包含 50 - 150 个消息内容)的第一个信息被使用。并且,相同字段的连续条目都使用了一个标志进行了压缩,这个标志表示与“它们与这个块中的第一个条目的字段相同”。因此,对于使用了相同字段的连续消息可以节省许多内存,即使是字段集随着时间发生缓慢变化的情况下也很节省内存。 -``` -> XRANGE mystream - + COUNT 2 -1) 1) 1506871964177.0 - 2) 1) "sensor-id" - 2) "1234" - 3) "temperature" - 4) "10.5" -2) 1) 1506872463535.0 - 2) 1) "foo" - 2) "10" -``` - -这里我们讲的是 ID 的范围,然后,为了取得在一个给定时间范围内的特定范围的元素,你可以使用 `XRANGE`,因为你可以省略 ID 的“序列” 部分。因此,你可以做的仅是指定“毫秒”时间,下面的命令的意思是: “从 UNIX 时间 1506872463 开始给我 10 个条目”: +为了从流中检索数据,这里有两种方法:范围查询,它是通过 `XRANGE` 命令实现的;和流播streaming,它是通过 XREAD 命令实现的。`XRANGE` 命令仅取得包括从开始到停止范围内的全部条目。因此,举例来说,如果我知道它的 ID,我可以使用如下的命名取得单个条目: -``` -127.0.0.1:6379> XRANGE mystream 1506872463000 + COUNT 10 -1) 1) 1506872463535.0 - 2) 1) "foo" - 2) "10" -2) 1) 1506872463535.1 - 2) 1) "bar" - 2) "20" ``` - -关于 `XRANGE` 最后一个重要的事情是,考虑到我们在回复中收到 ID,并且随后连续的 ID 只是增加了 ID 的序列部分,所以可以使用 `XRANGE` 去遍历整个流,接收每个调用的指定个数的元素。在 Redis 中的`*SCAN` 系列命令之后,它允许 Redis 数据结构迭代,尽管事实上它们不是为迭代设计的,但我避免再犯相同的错误。 - -### 使用 XREAD 处理变化的流:阻塞新的数据 - -当我们想通过 ID 或时间去访问流中的一个范围或者是通过 ID 去得到单个元素时,使用 XRANGE 是非常完美的。然而,在当数据到达时,流必须由不同的客户端处理时,它就不是一个很好的解决方案,它是需要某种形式的“池”。(对于*某些*应用程序来说,这可能是个好主意,因为它们仅是偶尔连接取数的) - -`XREAD` 命令是为读设计的,同时从多个流中仅指定我们从该流中得到的最后条目的 ID。此外,如果没有数据可用,我们可以要求阻塞,当数据到达时,去解除阻塞。类似于阻塞列表操作产生的效果,但是,这里的数据是从流中得到的。并且多个客户端可以在同时访问相同的数据。 - -这里有一个关于 `XREAD` 调用的规范示例: +> XRANGE mystream 1506871964177.0 1506871964177.0 +1) 1) 1506871964177.0 + 2) 1) "sensor-id" + 2) "1234" + 3) "temperature" + 4) "10.5" +``` + +不管怎样,你都可以使用指定的开始符号 `-` 和停止符号 `+` 去表示最小和最大的 ID。为了限制返回条目的数量,也可以使用 `COUNT` 选项。下面是一个更复杂的 `XRANGE` 示例: -``` -> XREAD BLOCK 5000 STREAMS mystream otherstream $ $ ``` - -它的意思是:从 `mystream` 和 `otherstream` 取得数据。如果没有数据可用,阻塞客户端 5000 毫秒。之后我们用关键字 `STREAMS` 指定我们想要监听的流,和我们的最后 ID,指定的 ID `$` 意思是:假设我现在已经有了流中的所有元素,因此,从下一个到达的元素开始给我。 - +> XRANGE mystream - + COUNT 2 +1) 1) 1506871964177.0 + 2) 1) "sensor-id" + 2) "1234" + 3) "temperature" + 4) "10.5" +2) 1) 1506872463535.0 + 2) 1) "foo" + 2) "10" +``` + +这里我们讲的是 ID 的范围,然后,为了取得在一个给定时间范围内的特定范围的元素,你可以使用 `XRANGE`,因为你可以省略 ID 的“序列” 部分。因此,你可以做的仅是指定“毫秒”时间,下面的命令的意思是: “从 UNIX 时间 1506872463 开始给我 10 个条目”: + +``` +127.0.0.1:6379> XRANGE mystream 1506872463000 + COUNT 10 +1) 1) 1506872463535.0 + 2) 1) "foo" + 2) "10" +2) 1) 1506872463535.1 + 2) 1) "bar" + 2) "20" +``` + +关于 `XRANGE` 需要注意的最重要的事情是,假设我们在回复中收到 ID,并且随后连续的 ID 只是增加了 ID 的序列部分,所以可以使用 `XRANGE` 去遍历整个流,接收每个调用的指定个数的元素。在 Redis 中的`*SCAN` 系列命令之后,它允许 Redis 数据结构迭代,尽管事实上它们不是为迭代设计的,但这样我可以避免再犯相同的错误。 + +### 使用 XREAD 处理流播:阻塞新的数据 + +当我们想通过 ID 或时间去访问流中的一个范围或者是通过 ID 去获取单个元素时,使用 XRANGE 是非常完美的。然而,在 streams 的案例中,当数据到达时,它必须由不同的客户端来消费时,这就不是一个很好的解决方案,这需要某种形式的“汇聚池”。(对于*某些*应用程序来说,这可能是个好主意,因为它们仅是偶尔连接取数的) + +`XREAD` 命令是为读取设计的,在同一个时间,从多个 streams 中仅指定我们从该流中得到的最后条目的 ID。此外,如果没有数据可用,我们可以要求阻塞,当数据到达时,去解除阻塞。类似于阻塞列表操作产生的效果,但是这里并没有消费从流中得到的数据。并且多个客户端可以同时访问相同的数据。 + +这里有一个关于 `XREAD` 调用的规范示例: + +``` +> XREAD BLOCK 5000 STREAMS mystream otherstream $ $ +``` + +它的意思是:从 `mystream` 和 `otherstream` 取得数据。如果没有数据可用,阻塞客户端 5000 毫秒。在 `STREAMS` 选项之后指定我们想要监听的关键字,最后的是指定想要监听的 ID,指定的 ID 为 `$` 的意思是:假设我现在需要流中的所有元素,因此,只需要从下一个到达的元素开始给我。 + 如果,从另外一个客户端,我发出这样的命令: -``` -> XADD otherstream * message “Hi There” - -在 XREAD 侧会出现什么情况呢? - -1) 1) "otherstream" - 2) 1) 1) 1506935385635.0 - 2) 1) "message" - 2) "Hi There" ``` - -和收到的数据一起,我们得到了最新收到的数据的 key,在下次的调用中,我们将使用接收到的最新消息的 ID: +> XADD otherstream * message “Hi There” +``` +在 XREAD 侧会出现什么情况呢? +``` +1) 1) "otherstream" + 2) 1) 1) 1506935385635.0 + 2) 1) "message" + 2) "Hi There" +``` -``` -> XREAD BLOCK 5000 STREAMS mystream otherstream $ 1506935385635.0 -``` - -依次类推。然而需要注意的是使用方式,有可能客户端在一个非常大的延迟(因为它处理消息需要时间,或者其它什么原因)之后再次连接。在这种情况下,期间会有很多消息堆积,为了确保客户端不被消息淹没,并且服务器不会花费太多时间提供给单个客户端的大量消息,所以,总是使用 `XREAD` 的 `COUNT` 选项是明智的。 - -### 流封顶 - -到现在为止,一直还都不错…… 然而,有些时候,流需要去删除一些旧的消息。幸运的是,这可以使用 `XADD` 命令的 `MAXLEN` 选项去做: +与收到的数据一起,我们得到了所收到数据的关键字,在下次的调用中,我们将使用接收到的最新消息的 ID: -``` -> XADD mystream MAXLEN 1000000 * field1 value1 field2 value2 ``` - -它是基本意思是,如果流添加的新元素被发现超过 `1000000` 个消息,那么,删除旧的消息,以便于长度回到 `1000000` 个元素以内。它很像是使用 `RPUSH` + `LTRIM` 的列表,但是,这是我们使用了一个内置机制去完成的。然而,需要注意的是,上面的意思是每次我们增加一个新的消息时,我们还需要另外的工作去从流中删除旧的消息。这将使用一些 CPU 资源,所以,在计算 MAXLEN 的之前,尽可能使用 `~` 符号,为了表明我们不是要求非常*精确*的 1000000 个消息,而是稍微多一些也不是一个大的问题: +> XREAD BLOCK 5000 STREAMS mystream otherstream $ 1506935385635.0 +``` + +依次类推。然而需要注意的是使用方式,客户端有可能在一个非常大的延迟(因为它处理消息需要时间,或者其它什么原因)之后再次连接。在这种情况下,期间会有很多消息堆积,为了确保客户端不被消息淹没,以及服务器不会因为给单个客户端提供大量消息而浪费太多的时间,使用 `XREAD` 的 `COUNT` 选项是非常明智的。 + +### 流封顶 + +到现在为止,一直还都不错…… 然而,有些时候,streams 需要去删除一些旧的消息。幸运的是,这可以使用 `XADD` 命令的 `MAXLEN` 选项去做: -``` -> XADD mystream MAXLEN ~ 1000000 * foo bar ``` - -这种方式 XADD 仅当它可以删除整个节点的时候才会删除元素。相比普通的 `XADD`,这种方式几乎可以自由地对流进行封顶。 - -### 消费组(开发中) - -这是第一个 Redis 中尚未实现而在开发中的特性。它也是来自 Kafka 的灵感,尽管在这里以不同的方式去实现的。重点是使用了 `XREAD`,客户端也可以增加一个 `GROUP ` 选项。 在相同组的所有客户端自动调用,以得到*不同的*消息。当然,不能从同一个流中被多个组读。在这种情况下,所有的组将收到流中到达的消息的相同副本。但是,在每个组内,消息是不会重复的。 - -当指定组时,能够指定一个 “RETRY ” 选项去扩展组:在这种情况下,如果消息没有使用 XACK 去进行确认,它将在指定的毫秒数后进行再次投递。这将为消息投递提供更佳的可靠性,这种情况下,客户端没有私有的方法去标记已处理的消息。这也是一项正在进行中的工作。 - -### 内存使用和节省的加载时间 - -因为被设计用来模拟 Redis 流,内存使用是显著降低的。这依赖于它们的域的数量、值和长度,但对于简单的消息,每 100 MB 内存使用可以有几百万条消息,此外,设想中的格式去需要极少的系列化:listpack 块以 radix 树节点方式存储,在磁盘上和内存中是以相同方式表示的,因此,它们可以被轻松地存储和读取。在一个实例中,Redis 能在 0.3 秒内可以从 RDB 文件中读取 500 万个条目。这使的流的复制和持久存储是非常高效的。 - -也计划允许从条目中间删除。现在仅部分实现,策略是删除的条目在标记中标识为已删除条目,并且,当达到已删除条目占全部条目的给定比例时,这个块将被回收重写,并且,如果需要,它将被连到相邻的另一个块上,以避免碎片化。 - -### 最终发布时间的结论 - -Redis 流将包含在年底前(LCTT 译注:本文原文发布于 2017 年 10 月)推出的 Redis 4.0 系列的稳定版中。我认为这个通用的数据结构将为 Redis 提供一个巨大的补丁,以用于解决很多现在很难去解决的情况:那意味着你(之前)需要创造性地滥用当前提供的数据结构去解决那些问题。一个非常重要的使用情况是时间系列,但是,我的感觉是,对于其它用例来说,通过 `TREAD` 来传递消息将是非常有趣的,因为它可以替代那些需要更高可靠性的发布/订阅的应用程序,而不是“即用即弃”,以及全新的用例。现在,如果你想在你需要的环境中评估新的数据结构的能力,可以在 GitHub 上去获得 “streams” 分支,开始去玩吧。欢迎向我们报告所有的 bug 。 :-) - -如果你喜欢这个视频,展示这个 streams 的实时会话在这里: https://www.youtube.com/watch?v=ELDzy9lCFHQ - - --------------------------------------------------------------------------------- - -via: http://antirez.com/news/114 - -作者:[antirez][a] -译者:[qhwdw](https://github.com/qhwdw) -校对:[wxy](https://github.com/wxy) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:http://antirez.com/ -[1]:http://antirez.com/news/114 -[2]:http://antirez.com/user/antirez -[3]:https://www.youtube.com/watch?v=ELDzy9lCFHQ +> XADD mystream MAXLEN 1000000 * field1 value1 field2 value2 +``` + +它是基本意思是,如果流上添加新元素后发现,它的消息数量超过了 `1000000` 个,那么,删除旧的消息,以便于长度重新回到 `1000000` 个元素以内。它很像是在列表中使用的 `RPUSH` + `LTRIM`,但是,这次我们是使用了一个内置机制去完成的。然而,需要注意的是,上面的意思是每次我们增加一个新的消息时,我们还需要另外的工作去从流中删除旧的消息。这将使用一些 CPU 资源,所以,在计算 MAXLEN 之前,尽可能使用 `~` 符号,以表明我们不要求非常*精确*的 1000000 个消息,就是稍微多一些也不是个大问题: + +``` +> XADD mystream MAXLEN ~ 1000000 * foo bar +``` + +这种方式的 XADD 仅当它可以删除整个节点的时候才会删除消息。相比普通的 `XADD`,这种方式几乎可以自由地对流进行封顶。 + +### 消费组(开发中) + +这是第一个 Redis 中尚未实现而在开发中的特性。它也是来自 Kafka 的灵感,尽管在这里以不同的方式去实现的。重点是使用了 `XREAD`,客户端也可以增加一个 `GROUP ` 选项。 在相同组的所有客户端,将自动地得到*不同的*消息。当然,同一个流可以被多个组读取。在这种情况下,所有的组将收到流中到达的消息的相同副本。但是,在每个组内,消息是不会重复的。 + +当指定组时,能够指定一个 “RETRY ” 选项去扩展组:在这种情况下,如果消息没有使用 XACK 去进行确认,它将在指定的毫秒数后进行再次投递。这将为消息投递提供更佳的可靠性,这种情况下,客户端没有私有的方法将消息标记为已处理。这一部分也正在开发中。 + +### 内存使用和加载时间节省 + +因为设计用来建模 Redis 的 streams,内存使用是非常低的。这取决于它们的字段、值的数量和它们的长度,对于简单的消息,每使用 100 MB 内存可以有几百万条消息,此外,格式设想为需要极少的序列化:listpack 块以 radix 树节点方式存储,在磁盘上和内存中都以相同方式表示的,因此,它们可以很轻松地存储和读取。例如,Redis 可以在 0.3 秒内从 RDB 文件中读取 500 万个条目。这使得 streams 的复制和持久存储非常高效。 + +也计划允许从条目中间进行部分删除。现在仅实现了一部分,策略是在条目在标记中标识为已删除条目,并且,当已删除条目占全部条目的比率达到给定值时,这个块将被回收重写,并且,如果需要,它将被连到相邻的另一个块上,以避免碎片化。 + +### 关于最终发布时间的结论 + +Redis 的 streams 将包含在年底前(LCTT 译注:本文原文发布于 2017 年 10 月)推出的 Redis 4.0 系列的稳定版中。我认为这个通用的数据结构将为 Redis 提供一个巨大的补丁,以用于解决很多现在很难去解决的情况:那意味着你(之前)需要创造性地滥用当前提供的数据结构去解决那些问题。一个非常重要的使用案例是时间序列,但是,我的感觉是,对于其它用例来说,通过 `TREAD` 来流播消息将是非常有趣的,因为对于那些需要更高可靠性的应用程序,可以使用发布/订阅模式来替换“即用即弃”,还有其它全新的使用案例。现在,如果你想在你的有问题环境中评估新数据结构的能力,可以在 GitHub 上去获得 “streams” 分支,开始去玩吧。欢迎向我们报告所有的 bug 。 :-) + +如果你喜欢这个视频,展示这个 streams 的实时会话在这里: https://www.youtube.com/watch?v=ELDzy9lCFHQ + +-------------------------------------------------------------------------------- + +via: http://antirez.com/news/114 + +作者:[antirez][a] +译者:[qhwdw](https://github.com/qhwdw) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:http://antirez.com/ +[1]:http://antirez.com/news/114 +[2]:http://antirez.com/user/antirez +[3]:https://www.youtube.com/watch?v=ELDzy9lCFHQ diff --git a/translated/tech/20180522 How to Run Your Own Git Server.md b/translated/tech/20180522 How to Run Your Own Git Server.md new file mode 100644 index 0000000000..e35b45aef5 --- /dev/null +++ b/translated/tech/20180522 How to Run Your Own Git Server.md @@ -0,0 +1,246 @@ +搭建属于你自己的 Git 服务器 +====== +**在本文中,我们的目的是让你了解如何设置属于自己的Git服务器。** + +Git 是由 Linux Torvalds 开发的一个版本控制系统,现如今正在被全世界大量开发者使用。许多公司喜欢使用基于 Git 版本控制的 GitHub 代码托管。[根据报道,GitHub 是现如今全世界最大的代码托管网站][3]。GitHub 宣称已经有 920 万用户和 2180 万个仓库。许多大型公司现如今也将代码迁移到 GitHub 上。[甚至于谷歌,一家搜索引擎公司,也正将代码迁移到 GitHub 上][4]。 + + +### 运行你自己的 Git 服务器 + +GitHub 能提供极佳的服务,但却有一些限制,尤其是你是单人或是一名 coding 爱好者。GitHub 其中之一的限制就是其中免费的服务没有提供代码私有化托管业务。[你不得不支付每月7美金购买5个私有仓库][5],并且想要更多的私有仓库则要交更多的钱。 + + +万一你想要仓库私有化或需要更多权限控制,最好的方法就是在你的服务器上运行 Git。不仅你能够省去一笔钱,你还能够在你的服务器有更多的操作。在大多数情况下,大多数高级 Linux 用户已经拥有自己的服务器,并且在这些服务器上推送 Git 就像“啤酒一样免费”(LCTT 译者注:指免费软件)。 + + +在这篇教程中,我们主要讲在你的服务器上,使用两种代码管理的方法。一种是运行一个纯 Git 服务器,另一个是使用名为 [GitLab][6] 的 GUI 工具。在本教程中,我在 VPS 上运行的操作系统是 Ubuntu 14.04 LTS。 + +### 在你的服务器上安装Git + +在本篇教程中,我们考虑一个简单案例,我们有一个远程服务器和一台本地服务器,现在我们需要使用这两台机器来工作。为了简单起见,我们就分别叫他们为远程服务器和本地服务器。 + +首先,在两边的机器上安装Git。你可以从依赖包中安装 Git,在本文中,我们将使用更简单的方法: + +``` +sudo apt-get install git-core + +``` +为 Git 创建一个用户。 +``` +sudo useradd git +passwd git + +``` +为了容易的访问服务器,我们设置一个免密 ssh 登录。首先在你本地电脑上创建一个 ssh 密钥: + +``` +ssh-keygen -t rsa + +``` +这时会要求你输入保存密钥的路径,这时只需要点击回车便保存在默认路径。第二个问题是输入访问远程服务器所需的密码。它生成两个密钥——公钥和私钥。记下您在下一步中需要使用的公钥的位置。 + +现在您必须将这些密钥复制到服务器上,以便两台机器可以相互通信。在本地机器上运行以下命令: + + +``` +cat ~/.ssh/id_rsa.pub | ssh git@remote-server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" + +``` +现在,用 ssh 登录进服务器并为 Git 创建一个项目路径。你可以为你的仓库设置一个你想要的目录。 + +现在跳转到该目录中: + +``` +cd /home/swapnil/project-1.git + +``` +现在新建一个空仓库: +``` +git init --bare +Initialized empty Git repository in /home/swapnil/project-1.git + +``` + +现在我们需要在本地机器上新建一个基于 Git 版本控制仓库: +``` +mkdir -p /home/swapnil/git/project + +``` +进入我们创建仓库的目录: +``` +cd /home/swapnil/git/project + +``` + +现在在该目录中创建项目所需的文件。留在这个目录并启动 git: + +``` +git init +Initialized empty Git repository in /home/swapnil/git/project + +``` +把所有文件添加到仓库中: +``` +git add . + +``` + +现在,每次添加文件或进行更改时,都必须运行上面的 add 命令。 您还需要为每个文件更改都写入提交消息。提交消息基本上说明了我们所做的更改。 + +``` +git commit -m "message" -a +[master (root-commit) 57331ee] message + 2 files changed, 2 insertions(+) + create mode 100644 GoT.txt + create mode 100644 writing.txt + +``` + +在这种情况下,我有一个名为 GoT(权力游戏审查)的文件,并且我做了一些更改,所以当我运行命令时,它指定对文件进行更改。 在上面的命令中 `-a` 选项意味着提交仓库中的所有文件。 如果您只更改了一个,则可以指定该文件的名称而不是使用 `-a`。 + +举一个例子: + +``` +git commit -m "message" GoT.txt +[master e517b10] message + 1 file changed, 1 insertion(+) + +``` + +到现在为止,我们一直在本地服务器上工作。现在我们必须将这些更改推送到远程服务器上,以便通过 Internet 访问,并且可以与其他团队成员进行协作。 + +``` +git remote add origin ssh://git@remote-server/repo->path-on-server..git + +``` +现在,您可以使用 “pull” 或 “push” 选项在服务器和本地计算机之间 push 或 pull: + +``` +git push origin master + +``` +如果有其他团队成员想要使用该项目,则需要将远程服务器上的仓库克隆到其本地计算机上: + +``` +git clone git@remote-server:/home/swapnil/project.git + +``` +这里 /home/swapnil/project.git 是远程服务器上的项目路径,在你本机上则会改变。 + +然后进入本地计算机上的目录(使用服务器上的项目名称): +``` +cd /project + +``` +现在他们可以编辑文件,写入提交更改信息,然后将它们推送到服务器: +``` +git commit -m 'corrections in GoT.txt story' -a +And then push changes: + +git push origin master + +``` +我认为这足以让一个新用户开始在他们自己的服务器上使用 Git。 如果您正在寻找一些 GUI 工具来管理本地计算机上的更改,则可以使用 GUI 工具,例如 QGit 或 GitK for Linux。 + + +### 使用 GitLab + +这是项目所有者和合作者的纯粹命令行解决方案。这当然不像使用 GitHub 那么简单。不幸的是,尽管 GitHub 是全球最大的代码托管商,但是它自己的软件别人却无法使用。因为它不是开源的,所以你不能获取源代码并编译你自己的 GitHub。与 WordPress 或 Drupal 不同,您无法下载 GitHub 并在您自己的服务器上运行它。 + + +像往常一样,在开源世界中,是没有终结的尽头。GitLab 是一个非常优秀的项目。这是一个开源项目,允许用户在自己的服务器上运行类似于 GitHub 的项目管理系统。 + + +您可以使用 GitLab 为团队成员或公司运行类似于 GitHub 的服务。您可以使用 GitLab 在公开发布之前开发私有项目。 + + +GitLab 采用传统的开源商业模式。他们有两种产品:免费的开源软件,用户可以在自己的服务器上安装,以及类似于 GitHub 的托管服务。 + + +可下载版本有两个版本,免费的社区版和付费企业版。企业版基于社区版,但附带针对企业客户的其他功能。它或多或少与 WordPress.org 或 Wordpress.com 提供的服务类似。 + + +社区版具有高度可扩展性,可以在单个服务器或群集上支持 25000 个用户。GitLab 的一些功能包括:Git 仓库管理,代码评论,问题跟踪,活动源和维基。它配备了 GitLab CI,用于持续集成和交付。 + + +Digital Ocean 等许多 VPS 提供商会为用户提供 GitLab 服务。 如果你想在你自己的服务器上运行它,你可以手动安装它。GitLab 为不同的操作系统提供 Omnibus 软件包。 在我们安装 GitLab 之前,您可能需要配置 SMTP 电子邮件服务器,以便 GitLab 可以在需要时随时推送电子邮件。官方推荐使用 Postfix。所以,先在你的服务器上安装 Postfix: + + +``` +sudo apt-get install postfix + +``` +在安装 Postfix 期间,它会问你一些问题,不要跳过它们。 如果你一不小心跳过,你可以使用这个命令来重新配置它: +``` +sudo dpkg-reconfigure postfix + +``` +运行此命令时,请选择 “Internet 站点”并为使用 Gitlab 的域名提供电子邮件ID。 + + +我是这样输入的: + +``` +This e-mail address is being protected from spambots. You need JavaScript enabled to view it + + +``` + +用 Tab 键并为 postfix 创建一个用户名。接下来将会要求你输入一个目标邮箱。 + +在剩下的步骤中,都选择默认选项。当我们安装且配置完成后,我们继续安装 GitLab。 + +我们使用 wget 来下载软件包(用 [最新包][7] 替换下载链接): + +``` +wget https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.4-omnibus.1-1_amd64.deb + +``` +然后安装这个包: + +``` +sudo dpkg -i gitlab_7.9.4-omnibus.1-1_amd64.deb + +``` +现在是时候配置并启动 GitLab 了。 + +``` +sudo gitlab-ctl reconfigure + +``` +您现在需要在配置文件中配置域名,以便您可以访问 GitLab。打开文件。 + +``` +nano /etc/gitlab/gitlab.rb + +``` + +在这个文件中编辑 'external_url' 并输入服务器域名。保存文件,然后从 Web 浏览器中打开新建的一个 GitLab 站点。 + + +默认情况下,它会以系统管理员的身份创建 'root',并使用 '5iveL!fe' 作为密码。 登录到 GitLab 站点,然后更改密码。 + +密码更改后,登录该网站并开始管理您的项目。 + +GitLab 有很多选项和功能。最后,我借用电影“黑客帝国”中的经典台词:“不幸的是,没有人知道 GitLab 可以做什么。你必须亲自尝试一下。” + + +-------------------------------------------------------------------------------- + +via: https://www.linux.com/learn/how-run-your-own-git-server + +作者:[Swapnil Bhartiya][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[wyxplus](https://github.com/wyxplus) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://www.linux.com/users/arnieswap +[1]:https://github.com/git/git +[2]:https://www.linuxfoundation.org/blog/10-years-of-git-an-interview-with-git-creator-linus-torvalds/ +[3]:https://github.com/about/press +[4]:http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html +[5]:https://github.com/pricing +[6]:https://about.gitlab.com/ +[7]:https://about.gitlab.com/downloads/ diff --git a/translated/tech/20180615 BLUI- An easy way to create game UI.md b/translated/tech/20180615 BLUI- An easy way to create game UI.md deleted file mode 100644 index 5fdb1e5094..0000000000 --- a/translated/tech/20180615 BLUI- An easy way to create game UI.md +++ /dev/null @@ -1,57 +0,0 @@ -BLUI:创建游戏 UI 的简单方法 -====== - -![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/gaming_plugin_blui_screenshot.jpg?itok=91nnYCt_) - -游戏开发引擎在过去几年中变得越来越易于​​使用。像 Unity 这样一直免费使用的引擎,以及最近从基于订阅的服务切换到免费服务的 Unreal,允许独立开发者使用 AAA 发布商使用的相同行业标准工具。虽然这些引擎都不是开源的,但每个引擎都能够促进其周围的开源生态系统的发展。 - -这些引擎中包含的插件允许开发人员通过添加特定程序来增强引擎的基本功能。这些程序的范围可以从简单的资源包到更复杂的事物,如人工智能 (AI) 集成。这些插件来自不同的创作者。有些是由引擎开发工作室和有些是个人提供的。后者中的很多是开源插件。 - -### 什么是 BLUI? - -作为独立游戏开发工作室的一部分,我体验到了在专有游戏引擎上使用开源插件的好处。Aaron Shea 开发的一个开源插件 [BLUI][1] 对我们团队的开发过程起到了重要作用。它允许我们使用基于 Web 的编程(如 HTML/CSS 和 JavaScript)创建用户界面 (UI) 组件。尽管虚幻引擎(我们选择的引擎)有一个内置的 UI 编辑器实现了类似目的,我们也选择使用这个开源插件。我们选择使用开源替代品有三个主要原因:它们的可访问性、易于实现以及伴随的开源程序活跃的、支持性好的在线社区。 - -在虚幻引擎的最早版本中,我们在游戏中创建 UI 的唯一方法是通过引擎的原生 UI 集成,使用 Autodesk 的 Scaleform 程序,或通过在虚幻社区中传播的一些选定的基于订阅的 Unreal 集成。在这些情况下,这些解决方案要么不能为独立开发者提供有竞争力的 UI 解决方案,要么对于小型团队来说太昂贵,要么只能为大型团队和 AAA 开发者提供。 - -在商业产品和 Unreal 的原生整合失败后,我们向独立社区寻求解决方案。我们在那里发现了 BLUI。它不仅与虚幻引擎无缝集成,而且还保持了一个强大且活跃的社区,经常推出更新并确保独立开发人员可以轻松访问文档。BLUI 使开发人员能够将 HTML 文件导入虚幻引擎,并在程序内部对其进行编程。这使得通过网络语言创建的 UI 能够集成到游戏的代码、资源和其他元素中,并拥有所有 HTML、CSS、Javascript 和其他网络语言的能力。它还为开源 [Chromium Embedded Framework][2] 提供全面支持。 - -### 安装和使用 BLUI - -使用 BLUI 的基本过程包括首先通过 HTML 创建 UI。开发人员可以使用任何工具来实现此目的,包括自举 JavaScript 代码,外部 API 或任何数据库代码。一旦这个 HTML 页面完成,你可以像安装任何 Unreal 插件那样安装它并加载或创建一个项目。项目加载后,你可以将 BLUI 函数放在 Unreal UI 图纸中的任何位置,或者通过 C++ 进行硬编码。开发人员可以通过其 HTML 页面调用函数,或使用 BLUI 的内部函数轻松更改变量。 - -![Integrating BLUI into Unreal Engine 4 blueprints][4] - -将 BLUI 集成到虚幻 4 图纸中。 - -在我们当前的项目中,我们使用 BLUI 将 UI 元素与游戏中的音轨同步,为游戏机制的节奏方面提供视觉反馈。将定制引擎编程与 BLUI 插件集成很容易。 - -![Using BLUI to sync UI elements with the soundtrack.][6] - -使用 BLUI 将 UI 元素与音轨同步。 - -通过 BLUI GitHub 页面上的[文档][7],将 BLUI 集成到虚幻 4 中是一个微不足道的过程。还有一个由支援虚幻引擎开发人员组成的[论坛][8],他们乐于询问和回答关于插件以及实现该工具时出现的任何问题。 - -### 开源优势 - -开源插件可以在专有游戏引擎的范围内扩展创意。他们继续降低进入游戏开发的障碍,并且可以产生前所未有的游戏内机制和资源。随着对专有游戏开发引擎的访问持续增长,开源插件社区将变得更加重要。不断增长的创造力必将超过专有软件,开源代码将会填补这些空白,并促进开发真正独特的游戏。而这种新颖性正是让独立游戏如此美好的原因! - --------------------------------------------------------------------------------- - -via: https://opensource.com/article/18/6/blui-game-development-plugin - -作者:[Uwana lkaiddi][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[geekpi](https://github.com/geekpi) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://opensource.com/users/uwikaiddi -[1]:https://github.com/AaronShea/BLUI -[2]:https://bitbucket.org/chromiumembedded/cef -[3]:/file/400616 -[4]:https://opensource.com/sites/default/files/uploads/blui_gaming_plugin-integratingblui.png (Integrating BLUI into Unreal Engine 4 blueprints) -[5]:/file/400621 -[6]:https://opensource.com/sites/default/files/uploads/blui_gaming_plugin-syncui.png (Using BLUI to sync UI elements with the soundtrack.) -[7]:https://github.com/AaronShea/BLUI/wiki -[8]:https://forums.unrealengine.com/community/released-projects/29036-blui-open-source-html5-js-css-hud-ui diff --git a/translated/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md b/translated/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md new file mode 100644 index 0000000000..040254585a --- /dev/null +++ b/translated/tech/20180711 4 add-ons to improve your privacy on Thunderbird.md @@ -0,0 +1,63 @@ +4 个提高你在 Thunderbird 上隐私的加载项 +====== + +![](https://fedoramagazine.org/wp-content/uploads/2017/08/tb-privacy-addons-816x345.jpg) +Thunderbird 是由 [Mozilla][1] 开发的流行免费电子邮件客户端。与 Firefox 类似,Thunderbird 提供了大量加载项来用于额外功能和自定义。本文重点介绍四个加载项,以改善你的隐私。 + +### Enigmail + +使用 GPG(GNU Privacy Guard)加密电子邮件是保持其内容私密性的最佳方式。如果你不熟悉 GPG,请[查看我们在 Magazine 上的入门介绍][2]。 + +[Enigmail][3] 是使用 OpenPGP 和 Thunderbird 的首选加载项。实际上,Enigmail 与 Thunderbird 集成良好,可让你加密、解密、数字签名和验证电子邮件。 + +### Paranoia + +[Paranoia][4] 可让你查看有关收到的电子邮件的重要信息。表情符号显示电子邮件在到达收件箱之前经过的服务器之间的加密状态。 + +黄色,快乐的表情告诉你所有连接都已加密。蓝色,悲伤的表情意味着一个连接未加密。最后,红色的,害怕的表情表示在多个连接上该消息未加密。 + +还有更多有关这些连接的详细信息,你可以用来检查哪台服务器用于投递邮件。 + +### Sensitivity Header + +[Sensitivity Header][5] 是一个简单的加载项,可让你选择外发电子邮件的隐私级别。使用选项菜单,你可以选择敏感度:正常、个人、隐私和机密。 + +添加此标头不会为电子邮件添加额外的安全性。但是,某些电子邮件客户端或邮件传输/用户代理(MTA/MUA)可以使用此标头根据敏感度以不同方式处理邮件。 + +请注意,开发人员将此加载项标记为实验性的。 + +### TorBirdy + +如果你真的担心自己的隐私,[TorBirdy][6] 就是给你设计的加载项。它将 Thunderbird 配置为使用 [Tor][7] 网络。 + +据其[文档][8]所述,TorBirdy 在以前没有使用 Tor 的电子邮件帐户上提供较少的隐私。 + +>请记住,跟之前使用 Tor 访问的电子邮件帐户相比,之前没有使用 Tor 访问的电子邮件帐户提供**更少**的隐私/匿名/更弱的假名。但是,TorBirdy 仍然对现有帐户或实名电子邮件地址有用。例如,如果你正在寻求隐匿位置 - 你经常旅行并且不想通过发送电子邮件来披露你的所有位置--TorBirdy 非常有效! + +请注意,要使用此加载项,必须在系统上安装 Tor。 + +照片由 [Braydon Anderson][9] 在 [Unsplash][10] 上发布。 + + +-------------------------------------------------------------------------------- + +via: https://fedoramagazine.org/4-addons-privacy-thunderbird/ + +作者:[Clément Verna][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[geekpi](https://github.com/geekpi) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://fedoramagazine.org +[1]:https://www.mozilla.org/en-US/ +[2]:https://fedoramagazine.org/gnupg-a-fedora-primer/ +[3]:https://addons.mozilla.org/en-US/thunderbird/addon/enigmail/ +[4]:https://addons.mozilla.org/en-US/thunderbird/addon/paranoia/?src=cb-dl-users +[5]:https://addons.mozilla.org/en-US/thunderbird/addon/sensitivity-header/?src=cb-dl-users +[6]:https://addons.mozilla.org/en-US/thunderbird/addon/torbirdy/?src=cb-dl-users +[7]:https://www.torproject.org/ +[8]:https://trac.torproject.org/projects/tor/wiki/torbirdy +[9]:https://unsplash.com/photos/wOHH-NUTvVc?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText +[10]:https://unsplash.com/search/photos/privacy?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText