擴展點設(shè)計

02_擴展點設(shè)計

一晚树、業(yè)務(wù)舉例

供應(yīng)鏈的業(yè)務(wù)中译秦,一個業(yè)務(wù)流程涉及到多個節(jié)點两残,并且每個節(jié)點的實現(xiàn)邏輯不同裤纹,如下圖所示

image.png

每一個節(jié)點都可能存在不同的實現(xiàn)委刘,有時候需要從多個實現(xiàn)中選擇一個(互斥),有時候需要選擇多個(組合)鹰椒。如果不對各種實現(xiàn)進行良好的管理锡移,帶來的問題是:

  • 代碼圈復(fù)雜度高。if-else漆际,switch分支多罩抗,影響代碼主干流程。閱讀性差灿椅,新人學(xué)習(xí)成本高

  • 分支之間沒有做隔離套蒂,改了一個地方可能影響其他分支

  • 隨著時間推移,需求增多茫蛹,代碼越來越復(fù)雜操刀,慢慢形成祖?zhèn)鞔a,之前看到的一張圖婴洼,就比較好的形容這種祖?zhèn)鞔a

image.png

二骨坑、場景收集&分析

image.png
  1. 節(jié)點管理:節(jié)點管理本質(zhì)上就是代碼隔離,即將一個節(jié)點的不同實現(xiàn)分散到不同的類里面柬采。

  2. 互斥:不同分支實現(xiàn)相互隔離欢唾,根據(jù)條件選擇唯一的實現(xiàn)

  3. 組合:一個節(jié)點的多個實現(xiàn)同時執(zhí)行

  4. 優(yōu)先級管理:在組合模式下,調(diào)用節(jié)點的多個實現(xiàn)粉捻,但是實現(xiàn)有優(yōu)先級順序

  5. 中斷策略:在組合模式下礁遣,調(diào)用節(jié)點的多個實現(xiàn),根據(jù)節(jié)點返回結(jié)果判斷是否繼續(xù)向下執(zhí)行

三肩刃、方案調(diào)研

(一) Java SPI調(diào)研

針對于上一節(jié)中提到的節(jié)點多種實現(xiàn)的問題祟霍,Java的SPI可以解決我們的問題。

image.png

Java SPI使用約定:

1盈包、當(dāng)服務(wù)提供者提供了接口的一種具體實現(xiàn)后沸呐,在jar包的META-INF/services目錄下創(chuàng)建一個以“接口全限定名”為命名的文件,內(nèi)容為實現(xiàn)類的全限定名呢燥;

2崭添、接口實現(xiàn)類所在的jar包放在主程序的classpath中;

3叛氨、主程序通過java.util.ServiceLoder動態(tài)裝載實現(xiàn)模塊呼渣,它通過掃描META-INF/services目錄下的配置文件找到實現(xiàn)類的全限定名根暑,把類加載到JVM;

4徙邻、SPI的實現(xiàn)類必須攜帶一個不帶參數(shù)的構(gòu)造方法排嫌;

(二) Cola 框架 & Halo框架調(diào)研

擴展點(ExtensionPoint)必須通過接口申明,擴展實現(xiàn)(Extension)是通過Annotation的方式標(biāo)注的缰犁,Extension里面使用BizCode和TenantId兩個屬性用來標(biāo)識身份淳地,

框架的Bootstrap類會在Spring啟動的時候做類掃描,進行Extension注冊帅容,在Runtime的時候颇象,通過TenantContext來選擇要使用的Extension。TenantContext是通過Interceptor在調(diào)用業(yè)務(wù)邏輯之前進行初始化的并徘。整個過程如下圖所示:

image.png

擴展點實現(xiàn)路由

image.png

比如在一個CRM系統(tǒng)里遣钳,客戶要添加聯(lián)系人Contact是一個,但是在添加聯(lián)系人之前麦乞,我們要判斷這個Contact是不是已經(jīng)存在了蕴茴,如果存在那么就不能添加了。不過在一個支持多業(yè)務(wù)的系統(tǒng)里面姐直,可能每個業(yè)務(wù)的沖突檢查都不一樣倦淀,這是一個典型的可以擴展的場景。

