不同于NLP,数据驱动、机器学习无法攻克NLU,原因有三

自然语言理解(NLU)是人工智能的核心课题之一,也被广泛认为是*困难和*具标志性的任务。近年来,机器学习虽然被广泛使用,但是却不能很好的解决自然语言理解问题,其中可能涉及很多原因,ONTOLOGIK.AI 的创始人和首席NLU科学家Walid Saba给出了自己的观点。

%title插图%num

20 世纪 90 年代早期,一场统计学革命取代了人工智能,并在 2000 年达到顶峰,而神经网络凭借深度学习成功回归。这一经验主义转变吞噬了人工智能的所有子领域,其中这项技术*具争议的应用领域是自然语言处理。

以数据为驱动的经验方法在 NLP 中被广泛使用的原因主要包括:符号和逻辑方法在取得三十年的霸权后未能产生可扩展的 NLP 系统,从而导致 NLP 中所谓的经验方法(EMNLP)兴起,这些方法可以用数据驱动、基于语料库、统计和机器学习来统称。

这种向经验主义转变的背后动机非常简单:在我们对语言是如何工作、以及语言如何与日常口语中谈论的世界知识相关联的,在对这些了解之前,经验和数据驱动的方法有助于构建文本处理应用程序。正如 EMNLP 的先驱之一 Kenneth Church 所解释的那样,在 NLP 领域,倡导数据驱动和统计方法的科研人员,他们对解决简单的语言任务感兴趣,其动机从来不是暗示语言就是这样工作的,而是做简单的事情总比什么都不做要好。Church 认为这种转变动机被严重误解了,正如 McShane 在 2017 年所指出的,后来的几代人误解了这种经验趋势。

%title插图%num▲EMNLP 会议创立者、先驱之一 Kenneth Church

这种被误导的趋势导致了一种不幸的情况:坚持使用大型语言模型(large language model, LLM)构建 NLP 系统,这需要巨大的计算能力,而且试图通过记忆大量数据来接近自然语言对象,这种做法是徒劳的。这种伪科学的方法不仅浪费时间和资源,而且会误导新一代的年轻科学家,错误地让他们认为语言就是数据。更糟糕的是,这种做法还阻碍了自然语言理解(NLU)的发展。

相反,现在应该重新思考 NLU 方法,因为对于 NLU 来说,大数据方法不但在心理上、认知上,甚至计算上都让人难以置信,而且盲目数据驱动的方法在理论上和技术上也是有缺陷的。

自然语言处理 VS 自然语言理解

虽然自然语言处理(NLP)和自然语言理解(NLU)经常互换使用,但是两者之间存在实质性差异,突出这种差异至关重要。事实上,区分自然语言处理和自然语言理解之间的技术差异,我们可以意识到以数据驱动和机器学习的方法虽然适用于 NLP 任务,但这种方法与 NLU 无关。以 NLP 中*常见的下游任务为例:

  • 摘要;
  • 主题抽取;
  • 命名实体识别;
  • 语义检索;
  • 自动标签;
  • 聚类。

上述任务与 PAC(Probably Approximately Correct, 可能近似正确)范式一致。具体地,NLP 系统的输出评估是主观的:没有客观的标准来判断诸如一个摘要优于另一个,或某个系统提取的主题、短语比另一个系统提取的更好等等。然而,自然语言理解不允许有这样的自由度。要完全理解一个话语或一个问题,需要理解说话者试图表达的唯一思想。为了理解这个复杂的过程,以自然语言查询为例:

我们是否有一位退休的 BBC 记者在冷战期间驻扎在东欧国家?

在数据库中,对上述查询将有且只有一个正确答案。将上述表达转化为正确的 SQL 或者 SPARQL 查询具有很大的挑战性。这个问题背后的关键点包括:

  • 需要正确解读「退休的 BBC 记者」,即所有曾在 BBC 工作、现已退休的记者的集合;
  • 通过保留那些曾经也在一些「东欧国家」工作过的「退休 BBC 记者」来进一步过滤上述内容。除了地理限制,还有时间限制,那些「退休的 BBC 记者」的工作时间必须是「冷战期间」;
  • 以上意味着将介词短语「在冷战期间」附加到「驻扎」而不是「东欧国家」;
  • 进行正确的量词范围界定:我们寻找的不是在某个东欧国家工作的一个(单一)记者,而是在任何东欧国家工作的任何记者。

以上对语义的理解不会是可能、大致正确,而是非常确定的*对正确。换句话说,我们必须从对上述问题的多种可能解释中得出唯一一种含义,根据常识知识,推出提问者问题背后的想法。总而言之,对普通口语的真正理解与单纯的文本(或语言)处理是完全不同的问题。在文本(或语言)处理中,我们可以接受近似正确的结果——结果在可接受的概率下也是正确的。

通过这个简短的描述,我们应该可以清楚地了解为什么 NLP 与 NLU 不同,以及为什么 NLU 对机器来说是困难的。但是 NLU 的困难到底是什么呢?

NLU 难点在于缺失文本现象

所谓的缺失文本现象(missing text phenomenon, MTP),可以将其理解为 NLP 任务挑战的核心。语言交流的过程如下图所示:说者将思想编码为某种语言表达,然后听者将该语言表达解码为说者意图传达的思想。

%title插图%num▲图 1:说者和听者的语言交流过程

解码过程就是 NLU 中的 U——也就是说,理解语言话语背后的思想正是解码过程需要做的事情。此外,在这个解码过程中没有近似或任何自由度——也就是说,从一个话语的多种可能意义来看,说话人想要表达的思想只有一个,而解码过程中的「理解」必须达到这一个思想,这正是 NLU 困难的原因。

在这种复杂的交流中,有两种可能的优化方案:(1)说者可以压缩(和*小化)在编码中发送的信息量,并希望听者在解码(解压缩)过程中做一些额外的工作;(2)说者尽*大努力传递所有必要的信息来传达思想,而听者几乎什么也不用做。

随着过程的自然演变,上述两种方案似乎已经得到一个很好的平衡,即说者和听者的总体工作都得到了同样的优化。这种优化导致说者可以编码尽可能少的信息,而忽略其他信息。遗漏的信息对于说者和听者来说,是可以通过安全假设获得的信息,这正是我们经常说的普通背景知识。

为了理解这一过程的复杂性,以下图为例:黄色框中的是未优化的信息,以及我们通常所说的信息量同等但小得多的文本信息(绿色框中信息)。

%title插图%num

绿色框中信息要短很多,这正是我们说话的方式,语言虽短,但传达的是与较长信息相同的思想。通常我们不会明确地说出所有想要表达的东西:

%title插图%num

也就是说,为了有效地沟通,我们在交流中通常不会说认为对方说都知道的信息。这也正是为什么我们都倾向于忽略相同的信息——因为我们都了解每个人都知道的,而这正是我们所谓的共同背景知识。人类在大约 20 万年的进化过程中,发展出的这一天才优化过程非常有效。但这就是 NLU 的问题所在:机器不知道我们遗漏了什么信息,因为机器不知道我们都知道什么。*终结果导致 NLU 是非常困难的,因为如果一个软件程序不能以某种方式揭示人类在语言交流中遗漏和隐含的所有东西,它就不能完全理解我们语言话语背后的思想。这实际上才是 NLU 的挑战,而不是解析、词干分析、词性标记、命名实体识别等。

%title插图%num▲图 2:NLU 中很多挑战都是因为缺失文本现象造成的:图中缺失的文本(隐式的假设)用红色表示

上述示例表明,NLU 的挑战在于发现缺失信息,并隐含地认为这些信息是共享背景知识。下图 3 进一步解释了缺失文本现象:

%title插图%num

我们在下文给出三个原因来解释为什么机器学习和数据驱动方法不能解决 NLU 问题。

ML 方法与 NLU 无关:ML 是压缩,语言理解需要解压缩

用机器来实现自然语言理解是非常困难的,因为我们日常口语所表达的都是高度压缩信息,「理解」的挑战在于解压缩出丢失文本。这对人类来说是很简单的事情,但对机器来说却大不相同,因为机器不知道人类掌握的知识。但 MTP 现象恰恰说明了为什么数据驱动与机器学习方法会在 NLP 任务中有效,但是在 NLU 中不起作用。

研究者在数学上已经建立了可学习性和可压缩性(COMP)之间的等价关系。也就是说,只有当数据高度可压缩(即它有很多冗余)时,在数据集中才会发生可学习性,反之亦然。虽然证明可压缩性和可学习性之间的关系相当复杂,但直观上很容易理解:可学习性是关于理解大数据的,它在多维空间中找到一个函数可以覆盖所有的数据集信息。因此,当所有数据点都可以压缩成一个流形时,就会发生可学习性。但是 MTP 告诉我们 NLU 是关于解压缩的。以下列内容为例:

%title插图%num

机器学习是将大量数据泛化为单个函数。另一方面,由于 MTP,自然语言理解需要智能的解压缩技术,以发现所有缺失和隐式假设文本。因此,机器学习和语言理解是不相容的——事实上,它们是矛盾的。

