Linux 基于 Docker 容器化部署 Pmhub 项目

Linux 基于 Docker 容器化部署Pmhub项目

使用 Docker Compose 搭建项目环境,简化项目部署过程,降低环境差异性问题,提升资源隔离安全性
线下爱你千万遍,不如线上见一面 (哈哈哈, 这句话戳到我了~🤣)

前置准备条件

  • 全部的中间件我都是基于**Linux****Docker**部署的, 为了更好的管理
  • 不习惯用**XShell**等工具可以装个可视化管理工具, 虽然有一定的侵入性…
  • 这里我用的可视化管理工具是**portainer.io**, 部署的容器都很容易管理

image.png

  • 强调一点, 这里部署项目仅供学习参考, 不要上生产环境!!
  • 部署的中间件我使用的基本都是默认端口, 很容易被别人扫的!!
  • 还有就是服务器上的中间件密码设置的复杂一些, 最好是大小写字母数字符号组合

( 必做 )MYSQL环境配置

  • 拉取镜像
docker pull mysql:8.0.33
  • 拷贝文件
mkdir -p /data/mysql/{conf,logs,data}

docker run -p 3306:3306 --name mysql -d mysql:8.0.33

docker cp mysql:/var/log/mysql /data/mysql
docker cp mysql:/var/lib/mysql /data/mysql
docker cp mysql:/etc/mysql/conf.d /data/mysql

chmod 777 /data/mysql/{conf,logs,data}

docker rm -f mysql
  • 创建容器
docker run -p 3306:3306 --name mysql \
-v /data/mysql/logs:/var/log/mysql \
-v /data/mysql/data:/var/lib/mysql \
-v /data/mysql/conf:/etc/mysql/conf.d \
-e TZ=Asia/Shanghai --restart=always \
-e MYSQL_ROOT_PASSWORD=ovo@Knight \
-d mysql:8.0.33
  • 连接数据库

image.png

  • 将SQL导入到服务器的数据库中

image.png

( 必做 )Redis环境配置

  • 拉取镜像
docker pull redis:7.0.12
  • 拷贝文件
mkdir -p /data/redis/{conf,data,log}
touch /data/redis/log/redis.log

docker run -p 6379:6379 --name redis -d redis:7.0.12

docker cp redis:/data /data/redis
docker cp redis:/etc/redis.log /data/redis/log
docker cp redis:/etc/redis/redis.conf /data/redis/conf

chmod -R 777 /data/redis/conf/redis.conf /data/redis/log/redis.log /data/redis/data

docker rm -f redis
  • 创建容器
docker run --name redis \
-p 6379:6379 --restart=always \
-v /data/redis/data:/data \
-v /data/redis/log/redis.log:/etc/redis.log \
-v /data/redis/conf:/etc/redis/redis.conf \
-d redis:7.0.12 redis-server /etc/redis/redis.conf
  • 用测试工具查看是否成功部署Redis

image.png

( 必做 )Nacos环境配置

  • 拉取镜像
docker pull nacos/nacos-server:v2.1.1
  • 拷贝文件
mkdir -p /data/nacos/{conf,logs,data}

docker run -p 8848:8848 --name nacos -d nacos/nacos-server:v2.1.1

docker cp nacos:/home/nacos/conf /data/nacos
docker cp nacos:/home/nacos/data /data/nacos
docker cp nacos:/home/nacos/logs /data/nacos

chmod 777 /data/nacos/{conf,logs,data}

docker rm -f nacos
  • 创建容器
  • **MYSQL_SERVICE_HOST=192.168.100.100**修改为自己的IP
  • **MYSQL_SERVICE_PASSWORD=ovo@Knight**修改为自己的MYSQL密码