那么在SOFA框架中声畏,我們可以這樣去做撞叽。

public interface ContactConflictRuleExtPt extends RuleI, ExtensionPointI {
   /**
    * 查詢聯(lián)系人沖突
    *
    * @param contact 沖突條件,不同業(yè)務(wù)會有不同的判斷規(guī)則
    * @return 沖突結(jié)果
    */
   public boolean queryContactConflict(ContactE contact);
}

2插龄、實現(xiàn)業(yè)務(wù)的擴展實現(xiàn)

@Extension(bizCode = BizCode.ICBU)
public class IcbuContactConflictRuleExt implements ContactConflictRuleExtPt {

    @Autowired
    private RepeatCheckServiceI repeatCheckService;
    @Autowired
    private MemberMappingQueryTunnel memberMappingQueryTunnel;
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 查詢聯(lián)系人沖突
     *
     * @param contact 沖突條件愿棋,不同業(yè)務(wù)會有不同的判斷規(guī)則
     * @return 沖突結(jié)果
     */
    @Override
    public boolean queryContactConflict(ContactE contact) {

        Set<String> emails = contact.getEmail();

        //具體的業(yè)務(wù)邏輯
        
        return false;
    }

3、在領(lǐng)域?qū)嶓w中調(diào)用擴展實現(xiàn)

@ToString
@Getter
@Setter
public class CustomerE extends Entity {
    /**
     * 公司ID
     */
    private String companyId;
    /**
     * 公司(客戶)名字
     */
    private String companyName;
    /**
     * 公司(客戶)英文名字
     */
    private String companyNameEn;
          /**
     * 給客戶添加聯(lián)系人
     * @param contact
     */
    public void addContact(ContactE contact,boolean checkConflict){
        // 業(yè)務(wù)檢查
        if (checkConflict) {
            ruleExecutor.execute(ContactConflictRuleExtPt.class, p -> p.queryContactConflict(contact));
        }
        contact.setCustomerId(this.getId());
        contactRepository.create(contact);
    }
}

(三) 我們對于擴展點的需求

image.png

cola擴展點的缺陷:

  • cola擴展點不支持組合場景

  • cola框架的Bootstrap類會在Spring啟動的時候做類掃描均牢,進行Extension注冊糠雨,在Runtime的時候,通過TenantContext(身份標(biāo)識信息)來選擇要使用的Extension膨处。TenantContext是通過Interceptor在調(diào)用業(yè)務(wù)邏輯之前進行初始化的见秤,在供應(yīng)鏈場景中,現(xiàn)在無法抽象出身份標(biāo)識信息真椿;或者執(zhí)行擴展點的時候傳參包含身份標(biāo)識信息,如果業(yè)務(wù)場景比較復(fù)雜乎澄,構(gòu)造身份標(biāo)識信息會比較麻煩突硝,因此考慮把擴展點的路由交個具體實現(xiàn)類處理,通過調(diào)用擴展點實現(xiàn)類的condition方案置济,判斷是否執(zhí)行該擴展點解恰,擴展點實現(xiàn):02_擴展點設(shè)計

三锋八、業(yè)務(wù)擴展點使用

1、xml配置

<context:component-scan base-package="com.sankuai.sjst"/>

2护盈、擴展點接口定義

擴展點必須以ExtPt結(jié)尾挟纱,通過ExtPt明顯標(biāo)識這是一個擴展點,擴展點實現(xiàn)類以Ext結(jié)尾

3、擴展點互斥場景實現(xiàn)

  • 定義業(yè)務(wù)擴展點接口
public interface AgreementGoodsBOBuilderExtPt extends ExtensionPointI<ScmIntelligentQueryGoodsContext, List<ScmPurchaseGoodsWithSuppliersBO>> {
}
  • 擴展點實現(xiàn)類-1