ML 方法甚至与 NLU 无关:统计意义不大

ML 本质上是一种基于数据发现某些模式(相关性)的范式。研究者希望在自然语言中出现的各种现象在统计上存在显著差异。举例来说:

1. *杯装不进手提箱,因为它太

1a. 小

1b. 大

同义词与反义词(例如小和大,开和关等)以相同的概率出现在上下文中,因此,在统计上来说 (1a) 和(1b) 是等价的,然而 (1a) 和(1b)所代表的内容也是相当不同的:在此句中,「它」在 (1a)中隐含的意思是指手提箱小,但在 (1b) 中是指*杯大,尽管它们的语义相差很大,但是 (1a) 和(1b)在统计上是等价的。因此,统计分析不能建模(甚至不能近似)语义。

ML 方法甚至与 NLU 无关:intenSion

逻辑学家长期以来一直在研究一种称为「intension」的语义概念。为了解释什么是「intension」,首先要从所谓的语义三角(meaning triangle)开始讲起,如下图所示:

%title插图%num

在语义三角中,每个「事物(或每个认知对象)」都具有三部分:一个指代概念符号,以及这个概念(有时)具有的一些实例。以「独角兽」这个概念为例,在现实生活中并没有实际的示例。概念本身是其所有潜在实例的理想化模板,可以想象,几个世纪以来,哲学家、逻辑学家和认知科学家一直在争论概念的本质及其定义,不管那场辩论如何,我们可以在一件事情上达成一致:一个概念(通常由某个符号 / 标签引用)由一组特性集合和属性定义,也许还有附加公理和既定事实等。然而,一个概念与实际(不完美)实例不同,在完美的数学世界中也是如此。举例而言,虽然下面的算术表达式都具有相同的扩展,但它们具有不同的「intension」:

%title插图%num

上图中所有表达式的值都是 16,在某种意义(它们的值)上来说是相等的,但这只是属性之一。事实上,上面的表达式还有其他几个属性,比如语法结构(为什么 a 和 d 是不同的)、运算符的数量等。其中值只是一个属性,可以称为扩展(extension),而所有属性的集合是 intension。在应用科学(工程、经济学等)中,我们可以认为这些对象是相等的,如果它们在值上是相等的,但在认知中,这种相等是不存在的。举例来说:

%title插图%num

假设 (1) 是真的——也就是说,假设 (1) 确实发生了,并且我们见证了它。尽管如此,这并不意味着我们可以假设 (2) 为真,尽管我们所做的只是将 (1) 中的 16 替换为一个(假设),而该假设等于它的值。我们用一个假定等于它的对象替换了陈述中的一个对象,并且我们从真实的事物推断出不真实的事物!虽然在物理科学中可以很容易地用一个属性的对象来替换它,但这在认知中是行不通的。

总结来说,本文讨论了机器学习和数据驱动方法与 NLU 无关的三个原因(尽管这些方法可能用于一些本质上是压缩任务的文本处理任务)。在传达思想时,我们传递的是高度压缩的语言信息,需要大脑来解释和揭示所有缺失但隐含的背景信息。在很多方面,构建大语言模型时,机器学习和数据驱动方法都在徒劳地试图寻找数据中根本不存在的东西。我们必须意识到,日常的口语信息,并不是理想的语言数据。

机器学习不会解决自然语言理解(NLU)问题

%title插图%num

实证和数据驱动的革命

在20世纪90年代前叶,一场统计学革命如风暴般冲击了人工智能领域——这场革命在2000年代达到顶峰,神经网络以其现代深度学习(Deep Learning,DL)的化身成功回归。

这种转变影响到了人工智能的所有子领域,其中*具争议的应用是自然语言处理(Natural Language Processing,NLP)——人工智能的一个子领域。

数据驱动的经验方法在NLP中的广泛使用,是因为符号和逻辑的方法论在独占鳌头的三十年后,未能产生可扩展的NLP系统,促使了所谓的自然语言处理经验法(Empirical methods in NLP,EMNLP)的兴起——EMNLP可以用来统称数据驱动、基于语料库的短语、统计和机器学习的方法。

这种经验主义的转变,其动机很简单:直到我们对语言的工作原理以及语言如何与我们在日常口语中谈论的世界的知识有一些了解之前,经验和数据驱动的方法可能有助于构建一些实用的文本处理应用程序。

EMNLP的先驱之一,Kenneth Church对这一动机的解释是,NLP 的数据驱动和统计方法的倡导者们对解决简单的语言任务更感兴趣——他们的动机从来不是弄清楚语言的工作原理,而是“做一些简单的事情总比什么都不做要好”。

然而,Church在一篇论文《钟摆摆得太远》(A Pendulum swing Too Far)中指出,人们严重误解了这种转变的动机。

正如Marjorie McShane(《人工智能时代语言学》的作者)在2017年所指出的,后来的几代人误解了这种实证趋势,这种趋势是由对简单任务的实际解决方案所激发的,因为他们假设这种近似正确模型( Probably Approximately Correct,PAC)范式将扩展到完全的自然语言理解(Natural Language Understanding, NLU)

McShane说:“这些信念是如何在NLP群体中获得准公理地位的,这是一个令人着迷的问题,Church的一项观察部分地回答了这个问题:*近和现在的几代NLPers(NLP研究者)在语言学和NLP历史方面接受的教育不够广泛,因此,缺乏哪怕触及皮毛的动力。”

这种被误导的趋势导致了不幸的事态 :坚持使用 大型语言模型(Large Language Models,LLM)构建NLP系统,需要大量的计算能力,并试图通过记忆大量数据来近似自然语言的真实情况,但结果是徒劳的。

这种伪科学的方法不仅浪费时间和资源,而且正在腐蚀一代年轻科学家,让他们认为语言只是数据——这只会导致*望,并阻碍NLU方面的任何真正进展。

那么,现在是时候重新思考NLU的工作方法了, NLU的“大数据”处理方法不仅在心理上、认知上,甚至在计算上都是不可信的,而且正如下文即将展示的 ,这种盲目的数据驱动的 NLU 方法在理论上和技术上也存在缺陷。

%title插图%num

自然语言处理 vs 自然语言理解

虽然自然语言处理(NLP)自然语言理解(NLU)经常可以交换着使用,但这两者之间有本质的区别,突出这一区别是至关重要的。

事实上,认识到语言理解和单纯的语言处理之间的技术差异,会让我们意识到数据驱动和机器学习的方法虽然适用于某些 NLP 任务,但与 NLU 无关。

参考以下*常见的NLP下游任务(Downstream NLP Tasks):

  • 总结
  • 主题提取
  • 命名实体识别(NER)
  • (语义)搜索
  • 自动标记
  • 聚类

上述所有任务都与作为所有机器学习方法基础的近似正确模型(PAC)范式一致。具体来说,评估某些自然语言处理系统对上述任务的输出是主观的:没有客观的标准来判断一个摘要是否比另一个好,或者某个系统提取的(关键)主题 / 短语比另一个系统提取的更好,等等。

然而,语言理解不允许有任何程度的自由。要完全理解一个话语或一个问题,需要理解说话者试图表达的唯一思想。

为了理解这个过程的复杂性,可以参考下面的这个自然语言问题:

Do we have a retired BBC reporter that was based in an East European country during the Cold War?
(我们是否有一位在冷战期间驻扎在东欧国家的退休 BBC 记者?)

在某些数据库中,这个问题只会有一个正确答案。

因此,将上述内容转换为正式的 SQL(或 SPARQL)查询非常具有挑战性,这一句话的理解便有以下的重点:

  • 正确解读“ retired BBC reporter ”这一名词——指的是所有曾在BBC工作,现在已经退休的记者。
  • 通过保留那些也在“东欧国家”工作过的“退休BBC记者”来进一步过滤上述内容。
    除了地理上的限制,还有时间上的限制,那些“退休的BBC记者”的工作时间必须是“冷战期间”。
  • 介词短语“during the Cold War”是基于“was based in”,而不是“an East European country”。

译者解释:比方说,将句子里的“during the Cold War”替换成“with membership in the Warsaw Pact”,
那么句子就变成了“Do we have a retired BBC reporter that was based in an East European country with membership in the Warsaw Pact?”
意思是:我们是否有一位驻扎在华沙条约组织内东欧国家的退休 BBC记者?”
也就是说,原句中,东欧国家这个名词不需要“冷战期间”或者“华沙条约组织”等限定词,它可以是任何东欧国家。如果把在英文句子里的“an East European country during the Cold War”视为一个整体,那就变成了“冷战时期的东欧国家”,整个句子都会产生歧义。

  • 划定正确的量词范围:我们要找的不是在“某个”东欧国家工作的“一个”记者,而是在任何东欧国家工作的任何记者。

为了避免引起歧义,以上所有语义理解功能都必须做到完全准确。换句话说,根据我们对世界的常识,我们必须从对上述问题的多种可能解释中,得到一个且唯一的意义,也就是说话人的核心思想。

