【docker重要】docker的下载安装、容器与docker、Dockerfile的写法、docker-compose、Harbor

[1 容器](#1 容器)
[2 docker](#2 docker)
[3 Dockerfile的写法](#3 Dockerfile的写法)
[4 docker-compose](#4 docker-compose)
[5 Harbor](#5 Harbor)

1 容器

bash 复制代码
1、容器
	1、什么是容器?
		用镜像启动的一个对外可以提供服务的进程即为容器。
		1、容器的本质是进程
		2、容器是由镜像创建的,跟镜像中保存的内容完全一致。
		3、容器与容器之间是完全隔离的(可以理解为国中之国)
		4、每一个容器都需要做的是尽量保持自己的体积足够的小。
	2、容器的生命周期
		容器之内必须至少有一个进程运行在前台。如果一个进程都没有的话,那么此容器就相当于完成了它的声明周期。
	
	3、容器的基本使用
		0、查看本机容器列表

			docker ps # 查看本机容器列表
			[root@docker ~]# docker ps
	CONTAINER ID   IMAGE     COMMAND      CREATED  		 STATUS    PORTS     NAMES
	容器的ID		   镜像    容器的启动命令  容器的创建时间   容器的状态 容器的端口 容器的名称
			
			STATUS:
				UP : 		启动状态
				Exited :	停止状态
				Created :  容器已经创建,当时没有启动
			
			参数:

				-a : 查看系统上所有的容器(包含未启动)
				-q : 只显示容器ID

		1、创建容器
			docker run		# docker run是创建并启动容器
			docker create 	# docker create是创建容器
			
			docker create 中的参数跟 docker run 类似 主要其本身的功能不同。
			
			
			docker run 
			
				格式:
				
					docker run [参数] 镜像  [启动命令]
					
					参数和启动命令可以省略。
				
				容器的启动流程:
				
					①:查看本地是否存在正在使用的镜像。
					②:如果本地不存在使用的镜像,则去仓库下载
					③:根据镜像来启动容器
				
				参数:
				
					-d 	: 以守护进程方式运行容器。
					--name : 自定义容器的名称
					
						[root@docker ~]# docker run -d --name nginx  nginx
					
					-p 		: 指定一个端口映射
					-P		:随机一个端口映射
					-i		: 打开标准输出
					-t		:创建一个终端
					-e		:在容器内部增加一个环境变量
					-h		: 指定一个主机名(默认的主机名是容器的缩写版ID )
					-v 		: 指定一个挂载卷(将宿主主机的目录挂载到容器内,从而实现文件互通)
			
			
					--network	: 链接一个网桥
					--link		: 链接一个容器
					
			
				在宿主主机上执行一个容器内部的命令
					docker exec [容器的ID或名称] [需要在容器内部执行的名]

					docker exec centos3 printenv 
			
				启动一个容器

					docker start [容器的名称|ID]
					[root@docker opt]# docker start centos6
		
		2、停止容器

			docker stop [容器的名称|ID]
		
		3、删除容器
			docker rm [容器的名称|ID]		
			docker rm -f [容器的ID或名称]
		
			案例:
				删除所有容器:docker rm -f $(docker ps -a -q)
		
		4、查看容器详细状态
			docker inspect [容器的ID或名称]
			案例:	
				要求判断指定容器是否正在运行?
					docker inspect -f '{{ .State.Running }}'  2d4f2e701fa8
		
		5、复制容器中的内容
			1、将容器内的文件复制到宿主主机
				docker cp [容器的ID]:[文件路径]  宿主主机路径
				docker cp ae1cde866e07:/root/init.sh /tmp/
				
			2、将宿主主机上的内容复制到容器
				docker cp [宿主主机路径] [容器的ID]:[文件路径]
				docker cp init.sh ae1cde866e07:/root/

		6、进入容器
			1、attach
				attach是进入容器,原理是将容器内的PID为1的进程开辟一个管道,链接到宿主主机。
				当在宿主主机上退出时,容器也随即退出(结束了生命周期)
			
			2、exec(推荐)
				exec其本质是在宿主主机上执行一个容器内的命令,但是加上-it参数,可以达到进入容器的效果,其原理是在容器内部新创建一个bash进程。所以当exec退出时不影响容器的正常运行。
				
				docker exec -it [容器的名称] [进入容器执行的命令]
			
			3、nsenter
				nsenter的原理是建立一个管道进程,链接到容器的内部。
				nsenter --target $( docker inspect -f '{{.State.Pid }}'  ) --mount --uts --ipc --net --pid
				[root@docker ~]# nsenter --target $( docker inspect -f '{{.State.Pid }}' centos ) --mount --uts --ipc --net --pid
		
			4、ssh的方式(及其不推荐)
				
			
		7、保存容器为镜像
			1、保存镜像(针对点是镜像)
				将镜像打包,发送到远程服务器		
				docker save # 将镜像保存成压缩包
					[root@docker ~]# docker save -o image.tar    nginx:latest   centos:latest
					[root@docker ~]# docker save > image.tar  nginx:latest   centos:latest
				
				docker load # 将镜像包导入本地镜像中
					[root@docker ~]# docker load -i image.tar 
					[root@docker ~]# docker load < image.tar 
			
			2、保存容器为镜像(针对点是容器)
				将容器打包成镜像
				1、将容器保存成镜像
					docker export
					
					docker export -o nginx.tar  modest_tharp
				
				2、将镜像包导入本地镜像中
					docker import 
					
					docker import nginx.tar nginx:v1
					
			
			3、保存容器
				将容器保存成本地镜像
				
				docker commit 
				
				docker commit -a "ShanHe" -m "这是一个弟弟" -p modest_tharp   nginx:v2

		8、查看容器的运行日志
			docker logs [容器的ID或名称]
			
				docker logs modest_tharp
				
				参数:
					-f : 持续监控
					
					docker logs -f modest_tharp
		
		9、暂停容器和重新运行容器
			容器将暂停服务
				docker pause modest_tharp
				
			重新运行容器,恢复提供和服务
				docker unpause modest_tharp
		
		10、容器的运行状态
		案例:
			用容器搭建一个小游戏
			1、代码(宿主主机)
			
			2、将目录映射到容器
			
			3、映射端口

				docker run -d --name mario -v /opt/html5-mario:/usr/share/nginx/html -p 8080:80  nginx
			
			4、浏览器测试

2 docker

bash 复制代码
1、Docker
	1、现有的互联网架构的劣势
		1000台虚拟机(CentOS 7)
			1、保证百分百不宕机
				CentOS 7  :  systemctl start mysqld
				CnetOS 6  : service mysqld start 

				CentOS 7  :  yum install mysql 
				Ubuntu 16 :  apt-get install mysql
				busybox   : apt install mysql 
			
			
			2、要求自动化(Ansible)
				service : 
					name: mysqld 
				yum:
					name: mysql
					when: 
				apt:
					name: mysql
					when 
			
			3、自动化运维(Python 操作 Ansible)
				
			4、如果我们的虚拟机不支持我们所需要安装的机器的时候?
			
			5、怎样保证快速稳定的部署并运行应用?
			需要一个提前打包好了的,拿来就能运行的应用,而且需要支持各种异构环境的服务变得尤为重要。
			镜像  --->  容器  --->  运行一个容器隔离的环境  --->  容器
			
	2、Docker简介
		docker是一个容器工具,包含docker中的三大概念:镜像、容器以及仓库。
		容器编排工具
			docker三剑客
			kubernetes    占领容器编排市场90% 

	3、安装Docker
		docker分为企业版和社区版
			企业版	: docker 
			社区版	: docker-ce 
				https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/
		
		# 安装依赖包
		yum install -y yum-utils device-mapper-persistent-data lvm2
		
		# 安装yum源
		yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

		sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
			
		# 安装 
		[root@localhost ~]# yum install docker-ce-19.03.9 -y

	
	4、镜像
		1、什么是镜像?
			用来启动容器的模板,镜像一般是存在镜像仓库中的。
			镜像仓库地址:https://hub.docker.com/search?type=image
			仓库分为三种:
				官方仓库:		hub.docker.com
				第三方仓库:	https://cr.console.aliyun.com/cn-hangzhou/instance/repositories
				自建仓库	:  haobor
				
		2、镜像的相关命令
			1、搜索镜像
				[root@localhost ~]# docker search centos
				NAME                              DESCRIPTION                                     STARS     OFFICIAL          AUTOMATED
				centos                            The official build of CentOS.                   6809      [OK]       
				ansible/centos7-ansible           Ansible on Centos7                              135                           [OK]
					镜像的名称						该镜像的介绍							镜像的收藏数  是否是官方镜像       是否是自建镜像

			2、下载镜像
				[root@localhost ~]# docker pull centos
				镜像名称的构成:
					[仓库URL]/[仓库的名称空间]/[仓库的名称]:[镜像的版本号]
					
					默认的仓库URL:docker.io
					默认的仓库名称空间:library
					仓库的名称:没有默认
					镜像版本号:默认是latest
					
			3、查看本地镜像列表
				docker images 或者 docker image ls

				[root@localhost ~]# docker images 
				REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
				centos       latest    5d0da3dc9764   5 weeks ago   231MB
				
				REPOSITORY:[仓库URL]/[仓库的名称空间]/[仓库的名称]
				TAG	: 版本号
				IMAGE ID : 缩写版的镜像ID
				CREATED	:创建该容器到现在的时间
				SIZE	:镜像大小
				
				[root@localhost ~]# docker image ls
				REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
				centos       latest    5d0da3dc9764   5 weeks ago   231MB
			
			4、查看镜像的详情
				docker inspect [镜像名称或ID]
			
			
			5、更改镜像的名称
				docker tag [原来的名称] [新名称]
				
			6、上传镜像
				将镜像上传至远程仓库。
				1、登录仓库
				2、修改镜像名称
					docker tar  registry.cn-hangzhou.aliyuncs.com/k8sos/centos:v1
				3、上传镜像
				
					docker push [仓库URL]/[仓库的名称空间]/[仓库的名称]:[版本号]
				
			7、登录仓库
				docker login [仓库URL]
				仓库URL默认是:docker.io 
				
			8、创建镜像
			
			9、删除镜像	
				docker rmi [仓库的名称ID]
				docker rmi 
				[root@localhost ~]# docker rmi test:v1
				
				
			10、修改镜像
				镜像一旦被创建,将无法进行修改,除非重新创建。

			11、查看镜像的构建历史
				[root@localhost ~]# docker history centos
				IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
				5d0da3dc9764   5 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
				<missing>      5 weeks ago   /bin/sh -c #(nop)  LABEL org.label-schema.sc...   0B        
				<missing>      5 weeks ago   /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0...   231MB  
								

3 Dockerfile的写法

bash 复制代码
1、访问容器中的服务
	1、端口映射
	2、使用IP


# 构建镜像
	1、Dockerfile
		规范:
			Dockerfile命名必须D大写其他必须小写。
			Dockerfile中所有的指令必须大写
			
		构建镜像的命令
			docker build 
			参数:
				-t : 指定构建镜像的名称
	
		1、FROM
			指定基础镜像。在Dockerfile中唯一一个必须项。
		
		2、RUN
			在构建镜像时,运行指定的命令。
			
			注意:运行的命令必须是基础镜像中包含的命令;执行的结果直接保存在镜像中。
		
		3、ADD
			将指定的文件上传到镜像中。
		
		4、COPY
			将指定的文件复制到镜像中。
			
			
		COPY 和 ADD 之间的区别?
		
			1、ADD支持自动解压功能(只支持tar包解压),COPY不支持
			2、ADD支持通过URL下载文件(不支持自动解压),COPY不支持

		5、EXPOSE
			指定容器需要向外暴露的端口
			如果没有指定任何端口,可以使用-p做端口映射,但是不能使用-P做端口映射。
		
		6、VOLUME
			指定挂在卷,指定的挂载卷并不是设置了就挂载到指定的目录。当容器启动的时候,如果添加了-v参数,以-v参数为准,如果没有,则在宿主主机上的/var/lib/docker/volumes,随机映射。
			
		
		7、CMD
			指定一个容器的启动命令。全局只能有一个,如果有多个,最后一个生效。
			
			exec格式
			
				["nginx", "-g", "daemon off;"]
			
			shell格式
			
				nginx -g 'daemon off;'

		8、WORKDIR
			设置工作目录。运行应用程序时的启始目录(默认是根目录),可以这样理解,执行命令时相对路径的原始目录。
		
		9、ARG
			设置运行时变量。
			
			案例:要求写一个Dockerfile,实现每次安装的软件都是自定义。
			
			[root@kubernetes docker]# docker build --build-arg=PACKAGE=zsh -t nginx:v15 .
		
		10、ONBUILD
			触发器,ONBUILD后面跟指令,在构建是不会执行,当当前镜像作为基础镜像构建时执行。
			
			
		11、ENV
			设置一个环境变量。
		
		12、MAINTAINER
			设置维护者信息。
		
		13、ENTRYPOINT
			设置启动命令。
			
			ENTRYPOINT 和 CMD 的区别

1、ENTRYPOINT 作为启动命令时无法被docker run覆盖(如果docker run指定命令,会被认为成ENTRYPOINT的参数)。
2、如果ENTRYPOINT和CMD指令同时存在,则CMD的相关内容会被设置成ENTRYPOINT的参数


	2、使用Dockerfile构建项目
		构建MySQL、PHP已经nginx镜像,实现搭建discuz
		
		GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
		FLUSH PRIVILEGES;


# docker-compose 


# Horbor私有仓库

server {

	server_name _;
	listen 80;
	
	root /usr/share/nginx/html;
	
	location / {
		index index.php ;
	}
	
	location ~* \.php$ {
		fastcgi_pass 127.0.0.1:9000;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi_params;
	}

}

4 docker-compose

bash 复制代码
1、数据库
	1、没用远程连接root用户
	2、启动MySQL数据库时,要求按照某个参数修改MySQL密码
	
	解决:
		1、如果说没用远程连接的用户,就直接创建
		2、启动之时,如果没有设置远程root密码,则不让启动
			要求启动之时必须设置MYSQL_ROOT_PASSWORD环境变量
		



vim create_user.sh

#!/bin/bash
while true;do
	/usr/local/mysql/bin/mysql -uroot -p''  -e 'show databases;' &>/dev/null
	if [ $? -eq 0 ];then
		/usr/local/mysql/bin/mysql -uroot -p${MYSQL_ROOT_PASSWORD} -h${HOSTNAME}  -e 'show databases;' &>/dev/null
		if [ $? -eq 0 ];then
			exit
		fi
		/usr/local/mysql/bin/mysql  -uroot -p'' -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' WITH GRANT OPTION;FLUSH PRIVILEGES;" &>/dev/null
		exit
	fi
	sleep 1;
done


# docker-compose 
	单机版的docker容器编排工具。
	docker images 是定义镜像的,主要是将应用程序可以在任何地方稳定运行。
	docker-compose 是批量管理docker容器
	docker-compose up  # 启动整个项目

	注意:docker-compose服务启动必须依赖于docker-compose.yml配置文件(重要:名字必须要使用docker-compose.yml, docker-compose.yaml, compose.yml, compose.yaml)

	1、docker-compose 的命令
		docker-compose up  : 启动一组服务
			参数:
				-d  : 以守护进程方式运行

	2、docker-compose ps 命令 : 展示容器列表
	
	3、docker-compose down : 删除当前的容器
	
	4、docker-compose exec [service] [cmd] # 进入service
	
	5、docker-compose restart mysql  重启service
	
	6、docker-compose rm [service] 删除service

		-f : 免交互
		-s : 先停止再删除
	
	7、docker-compose stop [service]	停止
	
	8、docker-compose start [service]	启动
	
	9、docker-compose top 打印容器内进程的详情
	
	10、docker-compose pause 暂停
	
	11、docker-compose unpause 恢复

2、docker-compose的模板

	version  :     # 指定docker-compose的配置模板的版本号。
	services :	   # 配置一个服务(也就是说配置一组容器)
	networks :     # 定义网桥


3、docker-compose的services
	1、build
		
		作用:直接构建容器并使用
		格式:string(Dockerfile的路径)
		案例:
			
	2、 ports
		作用:做端口映射的
		格式:ports: "宿主主机端口:容器端口"


version: "3"
services: 
  nginx:    # 组名
    build: ./mysql 
    ports:
      - "8090:80"

	3、command
		作用:指定一个容器的启动命令
		格式: command : 'cmd'
	
	4、container_name
		作用:设置容器的名称
		
	5、depends_on
		指定在那些镜像之后启动。
		
	6、images
		指定镜像
	
	7、env_file
		指定一个文件(文件中的内容会做成容器内部的环境变量)
		
	8、environment
		直接指定环境变量。
	
	9、healthcheck
		# 设置健康检查的方式 
		test: ["CMD", "curl", "-f", "http://localhost"]
		# 容器启动前15s不探测
		interval: 15s
		# 探测的超时时间
		timeout: 10s
		# 错误连续出现3次,则认定该容器不健康
		retries: 3
	
	
	ulimits:
      nproc: 65535
      nofile:
        soft: 20000
        hard: 40000
    sysctls:
      net.ipv4.ip_forward: 1
	 
	 
	volumes
		作用:挂载存储卷
		
		

docker run -d -p 8000:8000 -p 9443:9443 --name portainer \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer-ce:latest

[root@docker docker-compose]# cat docker-compose.yaml 
version: "3"
services: 
  portainer:
    ports:
      - "8000:8000"
      - "9443:9443"
    container_name: portainer
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/opt/portainer_data:/data"
    image: portainer/portainer-ce:latest

5 Harbor

haskell 复制代码
1、Harbor

	1、容器
	2、镜像
	3、网络
	4、Dockerfile
	
	5、docker的私有仓库
	
2、Harbor

	是由VMware公司的中国团队开发的。私有的docker镜像仓库。
	
	
	官方:https://hub.docker.com/
	
	第三方:https://registry.cn-hangzhou.aliyuncs.com
	
	私有的docker镜像仓库:Harbor
	
		1、登录注册
		2、安全认证
		3、镜像仓库
	
	官网:https://goharbor.io/
	
3、安装部署Harbor

	1、安装源
	
		[root@localhost ~]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
		[root@localhost ~]# curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
		
		
		[root@localhost ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
		[root@localhost ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
		
		[root@localhost ~]# yum clean all
		[root@localhost ~]# yum makecache
		
	2、安装常用的软件
	
		yum install -y vim net-tools wget -y 

	3、安装docker和docker-compose
	
		yum install docker-ce-19.03.9-3 -y 
		
	4、安装OpenSSL
	
		yum install openssl openssl-devel -y 

	5、下载Harbor
	
		https://github.com/goharbor/harbor/releases
		
	6、解压haobor
	
		[root@localhost ~]# tar -xf harbor-offline-installer-v2.3.3.tgz -C /usr/local/
	
	7、开启docker
	
		[root@localhost harbor]# systemctl enable --now docker
	
	8、生成ca证书
		[root@localhost harbor]# mkdir /opt/ssl
		[root@localhost harbor]# cd /opt/ssl
		[root@localhost ssl]# pwd
		/opt/ssl
		
		[root@localhost ssl]# openssl genrsa -out ca.key 4096
		
		[root@localhost ssl]# openssl req -x509 -new -nodes -sha512 -days 3650 \
				 -subj "/C=CN/ST=ShangHai/L=ShangHai/O=Oldboy/OU=Linux/CN=192.168.12.36" \
				 -key ca.key \
				 -out ca.crt
	
	9、生成服务器端证书
	
		[root@localhost ssl]# openssl genrsa -out 192.168.12.36.key 4096
		[root@localhost ssl]# openssl req -sha512 -new \
				-subj "/C=CN/ST=ShangHai/L=ShangHai/O=Oldboy/OU=Linux/CN=192.168.12.36" \
				-key 192.168.12.36.key \
				-out 192.168.12.36.csr
		
	10、生成x509 v3服务文件
	
		[root@localhost ssl]# cat > v3.ext <<-EOF
		authorityKeyIdentifier=keyid,issuer
		basicConstraints=CA:FALSE
		keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
		extendedKeyUsage = serverAuth
		subjectAltName = IP:192.168.12.36
		EOF

	11、生成证书
	openssl x509 -req -sha512 -days 3650 \
	-extfile v3.ext \
	-CA ca.crt -CAkey ca.key -CAcreateserial \
	-in 192.168.12.36.csr \
	-out 192.168.12.36.crt
	
	12、将192.168.12.36.csr转换成192.168.12.36.cert,给予docker使用
	
		openssl x509 -inform PEM -in 192.168.12.36.crt -out 192.168.12.36.cert
			
	13、创建docker证书存放目录

		[root@localhost ssl]# mkdir -p /etc/docker/certs.d/192.168.12.36
		
		[root@localhost ssl]# cp 192.168.12.36.key /etc/docker/certs.d/192.168.12.36/
		[root@localhost ssl]# cp 192.168.12.36.cert /etc/docker/certs.d/192.168.12.36/
		[root@localhost ssl]# cp ca.crt /etc/docker/certs.d/192.168.12.36/
		
		[root@localhost ssl]# ll /etc/docker/certs.d/192.168.12.36/
		total 12
		-rw-r--r--. 1 root root 2061 Oct 28 23:16 192.168.12.36.cert
		-rw-r--r--. 1 root root 3243 Oct 28 23:16 192.168.12.36.key
		-rw-r--r--. 1 root root 2025 Oct 28 23:16 ca.crt
		
		[root@localhost ssl]# systemctl restart docker
		
	14、修改Harbor的配置文件
	[root@localhost harbor]# pwd
	/usr/local/harbor
	[root@localhost harbor]# vim harbor.yml
	hostname: 192.168.12.36
	port: 80
	
	https:
	  # https port for harbor, default is 443
	  port: 443
	  certificate: /opt/ssl/192.168.12.36.crt
	  private_key: /opt/ssl/192.168.12.36.key
		
	
	15、生成配置
	
		[root@localhost harbor]# ./prepare 
	
	16、开始安装
	
		[root@localhost harbor]# ./install.sh 
		
	17、访问HTTPS
	
		用户名:admin
		密码: Harbor12345	
			
			FKQVOFHOIEZYUQJI
			
		
相关推荐
mengao123411 分钟前
centos 服务器 docker 使用代理
服务器·docker·centos
Eternal-Student18 分钟前
【docker 保存】将Docker镜像保存为一个离线的tar归档文件
运维·docker·容器
不是二师兄的八戒20 分钟前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
码农小丘26 分钟前
一篇保姆式centos/ubuntu安装docker
运维·docker·容器
爱编程的小生32 分钟前
Easyexcel(2-文件读取)
java·excel
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
Eternal-Student1 小时前
【1.2 Getting Started--->Installation Guide】
docker
计算机毕设指导61 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study2 小时前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data2 小时前
二叉树oj题解析
java·数据结构