Spring StateMachine 狀態(tài)機引擎在項目中的應用(五)-一種代碼抽象

背景

上文說到BizManager的實現婿脸,其實整體看讯屈,這塊東西跟spring statemachine并沒有什么關系亡嫌,純粹是個人寫的收不住了蛔垢,把近期的一些東西整理下击孩。對這部分不感興趣的可以跳過,直接看下一章節(jié)鹏漆,那塊是從外部調用狀態(tài)機引擎的實現巩梢,還更有用一些。

BaseBizManager定義

BaseBizManager統一規(guī)范業(yè)務處理的接口艺玲,其定義如下:

import org.springframework.statemachine.StateMachine;

/**
 * 定義寫服務的入口process模板方法
 *
 * @param <T>
 * @param <R>
 */
@FunctionalInterface
public interface BaseBizManager<T, R> {

    /**
     * process模板括蝠,用于處理通用寫服務相關方法,包括處理冪等饭聚、記錄日志忌警、事務保證等
     *
     * @param request
     * @return
     * @throws BusinessException
     */
    R process(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws BusinessException;

}

注意這里用了個可選參數stateMachines,有些場景在處理邏輯內部是不需要狀態(tài)機的秒梳,可不傳法绵。

抽象類實現

抽象類中使用到了spring的反射工具類箕速,封裝了一層,先提供出來:

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Objects;

public class ReflectionUtil {

    /**
     * 對Spring的ReflectionUtils中getValue方法做簡單封裝
     *
     * @param object
     * @param key
     * @param defaultVal
     * @return
     */
    public static Object getValue(Object object, String key, Object defaultVal) {
        Field field = ReflectionUtils.findField(object.getClass(), key);
        if (Objects.isNull(field)) {
            return defaultVal;
        }
        field.setAccessible(true);
        return ReflectionUtils.getField(field, object);
    }
}

然后是對應的抽象類實現

import com.vipfins.finance.middleplatform.order.util.ReflectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.annotation.Transactional;

/**
 * 處理訂單更新模板類朋譬, 寫DB操作盐茎、日志、處理統一事務\冪等操作
 */
@Slf4j
public abstract class AbstractBizManagerImpl<T,R> implements BaseBizManager<T,R>{
    
    @Autowired
    private BizOrderIdemRepository orderIdemRepository;

    @Autowired
    private BizOrderLogEventPublisher bizOrderLogEventPublisher;