总之,对普通口语的真正理解与单纯的文本(或语言)处理是完全不同的问题,在文本(或语言)处理中,我们可以接受近似正确的结果——也就是NLP。

通过这个简短的描述,应该可以清楚地了解为什么NLP与NLU不同,以及为什么NLU对机器来说是困难的。

但是NLU面临的困难到底是什么呢?

%title插图%num

自然语言理解的困境:缺失文本现象

“缺失文本现象”(Missing Text Phenomenon,MTP),被一度认为是NLU中所有挑战的核心。

语言交流如下图所示:传达者用某种自然语言将思想编码为语言表达,接收者将语言表达解码为传达者想要传达的思想。

%title插图%num

“解码”过程就是NLU中的“U”,也就是说,理解语言话语背后的思想正是在解码过程中发生的事情。这个过程中不能有任何妥协和误差,只能从传达者的话语里找到那唯一准确的思想,这便是NLU的困境。

对此,出现了两个优化的方向:

  • 传达者可以减少一句话里的信息量,或者让接收者增加一些额外的理解工作;
  • 传达者会尽*大努力,在一句话里传递全部想法,而接收者几乎什么都做不了。

这个过程演变出了一个正确的平衡,即传达者和接收者的整体工作都得到了同样的优化。这种优化导致传达者传递的信息,相对的也遗漏了接收者获得的信息。而往往被遗漏的信息,通常是我们假定的传达者和接收者都能获得的信息,也就是所谓的普通背景知识。

为了理解这一过程的复杂性,请看下图:红框中的是未优化过的原信息,而绿框中的是我们所说的“信息量同等”但内容少了很多的信息。

%title插图%num

绿框里的话就和我们日常生活说的话一样,简短却保证信息量的准确传达。通常我们不会明确地陈述所有其他的东西,因为我们为了有效的沟通,不会去说那些众所周知的事情,这是人类在20万年的进化过程中发展出来的技能,但这就是NLU的问题所在:机器不知道我们遗漏了什么,因为它们不知道我们都知道什么。

*终,得出一个结论:NLU是非常非常困难的。因为如果一个软件程序不能以某种方式“揭示”人类在语言交流中遗漏和隐含假定的所有东西,那么它就不能完全理解我们语言话语背后的思想。这实际上是NLU面临的挑战,而不是解析、词干提取、POS标记、命名实体识别等等。

%title插图%num

上图列举了NLU其他的众所周知的挑战,而这都是由于MTP(缺失文本现象)。这些句子中,缺失(和隐含假设)的文本都被标记为了红色。这些例子表明,NLU的挑战是去发现丢失的信息,但不是去填补这些信息,而是对这些信息为什么会被人类下意识省略有一个清楚的认识。

下文将列出三个原因,来解释为什么机器学习不会解决自然语言理解(NLU)问题。

%title插图%num

原因一:机器学习需要压缩,NLU需要解压缩

由于MTP的影响,机器对理解自然语言是*其困难的——因为,我们日常交流中的口语是经过高度压缩的,NLU的挑战在于,明明知道被压缩的内容,却选择去解压缩缺失的文本——对于我们人类来说很简单,而对于机器却很困难,因为机器并不知道我们都知道的东西。

但MTP现象恰恰说明了为什么数据驱动和机器学习方法可能在一些NLP下游任务中有用,但却与NLU无关。已有学者在数学上建立了可学习性和可压缩性之间的等价关系。也就是说,数据集的易学性只有在数据高度可压缩(即它有很多冗余)的情况下才会发生,反之亦然。

而压缩之间的证据和易学性相当技术,直观上很容易看出为什么:学习是关于消化大量的数据和发现一个函数在多维空间的覆盖整个数据集(以及看不见的数据相同的模式 / 分布)。因此,当所有的数据点可以压缩到一个单一的流形时,学习性就发生了。

但是MTP已经告诉了我们,NLU是需要解压缩的:

%title插图%num

机器学习是研究怎么将大量数据泛化成单个函数。而由于MTP的存在,NLU需要智能的“解压缩”技术来发现所有缺失的和隐含的假定文本。

因此,机器学习和NLU是不相容的——事实上,它们是矛盾的。

%title插图%num

原因二:没有统计的意义

机器学习本质上是一种基于在数据中发现某些模式(相关性)的范式。而自然语言中的各种现象在统计上存在显著差异。但是,以下面这个例子为证:

Q.*杯装不进手提箱,因为它也太
1a、小了
1b、大了

同义词和反义词,如“小”和“大”(或“开”和“闭”等)出现在相同的上下文中,概率相等。
因此,(1a)和(1b)在统计学上是等价的,但即使对一个4岁的孩子来说,(1a)和(1b)也是相当不同的:(1a)中的“它”指的是“手提箱”,(1b)中的“它”指的是“*杯”。

因此,统计分析不能建模(甚至不能近似)语义。

此时便会出现异议:只要用足够的案例进行填充,机器就可以统计出显著性。但是,需要多少案例才能让机器“学习”如何解析上文问题中这样的结构引用呢?

我们可以对一个“包”、一个“手提箱”、一个“公文包”进行一般化陈述,这些都被认为是通用类型“容器”的子类型。因此,在纯粹的数据驱动范式中,上面的每一个容器都是不同的,必须在数据中分别列出。
如果我们在上面的模式上加上语义上的差异(把“因为”改为“虽然”-),机器学习后一个粗略的计算显示,一个系统将需要呈现大约4000万个以上的变化,以学习如何解析引用。

正如著名认知科学家GeorgeMiller的话,为了捕捉NLU系统所需要的所有语法和语义变化,神经网络可能需要比宇宙中原子的数量还要多的特征数量!

%title插图%num

原因三:Intension

长期以来,逻辑学家一直在研究一个名为 “intension” 的语义概念。
为了解释 “intension” 是什么,要从“语义三角(meaning triangle)”的概念开始解释:

%title插图%num

在这个三角之中,一个符号用来指代一个概念,而概念可以有实际的对象作为实例。例如,神话中的独角兽只是一个概念,没有实际的独角兽实例。因此,每一事物(或认识的每一对象)都有三个部分:一个指向概念的符号,而概念有时也有实际的实例。

几个世纪以来,哲学家、逻辑学家和认知科学家一直在争论概念的本质及其定义。

他们有一个共识:概念(通常由一些符号 / 标签指代)是由一组属性和属性定义的,也许还有附加的公理和已建立的事实,等等。

然而,概念与实际的实例不同,在完美的数学世界中也是如此。
例如,虽然下面的算术表达式都有相同的扩展,但它们有不同的含义:

%title插图%num

“intension” 决定了概念的外延,但外延本身并不是概念的完整表现。

因此,虽然所有表达式的值都是16,它们的值是相等的,但这只是它们的属性之一。事实上,上面的表达式还有其他几个属性,比如它们的语法结构(这就是(a)和(d)不同的原因)、操作符的数量、操作数的数量,等等。

而这个值只是一个属性, “intension” 则是所有属性的集合。

而在应用科学(工程、经济学等)中,我们可以认为这些对象是平等的,但在认知(尤其是在语言理解)中,这种平等是失败的。

这里有一个简单的例子:

%title插图%num

假设(1)是真的,也并不意味着我们可以假设(2)为真,尽管我们所做的只是将(1)中的“16”替换为与它相等的值。在物理科学中,我们可以很容易地用一个具有相同属性的物体来代替一个物体,但在认知科学中却行不通。

那么,关于 “intension” 的讨论的要点是什么呢?
自然语言中充满了 “intension” 现象,因为语言所传达的思想对象具有不可忽视的 “intension” 。
机器学习和数据驱动方法的所有变体都是纯粹的外延——它们的操作对象是数字,而不是它们的符号和结构属性,因此在这个范式中,我们无法在自然语言中建模各种 “intension” 现象。

%title插图%num

结语

语言,是我们用来编码我们拥有的无限想法的工具。在构建越来越大的语言模型时,很多机器学习和数据驱动方法都在徒劳地试图寻找数据中根本不存在的东西。

本文分享了机器学习不会解决自然语言理解(NLU)问题的三大理由,而这篇文章本身也证明了“语言是被压缩了”的观点,其中的遣词造句都需要大脑来“揭示”所有缺失的信息。那么,你认为机器学习未来可以解决自然语言理解(NLU)问题吗?请参与下方投票和评论,分享你的真知灼见!

原文链接:https://thegradient.pub/machine-learning-wont-solve-the-natural-language-understanding-challenge/

Swift实现iOS内购

前言

  • Swift作为当前在github上成长*快的语言之一,本人在学习iOS未曾学习过OC,因此在做iOS项目过程中全部采用了Swift,下面详细介绍下Swift的内购的实现。github地址:https://github.com/RyanLeeLY/…

