Hyperledger Fabric Java智能合約實(shí)現(xiàn)

??運(yùn)行環(huán)境:ubuntu18.04 ?? docker基礎(chǔ)環(huán)境

環(huán)境變量設(shè)置
export FABRIC_PATH=/路徑/fabric-samples
export FABRIC_CFG_PATH=${FABRIC_PATH}/config/
export MSP_PATH=${FABRIC_PATH}/test-network/organizations
export CORE_PEER_TLS_ENABLED=true
export PATH=${FABRIC_PATH}/bin:$PATH

maven集成fabric-chaincode-shim津坑,實(shí)現(xiàn)區(qū)塊鏈智能合約

一护戳、pom.xml 添加依賴
    <repositories>
        <repository>
            <id>central</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>jitpack.io</id>
            <url>https://www.jitpack.io</url>
        </repository>
        <repository>
            <id>artifactory</id>
            <url>https://hyperledger.jfrog.io/hyperledger/fabric-maven</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.hyperledger.fabric-chaincode-java</groupId>
            <artifactId>fabric-chaincode-shim</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.38</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>chaincode</finalName>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>org.hyperledger.fabric.contract.ContractRouter</mainClass>
                                </transformer>
                            </transformers>
                            <filters>
                                <filter>
                                    <!-- filter out signature files from signed dependencies, else repackaging fails with security ex -->
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
二珍特、實(shí)現(xiàn)智能合約代碼
import com.alibaba.fastjson.JSON;

import java.util.Objects;

/**
 * 用戶帳戶對(duì)象
 */

public class User {
    private String userId;
    private String name;
    private double money;

    public User(final String userId, final String name, final double money) {
        this.userId = userId;
        this.name = name;
        this.money = money;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public double getMoney() {
        return money;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if ((obj == null) || (getClass() != obj.getClass())) {
            return false;
        }
        User other = (User) obj;
        return Objects.deepEquals(
                new String[] {getUserId(), getName()},
                    new String[] {other.getUserId(), other.getName()})
                &&
                Objects.deepEquals(
                        new double[] {getMoney()},
                        new double[] {other.getMoney()});
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUserId(), getName(), getMoney());
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

}
import com.alibaba.fastjson.JSON;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.*;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ledger.KeyModification;
import org.hyperledger.fabric.shim.ledger.KeyValue;
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;

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

/**
 * 智能合約-用戶賬本
 */
@Contract(name = "UserChainCode")
@Default
public class UserChainCode implements ContractInterface {

    public UserChainCode() {

    }

    /**
     * 初始化3條記錄
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public void init(final Context ctx) {
        addUser(ctx, "1", "mixm",100D);
        addUser(ctx, "2", "admin",200D);
        addUser(ctx, "3", "user",300D);
    }

    /**
     * 獲取該id的所有變更記錄
     */
    @Transaction(intent = Transaction.TYPE.EVALUATE)
    public String getHistory(final Context ctx, final String userId) {
        Map<String, String> userHistory = new HashMap<>();
        ChaincodeStub stub = ctx.getStub();
        QueryResultsIterator<KeyModification> iterator = stub.getHistoryForKey(userId);
        for (KeyModification result: iterator) {
            userHistory.put(result.getTxId(), result.getStringValue());
        }
        return JSON.toJSONString(userHistory);
    }

    /**
     * 新增用戶
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public String addUser(final Context ctx, final String userId, final String name, final double money) {
        ChaincodeStub stub = ctx.getStub();
        User user = new User(userId, name, money);
        String userJson = JSON.toJSONString(user);
        stub.putStringState(userId, userJson);
        return stub.getTxId();
    }

    /**
     * 查詢某個(gè)用戶
     */
    @Transaction(intent = Transaction.TYPE.EVALUATE)
    public User getUser(final Context ctx, final String userId) {
        ChaincodeStub stub = ctx.getStub();
        String userJSON = stub.getStringState(userId);
        if (userJSON == null || userJSON.isEmpty()) {
            String errorMessage = String.format("User %s does not exist", userId);
            throw new ChaincodeException(errorMessage);
        }
        return JSON.parseObject(userJSON, User.class);
    }