    @Override
    @Transactional(value = "finOrderocTransactionManager", rollbackFor = {BusinessException.class, Exception.class})
    public R process(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws BusinessException {
        try {
            // 冪等控制
            if (checkIdem(request)) {
                log.info("check idempotence bingo,request={}", request);
                throw new BusinessException(BizOrderErrorCode.SUCCESS, "冪等操作徙赢,本次請求忽略");
            }

            // 實際業(yè)務處理
            R resp = doProcess(request, stateMachines);
            log.info("response = {}", resp);

            return resp;
        } catch (BusinessException e) {
            log.error("process Business Exception = {}", e);
            throw new BusinessException(e.getErrorCode(), ExceptionUtil.getErrorMsg(e));
        } catch (Exception e) {
            log.error("process Exception = {}", e);
            throw new BusinessException(BizOrderErrorCode.ORDER_GENERIC_EXCEPTION, ExceptionUtil.getErrorMsg(e));
        }
    }
    
    /**
     * 實際的業(yè)務操作
     *
     * @param request 業(yè)務請求
     * @param stateMachines 將上游處理后的stateMachine傳遞進來字柠,后續(xù)持久化,可選參數
     * @return 業(yè)務結果
     */
    public abstract R doProcess(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception;

    /**
     * 判斷是否冪等
     * 冪等 ==> 返回true
     *
     * @param request
     * @return
     */
    private boolean checkIdem(T request) {
        boolean result = false;
        // 反射獲取請求中基礎數據
        String bizOrderId = (String) ReflectionUtil.getValue(request, "bizCode", "");
        String operationType = (String) ReflectionUtil.getValue(request, "operationType", "");
        String sourceId = (String) ReflectionUtil.getValue(request, "sourceId", "");

        String idemNo = bizOrderId + operationType + sourceId;
        BizOrderIdem idem = new BizOrderIdem(idemNo, bizOrderId);

        // 違反唯一性約束
        try {
            orderIdemRepository.insert(idem);
        } catch (DuplicateKeyException e) {
            result = true;
            log.error("接口重復消費, idemNo = {}, orderCode = {}, exception = {}", idemNo, bizOrderId, e);
        } catch (Exception e) {
            log.error("未知異常狡赐,exception={}", e);
        }

        return result;
    }
}

不同場景的BizManager實現

綜合來看窑业,有三種不同的bizManager實現:

  1. 創(chuàng)建訂單,創(chuàng)建邏輯自成一系枕屉,與其他業(yè)務邏輯思路均不相同
  2. 訂單狀態(tài)變化数冬,這塊主要關注訂單狀態(tài)的變化,當前是是什么搀庶,之后是什么拐纱,基本上所有訂單狀態(tài)變化的服務實現思路都一致
  3. 訂單狀態(tài)自動遷移,比如訂單從審核拒絕自動遷移到關閉狀態(tài)哥倔,這種需要主動觸發(fā)spring statemachine event秸架。

下面分別針對這三種實現方式說明:

訂單創(chuàng)建
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Map;

@Component("bizOrderCreateBizManager")
@Slf4j
public class BizOrderCreateBizManagerImpl extends AbstractBizManagerImpl<BizOrderCreateRequest, BizOrderCreateResponse> {

    @Resource
    private BaseConvertor<BizOrderCreateModel, BizOrder> createBizOrderConvertor;

    @Resource
    private BaseConvertor<BizOrderExtendsCreateModel, BizOrderExtends> createBizOrderExtendsConvertor;

    @Resource
    private BaseConvertor<BizOrderChannelCreateModel, BizOrderChannel> createBizOrderChannelConvertor;

    @Resource
    private BaseConvertor<BizOrderActivityCreateModel, BizOrderActivity> createBizOrderActivityConvertor;

    @Autowired
    private BizOrderRepository bizOrderRepository;

    @Autowired
    private BizOrderExtendsRepository bizOrderExtendsRepository;

    @Autowired
    private BizOrderChannelRepository bizOrderChannelRepository;

    @Autowired
    private BizOrderActivityRepository bizOrderActivityRepository;

    @Autowired
    private BizOrderLogEventPublisher bizOrderLogEventPublisher;
    
    @Autowired
    private FinMerchantContractQueryService contractQueryService;

    /**
     * 實際的業(yè)務操作
     *
     * @param request 業(yè)務請求
     * @return 結果
     */
    @Override
    public BizOrderCreateResponse doProcess(BizOrderCreateRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
        BizOrderCreateModel createModel = request.getBizOrderCreateModel();

        ...... // 校驗邏輯

        // 構造對應的訂單模型. 同時修改
        BizOrder bizOrder = createBizOrderConvertor.modelToEntityConvertor(createModel);
        bizOrder.setCallSystem(request.getCallSystem());
        
        // MDC日志埋點
        LogUtil.setBizId(bizOrder.getBizOrderId());

        // 對于XXX業(yè)務,在創(chuàng)建訂單時由于沒有詳細金額咆蒿,而是在簽約是才把資金等信息傳遞過來东抹,前期對于傳遞過來的數據不做處理(無效)
        if (!BizOrderBizTypeEnum.isIn(bizOrder.getBizType(), BizOrderBizTypeEnum.EMPLOAN) &&
                CollectionUtils.isNotEmpty(request.getSubBizOrderCreateModels())) {
            // 判斷主訂單中金額是否等于所有子訂單金額
            BigDecimal totalAmount = bizOrder.getRealAmount();
            BigDecimal totalSumFromSub = request.getSubBizOrderCreateModels().parallelStream().map(model ->
                    BigDecimal.valueOf(model.getRealAmount()))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            if(!totalAmount.equals(totalSumFromSub)){
                throw new BusinessException(BizOrderErrorCode.ORDER_AMOUNT_NOT_MATCH,"主子訂單金額不匹配");
            }

            bizOrder.setOrderLevel(BizOrderLevelEnum.MAIN.getOrderLevel()); // 主訂單

            request.getSubBizOrderCreateModels().parallelStream().forEach(subOrderModel -> {
                BizOrder subBizOrder = createBizOrderConvertor.modelToEntityConvertor(subOrderModel);
                // 重新設置parentId及orderLevel
                subBizOrder.setOrderLevel(BizOrderLevelEnum.DETAIL.getOrderLevel());
                subBizOrder.setParentId(bizOrder.getBizOrderId());

                bizOrderRepository.insertSelective(subBizOrder);
            });
        }
        bizOrderRepository.insertSelective(bizOrder);

        ......

        BizOrderCreateResponse createResponse = new BizOrderCreateResponse();
        createResponse.setBizOrderId(bizOrder.getBizOrderId());

        // send log event
        Map attrMap = com.google.common.collect.Maps.newHashMap();
        attrMap.put(AttributesKeyEnum.TARGET_STATUS.getShortKeyName(), createModel.getOrderStatus());
        attrMap.put(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem());

        // 發(fā)送記錄日志的event
        bizOrderLogEventPublisher.bizOrderEventPublish(bizOrder,
                request.getOperationType(),
                BizOrderStatusEnum.CREATE.getStatus(),
                attrMap);

        return createResponse;
    }

}

這里的convertor都是基于Orika的,有興趣的可以度娘下了解沃测。

可以看到缭黔,其實處理很簡單,就是將請求參數構造成訂單對象蒂破,然后入庫馏谨,加入了一部分數據校驗。

訂單狀態(tài)變遷—有明確目標

訂單狀態(tài)變遷附迷,由于重復代碼比較多惧互,所以這里抽象出來了另外一個模板類,如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.Instant;
import java.util.Date;
import java.util.Map;

/**
 * 主要處理訂單狀態(tài)變更喇伯,變更到穩(wěn)定節(jié)點狀態(tài)喊儡,而非審核中Auditing這種中間狀態(tài)
 *
 * 直接落庫,發(fā)送log event稻据,不需要其他操作
 */
@Slf4j
public abstract class BaseStatusSimpleChangeBizManagerImpl<T, R> extends AbstractBizManagerImpl<T, R> {

    @Autowired
    private BizOrderRepository bizOrderRepository;

    @Autowired
    private BizOrderLogEventPublisher bizOrderLogEventPublisher;

    /**
     * 實際的業(yè)務操作
     *
     * @param request       業(yè)務請求
     * @return 業(yè)務結果
     */
    public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {

        // 重新獲取訂單信息,肯定不是空艾猜,不然就在上層攔截了
        BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(request.getBizCode());

        BizOrderStatusModel statusModel = request.getBizOrderStatusModel();

        BizOrder newBizOrder = new BizOrder();

        newBizOrder.setOrderStatus(wrapTargetStatus(statusModel)); // 前面已經處理對應的狀態(tài)設置
        newBizOrder.setUpdateTime(Date.from(Instant.now()));
        newBizOrder.setFinishReason(wrapFinishReason(statusModel)); // 需要子類處理

        // 判斷是否需要處理attributes 及 effectMoney
        if (null != statusModel.getAttributesMap() && statusModel.getAttributesMap().size() > 0) {
            Map<String, String> curAttributes = AttributeUtil.fromString(bizOrder.getAttributesStr());
            curAttributes.putAll(statusModel.getAttributesMap());
            newBizOrder.setAttributesStr(AttributeUtil.toString(curAttributes));
        }

        newBizOrder.setBizOrderId(bizOrder.getBizOrderId());

        // 訂單信息保存
        int updateCount = bizOrderRepository.updateByPrimaryKeySelective(newBizOrder);
        if (1 != updateCount) {
            throw new BusinessException(BizOrderErrorCode.ORDER_UPDATE_ERROR, "訂單狀態(tài)變更失敗");
        }

        // send log event
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                // 如果effectMoney不為空,則記錄到log中
                Map<String,String> attributesMap = Maps.newHashMap();
                if (null != statusModel.getEffectAmount()) {
                    attributesMap.put(AttributesKeyEnum.EFFECT_MONEY_AMOUNT.getShortKeyName(), statusModel.getEffectAmount().toString());
                }
                if(null != statusModel.getAttributesMap()) {
                    attributesMap.putAll(statusModel.getAttributesMap());
                }
                attributesMap.put(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem());

                bizOrderLogEventPublisher.bizOrderEventPublish(newBizOrder, request.getOperationType(),
                        bizOrder.getOrderStatus(), attributesMap);
            }
        });

