mongdb

簡介

  • MongoDB 是一個基於分佈式 文件存儲的NoSQL數據庫
  • 由C++語言編寫,運行穩定,性能高
  • 旨在爲 WEB 應用提供可擴展的高性能數據存儲解決方案
  • 查看官方網站

MongoDB特色

  • 模式自由 :能夠把不一樣結構的文檔存儲在同一個數據庫裏
  • 面向集合的存儲:適合存儲 JSON風格文件的形式
  • 完整的索引支持:對任何屬性可索引
  • 複製和高可用性:支持服務器之間的數據複製,支持主-從模式及服務器之間的相互複製。複製的主要目的是提供冗餘及自動故障轉移
  • 自動分片:支持雲級別的伸縮性:自動分片功能支持水平的數據庫集羣,可動態添加額外的機器
  • 豐富的查詢:支持豐富的查詢表達方式,查詢指令使用JSON形式的標記,可輕易查詢文檔中的內嵌的對象及數組
  • 快速就地更新:查詢優化器會分析查詢表達式,並生成一個高效的查詢計劃
  • 高效的傳統存儲方式:支持二進制數據及大型對象(如照片或圖片)

1. 基本操做

  • MongoDB將數據存儲爲一個文檔,數據結構由鍵值(key=>value)對組成
  • MongoDB文檔相似於JSON對象,字段值能夠包含其餘文檔、數組、文檔數組
  • 安裝管理mongodb環境
  • 完成數據庫、集合的管理
  • 數據的增長、修改、刪除、查詢

名詞

SQL術語/概念 MongoDB術語/概念 解釋/說明
database database 數據庫
table collection 數據庫表/集合
row document 數據記錄行/文檔
column field 數據字段/域
index index 索引
table joins   錶鏈接,MongoDB不支持
primary key primary key 主鍵,MongoDB自動將_id字段設置爲主鍵
  • 三元素:數據庫,集合,文檔
    • 集合就是關係數據庫中的表
    • 文檔對應着關係數據庫中的行
  • 文檔,就是一個對象,由鍵值對構成,是json的擴展Bson形式
{'name':'guojing','gender':'男'}
  • 集合:相似於關係數據庫中的表,儲存多個文檔,結構不固定,如能夠存儲以下文檔在一個集合中
{'name':'guojing','gender':'男'}
{'name':'huangrong','age':18}
{'book':'shuihuzhuan','heros':'108'}
  • 數據庫:是一個集合的物理容器,一個數據庫中能夠包含多個文檔
  • 一個服務器一般有多個數據庫

1.1. 安裝

  • 下載mongodb的版本,兩點注意
    • 根據業界規則,偶數爲穩定版,如1.6.X,奇數爲開發版,如1.7.X
    • 32bit的mongodb最大隻能存放2G的數據,64bit就沒有限制
  • 到官網,選擇合適的版本下載
  • 解壓
tar -zxvf mongodb-linux-x86_64-ubuntu1604-3.4.0.tgz
  • 移動到/usr/local/目錄下
sudo mv -r mongodb-linux-x86_64-ubuntu1604-3.4.0/ /usr/local/mongodb
  • 將可執行文件添加到PATH路徑中
export PATH=/usr/local/mongodb/bin:$PATH

管理mongo

  • 配置文件在/etc/mongod.conf
  • 默認端口27017html

  • 啓動python

sudo service mongod start
  • 中止
sudo service mongod stop
  • 使用終端鏈接
  • 這個shell就是mongodb的客戶端,同時也是一個js的編譯器
mongo
  • 命令
db查看當前數據庫名稱
db.stats()查看當前數據庫信息
  • 終端退出鏈接
exit
或ctrl+c
  • GUI:robomongo,解壓後在bin目錄下找到運行程序
  • 界面以下:

 1.2 數據庫操做linux

數據庫切換

  • 查看當前數據庫名稱
db
  • 查看全部數據庫名稱
  • 列出全部在物理上存在的數據庫
show dbs
  • 切換數據庫
  • 若是數據庫不存在,則指向數據庫,但不建立,直到插入數據或建立集合時數據庫才被建立
use 數據庫名稱
  • 默認的數據庫爲test,若是你沒有建立新的數據庫,集合將存放在test數據庫中

數據庫刪除

  • 刪除當前指向的數據庫
  • 若是數據庫不存在,則什麼也不作


