区块链部署相关请参考区块链使用连接工具demo测试MetaMask、Remix和Ganache-CSDN博客
安装区块链相关Geth(基于CentOS7)_geth安装-CSDN博客
1、部署合约生成合约地址和ABI文件
服务器上文件目录结构:/home/tower/back-end/deploy/deploy_contract.py
python
import os
from dotenv import load_dotenv
from web3 import Web3
from solcx import compile_source, install_solc
# 加载环境变量(创建.env文件配置以下参数)
load_dotenv()
# -------------------------- 配置参数(需修改为你的私有链信息)--------------------------
GETH_NODE_URL = os.getenv("GETH_NODE_URL", "http://8.152.4.160:8545") # Geth RPC地址
PRIVATE_KEY = os.getenv("PRIVATE_KEY") # MetaMask账户私钥(需有足够ETH)
SOLC_VERSION = "0.8.17" # 合约编译器版本
# -------------------------------------------------------------------------------------
# 1. 安装指定版本Solidity编译器
install_solc(SOLC_VERSION)
# 2. 智能合约源码(与之前设计一致)
CONTRACT_SOURCE_CODE = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract BatteryDataChain {
struct BatteryHealthData {
string batteryCode;
uint256 dataUpdateTime;
uint256 remainingPower;
uint256 cycleTimes;
uint256 totalMileage;
uint256 currentVoltage;
uint256 onChainTime;
}
mapping(string => mapping(uint256 => BatteryHealthData)) private batteryDataMap;
mapping(string => uint256[]) private batteryTimeStamps;
event DataStored(string indexed batteryCode, uint256 indexed dataUpdateTime, uint256 onChainTime);
function storeBatteryData(
string calldata _batteryCode,
uint256 _dataUpdateTime,
uint256 _remainingPower,
uint256 _cycleTimes,
uint256 _totalMileage,
uint256 _currentVoltage
) external {
BatteryHealthData memory data = BatteryHealthData({
batteryCode: _batteryCode,
dataUpdateTime: _dataUpdateTime,
remainingPower: _remainingPower,
cycleTimes: _cycleTimes,
totalMileage: _totalMileage,
currentVoltage: _currentVoltage,
onChainTime: block.timestamp
});
batteryDataMap[_batteryCode][_dataUpdateTime] = data;
batteryTimeStamps[_batteryCode].push(_dataUpdateTime);
emit DataStored(_batteryCode, _dataUpdateTime, block.timestamp);
}
function queryByDeviceIdAndTime(string calldata _batteryCode, uint256 _dataUpdateTime) external view returns (BatteryHealthData memory) {
return batteryDataMap[_batteryCode][_dataUpdateTime];
}
function queryByDeviceId(string calldata _batteryCode) external view returns (uint256[] memory, BatteryHealthData[] memory) {
uint256[] memory times = batteryTimeStamps[_batteryCode];
BatteryHealthData[] memory datas = new BatteryHealthData[](times.length);
for (uint256 i = 0; i < times.length; i++) {
datas[i] = batteryDataMap[_batteryCode][times[i]];
}
return (times, datas);
}
}
"""
def deploy_contract():
# 3. 连接Geth私有链
w3 = Web3(Web3.HTTPProvider(GETH_NODE_URL))
if not w3.is_connected():
raise Exception("无法连接到Geth私有链,请检查节点地址!")
print("✅ 成功连接到Geth私有链")
# 4. 编译合约
compiled_sol = compile_source(
CONTRACT_SOURCE_CODE,
solc_version=SOLC_VERSION,
output_values=["abi", "bin"]
)
contract_id, contract_interface = compiled_sol.popitem()
abi = contract_interface["abi"]
bytecode = contract_interface["bin"]
print("✅ 合约编译完成")
# 5. 构建合约对象
BatteryContract = w3.eth.contract(abi=abi, bytecode=bytecode)
# 6. 准备部署交易
account = w3.eth.account.from_key(PRIVATE_KEY)
nonce = w3.eth.get_transaction_count(account.address) # 获取账户nonce
deploy_tx = BatteryContract.constructor().build_transaction({
"from": account.address,
"nonce": nonce,
"gasPrice": w3.eth.gas_price,
"gas": 3000000 # 私有链可适当调高gas限制
})
# 7. 签名并发送交易
signed_tx = account.sign_transaction(deploy_tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"📤 部署交易已发送,交易哈希:{w3.to_hex(tx_hash)}")
# 8. 等待交易确认
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
contract_address = tx_receipt["contractAddress"]
print(f"🎉 合约部署成功!合约地址:{contract_address}")
# 9. 保存ABI和合约地址到文件(供后端使用)
with open("../backend/contract_abi.json", "w") as f:
import json
json.dump(abi, f)
with open("../backend/contract_address.txt", "w") as f:
f.write(contract_address)
print("✅ ABI和合约地址已保存到本地文件")
if __name__ == "__main__":
try:
deploy_contract()
except Exception as e:
print(f"❌ 部署失败:{str(e)}")
部署完成后,/home/tower/back-end/backend/ 目录下会多出两个文件:
contract_abi.json--- 合约的接口定义(告诉后端有哪些函数可以调用)contract_address.txt--- 合约在链上的地址(0xCCe205aEDC0BB878632A1751402df976cE1bF52A)
| 步骤 | 代码 | 做了什么 |
|---|---|---|
| 连接节点 | Web3(Web3.HTTPProvider(GETH_NODE_URL)) |
通过 HTTP 连接到 8.152.4.160:8545 的 Geth 节点 |
| 编译合约 | compile_source(..., solc_version="0.8.17") |
用 Solidity 编译器把源码编译成 ABI + 字节码 |
| 派生账户 | w3.eth.account.from_key(PRIVATE_KEY) |
从私钥推出你的账户地址 |
| 构建交易 | BatteryContract.constructor().build_transaction(...) |
构造部署合约的交易 |
| 签名+发送 | account.sign_transaction(deploy_tx) → send_raw_transaction |
用私钥签名后广播到链上 |
| 等待确认 | wait_for_transaction_receipt |
等矿工打包,拿到合约地址 |
| 保存产物 | 写 contract_abi.json + contract_address.txt |
这两个文件是部署阶段的输出,也是后端连接区块链的桥梁 |
2、区块链连接合约
/home/tower/back-end/backend/chain_interaction.py 文件的 get_contract() 函数建立连接:
python
def get_contract():
w3 = Web3(Web3.HTTPProvider(GETH_NODE_URL)) # 连到同一个 Geth 节点
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=CONTRACT_ABI) # 拿到合约对象
return w3, contract
有了 contract 对象之后,系统就能调用合约函数了。分两种操作:
写操作 --- 需要私钥签名:
python
account = w3.eth.account.from_key(PRIVATE_KEY) # 用私钥派生账户
tx = contract.functions.storeBatteryData(...).build_transaction({...})
signed_tx = account.sign_transaction(tx) # 签名
w3.eth.send_raw_transaction(signed_tx.raw_transaction) # 发送
读操作--- 此时不需要私钥:
python
data = contract.functions.queryByDeviceId(battery_code).call() # 直接读,免费且无需签名

3、区块链连接账户
参考安装区块链相关Geth(基于CentOS7)_geth安装-CSDN博客
Step 1 安装好区块链后,geth init genesis.json ------ 只是"初始化账本"
| 动作 | 结果 |
|---|---|
| 创建数据目录结构 | 生成 data/geth/chaindata 等数据库文件 |
| 写入创世块 | 账本里有了区块 0 |
记录 alloc |
把 0x486d...0648 的 1000 ETH 写进世界状态 |
注意:它不生成私钥、不写 keystore、不产生账号。它只是"账本上记了一笔:这个地址有 1000 ETH"。这笔钱是谁的、有没有钥匙,它不管。
Step 2 启动节点 geth --networkid 1337 --mine ------ 只是"让链跑起来"
它做的事:进程运行、监听端口、开始出块、给矿工地址发奖励。
它同样不创建账号。它只是开始"运营"上面那个账本。
Step 3. 账号到底在哪一步创建?
只有 geth account new (或 console 里的 personal.newAccount())会创建账号,它的产物是:
私钥(随机256位)→ 推导出地址 → 私钥用密码加密后写成 keystore 文件
上述的 keystore 文件 UTC--2025-11-23...9054...a2beb 就是这么来的------它是独立于创世块的另一次操作的产物。

将上述 keyStore 中 UTC-文件进行解码,即可得到private_key。
解码文件:
python
from eth_account import Account
import json
# 替换为你的 keystore 文件路径
with open("UTC--2025-06-17T03-05-18.222102348Z--5c993678d3358462619906c9d5fbc93e98f1490a") as keyfile:
encrypted_key = keyfile.read()
password = "123" # 你设置的密码
private_key = Account.decrypt(encrypted_key, password)
print("Private key (hex):", private_key.hex())
4、检查区块链账户
Step 1 --- 检查现状:
geth attach http://8.152.4.160:8545 -exec 'eth.accounts' # 有哪些账户
geth attach http://8.152.4.160:8545 -exec 'eth.coinbase' # 当前矿工地址
geth attach http://8.152.4.160:8545 -exec 'eth.mining' # 是否在挖(true/false)
geth attach http://8.152.4.160:8545 -exec 'eth.blockNumber' # 当前区块高度
Step 2 --- 把矿工地址设为 eth.accounts0:
geth attach http://8.152.4.160:8545 -exec 'miner.setEtherbase(eth.accounts[0])'
# 返回 true 即成功;再执行 eth.coinbase 确认已变成该地址
Step 3 --- 解锁账户(如果是 Clique/PoA 私有链,这步是必须的;纯 PoW 可以跳过):
geth attach http://8.152.4.160:8545 -exec 'personal.unlockAccount(eth.accounts[0], "你的密码", 0)'
0 表示永久解锁(直到节点重启)。注意:这个密码就是你创建 keystore 时设的密码(就是解私钥用的同一个密码)。
Step 4 --- 启动挖矿:
geth attach http://8.152.4.160:8545 -exec 'miner.start()'
Step 5 --- 验证在挖 + 奖励到账(隔一会儿再看):
geth attach http://8.152.4.160:8545 -exec 'eth.mining' # true
geth attach http://8.152.4.160:8545 -exec 'eth.blockNumber' # 数字在涨
geth attach http://8.152.4.160:8545 -exec 'eth.getBalance(eth.accounts[0])' # 余额在涨
geth attach http://8.152.4.160:8545 -exec 'miner.getHashrate()' # 算力
setp 6 --- 停止挖矿:
geth attach http://8.152.4.160:8545 -exec 'miner.stop()'
5、修改环境变量 .env文件
检查完当前挖矿账户后,在步骤3中解码 private_key,将获取到的解码 key 放在/home/tower/back-end/backend/下的 .env文件中,至此合约文件、数据上区块链与私有链绑定成功,通过 PRIVATE_KEY = os.getenv("PRIVATE_KEY") 获取,进行交易即可完成。
