docker 构建git+maven+jdk8的centos7环境,实现轻量级的springboot项目的自动化部署

目录

背景

之前我部署springboot项目的时候会采用docker+jenkins的持续集成部署,但是jenkins比较重,而且配置起来相对麻烦一些,而且配置与项目不一起,不方便统一管理。所以这次的项目负责人推荐我使用轻量级的docker 构建git+maven+jdk8的centos7环境,实现轻量级的springboot项目的自动化部署

docker+jenkins的持续集成部署springboot项目(我的另一篇博客):部署SpringBoot的jar包项目让人头疼,不如使用jenkins+docker自动化部署jar包项目

部署分析

为什么采用git+maven+jdk8的centos7的docker环境去部署springboot项目?

  • 选用docker是因为docker在linux上安装镜像(软件)方便快速,而且不用复杂繁琐的配置,再者容器运行相对对立,不互相影响。
  • jdk8的环境就不多说了,jar包运行的基础环境。
  • git环境,主要是为了可能在GitLab、GitHub或者码云上可以随时拉去最新的项目,进行合作开发,随时部署。
  • maven环境,主要是为了可以将git新拉取的项目打成jar包。
  • centos7环境,主要是为了可以安装以上的各种环境,方便后期可以安装其余所需要的环境(软件)。

除此之外最好可以在centos7的环境上安装vim,方便编辑文件。

部署流程简要概括

  1. linux上提前下载好Git,方便项目的第一次clone
  2. 项目根目录下编写Dockerfile文件(方便后续的sh脚本构建docker镜像)
  3. 项目根目录下编写sh脚本,用于初始化docker镜像和容器,以及完成自动化部署
  4. 将刚才编写好的项目上传到GIT仓库(GitLab/GitHub/码云)
  5. 在linux服务器上使用git拉取项目代码
  6. 执行sh脚本的init方法,初始化docker镜像、容器,并进行项目部署(后续会详细说明)

项目部署

1.linux服务器下载Git

这里使用yum快速下载安装:

sudo yum install -y git

查看git版本(判断是否安装成功)

git --version

2.项目根目录下编写Dockerfile文件

FROM centos:7
RUN yum -y update \
    && yum -y install vim \
    && yum -y install git \
    && yum -y install java-1.8.0-openjdk-devel.x86_64 \
    && yum install -y maven \
    && mkdir ~/.m2
RUN echo '<?xml version="1.0" encoding="UTF-8"?><settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"><mirrors><mirror><id>alimaven</id><mirrorOf>central</mirrorOf><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/repositories/central/</url></mirror></mirrors></settings>' > ~/.m2/settings.xml

解析

  • FROM centos:7是要构建的镜像依赖于docker的centos7镜像,没有centos7的话,会直接去pull。
  • 之后就是在centos7的镜像上安装vim、git、jdk8和maven,并配置maven的阿里镜像。

3.项目根目录下编写sh脚本

为了分析的更加明白透彻,将sh脚本拆开分析,后续再附上完整的sh脚本文件。

sh脚本的命名,可以用"项目名.sh"

①定义全局变量

#!/bin/bash

# 镜像名字
IMAGE_NAME=centos7_mvn_git_java8

# docker 容器名字或者jar名字,这里都命名为这个
SERVER_NAME=xiaoyun-api-java

#这里的JAR_PATH为jar包所在位置
JAR_PATH=./xiaoyun_system/target/xiaoyun_system-1.0.0-SNAPSHOT.jar

profile=$2
port=$3
#Xms=$4
#Xmx=$5

分析

  • IMAGE_NAME为构建后的docker镜像名
  • SERVER_NAME为以IMAGE_NAME的镜像运行的容器名
  • JAR_PATH为maven打成jar包后的路径("./"指的是当前项目)
  • profile=$2为命令行输入的第二个参数,用来定义项目运行的环境(对应于application.yml的配置文件)
  • port=$3为命令行输入的第二个参数,用来定义项目的运行端口
  • Xms Xmx为定义jar包运行的内存等参数信息,暂时先注掉,如果有需要,可以自行定义(根据这篇博客,自定定义不成问题)

②初始化——构建docker镜像和项目运行容器

init()需要在git clone项目后,在宿主机当前项目下,执行sh文件执行(挂载到容器后,容器没有docker环境,不能构建镜像和容器,再说不执行这一步骤,还没有容器)(容器能不能对docker“套娃”,我还真没了解,即使可以,一般也没人在容器安装docker吧,无限套娃)

