Linux 中使用 docker-compose 部署 MongoDB 6 以上版本副本集及配置 SSL / TLS 协议

news2024/12/29 11:09:32

一、准备环境

MongoDB 副本集部署至少 3 个节点(奇数节点),为了保障数据安全性,可考虑将 MongoDB 节点分布在不同的主机上,本示例使用一台主机部署 3 个 MongoDB示例。

1、创建 MongoDB 集群数据相关目录
# 创建 MongoDB 集群根目录及 3 个子目录
mkdir -p /mongodb-cluster/{mongo01, mongo02, mongo03}

# 创建 3 个 MongoDB 下子目录
mkdir -p /mongodb-cluster/mongo01/{certs, configdb, data, db, logs}
mkdir -p /mongodb-cluster/mongo02/{certs, configdb, data, db, logs}
mkdir -p /mongodb-cluster/mongo03/{certs, configdb, data, db, logs}

# 准备给上面 logs 目录赋予操作权限(注意:未赋予 logs 目录权限,运行会报错)
chmod 777 /mongodb-cluster/mongo01/logs
chmod 777 /mongodb-cluster/mongo01/logs
chmod 777 /mongodb-cluster/mongo01/logs
2、生成服务端、集群内部成员、客户端相关证书

由于部署 MongoDB 副本集,集群成员和客户端需要与服务端进行认证通信,保证通信的可靠性,目前 MongoDB 提供如下方式进行通信认证:

security.clusterAuthMode

类型:String

默认值:keyFile

用于集群认证的方式。如果您使用 内部 x.509 身份验证,请在此处指定。此选项可以具有以下值之一:

价值描述
keyFile使用密钥文件进行身份验证。只接受密钥文件。
sendKeyFile用于滚动升级目的。发送密钥文件进行身份验证,但可以同时接受密钥文件和 x.509 证书。
sendX509用于滚动升级目的。发送 x.509 证书进行身份验证,但可以同时接受密钥文件和 x.509 证书。
x509受到推崇的。发送 x.509 证书进行身份验证并仅接受 x.509 证书。

通过上面说的几种通信认证方式,默认 keyFile 密钥文件方式认证,这种方式也是最简单的,只能用于测试环境下使用,通常我们为了服务端和客户端通信安全,则会使用 x509 认证方式,下面我就使用 x509 方式进行配置 TLS 协议认证。

注意:因为大多数 CA 高可靠证书,都是由第三方厂商提供,需要收费的。(这里自己通过 openssl 生成自签名 CA 证书,实现通信认证)。

自签名证书生成
(1)、生成根证书 ca.pem
# 生成 CA 私钥(ca.key)(不加密)
openssl genrsa -out ca.key 2048
# 生成 CA 证书签名请求(ca.csr)
# 每个集群成员证书和服务端证书中必须具有相同的 O、OU 和 DC (具体可参考:https://www.mongodb.com/docs/manual/core/security-x.509)
openssl req -new -key ca.key -out ca.csr # 注意:在执行这条命令后,需要按要求填写内容,最后面两项可选,密码可不填
# 生成自签名 CA 证书(ca.pem), 直接免费 100 年有效
openssl x509 -req -days 36500 -in ca.csr -signkey ca.key -out ca.pem
(2)、生成服务端证书 server.pem
# 生成 server 端私钥 (server.key) 不加密
openssl genrsa -out server.key 2048
# 生成 server 证书签名请求 (server.csr)
openssl req -new -key server.key -out server.csr # 注意:输入内容和上面的保持一致
# 使用 ca 证书签署服务端 csr 以生成服务端证书 (server.crt) 
openssl ca -days 36500 -in server.csr -out server.crt -cert ca.pem -keyfile ca.key # 注意:执行这条命令会出现 2 处错误,按如下方式解决,再执行

# 第一个错误
/etc/pki/CA/index.txt No such file or directory
# 直接执行如下命令
touch /etc/pki/CA/index.txt

# 第二个错误
/etc/pki/CA/serial No such file or directory
# 直接执行如下命令后,文件里面输入数字,如:01,再保存 
vim /etc/pki/CA/serial