docker run -d \
-e MODE=standalone \
--privileged=true \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=192.168.100.100 \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD=ovo@Knight\
-e MYSQL_SERVICE_DB_NAME=pmhub-nacos \
-e TIME_ZONE='Asia/Shanghai' \
-v /data/nacos/logs:/home/nacos/logs \
-v /data/nacos/data:/home/nacos/data \
-v /data/nacos/conf:/home/nacos/conf \
-p 8848:8848 -p 9848:9848 -p 9849:9849 \
--name nacos --restart=always nacos/nacos-server:v2.1.1

image.png

( 选做 )Seata环境配置

  • 创建对应的**seata**数据库, 也就是项目里的**pmhub_seata.sql**文件

  • 如果前面跟着我的步骤来的话, 数据库应该都已经创建了, 可以直接下一步

  • 拉取镜像

docker pull seataio/seata-server:1.5.2
  • 拷贝文件
mkdir -p /data/seata/conf

docker run -d -p 8091:8091 -p 7091:7091 --name seata seataio/seata-server:1.5.2

docker cp seata:/seata-server/resources/. /data/seata/conf

chmod 777 /data/seata/conf

docker rm -f seata
  • 修改配置文件**application.yml**

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${log.home:${user.home}/logs/seata}
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

seata:
  config:
    type: nacos
    nacos:
      server-addr: 192.168.100.100:8848
      namespace:
      group: DEFAULT_GROUP
      username: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.100.100:8848
      group: DEFAULT_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.100.100:3306/pmhub-seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
      user: root
      password: xxxxxx
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 1000
      max-wait: 5000
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**




  • **application.yml**中需要修改的地方, 换成自己的Nacos地址
  • Mysql数据库密码换成自己的, 其他保持跟我的一样就可以啦~

image.png
image.png
image.png

  • 在Nacos中新建pmhub-seata-dev.properties配置
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.100.100:3306/pmhub-seata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=xxxxxx
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

  • 这里的数据库IP地址和密码记得换成自己的哦~

image.png

  • **启动Seata容器 - ****SEATA_IP=192.168.100.100**改成自己服务器的地址
docker run --name seata \
-p 8091:8091 \
-p 7091:7091 \
-e SEATA_IP=192.168.100.100 \
-v /data/seata/conf:/seata-server/resources \
--privileged=true \
-d \
seataio/seata-server:1.5.2
  • 启动成功如图

image.png

( 选做 )容器可视化工具Portainer

  • 拉取镜像
docker pull portainer/portainer-ce
  • 启动镜像, 创建容器
docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /dockerData/portainer:/data --restart=always --name portainer portainer/portainer-ce:latest
  • 访问可视化工具 - http://192.168.100.100:9000 ( 改成自己的服务器地址 )
  • 第一次进入需要设置admin的账号和密码, 设置一下就可以进来查看Docker里的容器了

image.png

部署各服务到Docker

	**-------前言-------**
  • 如果觉得每次打**Jar**包麻烦的话, 可以将全部配置都改好然后统一打包
  • 如果觉得每次制作镜像麻烦的话, 可以写一个脚本一键Copy, 下面粘一个苍何老师的sh脚本
  • 我这种部署方式算是比较传统的Docker单服务部署, 后面我有时间会出**docker-compose**来部署
#!/bin/sh

# 复制项目的文件到对应docker路径,便于一键生成镜像。
usage() {
	echo "Usage: sh copy.sh"
	exit 1
}


# copy sql
echo "begin copy sql "
cp ../sql/pmhub_20240305.sql ./mysql/db
cp ../sql/pmhub_nacos_20240423.sql ./mysql/db