#初始化——构建镜像和容器(在宿主机执行)
init(){
  #容器id
  CID=$(docker ps | grep "$SERVER_NAME" | awk '{print $1}')
  #镜像id
  IID=$(docker images | grep "$IMAGE_NAME" | awk '{print $3}')
	# 构建docker镜像
	if [ -n "$IID" ]; then
		echo "Exit $SERVER_NAME image,IID=$IID"
	else
		echo "NOT exit $SERVER_NAME image,start build image..."
		# 根据项目个路径下的Dockerfile文件,构建镜像
		docker build -t $IMAGE_NAME .       
		echo "$SERVER_NAME image has been builded"	
	fi

	if [ -n "$CID" ]; then
			echo "Exit $SERVER_NAME container,CID=$CID.   ---Remove container"
			docker stop $SERVER_NAME   # 停止运行中的容器
			docker rm $SERVER_NAME     ##删除原来的容器
	fi
	
	# 构建容器
	echo "$SERVER_NAME container,start build..." 
	# 运行容器
	 # --name 容器的名字
	 #   -d   容器后台运行
	 #   -p   指定容器映射的端口和主机对应的端口
	 #   -v   将主机的目录挂载到容器的目录中(不可少)
	docker run -e TZ="Asia/Shanghai" -id -m 512M --memory-swap=1G --name $SERVER_NAME -v $PWD:/project/$SERVER_NAME $IMAGE_NAME
	echo "$SERVER_NAME container build end"
}

分析

  • 判断docker有没有要构建的镜像,如果有镜像,不构建镜像;如果没有镜像,根据项目根路径下的Dockerfile文件构建镜像。这个git+maven+jdk8的centos7镜像是可以重复使用的,就相当于软件一样,所以构建后,第二个项目使用就可以不构建了。 所以这里判断有当前镜像时,就不在构建镜像了。
  • 判断有没有预构建的容器,如果有,就先停止容器,删除容器;没有容器往下执行。然后构建容器(不管有没有容器,都重新构建容器)
  • 构建容器时,设置时区、运行内存和挂载目录等配置(也可以根据需求自行更改)

注意事项

1.项目之后的访问问题?

我这里sh运行的容器没有配置映射端口信息,因为我后续会配置nginx反向代理到容器的IP和端口(每个docker容器都有独立的IP),用于反向代理访问项目。
如果项目端口固定,也可以配置映射端口。

容器运行问题

  • 最好限制下容器运行内存等参数信息,否则如果服务器不大的话,可能会经常造成容器意外停止。
  • 写容器运行配置最好可以提前配置完全,否则中途更改配置就比较麻烦了。

③检查程序(jar包)是否运行

#检查程序是否在运行
is_exist(){
  pid=`ps -ef|grep $JAR_PATH|grep -v grep|awk '{print $2}' `
  #如果不存在返回1,存在返回0     
  if [ -z "${pid}" ]; then
   return 1
  else
    return 0
  fi
}

程序运行返回0,不运行返回1,用于后续执行的条件判断

④启动程序

#启动方法
start(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${SERVER_NAME} is already running. pid=${pid} ."
  else
    echo --------Starting application --------
    nohup java -server -Xms512m -Xmx512m -XX:SurvivorRatio=4 -Xss256k -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -jar $JAR_PATH --spring.profiles.active=${profile:-dev} --server.port=${port:-8091} > start.log 2>&1 &
    echo --------------Started!---------------
 
  fi
}

分析

  • 当程序没有运行,即is_exist返回1时在执行启动命令;若返回0则输出程序正在运行…
  • -jar $JAR_PATH运行jar包
  • --spring.profiles.active=${profile:-dev}在什么环境下运行程序,对应之后运行sh脚本的第二个参数(与全局变量的profile=$2对应)。默认dev环境运行(如果执行sh文件不输入第二个参数,则以dev运行)。
  • --server.port=${port:-8091}什么端口运行,对应之后运行sh脚本的第三个参数(与全局变量的port=$3对应)。默认8091运行(如果执行sh文件不输入第三个参数,则以8091端口运行)。
  • 其他的参数都是限制程序的运行环境,可以自定百度更改完善。

SpringBoot程序(jar包)运行环境如何确定?

可看下图:
在这里插入图片描述

  • application-dev.yml对应开发环境配置文件
  • application-test.yml对应测试环境配置文件
  • application-prod.yml对应生产环境配置文件
  • application.yml初始配置文件,指定默认以什么环境运行程序
为什么要定义这么多的环境配置文件?