# 解决完问题后,再执行上面的命令对证书进行签名(后面输入区域,都输入 y 即可)
openssl ca -days 36500 -in server.csr -out server.crt -cert ca.pem -keyfile ca.key

# 删掉 server.crt 中的 certificate 信息,保留加密证书内容即可

# 合并证书和私钥成 PEM 文件, 构建命令如下:
cat server.key server.crt > server.pem

# 再验证自签名证书是否成功(返回,OK 代表成功)
openssl verify -CAfile ca.pem server.pem
(3)、生成客户端证书 client.pem(流程和服务端一样,只是稍微改了下名)
# 生成 client 端私钥 (client.key) 不加密
openssl genrsa -out client.key 2048
# 生成 client 证书签名请求 (client.csr)
openssl req -new -key client.key -out client.csr # 注意:输入内容和上面的保持一致
# 使用 ca 证书签署客户端 csr 以生成客户端证书 (client.crt) 
openssl ca -days 36500 -in client.csr -out client.crt -cert ca.pem -keyfile ca.key 
# 注意:这条命令运行后会出现 2 个错误,是因为之前签署了服务端证书,前面自己创建的两个文件已存在,所以,需要把 /etc/pki/CA 下的 index.txt 和 serial 文件删除,再重新执行该命令,再解决前面提到的错误就可以了

# 解决完问题后,再执行上面的命令对证书进行签名(后面输入区域,都输入 y 即可)
openssl ca -days 36500 -in client.csr -out client.crt -cert ca.pem -keyfile ca.key

# 删掉 client.crt 中的 certificate 信息,保留加密证书内容即可

# 合并证书和私钥成 PEM 文件, 构建命令如下:

cat client.key client.crt > client.pem

# 再验证自签名证书是否成功(返回,OK 代表成功)

openssl verify -CAfile ca.pem client.pem
(4)、生成服务端成员证书 cluster.pem(流程和客户端一样,只是稍微改了下名),这里就不写了。
二、创建 MongoDB 集群的核心文件并配置
1、将前面生成的所有 PEM 证书 copy 到各 mongo 下 certs 目录中
# 执行如下命令
cp 文件路径 /mongodb-cluster/mongo01/certs
cp 文件路径 /mongodb-cluster/mongo02/certs
cp 文件路径 /mongodb-cluster/mongo03/certs

# 注意:确认包含如下 4 个文件
ca.pem  client.pem  cluster.pem  server.pem
2、创建各示例的 mongod.conf 核心文件并配置
# 创建 mongod.conf 配置文件
touch /mongodb-cluster/mongo01/configdb/mongod.conf
touch /mongodb-cluster/mongo02/configdb/mongod.conf
touch /mongodb-cluster/mongo03/configdb/mongod.conf

在各个示例 mongod.conf 文件里面写入如下内容:

注意:每个示例配置的端口号 27017 可不改,因为这是容器里面的端口,也可以根据需求更改

net:
  bindIp: 0.0.0.0 # 配置允许所有主机连接,默认只能本地连接
  port: 27017 # 因为在一台主机中,三个示例的端口对应 27017、27018、27019 或者 其它不冲突的端口也可以
  tls:
    CAFile: /data/certs/ca.pem # 容器内映射路径,在本配置文件下,所有配置的路径都是容器路径,作为宿主机的映射路径
    certificateKeyFile: /data/certs/server.pem
    clusterFile: /data/certs/cluster.pem
    allowInvalidCertificates: true
    allowInvalidHostnames: true
    allowConnectionsWithoutCertificates: true
    mode: requireTLS # MongoDB 认证模型,使用 TLS, 默认不使用 TLS
processManagement:
  fork: false  # 是否开启以守护进程模式在后台运行
replication:
  replSetName: rs0 # 副本集名称
security:
  clusterAuthMode: x509  # 集群安全认证模型使用 x509
  authorization: enabled # 启用基于 RBAC 权限访问操作