@Extension(name = "通過查詢主數(shù)據(jù)es索引構(gòu)建GoodsUnitBO")
public class AgreementGoodsBOBuilderByESQueryExt implements AgreementGoodsBOBuilderExtPt {
    @Resource
    private RemoteMainDataQueryService remoteMainDataQueryService;

    @Override
    public boolean condition(ScmIntelligentQueryGoodsContext context) {

        ScmIntelligentQueryGoodsConditionTO queryGoodsConditionTO = context.getQueryGoodsConditionTO();
        // GoodsUnitTO為空 且goodsIds不存在
        return queryGoodsConditionTO.getGoodsUnitTO() == null && queryGoodsConditionTO.getGoodsIdsSize() == 0;
    }

    @Override
    public List<ScmPurchaseGoodsWithSuppliersBO> invoke(ScmIntelligentQueryGoodsContext context) {
         // 業(yè)務(wù)邏輯
    }
}
  • 擴展點實現(xiàn)類-2
@Extension(name = "通過goodsIds參數(shù)構(gòu)建GoodsUnitBO")
public class AgreementGoodsBOBuilderByGoodsIdsExt implements AgreementGoodsBOBuilderExtPt {
    // spring 依賴注入
    @Resource
    private RemoteBaseService remoteBaseService;

    @Override
    public boolean condition(ScmIntelligentQueryGoodsContext context) {
        List<Long> goodsIds = context.getQueryGoodsConditionTO().getGoodsIds();
        return CollectionUtils.isNotEmpty(goodsIds);
    }

    @Override
    public List<ScmPurchaseGoodsWithSuppliersBO> invoke(ScmIntelligentQueryGoodsContext context) {
        // 業(yè)務(wù)邏輯
    }
}
  • 調(diào)用擴展點
List<ScmPurchaseGoodsWithSuppliersBO> purchaseAgreementGoodsBOs = extensionExecutor.execute(AgreementGoodsBOBuilderExtPt.class, context);

4腐宋、擴展點組合+優(yōu)先級管理 + 中斷策略實現(xiàn)

  • 擴展點接口定義
/**
 * 智能采購物品協(xié)議校驗擴展點
 */
public interface IntelligentPurchaseGoodsAgreementCheckExtPt extends ExtensionPointI<ScmIntelligentPurchaseCheckContext, ErrorItemAndStatus> {
}
  • 擴展點實現(xiàn)-1
@Order(1)
@Extension(name = "智能采購-配送中心配送物品校驗")
public class DistributionGoodsAgreementCheckExtPt implements IntelligentPurchaseGoodsAgreementCheckExtPt {
    @Resource
    private ScmSupplierCheckService scmSupplierCheckService;

    @Override
    public boolean condition(ScmIntelligentPurchaseCheckContext context) {
        return CollectionUtils.isNotEmpty(context.getGoodsAndDistributionOrgBOs());
    }

    @Override
    public ErrorItemAndStatus invoke(ScmIntelligentPurchaseCheckContext checkContext) {
        // 業(yè)務(wù)邏輯
        return new ErrorItemAndStatus();
    }
}
  • 擴展點實現(xiàn)-2
@Order(2)
@Extension(name = "智能采購-供應(yīng)商采購物品校驗")
public class SupplierGoodsAgreementCheckExtPt implements IntelligentPurchaseGoodsAgreementCheckExtPt {
    @Override
    public boolean condition(ScmIntelligentPurchaseCheckContext context) {
        return CollectionUtils.isNotEmpty(context.getGoodsAndSupplierBOs());
    }

    @Override
    public ErrorItemAndStatus invoke(ScmIntelligentPurchaseCheckContext context) {
        // 業(yè)務(wù)邏輯
        return new ErrorItemAndStatus();
    }
}

  • 擴展點執(zhí)行