比如在开发环境和测试环境swagger是可以使用的,但是在生产环境,即项目上线后swagger是不能使用的,防止随意访问api接口,保证安全性。
定义多种环境配置文件,在不同环境运行项目只需要直接指定配置文件就好,而不是不同环境下,修改application.yml的配置(这样不方便开发和维护)。

纯属个人理解

环境配置文件如何执行使用?

在sh脚本中,--spring.profiles.active=${profile:-dev}就是指定运行不同环境的配置文件,例如生产环境就是dev,测试环境就是test,生产环境就是prod。刚好对应于编写application-的后缀。

application.yml默认执行运行环境

application.yml内容如下,默认以dev环境运行程序(如果不指定程序运行环境)

spring:
  profiles:
    active: dev
项目运行端口的优先级

application.yml中配置有项目端口,sh的–server.port=${port:-8091}有默认端口,linux命令行还可以输入参数指定项目的运行端口,究竟项目会执行哪个配置的端口?

端口执行优先级:linux命令行指定的端口 > --server.port=${port:-8091}的默认端口 > application.yml中配置的端口

⑤停止程序

#停止方法
stop(){
  is_exist
  if [ $? -eq "0" ]; then
    kill -9 $pid
    echo -----------Application Stopped------------
  else
    echo "${JAR_PATH} is not running"
  fi  
}

is_exist返回0代表程序正在运行,通过kill -9停止程序

⑥重启程序

先停止容器,再启动程序

#重启
restart(){
  stop
  start
}

⑦查看程序运行状态

判断程序是否在运行

#输出运行状态
status(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${JAR_PATH} is running. Pid is ${pid}"
  else
    echo "${JAR_PATH} is NOT running."
  fi
}

⑧git拉去最新代码,maven打包,并运行程序

#mvn
pull(){
  echo "----------git:find status---------"
  git status
  echo "----------git:pull new coads---------"
  git pull origin develop
  if [ $? -ne 0 ]; then
    exit
  fi
  echo "----------mvn clean package -Dmaven.test.skip=true---------"
  mvn clean package -Dmaven.test.skip=true
  if [ $? -ne 0 ]; then
    exit
  fi
  echo "----------Preparing start application ---------"
  is_exist
  if [ $? -eq "0" ]; then
    restart
  else
    start
  fi
}

分析

  • git status查看git状态
  • git pull拉取最新代码,分支可自己修改
  • -Dmaven.test.skip=truemaven打包时跳过测试
  • 最后运行程序,根据状态选择启动还是重启程序
  • git拉取代码和mvn打包若出现错误会返回状态为0,则exit退出

⑨根据参数执行对应的方法(sh脚本的第一个参数)

#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
  "init")
    init
    ;;
  "start")
    start
    ;;
  "stop")
    stop
    ;;
  "status")
    status
    ;;
  "restart")
    restart
    ;;
  "pull")
    pull
    ;;
  *)
    usage
    ;;
esac

⑩执行sh不输入第一个参数,则进行提示,并退出

#使用说明,用来提示输入参数
usage() {
    echo "Usage: sh 执行脚本.sh [init|start|stop|restart|status|pull] [profile] [port]"
    exit 1
}

到此sh脚本编写完毕,文章末尾会附上完整的sh文件


4.上传项目到远程仓库

Dockerfile文件和sh脚本已经编写完毕,可以通过git上传到远程仓库,方便服务器拉取。
git上传项目的命令省略(比较简单)

5.git去clone项目到linux服务器

git clone 你的仓库的http方式的clone地址

以GitLab仓库为例:
在这里插入图片描述
我这里采用http的方式,这样比较简单,如果对安全性要求比较高,也可以采用ssh方式clone,但是相对麻烦一些。

6.进入clone项目后的根目录执行sh脚本,初始化docker镜像和容器

如果sh文件不再根目录,也可以进入相对应的目录

bash test.sh init

解析

  • sh脚本暂时命名为test.sh
  • 执行sh脚本的时候,输入第二个参数init,用于构建镜像和容器

判断是否构建成功

判断镜像是否存在

docker images

判断容器是否运行

docker ps

如果没有成功,说明构建命令没有配置对,需要修改一下。

7.进入容器,执行相应的sh脚本的命令

进入项目容器,假设容器名为test

docker exec -it test bash

进入挂载目录

即项目根目录(上文我挂载的是 /project/xiaoyun ,运行容器有挂载目录,会在容器运行时自动创建)

cd /project/xiaoyun

sh脚本运行命令大全

①查看项目(程序运行状态)

bash test.sh status

②启动项目

