瑞_MongoDB_安全认证

文章目录

    • 1 MongoDB副本集-Replica Sets
    • 2 分片集群-Sharded Cluster
    • 3 安全认证
      • 3.1 MongoDB的用户和角色权限简介
        • 3.1.1 相关概念
        • 3.1.2 角色权限相关命令
        • 3.1.3 常用的内置角色
      • 3.2 单实例环境
        • 3.2.1 关闭已开启的服务(可选)
        • 3.2.2 添加用户和权限
        • 3.2.3 服务端开启认证和客户端连接登录
          • 3.2.3.1 服务端开启认证——参数方式
          • 3.2.3.2 服务端开启认证——配置文件方式(推荐)
          • 3.2.4.3 客户端连接登录——先连接再认证
          • 3.2.4.4 客户端连接登录——连接时直接认证
        • 3.2.4 SpringDataMongoDB连接认证
      • 3.3 副本集环境
        • 3.3.1 前言
        • 3.3.2 关闭已开启的副本集服务(可选)
        • 3.3.3 通过主节点添加一个管理员帐号
        • 3.3.4 创建副本集认证的key文件
        • 3.3.5 修改配置文件指定keyfile
        • 3.3.6 重新启动副本集
        • 3.3.7 在主节点上添加普通账号
        • 3.3.8 SpringDataMongoDB连接副本集
      • 3.4 分片集群环境
        • 3.4.1 关闭已开启的集群服务(可选)
        • 3.4.2 创建分片集群认证的key文件
        • 3.4.3 修改配置文件指定keyfile
        • 3.4.4 重新启动节点
        • 3.4.5 创建帐号和认证
        • 3.4.6 SpringDataMongoDB连接认证

瑞&3l

???? 前言:本文章为瑞_系列专栏之《MongoDB》的集群和安全整合篇的 MongoDB 安全认证章节。由于博主是从B站黑马程序员的《MongoDB》学习其相关知识,所以本系列专栏主要是针对该课程进行笔记总结和拓展,文中的部分原理及图解等也是来源于黑马提供的资料,特此注明。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!

系列整合篇:《瑞_MongoDB(笔记超详细,有这一篇就够了)》
单实例环境部署请参考:《瑞_MongoDB的2.4.2 Linux安装MongoDB》
副本集环境部署请参考:《瑞_MongoDB_MongoDB副本集》
分片集群环境部署请参考:《瑞_MongoDB_MongoDB分片集群》




1 MongoDB副本集-Replica Sets

瑞:副本集的作用就是,当一台服务器挂了,可以无缝切换另一台服务,类似 MySQL 的主从复制,具体请见《瑞_MongoDB_MongoDB副本集》




2 分片集群-Sharded Cluster

瑞:副本集的特点是,无论搭建了多少个副本节点,存储的数据都是一样的,这样就会导致数据只能存在一台服务器上。随着业务量的扩大,一台服务器无法存储海量数据,此时就需要分片集群存储,具体请见《瑞_MongoDB_MongoDB分片集群》




3 安全认证

3.1 MongoDB的用户和角色权限简介

  默认情况下,MongoDB 实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB 不会对连接客户端进行用户验证,这是非常危险的。

  MongoDB 官网上说,为了能保障 MongoDB 的安全可以做以下几个步骤⬇️

  1️⃣ 使用新的端口,默认的 27017 端口如果一旦知道了 ip 就能连接上,不太安全。

  2️⃣ 设置 MongoDB 的网络环境,最好将 MongoDB 部署到公司服务器内网,外网无法访问。公司内部访问使用 vpn 等。

  3️⃣ 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。

  为了强制开启用户访问控制(用户验证),则需要在 MongoDB 实例启动时使用选项--auth或在指定启动配置文件中添加选项auth=true

3.1.1 相关概念

  在开始之前先了解一些相关概念

  • 启用访问控制

  MongoDB使用的是基于角色的访问控制(Role-Based Access Control,RBAC)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。

  在实例启动时添加选项--auth或指定启动配置文件中添加选项auth=true

  • 角色

  在 MongoDB 中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。

  • 权限

  权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。

  1️⃣ 资源(resource)包括:数据库、集合、部分集合和集群;

  2️⃣ 操作(action)包括:对资源进行的增、删、改、查(CRUD)操作。

  在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中,新创建角色可以继承其他角色的权限,在admin数据库中创建的角色可以继承在其它任意数据库中角色的权限。

3.1.2 角色权限相关命令

  关于角色权限的查看,可以通过如下命令查询(了解)

# 查询所有角色权限(仅用户自定义角色)
> db.runCommand({ rolesInfo: 1 })

# 查询所有角色权限(包含内置角色)
> db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })

# 查询当前数据库中的某角色的权限
> db.runCommand({ rolesInfo: "<rolename>" })