// 智能采購-配送物品協(xié)議校驗
List<ErrorItemAndStatus> errorItemAndStatuses =
        extensionExecutor.multiExecute(
                IntelligentPurchaseGoodsAgreementCheckExtPt.class,//擴展點接口
                intelligentPurchaseCheckContext,// 參數(shù)
                errorItemAndStatus -> ThriftStatusHelper.iserrorItemAndStatus.getStatus()));// 中斷策略

四紊服、業(yè)務(wù)擴展點原理

(一)、原理

image.png
  • spring在容器在啟動的時候胸竞,會調(diào)用getBean方法實例化&初始化對象
public void refresh() throws BeansException, IllegalStateException {

        synchronized (this.startupShutdownMonitor) {

            // Prepare this context for refreshing.

            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.

            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.

            prepareBeanFactory(beanFactory);

            try {

                // Allows post-processing of the bean factory in context subclasses.

                postProcessBeanFactory(beanFactory);

                // 調(diào)用 factory processors registered as beans in the context.

                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.

                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.

                initMessageSource();

                // Initialize event multicaster for this context.

                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.

                onRefresh();

                // Check for listener beans and register them.

                registerListeners();

                // 初始化所有單例對象

                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.

                finishRefresh();

            }catch (BeansException ex) {

                if (logger.isWarnEnabled()) {

                    logger.warn("Exception encountered during context initialization - " +

                            "cancelling refresh attempt: " + ex);

                }

                // Destroy already created singletons to avoid dangling resources.

                destroyBeans();

                // Reset 'active' flag.

                cancelRefresh(ex);

                // Propagate exception to caller.

                throw ex;

            }finally {

                // Reset common introspection caches in Spring's core, since we

                // might not ever need metadata for singleton beans anymore...

                resetCommonCaches();

            }

        }

    }
  • 初始化過程中會執(zhí)行spring開發(fā)出來的擴展點欺嗤,我們的業(yè)務(wù)擴展點框架實現(xiàn)了BeanPostProcessor接口,判斷對象的class是否有Extension注解卫枝,如果存在組件煎饼,將對象添加到ExtensionRepository中,其內(nèi)部接口是Map<String, List<ExtensionPointI>>結(jié)果校赤,key是擴展點接口的類名稱吆玖,value是實現(xiàn)類列表

  • 當(dāng)要執(zhí)行擴展點時,通過調(diào)用ExtensionExecutor.execute方法马篮,實現(xiàn)選擇一個擴展點實現(xiàn)類衰伯,來進行調(diào)用;調(diào)用ExtensionExecutor.multiExecute方法积蔚,按擴展點實現(xiàn)類的優(yōu)先級先后進行調(diào)用意鲸,如果設(shè)置了中斷策略,在執(zhí)行下一個擴展點實現(xiàn)類之前會先判斷是否中斷

(二)尽爆、核心模型

1怎顾、擴展點接口:

/**
 * ExtensionPointI is the parent interface of all ExtensionPoints
 * 擴展點表示一塊邏輯在不同的業(yè)務(wù)有不同的實現(xiàn),使用擴展點做接口申明漱贱,然后用Extension(擴展)去實現(xiàn)擴展點槐雾。
 *
 * @author heyong04
 */
public interface ExtensionPointI<T, R> {

    /**
     * 是否執(zhí)行當(dāng)前實現(xiàn)的條件
     *
     * @param context 調(diào)用上下文
     * @return 是否滿足條件
     */
    boolean condition(T context);

    /**
     * 擴展點實現(xiàn)的具體操作
     *
     * @param context 調(diào)用上下文
     * @return 執(zhí)行結(jié)果
     */
    R invoke(T context);
}

2、擴展點注解

用在擴展點實現(xiàn)類上幅狮,使用該注解募强,會將實現(xiàn)類注入到spring容器中

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface Extension {
    String name() default "";
}

3、Spring BeanPostProcessor擴展點實現(xiàn)

package com.sankuai.sjst.scm.extension.register;

