创建多节点 Fabirc 集群

发布于 2021-04-05 17:48:40 字数 20873 浏览 1059 评论 0

Hyperledger Fabric 的 fabric-samples/first-network 是在单机上运行的。本章描述如何将其部署到多台 VM 上。

假设有三台VM:

  • hostname:u1601 ip:192.168.16.101
  • hostname:u1602 ip:192.168.16.102
  • hostname:u1603 ip:192.168.16.103

计划将 u1603 当 orderer 节点,另两台当peer节点。
首先,按 Hyperledger Fabric Samples 的描述,在 3 台 VM 上都创建 Fabirc 运行环境,包括安装必要的二进制包,部署 docker 引擎和 docker-compose,部署 golang 环境,下载 Fabric 相关 docker 镜像等。

提示一个常用的 docker 命令,用于将所有容器删除,可用于peer或orderer的重启:

$ docker rm -f $(docker ps -aq)

生成密钥文件并复制(u1601)

登录u1601。首先进行环境清理,停止和删除现有docker容器,删除原有密钥文件。方法是:

$ cd /opt/fabric-samples/first-network
$ ./byfn.sh -m down

下面的命令生成密钥文件。密钥文件输出到了crypto-config目录下。

$ ../bin/cryptogen generate --config=./crypto-config.yaml

生成的密钥文件分别属于排序服务管理员(实际上是全局管理员)、两个组织管理员、4个peer管理员以及每个peer各一个的一般用户(参考)。

按正规要求,排序服务管理员的私钥文件应复制到u1603;org1组织的peer1节点的私钥文件复制到u1602。由于org1组织的peer0节点就是u1601,所以不用复制。因为本章只是验证Fabric-samples分布式部署,所以加密材料只是简单复制到了u1602和u1603,并没有删除应当保密的管理员私钥。

$ scp -r ./crypto-config root@u1602:/opt/fabric-samples/first-network/
$ scp -r ./crypto-config root@u1603:/opt/fabric-samples/first-network/

启动排序服务

要启动排序服务,首先要生成系统创世区块,然后编辑排序服务的docker-compose文件并启动。

$ ssh root@u1603
$ cd /opt/fabric-samples/first-network

生成系统通道创世区块

下面的命令生成系统通道的创世区块。文件输出到channel-artifacts目录下。

$ export FABRIC_CFG_PATH=$PWD
$ ../bin/configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block

编辑 docker-compose 配置文件

排序服务的docker-compose配置文件可以在原生docker-compose-cli.yaml上修改。

$ cp docker-compose-cli.yaml orderer.yaml

编辑orderer.yaml,删除大部分内容,只留下orderer相关的:

version: '2'
services:
  orderer.example.com:
    extends:
      file:   base/docker-compose-base.yaml
      service: orderer.example.com
    container_name: orderer.example.com

同原生定义的排序服务docker-compose配置文件相比,仅仅删除了byfn网络。这意味着,排序服务容器与其他容器的通信不再走docker的虚拟网络,而是通过宿主机网络(至于是bridge还是host模式,不清楚)。

启动 orderer 节点

$ docker-compose -f orderer.yaml up -d

(如果启动orderer时不加-d参数,屏幕会一直输出orderer容器的日志。)

准备peer0节点

使用u1601充当org1组织的peer0节点。

$ ssh root@u1601
$ cd /opt/fabric-samples/basic-network

生成事务文件

首先,生成创建应用通道的事务文件:

$ export CHANNEL_NAME=mychannel  
$ ../bin/configtxgen -profile TwoOrgsChannel \
 -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME

然后,生成“将peer0设置Org1的锚点peer”的事务文件:

$ ../bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate \
./channel-artifacts/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP

编辑 docker-compose 配置文件

计划在u1601上运行两个容器,peer0.org1.example.com和cli。前者是peer容器,后者是管理员用的客户端工具。在原生docker-compose-cli.yaml配置文件的基础上修改。

$ cp docker-compose-cli.yaml cli.yaml

编辑cli.yaml,修改成下面的样子:

version: '2'
networks:
  byfn:

