Spring StateMachine狀態(tài)機(jī)引擎在項(xiàng)目中的應(yīng)用(六)-外部調(diào)用&事務(wù)

說明

前面基本上涵蓋了一個(gè)項(xiàng)目中配置簡單狀態(tài)機(jī)的相關(guān)實(shí)現(xiàn)仇轻,不過還有一個(gè)關(guān)鍵點(diǎn)赴魁,就是外部代碼如何調(diào)用狀態(tài)機(jī),以及如何讓狀態(tài)機(jī)的持久化與業(yè)務(wù)邏輯代碼在同一個(gè)事務(wù)中氛堕,避免狀態(tài)機(jī)中狀態(tài)與實(shí)際訂單中不一致恬总,造成臟數(shù)據(jù)。

調(diào)用方式

外部調(diào)用狀態(tài)機(jī)引擎拳锚,需要以下三步:

  1. 通過創(chuàng)建/讀取的方式獲取當(dāng)前訂單對應(yīng)的狀態(tài)機(jī)引擎實(shí)例
  2. 構(gòu)造message熄浓。
  3. 發(fā)送message。

需要注意以下幾點(diǎn):

  1. 在狀態(tài)機(jī)發(fā)送完message之后年鸳,spring statemachine會通過ActionListener來監(jiān)聽趴久,同時(shí)判斷需要走到哪個(gè)Action中
  2. 只有在sendMessage完成之后,狀態(tài)機(jī)的當(dāng)前狀態(tài)才會更新為target狀態(tài)

所以對于調(diào)用狀態(tài)機(jī)搔确,做了以下代碼封裝:

接口:
/**
 * 存在狀態(tài)機(jī)做串聯(lián)時(shí)彼棍,統(tǒng)一的事務(wù)處理,將狀態(tài)機(jī)實(shí)例持久化也囊括在統(tǒng)一的事務(wù)中
 */
public interface StateMachineSendEventManager {

    /**
     * 發(fā)送狀態(tài)機(jī)event膳算,調(diào)用bizManagerImpl中具體實(shí)現(xiàn)座硕,同時(shí)處理狀態(tài)機(jī)持久化
     * <p>
     * 用于訂單的狀態(tài)變更
     *
     * @param request
     * @param operationTypeEnum
     * @param eventEnum
     * @return
     * @throws BusinessException
     */
    OrderBaseResponse sendStatusChangeEvent(BizOrderStatusRequest request,
                                            BizOrderOperationTypeEnum operationTypeEnum,
                                            BizOrderStatusChangeEventEnum eventEnum) throws Exception;


    /**
     * 同上,不過是用于訂單創(chuàng)建場景
     *
     * @param request
     * @param operationTypeEnum
     * @param eventEnum
     * @return
     * @throws Exception
     */
    BizOrderCreateResponse sendOrderCreateEvent(BizOrderCreateRequest request,
                                                BizOrderOperationTypeEnum operationTypeEnum,
                                                BizOrderStatusChangeEventEnum eventEnum) throws Exception;

}
對應(yīng)實(shí)現(xiàn)
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Slf4j
@Component("stateMachineSendEventManager")
public class StateMachineSendEventManagerImpl implements StateMachineSendEventManager {

    @Autowired
    private BizOrderRepository bizOrderRepository;

    @Autowired
    private BizOrderStateMachineBuildFactory bizOrderStateMachineBuildFactory;

    @Autowired
    @Qualifier("bizOrderRedisStateMachinePersister")
    private StateMachinePersister<BizOrderStatusEnum,BizOrderStatusChangeEventEnum,String> bizOrderRedisStateMachinePersister;