    /**
     * 查詢所有用戶
     */
    @Transaction(intent = Transaction.TYPE.EVALUATE)
    public String queryAll(final Context ctx) {
        ChaincodeStub stub = ctx.getStub();
        List<User> userList = new ArrayList<>();
        QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
        for (KeyValue result: results) {
            User user = JSON.parseObject(result.getStringValue(), User.class);
            System.out.println(user);
            userList.add(user);
        }
        return JSON.toJSONString(userList);
    }

    /**
     * 轉(zhuǎn)賬
     * @param sourceId 源用戶id
     * @param targetId 目標(biāo)用戶id
     * @param money 金額
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public String transfer(final Context ctx, final String sourceId, final String targetId, final double money) {
        ChaincodeStub stub = ctx.getStub();
        User sourceUser = getUser(ctx, sourceId);
        User targetUser = getUser(ctx, targetId);
        if (sourceUser.getMoney() < money) {
            String errorMessage = String.format("The balance of user %s is insufficient", sourceId);
            throw new ChaincodeException(errorMessage);
        }
        User newSourceUser = new User(sourceUser.getUserId(), sourceUser.getName(), sourceUser.getMoney() - money);
        User newTargetUser = new User(targetUser.getUserId(), targetUser.getName(), targetUser.getMoney() + money);
        stub.putStringState(sourceId, JSON.toJSONString(newSourceUser));
        stub.putStringState(targetId, JSON.toJSONString(newTargetUser));
        return stub.getTxId();
    }
}

  • 打包合約源碼
peer lifecycle chaincode package UserChainCode.tar.gz --path /工程目錄/UserChainCode --lang java --label UserChainCode
三芦缰、安裝合約
  • 設(shè)置peer0.org1環(huán)境:
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

??安裝合約:

peer lifecycle chaincode install UserChainCode.tar.gz

??成功返回值:

[2022-06-16 02:01:41.891 PDT [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nNUserChainCode:c8afcba20a3e5e70b1ca183a983d856e984908f02bc0244f4be0755940745271\022\rUserChainCode" > 
2022-06-16 02:01:41.891 PDT [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: UserChainCode:c8afcba20a3e5e70b1ca183a983d856e984908f02bc0244f4be0755940745271
  • ??設(shè)置peer0.org2環(huán)境:
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

??安裝合約:

peer lifecycle chaincode install UserChainCode.tar.gz
  • 查看安裝的合約清單:
peer lifecycle chaincode queryinstalled
四、合約審核

當(dāng)合約安裝后,需經(jīng)過機(jī)構(gòu)的審批達(dá)成一致后才允許使用西壮。
package-id通過合約清單查詢

  • peer0.org1審核合約

??環(huán)境變量設(shè)置

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

??審批合約

peer lifecycle chaincode approveformyorg \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name UserChainCode \
  --version 1.0 \
  --package-id UserChainCode:c8afcba20a3e5e70b1ca183a983d856e984908f02bc0244f4be0755940745271 \
  --sequence 1

??成功返回值:

2022-06-16 02:17:23.850 PDT [chaincodeCmd] ClientWait -> INFO 001 txid [f657a862e0f08e14429c31c660b1db798ee4099f21006b67f26e939c22510c9b] committed with status (VALID) at localhost:7051
  • peer0.org2審核合約
    ??設(shè)置環(huán)境:
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

??審批合約

peer lifecycle chaincode approveformyorg \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name UserChainCode \
  --version 1.0 \
  --package-id UserChainCode:c8afcba20a3e5e70b1ca183a983d856e984908f02bc0244f4be0755940745271 \
  --sequence 1

??成功返回值:

2022-06-16 02:13:17.149 PDT [chaincodeCmd] ClientWait -> INFO 001 txid [c7824a195ab4734421a452546334b10ed1e3043de947bd43e02afce692fa32b9] committed with status (VALID) at localhost:9051
  • 檢查合約的審批情況
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name UserChainCode --version 1.0 --sequence 1 --output json

??成功返回值:

{
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": true
    }
}
五、提交合約
  • 向通道提交合約
peer lifecycle chaincode commit \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name UserChainCode \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  --version 1.0 \
  --sequence 1

??成功返回:

2022-06-16 02:24:00.916 PDT [chaincodeCmd] ClientWait -> INFO 001 txid [a98706144cb7a982924935d175d3d9469a1ba384918fa3b2317796ab5c9fc45b] committed with status (VALID) at localhost:9051
2022-06-16 02:24:00.922 PDT [chaincodeCmd] ClientWait -> INFO 002 txid [a98706144cb7a982924935d175d3d9469a1ba384918fa3b2317796ab5c9fc45b] committed with status (VALID) at localhost:7051
  • 查看通道上已提交合約
peer lifecycle chaincode querycommitted --channelID mychannel --name UserChainCode --output json

??成功返回:

{
    "sequence": 1,
    "version": "1.0",
    "endorsement_plugin": "escc",
    "validation_plugin": "vscc",
    "validation_parameter": "EiAvQ2hhbm5lbC9BcHBsaWNhdGlvbi9FbmRvcnNlbWVudA==",
    "collections": {},
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": true
    }
}
六叫惊、測(cè)試合約
  • 初始化賬本
peer chaincode invoke -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  -C mychannel \
  -n UserChainCode \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  -c '{"function":"init","Args":[]}'

??成功返回:

2022-06-16 02:32:17.327 PDT [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
  • 查詢數(shù)據(jù)

??設(shè)置為peer0.org1環(huán)境

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

??查詢所有數(shù)據(jù)

peer chaincode query -C mychannel -n UserChainCode -c '{"Args":["queryAll"]}'

??成功返回:

[{"money":100.0,"name":"mixm","userId":"1"},{"money":200.0,"name":"admin","userId":"2"},{"money":300.0,"name":"user","userId":"3"}]

??單個(gè)查詢

peer chaincode query -C mychannel -n UserChainCode -c '{"Args":["getUser", "1"]}'

??成功返回:

  • 新增數(shù)據(jù)
peer chaincode invoke -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  -C mychannel \
  -n UserChainCode \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  -c '{"function":"addUser","Args":["4","test","400"]}'

??成功返回:

2022-06-16 04:45:33.799 PDT [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"3fec668b3342100ca362fc342d240cc5bc6fb1e52cced55233b4a984f4d13c3a"
  • 轉(zhuǎn)賬
peer chaincode invoke -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  -C mychannel \
  -n UserChainCode \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  -c '{"function":"transfer","Args":["4","1","400"]}'

??成功返回:

2022-06-16 04:48:05.651 PDT [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"5bfd15030101eb1fd538ffd622d8a66c79d237fabeaaa67342ee8ba04dc1dccc"
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末款青,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子霍狰,更是在濱河造成了極大的恐慌抡草,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔗坯,死亡現(xiàn)場(chǎng)離奇詭異康震,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宾濒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門腿短,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绘梦,你說我怎么就攤上這事橘忱。” “怎么了谚咬?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵鹦付,是天一觀的道長(zhǎng)尚粘。 經(jīng)常有香客問我择卦,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任秉继,我火速辦了婚禮祈噪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尚辑。我一直安慰自己辑鲤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布杠茬。 她就那樣靜靜地躺著月褥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓢喉。 梳的紋絲不亂的頭發(fā)上宁赤,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音栓票,去河邊找鬼决左。 笑死,一個(gè)胖子當(dāng)著我的面吹牛走贪,可吹牛的內(nèi)容都是我干的佛猛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼坠狡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼继找!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逃沿,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤码荔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后感挥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩搅,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年触幼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了硼瓣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡置谦,死狀恐怖堂鲤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媒峡,我是刑警寧澤瘟栖,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谅阿,受9級(jí)特大地震影響半哟,放射性物質(zhì)發(fā)生泄漏酬滤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一寓涨、第九天 我趴在偏房一處隱蔽的房頂上張望盯串。 院中可真熱鬧,春花似錦戒良、人聲如沸体捏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽几缭。三九已至,卻和暖如春沃呢,著一層夾襖步出監(jiān)牢的瞬間奏司,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工樟插, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留韵洋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓黄锤,卻偏偏與公主長(zhǎng)得像搪缨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸵熟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容