services:
  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer0.org1.example.com
    extra_hosts:
     - "orderer.example.com:192.168.16.103"
#     - "peer1.org1.example.com:192.168.16.102"
    networks:
      - byfn

  cli:
    container_name: cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_default
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
#    command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY} ${LANG}; sleep $TIMEOUT'
    volumes:
        - /var/run/:/host/var/run/
        - ./../chaincode/:/opt/gopath/src/github.com/chaincode
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - peer0.org1.example.com
    extra_hosts:
     - "orderer.example.com:192.168.16.103"
    networks:
      - byfn

同原来的docker-compose-cli.yaml相比,首先多了一个extra_hosts定义。extra_hosts的值会自动加入到容器的/etc/hosts文件中,以便根据hostname找到位于其他VM的服务,如找到排序服务(orderer)。
byfn这个docker虚拟网络也是需要的。如果不定义,会导致链码实例化报错。用docker network ls命令可以看到byfn被自动命名为net_byfn。而peer服务是从base/docker-compose-base.yaml继承来的,其中有个环境变量的定义CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_byfn,在运行时这个环境变量的值应该是net_byfn。)
(如果peer0.org1.example.com服务中不定义orderer的extra_hosts,会导致链码无法部署。)

启动 peer 容器并初始化

使用cli.yaml启动peer0:

$ TIMEOUT=10000 CHANNEL_NAME=$CHANNEL_NAME docker-compose -f cli.yaml up -d

docker ps命令可以看到启动了两个容器:peer0.org1.example.comcli

创建通道和将 peer0 加入通道

还记得之前生成的两个事务文件channel.txOrg1MSPanchors.tx吗?下面利用这两个事务文件对Fabric进行初始化。

进入cli容器,并查看4个环境变量,这四个变量反应了当前cli正在以peer0的身份运行:

$ docker exec -it cli bash
$ echo $CORE_PEER_MSPCONFIGPATH && echo $CORE_PEER_ADDRESS && echo $CORE_PEER_LOCALMSPID && echo $CORE_PEER_TLS_ROOTCERT_FILE

(根据fabric目前版本的通道权限策略,谁创建谁就是通道管理员。所以mychannel的管理员应为peer0的管理员) 为创建通道,之前需要创建通道需要的其他设置环境变量:

$ export CHANNEL_NAME=mychannel && export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
$ peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f \
./channel-artifacts/channel.tx --tls --cafile $ORDERER_CA

上面命令将包含在channel-artifacts/channel.tx中的配置信息发送给orderer,并在当前目录下生成新通道(mychannel)的创世区块文件mychannel.block

通道创建成功后,需要将当前peer(即peer0)加入到通道mychannel

$ peer channel join -b mychannel.block

如果你弄丢了mychannel.block(例如你重启了peer0容器),还可以用下列命令重新获取:

$ peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

改变锚点 peer 定义

回忆一下,工件Org1MSPanchors.tx是之前生成的。下面利用这个Org1MSPanchors.tx对通道定义进行变更,将peer0.org1.example.com定义为Org1的锚点peer。

$ peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/Org1MSPanchors.tx --tls --cafile $ORDERER_CA

(当初犯过一个错误,现在明白了:工件属于peer,与orderer无关。工件可以视为peer对配置进行修改的一个参数。)

链码安装、实例化和使用

链码安装

fabric-samples自带的例子链码安装到peer0(u1601)上:

$ peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/

如果不配置peer0对orderer的extra_hosts依赖,上面的链码安装会失败。

链码实例化

下面是对将链码在peer0(u1601)上实例化:

$ peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"

在实测中一开始没有定义byfn这个网络,导致了链码实例化失败。

链码使用

查询:

$ peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
Query Result: 100

调用(即a减少10,b增加10):

$ peer chaincode invoke -o orderer.example.com:7050  --tls --cafile  $ORDERER_CA  -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'
Chaincode invoke successful. result: status:200

重新查询一下a的值:

$ peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
Query Result: 90

建立 peer1(u1602)

在u1602上部署org1的第二个peer节点,即peer1。