以默认项目的环境配置文件和端口启动
bash test.sh start
指定环境配置文件和默认端口启动

假设以生产环境启动为例

bash test.sh start prod
指定环境配置文件和端口启动

假设以生产环境,端口为9010启动为例

bash test.sh start prod 9010

注:start、restart、pull都可以输入第二个和第三个参数,以相应的配置文件和端口启动

③停止项目

bash test.sh stop

④重启项目

bash test.sh restart

⑤拉取最新代码,打包,并启动项目

bash test.sh pull

8.配置项目可以被远程访问(可选)

容器配置映射端口,无需这个配置

如果sh文件启动docker容器时,映射了端口,那么不需要再进行额外的配置,直接根据映射端口去访问程序就好了。

配置nginx反向代理,访问容器中的程序

nginx可以使用宿主机的,也可以使用docker运行nginx容器。但是使用docker运行nginx容器,最好不要映射端口,而是共享宿主的端口,因为未来,你并不知道你配置的项目会用到那些端口。

nginx共享宿主机端口命令:--net host

nginx容器启动参考命令:

docker run -it --name nginx --net host -v /root/project:/var/www/html 
-v /root/nginx:/nginx_conf nginx /bin/bash

查看容器的ip

查看容器信息

docker inspect 运行容器名/容器id

找到对应的IPAddress,即为容器的IP
在这里插入图片描述

配置反向代理

IP为容器IP,端口为项目的运行端口号

可以将这个nginx配置文件以“项目名.conf”命名,放到项目更目录下,方便统一管理和维护。

server {
    listen       8091;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        proxy_pass http://172.18.0.2:8091;
        #root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}


关于nginx配置统一管理项目的conf文件可以参考我的博客:https://blog.csdn.net/qq_42937522/article/details/108179441

后续关于nginx的使用,访问项目等,暂且忽略

小结

到此docker构建git+maven+jdk8的centos7环境,实现轻量级的springboot项目的自动化部署已经完成。

缺点

还是有一些美中不足

  • linux服务器要预先安装Git,进行第一次clone代码(yum方式安装也只是需要执行一行代码,也比较方便)
  • 相对于jenkins不是完全自动化,还是要手动输入一些命令(不过命令都比较简单)

总体来看还是很nice的。

优点

  • 有了这个配置,从项目初期就可以进行服务器部署了,省去了部署时大量的繁琐操作。
  • 项目部署配置与项目一起统一管理,方便后期的维护。
  • 有了部署更改要求,直接更改sh脚本即可,非常方便

Dockerfile文件

FROM centos:latest
RUN yum -y update \
    && yum -y install vim \
    && yum -y install git \
    && yum -y install java-1.8.0-openjdk-devel.x86_64 \
    && yum install -y maven \
    && mkdir ~/.m2
RUN echo '<?xml version="1.0" encoding="UTF-8"?><settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"><mirrors><mirror><id>alimaven</id><mirrorOf>central</mirrorOf><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/repositories/central/</url></mirror></mirrors></settings>' > ~/.m2/settings.xml

sh部署脚本文件

#!/bin/bash

# 镜像名字
IMAGE_NAME=centos7_mvn_git_java8

# docker 容器名字或者jar名字,这里都命名为这个
SERVER_NAME=xiaoyun-api-java

#这里的JAR_PATH为jar包所在位置
JAR_PATH=./xiaoyun_system/target/xiaoyun_system-1.0.0-SNAPSHOT.jar

profile=$2
port=$3
#Xms=$4
#Xmx=$5

#使用说明,用来提示输入参数
usage() {
    echo "Usage: sh 执行脚本.sh [init|start|stop|restart|status|pull] [profile] [port]"
    exit 1
}

#初始化——构建镜像和容器(在宿主机执行)
init(){
  #容器id
  CID=$(docker ps | grep "$SERVER_NAME" | awk '{print $1}')
  #镜像id
  IID=$(docker images | grep "$IMAGE_NAME" | awk '{print $3}')
	# 构建docker镜像
	if [ -n "$IID" ]; then
		echo "Exit $SERVER_NAME image,IID=$IID"
	else
		echo "NOT exit $SERVER_NAME image,start build image..."
		# 根据项目个路径下的Dockerfile文件,构建镜像
		docker build -t $IMAGE_NAME .       
		echo "$SERVER_NAME image has been builded"	
	fi

	if [ -n "$CID" ]; then
			echo "Exit $SERVER_NAME container,CID=$CID.   ---Remove container"
			docker stop $SERVER_NAME   # 停止运行中的容器
			docker rm $SERVER_NAME     ##删除原来的容器
	fi
	
	# 构建容器
	echo "$SERVER_NAME container,start build..." 
	# 运行容器
	 # --name 容器的名字
	 #   -d   容器后台运行
	 #   -p   指定容器映射的端口和主机对应的端口
	 #   -v   将主机的目录挂载到容器的目录中(不可少)
	docker run -e TZ="Asia/Shanghai" -id --name $SERVER_NAME -v $PWD:/project/$SERVER_NAME $IMAGE_NAME
	echo "$SERVER_NAME container build end"
}
 
#检查程序是否在运行
is_exist(){
  pid=`ps -ef|grep $JAR_PATH|grep -v grep|awk '{print $2}' `
  #如果不存在返回1,存在返回0     
  if [ -z "${pid}" ]; then
   return 1
  else
    return 0
  fi
}
 
#启动方法
start(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${SERVER_NAME} is already running. pid=${pid} ."
  else
    echo --------Starting application --------
    nohup java -server -Xms512m -Xmx512m -XX:SurvivorRatio=4 -Xss256k -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -jar $JAR_PATH --spring.profiles.active=${profile:-dev} --server.port=${port:-8091} > start.log 2>&1 &
    echo --------------Started!---------------
 
  fi
}
 
#停止方法
stop(){
  is_exist
  if [ $? -eq "0" ]; then
    kill -9 $pid
    echo -----------Application Stopped------------
  else
    echo "${JAR_PATH} is not running"
  fi  
}
 
#输出运行状态
status(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${JAR_PATH} is running. Pid is ${pid}"
  else
    echo "${JAR_PATH} is NOT running."
  fi
}
 
#重启
restart(){
  stop
  start
}

#mvn
pull(){
  echo "----------git:find status---------"
  git status
  echo "----------git:pull new coads---------"
  git pull origin develop
  if [ $? -ne 0 ]; then
    exit
  fi
  echo "----------mvn clean package -Dmaven.test.skip=true---------"
  mvn clean package -Dmaven.test.skip=true
  if [ $? -ne 0 ]; then
    exit
  fi
  echo "----------Preparing start application ---------"
  is_exist
  if [ $? -eq "0" ]; then
    restart
  else
    start
  fi
}
 
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
  "init")
    init
    ;;
  "start")
    start
    ;;
  "stop")
    stop
    ;;
  "status")
    status
    ;;
  "restart")
    restart
    ;;
  "pull")
    pull
    ;;
  *)
    usage
    ;;
