如何安装和使用ArangoDB在Ubuntu 14.04

介绍

ArangoDB是一个NoSQL数据库。它是在2011年创建的,当时许多NoSQL数据库已经存在,目标是成为一个全面的数据库解决方案,可以覆盖各种用例。 其核心ArangoDB是一个 文档存储但这仅仅是个开始。 你可以用一个完整的查询语言(称为AQL)查询数据,使 符合ACID事务,在JavaScript应用程序与表单中添加自定义HTTP端点 嵌入式V8 ,等等。 因为ArangoDB有很多功能,它可以是吓人的,但第二次看起来不复杂。本文将帮助您安装ArangoDB,并将简要介绍其一些核心功能如何使用。 完成本教程后,您应该能够:
  • 在Ubuntu 14.04上安装ArangoDB
  • 配置ArangoDB的基本用法
  • 插入,修改和查询数据

核心概念

在整篇文章中,我们将使用一些核心概念。在ArangoDB上构建项目之前,您可能需要熟悉它们:
  • 文件存储 :在文档ArangoDB存储数据,而相比之下,如何关系数据库存储的数据。 文件是由键值对任意的数据结构。 关键是一个字符串,它的名字的 (如在关系数据库中的列)。 的可以是任何数据类型,甚至另一个文档。文档不绑定到任何模式。
  • 查询语言 :使用一个API或者发布的查询语言的数据交互。虽然前者给API用户留下了很多细节,但是查询语言将细节交给数据库。在关系数据库中,SQL是查询语言的示例。
  • ACID:四个属性 tomicity,C onsistency, 染料溶液和D urability描述了数据库事务的保证。 ArangoDB支持符合ACID的事务。
  • V8:谷歌的JavaScript引擎,Chrome的权力,可以方便地嵌入其他软件了。在ArangoDB中使用它可以在数据库中使用JavaScript。 ArangoDB的大部分内部功能都是用JavaScript构建的。
  • HTTP API:ArangoDB提供了一个HTTP API来允许客户端与数据库进行交互。 该API是面向资源的 ,可以用JavaScript进行扩展。

先决条件

在开始之前,请确保正确设置Droplet: 现在您应该使用新创建的用户登录到您的服务器。教程中的所有示例都可以从用户的主目录执行:
cd ~

第1步 - 安装ArangoDB

ArangoDB是为许多操作系统和发行版预先构建的。机会是高的,你不需要从源代码构建它。详情请参考ArangoDB 文档 。对于本教程,我们将使用Ubuntu 14.04 x64。 由于ArangoDB使用OpenSUSE系统的 建设服务 ,第一件事就是下载它的存储库公共密钥:
wget https://www.arangodb.com/repositories/arangodb2/xUbuntu_14.04/Release.key
你需要 sudo安装密钥:
sudo apt-key add Release.key
接下来添加apt存储库并更新索引:
sudo apt-add-repository 'deb https://www.arangodb.com/repositories/arangodb2/xUbuntu_14.04/ /'
sudo apt-get update
安装ArangoDB:
sudo apt-get install arangodb
我们可以通过查询HTTP API来检查一切是否正常:
curl http://localhost:8529/_api/version
以下输出表明ArangoDB已启动并正在运行:
Output{"server":"arango","version":"2.5.5"}

第2步 - 使用arangosh访问命令行

ArangoDB附带 arangosh ,一个命令行客户端,让你通过它的JavaScript运行完全访问数据库。您可以使用它来运行生产中的管理任务或脚本。 它也非常适合开始使用ArangoDB及其核心功能。要跟着,启动 arangosh这样的会议:
arangosh
结果基本上是一个JavaScript shell,你可以运行任意的JavaScript代码。例如,添加两个数字:
23 + 19
你会得到这个结果:
Output42
如果你想这个话题,键入要深入了解 tutorial入壳。

第3步 - 添加数据库用户

出于安全原因,只可能从添加用户 arangosh命令行界面。 您应该仍然在 arangosh从上一步外壳。 现在让我们添加一个新用户, sammy 。 此用户将可以访问整个数据库。 现在可以了,但是您可能想在生产环境中创建更多受限用户。 使用安全 password
require("org/arangodb/users").save("sammy", "password");
现在退出 arangosh外壳:
exit