# 查询其它数据库中指定的角色权限
> db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }

# 查询多个角色权限
> db.runCommand(
    {
        rolesInfo: [
            "<rolename>",
            { role: "<rolename>", db: "<database>" },
            ...
        ]
    }
)

  【示例】查看所有内置角色

> db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
{
        "roles" : [
                {
                        "role" : "__queryableBackup",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "__system",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "backup",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "clusterAdmin",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "clusterManager",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "clusterMonitor",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "dbAdmin",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "dbAdminAnyDatabase",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "dbOwner",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "enableSharding",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "hostManager",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "read",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "readAnyDatabase",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "readWrite",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "readWriteAnyDatabase",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "restore",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "root",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "userAdmin",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                },
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin",
                        "isBuiltin" : true,
                        "roles" : [ ],
                        "inheritedRoles" : [ ]
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1714337286, 5),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1714337288, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}
3.1.3 常用的内置角色

  常用的内置角色

  • 数据库用户角色:read、readWrite;
  • 所有数据库用户角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
  • 数据库管理角色:dbAdmin、dbOwner、userAdmin;
  • 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
  • 备份恢复角色:backup、restore;
  • 超级用户角色:root
  • 内部角色:system

  角色说明

角色 权限描述
read 读取指定数据库中任何数据
readWrite 读写指定数据库中任何数据,包括创建、重命名、删除集合
readAnyDatabase 读取所有数据库中任何数据(除了数据库config和local之外)
readWriteAnyDatabase 读写所有数据库中任何数据(除了数据库config和local之外)
userAdminAnyDatabase 在指定数据库创建和修改用户(除了数据库config和local之外)
dbAdminAnyDatabase 读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除了数据库config和local之外)
dbAdmin 读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作
userAdmin 在指定数据库创建和修改用户
clusterAdmin 对整个集群或数据库系统进行管理操作
backup 备份MongoDB数据最小的权限
restore 从备份文件中还原恢复MongoDB数据(除了system.profile集合)的权限
root 超级账号,超级权限

3.2 单实例环境

目标:对单实例的 MongoDB 服务开启安全认证,这里的单实例指的是未开启副本集或分片的 MongoDB 实例

   增加 MongoDB 的单实例的安全认证功能,可以在服务搭建的时候直接添加,也可以在之前搭建好的服务上添加。

   本文使用之前搭建好的服务,Linux 安装 MongoDB 服务可参考本系列《瑞_MongoDB的2.4.2 Linux安装MongoDB》,因此,需要先停止之前的服务(如果你是第一次接触本系列文章,则可跳过关闭服务步骤),如下一小节进行关闭操作

3.2.1 关闭已开启的服务(可选)

   由于本系列之前文章的操作,运行了多个 MongoDB 服务,现在需要关闭已开启的服务,执行ps -ef | grep mongo命令查看 mongo 服务,并执行kill -2 PID PID PID命令杀死 MongoDB 进程,如下图所示

在这里插入图片描述

关闭已开启的 mongo 服务有以下两种方式,上例使用的是方式2️⃣,实际操作时建议使用方式1️⃣

  方法1️⃣ 通过 mongo 客户端中的 shutdownServer 命令来标准关闭服务

# 客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行
/usr/local/mongodb/bin/mongo --host=192.168.133.131 --port=27017
# 切换到admin库
use admin
# 关闭服务
db.shutdownServer()

  显示如下图所示,即成功关闭服务

