JavaScript 通过 API 访问区块链详解

构建区块链在上一章中,我们构建了区块链数据结构的开端。在本章中,我们将构建一个允许我们与区块链交互的 API。为了构建 API,我们将使用 Express.js 库创建一个服务器,然后我们将构建三个不同的端点,允许我们与区块链交互

让我们开始从头开始构建 API。在本章中,我们将介绍以下主题:

让我们开始构建 API 或服务器,以便与区块链数据结构交互。我们将在一个新文件中构建 API,并将其放入dev文件夹中。让我们创建一个新文件并将其命名为api.js;我们将在这里构建整个 API:

现在,我们将使用一个名为Express.js的库来构建服务器或 API。让我们按照下面提到的步骤来安装它:

  1. 因此,前往谷歌,搜索Express.js npm,然后点击第一个链接(https://www.npmjs.com/package/express 。这将带您进入下一页:

  1. 我们必须将其作为依赖项安装,因此必须在终端中运行以下命令:

现在,我们在项目中将 Express 库作为一个依赖项

使用 Express 非常简单:让我们看看如何使用它:

  1. 只需复制文档中的示例代码并将其粘贴到我们的api.js文件中:
var express = require('express')
var app = express()

app.get('/', function (req, res) {
 res.send('Hello World')
})

app.listen(3000)

如您所见,在我们的文件顶部,我们需要express,我们刚刚下载的库,然后我们创建一个app。这个app将帮助我们处理不同的端点或不同的路由。

例如,我们有一个get端点,它就是/。有了这个端点,我们将返回Hello World的响应。整个服务器正在监听端口3000

  1. 要启动此服务器,请转到终端并运行以下命令:
node dev/api.js
  1. 现在我们的服务器应该正在运行。我们可以通过点击浏览器中的get端点路由来测试这一点,该路由将只是端口3000上的本地主机。
  2. 在浏览器中打开一个新选项卡,然后输入localhost:3000。在这里,您将看到文本 Hello World:

  1. 这是从端点发送给我们的响应。我们可以将文本更改为我们想要的任何内容,因此让我们将Hello World更改为Hello Coding JavaScript!
var express = require('express')
var app = express()

app.get('/', function (req, res) {
 res.send('Hello Coding JavaScript!')
})

app.listen(3000)
  1. 现在保存该命令,并通过在终端中再次运行以下命令重新启动服务器:
node dev/api.js
  1. 刷新浏览器选项卡,您将看到以下输出:

好了!使用 Express 非常简单直接。我们将使用 Express.js 库构建所有端点。

在本节中,我们将继续构建区块链 API,然后我们将首先在 API 中构建以下三个端点:

  • 第一个端点是/blockchain,它允许我们获取整个区块链,以便我们可以查看其中的数据。
  • 第二个端点是/transaction,它允许我们创建一个新的事务。
  • 第三个端点是/mine,这将允许我们使用上一章中提出的proofOfWork方法挖掘一个新块。这将是一个非常强大的端点,构建起来会很有趣。

这基本上是我们的 BaseCu 链 API 的基础。在dev/networkNode.js文件中,我们定义这些端点如下:

const express = require('express');
const app = express();

app.get('/blockchain', function (req, res) {

});

app.post('/transaction', function(req, res) {

});

app.get('/mine', function(req, res) {

});

app,listen(3000);

现在,我们想做的另一件事是对listen方法进行一些修改:

app,listen(3000, function(){
    console.log('listening on port 3000...'); 

});

我们在这个方法中添加了另一个参数,它是一个函数。在这个函数中,我们只需要打印出Listening on port 3000字符串。我们这样做的原因是,当我们的端口实际运行时,我们将看到这个文本。让我们到终端再次运行我们的api.js文件:

如您所见,前面的屏幕截图显示我们正在收听端口3000。每当我们看到此文本时,我们就知道我们的服务器正在运行。

在本节中,我们将对我们的环境进行改进,使我们的开发过程更加简单。我们要做的第一件事是安装一个名为nodemon的新软件包。在我们终端的blockchain目录中,我们将写入npm i nodemon --save命令:

每当我们对其中一个文件进行更改并保存它时,此 nodemon 库将自动为我们重新启动服务器,这样我们就不必在每次更改时来回地从终端到代码重新启动服务器。

要使用 nodemon,我们将打开我们的package.json文件。在上面写着"scripts"的地方,我们将添加一个新脚本:

{
 "name": "javaScript-blockchain",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "start": "nodemon --watch dev -e js dev/api.js"
 }
 "author": "",
 "license": "ISC",
 "dependencies": {
     "express": "^4.16.3",
     "nodemon": "^1.17.3",
     "sha256": "^0.2.0"
 }
}