第4步 - 配置Web界面

ArangoDB附带了一个非常强大的Web界面。它提供监控功能,数据浏览,交互的API文档,强大的查询编辑器,甚至集成 arangosh 。我们将重点关注本教程提醒的Web界面的使用。 为了使Web界面容易访问,我们需要进行一些准备工作:
  1. 启用身份验证
  2. 将ArangoDB绑定到公网接口

启用身份验证

ArangoDB像许多其他NoSQL数据库一样,禁用身份验证。 强烈建议 ,如果你在一个共享的环境中运行ArangoDB和/或要使用Web界面 启用身份验证 。 有关此主题的更多详细信息,请参阅 ArangoDB文档 。 激活在认证 /etc/arangodb/arangod.conf文件。 您可以运行此命令来创建一个备份文件,并设置 disable-authentication参数 no
sudo sed -i.bak 's/disable-authentication = yes/disable-authentication = no/g' /etc/arangodb/arangod.conf
或者,使用文本编辑器的设置 disable-authentication参数 no 。 重新启动数据库:
sudo service arangodb restart

将ArangoDB绑定到公共网络接口

配置ArangoDB在公共网络接口上监听。首先,打开 /etc/arangodb/arangod.conf文件进行编辑:
sudo nano /etc/arangodb/arangod.conf
定位活动 endpoint线,这应在的端 [server]下面的例子的部分块。 更新设置,如下图所示,使用自己的服务器的IP地址和端口 8529
/etc/arangodb/arangod.conf

. . .

endpoint = tcp://your_server_ip:8529
由于 arangosh使用它的默认配置,我们需要改变的端点 /etc/arangodb/arangosh.conf文件太:
sudo nano /etc/arangodb/arangosh.conf
再次,确保 endpoint线路设置为 tcp:// your_server_ip :8529
/etc/arangodb/arangosh.conf
pretty-print = true

[server]
endpoint = tcp://your_server_ip:8529
disable-authentication = true

. . .

如果您希望运行两个多部分单行命令来更新这两个文件,您可以运行以下命令:
sudo sed -i.bak "s/^endpoint = .*/endpoint = tcp:\/\/$(sudo ifconfig eth0 | grep "inet " | cut -d: -f 2 | awk '{print $1}'):8529/g" /etc/arangodb/arangod.conf
sudo sed -i.bak "s/^endpoint = .*/endpoint = tcp:\/\/$(sudo ifconfig eth0 | grep "inet " | cut -d: -f 2 | awk '{print $1}'):8529/g" /etc/arangodb/arangosh.conf
这些神秘的期待命令将提取当前的公网IP地址,并替换默认绑定地址( 127.0.0.1 )。 别担心, -i.bak选项创建更改配置之前更新。 现在重新启动ArangoDB:
sudo service arangodb restart

第5步 - 访问ArangoDB Web界面

现在,您应该可以在浏览器中访问Web界面:
http://your_server_ip:8529
请使用您在第3步中为数据库创建的用户名和密码登录。 警告:虽然我们设置身份验证,运输还没有保证。 在生产中,如果您使ArangoDB可从另一个主机访问,则应设置TLS加密。 您应该看到的第一个屏幕是包含数据库服务器的基本指标的仪表板: ArangoDB Web界面仪表板 在顶部导航的中心,你会看到 DB:_system。 这表示当前选择的数据库。 默认值是 _system数据库。 某些管理任务只能在执行 _system数据库。 对于以下部分,我们将创建一个要使用的数据库。将鼠标悬停在 DB:_system菜单项,然后单击 管理DB的链接。 在接下来的页面点击 添加数据库按钮。 填写表单创建一个名为数据库 music_library 。您必须在此对话框中输入与以前相同的用户名和密码,否则您将无法在以后访问新数据库: 在Web界面中创建一个新的数据库 我们现在设置开始实际做ArangoDB的东西。

第6步 - 使用arangosh执行CRUD操作

我们将离开Web界面现在返回到 arangosh命令行界面,以弥补ArangoDB基本的CRUD操作。稍后我们将在Web界面中再次覆盖相同的操作,但在shell中做这些操作有助于我们更好地了解事情的工作原理。 要继续,请返回到服务器的命令行。连接到新的 music_library使用您的用户和密码数据库:
arangosh --server.database music_library --server.username sammy --server.password password