起步

  • 首先我就不介绍如何在iTunesConnect上添加内购商品了,总之添加完商品后,我们需要用到的是productIdentifiers即你填写的商品ID,这个ID是商品在商店中的唯一标识。
  • 导入StoreKit,创建一个类,SKPaymentTransactionObserver协议:当交易在队列中有更新或者移出队列时这个观察者会被调用;SKProductsRequestDelegate实现该协议的代理会受到商品请求返回的信息。
    public class LYIAPManager: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate
  • 自定义协议,LYIAPRequestDelegate处理请求成功后的代理,LYIAPPaymentDelegate交易时的代理,
    LYIAPDelegate继承上面两个协议,方便使用。
  1. @objc public protocol LYIAPDelegate: LYIAPRequestDelegate, LYIAPPaymentDelegate{
  2. }
  3. @objc public protocol LYIAPRequestDelegate: NSObjectProtocol{
  4. @objc optional func requestFinished()
  5. }
  6. @objc public protocol LYIAPPaymentDelegate: NSObjectProtocol{
  7. func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  8. @objc optional func transactionFailed(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  9. @objc optional func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  10. @objc optional func transactionDeferred(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  11. @objc optional func transactionPurchasing(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  12. @objc optional func transactionRestoreFailedWithError(_ error:NSError)
  13. @objc optional func transactionRestoreFinished(_ isSuccess:Bool)
  14. }

全部实现

  1. //
  2. // LYIAPManager.swift
  3. // crater
  4. //
  5. // Created by 李尧 on 2016/10/11.
  6. // Copyright © 2016年 secstudio. All rights reserved.
  7. //
  8. import UIKit
  9. import StoreKit
  10. private func printLog<T>(_ message:T, file:String = #file, method:String = #function, line:Int = #line){
  11. #if DEBUG
  12. print(“\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)”)
  13. #endif
  14. }
  15. @objc public protocol LYIAPDelegate: LYIAPRequestDelegate, LYIAPPaymentDelegate{
  16. }
  17. @objc public protocol LYIAPRequestDelegate: NSObjectProtocol{
  18. @objc optional func requestFinished()
  19. }
  20. @objc public protocol LYIAPPaymentDelegate: NSObjectProtocol{
  21. func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  22. @objc optional func transactionFailed(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  23. @objc optional func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  24. @objc optional func transactionDeferred(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  25. @objc optional func transactionPurchasing(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
  26. @objc optional func transactionRestoreFailedWithError(_ error:Error)
  27. @objc optional func transactionRestoreFinished(_ isSuccess:Bool)
  28. }
  29. public let LYIAP = LYIAPManager.LYIAPInstance
  30. public class LYIAPManager: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate {
  31. fileprivate let VERIFY_RECEIPT_URL = “https://buy.itunes.apple.com/verifyReceipt”
  32. fileprivate let ITMS_SANDBOX_VERIFY_RECEIPT_URL = “https://sandbox.itunes.apple.com/verifyReceipt”
  33. fileprivate var restoreSuccess = false
  34. fileprivate var productDict:NSMutableDictionary?
  35. static let LYIAPInstance = LYIAPManager()
  36. var request:SKProductsRequest?
  37. var observer:SKPaymentTransactionObserver?
  38. weak var delegate:LYIAPDelegate?
  39. weak var requestDelegate:LYIAPRequestDelegate?
  40. weak var paymentDelegate:LYIAPPaymentDelegate?
  41. fileprivate override init() {
  42. }
  43. /**
  44. Example: let productsIds = NSSet(array: [“com.xxx.xxx.abc”])
  45. */
  46. func setRequestWithProducts(_ productsIds: NSSet, delegate: LYIAPDelegate?) {
  47. request = SKProductsRequest(productIdentifiers: productsIds as! Set<String>)
  48. request?.delegate = self
  49. SKPaymentQueue.default().add(self)
  50. if(delegate != nil){
  51. self.delegate = delegate!
  52. paymentDelegate = delegate!
  53. requestDelegate = delegate!
  54. }
  55. }
  56. func setPaymentTransactionsDelegate(_ delegate: LYIAPPaymentDelegate){
  57. paymentDelegate = delegate
  58. }
  59. func setProductsRequestDelegate(_ delegate: LYIAPRequestDelegate){
  60. requestDelegate = delegate
  61. }
  62. func removeRequestDelegate(){
  63. requestDelegate = nil
  64. }
  65. func removeProductsDelegate(){
  66. paymentDelegate = nil
  67. }
  68. func startRequest(){
  69. testIsNil()
  70. request?.start()
  71. }
  72. func cancelRequest(){
  73. testIsNil()
  74. request?.cancel()
  75. }
  76. func startPaymentWithProductId(_ productId: String){
  77. //if loaded
  78. if(SKPaymentQueue.canMakePayments()){
  79. guard productDict != nil else{
  80. printLog(“products haven’t been loaded”)
  81. return
  82. }
  83. requestPaymentWithProduct(productDict![productId] as! SKProduct)
  84. }else{
  85. printLog(“IAP is not supported!”)
  86. }
  87. }
  88. func restorePayment(){
  89. restoreSuccess = false
  90. SKPaymentQueue.default().restoreCompletedTransactions()
  91. }
  92. public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
  93. if (productDict == nil) {
  94. printLog(“first load products”)
  95. productDict = NSMutableDictionary(capacity: response.products.count)
  96. }
  97. for product in response.products {
  98. printLog(“product \(product.productIdentifier) loaded”)
  99. productDict!.setObject(product, forKey: product.productIdentifier as NSCopying)
  100. }
  101. requestDelegate?.requestFinished?()
  102. }
  103. public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
  104. print(“updatedTransactions”)
  105. for transaction in transactions {
  106. switch transaction.transactionState{
  107. case .purchased:
  108. printLog(“purchased”)
  109. paymentDelegate?.transactionPurchased(queue, transaction: transaction)
  110. SKPaymentQueue.default().finishTransaction(transaction)
  111. break
  112. case .failed:
  113. printLog(“failed”)
  114. paymentDelegate?.transactionFailed?(queue, transaction: transaction)
  115. SKPaymentQueue.default().finishTransaction(transaction)
  116. break
  117. case .restored:
  118. printLog(“restore”)
  119. restoreSuccess = true
  120. paymentDelegate?.transactionRestore?(queue, transaction: transaction)
  121. SKPaymentQueue.default().finishTransaction(transaction)
  122. break
  123. case .purchasing:
  124. paymentDelegate?.transactionPurchasing?(queue, transaction: transaction)
  125. printLog(“purchasing”)
  126. break
  127. case .deferred:
  128. paymentDelegate?.transactionDeferred?(queue, transaction: transaction)
  129. printLog(“deferred”)
  130. break
  131. }
  132. }
  133. }
  134. public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
  135. printLog(“retore failed with error:\(error)”)
  136. paymentDelegate?.transactionRestoreFailedWithError?(error)
  137. }
  138. public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
  139. printLog(“finished restore”)
  140. paymentDelegate?.transactionRestoreFinished?(restoreSuccess)
  141. }
  142. private func requestPaymentWithProduct(_ product: SKProduct){
  143. let payment = SKPayment(product: product)
  144. SKPaymentQueue.default().add(payment)
  145. }
  146. private func testIsNil(){
  147. if(request == nil){
  148. printLog(“request hasn’t been init”)
  149. }else if(request?.delegate == nil){
  150. printLog(“request delegate hasn’t been set”)
  151. }
  152. }
  153. func verifyPruchase(completion:@escaping (NSDictionary?, NSError?) -> Void) {
  154. // 验证凭据,获取到苹果返回的交易凭据
  155. let receiptURL = Bundle.main.appStoreReceiptURL
  156. // 从沙盒中获取到购买凭据
  157. let receiptData = NSData(contentsOf: receiptURL!)
  158. #if DEBUG
  159. let url = NSURL(string: ITMS_SANDBOX_VERIFY_RECEIPT_URL)
  160. #else
  161. let url = NSURL(string: VERIFY_RECEIPT_URL)
  162. #endif
  163. let request = NSMutableURLRequest(url: url! as URL, cachePolicy: NSURLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 10.0)
  164. request.httpMethod = “POST”
  165. let encodeStr = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithLineFeed)
  166. let payload = NSString(string: “{\”receipt-data\” : \”” + encodeStr! + “\”}”)
  167. let payloadData = payload.data(using: String.Encoding.utf8.rawValue)
  168. request.httpBody = payloadData;
  169. let session = URLSession.shared
  170. let semaphore = DispatchSemaphore(value: –1)
  171. let dataTask = session.dataTask(with: request as URLRequest,
  172. completionHandler: {(data, response, error) -> Void in
  173. if error != nil{
  174. print(“error1”)
  175. completion(nil,error as NSError?)
  176. }else{
  177. if (data==nil) {
  178. print(“error2”)
  179. completion(nil,error as NSError?)
  180. }
  181. do{
  182. let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
  183. if (jsonResult.count != 0) {
  184. // 比对字典中以下信息基本上可以保证数据安全
  185. // bundle_id&application_version&product_id&transaction_id
  186. // 验证成功
  187. let receipt = jsonResult[“receipt”] as! NSDictionary
  188. completion(receipt,nil)
  189. }
  190. print(jsonResult)
  191. }catch{
  192. print(“error3”)
  193. completion(nil,nil)
  194. }
  195. }
  196. semaphore.signal()
  197. }) as URLSessionTask
  198. dataTask.resume()
  199. semaphore.wait()
  200. }
  201. }