    /**
     * 發(fā)送狀態(tài)機(jī)event涕蜂,調(diào)用bizManagerImpl中具體實(shí)現(xiàn)华匾,同時(shí)處理狀態(tài)機(jī)持久化
     * <p>
     * 這里會send stateMachine event,從而跳轉(zhuǎn)到對應(yīng)的action --> bizManagerImpl机隙,出現(xiàn)事務(wù)嵌套的情況
     * <p>
     * 不過事務(wù)傳播默認(rèn)是TransactionDefinition.PROPAGATION_REQUIRED蜘拉,所以還是同一個(gè)事務(wù)中,
     * 只是事務(wù)范圍擴(kuò)大至stateMachine的持久化場景了,不要修改默認(rèn)的傳播機(jī)制
     *
     * @param request
     * @return
     * @throws BusinessException
     */
    @Override
    @Transactional(value = "finOrderocTransactionManager", rollbackFor = {BusinessException.class, Exception.class})
    public OrderBaseResponse sendStatusChangeEvent(BizOrderStatusRequest request,
                                                   BizOrderOperationTypeEnum operationTypeEnum,
                                                   BizOrderStatusChangeEventEnum eventEnum) throws Exception {

        // 獲取狀態(tài)機(jī)信息
        StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine =
                getStateMachineFromStatusReq(request, operationTypeEnum);


        boolean result = statusChangeCommonOps(stateMachine, request, eventEnum);

        OrderBaseResponse resp = new OrderBaseResponse();
        if (!result) {
            resp.setResultCode(BizOrderErrorCode.ORDER_STATE_MACHINE_EXECUTE_ERR.name());
            resp.setMsg("訂單狀態(tài)操作異常");
        }

        log.info("order status change resp is {}", resp);

        // 更新redis中數(shù)據(jù)
        // 發(fā)送event寫log的動(dòng)作還是放在業(yè)務(wù)里面有鹿,這里無法囊括所有業(yè)務(wù)數(shù)據(jù)
        if (result) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    // 將數(shù)據(jù)持久化到redis中,以bizOrderId作為對應(yīng)Key
                    try {
                        bizOrderRedisStateMachinePersister.persist(stateMachine, request.getBizCode());
                    } catch (Exception e) {
                        log.error("Persist bizOrderStateMachine error", e);
                    }
                }
            });
        }

        return resp;
    }

    /**
     * 同上诸尽,不過是用于訂單創(chuàng)建場景,請求為BizOrderCreateRequest
     *
     * @param bizOrderCreateRequest
     * @param operationTypeEnum
     * @param eventEnum
     * @return
     * @throws Exception
     */
    @Override
    @Transactional(value = "finOrderocTransactionManager", rollbackFor = {BusinessException.class, Exception.class})
    public BizOrderCreateResponse sendOrderCreateEvent(BizOrderCreateRequest bizOrderCreateRequest,
                                                       BizOrderOperationTypeEnum operationTypeEnum,
                                                       BizOrderStatusChangeEventEnum eventEnum) throws Exception {


        // 獲取對應(yīng)的stateMachine
        StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine =
                getStateMachineFromCreateReq(bizOrderCreateRequest, operationTypeEnum);

        Message<BizOrderStatusChangeEventEnum> eventMsg = MessageBuilder.withPayload(eventEnum)
                // key 與 status change 時(shí)不同印颤,對應(yīng)的model也不同
                .setHeader(BizOrderConstants.BIZORDER_CONTEXT_CREATE_KEY, bizOrderCreateRequest)
                // 根據(jù)傳遞過來的訂單狀態(tài)決定后續(xù)choice跳轉(zhuǎn)
                .setHeader(BizOrderConstants.FINAL_STATUS_KEY, bizOrderCreateRequest.getBizOrderCreateModel().getOrderStatus())
                .build();

        BizOrderCreateResponse createResponse = new BizOrderCreateResponse();

        boolean sendResult = false;

        if (BizOrderStateMachineUtils.acceptEvent(stateMachine, eventMsg)) {
            sendResult = stateMachine.sendEvent(eventMsg);
            log.info("order statemachine send event={},result={}", eventMsg, sendResult);
        } else {
            createResponse.setResultCode(BizOrderErrorCode.NO_ORDER_STATE_MACHINE_TRANSTION_ERR.name());
            createResponse.setMsg("當(dāng)前訂單無法執(zhí)行請求動(dòng)作");
        }

        if (sendResult) {
            createResponse.setBizOrderId(bizOrderCreateRequest.getBizOrderCreateModel().getBizOrderId());
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    // 將數(shù)據(jù)持久化到redis中,以bizOrderId作為對應(yīng)Key
                    try {
                        bizOrderRedisStateMachinePersister.persist(stateMachine,
                                createResponse.getBizOrderId());
                    } catch (Exception e) {
                        throw new BusinessException(BizOrderErrorCode.ORDER_STATE_MACHINE_EXECUTE_ERR, "狀態(tài)機(jī)持久化失敗");
                    }
                }
            });
        }


        return createResponse;
    }

    /**
     * 狀態(tài)處理的通用操作抽取
     *
     * @param stateMachine  狀態(tài)機(jī)
     * @param statusRequest 狀態(tài)變更請求
     * @return 執(zhí)行結(jié)果
     * @throws Exception 異常
     */
    private boolean statusChangeCommonOps(
            StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine,
            BizOrderStatusRequest statusRequest,
            BizOrderStatusChangeEventEnum eventEnum) {

        log.info("order statemachine send event={}", eventEnum);


        // 執(zhí)行引擎您机,sendEvent,result為執(zhí)行結(jié)果,通過actionListener跳轉(zhuǎn)到對應(yīng)的Action
        Message<BizOrderStatusChangeEventEnum> eventMsg = MessageBuilder.
                withPayload(eventEnum)
                .setHeader(BizOrderConstants.BIZORDER_CONTEXT_KEY, statusRequest)
                // 只有在需要判斷(choice)的場景才用得到,guard實(shí)現(xiàn)中使用
                .setHeader(BizOrderConstants.FINAL_STATUS_KEY, statusRequest.getBizOrderStatusModel().getTargetOrderStatus())
                .build();

        // 取到對應(yīng)的狀態(tài)機(jī)年局,判斷是否可以執(zhí)行
        boolean result = false;

        // 狀態(tài)機(jī)的當(dāng)前狀態(tài)际看,只有在執(zhí)行結(jié)束后才會變化,也就是節(jié)點(diǎn)對應(yīng)的action執(zhí)行完才會變更
        // 所以在result=true的情況下矢否,更新狀態(tài)機(jī)的持久化狀態(tài)才有效
        if (BizOrderStateMachineUtils.acceptEvent(stateMachine, eventMsg)) {
            result = stateMachine.sendEvent(eventMsg);
            log.info("order statemachine send event={},result={}", eventMsg, result);
        } else {
            throw new BusinessException(BizOrderErrorCode.NO_ORDER_STATE_MACHINE_TRANSTION_ERR, "當(dāng)前訂單無法執(zhí)行請求動(dòng)作");
        }
        return result;

    }

    /**
     * 從statusRequest中獲取statemachine實(shí)例
     *
     * @param statusRequest     狀態(tài)請求
     * @param operationTypeEnum 操作類型
     * @return 狀態(tài)機(jī)實(shí)例
     * @throws Exception 異常
     */
    private StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>
    getStateMachineFromStatusReq(BizOrderStatusRequest statusRequest,
                                 BizOrderOperationTypeEnum operationTypeEnum) throws Exception {
        log.info("Order status change request={},operationType={}", statusRequest, operationTypeEnum);

        if (!StringUtils.equals(statusRequest.getBizCode(), statusRequest.getBizOrderStatusModel().getBizOrderId())) {
            throw new BusinessException(BizOrderErrorCode.ORDER_COMMON_ILLEGAL_ARGUMENT, "請求數(shù)據(jù)異常");
        }

        // 查詢訂單仲闽,判斷請求數(shù)據(jù)是否合法
        BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(statusRequest.getBizCode());
        if (null == bizOrder
                || !StringUtils.equals(bizOrder.getBizType(), statusRequest.getBizOrderStatusModel().getBizType())
                || !StringUtils.equals(bizOrder.getOrderStatus(), statusRequest.getBizOrderStatusModel().getCurrentOrderStatus())
                ) {
            throw new BusinessException(BizOrderErrorCode.ORDER_COMMON_ILLEGAL_ARGUMENT, "請求數(shù)據(jù)與訂單實(shí)際數(shù)據(jù)不符");
        }

        // 構(gòu)造狀態(tài)機(jī)模板
        StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> srcStateMachine =
                bizOrderStateMachineBuildFactory.createStateMachine(statusRequest.getBizOrderStatusModel().getBizType(),
                        statusRequest.getBizOrderStatusModel().getSubBizType());

        // 從redis中獲取對應(yīng)的statemachine,并判斷當(dāng)前節(jié)點(diǎn)是否可以滿足僵朗,如果無法從redis中獲取對應(yīng)的的statemachine赖欣,則取自DB
        StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine
                = bizOrderRedisStateMachinePersister.restore(srcStateMachine, statusRequest.getBizCode());

        // 由于DB中已持久化,基本上不太可能出現(xiàn)null的情況验庙,目前唯一能想到會出現(xiàn)的情況就是緩存擊穿顶吮,先拋錯(cuò)
        if (null == stateMachine) {
            throw new BusinessException(BizOrderErrorCode.NO_CORRESPONDING_STATEMACHINE_ERR, "不存在訂單對應(yīng)的狀態(tài)機(jī)實(shí)例");
        }
        log.info("order stateMachine info is {}", srcStateMachine);

        return stateMachine;
    }

    /**
     * 獲取statemachine實(shí)例
     *
     * @param createRequest     狀態(tài)請求
     * @param operationTypeEnum 操作類型
     * @return 狀態(tài)機(jī)實(shí)例
     * @throws Exception 異常
     */
    private StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> getStateMachineFromCreateReq(BizOrderCreateRequest createRequest,
                                                                                                         BizOrderOperationTypeEnum operationTypeEnum) throws Exception {
        log.info("Order create request={},operationType={}", createRequest, operationTypeEnum);

        // 構(gòu)造狀態(tài)機(jī)模板
        StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> srcStateMachine =
                bizOrderStateMachineBuildFactory.createStateMachine(createRequest.getBizOrderCreateModel().getBizType(),
                        createRequest.getBizOrderCreateModel().getSubBizType());

        if (null == srcStateMachine) {
            throw new BusinessException(BizOrderErrorCode.NO_CORRESPONDING_STATEMACHINE_ERR, "不存在訂單對應(yīng)的狀態(tài)機(jī)實(shí)例");
        }

        // 如果是sign,表示訂單已存在粪薛,需要額外判斷并restore狀態(tài)機(jī)悴了;如果是直接create,則不需要處理這些判斷
        if (StringUtils.equalsIgnoreCase(BizOrderOperationTypeEnum.SIGN.getOperationType(),
                createRequest.getOperationType())) {
            if (!StringUtils.equals(createRequest.getBizCode(), createRequest.getBizOrderCreateModel().getBizOrderId())) {
                throw new BusinessException(BizOrderErrorCode.ORDER_COMMON_ILLEGAL_ARGUMENT, "請求數(shù)據(jù)異常");
            }

            // 查詢訂單,判斷請求數(shù)據(jù)是否合法
            BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(createRequest.getBizCode());
            if (null == bizOrder
                    || !StringUtils.equals(bizOrder.getBizType(), createRequest.getBizOrderCreateModel().getBizType())
                    ) {
                throw new BusinessException(BizOrderErrorCode.ORDER_COMMON_ILLEGAL_ARGUMENT, "請求數(shù)據(jù)與訂單實(shí)際數(shù)據(jù)不符");
            }

            // 從redis中獲取對應(yīng)的statemachine湃交,并判斷當(dāng)前節(jié)點(diǎn)是否可以滿足熟空,如果無法從redis中獲取對應(yīng)的的statemachine,則取自DB
            StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine
                    = bizOrderRedisStateMachinePersister.restore(srcStateMachine, createRequest.getBizOrderCreateModel().getBizOrderId());

            // 由于DB中已持久化搞莺,基本上不太可能出現(xiàn)null的情況息罗,目前唯一能想到會出現(xiàn)的情況就是緩存擊穿,先拋錯(cuò)
            if (null == stateMachine) {
                throw new BusinessException(BizOrderErrorCode.NO_CORRESPONDING_STATEMACHINE_ERR, "不存在訂單對應(yīng)的狀態(tài)機(jī)實(shí)例");
            }

            return stateMachine;
        }

        log.info("order stateMachine info is {}", srcStateMachine);

        return srcStateMachine;
    }
}