1.3. 集合操做db.dropDatabase()

集合建立

  • 語法
db.createCollection(name, options)
  • name是要建立的集合的名稱
  • options是一個文檔,用於指定集合的配置
  • 選項​​參數是可選的,因此只須要到指定的集合名稱。如下是可使用的選項列表:
  • 例1:不限制集合大小
db.createCollection("stu")
  • 例2:限制集合大小,後面學會插入語句後能夠查看效果
  • 參數capped:默認值爲false表示不設置上限,值爲true表示設置上限
  • 參數size:當capped值爲true時,須要指定此參數,表示上限大小,當文檔達到上限時,會將以前的數據覆蓋,單位爲字節
db.createCollection("sub", { capped : true, size : 10 } )

查看當前數據庫的集合

  • 語法
show collections

刪除

  • 語法



db.集合名稱.drop()

1.4. 數據類型

  • 下表爲MongoDB中經常使用的幾種數據類型:
  • Object ID:文檔ID
  • String:字符串,最經常使用,必須是有效的UTF-8
  • Boolean:存儲一個布爾值,true或false
  • Integer:整數能夠是32位或64位,這取決於服務器
  • Double:存儲浮點值
  • Arrays:數組或列表,多個值存儲到一個鍵
  • Object:用於嵌入式的文檔,即一個值爲一個文檔
  • Null:存儲Null值
  • Timestamp:時間戳
  • Date:存儲當前日期或時間的UNIX時間格式

 

object id

  • 每一個文檔都有一個屬性,爲_id,保證每一個文檔的惟一性
  • 能夠本身去設置_id插入文檔
  • 若是沒有提供,那麼MongoDB爲每一個文檔提供了一個獨特的_id,類型爲objectID
  • objectID是一個12字節的十六進制數
    • 前4個字節爲當前時間戳
    • 接下來3個字節的機器ID
    • 接下來的2個字節中MongoDB的服務進程id
    • 最後3個字節是簡單的增量值

1.5. 數據操做 

插入

  • 語法
db.集合名稱.insert(document)
  • 插入文檔時,若是不指定_id參數,MongoDB會爲文檔分配一個惟一的ObjectId
  • 例1
db.stu.insert({name:'gj',gender:1})
  • 例2
s1={_id:'20160101',name:'hr'}
s1.gender=0
db.stu.insert(s1)

簡單查詢

  • 語法
db.集合名稱.find()

更新

  • 語法
db.集合名稱.update(
   <query>,
   <update>,
   {multi: <boolean>}
)
  • 參數query:查詢條件,相似sql語句update中where部分
  • 參數update:更新操做符,相似sql語句update中set部分
  • 參數multi:可選,默認是false,表示只更新找到的第一條記錄,值爲true表示把知足條件的文檔所有更新
  • 例3:全文檔更新
db.stu.update({name:'hr'},{name:'mnc'})
  • 例4:指定屬性更新,經過操做符$set
db.stu.insert({name:'hr',gender:0})
db.stu.update({name:'hr'},{$set:{name:'hys'}})
  • 例5:修改多條匹配到的數據
db.stu.update({},{$set:{gender:0}},{multi:true})

保存

  • 語法
db.集合名稱.save(document)
  • 若是文檔的_id已經存在則修改,若是文檔的_id不存在則添加web

  • 例6正則表達式

db.stu.save({_id:'20160102','name':'yk',gender:1})
  • 例7
db.stu.save({_id:'20160102','name':'wyk'})

刪除

  • 語法
db.集合名稱.remove(
   <query>,
   {
     justOne: <boolean>
   }
)
  • 參數query:可選,刪除的文檔的條件
  • 參數justOne:可選,若是設爲true或1,則只刪除一條,默認false,表示刪除多條
  • 例8:只刪除匹配到的第一條
db.stu.remove({gender:0},{justOne:true})
  • 例9:所有刪除
db.stu.remove({})

關於size的示例

  • 例10
  • 建立集合
db.createCollection('sub',{capped:true,size:10})
  • 插入第一條數據庫查詢
db.sub.insert({title:'linux',count:10})
db.sub.find()
  • 插入第二條數據庫查詢
db.sub.insert({title:'web',count:15})
db.sub.find()
  • 插入第三條數據庫查詢