在这里插入图片描述

  方法2️⃣ 通过系统的kill命令直接杀死 MongoDB 进程,快速关闭服务(快速,简单,但数据可能会出错

# 查询 MongoDB 服务进程PID
ps -ef | grep mongod
# 使用 kill -2 或 kill -9 杀死 MongoDB 服务进程(kill -2 会尝试以优雅的方式终止进程,并允许进程执行清理工作;而 kill -9 则会立即停止进程的运行,不考虑进程的优雅结束或释放资源)
kill -2 3028

在这里插入图片描述

【补充】

  如果一旦数据损坏,可以进行如下操作⬇️
  1️⃣ 删除lock文件:rm -f /usr/local/mongodb/single/data/db/*.lock
  2️⃣ 修复数据:/usr/local/mongodb/bin/mongod --repair --dbpath=/usr/local/mongodb/single/data/db/

3.2.2 添加用户和权限

单实例环境即 Linux 安装 MongoDB 服务,可参考本系列《瑞_MongoDB的2.4.2 Linux安装MongoDB》

  1️⃣ 按照普通无授权认证的配置,来配置服务端的配置文件/usr/local/mongodb/single/mongod.conf(复用之前的文章)

systemLog:
 # MongoDB发送所有日志输出的目标指定为文件
 # The path of the log file to which mongod or mongos should send all diagnostic logging information
 destination: file
 # mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
 path: "/usr/local/mongodb/single/log/mongod.log"
 # 当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
 logAppend: true
storage:
 # mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
 ##The directory where the mongod instance stores its data.Default Value is "/data/db".
 dbPath: "/usr/local/mongodb/single/data/db"
 journal:
  # 启用或禁用持久性日志以确保数据文件保持有效和可恢复。
  enabled: true
processManagement:
 # 启用在后台运行mongos或mongod进程的守护进程模式。
 fork: true
net:
 # 服务实例绑定的IP,默认是localhost
 bindIp: localhost,192.168.133.131
 # bindIp
 # 绑定的端口,默认是27017
 port: 27017

  2️⃣ 按之前未开启认证的方式(不添加--auth参数)来启动 MongoDB 服务

建议:在操作用户时,启动 mongod 服务时尽量不要开启授权,尽量使用配置文件方式代替参数启动

/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/single/mongod.conf
# 或
/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/single/mongod.conf

在这里插入图片描述

  3️⃣ 使用 Mongo 客户端登录

/usr/local/mongodb/bin/mongo --host=192.168.133.131 --port=27017

在这里插入图片描述

  4️⃣ 创建两个管理员用户,一个是系统的超级管理员myroot,一个是 admin 库的管理用户myadmin

# 查看数据库
> show dbs
admin      0.000GB
articledb  0.000GB
config     0.000GB
local      0.000GB
rayTest    0.000GB
# 切换到admin库
> use admin
switched to db admin
# 确认已切换到admin库
> db
admin
# 创建系统超级用户 myroot,设置密码123456,设置角色root
# > db.createUser({user:"myroot",pwd:"123456",roles:[ { "role" : "root", "db" :"admin" } ]})
# 或
> db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: { "user" : "myroot", "roles" : [ "root" ] }
# 创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理
> db.createUser({user:"myadmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
        "user" : "myadmin",
        "roles" : [
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}
# 查看已经创建了的用户的情况
> db.system.users.find()
{ "_id" : "admin.myroot", "userId" : UUID("ebcc96af-56be-40c4-accc-122fd5c8d0f6"), "user" : "myroot", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "pSQvpKqaguK16dOjQh/WUw==", "storedKey" : "NLtcyxvrm6csgvwudvnP3rxvS0g=", "serverKey" : "j0ONQ+1Ny/ZEmEhHkQHo/n4B7X0=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "JxSQhmOUBLkyg4xuIi4uxqIaPMxzrDOHWRc16A==", "storedKey" : "05U7LHA/43k5AlkMxRp81F9TThdsMgnUE0k+iCGI9tM=", "serverKey" : "Q2iijziAWotJ70bi/a4Njt5FssDu9yXBsz3515LxOQA=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }
{ "_id" : "admin.myadmin", "userId" : UUID("653e4032-8b5c-44d3-a0e2-f55474f44472"), "user" : "myadmin", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "Cl79GxxPiwF4viOKiwcmBA==", "storedKey" : "EbCqEejCEIUPWiMuYMVFJxfluBc=", "serverKey" : "VVyZc3Q7EyDl/MSW2xPzOjzNluE=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "/O+Ni20QBWeWYScYlO5Zbp685vHee+e/kCDsHA==", "storedKey" : "9ajUY9VGDn6wduIM8+Wt3r6UZAfr/OIkPDUVoqnEN2Y=", "serverKey" : "EEZOLSJGO4FqdT+WWrn3cS182rcgzfS5VfYgYTG6DAQ=" } }, "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }
# 删除用户myadmin
> db.dropUser("myadmin")
true
# 确认用户myadmin被删除
> db.system.users.find()
{ "_id" : "admin.myroot", "userId" : UUID("ebcc96af-56be-40c4-accc-122fd5c8d0f6"), "user" : "myroot", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "pSQvpKqaguK16dOjQh/WUw==", "storedKey" : "NLtcyxvrm6csgvwudvnP3rxvS0g=", "serverKey" : "j0ONQ+1Ny/ZEmEhHkQHo/n4B7X0=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "JxSQhmOUBLkyg4xuIi4uxqIaPMxzrDOHWRc16A==", "storedKey" : "05U7LHA/43k5AlkMxRp81F9TThdsMgnUE0k+iCGI9tM=", "serverKey" : "Q2iijziAWotJ70bi/a4Njt5FssDu9yXBsz3515LxOQA=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }
>
# 修改密码
> db.changeUserPassword("myroot", "123456")

在这里插入图片描述

角色userAdminAnyDatabase说明:在指定数据库创建和修改用户(除了数据库 config 和 local 之外)

  【提示】

  1. 本案例创建了两个用户,分别对应超管和专门用来管理用户的角色,事实上,你只需要一个用户即可。如果你对安全要求很高,防止超管泄漏,则不要创建超管用户。
  2. 和其它数据库(如 MySQL)一样,权限的管理都差不多一样,也是将用户和权限信息保存到数据库对应的表中。MongoDB 存储所有的用户信息在 admin 数据库的集合system.users中,保存用户名、密码和数据库信息。
  3. 如果不指定数据库,则创建的指定的权限的用户在所有的数据库上有效,如{role:"userAdminAnyDatabase", db:""}

  5️⃣ 认证测试,测试添加的用户是否正确

# 切换到admin
> use admin
# 密码故意输错
> db.auth("myroot","ray")
Error: Authentication failed.
0
# 输入正确的密码
> db.auth("myroot","123456")
1

在这里插入图片描述

瑞:在一般情况下,不建议直接使用超级管理员用户去操作,因为相当危险,所以一般情况下还会创建一个权限相对小的普通用户,并使用普通用户进行操作某个具体的数据库

  6️⃣ 创建普通用户

  创建普通用户可以在没有开启认证的时候添加,也可以在开启认证之后添加,但开启认证之后,必须使用有操作 admin 库的用户登录认证后才能操作。底层都是将用户信息保存在了 admin 数据库的集合system.users中。

# 创建(切换)将来要操作的数据库articledb,如果你没有的话请先创建该数据库
> use articledb
switched to db articledb
# 创建用户,拥有articledb数据库的读写权限readWrite,密码是123456
> db.createUser({user: "ray", pwd: "123456", roles: [{ role: "readWrite", db:"articledb" }]})
Successfully added user: {
        "user" : "ray",
        "roles" : [
                {
                        "role" : "readWrite",
                        "db" : "articledb"
                }
        ]
}
# 测试是否可用
> db.auth("ray","123456")
1

在这里插入图片描述

提示:如果开启了认证后,登录的客户端的用户必须使用 admin 库的角色,如拥有 root 角色的 myadmin 用户,再通过 myadmin 用户去创建其他角色的用户

3.2.3 服务端开启认证和客户端连接登录

由于服务端启动的时候没有添加开启认证的操作命令,所以当前不用登录用户依然可以直接操作。如果想要登录用户后才能进行操作,则需要进行以下操作

  1️⃣ 关闭已经启动的服务。执行ps -ef | grep mongo命令查看 mongo 服务,并执行kill -2 PID命令杀死 MongoDB 进程,如下图所示

在这里插入图片描述

  2️⃣ 以服务端开启认证的方式启动服务

  有两种方式开启权限认证启动服务:一种是参数方式,一种是配置文件方式

3.2.3.1 服务端开启认证——参数方式

  在启动时指定参数--auth

/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/single/mongod.conf --auth

瑞:以参数方式开启认证在开发当中很少使用。因为这种方式必须要每次启动的时候带上参数,如果哪一次忘了,就相当于安全认证不起作用,非常不安全,操作不严谨。

3.2.3.2 服务端开启认证——配置文件方式(推荐)

  在mongod.conf配置文件中加入开启授权认证的配置

  使用vi /usr/local/mongodb/single/mongod.conf编辑配置文件

瑞:指令使用:按a切换到输入状态,将下面配置文件内容复制过去,然后按esc输入:wq保存文件并退出

  添加如下配置

security:
 # 开启授权认证
 authorization: enabled

在这里插入图片描述

  启动时可不加--auth参数

/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/single/mongod.conf

  此时连接客户端,发现可以正常连接,但无操作权限,如下图执行show dbs后啥也看不见

/usr/local/mongodb/bin/mongo --host=192.168.133.131 --port=27017

在这里插入图片描述

  开启认证后再登录,打印的日志会有所减少。相关操作需要认证后才可以执行成功。


  开启了认证的情况下的客户端登录,有两种认证方式

  1️⃣ 先连接登录,再在 mongo shell 中认证

  2️⃣ 登录时直接认证

3.2.4.3 客户端连接登录——先连接再认证

  连接客户端

/usr/local/mongodb/bin/mongo --host=192.168.133.131 --port=27017

  再在相应的数据库中使用 mongo shell 进行认证

  【示例1】使用use admin数据库进行 myroot 角色(超级管理员)认证

[root@localhost ~]# /usr/local/mongodb/bin/mongo --host=192.168.133.131 --port=27017
MongoDB shell version v4.0.10
connecting to: mongodb://192.168.133.131:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("98afb9e9-b56c-4f3e-a9df-8f573871e252") }
MongoDB server version: 4.0.10
> show dbs
>
> use admin
switched to db admin
> db.auth("myroot",
上一篇:uniapp引入 uview( HBuilder 和 npm 两种安装方式) #按需引入-方式一、HBuilder 安装 uview


下一篇:关闭Ubuntu烦人的apport