這里需要關(guān)注BizOrderStateMachineUtils.acceptEvent才沧,相當(dāng)于在執(zhí)行之前判斷是否可以執(zhí)行阱当,其實(shí)sendEvent中存在一樣判斷是否可執(zhí)行的代碼,不過這里抽取出來做了一個(gè)事前判斷糜工,實(shí)現(xiàn)如下:

import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.StateMachineUtils;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.trigger.DefaultTriggerContext;
import org.springframework.statemachine.trigger.Trigger;

public class BizOrderStateMachineUtils {

    /**
     * 判斷是否可以執(zhí)行對應(yīng)的event
     *
     * @param stateMachine
     * @param eventMsg
     * @return
     */
    public static boolean acceptEvent(StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine,
                                      Message<BizOrderStatusChangeEventEnum> eventMsg) {
        State<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> cs = stateMachine.getState();

        for (Transition<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> transition : stateMachine.getTransitions()) {
            State<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> source = transition.getSource();
            Trigger<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> trigger = transition.getTrigger();

            if (cs != null && StateMachineUtils.containsAtleastOne(source.getIds(), cs.getIds())) {
                if (trigger != null && trigger.evaluate(new DefaultTriggerContext<>(eventMsg.getPayload()))) {
                    return true;
                }
            }
        }
        return false;
    }

}