创建文档集合

如果您来自关系数据库背景,则Collection是ArangoDB等价于SQL数据库中的表。我们将创建一个集合来存储我们的音乐库中的歌曲:
db._createDocumentCollection('songs')
ArangoDB提供了一系列的 管理集合的方法 。 大多数人在这一点上不感兴趣,但请随着你进一步到ArangoDB,看看他们。 现在,我们将重点放在 CRUD操作(创建,读取,更新和删除) -也就是说,如何让进出数据库的实际数据。

创建文档

在这里你会在基于SQL的数据库行,ArangoDB有 文件 。 ArangoDB中的文档是JSON对象。 每个文档与集合相关联,并有三个核心属性: _id_rev_key 。 文档被唯一地识别其数据库里面 的文件句柄它由集合名称和的 _key ,由分离 / 。 文档手柄存储在 _id一个文件,场。 两者 _key_id类似于在一个关系数据库中的主键。 注意:如果你没有自己的东西指定,ArangoDB将创建一个_key为每个文档。 你可以指定一个自定义_key如果你愿意,但你必须确保它是独一无二的。 在本教程中,我们将设置_key明确,使其更容易复制和粘贴的例子。 让我们的第一个文档添加到 songs集合:
db.songs.save(
{ title: "Immigrant Song", album: "Led Zeppelin III", artist: "Led Zeppelin", year: 1970, length: 143, _key: "immigrant_song" }
)
Output{ 
  "error" : false, 
  "_id" : "songs/immigrant_song", 
  "_rev" : "11295857653", 
  "_key" : "immigrant_song" 
}
db对象包含所有集合作为属性。 每个集合提供与该集合中的文档交互的功能。 在 save函数接受任何JSON对象并将其存储为集合中的文件,返回上述核心属性,以及是否发生了错误。每个操作的返回都是一个JSON对象。 要有东西玩,我们需要一些更多的文件。只需复制并粘贴下一个代码段,即可向数据库添加更多条目:
db.songs.save(
{album: "Led Zeppelin III", title: "Friends", artist: "Led Zeppelin", year: 1970, length: 235, _key: "friends"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Celebration Day", artist: "Led Zeppelin", year: 1970, length: 209, _key: "celebration_day"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Since I've Been Loving You", artist: "Led Zeppelin", year: 1970, length: 445, _key: "since_i_ve_been_loving_you"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Out On the Tiles", artist: "Led Zeppelin", year: 1970, length: 244, _key: "out_on_the_tiles"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Gallows Pole", artist: "Led Zeppelin", year: 1970, length: 298, _key: "gallows_pole"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Tangerine", artist: "Led Zeppelin", year: 1970, length: 192, _key: "tangerine"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "That's the Way", artist: "Led Zeppelin", year: 1970, length: 338, _key: "that_s_the_way"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Bron-Y-Aur Stomp", artist: "Led Zeppelin", year: 1970, length: 260, _key: "bron_y_aur_stomp"}
);

db.songs.save(
{album: "Led Zeppelin III", title: "Hats Off to (Roy) Harper", artist: "Led Zeppelin", year: 1970, length: 221, _key: "hats_off_to_roy_harper"}
);

阅读文档

要检索文档,你可以使用文档手柄或 _key 。 只有在不经过集合本身的情况下,才需要使用文档句柄。 有一个集合,你可以使用 document的功能:
db.songs.document('immigrant_song');
Output{ 
  "year" : 1970, 
  "length" : 143, 
  "title" : "Immigrant Song", 
  "album" : "Led Zeppelin III", 
  "artist" : "Led Zeppelin", 
  "_id" : "songs/immigrant_song", 
  "_rev" : "11295857653", 
  "_key" : "immigrant_song" 
}
现在我们可以创建和阅读文档,我们将研究如何更改它们:

更新文档

当涉及到更新你的数据,你有两个选择: replaceupdate 。 该 replace功能将用新的替换整个文档,即使你完全提供不同的属性。 的 update功能,另一方面,将刚由具有给定属性合并它补丁的文件。 让我们尝试一个破坏性较小 update首先,我们更新 genre我们的歌曲之一:
db.songs.update("songs/immigrant_song",

{ genre: "Hard Rock" }

);
让我们来看看更新的歌曲条目:
db.songs.document("songs/immigrant_song");
Output{ 
  "year" : 1970, 
  "length" : 143, 
  "title" : "Immigrant Song", 
  "album" : "Led Zeppelin III", 
  "artist" : "Led Zeppelin", 
  "genre" : "Hard Rock", 
  "_id" : "songs/immigrant_song", 
  "_rev" : "11421424629", 
  "_key" : "immigrant_song" 
}
update的时候,你有一个大的文件,只需要更新其属性的一小部分功能特别有用。 相比之下,使用相同的JSON的 replace函数会破坏你的数据。
db.songs.replace("songs/immigrant_song",

{ genre: "Hard Rock" }

);
立即查看更新的歌曲:
db.songs.document("songs/immigrant_song")
如您所见,原始数据已从文档中删除:
Output{ 
  "genre" : "Hard Rock", 
  "_id" : "songs/immigrant_song", 
  "_rev" : "11495939061", 
  "_key" : "immigrant_song" 
}

删除文档

从集合中删除一个文件,调用 remove的文件处理功能:
db.songs.remove("songs/immigrant_song")
虽然 arangosh外壳是一个很好的工具,这对探索ArangoDB的其他功能繁琐。接下来,我们将研究内置的Web界面以深入了解其功能。

第7步 - 使用Web界面执行CRUD操作

我们已经看到了如何处理的文件 arangosh ,现在我们回到Web界面。 访问 http:// your_server_ip :8529/_db/music_library在浏览器中。

创建文档集合

点击顶部导航栏中的 收藏标签。 你可以看到现有的 songs ,我们在命令行中添加的收集;随时点击它并查看条目,如果你喜欢。 从主 集合页,点击 Add按钮 集合在Web界面中添加集合 既然我们已经拥有 songs ,我们将增加一个 albums收藏。 输入 albums作为在弹出的 新建对话框 集合 名称 。 默认类型, 文档 ,很好。 点击 保存 ,你会看到页面上现在两个集合。 点击 albums集合。您将看到一个空集合: 集合视图

创建文档

点击右上角的 +号以添加文件。 你会先得到要了 _key 。 输入 led_zeppelin_III的关键。 接下来有一个表单,您可以在其中编辑文档的内容。有添加属性称为 的图形化的方式,但现在,从 树的下拉菜单中选择切换到 代码视图: 创建文档 请将以下JSON复制并粘贴到编辑器区域(确保只使用一组花括号):
{
"name": "Led Zeppelin III",
"release_date": "1970-10-05",
"producer": "Jimmy Page",
"label": "Atlantic",
"length": 2584
}
请注意,需要在此模式下引用键。大功告成后,点击 保存按钮。页面应呈绿色闪烁片刻,表示成功保存。

阅读文档

您需要保存新文件后手动导航回到 集合页。 如果你点击 albums集,你会看到新的条目。

更新文档

要编辑文档的内容,只需在文档概览中单击要编辑的行即可。您将看到与创建新文档时相同的编辑器。

删除文档

删除文档,用户只需按简单 -在每个文档行的末尾图标。系统提示时确认删除。 此外,对于特定集合的 集合概述页面,您可以导出和导入数据,管理指标,并过滤的文件。 如前所述,Web界面有很多提供。涵盖每个功能都超出了本教程的范围,因此欢迎您自己探索其他功能。我们将在本教程中只讨论一个更多的功能:AQL编辑器。

第8步 - 使用AQL查询数据

如前面提到的,ArangoDB提供了一种称为AQL的完整查询语言。 与AQL在Web界面交互,点击顶部导航的 AQL编辑器选项卡上。您将看到一个空白编辑器。 到编辑器,结果视图之间切换,使用右上角的 查询结果选项卡: AQL编辑器 编辑器具有语法高亮,撤消/重做功能和查询保存。以下部分将探讨AQL的一些功能。对于一个完整的参考,请访问 全面的文档

AQL基础

AQL是一种声明性语言,意味着一个查询表达应该实现什么结果,而不是如何实现它。它允许查询数据,但也可以修改数据。这两种方法可以组合以实现复杂的任务。 在AQL中读取和修改查询完全符合ACID。操作将完全完成或根本不完成。即使读取数据也会在数据的一致性快照上发生。 我们再次开始创建数据。让我们添加更多的歌曲给我们的 songs集。只需复制并粘贴以下查询:
FOR song IN [

{ album: "Led Zeppelin", title: "Good Times Bad Times", artist: "Led Zeppelin", length: 166, year: 1969, _key: "good_times_bad_times" }

,

{ album: "Led Zeppelin", title: "Dazed and Confused", artist: "Led Zeppelin", length: 388, year: 1969, _key: "dazed_and_confused" }

,

{ album: "Led Zeppelin", title: "Communication Breakdown", artist: "Led Zeppelin", length: 150, year: 1969, _key: "communication_breakdown" }

]

INSERT song IN songs
单击 提交按钮。 该查询已经是AQL是如何工作的一个很好的例子:你遍历与文件清单 FOR并在每个文件的执行操作。该列表可以是具有JSON对象或数据库中任何集合的数组。操作包括过滤,修改,选择更多文档,创建新结构或(如本示例中)将文档插入数据库。事实上,AQL也支持所有的CRUD操作。 要获取数据库中所有歌曲的概述,请运行以下查询。这是一个相当于 SELECT * FROM songs在基于SQL的数据库(因为编辑器记住最后的查询,您应该单击 回收站图标以清除编辑):
FOR song IN songs RETURN song
现在,您将在文本字段中看到歌曲数据库中的所有条目。回到 查询选项卡,并再次清除编辑器。 另一个示例涉及对于超过三分钟的播放时间的歌曲的基本过滤:
FOR song IN songs

FILTER song.length > 180

RETURN song
结果呈现在编辑器中的 结果选项卡: 查询结果

复杂AQL示例

AQL配备了 一套功能对所有支持的数据类型,甚至允许 增加新的功能 。 结合在查询中分配变量的能力,您可以构建非常复杂的构造。 这允许您将数据密集型操作移动到更接近数据本身,而不是在客户端上执行它们。 为了说明这一点,我们将格式化一首歌的时间为 mm:ss ,使其很好地阅读用户:
FOR song IN songs

FILTER song.length > 180

LET minutes = FLOOR(song.length / 60)

LET seconds = song.length % 60

RETURN

{ title: song.title, duration: CONCAT_SEPARATOR(':', minutes, seconds) }
这次我们将返回歌曲标题和持续时间。该 RETURN允许您创建一个新的JSON对象返回为每个输入文件。 AQL是具有很多功能的复杂语言。但还有一个值得一提的功能,特别是在NoSQL数据库的上下文中:Joins。

加入AQL

使用文档存储作为数据库有几个含义。您应该使用与使用关系数据库时不同的方式建模数据。 在文档存储中,您可以嵌入否则将被建模为关系的数据,但这种方法并不总是可行的。有些情况下,关系更有意义。如果没有能力让数据库执行所需的连接,您将最终加入客户端上的数据,或者反规范化您的数据模型和嵌入子文档。这对于复杂和大的数据集变得尤其成问题。 所以,让我们做一个联接。 为了说明这个功能,我们将取代 album用的一个参考的歌曲属性 albums集合。 我们已经创建了相册 齐柏林飞艇III作为前一个文件。如果在前面的示例中删除了相册,请返回并重新添加。 这个查询会诀窍:
FOR album IN albums

FOR song IN songs

FILTER song.album == album.name

LET song_with_album_ref = MERGE(UNSET(song, 'album'),

{ album_key: album._key }

)

REPLACE song WITH song_with_album_ref IN songs
我们首先遍历所有相册,然后查找该相册关联的所有歌曲。下一步是创建一个新的文件,它包含了 album_key属性和 UNSETalbum属性。 我们将使用 REPLACE而不是 UPDATE更新歌曲文件。这是可能的,因为我们之前创建了一个新的歌曲文档。 此数据迁移后,我们现在可以在一个位置保存相册文档。当提取歌曲数据时,我们可以使用连接将歌曲名称再次添加到歌曲文档:
FOR song IN songs

FOR album IN albums

FILTER album._key == song.album_key

RETURN MERGE(song,

{ album: album.name }

)
我们几乎没有刮过AQL可以完成什么的表面,但是你应该对什么是可能的一个好的印象。对于一个完整的语言参考和更多示例,请参阅大量的 文档

(可选)第9步 - 进行备份

一旦将ArangoDB数据库投入生产,您应该开始考虑备份。但是,在此之前建立备份是一个好的做法。 使用 从DigitalOcean备份功能是一个良好的开端。 此外,您可能要考虑使用 arangodumparangorestore有什么备份以及在何处存储备份更细粒度的控制。

(可选)第10步 - 升级

当ArangoDB的新版本发布时,它将通过配置的软件包存储库发布。要安装最新版本,首先需要更新存储库索引:
sudo apt-get update
现在停止数据库:
sudo service arangodb stop
将其更新到最新版本:
sudo apt-get install arangodb
注意:在安装更新后,系统将尝试启动arangodb服务。 这可能会失败,因为数据库文件需要升级。 这是可以预期的。 您可能需要自己升级数据库文件:
sudo service arangodb upgrade
之后,像往常一样启动服务器:
sudo service arangodb start

使用Foxx应用程序扩展ArangoDB

在我们完成之前,还有一件值得一提的事情:由于ArangoDB有一个集成的V8引擎来处理所有的JavaScript,它内置了一个HTTP服务器,我们可以扩展现有的HTTP API与自定义端点。此功能被称为 福克斯 。 Foxx是使用ArangoDB构建具有持久数据的自定义微服务的框架。 Foxx应用程序是用JavaScript编写的,并在ArangoDB的V8环境中运行。应用程序可以直接访问本机JavaScript界面,因此可以访问数据,而无需任何HTTP往返。 Foxx提供了一个最小的框架,在Sinatra for Ruby或Flask for Python的意义上。您编写控制器来处理传入的请求并在模型中实现业务逻辑。 Foxx应用程序可以通过Web界面进行管理,可以像任何其他应用程序一样开发。您可以将它们置于版本控制之下,甚至直接从Git存储库进行部署。因为它们只是JavaScript,所以对它们进行单元测试是很简单的。对于简单的用例,它们非常像关系数据库系统中的存储过程,但是Foxx代码更容易维护和测试。 使用Foxx应用程序作为存储过程只是开始。想象一下,你有多个应用程序共享某些业务逻辑。使用Foxx,您可以将此业务逻辑移动到更接近数据,以使处理更快,并降低在组件之间分配共享实现的复杂性。运行ArangoDB作为一个集群甚至负责使Foxx应用程序可用于集群中的每个成员。 甚至整个web应用程序可能与Foxx。使用前端框架(如Angular或Ember)允许您完全从数据库运行应用程序。这不需要额外的基础设施。在生产环境中,你最终会把Nginx或类似的东西放在ArangoDB的前面。 ArangoDB附带一些提供常见功能的Foxx应用程序,例如身份验证和会话存储。你甚至可以使用npm包,如果他们不依赖于HTTP功能。

结论

ArangoDB是一个强大的数据库,有广泛的支持用例。它是良好的维护和有非常好的文件。开始使用很容易,因为每个主要操作系统都有软件包。 Web界面降低了探索特性的负担,如果您来自关系背景,使用AQL与使用SQL没有什么不同。 有了使用JavaScript应用程序扩展数据库的选项,以及图表功能,使ArangoDB成为一个完整的包,以使应用程序启动和增长。 到目前为止,我们已经分享了ArangoDB的全貌。 作为下一步,我们建议如下:
  • 对于任何真正的应用程序,您将与HTTP API进行交互。我们没有包括在这里,因为你很可能不会直接使用它,而是通过许多之一母语驱动程序
  • 大多数时候,通过AQL与ArangoDB中的数据进行交互。如果你想在生产环境中使用ArangoDB,就必须使用它。
  • ArangoDB不仅是一个文档存储,而且还具有非常强大的图形功能。它允许您在有向图中将数据建模为顶点。关系可以被建模为那些顶点,而不是使用之间的边缘_key引用。 以这种方式建模的数据也有一些好处超过在SQL数据库中使用的关系的方式。
赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