我们添加了"start": "nodemon --watch dev -e js dev/api.js"。这意味着当我们运行start命令时,我们希望nodemon监视我们的dev文件夹,并监视我们所有的 JavaScript 文件。每当这些 JS 文件中的一个被更改并保存时,我们希望 nodemon 为我们重新启动我们的dev/api.js文件。保存package.json文件。现在,每当我们在dev文件夹中进行更改并保存它时,我们的服务器就会自动重启。让我们来测试一下。

我们去候机楼吧。我们的服务器现在应该使用 nodemon:

我们已经使用npm start命令启动了服务器。您可以看到,这是在端口3000上侦听。每当我们对其中一个 JS 文件进行更改并保存它时,我们都会看到服务器自动重启:

正如您所看到的,服务器正在再次监听端口3000,这只是一个工具,我们使用它来简化开发。现在,我们想要使用的另一个工具是 Postman

Postman 工具允许我们调用任何 post 端点,并通过请求将数据发送到这些端点。让我们了解如何安装它:

  1. 前往https://www.getpostman.com 下载应用程序。一旦你下载了应用程序,我们就可以进行一个小测试,看看如何使用这个邮递员应用程序到达我们的/transaction端点。

  2. 下载后打开邮递员应用程序。您将看到类似于以下屏幕截图的内容:

  1. 现在,在邮递员应用程序中,我们将向http://localhost:3000/transaction发出发帖请求:

  1. 为了测试/transaction端点是否工作,让我们在输出中返回一些内容。在我们的/transaction端点中,我们添加了以下行:
app.post('/transaction', function(req, res) {
    res.send('It works!!!');
});
  1. 让我们保存文件,现在当我们到达这个端点时,我们应该将文本It works!!!返回给我们。单击 Send 按钮,您将获得输出,如以下屏幕截图所示:

  1. 现在,在我们的 API 中,大多数时候我们都会点击post端点,我们希望向其发送数据。例如,当我们点击/transaction端点时,我们希望创建一个新的事务。因此,我们必须将事务数据,如交易金额、发送方和接收方发送到/transaction端点。我们可以通过邮递员来完成这项工作,实际上非常简单。我们要做的是在我们的 post 请求中发送一些信息。您可以通过单击“正文”选项卡来执行此操作:

  1. 接下来,确保选中了 raw 选项,并从下拉列表中选择了 JSON(application/JSON)。您还可以看到,我们制作了一个 JSON 对象,并在其中放入了一些数据。我们添加了amount作为20比特币,地址为sender,地址为recipient

Remember that everything has to be in JSON format, so we need all of our quotations to be in double quotes or the terms won't work. 

  1. 为了测试我们是否在端点内接收到所有这些信息,我们将打印整个req.bodyreq.body只是我们在 JSON 对象中创建的信息:
app.post('/transaction', function(req, res) {
    console.log(req.body);
    res.send(`The amount of the transaction is ${req.body.amount}
     bitcoin.`);
});

如您所见,我们也在响应中发送了一些不同的信息。我们在 back ticks 中添加了一个句子,并且我们还使用了一些字符串插值${req.body.amount},这将只返回amount

  1. 现在,为了使${req.body.amount}能够工作,我们需要安装另一个库来访问这些信息。让我们回到终点站;我们将退出当前正在侦听端口3000的进程,并将安装一个名为body-parser的包:

  1. 现在让我们用npm start再次启动服务器。
  2. 当使用body-parser时,我们只想在我们导入app的行之后的文件顶部导入它:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

为了使用这个body-parser库,我们添加了下面两行。这两行代码都在说明,如果请求包含 JSON 数据或表单数据,我们只需要解析该数据,以便在任何端点中访问它。因此,对于我们命中的任何端点,我们的数据都将首先通过body-parser以便我们可以访问数据,然后在接收数据的任何端点中使用。

  1. 现在我们正在使用body-parser,我们应该能够访问金额。让我们保存api.js文件并尝试发送请求,如下所示:

成功了!我们得到返回的字符串,表示交易金额为 20 比特币。

在我们的终端中,由于我们注销了整个req.body,我们可以看到关于金额、发送者和接收者的全部信息显示:

伟大的现在,需要注意的一点是,在本章的其余部分中,您应该始终让服务器运行,这意味着您应该始终运行npm start命令,以便我们可以使用我们的 API,访问不同的端点,并测试它是否工作。

让我们继续构建我们的区块链 API。在本节中,我们将与/blockchain端点进行交互。这意味着我们必须从blockchain.js文件导入区块链:

const Blockchain = require('./blockchain');

我们现在已经导入了我们的区块链数据结构或区块链构造函数。接下来,我们想制作一个区块链实例。我们可以这样做:

const bitcoin = new Blockchain();

现在我们有一个区块链构造器函数的实例,我们称它为bitcoin。你可以随意调用它,但为了简单起见,我将把它称为bitcoin

让我们在/blockchain终点站的基础上再接再厉。该端点要做的就是将我们的整个区块链发送回调用该端点的人。为此,我们将添加一行以发送响应:

app.get('/blockchain', function(req, res) {
    res.send(bitcoin);
});

信不信由你,这就是我们要为这个终点所做的一切

现在,我们可以在浏览器中使用该端点来测试该端点是否工作:

  1. 让我们转到浏览器并点击localhost:3000/blockchain

  1. 正如你所看到的,我们找回了我们的整个区块链。现在,你可能已经注意到这有点难读,所以为了让它可读,让我们下载一个名为JSON 格式化程序的 Chrome 扩展。你可以用谷歌搜索它,并将扩展添加到你的 Chrome 浏览器中。安装后,再次刷新页面,您将获得以下输出:

如您所见,我们以更可读的格式返回数据,即 JSON 格式。你可以看到我们有chain,其中有一个项目——我们的创世纪区块——我们还有pendingTransaction区块。这很酷,我们可以看出我们的/blockchain端点正在工作,因为我们得到了整个区块链

在本节中,我们将构建事务端点。让我们按照下面提到的步骤进行操作:

  1. 在我们开始之前,请确保无论何时使用我们的区块链,您的服务器都在运行。我们可以通过在终端中运行npm start命令来实现这一点。
  2. 让我们转到api.js文件并构建事务端点。首先,取出我们前面在/transaction端点中添加的示例代码,在我们的区块链中创建一个新交易。为此,我们将使用我们在第 2 章中构建的blockchain.js文件中的createNewTransaction方法构建区块链
  3. 如您所知,我们的createNewTransaction方法包含三个参数:amountsenderrecipient
Blockchain.prototype.createNewTransaction = function(amount, sender, recipient) {
  const newTransaction = {
    amount: amount,
    sender: sender,
    recipient: recipient
  };

  this.pendingTransactions.push(newTransaction);

  return this.getLastBlock()['index'] + 1;
};
  1. 此方法返回新事务将添加到的块编号或索引。这就是创建事务所需的全部内容,因此在/transaction端点中,我们将添加以下行:
app.post('/transaction', function(req, res) {
  const blockIndex = bitcoin.createNewTransaction(req.body.amount,
   req.body.sender, req.body.recipient) 
});
  1. 在我们的端点中,我们将假设所有这些数据都是由调用此端点的人通过req.body发送进来的。结果将保存在blockIndex中,这就是我们将发送回调用此端点的人的内容。我们将以note的形式发回:
app.post('/transaction', function(req, res) {
  const blockIndex = bitcoin.createNewTransaction(req.body.amount,
  req.body.sender, req.body.recipient) 
 res.json({ note:`Transaction will be added in block
    ${blockIndex}.`});
});

如您所见,注释将告诉我们该事务将添加到哪个块。我们使用了字符串插值来传递blockIndex值。让我们保存此文件并使用 Postman 测试此端点。

现在,让我们转到 Postman 并应用类似于我们前面设置的设置:

我们已经选择了 POST 请求,我们的目标是/transaction端点。在 Body 选项卡中,我们选中了 raw,文本被选择为 JSON 格式。我们已经在 JSON 对象中传递了amountsenderrecipient的值,这将是我们的req.body,我们将在这个对象上发送所有的事务数据。在/transaction端点中提到的req.body帮助下,我们可以访问金额、发件人地址和收件人。