        return new OrderBaseResponse(); // 返回值不會用到
    }

    /**
     * 構造目標狀態(tài)
     *
     * @param statusModel 狀態(tài)模型
     * @return 結果
     */
    public abstract String wrapTargetStatus(BizOrderStatusModel statusModel);

    /**
     * 構造關閉原因,僅需要在close匆赃、success淤毛、cancel場景下處理
     * @param statusModel 狀態(tài)模型
     * @return 結果
     */
    public abstract String wrapFinishReason(BizOrderStatusModel statusModel);

}

比如訂單狀態(tài)變?yōu)槿∠涂梢院喕瘜崿F成這個樣子(實現BaseStatusSimpleChangeBizManagerImpl):

import org.apache.commons.lang3.StringUtils;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;

@Component("bizOrderCancelBizManager")
public class BizOrderCancelBizManagerImpl extends BaseStatusSimpleChangeBizManagerImpl<BizOrderStatusRequest, OrderBaseResponse> {

    /**
     * 實際的業(yè)務操作
     *
     * @param request       業(yè)務請求
     * @return 業(yè)務結果
     */
    @Override
    public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
        return super.doProcess(request,stateMachines);
    }

    /**
     * 構造目標狀態(tài)
     *
     * @param statusModel 狀態(tài)模型
     * @return 結果
     */
    @Override
    public String wrapTargetStatus(BizOrderStatusModel statusModel) {
        return BizOrderStatusEnum.CANCEL.getStatus();
    }

    /**
     * 關閉原因炸庞,僅需要在close、success荚斯、cancel場景下處理
     *
     * @param statusModel
     * @return
     */
    @Override
    public String wrapFinishReason(BizOrderStatusModel statusModel) {
        if (StringUtils.isBlank(statusModel.getFinishReason())) {
            return "CANCEL_FROM_" + StringUtils.upperCase(statusModel.getCurrentOrderStatus());
        }
        return statusModel.getFinishReason();
    }
}

