web3j函数回调使用详解

前面solc和web3编译和加载代码的步骤参考其他文章。

web3j中deploy代码

solidity代码

pragma solidity >0.4.18;

contract SimpleVoting {

    bytes32[] public candidateList;
    mapping (bytes32 => uint8) public votesReceived;
    
    constructor(bytes32[]  memory candidateNames) public {
        candidateList = candidateNames;
    }

    function voteForCandidate(bytes32 candidate) public {
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
    }

    function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        require(validCandidate(candidate));
        return votesReceived[candidate];
    }

    function validCandidate(bytes32 candidate) view public returns (bool) {
        for(uint8 i = 0; i < candidateList.length; i++) {
            if(candidateList[i] == candidate)
                return true;
        }
        return false;
    }
}

编译后的java代码(有些部分需要手动修改,后文会写道):

package com.gason.blockchain.eth;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Bytes32;
import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.RemoteCall;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.Contract;
import org.web3j.tx.TransactionManager;
import org.web3j.tx.gas.ContractGasProvider;

/**
 * <p>Auto generated code.
 * <p><strong>Do not modify!</strong>
 * <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
 * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the
 * <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
 *
 * <p>Generated with web3j version 4.5.5.
 */
@SuppressWarnings("rawtypes")
public class SimpleVoting extends Contract {
    private static final String BINARY = "";

    public static final String FUNC_CANDIDATELIST = "candidateList";

    public static final String FUNC_TOTALVOTESFOR = "totalVotesFor";

    public static final String FUNC_VALIDCANDIDATE = "validCandidate";

    public static final String FUNC_VOTEFORCANDIDATE = "voteForCandidate";

    public static final String FUNC_VOTESRECEIVED = "votesReceived";

    @Deprecated
    protected SimpleVoting(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    protected SimpleVoting(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        super(BINARY, contractAddress, web3j, credentials, contractGasProvider);
    }

    @Deprecated
    protected SimpleVoting(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
        super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit);
    }

    protected SimpleVoting(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
        super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider);
    }

    public RemoteFunctionCall<Bytes32> candidateList(BigInteger param0) {
        final Function function = new Function(
                FUNC_CANDIDATELIST,
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Uint256(param0)),
                Arrays.<TypeReference<?>>asList(new TypeReference<Bytes32>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

    public RemoteFunctionCall<Uint8> totalVotesFor(String candidate) {
        final Function function = new Function(
                FUNC_TOTALVOTESFOR,
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(StrToByte(candidate))),
                Arrays.<TypeReference<?>>asList(new TypeReference<Uint8>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

    public RemoteFunctionCall<Bool> validCandidate(String candidate) {
        final Function function = new Function(
                FUNC_VALIDCANDIDATE,
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(StrToByte(candidate))),
                Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

    public RemoteFunctionCall<TransactionReceipt> voteForCandidate(String candidate) {
        final Function function = new Function(
                FUNC_VOTEFORCANDIDATE,
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(StrToByte(candidate))),
                Collections.<TypeReference<?>>emptyList());
        return executeRemoteCallTransaction(function);
    }

    public RemoteFunctionCall<TransactionReceipt> votesReceived(byte[] param0) {
        final Function function = new Function(
                FUNC_VOTESRECEIVED,
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(param0)),
                Collections.<TypeReference<?>>emptyList());
        return executeRemoteCallTransaction(function);
    }

    @Deprecated
    public static SimpleVoting load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return new SimpleVoting(contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    @Deprecated
    public static SimpleVoting load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
        return new SimpleVoting(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
    }

    public static SimpleVoting load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        return new SimpleVoting(contractAddress, web3j, credentials, contractGasProvider);
    }

    public static SimpleVoting load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
        return new SimpleVoting(contractAddress, web3j, transactionManager, contractGasProvider);
    }

    public static RemoteCall<SimpleVoting> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, List<byte[]> candidateNames) {
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
                org.web3j.abi.datatypes.generated.Bytes32.class,
                org.web3j.abi.Utils.typeMap(candidateNames, org.web3j.abi.datatypes.generated.Bytes32.class))));
        return deployRemoteCall(SimpleVoting.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor);
    }

    public static RemoteCall<SimpleVoting> deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, List<byte[]> candidateNames) {
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
                org.web3j.abi.datatypes.generated.Bytes32.class,
                org.web3j.abi.Utils.typeMap(candidateNames, org.web3j.abi.datatypes.generated.Bytes32.class))));
        return deployRemoteCall(SimpleVoting.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor);
    }

    @Deprecated
    public static RemoteCall<SimpleVoting> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, List<byte[]> candidateNames) {
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
                org.web3j.abi.datatypes.generated.Bytes32.class,
                org.web3j.abi.Utils.typeMap(candidateNames, org.web3j.abi.datatypes.generated.Bytes32.class))));
        return deployRemoteCall(SimpleVoting.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor);
    }

    @Deprecated
    public static RemoteCall<SimpleVoting> deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, List<byte[]> candidateNames) {
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
                org.web3j.abi.datatypes.generated.Bytes32.class,
                org.web3j.abi.Utils.typeMap(candidateNames, org.web3j.abi.datatypes.generated.Bytes32.class))));
        return deployRemoteCall(SimpleVoting.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor);
    }

    public static RemoteCall<SimpleVoting> deployByString(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, List<String> candidateNames) {
        List<byte[]> candidateNamesByte = candidateNames.stream().map(val -> StrToByte(val)).collect(Collectors.toList());
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
                org.web3j.abi.datatypes.generated.Bytes32.class,
                org.web3j.abi.Utils.typeMap(candidateNamesByte, org.web3j.abi.datatypes.generated.Bytes32.class))));
        return deployRemoteCall(SimpleVoting.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor);
    }

    public static byte[] StrToByte(String str) {
        byte[] byteValue = str.getBytes();
        byte[] byteValueLen32 = new byte[32];
        System.arraycopy(byteValue, 0, byteValueLen32, 0, byteValue.length);
        return byteValueLen32;
    }
}