现在让我们测试这个端点:

如您所见,当我们单击 Postman 上的 Send 按钮时,我们得到了输出,因为事务将添加到块 2 中。我们在这里得到区块 2 的原因是,当我们启动区块链时,已经创建了一个区块,这创建了 genesis 区块。因此,该事务被添加到块 2。

我们可以测试的另一种方法是点击我们的/blockchain端点,以确保该端点正常工作。当我们点击该端点时,我们应该期望将整个区块链返回给我们。在该区块链中,应该有一个区块——我们的 genesis 区块——并且应该有一个未决交易,即我们刚刚创建的交易。到浏览器localhost:3000/blockchain让我们看一下:

如你所见,整个对象就是我们的整个区块链——第一部分是我们拥有 genesis 区块的链,第二部分是我们刚刚创建的未决交易。我们的/transaction端点工作正常。

让我们为区块链 API 构建最终端点:mine 端点,这将挖掘并创建一个新块:

  1. 要创建一个新块,我们将使用我们在blockchain.js文件中已经定义的createNewBlock方法。让我们转到api.js文件,在/mine端点内创建一个新块:
app.get('/mine', function(req, res) {
    const newBlock = bitcoin.createNewBlock();
});
  1. createNewBlock方法包含三个参数:noncepreviousBlockHashhash
Blockchain.prototype.createNewBlock = function(nonce, previousBlockHash, hash) {
  const newBlock = {
    index: this.chain.length + 1,
    timestamp: Date.now(),
    transactions: this.pendingTransactions,
    nonce: nonce,
    hash: hash,
    previousBlockHash: previousBlockHash
  };

  this.pendingTransactions = [];
  this.chain.push(newBlock);

  return newBlock;
};
  1. 现在我们必须进行计算,以获得所有这三个数据,所以让我们这样做。让我们从获取上一个块开始,以便获取其哈希:
app.get('/mine', function(req, res) {
  const lastBlock = bitcoin.getLastBlock();
  const previousBlockHash = lastBlock['hash'];

如您所见,我们已经创建了lastBlock,这是我们链中的最后一个区块,或者是我们新区块的前一个区块。为了得到最后一个区块的hash,我们创建了previousBlockHash。有了这些,我们现在可以得到我们的previousBlockHash,这是我们接下来的createNewBlock方法所需要的参数之一。

  1. 接下来,让我们来看看我们的nonce。要为我们的块生成一个nonce,我们需要生成一个proofOfWork,它是我们在blockchain.js文件中创建的:
Blockchain.prototype.proofOfWork = function(previousBlockHash, currentBlockData) {
  let nonce = 0;
  let hash = this.hashBlock(previousBlockHash, currentBlockData,
  nonce);
  while (hash.substring(0, 4) !== '0000') {
    nonce++;
    hash = this.hashBlock(previousBlockHash, currentBlockData,
    nonce);
  }

  return nonce;
};
  1. 在我们的/mine端点中,我们将添加以下行:
const nonce = bitcoin.proofOfWork(previousBlockHash, currentBlockData);
  1. 所以,通过我们的proofOfWork方法,我们将得到一个nonce返回给我们。让我们将其保存为我们的nonce变量。我们的proofOfWork方法包含两个参数:previousBlockHash(我们已经有了)和currentBlockData。让我们来定义我们的currentBlockData
const currentBlockData = {
    transactions: bitcoin.pendingTransactions,
    index: lastBlock['index'] + 1
  };

我们把我们的currentBlockData作为一个对象,它由数据组成。这个数据将只包括这个块中的transactions和一个index,它是我们将要创建的新块的索引;我们的lastBlock加 1 的索引。currentBlockData对象就是这个新区块中的transactions和它的index,通过这个,我们现在可以计算我们的nonce,就像我们的previousBlockHashcurrentBlockData一样。

  1. 现在,我们的createNewBlock方法必须接受的最后一个参数是这个新块的hash,现在让我们计算一下。为了创建这个新块的散列,我们将使用我们的hashBlock方法。我们将在/mine端点中添加以下行:
const blockHash = bitcoin.hashBlock(previousBlockHash, currentBlockData, nonce);

如您所知,我们已经在blockchain.js文件中创建了hashBlock方法。此方法接受三个参数:previousBlockHashcurrentBlockDatanonce。我们已经有了所有这些参数,所以我们调用它并将结果保存在一个名为blockHash的变量中

  1. 我们现在有了运行createNewBlock方法所需的所有参数,所以让我们分配这些参数:
const newBlock = bitcoin.createNewBlock(nonce, previousBlockHash, blockHash);

这里发生的事情真是太棒了。如你所见,创建这个新区块需要进行许多不同的计算,我们可以通过使用区块链数据结构进行所有这些计算。这是一个非常强大的数据结构,我们的区块链现在可以通过使用proofOfWork挖掘新的区块,这与许多其他区块链的功能类似。

  1. 在这一点上,我们已经创建了我们的新区块,我们真正要做的就是将响应发送回开采该区块的人。接下来,我们将在/mine端点中添加以下行:
res.json({
  note: "New block mined successfully",
  block: newBlock
});

我们只是寄回一张便条,上面写着新区块开采成功,同时也写着我们刚刚创建的newBlock。现在,发回这个newBlock不会以任何方式影响我们的区块链。我们正在发回newBlock以便创建或开采这个新区块的人知道它是什么样子。

  1. 现在还有一件事我们必须做:每次有人挖掘一个新区块,他们就会因为创建新区块而获得奖励。我们所要做的就是进行交易,并向挖掘这个新区块的人发送一点比特币作为奖励。为此,在/mine端点内,我们将创建一个新交易:
bitcoin.createNewTransaction(12.5, "00", nodeAddress);

目前,2018 年,在真正的比特币区块链中挖掘新区块将获得 12.5 比特币奖励。为了与真正的比特币保持一致,我们也将获得我们的奖励12.5比特币。作为发送方地址,我们已经输入了值00,。这样,每当我们查看网络上的交易时,我们都知道如果交易是从地址00进行的,这是一种挖掘奖励。

现在我们只需要一个收件人的地址,nodeAddress。我们需要向挖掘新区块的人发送12.5比特币——但我们如何才能找到它?好吧,我们将向我们当前所在的节点发送此奖励,这是我们正在处理的整个 API 文件。我们可以将整个 API 视为比特币区块链中的网络节点

在未来的章节中,我们将有多个 API 实例,它们将作为大清洁区块链中的不同网络节点。现在,每当我们访问我们创建的任何端点时,我们总是只与这个网络节点通信。然而,由于我们知道所有区块链技术都是分布式,并且托管在许多不同的网络节点上,因此我们将继续创建更多的网络节点。但目前,我们的整个区块链仅托管在这一个网络节点上。

现在,每当我们到达/mine端点时,我们都希望奖励这个节点挖掘新块。为了给这个节点它应得的12.5比特币奖励,我们需要一个地址来发送比特币,现在让我们为这个节点创建一个地址。

要创建此节点的地址,我们将使用终端导入名为uuid的新库:

输入npm i uuid --save命令并点击回车后,将添加包。您可以使用npm start 命令再次启动服务器。

现在,让我们在api.js文件的顶部导入新的uuid库:

const uuid = require('uuid/v1');

如您所见,我们已经导入了uuid库的版本 1。这个库为我们创建了一个唯一的随机字符串,我们将使用这个字符串作为这个网络节点的地址。为此,我们将添加以下行:

const nodeAddress = uuid().split('-').join('');

我们想对从这个库中获得的字符串进行修改的一件事是,存在两个破折号——我们不希望地址中有任何破折号。在这里,我们只需在所有破折号上拆分该字符串,然后用传入的空字符串重新连接它。我们将得到的nodeAddress是一个随机字符串,它保证在很大程度上是唯一的。我们真的希望这个字符串是唯一的,因为我们不希望有两个具有相同地址的节点,否则我们将最终向错误的人发送比特币,这将是不好的。现在我们可以简单地传递这个nodeAddress变量输入到我们的createNewTransaction方法中。

在下一节中,我们将测试我们的/mine端点,以及/transaction/blockchain端点,以确保它们都正常工作和交互。

在本节中,我们将测试我们的/mine端点,以及/transaction/blockchain端点,以确保一切都能很好地协同工作

在我们测试之前,最好去掉proofOfWork方法中的console.log语句。这是因为拥有它只会让你的程序更难工作,因此需要更多的时间来计算。

首先,让我们测试上一节中刚刚构建的/mine端点。让我们转到浏览器,点击localhost:3000/blockchain

现在,我们拥有整个区块链,其中链中有一个区块——我们的 genesis 区块——我们也没有未决交易。

现在,让我们打开另一个选项卡并点击/mine端点。这将为我们挖掘并创建一个新区块:

我们注意到新块挖掘成功。我们还得到了新块,我们可以看到块上的所有数据。它有一个散列,还有前一个块的散列,即 genesis 块,以及其中的一个事务。你可能会想,我们没有创建一个交易,那么这个交易是从哪里来的?这个交易实际上是我们放入我们的端点的挖掘奖励,它有12.5比特币挖掘奖励交易。看起来我们的挖掘端点工作得很好。

为了测试并确保我们确实创建了这个新块,我们可以返回到/blockchain端点并刷新页面:

我们现在有两个区块链:一个是创世纪区块,另一个是我们刚刚创建的区块。第二个区块中也有交易,它有奖励。

让我们再挖一块来测试一下。前往我们的/mine端点并刷新它:

我们刚刚挖掘了另一个区块,这是我们的第三个区块。我们可以看到,我们得到了timestamp和另一个交易,这是挖掘奖励,我们还有剩余的数据。现在让我们回到/blockchain端点并刷新它:

如你所见,我们有三个街区。区块 3 是我们刚刚创建的,其中有我们的采矿奖励交易。另外需要注意的是,我们的previousBlockHash实际上与区块 2 的hash一致。这有助于保护我们的区块链,这很好。

现在,让我们使用/transaction端点创建一些事务。为此,请转到 Postman,确保设置与之前相同,并进行以下更改:

我们已将amount设置为1000比特币。我们将保留发件人和收件人地址,但您可以将其更改为任何您想要的地址。一旦我们发布到/transaction端点,作为回报,我们应该得到文本事务,它将被添加到块 4 中,我们做到了。这个事务被添加到块 4 中,因为我们的链中已经有三个块。

让我们做另一个示例事务。在这里,我们将把amount改为50比特币,并对发送方和接收方的地址进行一些更改。因此,当我们发送此请求时,我们应该得到相同的响应:将在块 4 中添加交易。这是因为我们尚未挖掘新块。让我们尝试一下:

成功了。现在,让我们回头再看看我们的整个区块链。这一次,我们应该期望得到相同的区块链和我们刚刚创建的两个未决交易。让我们刷新页面并查看输出:

您会注意到这有三个块和两个挂起的事务。现在,如果我们转到/mine端点并刷新页面,这两个挂起的事务将添加到块 4:

我们成功地挖掘了一个新区块。它有我们的数据,还有三个事务。前两个事务是我们在 Postman 中创建的,第三个是我们的挖掘奖励事务。现在,如果我们返回/blockchain端点并刷新它,我们将看到两个挂起的事务消失了,并且它们已添加到块 4:

如您所见,block 4 包含所有三个事务,我们的pendingTransactions现在为空。结果很好。现在,我鼓励您创建更多的事务并挖掘另一个块,以确保一切正常工作。

通过构建整个 API 和区块链并真正理解代码的工作原理,理解区块链技术的实际工作原理就变得容易多了,而且您还意识到其中很多其实并不那么复杂。

在测试这些端点的任何时候,如果对文件进行更改并保存,则服务器将重新启动。这将产生一个新的区块链实例,这意味着到目前为止您创建的所有内容都将被清除。

在本章中,我们学习了如何在项目中设置 Express.js,以及如何使用它构建 API/服务器。然后我们安装了 Postman,并了解了如何使用它来测试端点。在此之后,我们继续构建服务器的各种端点,并测试这些端点,以验证它们是否正常工作

在下一章中,我们将创建一个节点网络或分布式网络来承载我们的区块链,就像在现实世界中承载的一样。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

趣谈Linux操作系统 -〔刘超〕

深入浅出计算机组成原理 -〔徐文浩〕

ZooKeeper实战与源码剖析 -〔么敬国〕

大数据经典论文解读 -〔徐文浩〕

反爬虫兵法演绎20讲 -〔DS Hunter〕

林外 · 专利写作第一课 -〔林外〕

AI绘画核心技术与实战 -〔南柯〕

结构写作力 -〔李忠秋〕

云时代的JVM原理与实战 -〔康杨〕