db.sub.insert({title:'sql',count:8})
db.sub.find()
  • 插入第四條數據庫查詢
db.sub.insert({title:'django',count:12})
db.sub.find()
  • 插入第五條數據庫查詢
db.sub.insert({title:'python',count:14})
db.sub.find()

 1.6

數據查詢

基本查詢

  • 方法find():查詢
db.集合名稱.find({條件文檔})
  • 方法findOne():查詢,只返回第一個
db.集合名稱.findOne({條件文檔})
  • 方法pretty():將結果格式化
db.集合名稱.find({條件文檔}).pretty()

比較運算符

  • 等於,默認是等於判斷,沒有運算符
  • 小於$lt
  • 小於或等於$lte
  • 大於$gt
  • 大於或等於$gte
  • 不等於$ne
  • 例1:查詢名稱等於'gj'的學生
db.stu.find({name:'gj'})
  • 例2:查詢年齡大於或等於18的學生
db.stu.find({age:{$gte:18}})

邏輯運算符

  • 查詢時能夠有多個條件,多個條件之間須要經過邏輯運算符鏈接
  • 邏輯與:默認是邏輯與的關係
  • 例3:查詢年齡大於或等於18,而且性別爲1的學生
db.stu.find({age:{$gte:18},gender:1})
  • 邏輯或:使用$or
  • 例4:查詢年齡大於18,或性別爲0的學生
db.stu.find({$or:[{age:{$gt:18}},{gender:1}]})
  • and和or一塊兒使用
  • 例5:查詢年齡大於18或性別爲0的學生,而且學生的姓名爲gj
db.stu.find({$or:[{age:{$gte:18}},{gender:1}],name:'gj'})

範圍運算符

  • 使用"$in","$nin" 判斷是否在某個範圍內
  • 例6:查詢年齡爲1八、28的學生
db.stu.find({age:{$in:[18,28]}})

支持正則表達式

  • 使用//或$regex編寫正則表達式
  • 例7:查詢姓黃的學生
db.stu.find({name:/^黃/})
db.stu.find({name:{$regex:'^黃'}}})

自定義查詢

  • 使用$where後面寫一個函數,返回知足條件的數據
  • 例7:查詢年齡大於30的學生


1.6.1 Limit和Skipdb.stu.find({$where:function(){return this.age>20}})

Limit

  • 方法limit():用於讀取指定數量的文檔
  • 語法:
db.集合名稱.find().limit(NUMBER)
  • 參數NUMBER表示要獲取文檔的條數
  • 若是沒有指定參數則顯示集合中的全部文檔
  • 例1:查詢2條學生信息
db.stu.find().limit(2)

skip

  • 方法skip():用於跳過指定數量的文檔
  • 語法:
db.集合名稱.find().skip(NUMBER)
  • 參數NUMBER表示跳過的記錄條數,默認值爲0
  • 例2:查詢從第3條開始的學生信息
db.stu.find().skip(2)

一塊兒使用

  • 方法limit()和skip()能夠一塊兒使用,不分前後順序sql

  • 建立數據集mongodb

for(i=0;i<15;i++){db.t1.insert({_id:i})}
  • 查詢第5至8條數據


1.6.2db.stu.find().limit(4).skip(5) 或 db.stu.find().skip(5).limit(4)

投影

  • 在查詢到的返回結果中,只選擇必要的字段,而不是選擇一個文檔的整個字段
  • 如:一個文檔有5個字段,須要顯示只有3個,投影其中3個字段便可
  • 語法:
  • 參數爲字段與值,值爲1表示顯示,值爲0不顯示
db.集合名稱.find({},{字段名稱:1,...})
  • 對於須要顯示的字段,設置爲1便可,不設置即爲不顯示
  • 特殊:對於_id列默認是顯示的,若是不顯示須要明確設置爲0
  • 例1
db.stu.find({},{name:1,gender:1})
  • 例2


1.6.3db.stu.find({},{_id:0,name:1,gender:1})

排序

  • 方法sort(),用於對結果集進行排序
  • 語法
db.集合名稱.find().sort({字段:1,...})
  • 參數1爲升序排列
  • 參數-1爲降序排列
  • 例1:根據性別降序,再根據年齡升序


1.6.4db.stu.find().sort({gender:-1,age:1})

統計個數

  • 方法count()用於統計結果集中文檔條數
  • 語法
