Go与智能合约的交互

1.前言在区块链开发中,智能合约是链上逻辑的核心,而链下服务则承担着调用合约、读取状态、发起交易等关键角色。对于需要构建后端接口、自动化脚本、监控服务等功能的开发者来说,熟练掌握如何用传统语言与合约交互至关重要。在众多语言中,Go以其性能、稳定性和原生以太坊生态支持(如Geth)成为构建链

1. 前言

在区块链开发中,智能合约是链上逻辑的核心,而链下服务则承担着调用合约、读取状态、发起交易等关键角色。对于需要构建后端接口、自动化脚本、监控服务等功能的开发者来说,熟练掌握如何用传统语言与合约交互至关重要。

在众多语言中,Go 以其性能、稳定性和原生以太坊生态支持(如 Geth)成为构建链下服务的首选之一。特别是在需要高并发、长时间运行的服务(如 bot、跨链中继、预言机)中,Go 表现尤为出色。 这篇文章将分享我在 Windows 10 + Foundry 环境下,使用 Go 与智能合约交互的实践经验,适合以下开发者: ● 有一定 Go 或区块链基础,但首次尝试将 Go 接入智能合约 ● 正在使用 Foundry 进行合约开发,想通过 Go 编写测试或服务端逻辑 ● 希望在本地快速完成 ABI 加载、合约绑定、交易调用、状态查询等常见操作

文章以一个简单的 Counter 合约为例,完整覆盖从环境准备、合约部署、Go 调用,到测试验证的全过程。重点不是零基础教学,而是帮你打通工具链、理清流程、快速实战落地。

2. 部署合约

大概流程

  1. 使用Foundry初始化项目,项目名为counter
  2. 项目编译build,编译后自动生成ABI文件
  3. 启动anvil本地网络,部署合约

2.1 初始化项目

使用git bash客户端运行

forge init counter

Initializing D:\dapp\counter...
Installing forge-std in D:/dapp/counter\lib\forge-std (url: Some("https://github.com/foundry-rs/forge-std"), tag: None)
Cloning into 'D:/dapp/counter/lib/forge-std'...
remote: Enumerating objects: 2111, done.
remote: Counting objects: 100% (1042/1042), done.
remote: Compressing objects: 100% (148/148), done.
remote: Total 2111 (delta 955), reused 906 (delta 894), pack-reused 1069 (from 1)
Receiving objects: 100% (2111/2111), 680.96 KiB | 651.00 KiB/s, done.
Resolving deltas: 100% (1431/1431), done.
warning: in the working copy of '.gitmodules', LF will be replaced by CRLF the next time Git touches it
    Installed forge-std v1.9.7
    Initialized forge project

初始化后的项目结构

$ cd counter/
$ ls
README.md  foundry.toml  lib/  script/  src/  test/

2.2 项目编译

编译完成后,ABI 文件会生成在 out 目录中

$ forge build
[⠊] Compiling...
[⠑] Compiling 23 files with Solc 0.8.28
[⠃] Solc 0.8.28 finished in 1.63s
Compiler run successful!

编译后项目结构:

$ ls
README.md  cache/  foundry.toml  lib/  out/  script/  src/  test/

# 只有一个Counter合约
$ ls src/
Counter.sol

# 在out中找到Counter合约编译后的json文件
$ ls out/Counter.sol/
Counter.json

查看编译的ABI文件内容:out/Counter.sol/Counter.json

  • ABI (Application Binary Interface): 用于与智能合约交互的接口定义。——单独提取这个json对象
  • bytecode: 合约的已编译字节码,用于部署到以太坊网络。
  • metadata: 包括合约的编译器版本、源代码哈希等信息。
  • 其他调试信息: 如合约的函数签名、事件定义等。

whiteboard_exported_image.png

2.3 提取ABI文件

作用:

  • 前端与合约交互时,使用 ABI 文件生成合约实例。
  • 调用合约方法或监听事件时,ABI 提供必要的接口信息。

不同语言在与智能合约交互时,通常<u>都有</u>以下两种方式:

  1. 动态加载:直接读取 Counter.abi.json 动态加载合约接口进行调用
  2. 静态绑定:通过语言特定的工具库,使用 Counter.abi.json 生成绑定代码,再通过类型安全的方式进行交互
