基于責(zé)任鏈模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限框架

前言:設(shè)計(jì)模式源于生活

責(zé)任鏈基本概念

客戶端發(fā)出一個(gè)請(qǐng)求,鏈上的對(duì)象都有機(jī)會(huì)來(lái)處理這一請(qǐng)求,而客戶端不需要知道誰(shuí)是具體的處理對(duì)象。
這樣就實(shí)現(xiàn)了請(qǐng)求者和接受者之間的解耦,并且在客戶端可以實(shí)現(xiàn)動(dòng)態(tài)的組合職責(zé)鏈。使編程更有靈活性。

責(zé)任鏈定義

使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免了請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有對(duì)象處理它為止。其過(guò)程實(shí)際上是一個(gè)遞歸調(diào)用。

要點(diǎn):

1.有多個(gè)對(duì)象共同對(duì)一個(gè)任務(wù)進(jìn)行處理。
2.這些對(duì)象使用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),形成一個(gè)鏈,每個(gè)對(duì)象知道自己的下一個(gè)對(duì)象。
3.一個(gè)對(duì)象對(duì)任務(wù)進(jìn)行處理,可以添加一些操作后將對(duì)象傳遞個(gè)下一個(gè)任務(wù)。也可以在此對(duì)象上結(jié)束任務(wù)的處理,并結(jié)束任務(wù)。
4.客戶端負(fù)責(zé)組裝鏈?zhǔn)浇Y(jié)構(gòu),但是客戶端不需要關(guān)心最終是誰(shuí)來(lái)處理了任務(wù)。

責(zé)任鏈優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

職責(zé)鏈模式的最主要功能就是:動(dòng)態(tài)組合,請(qǐng)求者和接受者解耦。
請(qǐng)求者和接受者松散耦合:請(qǐng)求者不需要知道接受者,也不需要知道如何處理。每個(gè)職責(zé)對(duì)象只負(fù)責(zé)自己的職責(zé)范圍,其他的交給后繼者。各個(gè)組件間完全解耦。
動(dòng)態(tài)組合職責(zé):職責(zé)鏈模式會(huì)把功能分散到單獨(dú)的職責(zé)對(duì)象中,然后在使用時(shí)動(dòng)態(tài)的組合形成鏈,從而可以靈活的分配職責(zé)對(duì)象,也可以靈活的添加改變對(duì)象職責(zé)。

缺點(diǎn):

產(chǎn)生很多細(xì)粒度的對(duì)象:因?yàn)楣δ芴幚矶挤稚⒌搅藛为?dú)的職責(zé)對(duì)象中,每個(gè)對(duì)象功能單一,要把整個(gè)流程處理完,需要很多的職責(zé)對(duì)象,會(huì)產(chǎn)生大量的細(xì)粒度職責(zé)對(duì)象。
不一定能處理:每個(gè)職責(zé)對(duì)象都只負(fù)責(zé)自己的部分,這樣就可以出現(xiàn)某個(gè)請(qǐng)求,即使把整個(gè)鏈走完,都沒(méi)有職責(zé)對(duì)象處理它。這就需要提供默認(rèn)處理,并且注意構(gòu)造鏈的有效性。

責(zé)任鏈應(yīng)用場(chǎng)景

1.java過(guò)濾器的底層實(shí)現(xiàn)filter(例如:請(qǐng)求進(jìn)來(lái)到服務(wù)端,filter會(huì)經(jīng)過(guò)參數(shù)過(guò)濾、session過(guò)濾、表單過(guò)濾、請(qǐng)求頭過(guò)濾等等)
2.erp審批流程(例如:項(xiàng)目組長(zhǎng)->項(xiàng)目經(jīng)理->項(xiàng)目總監(jiān)->人事經(jīng)理)
3.多重if判斷問(wèn)題

責(zé)任鏈模式環(huán)境流程圖