使用java进行部署:

      String pk="";//写你私钥
       Web3j web3 = Web3j.build(new HttpService());
        ContractGasProvider contractGasProvider = new DefaultGasProvider();
        Credentials credentials = Credentials.create(pk);
        List<String> candidateNames = Arrays.asList("Bob", "Tom", "Jerry");
        SimpleVoting registryContract = SimpleVoting.deployByString(web3, credentials, contractGasProvider, candidateNames).sendAsync().get();
       // 合约地址,记得保存
        System.out.println(registryContract.getContractAddress());

web3j中的方法调用

  • 注意:solidity中的byte32[]类型与Java中String的转换,String需要转为byte32[]
public static byte[] StrToByte(String str) {
        byte[] byteValue = str.getBytes();
        byte[] byteValueLen32 = new byte[32];
        System.arraycopy(byteValue, 0, byteValueLen32, 0, byteValue.length);
        return byteValueLen32;
    }
  • 无返回值
    solidity代码:
   function voteForCandidate(bytes32 candidate) public {
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
    }

java编译后:
executeRemoteCallTransaction无返回值,Function中第一个参数为函数名,第二个为输入参数集合,第二为返回参数集合,如果无返回参数,就输入 Collections.<TypeReference<?>>emptyList()

    public RemoteFunctionCall<TransactionReceipt> voteForCandidate(String candidate) {
        final Function function = new Function(
                "voteForCandidate",
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(StrToByte(candidate))),
                Collections.<TypeReference<?>>emptyList());
        return executeRemoteCallTransaction(function);
    }

调用:

      Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
        ContractGasProvider contractGasProvider = new DefaultGasProvider();
      //PK为你的私钥
        Credentials credentials = Credentials.create(PK);
      //address为你之前部署后返回的合约地址
        SimpleVoting registryContract = SimpleVoting.load(address, web3, credentials, contractGasProvider);
        registryContract.voteForCandidate("Bob").sendAsync().get();
  • 单个参数返回
    solidity:
 function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        require(validCandidate(candidate));
        return votesReceived[candidate];
    }