首先,从u1603(排序服务所在节点)上将加密材料复制过来:

$ ssh root@u1602
$ cd /opt/fabric-samples/first-network
$ scp -r root@u1603:/opt/fabric-samples/first-network/crypto-config .
$ scp root@u1601:/opt/fabric-samples/first-network/cli.yaml .

上面将peer0节点的docker-compose配置文件cli.yaml也复制过来了。
cli.yaml进行适当的编辑,成下面的样子:

version: '2'
networks:
  byfn:

services:

  peer1.org1.example.com:
    container_name: peer1.org1.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer1.org1.example.com
    extra_hosts:
     - "orderer.example.com:192.168.16.103"
     - "peer0.org1.example.com:192.168.16.101"
    networks:
      - byfn

  cli:
    container_name: cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer1.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
#    command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY} ${LANG}; sleep $TIMEOUT'
    volumes:
        - /var/run/:/host/var/run/
        - ./../chaincode/:/opt/gopath/src/github.com/chaincode
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - peer1.org1.example.com
    extra_hosts:
     - "orderer.example.com:192.168.16.103"
    networks:
      - byfn

另外还需要修改u1602节点的first-network/base/docker-compose-base.yaml文件,原来peer1的暴露端口是8051,现在改成7051:

  peer1.org1.example.com:
(略)
    ports:
      - 7051:7051
      - 7053:7053

(extra_hosts: - "peer0.org1.example.com:192.168.16.101"这一条必须要,否则会报错) 启动容器::

$ TIMEOUT=10000 CHANNEL_NAME=mychannel docker-compose -f cli.yaml up -d

将 peer1 加入通道

peer1的操作同peer0相比就比较简单了。它不是组织org1在通道mychannel的锚peer,仅需要加入通道即可。

$ docker exec -it cli bash
$ export CHANNEL_NAME=mychannel && export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
$ peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
$ peer channel join -b mychannel.block

peer1 链码测试

链码安装

fabric-samples自带的例子链码安装到peer1(u1602)上:

$ peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/

链码查询

对于通道中的同一组织的新peer,链码不用实例化直接调用(原理不明):

$ peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
Query Result: 90

Org3 及其 peer 加入网络

在节计划启用一个新的虚拟机u1604。在这个虚拟机中将部署org3的一个peer:peer0.org3.example.com
在开始本节前,请先阅读重新配置首个网络(First-Network)。本节将遵循这篇文章的步骤,将Org3添加到系统通道,并将Org3的peer0节点加入应用通道mychannel中。 在准备阶段,需要生成Org3的加密材料和事务文件。准备阶段将在u1601上进行,等完成后,再把加密材料和事务文件复制到u1604。

为 Org3 准备加密材料和事务文件

首先,生成加密材料。

$ ssh root@u1601
$ cd /opt/fabric-samples/first-network/org3-artifacts
$ ../../bin/cryptogen generate --config=./org3-crypto.yaml
$ export FABRIC_CFG_PATH=$PWD && ../../bin/configtxgen -printOrg Org3MSP > ../channel-artifacts/org3.json
$ cd ../ && cp -r crypto-config/ordererOrganizations org3-artifacts/crypto-config/

然后进入u1601的cli容器。