1.抽象處理者(Handler)角色:定義出一個(gè)處理請(qǐng)求的接口。如果需要,接口可以定義 出一個(gè)方法以設(shè)定和返回對(duì)下家的引用。這個(gè)角色通常由一個(gè)Java抽象類或者Java接口實(shí)現(xiàn)。上圖中Handler類的聚合關(guān)系給出了具體子類對(duì)下家的引用,抽象方法handleRequest()規(guī)范了子類處理請(qǐng)求的操作。
2.具體處理者(ConcreteHandler)角色:具體處理者接到請(qǐng)求后,可以選擇將請(qǐng)求處理掉,或者將請(qǐng)求傳給下家。由于具體處理者持有對(duì)下家的引用,因此,如果需要,具體處理者可以訪問(wèn)下家

接下來(lái)將通過(guò)兩種基于責(zé)任鏈設(shè)計(jì)模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限方式

兩種方式POM都通用

<?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>org.example</groupId>
    <artifactId>responsiit_model</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <!--            <scope>provided</scope>-->
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- mysql 依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>


    </dependencies>
</project>

第一種:基于內(nèi)存,模板模式+工廠模式+責(zé)任鏈模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限框架

抽象handler

/**
 * @Author: klm
 * @Description: 抽象網(wǎng)關(guān)
 */
public abstract class GatewayHandler {

    abstract public void service();

    protected GatewayHandler nextGatewayHandler;

    public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
        this.nextGatewayHandler = gatewayHandler;
    }

    protected void nextService() {
        if (nextGatewayHandler != null) {
            nextGatewayHandler.service();
        }
    }
}

具體API限流handler

/**
 * @Author: klm
 * @Description: API接口限流Handler
 */
@Slf4j
public class CurrentLimitHandler extends GatewayHandler {

    @Override
    public void service() {
        log.info("第一關(guān):API接口限流");
        nextService();
    }
}

具體黑名單限流Handler

/**
 * @Author: klm
 * @Description: 黑名單限流Handler
 */
@Slf4j
public class BlacklistHandler extends GatewayHandler {

    @Override
    public void service() {
        log.info("第二關(guān):黑名單限流");
        nextService();
    }
}

具體攔截用戶信息Handler

/**
 * @Author: klm
 * @Description: 攔截用戶信息Handler
 */
@Slf4j
public class ConversationHandler extends GatewayHandler {

    @Override
    public void service() {
        log.info("第三關(guān):攔截用戶信息");
    }
}

工廠:初始化對(duì)象并設(shè)置引用執(zhí)行順序

/**
 * @Author: klm
 * @Description: 工廠
 */
public class FactoryHandler {

    public static GatewayHandler getCurrentLimitHandler() {
        //初始化對(duì)象
        GatewayHandler currentLimitHandler = new CurrentLimitHandler();
        GatewayHandler blacklistHandler = new BlacklistHandler();
        GatewayHandler conversationHandler = new ConversationHandler();

        //設(shè)置引用,執(zhí)行順序設(shè)置
        currentLimitHandler.setNextGatewayHandler(blacklistHandler);
        blacklistHandler.setNextGatewayHandler(conversationHandler);
        return currentLimitHandler;
    }
}

測(cè)試類,執(zhí)行一波,看看效果

public class test {
    public static void main(String[] args) {
        GatewayHandler currentLimitHandler = FactoryHandler.getCurrentLimitHandler();
        currentLimitHandler.service();
    }
}

代碼還有需要改進(jìn)的地方

改進(jìn)點(diǎn):在每個(gè)handler處理器里面,都會(huì)要執(zhí)行一次nextService()方法,執(zhí)行下一個(gè)Handler,但是這種行為比較冗余,low所以我們改造一波
抽象Handler改造點(diǎn)

抽象Handler改造點(diǎn)

/**
 * @Author: klm
 * @Description: 抽象網(wǎng)關(guān)
 */
public abstract class GatewayHandler {

    //改造點(diǎn)1:將訪問(wèn)修飾符改為只有子類才能使用,不再進(jìn)行公開(kāi)使用
    protected abstract void service();

    protected GatewayHandler nextGatewayHandler;