storage:
  engine: wiredTiger # 6.0 以上版本默认使用 wiredTiger 引擎, 从 4.2 版本移除了 MMAPv1 存储引擎
  # 具体参数配置可参考:https://www.mongodb.com/docs/manual/reference/configuration-options/#storage-options
  wiredTiger:
      engineConfig:
         cacheSizeGB: 0.5
         journalCompressor: zstd # 默认使用 snappy 方式压缩,这里使用 zstd 高效压缩方式 
         zstdCompressionLevel: 6 # 设置 zstd 方式压缩级别,默认为 6, 范围:1-22, 级别越高,压缩率也越高
      collectionConfig:
         blockCompressor: zstd
      indexConfig:
         prefixCompression: true # 开启索引前缀压缩
systemLog:
  destination: file  # 日志存储方式为 file, 默认为 system_log 输出方式
  logAppend: true # 是否开启日志追加,默认为 false
  path: /data/logs/mongod.log  # 日志存储路径
3、创建 docker-compose.yaml 部署文件
# 创建 docker-compose.yaml 文件
touch /mongodb-cluster/docker-compose.yaml

在 docker-compose.yaml 文件里面写入如下内容:

version: "3.9"
services:
  mongo01:
    container_name: mongo01 # 容器名
    image: mongo # 拉取的镜像,未指定版本,默认 latest 最新版
    ports:
      - 27017:27017 # 容器内端口号映射,如:宿主机端口:容器端口
    environment:
      TZ: Asia/Shanghai # 配置时区信息
    volumes:
      - /etc/localtime:/etc/localtime
      - ./mongo01/db:/data/db  # 挂载数据 db 目录, 默认容器内是 /data/db, 因为前面没配置 storage.dbPath 路径
      - ./mongo01/configdb:/data/configdb # 挂载前面的 mongod.conf 配置文件, 默认容器内 /data/configdb
      - ./mongo01/certs:/data/certs # 这个是自定义挂载的 TLS 相关配置
      - ./mongo01/logs:/data/logs # 挂载容器内日志目录
    restart: always
    command: mongod --replSet rs0 -f /data/configdb/mongod.conf # 容器启动后,需要执行的命令
    networks:
      - mongo_network # 使用自定义网络
  mongo02:
    container_name: mongo02
    image: mongo
    ports:
      - 27018:27018
    environment:
      TZ: Asia/Shanghai
    volumes:
      - /etc/localtime:/etc/localtime
      - ./mongo02/db:/data/db
      - ./mongo02/configdb:/data/configdb
      - ./mongo02/certs:/data/certs
      - ./mongo02/logs:/data/logs
    restart: always
    command: mongod --replSet rs0 -f /data/configdb/mongod.conf
    networks:
      - mongo_network
  mongo03:
    container_name: mongo03
    image: mongo
    ports:
      - 27019:27019
    environment:
      TZ: Asia/Shanghai
    volumes:
      - /etc/localtime:/etc/localtime
      - ./mongo03/db:/data/db
      - ./mongo03/configdb:/data/configdb
      - ./mongo03/certs:/data/certs
      - ./mongo03/logs:/data/logs
    restart: always
    command: mongod --replSet rs0 -f /data/configdb/mongod.conf
    networks:
      - mongo_network 
# 创建容器网络
networks:
  mongo_network:
    name: mongo_network
三、启动 MongoDB 集群并初始化及配置客户端认证
1、启动 MongoDB 集群
# 使用如下命令启动
docker-compose up -d

# 查看容器日志(未输出错误信息或没有日志信息则成功)
docker logs -f mongo01 -n 100

# 需要查看具体容器输出日志,可在前面配置的日志目录中查看
2、进入其中一个容器,进行集群初始化(在 3 个示例中随便进入哪个都行,但只要对一个容器内做初始化即可)
# 进入容器
docker exec -it mongo01 /bin/bash

# 进入 mongo 服务端, 需要指定客户端签名证书(注意:MongoDB 6.0 以上版本, 客户端命令为 mongosh)
# --host 是你前面生成签名证书时填的 Common Name 的值, 简称:CN, 可根据需求填写, 配置写错了, 会导致连接有问题
# 具体可参考:https://www.mongodb.com/docs/manual/core/security-x.509
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017