db.集合名稱.find({條件}).count()
  • 也能夠與爲
db.集合名稱.count({條件})
  • 例1:統計男生人數
db.stu.find({gender:1}).count()
  • 例2:統計年齡大於20的男生人數

1.6.5db.stu.count({age:{$gt:20},gender:1})

消除重複

  • 方法distinct()對數據進行去重
  • 語法
db.集合名稱.distinct('去重字段',{條件})
  • 例1:查找年齡大於18的性別(去重)

1.7db.stu.distinct('gender',{age:{$gt:18}})

總結

  • 安裝
  • 數據庫建立、刪除
  • 集合建立、刪除
  • 文檔增長、修改、刪除
  • 文檔查詢:find(),limit(),skip(),投影,sort(),count(),distinct()

做業

  • 建立科目集合sub,並進行數據的操做
  • 查詢sub中的數據

2. shell

高級操做

  • 講解關於mongodb的高級操做,包括聚合、主從複製、分片、備份與恢復、MR
  • 完成python與mongodb的交互

2.1數據庫

聚合 aggregate

  • 聚合(aggregate)主要用於計算數據,相似sql中的sum()、avg()
  • 語法
db.集合名稱.aggregate([{管道:{表達式}}])

管道

  • 管道在Unix和Linux中通常用於將當前命令的輸出結果做爲下一個命令的輸入
ps ajx | grep mongo
  • 在mongodb中,管道具備一樣的做用,文檔處理完畢後,經過管道進行下一次處理
  • 經常使用管道
    • $group:將集合中的文檔分組,可用於統計結果
    • $match:過濾數據,只輸出符合條件的文檔
    • $project:修改輸入文檔的結構,如重命名、增長、刪除字段、建立計算結果
    • $sort:將輸入文檔排序後輸出
    • $limit:限制聚合管道返回的文檔數
    • $skip:跳過指定數量的文檔,並返回餘下的文檔
    • $unwind:將數組類型的字段進行拆分

表達式

  • 處理輸入文檔並輸出
  • 語法
表達式:'$列名'
  • 經常使用表達式
    • $sum:計算總和,$sum:1同count表示計數
    • $avg:計算平均值
    • $min:獲取最小值
    • $max:獲取最大值
    • $push:在結果文檔中插入值到一個數組中
    • $first:根據資源文檔的排序獲取第一個文檔數據
    • $last:根據資源文檔的排序獲取最後一個文檔數據
2.1.1

$group

  • 將集合中的文檔分組,可用於統計結果
  • _id表示分組的依據,使用某個字段的格式爲'$字段'
  • 例1:統計男生、女生的總人數
db.stu.aggregate([
    {$group:
        {
            _id:'$gender',
            counter:{$sum:1}
        }
    }
])

Group by null

  • 將集合中全部文檔分爲一組
  • 例2:求學生總人數、平均年齡
db.stu.aggregate([
    {$group:
        {
            _id:null,
            counter:{$sum:1},
            avgAge:{$avg:'$age'}
        }
    }
])

透視數據

  • 例3:統計學生性別及學生姓名
db.stu.aggregate([
    {$group:
        {
            _id:'$gender',
            name:{$push:'$name'}
        }
    }
])
  • 使用$$ROOT能夠將文檔內容加入到結果集的數組中,代碼以下


2.1.2db.stu.aggregate([ {$group: { _id:'$gender', name:{$push:'$$ROOT'} } } ])

$match

  • 用於過濾數據,只輸出符合條件的文檔
  • 使用MongoDB的標準查詢操做
  • 例1:查詢年齡大於20的學生
db.stu.aggregate([
    {$match:{age:{$gt:20}}}
])
  • 例2:查詢年齡大於20的男生、女生人數



db.stu.aggregate([ {$match:{age:{$gt:20}}}, {$group:{_id:'$gender',counter:{$sum:1}}} ])
2.1.3

$project

  • 修改輸入文檔的結構,如重命名、增長、刪除字段、建立計算結果
  • 例1:查詢學生的姓名、年齡
db.stu.aggregate([
    {$project:{_id:0,name:1,age:1}}
])
  • 例2:查詢男生、女生人數,輸出人數


2.1.4db.stu.aggregate([ {$group:{_id:'$gender',counter:{$sum:1}}}, {$project:{_id:0,counter:1}} ])