    public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
        this.nextGatewayHandler = gatewayHandler;
    }

    //改造點(diǎn)3:將執(zhí)行下一次操作的行為,改為我們的入口操作
    protected void nextService() {
        if (nextGatewayHandler != null) {
//            nextGatewayHandler.service();
            nextGatewayHandler.execute();
        }
    }

    //改造點(diǎn)2:抽出一個(gè)模板:用于外部調(diào)用的入口
    public void execute() {
        service();

        nextService();
    }
}

具體執(zhí)行的handler,其他的handler也需要去掉冗余,我這里就不顯示

/**
 * @Author: klm
 * @Description: 黑名單限流Handler
 */
@Slf4j
public class BlacklistHandler extends GatewayHandler {

    @Override
    public void service() {
        log.info("第二關(guān):黑名單限流");
        //改造點(diǎn)4:注釋掉,去掉冗余操作
//        nextService();
    }
}

測(cè)試類

/**
 * @Author: klm
 * @Description:
 */
public class test {
    public static void main(String[] args) {
        GatewayHandler currentLimitHandler = FactoryHandler.getCurrentLimitHandler();
//        currentLimitHandler.service();
        currentLimitHandler.execute();
    }
}

第二種:基于Mysql,模板模式+責(zé)任鏈模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限框架

db sql文件

CREATE TABLE `gateway_handler` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `handler_name` varchar(32) DEFAULT NULL COMMENT 'handler名稱',
  `handler_id` varchar(32) DEFAULT NULL COMMENT 'handler主鍵id',
  `prev_handler_id` varchar(32) DEFAULT NULL,
  `next_handler_id` varchar(32) DEFAULT NULL COMMENT '下一個(gè)handler',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='權(quán)限表';

-- ----------------------------
-- Records of gateway_handler
-- ----------------------------
INSERT INTO `gateway_handler` VALUES ('16', 'Api接口限流', 'currentLimitHandler', null, 'blacklistHandler');
INSERT INTO `gateway_handler` VALUES ('17', '黑名單攔截', 'blacklistHandler', 'currentLimitHandler', 'conversationHandler');
INSERT INTO `gateway_handler` VALUES ('18', '會(huì)話驗(yàn)證', 'conversationHandler', 'blacklistHandler', null);


-- 查詢第一個(gè)
SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId,next_handler_id as nextHandlerId FROM gateway_handler gh where gh.prev_handler_id is null;

SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId,next_handler_id as nextHandlerId FROM gateway_handler gh where gh.handler_id = 'blacklistHandler';

entity類

/**
 * @Author: klm
 * @Description:
 */
@Data
public class GatewayHandlerEntity implements Serializable, Cloneable {
    /**
     * 主鍵ID
     */
    private Integer id;
    /**
     * handler名稱
     */
    private String handlerName;
    /**
     * handler主鍵id
     */
    private String handlerId;
    /**
     * 下一個(gè)handler
     */
    private String nextHandlerId;

}

mapper類

/**
 * @Author: klm
 * @Description:
 */
@Repository
public interface GatewayHandlerMapper {

    /**
     * 獲取第一個(gè)執(zhí)行的handler
     *
     * @return
     */
    @Select("SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId," +
            "next_handler_id as nextHandlerId FROM gateway_handler gh " +
            "where gh.prev_handler_id is null;")
    GatewayHandlerEntity getFirstGatewayHandler();

    /**
     * 獲取下一個(gè)執(zhí)行的handler
     *
     * @param handlerId
     * @return
     */
    @Select("SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId," +
            "next_handler_id as nextHandlerId FROM gateway_handler gh " +
            "where gh.handler_id = #{handlerId};")
    GatewayHandlerEntity getNextGatewayHandler(@Param("handlerId") String handlerId);
}

service類

/**
 * @Author: klm
 * @Description:
 */
public interface GatewayHandlerService {

    /**
     * db責(zé)任鏈模式
     *
     * @return
     */
    GatewayHandler getGatewayHandler();
}

service實(shí)現(xiàn)類

/**
 * @Author: klm
 * @Description:
 */
@Service
@Slf4j
public class GatewayHandlerServiceImpl implements GatewayHandlerService {

    @Autowired
    private GatewayHandlerMapper gatewayHandlerMapper;