基本上每個簡單的狀態(tài)變化邏輯埠居,都是如此實現。

訂單狀態(tài)變遷--需要再次發(fā)送消息

這種同樣抽象出了一個模板類事期,如下:

import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.Instant;
import java.util.Date;

/**
 * 主要主動處理訂單到達中間狀態(tài)的變更處理滥壕,比如toXXXAction對應的基類
 *
 * 需要發(fā)送statemachine event 用來串聯下一步操作,并寫db兽泣,發(fā)log event
 */
@Slf4j
public abstract class BaseStatusToUnstableTargetBizManagerImpl<T, R> extends AbstractBizManagerImpl<T, R> {

    @Autowired
    private BizOrderRepository bizOrderRepository;

    @Autowired
    private BizOrderLogEventPublisher bizOrderLogEventPublisher;

    @Autowired
    private BeanMapper beanMapper;

    /**
     * 實際的業(yè)務操作
     *
     * @param request       業(yè)務請求
     * @param stateMachines 將上游處理后的stateMachine傳遞進來绎橘,后續(xù)持久化
     * @return 業(yè)務結果
     */
    OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
        // 修改狀態(tài)
        BizOrderStatusModel statusModel = request.getBizOrderStatusModel();

        BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(statusModel.getBizOrderId());
        String currentStatus = bizOrder.getOrderStatus();

        BizOrder newBizOrder = new BizOrder();
        newBizOrder.setOrderStatus(statusModel.getTargetOrderStatus());
        newBizOrder.setBizOrderId(bizOrder.getBizOrderId());
        newBizOrder.setUpdateTime(Date.from(Instant.now()));

        int updateCount = bizOrderRepository.updateByPrimaryKeySelective(newBizOrder);
        if (1 != updateCount) {
            throw new BusinessException(BizOrderErrorCode.ORDER_UPDATE_ERROR, "更新訂單狀態(tài)失敗");
        }

        // send spring statemachine event
        if (null != stateMachines && stateMachines.length > 0) {
            StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine = stateMachines[0];

            // 重新構造request
            statusModel.setFinishReason(wrapFinishReason(statusModel.getTargetOrderStatus()) + statusModel.getFinishReason());
            statusModel.setCurrentOrderStatus(statusModel.getTargetOrderStatus());
            statusModel.setTargetOrderStatus(wrapTargetOrderStatus());

            request.setBizOrderStatusModel(statusModel); // need or not
            
            Message<BizOrderStatusChangeEventEnum> eventMsg = MessageBuilder.
                    withPayload(wrapToEvent())
                    .setHeader(BizOrderConstants.BIZORDER_CONTEXT_KEY, request)
                    .build();

            stateMachine.sendEvent(eventMsg);
        }

        // send log event
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
//            @Retryable(maxAttempts = 5)
            public void afterCommit() {
                // 發(fā)送記錄日志的event
                bizOrderLogEventPublisher.bizOrderEventPublish(bizOrder,
                        request.getOperationType(), currentStatus,
                        Maps.newHashMap(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem()));

            }
        });

        return new OrderBaseResponse();
    }

    /**
     * 構造待發(fā)送的statemachine event
     *
     * @return 對應的event
     */
    public abstract BizOrderStatusChangeEventEnum wrapToEvent();

    /**
     * 構造對應的狀態(tài)
     *
     * @return 狀態(tài)
     */
    public abstract String wrapTargetOrderStatus();

    /**
     * 構造對應的結束原因,只有finish態(tài)才需要返回唠倦,否則直接return null即可
     *
     * @param curOrderStatus 當前訂單狀態(tài)
     * @return 訂單結束原因
     */
    public abstract String wrapFinishReason(String curOrderStatus);
}