import com.sankuai.sjst.scm.constant.ExtensionConstant;
import com.sankuai.sjst.scm.exception.ExtensionException;
import com.sankuai.sjst.scm.extension.Extension;
import com.sankuai.sjst.scm.extension.ExtensionPointI;
import com.sankuai.sjst.scm.extension.RegisterI;
import com.sankuai.sjst.scm.extension.repository.ExtensionRepository;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 * ExtensionRegister
 *
 * @author heyong
 */
@Component
public class ExtensionRegister implements RegisterI, BeanPostProcessor {
    // 防止bean重復(fù)添加到ExtensionRepository
    private static final ConcurrentSkipListSet<String> EXTENSION_BEAN_NAME_SET = new ConcurrentSkipListSet<>();

    @Autowired
    private ExtensionRepository extensionRepository;

    @Override
    public void doRegistration(Class<?> clazz, ExtensionPointI extensionPointI) {
        Class<? extends ExtensionPointI> extPtClass = calculateExtensionPoint(clazz);
        extensionRepository.put(extPtClass, extensionPointI);
    }

    /**
     * @param targetClz 子類
     * @return
     */
    private Class<? extends ExtensionPointI> calculateExtensionPoint(Class<?> targetClz) {

        Class[] interfaces = targetClz.getInterfaces();
        if (ArrayUtils.isEmpty(interfaces)) {
            throw new ExtensionException("Please assign a extension point interface for " + targetClz);
        }

        for (Class iface : interfaces) {
            String extensionPoint = iface.getSimpleName();
            if (StringUtils.contains(extensionPoint, ExtensionConstant.EXTENSION_EXTPT_NAMING)) {
                return iface;
            }
        }
        throw new ExtensionException("Your name of ExtensionPoint for " + targetClz + " is not valid, must be end of " + ExtensionConstant.EXTENSION_EXTPT_NAMING);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 已經(jīng)處理過的擴展點類崇摄,不需要處理
        if (EXTENSION_BEAN_NAME_SET.contains(beanName)) {
            return bean;
        }

        Class<?> targetClass = AopUtils.getTargetClass(bean);
        Extension extension = AnnotationUtils.findAnnotation(targetClass, Extension.class);
        if (Objects.nonNull(extension)) {
            EXTENSION_BEAN_NAME_SET.add(beanName);
            doRegistration(targetClass, (ExtensionPointI) bean);
        }
        return bean;
    }

}

4擎值、擴展點執(zhí)行器

/**
 * <p>擴展點抽象執(zhí)行器</p>
 *
 * @author heyong04@meituan.com
 * @version AbstractComponentExecutor.class 2020-09-14 上午11:33
 * @since 1.0.0
 **/
public abstract class AbstractComponentExecutor {

    /**
     * Execute extension with Response
     *
     * @param targetClz 擴展點接口定義
     * @param context   擴展點上下文信息
     * @param <R>       擴展點接口入?yún)㈩愋?     * @param <T>       擴展點接口出參類型
     * @return 執(zhí)行結(jié)果
     */
    public <R, T> R execute(Class<? extends ExtensionPointI<T, R>> targetClz, T context) {
        ExtensionPointI extensionPointI = locateComponent(targetClz, context);
        return (R) extensionPointI.invoke(context);
    }

    /**
     * Multi Execute extension with Response
     *
     * @param targetClz 擴展點接口
     * @param context   擴展點上下文信息
     * @param <R>       擴展點接口入?yún)㈩愋?     * @param <T>       擴展點接口出參類型
     * @return 執(zhí)行結(jié)果, 使用list包裝了每個擴展點實現(xiàn)的返回值
     */
    public <R, T> List<R> multiExecute(Class<? extends ExtensionPointI<T, R>> targetClz, T context) {
        return multiExecute(targetClz, context, new DefaultInterruptionStrategy<>());
    }