esac

sh脚本需要根据实际项目需求修改的地方

修改点备注
IMAGE_NAME镜像名(可以重复使用镜像)
SERVER_NAME项目运行的容器名
JAR_PATHmvn打包的jar路径
–spring.profiles.active=${profile:-dev}-dev(默认的项目启动环境配置文件)
–server.port=${port:-8091}-8091(默认的项目启动端口)
git pull origin developgit拉取代码的默认分支

到此完毕!如有遇到问题,或者有更好的改进方案,方便回复讨论!!!

更新

2020.10.12更新

JVM调优

注:如果是生产环境,服务器可以抗住压力的话,可以修改JVM的参数限制
否则分配给jar包运行的内存太小,可能导致经常宕机。

start(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${SERVER_NAME} is already running. pid=${pid} ."
  else
    echo --------Starting application --------
    nohup java -server -XX:-DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -jar $JAR_PATH --spring.profiles.active=${profile:-dev} --server.port=${port:-8091} > start.log 2>&1 &
    echo --------------Started!---------------
 
  fi
}

nginx配置

关于nginx配置可参考博客:
docker安装nginx规范所有项目的反向代理(一个项目一个反向代理的conf配置文件)

关于Dockerfile

Dockerfile中FROM centos:latest,可根据部署服务器已经pull好的centos镜像进行调整。比如服务器上已经安装了centos:centos7,或者centos:7,可以视情况进行版本修改。避免服务器安装多个不必要类似于centos这样的镜像。

2020.12.5更新

如果是前端后端项目都放在一个git仓库中,而且sh文件放在后端项目的根路径下,会出现 .git文件 不能正常挂载到后端容器从而导致执行pull命令失败。

解决:修改以下sh文件
主要是将$PWD修改为$PWD/../,挂载目录向上一级即可。

docker run -e TZ="Asia/Shanghai" -id --name $SERVER_NAME -v $PWD/../:/project/$SERVER_NAME $IMAGE_NAME

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:博客之星2021 设计师:Hiro_C 返回首页
评论 3

打赏作者

Duktig丶

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值