同樣的称鳞,比如訂單需要自動跳轉到關單情況就可以實現如下:

import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;

/**
 * 跳轉到close狀態(tài) 前一節(jié)點的邏輯處理
 */
@Component("bizOrderToCloseBizManager")
public class BizOrderToCloseBizManagerImpl extends BaseStatusToUnstableTargetBizManagerImpl<BizOrderStatusRequest, OrderBaseResponse> {

    /**
     * 實際的業(yè)務操作
     *
     * @param request       業(yè)務請求
     * @param stateMachines 將上游處理后的stateMachine傳遞進來,后續(xù)持久化
     * @return 業(yè)務結果
     */
    @Override
    public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
        return super.doProcess(request, stateMachines);
    }

    /**
     * 構造待發(fā)送的statemachine event
     *
     * @return 對應的event
     */
    @Override
    public BizOrderStatusChangeEventEnum wrapToEvent() {
        return BizOrderStatusChangeEventEnum.EVT_SYS_CLOSE;
    }

    /**
     * 構造對應的狀態(tài)
     *
     * @return 狀態(tài)
     */
    @Override
    public String wrapTargetOrderStatus() {
        return BizOrderStatusEnum.CLOSE.getStatus() + "";
    }

    /**
     * 構造對應的結束原因稠鼻,只有finish態(tài)才需要返回冈止,否則直接return null即可
     *
     * @param curOrderStatus 當前訂單狀態(tài)
     * @return 訂單結束原因
     */
    @Override
    public String wrapFinishReason(String curOrderStatus) {
        return "CLOSE_FROM_" + curOrderStatus + "||";
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市候齿,隨后出現的幾起案子熙暴,更是在濱河造成了極大的恐慌,老刑警劉巖慌盯,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件周霉,死亡現場離奇詭異,居然都是意外死亡亚皂,警方通過查閱死者的電腦和手機俱箱,發(fā)現死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灭必,“玉大人匠楚,你說我怎么就攤上這事〕Р疲” “怎么了芋簿?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長璃饱。 經常有香客問我与斤,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任撩穿,我火速辦了婚禮磷支,結果婚禮上,老公的妹妹穿的比我還像新娘食寡。我一直安慰自己雾狈,他們只是感情好,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布抵皱。 她就那樣靜靜地躺著善榛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呻畸。 梳的紋絲不亂的頭發(fā)上移盆,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音伤为,去河邊找鬼咒循。 笑死,一個胖子當著我的面吹牛绞愚,可吹牛的內容都是我干的叙甸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼位衩,長吁一口氣:“原來是場噩夢啊……” “哼蚁署!你這毒婦竟也來了?” 一聲冷哼從身側響起蚂四,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤光戈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遂赠,有當地人在樹林里發(fā)現了一具尸體久妆,經...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年跷睦,在試婚紗的時候發(fā)現自己被綠了筷弦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡抑诸,死狀恐怖烂琴,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蜕乡,我是刑警寧澤奸绷,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站层玲,受9級特大地震影響号醉,放射性物質發(fā)生泄漏反症。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一畔派、第九天 我趴在偏房一處隱蔽的房頂上張望铅碍。 院中可真熱鬧,春花似錦线椰、人聲如沸胞谈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烦绳。三九已至,卻和暖如春莱衩,著一層夾襖步出監(jiān)牢的瞬間爵嗅,已是汗流浹背娇澎。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工笨蚁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趟庄。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓括细,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戚啥。 傳聞我的和親對象是個殘疾皇子奋单,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內容

  • 煢煢女王踽獨行,戎馬倥傯卻誰知猫十。 半世伶俜空自許览濒,風雨間隙自傲然。
    燕子鴻閱讀 739評論 0 51
  • 文章的目的主要是針對面試官的提問拖云,做出盡可能精簡而全面的回答贷笛。若讀者對某塊的知識不能太理解,還請參閱其他大佬比較詳...
    紅茶瑪奇朵鴨閱讀 339評論 1 2
  • 親愛的家人們大家好 我是杭州佳能廣告工程部鄭剛宙项,今天是2018.10.28,星期日乏苦,是我第53天的日精進,給大家分...
    剛子_0662閱讀 156評論 0 0
  • 也許尤筐,進步不大汇荐。 可是,堅持就有希望盆繁。 或許吧掀淘,再努力點。 希望就在前方油昂。
    減肥的女孩閱讀 212評論 0 0