其他說明:

  1. 關(guān)注getStateMachineFromXXX弊添,這里其實(shí)就是調(diào)用了builderFactory創(chuàng)建了對應(yīng)的實(shí)例。
  2. 還有個(gè)遺留問題捌木,即每次從redis/db中restore時(shí)油坝,都需要生成一個(gè)最初狀態(tài)的狀態(tài)機(jī)實(shí)例,然后傳入進(jìn)去刨裆,轉(zhuǎn)化成當(dāng)前狀態(tài)澈圈,這種方式可能會造成比較大的資源消耗。
  3. 在sendXXXEvent方法上帆啃,都加上了@Transactional注解瞬女,這里代碼中的注釋也說的很清楚,會跟AbstractBizManager中的@Tranactional形成嵌套努潘,所以事務(wù)傳播方式只能是默認(rèn)的TransactionDefinition.PROPAGATION_REQUIRED诽偷,不能修改,否則會問題疯坤。
  4. 至于再外部調(diào)用报慕,直接調(diào)用StateMachineSendEventManager即可,與狀態(tài)機(jī)無關(guān)了压怠。
  5. 至此眠冈,完結(jié)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菌瘫,一起剝皮案震驚了整個(gè)濱河市蜗顽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雨让,老刑警劉巖雇盖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宫患,居然都是意外死亡刊懈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門娃闲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虚汛,“玉大人,你說我怎么就攤上這事皇帮【砹ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵属拾,是天一觀的道長将谊。 經(jīng)常有香客問我,道長渐白,這世上最難降的妖魔是什么尊浓? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纯衍,結(jié)果婚禮上栋齿,老公的妹妹穿的比我還像新娘。我一直安慰自己襟诸,他們只是感情好瓦堵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歌亲,像睡著了一般菇用。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陷揪,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天惋鸥,我揣著相機(jī)與錄音,去河邊找鬼悍缠。 笑死揩慕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扮休。 我是一名探鬼主播迎卤,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玷坠!你這毒婦竟也來了蜗搔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤八堡,失蹤者是張志新(化名)和其女友劉穎樟凄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兄渺,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缝龄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叔壤。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞎饲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炼绘,到底是詐尸還是另有隱情嗅战,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布俺亮,位于F島的核電站驮捍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脚曾。R本人自食惡果不足惜东且,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望本讥。 院中可真熱鬧苇倡,春花似錦、人聲如沸囤踩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堵漱。三九已至综慎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勤庐,已是汗流浹背示惊。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愉镰,地道東北人米罚。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像丈探,于是被迫代替她去往敵國和親录择。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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