使用方法

  • 新建一个ViewController,实现协议LYIAPDelegate
  1. import UIKit
  2. import StoreKit
  3. class ViewController: UIViewController,LYIAPDelegate {
  4. override func viewDidLoad() {
  5. super.viewDidLoad()
  6. let productsIds = NSSet(array: [“com.xxx.xxx.abc”])
  7. LYIAP.setRequestWithProducts(productsIds, delegate: self)
  8. LYIAP.startRequest()
  9. // Do any additional setup after loading the view, typically from a nib.
  10. }
  11. override func didReceiveMemoryWarning() {
  12. super.didReceiveMemoryWarning()
  13. // Dispose of any resources that can be recreated.
  14. }
  15. func requestFinished() {
  16. // Do something when products have been loaded.
  17. }
  18. func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
  19. // Identifier of the product that has been purchased
  20. debugPrint(transaction.payment.productIdentifier)
  21. LYIAP.verifyPruchase(completion: {(receipt,error) in
  22. // You can verify the transaction. In this callback, you will get the receipt if the transaction is verified by the APPLE. You can compare some tranction infomation with the receipt.
  23. debugPrint(receipt)
  24. })
  25. }
  26. func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
  27. // Identifier of the product that has been restored
  28. // You must add restore function to your app accroding to APPLE’s provisions
  29. debugPrint(transaction.payment.productIdentifier)
  30. }
  31. func transactionRestoreFinished(_ isSuccess: Bool) {
  32. // It is called when restore is finished. isSuccess will be true when some products have been restored successfully.
  33. }
  34. }

 

iOS内购(代码部分,swift)

一.创建支付类
1.创建一个类继承NSObject为内购付款类

import UIKit
class AppPayManager: NSObject {

}
2.类内引入头文件

import StoreKit
3.实现单利,方便支付结果的回调

let payManager = AppPayManager()
class var shareInstance : AppPayManager {
return payManager
}
二.发起购买请求
1.发起购买请求前的准备工作声明商品id String类型,声明回调闭包,以及支付结果状态码后面会用到

var proId:String!
//21008表示生产换使用 21007表示测试环境使用
var state = 21008
var resultBlock:(_ result:String)->Void = { (_ result:String)->Void in

}
2.声明并实现发起购买请求的方法,并在方法中添加购买结果的监听。

增加监听的协议

class AppPayManager: NSObject ,SKPaymentTransactionObserver{
实现发起购买请求,参数一商品id,参数2回调逃逸闭包(商品id,也就是在开发者网站添加商品的id,在这里可以先提供一个com.saixin.eduline6)

//MARK:发起购买请求
func startPay(proId:String,resultBlock:@escaping ((_ result:String)->Void)) {
self.resultBlock = resultBlock
if !SKPaymentQueue.canMakePayments() {
print(“不可使用苹果支付”)
return
}
//监听购买结果
SKPaymentQueue.default().add(self)
self.proId = proId
let set = Set.init([proId])
let requst = SKProductsRequest.init(productIdentifiers: set)
requst.delegate = self
requst.start()
}
3.发起请求后里面设置了一个SKProductsRequst的代理,所以需要遵循协议并实现协议方法

增加一个SKProductsRequst协议

class AppPayManager: NSObject ,SKProductsRequestDelegate,SKPaymentTransactionObserver{
实现协议方法

//MARK:发起购买请求回调代理方法
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let productArray = response.products
if productArray.count == 0 {
print(“此商品id没有对应的商品”)
return
}
var product:SKProduct!
for pro in productArray {
if pro.productIdentifier == proId {
product = pro
break
}
}
print(product.description)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
print(product.productIdentifier)
let payment = SKMutablePayment.init(product: product)
payment.quantity = 1
SKPaymentQueue.default().add(payment)
}
三.获取购买结果
实现SKPaymentTransactionObserver的协议方法监听购买结果

//MARK:购买结果 监听回调
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for tran in transactions {
switch tran.transactionState {
case .purchased://购买完成
SKPaymentQueue.default().finishTransaction(tran)
completePay(transaction: tran)
break
case.purchasing://商品添加进列表
break
case.restored://已经购买过该商品
SKPaymentQueue.default().finishTransaction(tran)
break
case.failed://购买失败
SKPaymentQueue.default().finishTransaction(tran)
break
default:
break
}
}
}
四.支付成功获取交易凭证
//MARK:购买成功验证凭证
func completePay(transaction:SKPaymentTransaction) {
//获取交易凭证
let recepitUrl = Bundle.main.appStoreReceiptURL
let data = try! Data.init(contentsOf: recepitUrl!)
if recepitUrl == nil {
self.resultBlock(“交易凭证为空”)
print(“交易凭证为空”)
return
}

if verify_type == 0 {//客户端验证
verify(data: data,transaction: transaction)
}else{//服务器端校验

}
//注销交易
SKPaymentQueue.default().finishTransaction(transaction)
}
五.校验支付凭证完成支付
方式一.客户端校验支付凭证

首先验证正式环境,如果返回结果为21007则表示为沙盒环境,再去校验沙盒环境,当status为0时则表示校验成功

//沙盒验证地址
let url_receipt_sandbox = “https://sandbox.itunes.apple.com/verifyReceipt”
//生产环境验证地址
let url_receipt_itunes = “https://buy.itunes.apple.com/verifyReceipt”
//MARK:客户端验证
func verify(data:Data,transaction:SKPaymentTransaction) {
let base64Str = data.base64EncodedString(options: .endLineWithLineFeed)
let params = NSMutableDictionary()
params[“receipt-data”] = base64Str
let body = try! JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
var request = URLRequest.init(url: URL.init(string: state == 21008 ? url_receipt_itunes : url_receipt_sandbox)!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 20)
request.httpMethod = “POST”
request.httpBody = body
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
let dict = try! JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
print(dict)
SKPaymentQueue.default().finishTransaction(transaction)
let status = dict[“status”] as! Int
switch(status){
case 0:

self.resultBlock(“购买成功”)
break
case 21007:
self.state = 21007
self.verify(data: data!, transaction: transaction)
break
default:
self.resultBlock(“验证失败”)
break
}
//移除监听
SKPaymentQueue.default().remove(self)
}
task.resume()
}
方式二.服务器端校验

一般来讲是将获取到的支付凭证转化为base64编码格式传递后台便可以,和普通接口没有区别。

六.漏单处理
一般是客户已经完成支付,但尚未来得及校验支付凭证便退出了APP。一般我们可以在用户完成支付的同时存储支付凭证到本地,校验完支付凭证之后将数据清除。每次启动应用如果支付凭证还在则表示有漏单情况,直接发起校验请求便可。

七.内购工具类连接
https://download.csdn.net/download/weixin_39339407/14240375

八.开放者账号内购配置连接
https://blog.csdn.net/weixin_39339407/article/details/112671902

iOS内购代码(苹果支付ApplePay)

刚刚做了内购, 记录一下
这里直接上代码, 至于写代码之前的一些设置工作参考以下文章:
http://www.jianshu.com/p/690a7c68664e
http://www.jianshu.com/p/86ac7d3b593a