# 进入 mongo 服务端后,需要先创建一个用户并赋予权限,不然,操作会出错
# 执行如下命令, 创建用户(注意:在执行命令之前,需要先 use admin 切换到 admin 库)
# 具体可参考:https://www.mongodb.com/docs/manual/reference/built-in-roles
db.createUser(
	{
		user:"root", # 用户名
		pwd:"123456", # 密码
		roles:[{role:"root",db:"admin"}] # 可配置多个角色, root 角色表示拥有超级用户角色, 作用于 admin 数据库
	}
);

# 使用刚创建的 root 用户登录认证, 进行执行接下来的操作
# root 用户登录认证(注意:需要在 admin 库执行, use admin 切换命令)
db.auth("root", "123456")
# 执行如下命令,初始化 mongo 副本集, 返回 ok 代表已初始化成功
rs.initiate( {
   _id : "rs0", # 副本集名称
   members: [
      { _id: 0, host: "172.21.0.3:27017" }, # mongo 实例 IP, 配置成宿主机 IP 即可
      { _id: 1, host: "172.21.0.4:27018" },
      { _id: 2, host: "172.21.0.2:27019" }
   ]
})

# 再执行 rs.status() 命令查看集群状态, 也可通过 rs.conf() 命令查看集群节点配置信息
3、使用 x509 证书对客户端进行身份验证

将 x.509 证书添加 subject 为用户要使用客户端证书进行身份验证,您必须首先将来自客户端证书的值 subject 作为 MongoDB 用户添加到 $external 数据库中。每个唯一的 x.509 客户端证书对应一个 MongoDB 用户。您不能使用单个客户端证书对多个 MongoDB 用户进行身份验证。具体可参考:https://www.mongodb.com/docs/manual/tutorial/configure-x509-client-authentication

# 在宿主机上执行如下命令(注意:client.pem 是前面生成的客户端签名证书)
# 使用以下命令从客户端证书中检索 RFC2253 格式化:subject
openssl x509 -in client.pem -inform PEM -subject -nameopt RFC2253

# 该命令会返回 subject 字符串和证书:
subject= CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry
-----BEGIN CERTIFICATE-----
# ...
-----END CERTIFICATE-----

# 添加作为用户 RFC2253 的合规值, subject 根据需要省略空格。
# 首先, 需要进入主节点容器内 mongo 服务端, 执行如下操作:
# 进入容器
docker exec -it mongo01 /bin/bash
# 进入 mongo 服务端
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017
# 需要切换为 admin 库, use admin 命令, 再执行 root 用户认证命令
db.auth("root", "123456")

# 以下添加一个用户并授予该用户 readWrite 在数据库中的角色 test 和 root 角色:
db.getSiblingDB("$external").runCommand(
  {
    createUser: "1491140482@qq.com,CN=localhost,OU=BND,O=BN,ST=JS,C=CN",
    roles: [
         { role: "readWrite", db: "test" },
         { role: "root", db: "admin" }
    ],
    writeConcern: { w: "majority" , wtimeout: 5000 }
  }
)

# 客户端连接后认证, 使用如下命令:
db.getSiblingDB("$external").auth(
  {
    mechanism: "MONGODB-X509"
  }
)

# 客户端连接期间进行身份认证, 执行如下命令:
mongosh --tls --tlsCertificateKeyFile client.pem \
    --tlsCAFile ca.pem \
    --authenticationDatabase '$external' \
    --authenticationMechanism MONGODB-X509
四、测试 MongoDB 副本集数据同步
1、在主容器里面写入数据,再进入从容器里面查询
# 进入主容器
docker exec -it mongo01 /bin/bash

# 进入 mongo 服务端
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27017

# 切换 admin 库, use admin 命令
use admin
# db 认证
db.auth("root", "123456")
# 插入一条数据在 test 库中, 文档不存在会自动创建
db.test.insertOne( { x: 1 } )
# 查询数据
db.test.find()

# 然后, 下面再从容器里面查看是否有数据
docker exec -it mongo02 /bin/bash # 进入从容器
mongosh --tls --tlsCertificateKeyFile /data/certs/client.pem --tlsCAFile /data/certs/ca.pem --host localhost --port 27018 # 进入 mongo 服务端

use admin # 切换 admin
db.auth("root", "123456") # db 认证
db.test.find() # 查询数据, 这里会出错, 默认是强制读主的, 不允许从节点进行读操作, 只能作为备份和高可用, 需要显示开启读操作