    @Override
    public GatewayHandler getGatewayHandler() {

        //獲取頭節(jié)點(diǎn)
        GatewayHandlerEntity firstGatewayHandler = gatewayHandlerMapper.getFirstGatewayHandler();
        if (Objects.isNull(firstGatewayHandler)) {
            log.info("未配置頭節(jié)點(diǎn),請(qǐng)注意");
            return null;
        }

        String handlerId = firstGatewayHandler.getHandlerId();
        if (StringUtils.isEmpty(handlerId)) {
            log.info("未配置頭節(jié)點(diǎn),請(qǐng)注意");
            return null;
        }

        //從容器中獲取bean對(duì)象
        GatewayHandler bean = SpringUtils.getBean(handlerId, GatewayHandler.class);
        if (Objects.isNull(bean)) {
            log.info("容器中未找到");
            return null;
        }

        //下一節(jié)點(diǎn)名稱
        String nextHandlerId = firstGatewayHandler.getNextHandlerId();

        //記錄當(dāng)前對(duì)象
        GatewayHandler currentBean = bean;
        while (!StringUtils.isEmpty(nextHandlerId)) {

            //從容器獲取下一個(gè)執(zhí)行handler
            GatewayHandler nextBean = SpringUtils.getBean(nextHandlerId, GatewayHandler.class);
            if (Objects.isNull(nextBean)) {
                break;
            }

            //查詢db獲取下一個(gè)handlerid
            GatewayHandlerEntity nextGatewayHandler = gatewayHandlerMapper.getNextGatewayHandler(nextHandlerId);
            if (Objects.isNull(nextGatewayHandler)) {
                break;
            }
            nextHandlerId = nextGatewayHandler.getNextHandlerId();


            currentBean.setNextGatewayHandler(nextBean);
            currentBean = nextBean;
        }

        return bean;
    }
}

抽象handler

/**
 * @Author: klm
 * @Date: 2020/8/5 09:41
 * @Description:
 */
public abstract class GatewayHandler {

    protected abstract void doService();

    protected GatewayHandler nextGatewayHandler;

    public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
        this.nextGatewayHandler = gatewayHandler;
    }

    protected void nextService() {
        if (nextGatewayHandler != null) {
//            nextGatewayHandler.doService();
            nextGatewayHandler.execute();
        }
    }

    public void execute() {
        doService();

        nextService();
    }
}

具體API接口限流Handler

/**
 * @Author: klm
 * @Description:
 */
@Component
@Slf4j
public class CurrentLimitHandler extends GatewayHandler {

    @Override
    public void doService() {
        log.info("第一關(guān):API接口限流");
//        nextService();
    }
}

具體黑名單限流Handler

/**
 * @Author: klm
 * @Description:
 */
@Component
@Slf4j
public class BlacklistHandler extends GatewayHandler {

    @Override
    public void doService() {
        log.info("第二關(guān):黑名單限流");
//        nextService();
    }
}

具體攔截用戶信息Handler

/**
 * @Author: klm
 * @Description:
 */
@Component
@Slf4j
public class ConversationHandler extends GatewayHandler {

    @Override
    public void doService() {
        log.info("第三關(guān):攔截用戶信息");
    }
}

SpringUtils類

/**
 * @Author: klm
 * @Description:
 */
@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //獲取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通過(guò)name獲取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通過(guò)class獲取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通過(guò)name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

controller類

/**
 * @Author: klm
 * @Description:
 */
@RestController
public class GatewayHandlerController {

    @Autowired
    private GatewayHandlerService gatewayHandlerService;

    @GetMapping("/test")
    public void test() {
        GatewayHandler gatewayHandler = gatewayHandlerService.getGatewayHandler();
        gatewayHandler.execute();
    }
}

application.yml文件

###服務(wù)啟動(dòng)端口號(hào)
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/responsibility?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver


####打印MyBatias日志
logging:
  level:
    ### 開(kāi)發(fā)環(huán)境使用DEBUG 生產(chǎn)環(huán)境info或者error
    com.xuyu.mapper: DEBUG

訪問(wèn)網(wǎng)頁(yè):執(zhí)行l(wèi)ocalhost:8080/test
執(zhí)行效果圖


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374