需要注意的是:

  1. 只要工程配置了对应的证书, 就能请求商品信息, 不需要任何其他处理
  2. 沙盒测试填写的邮箱不能是已经绑定appleID的邮箱, 也不能是AppleID的救援邮箱, 其他的无所谓, 其实, 哪怕你填写的邮箱不存在也没有关系
  1. //
  2. // IAPManager.m
  3. // SpeakEnglish
  4. //
  5. // Created by Daniel on 16/6/8.
  6. // Copyright © 2016年 Daniel. All rights reserved.
  7. //
  8. #import “IAPManager.h”
  9. #import <StoreKit/StoreKit.h>
  10. @interface IAPManager ()<SKPaymentTransactionObserver, SKProductsRequestDelegate>
  11. // 所有商品
  12. @property (nonatomic, strong)NSArray *products;
  13. @property (nonatomic, strong)SKProductsRequest *request;
  14. @end
  15. static IAPManager *manager = nil;
  16. @implementation IAPManager
  17. + (instancetype)shareIAPManager {
  18. static dispatch_once_t onceToken;
  19. dispatch_once(&onceToken, ^{
  20. manager = [self new];
  21. [[SKPaymentQueue defaultQueue] addTransactionObserver:manager];
  22. });
  23. return manager;
  24. }
  25. – (void)dealloc {
  26. [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  27. }
  28. // 请求可卖的商品
  29. – (void)requestProducts
  30. {
  31. if (![SKPaymentQueue canMakePayments]) {
  32. // 您的手机没有打开程序内付费购买
  33. return;
  34. }
  35. // 1.请求所有的商品ID
  36. NSString *productFilePath = [[NSBundle mainBundle] pathForResource:@”iapdemo.plist” ofType:nil];
  37. NSArray *products = [NSArray arrayWithContentsOfFile:productFilePath];
  38. // 2.获取所有的productid
  39. NSArray *productIds = [products valueForKeyPath:@”productId”];
  40. // 3.获取productid的set(集合中)
  41. NSSet *set = [NSSet setWithArray:productIds];
  42. // 4.向苹果发送请求,请求可卖商品
  43. _request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
  44. _request.delegate = self;
  45. [_request start];
  46. }
  47. /**
  48. * 当请求到可卖商品的结果会执行该方法
  49. *
  50. * @param response response中存储了可卖商品的结果
  51. */
  52. – (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
  53. {
  54. for (SKProduct *product in response.products) {
  55. // 用来保存价格
  56. NSMutableDictionary *priceDic = @{}.mutableCopy;
  57. // 货币单位
  58. NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
  59. [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
  60. [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
  61. [numberFormatter setLocale:product.priceLocale];
  62. // 带有货币单位的价格
  63. NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];
  64. [priceDic setObject:formattedPrice forKey:product.productIdentifier];
  65. NSLog(@”价格:%@”, product.price);
  66. NSLog(@”标题:%@”, product.localizedTitle);
  67. NSLog(@”秒速:%@”, product.localizedDescription);
  68. NSLog(@”productid:%@”, product.productIdentifier);
  69. }
  70. // 保存价格列表
  71. [[NSUserDefaults standardUserDefaults] setObject:priceDic forKey:@”priceDic”];
  72. [[NSUserDefaults standardUserDefaults] synchronize];
  73. // 1.存储所有的数据
  74. self.products = response.products;
  75. self.products = [self.products sortedArrayWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(SKProduct *obj1, SKProduct *obj2) {
  76. return [obj1.price compare:obj2.price];
  77. }];
  78. }
  79. #pragma mark – 购买商品
  80. – (void)buyProduct:(SKProduct *)product
  81. {
  82. // 1.创建票据
  83. SKPayment *payment = [SKPayment paymentWithProduct:product];
  84. WELog(@”productIdentifier—-%@”, payment.productIdentifier);
  85. // 2.将票据加入到交易队列中
  86. [[SKPaymentQueue defaultQueue] addPayment:payment];
  87. }
  88. #pragma mark – 实现观察者回调的方法
  89. /**
  90. * 当交易队列中的交易状态发生改变的时候会执行该方法
  91. *
  92. * @param transactions 数组中存放了所有的交易
  93. */
  94. – (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
  95. {
  96. /*
  97. SKPaymentTransactionStatePurchasing, 正在购买
  98. SKPaymentTransactionStatePurchased, 购买完成(销毁交易)
  99. SKPaymentTransactionStateFailed, 购买失败(销毁交易)
  100. SKPaymentTransactionStateRestored, 恢复购买(销毁交易)
  101. SKPaymentTransactionStateDeferred *终状态未确定
  102. */
  103. for (SKPaymentTransaction *transaction in transactions) {
  104. switch (transaction.transactionState) {
  105. case SKPaymentTransactionStatePurchasing:
  106. WELog(@”用户正在购买”);
  107. break;
  108. case SKPaymentTransactionStatePurchased:
  109. WELog(@”productIdentifier—–>%@”, transaction.payment.productIdentifier);
  110. [self buySuccessWithPaymentQueue:queue Transaction:transaction];
  111. break;
  112. case SKPaymentTransactionStateFailed:
  113. NSLog(@”购买失败”);
  114. [queue finishTransaction:transaction];
  115. break;
  116. case SKPaymentTransactionStateRestored:
  117. NSLog(@”恢复购买”);
  118. //TODO:向服务器请求补货,服务器补货完成后,客户端再完成交易单子
  119. //[queue finishTransaction:transaction];
  120. break;
  121. case SKPaymentTransactionStateDeferred:
  122. NSLog(@”*终状态未确定”);
  123. break;
  124. default:
  125. break;
  126. }
  127. }
  128. }
  129. – (void)buySuccessWithPaymentQueue:(SKPaymentQueue *)queue Transaction:(SKPaymentTransaction *)transaction {
  130. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  131. NSDictionary *params = @{@”user_id”:@”user_id”,
  132. // 获取商品
  133. @”goods”:[self goodsWithProductIdentifier:transaction.payment.productIdentifier]};
  134. [manager POST:@”url” parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
  135. if ([responseObject[@”code”] intValue] == 200) {
  136. // 防止丢单, 必须在服务器确定后从交易队列删除交易
  137. // 如果不从交易队列上删除交易, 下次调用addTransactionObserver:, 仍然会回调’updatedTransactions’方法, 以此处理丢单
  138. WELog(@”购买成功”);
  139. [queue finishTransaction:transaction];
  140. }
  141. } failure:^(NSURLSessionDataTask *task, NSError *error) {
  142. }];
  143. }
  144. // 商品列表 也可以使用从苹果请求的数据, 具体细节自己视情况处理
  145. // goods1 是商品的ID
  146. – (NSString *)goodsWithProductIdentifier:(NSString *)productIdentifier {
  147. NSDictionary *goodsDic = [[NSUserDefaults standardUserDefaults] objectForKey:@”priceDic”];
  148. return goodsDic[productIdentifier];
  149. }
  150. // 恢复购买
  151. – (void)restore
  152. {
  153. [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
  154. }
  155. – (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
  156. // 恢复失败
  157. WELog(@”恢复失败”);
  158. }
  159. // 取消请求商品信息
  160. – (void)dealloc {
  161. [_request cancel];
  162. }
  163. @end

补充:

对于丢单的交易,在执行初始化[[SKpaymentQueue defaultQueue] addTransactionObserver: self] 的时候,如果有未完成的交易,会直接回调updatedTransactions,并且进入case SKPaymentTransationStateRestored,此时把这些未完成的交易告知服务器进行补货,补货完成再通知客户端,客户端再执行completeTransaction关闭单子

总结:
内购有三个可能出现的问题

  1. 支付成功后, 没来得及向服务器发送交易成功的数据就退出应用, 导致丢单. 这个问题貌似不需要本地化数据也已经没问题了, 除非再次回调updatedTransactions方法时已经拿不到票据了, 这样才有必要本地存储票据.
  2. 无法绑定交易和对应的用户. 因为applicationUsername的存在这已经不是问题了.
  3. 只用别人的手机进行购买, 没来得及向服务器发送交易成功的数据就退出应用, 导致丢单. 如果别人再也不打开这个应用甚至删掉了, 目前看来, 没有办法解决

参考资料:

  1. 苹果内购二次验证 PHP代码
    http://my.oschina.net/qianglong/blog/503861
  2. In-App Purchase Programming Guide
    https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html#//apple_ref/doc/uid/TP40008267
  3. iPhone In App Purchase购买完成时验证transactionReceipt
    http://www.cnblogs.com/eagley/archive/2011/06/15/2081577.html

苹果内购代码

*步:创建一个Plist文件,保存自己的商品id(prodectId)

%title插图%num

第二步:获取商品ID,向苹果服务器请求自己的可买的商品(products),结果通过代理方法返回
NSString *path = [[NSBundlemainBundle] pathForResource:@”iap.plist”ofType:nil];
NSArray *products = [NSArrayarrayWithContentsOfFile:path];

//获取所有的productid
NSArray *productidA = [productsvalueForKeyPath:@”productid”];

//获取productid的set(集合)
NSSet *set = [NSSetsetWithArray:productidA];

//向苹果发送请求,请求可买商品
self.productsRequest = [[SKProductsRequestalloc]initWithProductIdentifiers:set];
self.productsRequest.delegate =self;
[self.productsRequeststart];

第三步:苹果服务器返回商品数据会调用代理方法,可以将商品进行排序

#pragma mark – SKProductsRequestDelegate
/**
* 当请求到可卖商品的结果会执行该方法
*
* @param response response中存储了可卖商品的结果
*/
// Sent immediately before -requestDidFinish:
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response {
self.products = response.products;
//将商品经行排序
self.products = [self.productssortedArrayWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(SKProduct *obj1,SKProduct *obj2) {
return [obj1.pricecompare:obj2.price];
}];
}

第四步:添加实时观察者,用户购买行为通过观察者模式进行回调,在viewWillAppear:方法里面添加观察者,后面在viewDidDisappear:方法里面移除观察者,防止其他页面使用内购,影响此页面的逻辑
– (void)viewWillAppear:(BOOL)animated {
[superviewWillAppear:animated];
// 添加观察者监听交易状态
[[SKPaymentQueuedefaultQueue] addTransactionObserver:self];
}

第五步:根据苹果服务器返回的商品,选择商品ID发送购买请求(注意:可能苹果测试服务器慢,主要做一个商品ID容错判断,防止崩溃),结果通过观察者模式回调回来,结果返回时,要将交易从队列中移除
/* 1.取出模型*/
SKProduct *product = [self.productslastObject];
if (product.productIdentifier) {
// 2.购买商品
[self buyProduct:product];
} else {
[MBProgressHUDshowError:@”系统繁忙,请您稍后再试!”];
}

/**
* 购买商品
*
* @param product 商品数据
*/
– (void)buyProduct:(SKProduct *)product {

[MBProgressHUD showMessage:@”购买中…” toView:self.view];
//创建票据
SKPayment *payment = [SKPayment paymentWithProduct:product];

//将票据加入到交易队列中
[[SKPaymentQueue defaultQueue] addPayment:payment];

}

/**
* 当交易队列中的交易状态发生改变的时候会执行该方法
*
* @param transactions 数组中存放了所有的交易
*/
– (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
/*
SKPaymentTransactionStatePurchasing, 正在购买
SKPaymentTransactionStatePurchased, 购买完成(销毁交易)
SKPaymentTransactionStateFailed, 购买失败(销毁交易)
SKPaymentTransactionStateRestored, 恢复购买(销毁交易)
SKPaymentTransactionStateDeferred *终状态未确定
*/
for (SKPaymentTransaction *transactionin transactions) {
switch (transaction.transactionState) {
caseSKPaymentTransactionStatePurchasing:
NSLog(@”用户正在购买”);
break;

caseSKPaymentTransactionStatePurchased:
NSLog(@”购买成功”);
[queue finishTransaction:transaction];
break;

caseSKPaymentTransactionStateFailed:
[queue finishTransaction:transaction];
break;

caseSKPaymentTransactionStateRestored:
NSLog(@”恢复购买”);
[queue finishTransaction:transaction];
break;

caseSKPaymentTransactionStateDeferred:
NSLog(@”*终状态未确定”);
break;

default:
break;
}
}
}

第六步:根据苹果服务器返回的购买结果,进行相应的处理,购买成功后可以讲票据发送给后台,后台进行二次验证,保存票据信息或者是本地进行验证

//1.服务器验证
/**
* 发送用户数据给服务器
*/
– (void)sendUserPayMessageToSever {

__weak __typeof(&*self)weakSelf = self;
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
NSURL *receiptURL = [[NSBundlemainBundle] appStoreReceiptURL];
// 从沙盒中获取到购买凭据
NSData *receiptData = [NSDatadataWithContentsOfURL:receiptURL];
/**
BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
BASE64是可以编码和解码的
*/
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

NSDictionary *parameters =@{@”receipt”:encodeStr};

[XYNetworkServermethodPostWithUrl:IPAUrlparameters:parameters SuccessBlock:^(id responseObject) {
NSLog(@”%@”,responseObject);
} andErrorBlock:^(id error) {
NSLog(@”%@”,error);
}];

}

//2.本地验证
– (void)verifyPruchase{
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
NSURL *receiptURL = [[NSBundlemainBundle] appStoreReceiptURL];
// 从沙盒中获取到购买凭据
NSData *receiptData = [NSDatadataWithContentsOfURL:receiptURL];
// 发送网络POST请求,对购买凭据进行验证
NSURL *url = [NSURLURLWithString:Url];
// 国内访问苹果服务器比较慢,timeoutInterval需要长一点
NSMutableURLRequest *request = [NSMutableURLRequestrequestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicytimeoutInterval:10.0f];

request.HTTPMethod =@”POST”;

// 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
// 传输的是BASE64编码的字符串
/**
BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
BASE64是可以编码和解码的
*/
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

NSString *payload = [NSStringstringWithFormat:@”{\”receipt-data\” : \”%@\”}”, encodeStr];
NSData *payloadData = [payloaddataUsingEncoding:NSUTF8StringEncoding];

request.HTTPBody = payloadData;

// 提交验证请求,并获得官方的验证JSON结果
NSData *result = [NSURLConnectionsendSynchronousRequest:requestreturningResponse:nilerror:nil];

// 官方验证结果为空
if (result ==nil) {
NSLog(@”验证失败”);
}

NSDictionary *dict = [NSJSONSerializationJSONObjectWithData:resultoptions:NSJSONReadingAllowFragmentserror:nil];

NSLog(@”%@”, dict);

if (dict !=nil) {
// 比对字典中以下信息基本上可以保证数据安全
// bundle_id&application_version&product_id&transaction_id
NSLog(@”验证成功”);
}
}

第七步:移除观察者(注意:因为苹果服务器比较慢,这里要将SKProductsRequest代理对象的代理置空,防止苹果服务器返回数据时,控制器销毁了,找不到代理对象,奔溃报野指针错误)

– (void)viewDidDisappear:(BOOL)animated {
[superviewDidDisappear:animated];

#warning 移除向苹果服务器请求(SKProductsRequest)的地理对象,否则可能会奔溃
if ([selfisMovingFromParentViewController]) {
if (self.productsRequest.delegate == self) {
self.productsRequest.delegate =nil;
}
}
// 移除观察交易状态的观察者
[[SKPaymentQueuedefaultQueue] removeTransactionObserver:self];
}

第八步:为了更加安全的运行程序,在dealloc移除SKProductsRequest代理对象的代理置空,防止苹果服务器返回数据时,控制器销毁了,找不到代理对象,奔溃报野指针错误

– (void)dealloc {
#warning 移除向苹果服务器请求(SKProductsRequest)的地理对象,否则可能会奔溃
if (self.productsRequest.delegate == self) {
self.productsRequest =nil;
}
[[NSNotificationCenterdefaultCenter] removeObserver:self];
NSLog(@”——- dealloc”);
}

 

苹果cms如何添加广告代码

今天对添加苹果cms3种常见的广告代码简单的了解下,如果你想添加广告又不懂代码,可以按照下面的案例公式直接仿照套用即可

1,纯链接文字广告代码:“#”号改成自己要指向的链接地址。文字改成你要显示的文字。

<a target=”_blank” href=”#”>文字</a>
1
效果图如下

%title插图%num

2,高亮按钮文字广告代码:“#”号改成自己要指向的链接地址。文字改成你要显示的文字。

<a class=”btn btn-primary” target=”_blank” href=”#”>文字</a>
1
效果图如下

%title插图%num

3,带链接的图片自适应广告代码:“#”号改成自己要指向的链接地址。图片地址就是填写这张图片的地址了

<a target=”_blank” href=”#”><img class=”img-responsive” src=”图片地址”/></a>
1
效果图如下

%title插图%num

4,苹果cms*新几款带后台模板提供了全站浮窗广告位及各页面常用的固定位置广告位,下图是全站通用浮窗广告位,也提供了3个可以自定义的广告位,可以直接把上面的代码复制到预留的广告位即可。

第二十一套苹果cmsv10模板

前台演示:http://demo.mytheme.cn/index.php?id=107 后台演示:http://107.demo.mytheme.cn/template/mytheme/admin/?do=home

第二十二套苹果cmsv10模板

前台演示:http://demo.mytheme.cn/index.php?id=149 后台演示:http://149.demo.mytheme.cn/template/mytheme/admin/?do=home

第二十四套苹果cmsv10模板

前台演示:http://demo.mytheme.cn/index.php?id=181 后台演示:http://181.demo.mytheme.cn/template/mytheme/admin/login.php

%title插图%num

固定广告位都在【页面配置】里面,根据需求自己每个页面设置即可

 

文件系统(百万文件)同步策略

公司文件系统有做简单备份的需求(主文件服务器上有文件新增,需要动态同步到备份服务器)。

之前采用的是自己写 java 程序扫库,将新增的文件 copy 到备份服务器,但有的文件在库里面是没有对应记录的,每增加一种该类型就需要修改程序,比较麻烦。

领导想采用现有的开源项目来实现文件的备份,于是乎研究了几款网上比较火的同步软件,基本都是监听+copy 两个操作。
inotify + rsync
sersync(基于 inotify+rsync)
lsyncd(lua 实现 inotify 监听的功能,再通过 cp 或 rsync 进行同步)
发现这些工具在需要监听的文件夹比较小,文件不多的时候可以正常运行。但部署到线上监听文件系统文件夹(12T 大小的文件,接近 200W 文件),有文件新增后,这些工具不能正常监听到,也就无法实现文件同步。

请教各位前辈,你们公司有采用过百万数量级的文件同步么,求指教。
第 1 条附言 · 2016-08-19 10:44:41 +08:00
不需要实时同步
文件 rsync inotify 备份6 条回复 • 2016-08-19 20:49:50 +08:00
Aliencn 1
Aliencn 2016-08-19 10:37:45 +08:00
我用 python 调度 rsync 同步,实现了数十万个文件,上 T 大小,上百个节点之间的非实时数据同步。
你这个文件级别,要做实时同步,做起来有点费脑。
不知道你业务场景,一个可能对你有用的方案就是把 12T 数据分散到不同机器上分别监控同步。
wendellup 2
wendellup 2016-08-19 10:44:29 +08:00
@Aliencn 不需要实时同步的
just4test 3
just4test 2016-08-19 11:25:41 +08:00
“之前采用的是自己写 java 程序扫库,将新增的文件 copy 到备份服务器,但有的文件在库里面是没有对应记录的,每增加一种该类型就需要修改程序,比较麻烦。 ”

不能走配置文件吗?
julor 4
julor 2016-08-19 11:42:37 +08:00 via Android
syncthing
jyf007 5
jyf007 2016-08-19 11:56:28 +08:00 via Android
我提一个, hadoop ,是这样不能同步吗?,那我没有办法了
zado 6
zado 2016-08-19 20:49:50 +08:00
看看能不能监听文件的产生者。

关于 Nginx 多站点配置 IPv6 访问的问题

/usr/local/nginx/conf/nginx.conf : ( Nginx Default 的配置文件)

server {
listen 80;
listen [::]:80 ipv6only=on;
server_name _;
}

include vhost/*.conf;
}
/usr/local/nginx/conf/vhost/xxx.conf:

server {

listen 443 ssl http2 fastopen=3 reuseport;
listen [::]:443 ssl http2 fastopen=3 reuseport ipv6only=on;

}

server {
listen 80;
listen [::]:80 ipv6only=on;

server_name xxxx.com;

location / {
rewrite ^/(.*)$ https://xxx.com/$1 permanent;
}
}
service nginx reload

提示错误: nginx: [emerg] duplicate listen options for [::]:80 in /usr/local/nginx/conf/vhost/xxx.conf:97

请问我的配置错在哪里呢?我想在 IPv6 访问的时候也能重定向到 https 页面。

Nginx listen conf usr12 条回复 • 2018-04-13 13:35:58 +08:00
lslqtz 1
lslqtz 2016-05-31 06:23:45 +08:00 via iPhone
我去 ttlsa 看了下,有–with-ipv6 吗? 我记得不加 ipv6only=on 是同时监听 ipv6 和 ipv4 更简洁一些。
gyzit 2
gyzit 2016-05-31 06:42:41 +08:00 via iPhone
@lslqtz 有的,其实我测试过主站通过 ipv6 访问是没问题的。但就是重定向那里不行。

还有一个原因是我用 nginx 内置了 Google Analytics ,用 ipv6only 是为了他在提交数据的时候准确。?
lslqtz2 3
lslqtz2 2016-05-31 06:47:02 +08:00
listen [::]:80 default ipv6only=on;
这是我在 ttlsa 看到的,搜了搜发现只有几个英文贴。。
gyzit 4
gyzit 2016-05-31 06:54:22 +08:00
@lslqtz2 依然会有同样的错误,我之前也查过很多资料的,包括 Google 。发现很少网站有提到,所以才上来问问大家。
lslqtz2 5
lslqtz2 2016-05-31 07:02:22 +08:00
@gyzit 我也去查查。
lslqtz2 6
lslqtz2 2016-05-31 07:03:12 +08:00
@gyzit
关键这句:[Because the first file has the ipv6only=on option, the second file does not need it.]

You can only specify options for the listen directive once per combination of host:port (see the documentation).

The error is being caused by this line in your second file:

listen [::]:80 ipv6only=on;
Because the first file has the ipv6only=on option, the second file does not need it.

Changing the beginning of the second file to the following should fix the problem:

server {
listen 80;
listen [::]:80;


}
gyzit 7
gyzit 2016-05-31 07:24:19 +08:00
@lslqtz2 谢谢!

我吧 `/usr/local/nginx/conf/vhost/xxx.conf` *底下重定向的 server 的 ipv6only=on 删了,好像就没问题了。

但我还有个小疑问,他说 Because the first file has the ipv6only=on option, the second file does not need it.

我 80 端口的 ipv6only=on 是在 nginx.conf ,那我下面 vhost 配置文件不加 ipv6only=on ,这个没疑问。

那我的 https 的 first file 是 xxx.conf ,那他怎么确定这份就是*份呢?当我下次添加虚拟机的时候会出错吗?
lslqtz2 8
lslqtz2 2016-05-31 07:32:02 +08:00
@gyzit ipv6 没怎么碰过,不清楚。。反正我 ipv4 都是一个 server 块用一个 listen 80;的,这个估计得下次试试看。
gyzit 9
gyzit 2016-05-31 07:55:39 +08:00
@lslqtz2 对呀,就是 ipv4 可以一个 server 块 listen 80 不影响其他。但是 ipv6 就有点不一样。。。
Showfom 10
Showfom 2016-05-31 07:58:13 +08:00 via iPhone ❤️ 1
ipv6only 只能出现一次的啦
gyzit 11
gyzit 2016-05-31 08:27:18 +08:00 via iPhone
@Showfom 好的我懂了,谢谢。?
Joan114 12
Joan114 2018-04-13 13:35:58 +08:00
你好,请问你的 ipv6 多站点配置成功了吗?
我现在在配置多站点的时候遇到一个问题,都配置好测试的时候,发现提示这个
This domain has no IPv6 DNS server, this may prevent some IPv6-only users from reaching it.

请问这是什么原因导致的呢

请教 n 个 IBM 小型机问题

公司*近收了一台银行退役的 IBM 小型机,型号目测是 IBM Power 740 。 目前我所了解的:

机器架构是 PowerPC
没有显示器接口,估计要另外接一个管理设备
系统只能使用 PowerPC 架构的
疑问:

不知 Sybase 数据库是否可以在上面运行
有 x86 的程序源代码,不知可否在小型机上安装 Power PC 版的 gcc ,然后编译运行
在 IBM 官网上如何获取详细的使用说明书及技术支持等资料
可以安装何种类型的虚拟机 vmware? kvm? xen? 还是 IBM 专用的虚拟机?
是否还需要购买相关授权软件?
正面

背面

IBM 小型机 虚拟机 powerpc12 条回复 • 2016-09-01 21:01:44 +08:00
majunbo 1
majunbo 2016-08-24 11:21:28 +08:00
安装 IBM AIX 或者 Linux ,
你想要的软件光盘里几乎都有。
ahcat 2
ahcat 2016-08-24 11:31:34 +08:00 via iPhone
powervm
yjxjn 3
yjxjn 2016-08-24 11:50:47 +08:00
曾经做过 IBM 小机开发的来回答一下,接触的面比较小,但是有些东西还是知道的。
不知 Sybase 数据库是否可以在上面运行
-貌似不行,我知道的基本都是 DB2 数据库。。
有 x86 的程序源代码,不知可否在小型机上安装 Power PC 版的 gcc ,然后编译运行
-这个没试过,但是我认为是可以的。
在 IBM 官网上如何获取详细的使用说明书及技术支持等资料
-IBM 官网有个叫 DEVELOPerWORKs 的网站,里面有 IBM 所有产品技术资料,或者是 IBM Knowledge Center 去找。
可以安装何种类型的虚拟机 vmware? kvm? xen? 还是 IBM 专用的虚拟机?
-没接触过。
是否还需要购买相关授权软件?
-需要。有些是试用版本,需要购买授权的。
ryan93 4
ryan93 2016-08-24 11:53:26 +08:00
@majunbo 送来的就只是一台机器,没有包装箱,也没有说明书和光盘之类的
shakoon 5
shakoon 2016-08-24 12:14:16 +08:00
1 、显示器连接需要用 hmc 设备,才能接出 vgi 接口接显示器
2 、可以装 sybase ,有 aix 版本的
3 、虚拟化方面, ibm 自己的 powervm 是基于 xen 的, kvm 、 vmware 均有支持 aix 的产品,可以虚拟 aix 和 linux
4 、技术资料在 ibm developerworks 下有,部分有中文。大部分软件是商业产品,虽然没有序列号、硬件绑定之说,但是理论上需要购买授权
5 、不能直接编译 x86 代码
majunbo 6
majunbo 2016-08-24 12:53:38 +08:00
@ryan93 看 3 楼 5 楼,已经解释很清楚了,
操作系统可以通过 1 、 HMC 2 、串口 3 、显示器 这三种方式安装,它有 PowerVM ,一台可以划分为两台安装系统。
自己上 IBM 网站看学习资料吧,你要学的东西不少。另外,有个高人叫赵小杰,你可以找找他的资料:
http://wenku.baidu.com/link?url=PhtNQHpfIsjT2w3wlHTRJr9fqjiQ00WxNmmoquoWq9dawJsysRxOZnnTDGRZK6Vo4B-xcUYHbWerwjpFbDstFu7KY5rYgHHNxi-VNKiMCV_
just4test 7
just4test 2016-08-24 13:49:24 +08:00
这破玩意有个啥用,公司收他干嘛
unkn369 8
unkn369 2016-08-24 13:51:19 +08:00
估计很耗电
ryan93 9
ryan93 2016-08-24 14:31:27 +08:00
@yjxjn @shakoon @majunbo 谢谢,收获许多知识
lcatt 10
lcatt 2016-08-24 14:45:42 +08:00
10 台小型机在机房停机 3 年+了。。。年底准备走报废流程扔掉。。。 (纯吐槽)
fzinfz 11
fzinfz 2016-08-24 15:55:26 +08:00
IBM 的机器先看 redbook
http://www.redbooks.ibm.com/redpapers/pdfs/redp4984.pdf
snoopygao 12
snoopygao 2016-09-01 21:01:44 +08:00
2011 年曾经花 2600 买过一台旧机器,为了学 AIX ,后来看到 X86 越来越强大,扔掉了,现在在做虚拟化和云计算