java编译后,executeRemoteCallSingleValueReturn为单个参数返回,

   public RemoteFunctionCall<Uint8> totalVotesFor(String candidate) {
        final Function function = new Function(
                "totalVotesFor",
                Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(StrToByte(candidate))),
                Arrays.<TypeReference<?>>asList(new TypeReference<Uint8>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

调用:

        Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
        ContractGasProvider contractGasProvider = new DefaultGasProvider();
        Credentials credentials = Credentials.create(PK);
        SimpleVoting registryContract = SimpleVoting.load(address, web3, credentials, contractGasProvider);
        System.out.println(registryContract.totalVotesFor("Bob").sendAsync().get().getValue());
  • 多参数返回
    solidity代码:
pragma solidity >0.4.0;

// 官方文档众筹实现
// https://solidity.readthedocs.io/en/develop/types.html#structs

// 独立实现
contract CrowdFunding {

    // 众筹项目
    struct Fund {
        // 众筹地址
        address owner;
        // 众筹描述
        string desc;
        // 众筹目标
        uint goal;

        // 已筹金币
        uint coins;
        // 是否结束
        bool finished;

        // 捐赠人数
        uint recordCounts;
        // 捐赠记录
        mapping(uint => Record) records;
        // 原本使用 Record[] records 数组定义
        // 但是貌似目前版本尚不支持
        // 于是将 数组 拆分成 长度 + 映射
        // https://solidity.readthedocs.io/en/develop/types.html#mappings
    }

    // 捐赠记录
    struct Record {
        // 捐赠成员
        address member;
        // 捐赠金币
        uint coin;
        // 捐赠时间
        uint time;
    }

    // 众筹地址列表
    Fund[] funds;

    // 获取众筹项目数量
    function getFundCount() public view returns (uint) {
        return funds.length;
    }

    // 获取众筹项目信息
    // 参数:项目编号
    // 返回:众筹地址 众筹描述 众筹目标 已筹金币 是否结束
    function getFundInfo(uint fundIndex) public view returns (address, string memory, uint, uint, bool) {
        Fund storage fund = funds[fundIndex];
        return (fund.owner, fund.desc, fund.goal, fund.coins, fund.finished);
    }

    // 获取众筹捐赠人数
    function getRecordCount(uint fundIndex) public view returns (uint) {
        return funds[fundIndex].recordCounts;
    }

    // 获取众筹捐赠记录
    // 参数:项目编号 记录编号
    // 返回:捐赠成员 捐赠金币 捐赠时间
    function getRecordInfo(uint fundIndex, uint recordIndex) public view returns (address, uint, uint) {
        Record storage record = funds[fundIndex].records[recordIndex];
        return (record.member, record.coin, record.time);
    }

    // 为自己发起众筹
    function raiseFund(string memory info, uint goal) public {
        funds.push(Fund(msg.sender, info, goal, 0, false, 0));
    }

    // 为别人发送金币
    // https://solidity.readthedocs.io/en/latest/miscellaneous.html#modifiers
    // payable for functions: Allows them to receive Ether together with a call.
    function sendCoin(uint fundIndex) public payable {
        // 默认属性
        // msg.sender 转账账户
        // msg.value 转账数目
        // 智能合约默认单位 wei
        // 1 ether = 10^18 wei

        // 引用拷贝
        Fund storage fund = funds[fundIndex];
        require(!fund.finished);

        // 转账 失败自动退出
        address(uint256(fund.owner)).transfer(msg.value);

        // 更新众筹信息
        fund.coins += msg.value;
        fund.records[fund.recordCounts++] = Record(msg.sender, msg.value, now);
        fund.finished = fund.coins >= fund.goal * 1 ether ? true : false;
    }

    // 回退函数 防止抛出异常
    // https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function
    // if you want your contract to receive Ether, you have to implement a fallback function.
// 后面两个函数是最新版本编译器所需要的,可能跟网上一些不一样
         fallback () external  payable  { }
         receive() external payable {  }
}

java编译后(部分通过手工修改):

package com.gason.blockchain.eth;

import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.RemoteCall;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.Contract;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.utils.Convert;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;

public class CrowdFundingContract extends Contract implements CrowdFundingInterface {
    private static final String BINARY = "";

    /**
     * CrowdFunding合约
     *
     * @param contractAddress 合约地址
     * @param web3j           RPC请求
     * @param credentials     钱包凭证
     * @param gasPrice        GAS价格
     * @param gasLimit        GAS上限
     */
    private CrowdFundingContract(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice,
                                 BigInteger gasLimit) {
        super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
    }


    protected CrowdFundingContract(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        super(BINARY, contractAddress, web3j, credentials, contractGasProvider);
    }
    /**
     * 部署合约
     *
     * @param web3j       RPC请求
     * @param credentials 钱包凭证
     * @param gasPrice    GAS价格
     * @param gasLimit    GAS上限
     * @return
     */
    public static RemoteCall<CrowdFundingContract> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        // 构造函数传参 NULL
        String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList());
        return deployRemoteCall(CrowdFundingContract.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor);
    }

    public static RemoteCall<CrowdFundingContract> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        return deployRemoteCall(CrowdFundingContract.class, web3j, credentials, contractGasProvider, BINARY, "");
    }

    /**
     * 加载合约
     *
     * @param contractAddress 合约地址
     * @param web3j           RPC请求
     * @param credentials     钱包凭证
     * @param gasPrice        GAS价格
     * @param gasLimit        GAS上限
     * @return
     */
    public static CrowdFundingContract load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return new CrowdFundingContract(contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    public static CrowdFundingContract load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        return new CrowdFundingContract(contractAddress, web3j, credentials, contractGasProvider);
    }

    //////////////////////////////////////////
    //          crowdfunding.sol            //
    //////////////////////////////////////////

    /**
     * 获取众筹项目数量
     * rinfo.get(0).getValue().toString()
     * function getFundCount() public view returns (uint)
     */
    @Override
    public RemoteCall<Uint256> getFundCount() {
        Function function = new Function("getFundCount", Arrays.<Type>asList(),
                Arrays.<TypeReference<?>>asList(new TypeReference<Uint256>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

    /**
     * 获取众筹项目信息
     * function getFundInfo(uint fundIndex) public view returns (address, string, uint, uint, bool)
     */
    @Override
    public RemoteCall<List<Type>> getFundInfo(int fundIndex) {
        Function function = new Function("getFundInfo", Arrays.<Type>asList(new Uint256(fundIndex)),
                Arrays.<TypeReference<?>>asList(new TypeReference<Address>() {
                }, new TypeReference<Utf8String>() {
                }, new TypeReference<Uint256>() {
                }, new TypeReference<Uint256>() {
                }, new TypeReference<Bool>() {
                }));
        return executeRemoteCallMultipleValueReturn(function);
    }

    /**
     * 获取众筹捐赠人数
     * function getRecordCount(uint fundIndex) public view returns (uint)
     */
    @Override
    public RemoteCall<Uint256> getRecordCount(int fundIndex) {
        Function function = new Function("getRecordCount", Arrays.<Type>asList(new Uint256(fundIndex)),
                Arrays.<TypeReference<?>>asList(new TypeReference<Uint256>() {
                }));
        return executeRemoteCallSingleValueReturn(function);
    }

    /**
     * 获取众筹捐赠记录
     * function getRecordInfo(uint fundIndex, uint recordIndex) public view returns (address, uint, uint)
     */
    @Override
    public RemoteCall<List<Type>> getRecordInfo(int fundIndex, int recordIndex) {
        Function function = new Function("getRecordInfo", Arrays.<Type>asList(new Uint256(fundIndex), new Uint256(recordIndex)),
                Arrays.<TypeReference<?>>asList(new TypeReference<Address>() {
                }, new TypeReference<Uint256>() {
                }, new TypeReference<Uint256>() {
                }));
        return executeRemoteCallMultipleValueReturn(function);
    }

    /**
     * 为自己发起众筹
     * function raiseFund(string desc, uint goal) public
     */
    @Override
    public RemoteCall<TransactionReceipt> raiseFund(String desc, int goal) {
        Function function = new Function("raiseFund", Arrays.<Type>asList(new Utf8String(desc), new Uint256(goal)),
                Arrays.<TypeReference<?>>asList());
        return executeRemoteCallTransaction(function);
    }

    /**
     * 为别人发送金币
     * function sendCoin(uint fundIndex) public payable
     */
    @Override
    public RemoteCall<TransactionReceipt> sendCoin(int fundIndex, int coin) {
        Function function = new Function("sendCoin", Arrays.<Type>asList(new Uint256(fundIndex)),
                Arrays.<TypeReference<?>>asList());
        return executeRemoteCallTransaction(function, BigInteger.valueOf(coin).multiply(Convert.Unit.ETHER.getWeiFactor().toBigInteger()));
    }
}

调用:

        Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
        ContractGasProvider contractGasProvider = new DefaultGasProvider();
        Credentials credentials = Credentials.create(PK);
        CrowdFundingContract registryContract = CrowdFundingContract.load(address, web3, credentials, contractGasProvider);
        List<Type> outputs = registryContract.getFundInfo(0).sendAsync().get();
        System.out.println("address:" + outputs.get(0).toString());
        System.out.println("desc:" + outputs.get(1).toString());
        System.out.println("goal:" + Integer.parseInt(outputs.get(2).getValue().toString()));
        System.out.println("coins:" + new BigInteger(outputs.get(3).getValue().toString()).divide(Convert.Unit.ETHER.getWeiFactor().toBigInteger()).intValue());
        System.out.println("finished:" + Boolean.parseBoolean(outputs.get(4).getValue().toString()));

参考了:https://github.com/niyuelin1990/CrowdFunding
如果对你有帮助记得点赞

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容