# 解决方案, 如下:
rs.secondaryOk() or db.getMongo().setReadPref("primaryPreferred")
# 这两条命令执行其中一条即可, 再次执行 db.test.find() 查询数据就能读到了
# 注意:rs.secondaryOk() 命令在新版本被弃用了, 建议使用:db.getMongo().setReadPref("primaryPreferred") 命令进行操作
五、使用 Navicat 客户端连接 MongoDB 副本集
1、将服务器中的客户端签名证书和 CA 证书导出

包含:ca.pem 和 client.pem 两个文件

2、进行使用 Navicat 连接,如下图:

在这里插入图片描述

继续配置启用 SSL 模式进行连接,如下图:

在这里插入图片描述

然后,点击测试连接,即可连接成功。

六、其它
1、添加副本,在登录到主节点下输入
rs.add("ip:port")
2、删除副本
rs.remove("ip:port")
3、新增仲裁节点
rs.addArb("ip:port")
4、修改副本集里面的节点 IP
# 声明 cfg 变量
cfg = rs.conf()

# 执行修改各节点的 IP
cfg.members[0].host = "192.xxx.xxx.121:27017"
cfg.members[1].host = "192.xxx.xxx.121:27018"
cfg.members[2].host = "192.xxx.xxx.121:27019"

# 重新加载配置
rs.reconfig(cfg)

# 查询副本集状态
rs.status()

# 查询副本集配置
rs.conf()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据|大数据基础(概念向)

目录 📚大数据概念 🐇常见数据存储单位 🐇大数据的特点(5V) 🐇大数据 VS 数据库 🌟数据库 🌟大数据 📚大数据业务分析基本步骤 🐇收集数据 &#x1f4…

RockerMQ简介和单节点部署

目录一、RockerMQ简介二、Linux中单节点部署1、准备工作2、下载和解压3、修改初始内存4、启动5、查看进程6、发送接收消息测试7、关闭三、控制台的安装与启动(可视化页面)1、修改配置(1)修改端口号(2)指定RocketMQ的name server地…

企业知识管理常见的误区及解决方案

在企业信息化的背景下,越来越多的首席信息官(CIO)承担着促进组织知识管理实施的责任。然而,从实践的角度来看,虽然我国大多数知识管理实施项目都取得了一定的成果,但与预期有很大的不同,甚至许多…

这18个被全网吹爆了的AI绘画工具,分享给你!

伴随着ChatGPT的横空出世,一场史无前例的科技革命正在拉开序幕。 AI 拥有强大的信息储备和数据处理能力,无论是速度、质量,还是思维模式,都让人只呼不得了!写代码、造论文丝毫不在话下,甚至还能和你探讨茶…

当 Amazon Lambda 遇上 Apache APISIX 可以擦出什么火花?

本文首先介绍了什么是 Serverless,以及为什么需要 Serverless;其次,讲述了一个好的网关在 Serverless 架构下的重要性,而 APISIX 就是这样的一个网关;最后,本文重点介绍了 APISIX 中的 Serverless 类型的插…

您应该知道的几个安卓照片恢复应用程序

如果您不小心删除了存储在 Android 手机上的一些重要照片,该怎么办?如果您之前已创建备份,则只需将备份文件中的照片恢复到您的手机即可。但数据丢失往往是突然发生的,可能是由于误操作、恢复出厂设置或物理损坏等原因造成的。如果…

高性能低功耗4口高速USB2.0 HUB NS1.1S 兼容FE1.1

NS1.1S是一款高性能、低功耗4口高速 USB2.0 HUB 控制器,上行端口兼容高速 480MHz和全速12MHz两种模式,4个下行端口兼容高速480MHz、全速12MHz、低速1.5MHz三种模式。 NS1.1S采用状态机单事务处理架构,而非单片机架构,多个事务缓冲…

Java无法通过形参设置为null改变实参

文章目录问题描述问题例子问题分析问题描述 在实际业务开发过程中,我们会把实参传递给形参,在方法体内对引用对象进行构建或者修改,从而改变实参,因为对形参对象属性修改时,实参对象也会随着改变,详情请看&…