    /**
     * Multi Execute extension with Response
     *
     * @param targetClz            擴展點接口
     * @param context              擴展點上下文信息
     * @param <R>                  擴展點接口入?yún)㈩愋?     * @param <T>                  擴展點接口出參類型
     * @param interruptionStrategy 中斷策略
     * @return 執(zhí)行結(jié)果, 使用list包裝了每個擴展點實現(xiàn)的返回值
     */
    public <R, T> List<R> multiExecute(Class<? extends ExtensionPointI<T, R>> targetClz, T context, InterruptionStrategy<R> interruptionStrategy) {
        List<ExtensionPointI> extensionPointIs = locateComponents(targetClz, context);

        List<R> combinationResult = Lists.newArrayListWithExpectedSize(extensionPointIs.size());
        for (ExtensionPointI extensionPointI : extensionPointIs) {
            R result = (R) extensionPointI.invoke(context);
            combinationResult.add(result);
            if (interruptionStrategy.interrupt(result)) {
                return combinationResult;
            }
        }

        return combinationResult;
    }

    /**
     * 加載擴展實現(xiàn)
     *
     * @param targetClz 擴展點接口
     * @param context   擴展點上下文信息
     * @param <T>       擴展點接口入?yún)㈩愋?     * @param <R>       擴展點接口出參類型
     * @return 擴展點實現(xiàn)
     */
    abstract <T, R> ExtensionPointI locateComponent(Class<? extends ExtensionPointI<T, R>> targetClz, T context);

    /**
     * 加載多個擴展點實現(xiàn)
     *
     * @param <T>       擴展點接口入?yún)㈩愋?     * @param <R>       擴展點接口出參類型
     * @param targetClz 擴展點接口
     * @param context   擴展點接口入?yún)?     * @return 擴展點實現(xiàn)列表
     */
    abstract <T, R> List<ExtensionPointI> locateComponents(Class<? extends ExtensionPointI<T, R>> targetClz, T context);
}

5、中斷策略

/**
 * <p>擴展點執(zhí)行中斷策略接口</p>
 *
 * @author heyong04@meituan.com
 * @version InterruptionStrategy.class 2020-09-14 上午11:33
 * @since 1.0.0
 **/
public interface InterruptionStrategy<R> {
    /**
     * 是否中斷執(zhí)行
     *
     * @param extensionPointResult 擴展點執(zhí)行返回結(jié)果
     * @return
     */
    boolean interrupt(R extensionPointResult);
}

五逐抑、使用規(guī)范

image.png

六鸠儿、擴展點相對于策略模式優(yōu)勢

1、基于Strategy Pattern的擴展,沒有找到一個很好的固化到框架中的方法

2进每、使用Strategy Pattern汹粤,沒有規(guī)范的限制,編碼相對隨意

七田晚、參考文檔

https://blog.csdn.net/significantfrank/article/details/85785565

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘱兼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贤徒,更是在濱河造成了極大的恐慌芹壕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泞莉,死亡現(xiàn)場離奇詭異哪雕,居然都是意外死亡,警方通過查閱死者的電腦和手機鲫趁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門斯嚎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挨厚,你說我怎么就攤上這事堡僻。” “怎么了疫剃?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵钉疫,是天一觀的道長。 經(jīng)常有香客問我巢价,道長牲阁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任壤躲,我火速辦了婚禮城菊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碉克。我一直安慰自己凌唬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布漏麦。 她就那樣靜靜地躺著客税,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撕贞。 梳的紋絲不亂的頭發(fā)上更耻,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音麻掸,去河邊找鬼酥夭。 笑死,一個胖子當(dāng)著我的面吹牛脊奋,可吹牛的內(nèi)容都是我干的熬北。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诚隙,長吁一口氣:“原來是場噩夢啊……” “哼讶隐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起久又,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤巫延,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后地消,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炉峰,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年脉执,在試婚紗的時候發(fā)現(xiàn)自己被綠了疼阔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡半夷,死狀恐怖婆廊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巫橄,我是刑警寧澤淘邻,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站湘换,受9級特大地震影響宾舅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彩倚,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一筹我、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧署恍,春花似錦崎溃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呼巷,卻和暖如春囱修,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背王悍。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工破镰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓鲜漩,卻偏偏與公主長得像源譬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子孕似,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355