$ docker exec -it cli bash
$ apt update && apt install jq
$ configtxlator start &
$ CONFIGTXLATOR_URL=http://127.0.0.1:7059
$ export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem  && export CHANNEL_NAME=mychannel
$ peer channel fetch config config_block.pb -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
$ curl -X POST --data-binary @config_block.pb "$CONFIGTXLATOR_URL/protolator/decode/common.Block" | jq . > config_block.json
$ jq .data.data[0].payload.data.config config_block.json > config.json
$ jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1] } } } }}' config.json ./channel-artifacts/org3.json >& updated_config.json
$ curl -X POST --data-binary @config.json "$CONFIGTXLATOR_URL/protolator/encode/common.Config" > config.pb
$ curl -X POST --data-binary @updated_config.json "$CONFIGTXLATOR_URL/protolator/encode/common.Config" > updated_config.pb
$ curl -X POST -F channel=$CHANNEL_NAME -F "original=@config.pb" -F "updated=@updated_config.pb" "${CONFIGTXLATOR_URL}/configtxlator/compute/update-from-configs" > org3_update.pb
$ curl -X POST --data-binary @org3_update.pb "$CONFIGTXLATOR_URL/protolator/decode/common.ConfigUpdate" | jq . > org3_update.json
$ echo '{"payload":{"header":{"channel_header":{"channel_id":"mychannel", "type":2 } },"data":{"config_update":'$(cat org3_update.json)' } }}' | jq . > org3_update_in_envelope.json
$ curl -X POST --data-binary @org3_update_in_envelope.json "$CONFIGTXLATOR_URL/protolator/encode/common.Envelope" > org3_update_in_envelope.pb
$ peer channel signconfigtx -f org3_update_in_envelope.pb
$ peer channel update -f org3_update_in_envelope.pb -c $CHANNEL_NAME -o orderer.example.com:7050 --tls --cafile $ORDERER_CA

关于上述操作的解释,请参阅重新配置首个网络(First-Network)
这时,Org3的配置信息已经进入了系统通道的配置中。
下面该到u1604虚拟机去启动Org3的peer了。

为 Org3 准备 docker-compose 配置

从u1601将Org3的加密材料复制到u1604:

$ ssh root@u1604
$ cd /opt/fabric-samples/first-network
$ scp -r root@u1601:/opt/fabric-samples/first-network/org3-artifacts .

编辑BYFN自带的Org3的docker-compose配置文件docker-compose-org3.yaml为以下内容:

version: '2'
networks:
  byfn:
services:
  peer0.org3.example.com:
    container_name: peer0.org3.example.com
    extends:
      file: base/peer-base.yaml
      service: peer-base
    environment:
      - CORE_PEER_ID=peer0.org3.example.com
      - CORE_PEER_ADDRESS=peer0.org3.example.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:7051
      - CORE_PEER_LOCALMSPID=Org3MSP
    volumes:
        - /var/run/:/host/var/run/
        - ./org3-artifacts/crypto-config/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp
        - ./org3-artifacts/crypto-config/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls
    ports:
      - 7051:7051
      - 7053:7053
    extra_hosts:
     - "peer0.org1.example.com:192.168.16.101"
     - "peer1.org1.example.com:192.168.16.102"
     - "orderer.example.com:192.168.16.103"
    networks:
      - byfn

  Org3cli:
    container_name: Org3cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=Org3cli
      - CORE_PEER_ADDRESS=peer0.org3.example.com:7051
      - CORE_PEER_LOCALMSPID=Org3MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash -c 'sleep 10000'
    volumes:
        - /var/run/:/host/var/run/
        - ./../chaincode/:/opt/gopath/src/github.com/chaincode
        - ./org3-artifacts/crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
    depends_on:
      - peer0.org3.example.com
#      - peer1.org3.example.com
    extra_hosts:
     - "orderer.example.com:192.168.16.103"
    networks:
      - byfn

启动Org3的容器:

$ docker-compose -f docker-compose-org3.yaml up -d

进入Org3cli容器:

$ docker exec -it Org3cli bash

下面的操作分别是设置环境变量、获取mychannel的创世区块、将当前peer(peer0.org3.example.com)加入到mychannel

$ export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem && export CHANNEL_NAME=mychannel
$ peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
$ peer channel join -b mychannel.block

安装链码和升级背书策略

peer0.org3.example.com(u1604)上安装链码mycc,指定版本号为2.0是为了升级背书策略。

$ peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/

回到u1601。由于通道mychannel是u1601上的peer0创建的,peer0的管理员就是通道的管理员,所以升级链码的操作应在u1601上进行。

$ ssh root@u1601
$ docker exec -it cli bash
$ peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/
$ peer chaincode upgrade -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 2.0 -c '{"Args":["init","a","90","b","210"]}' -P "OR ('Org1MSP.member','Org3MSP.member')"

在 u1601 的 cli 容器中升级链码时,注意 export 上面的环境变量(如 $ORDERER_CA)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文