《Java核心技术》笔记——第六章

文章目录CH6.接口、lambda表达式与内部类1.接口基本2.常用接口3.lambda表达式4.内部类5.服务加载器与代理前章: 第三章~第五章的学习笔记CH6.接口、lambda表达式与内部类 1.接口基本 接口基本——interface声明,方法无需指明public(默认都是…

基于node vue的电商系统 mongodb express框架

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 基于node vue的电商系统 mongodb express框架前言技术栈基本功能普通用户管理员一、运行截图?二、使用步骤1.前端main.js2.后端admin路由前言 技术栈 本项目采用…

行业分析| OA系统中的实时通讯

前言 当前实时通讯市场中有QQ、MSN、网络电话以及视频会议等,同时也有各个互联网巨头推出的基于自身平台的实时通讯工具,比如,百度hi,淘宝旺旺等,相对而言其与纯粹的实时通讯工具不同,基于自身平台的实时通…

点击化学 PEG 试剂1858242-47-3,Propargyl丙炔基-PEG1-乙酸活性酯

Propargyl-PEG1-Acetic acid-NHS ester,丙炔基-聚乙二醇-乙酸琥珀酰亚胺酯,丙炔基-PEG1-乙酸活性酯,丙炔基-PEG1-乙酸-NHS 酯产品规格:1.CAS号:1858242-47-32.分子式:C9H9NO53.分子量:211.174.包…

ChatGPT商业前景如何?人工智能未来会如何发展?

ChatGPT不仅在互联网和多个行业引发人们的关注,在投资界还掀起了机构对人工智能领域的投资热潮。人工智能聊天程序ChatGPT在去年11月亮相之后,在推出仅两个月后,今年1月份的月活用户已达到了1亿,成为史上增长最快的消费者应用程序…

【selenium 自动化测试】如何搭建自动化测试环境,搭建环境过程应该注意的问题

最近也有很多人私下问我,selenium学习难吗,基础入门的学习内容很多是3以前的版本资料,对于有基础的人来说,3到4的差别虽然有,但是不足以影响自己,但是对于没有学过的人来说,通过资料再到自己写的…

UA-DETRAC数据集转YOLO格式

一: 数据集下载 链接:(后续添加) 二: 处理标注文件 先处理标注文件,UA-DETRAC提供的标注文件格式是VOC格式,需要先转为XML格式,然后再将每个XML文件转为YOLO文件。 下面提供两个代…

git 本地新建分支并进行合并

由于新的要求 不允许在线上直接clone下的git分支进行开发,只能本地新建分支再往线上分支合并远程库clone到本地库 git clone 需要下载的git地址注意我下载下来的是dev分支 根据实际情况进行分析git clone https://gitee.com/hello.git本地创建新的分支 git checkout…

Keil编译头文件iec_std_functions.h错误解决

Keil 编译IEC61131-3库,头文件,大量出现以下错误; cast to type ? is not allowed compiling resource1.c... ..\PLC\rts\matiec\lib\C\iec_std_functions.h(192): error: #119: cast to type "TIME" is not allowed…

公派访问学者的申请条件

知识人网海外访问学者申请老师为大家分享公派访问学者申请的基本条件以及哪些人员的申请是暂不受理的,供大家参考:一、 申请人基本条件:1.热爱社会主义祖国,具有良好的思想品德和政治素质,无违法违纪记录。2.具有良好专…

Java常见问题总结五

1、垃圾回收方式 SerialGC(串行垃圾回收):为单线程环境设计且使用一个线程进行垃圾回收,会暂停所有的用户线程。 ParalleGC(并行垃圾回收):对过GC线程并行工作,此时用户线程是停止的。 ConcMarkSweep(CMS):用户线程和GC线程同时执…

Tatuk GIS Developer Kernel for .NET 最新Crack

Tatuk GIS Developer Kernel for .NET 是一个地理SDK,它是受控代码和 .NET GIS SDK,用于为用户 Windows 操作系统创建 GIS 专业软件的过程。它被认为是一个完全用于 Win Forms 的 .NET CIL,WPF 的框架是为 C# 以及 VB.NET、VC、oxygen 以及最…