$sort

  • 將輸入文檔排序後輸出
  • 例1:查詢學生信息,按年齡升序
b.stu.aggregate([{$sort:{age:1}}])
  • 例2:查詢男生、女生人數,按人數降序

2.db.stu.aggregate([ {$group:{_id:'$gender',counter:{$sum:1}}}, {$sort:{counter:-1}} ])

2.1.5

$limit

  • 限制聚合管道返回的文檔數
  • 例1:查詢2條學生信息
db.stu.aggregate([{$limit:2}])

$skip

  • 跳過指定數量的文檔,並返回餘下的文檔
  • 例2:查詢從第3條開始的學生信息
db.stu.aggregate([{$skip:2}])
  • 例3:統計男生、女生人數,按人數升序,取第二條數據
db.stu.aggregate([
    {$group:{_id:'$gender',counter:{$sum:1}}},
    {$sort:{counter:1}},
    {$skip:1},
    {$limit:1}
])
  • 注意順序:先寫skip,再寫limit

2.1.6django

$unwind

  • 將文檔中的某一個數組類型字段拆分紅多條,每條包含數組中的一個值

語法1

  • 對某字段值進行拆分
db.集合名稱.aggregate([{$unwind:'$字段名稱'}])
  • 構造數據
db.t2.insert({_id:1,item:'t-shirt',size:['S','M','L']})
  • 查詢
db.t2.aggregate([{$unwind:'$size'}])

語法2

  • 對某字段值進行拆分
  • 處理空數組、非數組、無字段、null狀況
db.inventory.aggregate([{
    $unwind:{
        path:'$字段名稱',
        preserveNullAndEmptyArrays:<boolean>#防止數據丟失
    }
}])
  • 構造數據
db.t3.insert([
{ "_id" : 1, "item" : "a", "size": [ "S", "M", "L"] },
{ "_id" : 2, "item" : "b", "size" : [ ] },
{ "_id" : 3, "item" : "c", "size": "M" },
{ "_id" : 4, "item" : "d" },
{ "_id" : 5, "item" : "e", "size" : null }
])
  • 使用語法1查詢
db.t3.aggregate([{$unwind:'$size'}])
  • 查看查詢結果,發現對於空數組、無字段、null的文檔,都被丟棄了
  • 問:如何能不丟棄呢?
  • 答:使用語法2查詢


2.2安全db.t3.aggregate([{$unwind:{path:'$sizes',preserveNullAndEmptyArrays:true}}])

超級管理員

  • 爲了更安全的訪問mongodb,須要訪問者提供用戶名和密碼,因而須要在mongodb中建立用戶
  • 採用了角色-用戶-數據庫的安全管理方式
  • 經常使用系統角色以下:
    • root:只在admin數據庫中可用,超級帳號,超級權限
    • Read:容許用戶讀取指定數據庫
    • readWrite:容許用戶讀寫指定數據庫
  • 建立超級管理用戶
use admin
db.createUser({
    user:'admin',
    pwd:'123',
    roles:[{role:'root',db:'admin'}]
})

啓用安全認證

  • 修改配置文件
sudo vi /etc/mongod.conf
  • 啓用身份驗證
  • 注意:keys and values之間必定要加空格, 不然解析會報錯
security:
  authorization: enabled
  • 重啓服務
sudo service mongod stop
sudo service mongod start
  • 終端鏈接
mongo -u 'admin' -p '123' --authenticationDatabase 'admin'

普通用戶管理

  • 使用超級管理員登陸,而後進入用戶管理操做
  • 查看當前數據庫的用戶
use test1
show users
  • 建立普通用戶
db.createUser({
    user:'t1',
    pwd:'123',
    roles:[{role:'readWrite',db:'test1'}]
})
  • 終端鏈接
mongo -u t1 -p 123 --authenticationDatabase test1
  • 切換數據庫,執行命令查看效果

  • 修改用戶:能夠修改pwd、roles屬性


2.3db.updateUser('t1',{pwd:'456'})

複製(副本集)

什麼是複製

  • 複製提供了數據的冗餘備份,並在多個服務器上存儲數據副本,提升了數據的可用性,並能夠保證數據的安全性
  • 複製還容許從硬件故障和服務中斷中恢復數據

