FISCO BCOS 部署Solidity投票智能合约并基于Java SDK 调用智能合约详细指南

目录

设计方案

一、环境准备

[1.1 安装FISCO BCOS节点](#1.1 安装FISCO BCOS节点)

[1.2 创建Java项目](#1.2 创建Java项目)

二、Solidity投票智能合约

[2.1 创建Voting.sol](#2.1 创建Voting.sol)

[2.2 编译合约](#2.2 编译合约)

[三、Java SDK配置与实现](#三、Java SDK配置与实现)

[3.1 配置文件](#3.1 配置文件)

[3.2 SDK配置类](#3.2 SDK配置类)

[3.3 合约服务类](#3.3 合约服务类)

[3.4 控制器类](#3.4 控制器类)

[3.5 主应用类](#3.5 主应用类)

四、部署与测试

[4.1 创建证书目录结构](#4.1 创建证书目录结构)

[4.2 编译和运行](#4.2 编译和运行)

[4.3 使用Postman测试API](#4.3 使用Postman测试API)

[1. 部署合约](#1. 部署合约)

[2. 授予投票权](#2. 授予投票权)

[3. 投票](#3. 投票)

[4. 查询获胜者](#4. 查询获胜者)

[5. 查询所有提案](#5. 查询所有提案)

五、高级功能扩展

[5.1 事件监听器](#5.1 事件监听器)

[5.2 数据库集成(可选)](#5.2 数据库集成(可选))

六、注意事项

[6.1 安全性考虑](#6.1 安全性考虑)

[6.2 性能优化](#6.2 性能优化)

[6.3 错误处理](#6.3 错误处理)


设计方案

我们使用FISCO BCOS Java SDK来调用智能合约。以下是步骤:

  1. 1、环境准备:确保已经安装并启动了FISCO BCOS节点,并且已经生成了SDK所需的证书(ca.crt、sdk.crt、sdk.key)。

  2. 2、编写Solidity投票智能合约(同上)。

  3. 3、编译Solidity合约,获取abi和bin文件。

  4. 4、使用Java SDK进行合约部署和调用。

我们将使用一个简单的Java项目,基于Spring Boot框架,使用Java SDK与链交互。

下面为您提供完整的FISCO BCOS投票合约部署和Java SDK调用实现方案。

一、环境准备

1.1 安装FISCO BCOS节点

bash 复制代码
# 下载安装脚本
curl -LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v3.4.0/build_chain.sh && chmod u+x build_chain.sh

# 构建单节点链
./build_chain.sh -l 127.0.0.1:4 -p 30300,20200,8545

# 启动节点
./nodes/127.0.0.1/start_all.sh

# 检查节点状态
./nodes/127.0.0.1/fisco-bcos -v

# 获取节点证书
cd nodes/127.0.0.1/
ls sdk/  # 查看ca.crt, sdk.crt, sdk.key等文件

1.2 创建Java项目

使用Maven创建项目:

XML 复制代码
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.fisco.bcos</groupId>
    <artifactId>voting-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <fisco-bcos-java-sdk.version>3.4.0</fisco-bcos-java-sdk.version>
        <spring-boot.version>2.7.0</spring-boot.version>
    </properties>
    
    <dependencies>
        <!-- FISCO BCOS Java SDK -->
        <dependency>
            <groupId>org.fisco-bcos.java-sdk</groupId>
            <artifactId>fisco-bcos-java-sdk</artifactId>
            <version>${fisco-bcos-java-sdk.version}</version>
        </dependency>
        
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- JSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.23</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

二、Solidity投票智能合约

2.1 创建Voting.sol

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Voting {
    // 投票者信息
    struct Voter {
        uint weight;        // 投票权重
        bool voted;         // 是否已投票
        uint8 voteIndex;    // 投票提案索引
        address delegate;   // 委托投票地址
    }
    
    // 提案信息
    struct Proposal {
        bytes32 name;       // 提案名称(bytes32格式)
        uint voteCount;     // 得票数
    }
    
    // 状态变量
    address public chairperson;                 // 主持人地址
    mapping(address => Voter) public voters;    // 投票者映射
    Proposal[] public proposals;                // 提案列表
    
    // 事件定义
    event VoterRegistered(address indexed voter);
    event Voted(address indexed voter, uint indexed proposalIndex);
    event Delegated(address indexed from, address indexed to);
    event ProposalCreated(bytes32 indexed name);
    event VotingResult(uint winningProposalIndex, bytes32 winningProposalName);
    
    /**
     * @dev 构造函数,初始化提案
     * @param proposalNames 提案名称数组
     */
    constructor(bytes32[] memory proposalNames) {
        chairperson = msg.sender;
        
        // 主持人自动获得投票权
        voters[chairperson] = Voter({
            weight: 1,
            voted: false,
            voteIndex: 0,
            delegate: address(0)
        });
        
        // 创建提案
        for (uint i = 0; i < proposalNames.length; i++) {
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
            emit ProposalCreated(proposalNames[i]);
        }
    }
    
    /**
     * @dev 主持人授予投票权
     * @param voter 投票者地址
     */
    function giveRightToVote(address voter) public {
        require(msg.sender == chairperson, "Only chairperson can give voting rights");
        require(!voters[voter].voted, "The voter already voted");
        require(voters[voter].weight == 0, "Voter already has right");
        
        voters[voter].weight = 1;
        emit VoterRegistered(voter);
    }
    
    /**
     * @dev 批量授予投票权
     * @param votersArray 投票者地址数组
     */
    function giveRightToVoteBatch(address[] calldata votersArray) public {
        require(msg.sender == chairperson, "Only chairperson can give voting rights");
        
        for (uint i = 0; i < votersArray.length; i++) {
            address voter = votersArray[i];
            if (!voters[voter].voted && voters[voter].weight == 0) {
                voters[voter].weight = 1;
                emit VoterRegistered(voter);
            }
        }
    }
    
    /**
     * @dev 委托投票
     * @param to 被委托人地址
     */
    function delegate(address to) public {
        Voter storage sender = voters[msg.sender];
        require(!sender.voted, "You already voted");
        require(to != msg.sender, "Self-delegation is disallowed");
        require(voters[to].weight > 0, "Delegate has no voting right");
        
        // 查找最终委托地址
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;
            require(to != msg.sender, "Found loop in delegation");
        }
        
        sender.voted = true;
        sender.delegate = to;
        
        Voter storage delegateTo = voters[to];
        if (delegateTo.voted) {
            // 被委托人已投票,增加对应提案的票数
            proposals[delegateTo.voteIndex].voteCount += sender.weight;
        } else {
            // 被委托人未投票,增加其权重
            delegateTo.weight += sender.weight;
        }
        
        emit Delegated(msg.sender, to);
    }
    
    /**
     * @dev 投票
     * @param proposalIndex 提案索引
     */
    function vote(uint8 proposalIndex) public {
        Voter storage sender = voters[msg.sender];
        require(sender.weight > 0, "Has no right to vote");
        require(!sender.voted, "Already voted");
        require(proposalIndex < proposals.length, "Invalid proposal index");
        
        sender.voted = true;
        sender.voteIndex = proposalIndex;
        
        // 计票
        proposals[proposalIndex].voteCount += sender.weight;
        
        emit Voted(msg.sender, proposalIndex);
    }
    
    /**
     * @dev 计算获胜提案
     * @return winningProposalIndex 获胜提案索引
     */
    function winningProposal() public view returns (uint winningProposalIndex) {
        uint winningVoteCount = 0;
        for (uint i = 0; i < proposals.length; i++) {
            if (proposals[i].voteCount > winningVoteCount) {
                winningVoteCount = proposals[i].voteCount;
                winningProposalIndex = i;
            }
        }
    }
    
    /**
     * @dev 获取获胜提案名称
     * @return winnerName 获胜提案名称
     */
    function winnerName() public view returns (bytes32 winnerName) {
        winnerName = proposals[winningProposal()].name;
    }
    
    /**
     * @dev 结束投票并公布结果
     */
    function endVoting() public returns (uint, bytes32) {
        require(msg.sender == chairperson, "Only chairperson can end voting");
        
        uint winningIndex = winningProposal();
        bytes32 winningName = proposals[winningIndex].name;
        
        emit VotingResult(winningIndex, winningName);
        return (winningIndex, winningName);
    }
    
    /**
     * @dev 获取提案数量
     * @return 提案数量
     */
    function getProposalsCount() public view returns (uint) {
        return proposals.length;
    }
    
    /**
     * @dev 获取提案详情
     * @param index 提案索引
     * @return name 提案名称
     * @return voteCount 得票数
     */
    function getProposal(uint index) public view returns (bytes32 name, uint voteCount) {
        require(index < proposals.length, "Invalid proposal index");
        Proposal memory proposal = proposals[index];
        return (proposal.name, proposal.voteCount);
    }
    
    /**
     * @dev 获取所有提案信息
     * @return names 提案名称数组
     * @return voteCounts 得票数数组
     */
    function getAllProposals() public view returns (bytes32[] memory names, uint[] memory voteCounts) {
        uint length = proposals.length;
        names = new bytes32[](length);
        voteCounts = new uint[](length);
        
        for (uint i = 0; i < length; i++) {
            names[i] = proposals[i].name;
            voteCounts[i] = proposals[i].voteCount;
        }
    }
    
    /**
     * @dev 获取投票者信息
     * @param voterAddress 投票者地址
     * @return weight 投票权重
     * @return voted 是否已投票
     * @return voteIndex 投票提案索引
     * @return delegate 委托地址
     */
    function getVoter(address voterAddress) public view returns (
        uint weight,
        bool voted,
        uint8 voteIndex,
        address delegate
    ) {
        Voter memory voter = voters[voterAddress];
        return (voter.weight, voter.voted, voter.voteIndex, voter.delegate);
    }
}

2.2 编译合约

bash 复制代码
# 安装solc编译器
npm install -g solc

# 编译合约
solcjs --bin --abi Voting.sol -o build/

# 生成Java wrapper
# 使用FISCO BCOS提供的sol2java.sh工具
./sol2java.sh org.fisco.bcos.voting build/Voting.sol

三、Java SDK配置与实现

3.1 配置文件

XML 复制代码
# src/main/resources/application.yml
spring:
  application:
    name: fisco-voting-demo

fisco:
  bcos:
    # 链配置
    chain-id: 1
    group-id: 1
    
    # 节点连接配置
    nodes:
      - 127.0.0.1:20200
    
    # 证书配置
    cert-path: classpath:conf/ca.crt
    private-key: classpath:conf/sdk.key
    cert-file: classpath:conf/sdk.crt
    
    # 账户配置
    account:
      key-file: classpath:conf/sdk.key
      password: ""
      
    # 合约配置
    contract:
      voting:
        address: ""  # 部署后更新
        abi: classpath:contracts/Voting.abi
        bin: classpath:contracts/Voting.bin

3.2 SDK配置类

java 复制代码
// src/main/java/com/fisco/bcos/config/FiscoConfig.java
package com.fisco.bcos.config;

import lombok.Data;
import org.fisco.bcos.sdk.v3.BcosSDK;
import org.fisco.bcos.sdk.v3.client.Client;
import org.fisco.bcos.sdk.v3.config.ConfigOption;
import org.fisco.bcos.sdk.v3.config.exceptions.ConfigException;
import org.fisco.bcos.sdk.v3.crypto.keypair.CryptoKeyPair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@Configuration
@Data
public class FiscoConfig {
    
    @Value("${fisco.bcos.chain-id}")
    private int chainId;
    
    @Value("${fisco.bcos.group-id}")
    private int groupId;
    
    @Value("${fisco.bcos.nodes}")
    private String[] nodes;
    
    @Value("${fisco.bcos.cert-path}")
    private String certPath;
    
    @Value("${fisco.bcos.cert-file}")
    private String certFile;
    
    @Value("${fisco.bcos.private-key}")
    private String privateKey;
    
    @Bean
    public ConfigOption configOption() throws IOException, ConfigException {
        // 读取证书文件
        ClassPathResource caResource = new ClassPathResource(certPath);
        ClassPathResource sslCertResource = new ClassPathResource(certFile);
        ClassPathResource sslKeyResource = new ClassPathResource(privateKey);
        
        // 构建配置选项
        org.fisco.bcos.sdk.v3.config.model.ConfigProperty configProperty = 
            new org.fisco.bcos.sdk.v3.config.model.ConfigProperty();
        
        configProperty.setCryptoMaterial(
            new org.fisco.bcos.sdk.v3.config.model.CryptoMaterialConfig(
                caResource.getFile().getAbsolutePath(),
                sslCertResource.getFile().getAbsolutePath(),
                sslKeyResource.getFile().getAbsolutePath(),
                null,
                null
            )
        );
        
        configProperty.setNetwork(
            new org.fisco.bcos.sdk.v3.config.model.NetworkConfig(
                Arrays.asList(nodes)
            )
        );
        
        configProperty.setAmop(null);
        configProperty.setAccount(null);
        configProperty.setThreadPool(null);
        
        return new ConfigOption(configProperty, "config.toml");
    }
    
    @Bean
    public BcosSDK bcosSDK(ConfigOption configOption) {
        return new BcosSDK(configOption);
    }
    
    @Bean
    public Client client(BcosSDK bcosSDK) {
        return bcosSDK.getClient(groupId);
    }
    
    @Bean
    public CryptoKeyPair cryptoKeyPair(Client client) throws IOException {
        // 从文件加载私钥
        ClassPathResource keyResource = new ClassPathResource(privateKey);
        String privateKeyHex = org.apache.commons.io.FileUtils.readFileToString(
            keyResource.getFile(), "UTF-8"
        ).trim();
        
        // 创建密钥对
        return client.getCryptoSuite().createKeyPair(privateKeyHex);
    }
}

3.3 合约服务类

java 复制代码
// src/main/java/com/fisco/bcos/service/VotingService.java
package com.fisco.bcos.service;

import com.fisco.bcos.config.FiscoConfig;
import lombok.extern.slf4j.Slf4j;
import org.fisco.bcos.sdk.v3.client.Client;
import org.fisco.bcos.sdk.v3.codec.datatypes.generated.tuples.generated.Tuple4;
import org.fisco.bcos.sdk.v3.codec.datatypes.generated.tuples.generated.Tuple2;
import org.fisco.bcos.sdk.v3.contract.Contract;
import org.fisco.bcos.sdk.v3.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.v3.model.TransactionReceipt;
import org.fisco.bcos.sdk.v3.transaction.codec.decode.TransactionDecoderInterface;
import org.fisco.bcos.sdk.v3.transaction.codec.decode.TransactionDecoderService;
import org.fisco.bcos.sdk.v3.transaction.model.exception.ContractException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Service
@Slf4j
public class VotingService {
    
    @Autowired
    private Client client;
    
    @Autowired
    private CryptoKeyPair cryptoKeyPair;
    
    @Autowired
    private FiscoConfig fiscoConfig;
    
    @Value("${fisco.bcos.contract.voting.abi}")
    private String abiPath;
    
    @Value("${fisco.bcos.contract.voting.bin}")
    private String binPath;
    
    @Value("${fisco.bcos.contract.voting.address}")
    private String contractAddress;
    
    private Voting votingContract;
    private String abiContent;
    private String binContent;
    private TransactionDecoderInterface txDecoder;
    
    @PostConstruct
    public void init() throws IOException {
        // 读取ABI和BIN文件
        ClassPathResource abiResource = new ClassPathResource(abiPath);
        ClassPathResource binResource = new ClassPathResource(binPath);
        
        abiContent = StreamUtils.copyToString(abiResource.getInputStream(), StandardCharsets.UTF_8);
        binContent = StreamUtils.copyToString(binResource.getInputStream(), StandardCharsets.UTF_8);
        
        txDecoder = new TransactionDecoderService(client.getCryptoSuite());
        
        // 如果合约地址已配置,则加载合约
        if (contractAddress != null && !contractAddress.isEmpty()) {
            loadContract(contractAddress);
        }
    }
    
    /**
     * 部署投票合约
     */
    public Map<String, Object> deployContract(List<String> proposalNames) throws ContractException, IOException {
        log.info("Deploying voting contract with proposals: {}", proposalNames);
        
        // 转换提案名称为bytes32
        List<byte[]> bytes32Proposals = new ArrayList<>();
        for (String name : proposalNames) {
            byte[] bytes32 = new byte[32];
            byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
            System.arraycopy(nameBytes, 0, bytes32, 0, Math.min(nameBytes.length, 32));
            bytes32Proposals.add(bytes32);
        }
        
        // 部署合约
        votingContract = Voting.deploy(
            client, 
            cryptoKeyPair, 
            abiContent,
            binContent,
            bytes32Proposals
        );
        
        contractAddress = votingContract.getContractAddress();
        log.info("Contract deployed at address: {}", contractAddress);
        
        // 解析部署交易回执
        TransactionReceipt receipt = votingContract.getTransactionReceipt();
        
        Map<String, Object> result = new HashMap<>();
        result.put("contractAddress", contractAddress);
        result.put("transactionHash", receipt.getTransactionHash());
        result.put("status", receipt.getStatus());
        result.put("blockNumber", receipt.getBlockNumber());
        result.put("gasUsed", receipt.getGasUsed());
        result.put("proposals", proposalNames);
        
        return result;
    }
    
    /**
     * 加载已部署的合约
     */
    public void loadContract(String contractAddress) {
        log.info("Loading contract from address: {}", contractAddress);
        
        votingContract = Voting.load(
            contractAddress,
            abiContent,
            client,
            cryptoKeyPair
        );
        
        this.contractAddress = contractAddress;
        log.info("Contract loaded successfully");
    }
    
    /**
     * 授予投票权
     */
    public Map<String, Object> giveRightToVote(String voterAddress) throws ContractException {
        log.info("Giving voting right to: {}", voterAddress);
        
        TransactionReceipt receipt = votingContract.giveRightToVote(voterAddress);
        Map<String, Object> result = parseReceipt(receipt);
        
        log.info("Voting right granted to {}", voterAddress);
        return result;
    }
    
    /**
     * 批量授予投票权
     */
    public Map<String, Object> giveRightToVoteBatch(List<String> voterAddresses) throws ContractException {
        log.info("Batch giving voting rights to: {}", voterAddresses);
        
        TransactionReceipt receipt = votingContract.giveRightToVoteBatch(voterAddresses);
        Map<String, Object> result = parseReceipt(receipt);
        
        log.info("Batch voting rights granted successfully");
        return result;
    }
    
    /**
     * 投票
     */
    public Map<String, Object> vote(int proposalIndex) throws ContractException {
        log.info("Voting for proposal index: {}", proposalIndex);
        
        TransactionReceipt receipt = votingContract.vote(BigInteger.valueOf(proposalIndex));
        Map<String, Object> result = parseReceipt(receipt);
        
        // 解析投票事件
        List<Map<String, Object>> events = decodeEvents(receipt, "Voted");
        result.put("events", events);
        
        log.info("Vote cast for proposal {}", proposalIndex);
        return result;
    }
    
    /**
     * 委托投票
     */
    public Map<String, Object> delegate(String toAddress) throws ContractException {
        log.info("Delegating vote to: {}", toAddress);
        
        TransactionReceipt receipt = votingContract.delegate(toAddress);
        Map<String, Object> result = parseReceipt(receipt);
        
        // 解析委托事件
        List<Map<String, Object>> events = decodeEvents(receipt, "Delegated");
        result.put("events", events);
        
        log.info("Vote delegated to {}", toAddress);
        return result;
    }
    
    /**
     * 获取获胜提案
     */
    public Map<String, Object> getWinner() throws ContractException {
        log.info("Getting winning proposal");
        
        BigInteger winningIndex = votingContract.winningProposal();
        byte[] winningNameBytes = votingContract.winnerName();
        
        // 转换bytes32为字符串
        String winningName = new String(winningNameBytes, StandardCharsets.UTF_8).trim();
        
        Map<String, Object> result = new HashMap<>();
        result.put("winningIndex", winningIndex);
        result.put("winningName", winningName);
        
        log.info("Winning proposal: {} (index: {})", winningName, winningIndex);
        return result;
    }
    
    /**
     * 结束投票并公布结果
     */
    public Map<String, Object> endVoting() throws ContractException {
        log.info("Ending voting and announcing results");
        
        TransactionReceipt receipt = votingContract.endVoting();
        Map<String, Object> result = parseReceipt(receipt);
        
        // 解析投票结果事件
        List<Map<String, Object>> events = decodeEvents(receipt, "VotingResult");
        result.put("votingResult", events);
        
        log.info("Voting ended successfully");
        return result;
    }
    
    /**
     * 获取所有提案信息
     */
    public List<Map<String, Object>> getAllProposals() throws ContractException {
        log.info("Getting all proposals");
        
        BigInteger count = votingContract.getProposalsCount();
        List<Map<String, Object>> proposals = new ArrayList<>();
        
        for (int i = 0; i < count.intValue(); i++) {
            Tuple2<byte[], BigInteger> proposal = votingContract.getProposal(BigInteger.valueOf(i));
            
            Map<String, Object> proposalInfo = new HashMap<>();
            proposalInfo.put("index", i);
            proposalInfo.put("name", new String(proposal.getValue1(), StandardCharsets.UTF_8).trim());
            proposalInfo.put("voteCount", proposal.getValue2());
            
            proposals.add(proposalInfo);
        }
        
        log.info("Found {} proposals", proposals.size());
        return proposals;
    }
    
    /**
     * 获取提案数量
     */
    public int getProposalCount() throws ContractException {
        BigInteger count = votingContract.getProposalsCount();
        return count.intValue();
    }
    
    /**
     * 获取投票者信息
     */
    public Map<String, Object> getVoterInfo(String voterAddress) throws ContractException {
        log.info("Getting voter info for: {}", voterAddress);
        
        Tuple4<BigInteger, Boolean, BigInteger, String> voter = votingContract.getVoter(voterAddress);
        
        Map<String, Object> voterInfo = new HashMap<>();
        voterInfo.put("address", voterAddress);
        voterInfo.put("weight", voter.getValue1());
        voterInfo.put("voted", voter.getValue2());
        voterInfo.put("voteIndex", voter.getValue3());
        voterInfo.put("delegate", voter.getValue4());
        
        log.info("Voter info retrieved");
        return voterInfo;
    }
    
    /**
     * 获取合约地址
     */
    public String getContractAddress() {
        return contractAddress;
    }
    
    /**
     * 解析交易回执
     */
    private Map<String, Object> parseReceipt(TransactionReceipt receipt) {
        Map<String, Object> result = new HashMap<>();
        result.put("transactionHash", receipt.getTransactionHash());
        result.put("status", receipt.getStatus());
        result.put("blockNumber", receipt.getBlockNumber());
        result.put("gasUsed", receipt.getGasUsed());
        result.put("from", receipt.getFrom());
        result.put("to", receipt.getTo());
        result.put("output", receipt.getOutput());
        
        return result;
    }
    
    /**
     * 解码事件日志
     */
    private List<Map<String, Object>> decodeEvents(TransactionReceipt receipt, String eventName) {
        List<Map<String, Object>> events = new ArrayList<>();
        
        try {
            org.fisco.bcos.sdk.v3.codec.EventValues eventValues = 
                txDecoder.decodeEvents(receipt.getLogEntries(), abiContent, eventName);
            
            if (eventValues != null && eventValues.getNonIndexedValues() != null) {
                for (int i = 0; i < eventValues.getNonIndexedValues().size(); i++) {
                    Map<String, Object> event = new HashMap<>();
                    event.put("name", eventName);
                    event.put("value", eventValues.getNonIndexedValues().get(i).getValue());
                    events.add(event);
                }
            }
        } catch (Exception e) {
            log.error("Failed to decode events: {}", e.getMessage());
        }
        
        return events;
    }
}

// 合约Java Wrapper类(通过sol2java.sh生成)
// 此处为简化版,实际应使用工具生成
class Voting extends Contract {
    public Voting(String contractAddress, String abi, Client client, CryptoKeyPair credential) {
        super(abi, contractAddress, client, credential);
    }
    
    public static Voting deploy(Client client, CryptoKeyPair credential, String abi, String binary, List<byte[]> proposalNames) {
        return deploy(Voting.class, client, credential, abi, binary, "", proposalNames);
    }
    
    public static Voting load(String contractAddress, String abi, Client client, CryptoKeyPair credential) {
        return new Voting(contractAddress, abi, client, credential);
    }
    
    public TransactionReceipt giveRightToVote(String voter) {
        return executeTransaction("giveRightToVote", voter);
    }
    
    public TransactionReceipt giveRightToVoteBatch(List<String> voters) {
        return executeTransaction("giveRightToVoteBatch", voters);
    }
    
    public TransactionReceipt vote(BigInteger proposalIndex) {
        return executeTransaction("vote", proposalIndex);
    }
    
    public TransactionReceipt delegate(String to) {
        return executeTransaction("delegate", to);
    }
    
    public BigInteger winningProposal() throws ContractException {
        return call("winningProposal");
    }
    
    public byte[] winnerName() throws ContractException {
        return call("winnerName");
    }
    
    public TransactionReceipt endVoting() {
        return executeTransaction("endVoting");
    }
    
    public BigInteger getProposalsCount() throws ContractException {
        return call("getProposalsCount");
    }
    
    public Tuple2<byte[], BigInteger> getProposal(BigInteger index) throws ContractException {
        return call("getProposal", index);
    }
    
    public Tuple4<BigInteger, Boolean, BigInteger, String> getVoter(String voterAddress) throws ContractException {
        return call("getVoter", voterAddress);
    }
}

3.4 控制器类

java 复制代码
// src/main/java/com/fisco/bcos/controller/VotingController.java
package com.fisco.bcos.controller;

import com.fisco.bcos.service.VotingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/voting")
@Slf4j
public class VotingController {
    
    @Autowired
    private VotingService votingService;
    
    /**
     * 部署投票合约
     */
    @PostMapping("/deploy")
    public Map<String, Object> deployContract(@RequestBody Map<String, Object> request) {
        try {
            @SuppressWarnings("unchecked")
            List<String> proposals = (List<String>) request.get("proposals");
            
            if (proposals == null || proposals.isEmpty()) {
                proposals = Arrays.asList("Proposal A", "Proposal B", "Proposal C");
            }
            
            Map<String, Object> result = votingService.deployContract(proposals);
            result.put("success", true);
            result.put("message", "Contract deployed successfully");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to deploy contract: {}", e.getMessage(), e);
            return createErrorResponse("Deploy failed: " + e.getMessage());
        }
    }
    
    /**
     * 加载合约
     */
    @PostMapping("/load")
    public Map<String, Object> loadContract(@RequestBody Map<String, Object> request) {
        try {
            String contractAddress = (String) request.get("contractAddress");
            votingService.loadContract(contractAddress);
            
            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("message", "Contract loaded successfully");
            result.put("contractAddress", contractAddress);
            
            return result;
        } catch (Exception e) {
            log.error("Failed to load contract: {}", e.getMessage(), e);
            return createErrorResponse("Load failed: " + e.getMessage());
        }
    }
    
    /**
     * 授予投票权
     */
    @PostMapping("/grant")
    public Map<String, Object> grantVotingRight(@RequestBody Map<String, Object> request) {
        try {
            String voterAddress = (String) request.get("voterAddress");
            
            Map<String, Object> result = votingService.giveRightToVote(voterAddress);
            result.put("success", true);
            result.put("message", "Voting right granted");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to grant voting right: {}", e.getMessage(), e);
            return createErrorResponse("Grant failed: " + e.getMessage());
        }
    }
    
    /**
     * 批量授予投票权
     */
    @PostMapping("/grant/batch")
    public Map<String, Object> grantVotingRightBatch(@RequestBody Map<String, Object> request) {
        try {
            @SuppressWarnings("unchecked")
            List<String> voterAddresses = (List<String>) request.get("voterAddresses");
            
            Map<String, Object> result = votingService.giveRightToVoteBatch(voterAddresses);
            result.put("success", true);
            result.put("message", "Batch voting rights granted");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to grant batch voting rights: {}", e.getMessage(), e);
            return createErrorResponse("Batch grant failed: " + e.getMessage());
        }
    }
    
    /**
     * 投票
     */
    @PostMapping("/vote")
    public Map<String, Object> vote(@RequestBody Map<String, Object> request) {
        try {
            Integer proposalIndex = (Integer) request.get("proposalIndex");
            
            Map<String, Object> result = votingService.vote(proposalIndex);
            result.put("success", true);
            result.put("message", "Vote cast successfully");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to vote: {}", e.getMessage(), e);
            return createErrorResponse("Vote failed: " + e.getMessage());
        }
    }
    
    /**
     * 委托投票
     */
    @PostMapping("/delegate")
    public Map<String, Object> delegate(@RequestBody Map<String, Object> request) {
        try {
            String toAddress = (String) request.get("toAddress");
            
            Map<String, Object> result = votingService.delegate(toAddress);
            result.put("success", true);
            result.put("message", "Vote delegated successfully");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to delegate: {}", e.getMessage(), e);
            return createErrorResponse("Delegate failed: " + e.getMessage());
        }
    }
    
    /**
     * 获取获胜提案
     */
    @GetMapping("/winner")
    public Map<String, Object> getWinner() {
        try {
            Map<String, Object> result = votingService.getWinner();
            result.put("success", true);
            result.put("message", "Winner retrieved successfully");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to get winner: {}", e.getMessage(), e);
            return createErrorResponse("Get winner failed: " + e.getMessage());
        }
    }
    
    /**
     * 结束投票
     */
    @PostMapping("/end")
    public Map<String, Object> endVoting() {
        try {
            Map<String, Object> result = votingService.endVoting();
            result.put("success", true);
            result.put("message", "Voting ended successfully");
            
            return result;
        } catch (Exception e) {
            log.error("Failed to end voting: {}", e.getMessage(), e);
            return createErrorResponse("End voting failed: " + e.getMessage());
        }
    }
    
    /**
     * 获取所有提案
     */
    @GetMapping("/proposals")
    public Map<String, Object> getAllProposals() {
        try {
            List<Map<String, Object>> proposals = votingService.getAllProposals();
            
            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("message", "Proposals retrieved successfully");
            result.put("proposals", proposals);
            result.put("count", proposals.size());
            
            return result;
        } catch (Exception e) {
            log.error("Failed to get proposals: {}", e.getMessage(), e);
            return createErrorResponse("Get proposals failed: " + e.getMessage());
        }
    }
    
    /**
     * 获取投票者信息
     */
    @GetMapping("/voter/{address}")
    public Map<String, Object> getVoterInfo(@PathVariable String address) {
        try {
            Map<String, Object> voterInfo = votingService.getVoterInfo(address);
            
            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("message", "Voter info retrieved successfully");
            result.put("voter", voterInfo);
            
            return result;
        } catch (Exception e) {
            log.error("Failed to get voter info: {}", e.getMessage(), e);
            return createErrorResponse("Get voter info failed: " + e.getMessage());
        }
    }
    
    /**
     * 获取合约地址
     */
    @GetMapping("/address")
    public Map<String, Object> getContractAddress() {
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("contractAddress", votingService.getContractAddress());
        
        return result;
    }
    
    /**
     * 健康检查
     */
    @GetMapping("/health")
    public Map<String, Object> healthCheck() {
        Map<String, Object> result = new HashMap<>();
        result.put("status", "UP");
        result.put("service", "FISCO BCOS Voting Service");
        result.put("timestamp", System.currentTimeMillis());
        
        return result;
    }
    
    private Map<String, Object> createErrorResponse(String message) {
        Map<String, Object> error = new HashMap<>();
        error.put("success", false);
        error.put("message", message);
        
        return error;
    }
}

3.5 主应用类

java 复制代码
// src/main/java/com/fisco/bcos/VotingApplication.java
package com.fisco.bcos;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties
public class VotingApplication {
    public static void main(String[] args) {
        SpringApplication.run(VotingApplication.class, args);
    }
}

四、部署与测试

4.1 创建证书目录结构

XML 复制代码
src/main/resources/
├── conf/
│   ├── ca.crt
│   ├── sdk.crt
│   └── sdk.key
├── contracts/
│   ├── Voting.abi
│   └── Voting.bin
└── application.yml

4.2 编译和运行

bash 复制代码
# 编译项目
mvn clean package

# 运行应用
java -jar target/voting-demo-1.0.0.jar

# 或使用Maven运行
mvn spring-boot:run

4.3 使用Postman测试API

1. 部署合约
bash 复制代码
POST http://localhost:8080/api/voting/deploy
Content-Type: application/json

{
    "proposals": ["区块链升级", "社区治理", "技术改进"]
}
2. 授予投票权
bash 复制代码
POST http://localhost:8080/api/voting/grant
Content-Type: application/json

{
    "voterAddress": "0xYourVoterAddress"
}
3. 投票
bash 复制代码
POST http://localhost:8080/api/voting/vote
Content-Type: application/json

{
    "proposalIndex": 0
}
4. 查询获胜者
bash 复制代码
GET http://localhost:8080/api/voting/winner
5. 查询所有提案
bash 复制代码
GET http://localhost:8080/api/voting/proposals

五、高级功能扩展

5.1 事件监听器

java 复制代码
// src/main/java/com/fisco/bcos/listener/VotingEventListener.java
package com.fisco.bcos.listener;

import lombok.extern.slf4j.Slf4j;
import org.fisco.bcos.sdk.v3.BcosSDK;
import org.fisco.bcos.sdk.v3.client.Client;
import org.fisco.bcos.sdk.v3.codec.EventValues;
import org.fisco.bcos.sdk.v3.eventsub.EventCallback;
import org.fisco.bcos.sdk.v3.eventsub.EventSubParams;
import org.fisco.bcos.sdk.v3.model.EventLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.util.List;

@Component
@Slf4j
public class VotingEventListener {
    
    @Autowired
    private Client client;
    
    @Autowired
    private BcosSDK bcosSDK;
    
    private String contractAddress = "your_contract_address";
    
    @PostConstruct
    public void init() {
        subscribeToVotingEvents();
    }
    
    private void subscribeToVotingEvents() {
        try {
            // 订阅Voted事件
            EventSubParams votedEventParams = new EventSubParams();
            votedEventParams.setFromBlock(BigInteger.ZERO);
            votedEventParams.setToBlock(BigInteger.valueOf(-1)); // 最新块
            votedEventParams.setAddresses(Arrays.asList(contractAddress));
            votedEventParams.setTopics(Arrays.asList(
                client.getCryptoSuite().hash("Voted(address,uint256)")
            ));
            
            bcosSDK.getEventSubscribe().subscribeEvent(votedEventParams, new EventCallback() {
                @Override
                public void onReceiveLog(EventLog eventLog) {
                    log.info("Voted event received: {}", eventLog);
                    // 处理投票事件
                    processVotedEvent(eventLog);
                }
            });
            
            log.info("Subscribed to voting events");
            
        } catch (Exception e) {
            log.error("Failed to subscribe to events: {}", e.getMessage(), e);
        }
    }
    
    private void processVotedEvent(EventLog eventLog) {
        try {
            String voter = "0x" + eventLog.getTopics().get(1).substring(26);
            BigInteger proposalIndex = new BigInteger(eventLog.getTopics().get(2).substring(2), 16);
            
            log.info("Voter {} voted for proposal {}", voter, proposalIndex);
            
            // 可以在这里触发业务逻辑,如发送通知、更新数据库等
            
        } catch (Exception e) {
            log.error("Failed to process voted event: {}", e.getMessage());
        }
    }
}

5.2 数据库集成(可选)

java 复制代码
// 存储投票记录到数据库
@Service
public class VotingRecordService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void saveVotingRecord(String voter, int proposalIndex, String txHash) {
        String sql = "INSERT INTO voting_records (voter_address, proposal_index, transaction_hash, vote_time) " +
                    "VALUES (?, ?, ?, NOW())";
        
        jdbcTemplate.update(sql, voter, proposalIndex, txHash);
    }
    
    public List<Map<String, Object>> getVotingHistory(String voter) {
        String sql = "SELECT * FROM voting_records WHERE voter_address = ? ORDER BY vote_time DESC";
        return jdbcTemplate.queryForList(sql, voter);
    }
}

六、注意事项

6.1 安全性考虑

  1. 私钥管理:不要将私钥硬编码在代码中,使用环境变量或密钥管理服务

  2. 输入验证:所有API输入都应进行严格验证

  3. 权限控制:实现基于角色的访问控制

  4. 事务管理:确保操作的原子性和一致性

6.2 性能优化

  1. 连接池:配置合适的连接池大小

  2. 缓存:对频繁查询的数据使用缓存

  3. 批量操作:使用批量方法减少交易数量

  4. 异步处理:对耗时操作使用异步处理

6.3 错误处理

  1. 合约异常:正确处理ContractException

  2. 网络异常:实现重试机制

  3. 资源清理:确保正确释放资源

  4. 日志记录:记录详细的错误信息以便排查

这个完整的Java SDK实现方案提供了从环境搭建、合约部署到API调用的全流程。您可以根据实际需求进行调整和扩展。

相关推荐
木井巳2 小时前
【递归算法】计算布尔二叉树的值
java·算法·leetcode·深度优先
2 小时前
java关于时间类
java·开发语言
java1234_小锋2 小时前
Spring里AutoWired与Resource区别?
java·后端·spring
风象南2 小时前
Spring Boot 定时任务多实例互斥执行
java·spring boot·后端
崎岖Qiu2 小时前
【深度剖析】:结合 Spring Bean 的生命周期理解 @PostConstruct 的原理
java·笔记·后端·spring·javaee
毕设源码-郭学长2 小时前
【开题答辩全过程】以 基于Springboot旅游景点管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
是三好2 小时前
JUC并发编程
java·开发语言
芬加达2 小时前
leetcode221 最大正方形
java·数据结构·算法
猿小羽2 小时前
深度实战:Spring AI 与 MCP(Model Context Protocol)构建下一代 AI Agent
java·大模型·llm·ai agent·spring ai·开发者工具·mcp