# copy html
echo "begin copy html "
cp -r ../pmhub-ui/dist/** ./nginx/html/dist


# copy jar
echo "begin copy pmhub-gateway "
cp ../pmhub-gateway/target/pmhub-gateway.jar ./pmhub/gateway/jar

echo "begin copy pmhub-auth "
cp ../pmhub-auth/target/pmhub-auth.jar ./pmhub/auth/jar

echo "begin copy pmhub-monitor "
cp ../pmhub-monitor/target/pmhub-monitor.jar  ./pmhub/monitor/jar

echo "begin copy pmhub-system "
cp ../pmhub-modules/pmhub-system/target/pmhub-system.jar ./pmhub/modules/system/jar

echo "begin copy pmhub-job "
cp ../pmhub-modules/pmhub-job/target/pmhub-job.jar ./pmhub/modules/job/jar

echo "begin copy pmhub-gen "
cp ../pmhub-modules/pmhub-gen/target/pmhub-gen.jar ./pmhub/modules/gen/jar

echo "begin copy pmhub-project "
cp ../pmhub-modules/pmhub-project/target/pmhub-project.jar ./pmhub/modules/project/jar

echo "begin copy pmhub-workflow "
cp ../pmhub-modules/pmhub-workflow/target/pmhub-workflow.jar ./pmhub/modules/workflow/jar

PmHub-gateway

修改配置文件bootstrap.yml

image.png

修改Nacos中pmhub-gateway-dev.yml的配置
  • 改成自己服务器的IP和Redis部署的端口, 有密码加上密码

image.png

package for jar 包

image.png

将 jar 包上传服务器
  • 创建一个**Dockerfile**文件, 文件路径可以和我的一样

image.png

编写Dockerfile
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-gateway.jar /pmhub-gateway.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-gateway.jar"]
将 jar 包制作成镜像
  • 先进入当前放置 jar 包的目录下, 再构建镜像
cd /root/pmhub/gateway/
docker build -t pmhub-gateway:v0.01 .

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

查看构建的镜像 - docker images

image.png

启动gateway网关服务 - 再查看启动日志
docker run -d --name pmhub-gateway -p 6880:6880 pmhub-gateway:v0.01
docker logs -f  pmhub-gateway
启动成功画面如下

image.png

  • 如果出现下面的错误大概率是Nacos没有成功启动

image.png

访问一下检查是否部署成功

image.png

PmHub-system

  • 前面的几步都和**pmhub-gateway**一样, 按部就班就行, 下面放一些截图供参考
  • **修改Nacos的配置文件记得发布哦~ 不然就白瞎了~ **🤣
  • **项目有个地方Redis配置要改成服务器的IP!!!, 如下图 : **

image.png

  • 修改**bootstrap.yml**配置文件

image.png

  • 修改Nacos里的**pmhub-system-dev.yml**文件

image.png

image.png

  • 将项目打成 Jar 包

image.png\

  • **Jar**包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-system.jar /pmhub-system.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-system.jar"]
  • 将 Jar 包制作成镜像
docker build -t pmhub-system:v0.01 .

image.png

  • 启动**pmhub-system**服务, 再查看服务日志
docker run -d --name pmhub-system -p 6801:6801 pmhub-system:v0.01
docker logs -f pmhub-system

image.png

  • 检查是否部署成功

image.png

PmHub-auth

  • 到这样应该也轻车熟路了吧, 哈哈哈~

  • 自己动手尝试部署一个看看咯? 你一定可以的 ! ! !

  • 修改**bootstrap.yml**配置文件

image.png

  • 修改Nacos里的pmhub-auth-dev.yml配置文件

image.png

  • 打包成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile 文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-auth.jar /pmhub-auth.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-auth.jar"]
  • 将 Jar 包制作成镜像
docker build -t pmhub-auth:v0.01 .

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 启动**pmhub-auth**服务, 再查看启动日志
docker run -d --name pmhub-auth -p 6800:6800 pmhub-auth:v0.01
docker logs -f pmhub-auth

image.png

  • 检查是否部署成功

image.png

PmHub-project

image.png

  • 修改**bootstrap.yml**配置文件image.png

  • 修改Nacos中的**pmhub-project-dev.yml**配置文件

image.png
image.png
image.png

  • 将项目打成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-project.jar /pmhub-project.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-project.jar"]
  • 将Jar包制作成镜像
docker build -t pmhub-project:v0.01 .

image.png

  • 启动**pmhub-project**服务, 再查看启动日志
docker run -d --name pmhub-project -p 6806:6806 pmhub-project:v0.01
  • 这个Error是因为没有启动Seata中间件, 但无伤大雅哈哈哈~
  • 不过后面**pmhub-project**会一直输出Error日志, 因为检测不到seata
  • 你可以选择部署seata然后写上相关的配置, 或者直接注释掉seata的配置也可以

image.png

  • 检查是否部署成功

image.png

PmHub-workflow

  • 准备要完成全服务的部署啦, 再坚持一下下~ 😜

  • 修改**bootstrap.yml**配置文件

image.png

  • 修改Nacos中的**pmhub-workflow-dev.yml**配置文件

image.png
image.png
image.png
image.png

  • 将项目打成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-workflow.jar /pmhub-workflow.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-workflow.jar"]
  • 将Jar包制作成镜像
docker build -t pmhub-workflow:v0.01 .

image.png

  • 启动**pmhub-workflow**, 再查看启动日志
docker run -d --name pmhub-workflow -p 6808:6808 pmhub-workflow:v0.01
docker logs -f pmhub-workflow

image.png

  • 检查是否部署成功

image.png

PmHub-gen

  • **pmhub-gen****pmhub-job****部署基本一样啦~ **

  • 修改**bootstrap.yml**配置文件

image.png

  • 修改Nacos中**pmhub-gen-dev.yml**配置文件

image.png
image.png

  • 将项目打成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-gen.jar /pmhub-gen.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-gen.jar"]
  • 将 Jar 包制作成镜像
docker build -t pmhub-gen:v0.01 .

image.png

  • 启动**pmhub-gen**, 再查看启动日志
docker run -d --name pmhub-gen -p 6802:6802 pmhub-gen:v0.01
docker logs -f pmhub-gen

image.png

  • 检查是否部署成功

image.png

PmHub-job

  • 还剩两个就部署完啦, 再坚持坚持~ 🐱

  • 修改**bootstrap.yml**配置文件

image.png

  • 修改Nacos中的**pmhub-job-dev.yml**文件

image.png
image.png

  • 将项目打成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-job.jar /pmhub-job.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-job.jar"]
  • 将 Jar 包制作成镜像
docker build -t pmhub-job:v0.01 .

image.png

  • 启动**pmhub-job**, 再查看启动日志
docker run -d --name pmhub-job -p 6803:6803 pmhub-job:v0.01
docker logs -f pmhub-job

image.png

  • 检查是否部署成功, 有响应就是部署成功啦~

image.png

PmHub-monitor

  • **终于到最后一个服务啦, 马上就全部部署成功啦~ **😸

  • 修改**bootstrap.yml**配置文件

image.png

  • 将项目打成 Jar 包

image.png

  • 将 Jar 包上传服务器

image.png

  • 编写Dockerfile文件
# 基础镜像
FROM openjdk:11.0-jre-buster

# author
MAINTAINER ccoio

# 复制jar文件到路径
COPY pmhub-monitor.jar /pmhub-monitor.jar

# 启动网关服务
ENTRYPOINT ["java","-jar","/pmhub-monitor.jar"]
  • 将 Jar 包制作成镜像
docker build -t pmhub-monitor:v0.01 .

image.png

  • 启动**pmhub-monitor**, 再查看启动日志
docker run -d --name pmhub-monitor -p 6888:6888 pmhub-monitor:v0.01
docker logs -f pmhub-monitor

image.png

  • 检查是否部署成功
  • 输入**[**http://192.168.100.100:6888/login**](http://192.168.100.100:6888/login)** , 账号: admin 密码: 123456

image.png

image.png

小结

  • 至此全部服务都成功部署啦~
  • 我这种部署方式简单, 好理解, 就是略有些麻烦…
  • 但我觉得这样部署对于新手的理解会更好一些吧
  • 至少能让新手有更多实操的机会( 绝对不是我懒的写docker-compose! 哈哈哈)
  • 期待大家的反馈, 有问题可以评论区留言, 一起讨论, 一起学习, 共同进步 ! 🎉
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值