爲何要複製

  • 數據備份
  • 數據災難恢復
  • 讀寫分離
  • 高(24* 7)數據可用性
  • 無宕機維護
  • 副本集對應用程序是透明

複製的工做原理

  • 複製至少須要兩個節點A、B...
  • A是主節點,負責處理客戶端請求
  • 其他的都是從節點,負責複製主節點上的數據
  • 節點常見的搭配方式爲:一主一從、一主多從
  • 主節點記錄在其上的全部操做,從節點按期輪詢主節點獲取這些操做,而後對本身的數據副本執行這些操做,從而保證從節點的數據與主節點一致
  • 主節點與從節點進行數據交互保障數據的一致性

複製的特色

  • N 個節點的集羣
  • 任何節點可做爲主節點
  • 全部寫入操做都在主節點上
  • 自動故障轉移
  • 自動恢復

設置複製節點

  • 接下來的操做須要打開多個終端窗口,並且可能會鏈接多臺ubuntu主機,會顯得有些亂,建議在xshell中實現
  • step1:建立數據庫目錄t一、t2
  • 在Desktop目錄下演示,其它目錄也能夠,注意權限便可
mkdir t1
mkdir t2
  • step2:使用以下格式啓動mongod,注意replSet的名稱是一致的
mongod --bind_ip 192.168.196.128 --port 27017 --dbpath ~/Desktop/t1 --replSet rs0
mongod --bind_ip 192.168.196.128 --port 27018 --dbpath ~/Desktop/t2 --replSet rs0
  • step3:鏈接主服務器,此處設置192.168.196.128:27017爲主服務器
mongo --host 192.168.196.128 --port 27017
  • step4:初始化
rs.initiate()
  • 初始化完成後,提示符以下圖:

 

  • step5:查看當前狀
rs.status()
  • 當前狀態以下圖:
  •  

  • step6:添加複本集
rs.add('192.168.196.128:27018')
  • step7:複本集添加成功後,當前狀態以下圖:

 

  • step8:鏈接第二個mongo服務
mongo --host 192.168.196.128 --port 27018
  • 鏈接成功後,提示符以下圖:

初始化

  • step9:向主服務器中插入數據
use test1
for(i=0;i<10;i++){db.t1.insert({_id:i})}
db.t1.find()
  • step10:在從服務器中插查詢
  • 說明:若是在從服務器上進行讀操做,須要設置rs.slaveOk()
rs.slaveOk()
db.t1.find()

其它說明

  • 刪除從節點
rs.remove('192.168.196.128:27018')
  • 關閉主服務器後,再從新啓動,會發現原來的從服務器變爲了從服務器,新啓動的服務器(原來的從服務器)變爲了從服務器

2.4

備份

  • 語法
mongodump -h dbhost -d dbname -o dbdirectory
  • -h:服務器地址,也能夠指定端口號
  • -d:須要備份的數據庫名稱
  • -o:備份的數據存放位置,此目錄中存放着備份出來的數據
  • 例1
sudo mkdir test1bak
sudo mongodump -h 192.168.196.128:27017 -d test1 -o ~/Desktop/test1bak

恢復

  • 語法
mongorestore -h dbhost -d dbname --dir dbdirectory
  • -h:服務器地址
  • -d:須要恢復的數據庫實例
  • --dir:備份數據所在位置
  • 例2

2.5mongorestore -h 192.168.196.128:27017 -d test2 --dir ~/Desktop/test1bak/test1

與python交互

進入虛擬環境
sudo pip install pymongo
或源碼安裝
python setup.py
  • 引入包pymongo
import pymongo
  • 鏈接,建立客戶端
client=pymongo.MongoClient("localhost", 27017)
  • 得到數據庫test1
db=client.test1
  • 得到集合stu
stu = db.stu
  • 添加文檔
s1={name:'gj',age:18}
s1_id = stu.insert_one(s1).inserted_id
  • 查找一個文檔
s2=stu.find_one()
  • 查找多個文檔1
for cur in stu.find():
    print cur
  • 查找多個文檔2
cur=stu.find()
cur.next()
cur.next()
cur.next()
  • 獲取文檔個數

2.6print stu.count()

總結

  • 聚合
  • 安全
  • 副本集
  • 備份與恢復
  • 與python交互

做業

  • 熟練聚合
  • 熟練與python交互
相關文章
相關標籤/搜索