语言 工具库 功能说明
Go go-ethereum(Geth) 使用 abigen 工具将 ABI 生成为 Go 包,实现合约的类型安全绑定与调用
Java Web3j 可自动从 ABI 和 BIN 文件生成 Java 类,与合约交互
JS/TS web3.js / ethers.js 可直接加载 ABI 文件并创建合约实例,进行方法调用
Rust ethers-rs 支持从 ABI 生成 Rust 类型绑定,或动态加载 ABI 调用
Python web3.py 可通过 ABI 动态创建合约对象,进行函数调用和事件监听

从out/Counter.sol/Counter.json中提取abi对象

$ jq '.abi' out/Counter.sol/Counter.json > Counter.abi.json

$ ls
Counter.abi.json  README.md  cache/  foundry.toml  lib/  out/  script/  src/  test/

查看abi文件内容,其中每个函数或事件以 JSON 对象的形式描述:

  • name: 函数或事件的名称。
  • type: 类型(如 function、constructor、event 等)。
  • inputs: 输入参数的数组,描述参数名称、类型等。
  • outputs: 输出参数的数组(仅函数有)。
  • stateMutability: 函数的状态可变性(如 view、pure、nonpayable、payable)。
$ jq . Counter.abi.json
[
  {
    "type": "function",
    "name": "increment",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "number",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "setNumber",
    "inputs": [
      {
        "name": "newNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  }
]

2.4 部署合约

启动anvil本地网络

$ anvil
                             _   _
                            (_) | |
      __ _   _ __   __   __  _  | |
     / _` | | '_ \  \ \ / / | | | |
    | (_| | | | | |  \ V /  | | | |
     \__,_| |_| |_|   \_/   |_| |_|

    1.0.0-stable (e144b82070 2025-02-13T20:02:16.393821500Z)
    https://github.com/foundry-rs/foundry

默认账户
Available Accounts
==================

(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.000000000000000000 ETH)
(1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000.000000000000000000 ETH)
(2) 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC (10000.000000000000000000 ETH)
(3) 0x90F79bf6EB2c4f870365E785982E1f101E93b906 (10000.000000000000000000 ETH)
(4) 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 (10000.000000000000000000 ETH)
(5) 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc (10000.000000000000000000 ETH)
(6) 0x976EA74026E726554dB657fA54763abd0C3a0aa9 (10000.000000000000000000 ETH)
(7) 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 (10000.000000000000000000 ETH)
(8) 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f (10000.000000000000000000 ETH)
(9) 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 (10000.000000000000000000 ETH)

默认私钥,和上面的账户一一对应
Private Keys
==================

(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6

默认钱包,助记词,
Wallet
==================
Mnemonic:          test test test test test test test test test test test junk
Derivation path:   m/44'/60'/0'/0/

链id
Chain ID
==================
31337

Base Fee
==================
1000000000

Gas Limit
==================
30000000

Genesis Timestamp
==================
1745993379

# 本地节点的url
Listening on 127.0.0.1:8545

部署合约

forge script script/Counter.s.sol \
--rpc-url 127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast

[⠃] Compiling...
No files changed, compilation skipped
Script ran successfully.
## Setting up 1 EVM.
==========================
Chain 31337
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 203856
Estimated amount required: 0.000407712000203856 ETH
==========================
##### anvil-hardhat
✅  [Success] Hash: 0x6795deaad7fd483eda4b16af7d8b871c7f6e49beb50709ce1cf0ca81c29247d1
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3  合约地址,每次部署都不一样
Block: 1
Paid: 0.000156813000156813 ETH (156813 gas * 1.000000001 gwei)

✅ Sequence #1 on anvil-hardhat | Total Paid: 0.000156813000156813 ETH (156813 gas * avg 1.000000001 gwei)                                                                                                                                                                                                                      
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Transactions saved to: D:/dapp/test_go_solidity/broadcast\Counter.s.sol\31337\run-latest.json
Sensitive values saved to: D:/dapp/test_go_solidity/cache\Counter.s.sol\31337\run-latest.json

3. Go对接

Go 是以太坊生态中的核心语言,最著名的以太坊客户端 Geth(go-ethereum) 就是用 Go 编写的。Go 语言具备部署轻量、高性能和并发友好的特性,适合构建区块链后端服务、合约交互接口以及链上自动化工具。

在 Go 项目中对接合约,主要有两种方式:

  • 静态绑定方式(推荐)
    使用 abigen 工具将合约 ABI 封装为 .go 文件(如 Counter.go),开发体验好,类型安全、IDE 可提示方法名、参数等。
  • 动态 ABI 加载方式
    直接读取 Counter.abi.json,通过 ABI 反射进行函数调用,适合脚本化、通用工具或测试环境。

对接Counter合约为例:

  1. 创建一个go项目:使用GoLand初始化一个项目
  2. 拷贝ABI的Counter.abi.json 和 Counter.go 到项目的contracts文件夹:包含了下面两种对接方式
  3. 拷贝测试代码
  4. go mod tidy 加载依赖
  5. 点击测试执行
# 项目结构如下:
D:\go_prj\go_test> tree /f
D:.
│  go.mod
│  go.sum
├─.idea
│      .gitignore
│      go_test2.iml
│      modules.xml
│      workspace.xml
├─contracts
│      Counter.abi.json
│      Counter.go
├─test1
│      test1.go
└─test2
        test2.go

3.1 动态加载

直接将 Counter.abi.json 拷贝到go项目的contracts文件夹中

package main

import (
    "bytes"
    "context"
    "crypto/ecdsa"
    "fmt"
    "log"
    "math/big"
    "os"
    "time"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"
)

const (
    RPC_URL          = "http://localhost:8545"
    CHAIN_ID         = 31337
    PRIVATE_KEY      = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
    CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
    ABI_PATH         = "contracts/Counter.abi.json"
)

func main() {
    // 连接链、加载私钥、加载 ABI
    client := MustConnectClient(RPC_URL)
    privateKey := MustLoadPrivateKey(PRIVATE_KEY)
    fromAddr := crypto.PubkeyToAddress(privateKey.PublicKey)
    contractABI := MustLoadABI(ABI_PATH)
    contractAddr := common.HexToAddress(CONTRACT_ADDRESS)
    // 构建交易签名器
    auth := MustBuildTransactor(client, privateKey, big.NewInt(CHAIN_ID))

    // 调用 setNumber 方法(交易)
    SendContractTx(client, auth, contractAddr, contractABI, "setNumber", big.NewInt(99))
    // 读取当前计数器值
    CallContractView(client, fromAddr, contractAddr, contractABI, "number")

    // 调用 increment 方法(交易)
    auth.Nonce = new(big.Int).Add(auth.Nonce, big.NewInt(1)) // 手动递增 nonce
    SendContractTx(client, auth, contractAddr, contractABI, "increment")

    // 读取当前计数器值
    CallContractView(client, fromAddr, contractAddr, contractABI, "number")
}

func MustConnectClient(url string) *ethclient.Client {
    client, err := ethclient.Dial(url)
    if err != nil {
        log.Fatalf("连接客户端失败: %v", err)
    }
    return client
}

func MustLoadPrivateKey(hexkey string) *ecdsa.PrivateKey {
    key, err := crypto.HexToECDSA(hexkey)
    if err != nil {
        log.Fatalf("加载私钥失败: %v", err)
    }
    return key
}

func MustLoadABI(path string) abi.ABI {
    data, err := os.ReadFile(path)
    if err != nil {
        log.Fatalf("读取 ABI 文件失败: %v", err)
    }
    parsedABI, err := abi.JSON(bytes.NewReader(data))
    if err != nil {
        log.Fatalf("解析 ABI 失败: %v", err)
    }
    return parsedABI
}

func MustBuildTransactor(client *ethclient.Client, key *ecdsa.PrivateKey, chainID *big.Int) *bind.TransactOpts {
    from := crypto.PubkeyToAddress(key.PublicKey)
    nonce, err := client.PendingNonceAt(context.Background(), from)
    if err != nil {
        log.Fatalf("获取 nonce 失败: %v", err)
    }
    gasPrice, _ := client.SuggestGasPrice(context.Background())

    auth, err := bind.NewKeyedTransactorWithChainID(key, chainID)
    if err != nil {
        log.Fatalf("创建 transactor 失败: %v", err)
    }
    auth.Nonce = big.NewInt(int64(nonce))
    auth.Value = big.NewInt(0)
    auth.GasLimit = 300000
    auth.GasPrice = gasPrice

    return auth
}

func CallContractView(client *ethclient.Client, from, to common.Address, contractABI abi.ABI, method string, args ...interface{}) {
    input, err := contractABI.Pack(method, args...)
    if err != nil {
        log.Fatalf("打包 ABI 方法失败: %v", err)
    }

    msg := ethereum.CallMsg{From: from, To: &to, Data: input}
    output, err := client.CallContract(context.Background(), msg, nil)
    if err != nil {
        log.Fatalf("调用链上方法失败: %v", err)
    }

    var result *big.Int
    if err := contractABI.UnpackIntoInterface(&result, method, output); err != nil {
        log.Fatalf("解析返回值失败: %v", err)
    }

    fmt.Println("查询成功:", result)
}

func SendContractTx(client *ethclient.Client, auth *bind.TransactOpts, contract common.Address, contractABI abi.ABI, method string, args ...interface{}) {
    data, err := contractABI.Pack(method, args...)
    if err != nil {
        log.Fatalf("打包交易数据失败: %v", err)
    }

    tx := types.NewTransaction(auth.Nonce.Uint64(), contract, auth.Value, auth.GasLimit, auth.GasPrice, data)
    signedTx, err := auth.Signer(auth.From, tx)
    if err != nil {
        log.Fatalf("签名交易失败: %v", err)
    }

    if err := client.SendTransaction(context.Background(), signedTx); err != nil {
        log.Fatalf("发送交易失败: %v", err)
    }

    hash := signedTx.Hash()
    fmt.Println("发送交易成功:", hash.Hex())
    time.Sleep(1 * time.Second) // 等待交易确认
}

执行结果

发送交易成功: 0x5f07e13ecbe8d304a7271c2bfd1fa6d056c1cff48900eb9b85cbc2ac1c92c6a8
查询成功: 99
发送交易成功: 0xb2c173bc2d5c87637aeac3fdd69f286d40d9a07a309fa9bb8b9124c2d7bba4c0
查询成功: 100

3.2 静态绑定

生成 Counter.go 拷贝到go项目的contracts文件夹中

# 回到counter项目中执行
$ pwd
/d/dapp/counter

# 使用geth(Go语言的以太坊客户端)的abigen命令,将ABI文件生成与之交互的Go语言代码
# 生成 Counter.go 拷贝到go项目的contracts文件夹中
$ abigen --abi=Counter.abi.json --pkg=counter --out=Counter.go

对接案例:

package main

import (
    "context"
    "crypto/ecdsa"
    "fmt"
    "github.com/ethereum/go-ethereum/core/types"
    "log"
    "math/big"
    "reflect"
    "time"
    _ "time"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"

    counter "go_test/contracts" // 替换为你的模块名
)

// 从anvil启动后的控制台中获取
const (
    RPC_URL          = "http://localhost:8545"
    CHAIN_ID         = 31337
    PRIVATE_KEY      = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
    CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
)

func main() {
    client := connectToClient()
    privateKey := loadPrivateKey(PRIVATE_KEY)
    auth := createTransactor(client, privateKey, big.NewInt(CHAIN_ID))
    instance := deployOrLoadContract(client, common.HexToAddress(CONTRACT_ADDRESS))

    SendContractTx(instance.SetNumber, auth, big.NewInt(99))
    CallContractView(instance.Number, nil)
    //setNumber(instance, auth)
    //queryNumber(instance)

    auth.Nonce = new(big.Int).Add(auth.Nonce, big.NewInt(1)) // 手动递增 nonce
    SendContractTx(instance.Increment, auth)
    CallContractView(instance.Number, nil)
    //increment(instance, auth)
    //queryNumber(instance)
}

// 连接到以太坊节点
func connectToClient() *ethclient.Client {
    client, err := ethclient.Dial(RPC_URL)
    if err != nil {
        log.Fatal("连接客户端失败:", err)
    }
    return client
}

// 加载私钥
func loadPrivateKey(keyStr string) *ecdsa.PrivateKey {
    privateKey, err := crypto.HexToECDSA(keyStr)
    if err != nil {
        log.Fatal("加载私钥失败:", err)
    }
    return privateKey
}

// 创建交易签名器
func createTransactor(client *ethclient.Client, privateKey *ecdsa.PrivateKey, chainID *big.Int) *bind.TransactOpts {
    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("公钥类型断言失败")
    }

    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal("获取 nonce 失败:", err)
    }

    auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
    if err != nil {
        log.Fatal("创建 transactor 失败:", err)
    }

    gasPrice, _ := client.SuggestGasPrice(context.Background())

    auth.Nonce = big.NewInt(int64(nonce))
    auth.Value = big.NewInt(0)
    auth.GasLimit = uint64(3000000)
    auth.GasPrice = gasPrice

    return auth
}

// 加载合约实例
func deployOrLoadContract(client *ethclient.Client, contractAddress common.Address) *counter.Counter {
    instance, err := counter.NewCounter(contractAddress, client)
    if err != nil {
        log.Fatal("加载合约失败:", err)
    }
    return instance
}

// 调用 SetNumber 方法
func setNumber(instance *counter.Counter, auth *bind.TransactOpts) {
    tx, err := instance.SetNumber(auth, big.NewInt(100))
    if err != nil {
        log.Fatal("调用 SetNumber 失败:", err)
    }
    fmt.Println("SetNumber tx sent:", tx.Hash().Hex())
}

// 调用 Increment 方法
func increment(instance *counter.Counter, auth *bind.TransactOpts) {
    tx, err := instance.Increment(auth)
    if err != nil {
        log.Fatal("调用 Increment 失败:", err)
    }
    fmt.Println("Increment tx sent:", tx.Hash().Hex())
}

// 调用合约的方法并发送交易
func SendContractTx(method interface{}, auth *bind.TransactOpts, args ...interface{}) {
    methodVal := reflect.ValueOf(method)
    if methodVal.Kind() != reflect.Func {
        log.Fatal("传入的 method 不是函数")
    }

    // 构造参数:第一个是 auth,后续是方法参数
    in := []reflect.Value{reflect.ValueOf(auth)}
    for _, arg := range args {
        in = append(in, reflect.ValueOf(arg))
    }

    // 调用方法
    results := methodVal.Call(in)
    if len(results) != 2 {
        log.Fatal("期望返回 (tx, err),但函数返回数量不匹配")
    }

    // 解析返回值
    txVal := results[0].Interface()
    errVal := results[1].Interface()

    if errVal != nil {
        log.Fatalf("合约方法调用失败: %v", errVal)
    }

    tx := txVal.(*types.Transaction)
    fmt.Println("交易已发送,Tx Hash:", tx.Hash().Hex())
    time.Sleep(1 * time.Second) // 等待交易确认
}

// 查询当前 count 值
func queryNumber(instance *counter.Counter) {
    result, err := instance.Number(nil)
    if err != nil {
        log.Fatal("查询 Number 失败:", err)
    }
    fmt.Println("查询成功:", result)
}

func CallContractView(method interface{}, args ...interface{}) {
    methodVal := reflect.ValueOf(method)
    if methodVal.Kind() != reflect.Func {
        log.Fatal("传入的 method 不是函数")
    }

    // 构造参数列表
    in := []reflect.Value{}
    for _, arg := range args {
        if arg == nil {
            // 如果参数是 nil,我们用函数参数类型构造一个零值
            // 例如 bind.CallOpts 为 nil,构造一个零值表示默认
            numIn := methodVal.Type().NumIn()
            if len(in) &lt; numIn {
                in = append(in, reflect.Zero(methodVal.Type().In(len(in))))
            } else {
                log.Fatal("传入 nil 参数但无法推断类型")
            }
        } else {
            in = append(in, reflect.ValueOf(arg))
        }
    }

    // 调用方法
    results := methodVal.Call(in)

    if len(results) != 2 {
        log.Fatal("期望返回 (result, err),但函数返回数量不匹配")
    }

    // 解析返回值
    result := results[0].Interface()
    errVal := results[1].Interface()

    if errVal != nil {
        log.Fatalf("调用 view 方法失败: %v", errVal)
    }

    fmt.Println("查询成功:", result)
}

执行结果

交易已发送,Tx Hash: 0x8fb50b5ad2c6faf296bff8cc09cff91bbebe493b17393d7712d8c365457372a9
查询成功: 99
交易已发送,Tx Hash: 0x777dd7f67f3bca754a4c66fd05fa38e34e8a042473222cdc4d5df5003a089852
查询成功: 100
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
无说有听
无说有听
0x919C...8e48
hello